Skip to content
This repository has been archived by the owner on Mar 24, 2024. It is now read-only.

Commit

Permalink
Replace ts-prune with ts-unused-exports (#6487)
Browse files Browse the repository at this point in the history
**User-Facing Changes**
None

**Description**
ts-prune has gone into maintenance mode and is using some old
dependencies that block us from using new TS features like `satisfies`
(nadeesha/ts-prune#218).

Also considered [`unimported`](https://github.com/smeijer/unimported)
but it seems nice to have the ability to invoke it a library.

https://npmtrends.com/ts-prune-vs-ts-unused-exports-vs-unimported

This PR also includes a patch to `depcheck` to get `satisfies` support
(the issues have already been fixed on main but it hasn't been released
yet): depcheck/depcheck#788
  • Loading branch information
jtbandes committed Jul 21, 2023
1 parent d56e2e4 commit 8171ae1
Show file tree
Hide file tree
Showing 62 changed files with 242 additions and 537 deletions.
85 changes: 48 additions & 37 deletions ci/lint-unused-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,65 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import { info } from "@actions/core";

import { execOutput } from "./exec";
import path from "path";
import tsUnusedExports from "ts-unused-exports";

// Identify unused exports
//
// An export is considered unused if it is never imported in any source file.
//
// Note: use the "// ts-prune-ignore-next" comment above an export if you would like to mark it
// Note: use the "// ts-unused-exports:disable-next-line" comment above an export if you would like to mark it
// as used even though it appears unused. This might happen for exports which are injected via webpack.
async function main(): Promise<void> {
const { stdout, status } = await execOutput(
"ts-prune",
const results = tsUnusedExports(path.join(__dirname, "../packages/studio-base/tsconfig.json"), [
"--findCompletelyUnusedFiles",
"--ignoreLocallyUsed",
]);
const ignorePathsRegex = new RegExp(
[
"-p",
"packages/studio-base/tsconfig.json",
"--error",
"--ignore",
[
String.raw`used in module`,
String.raw`^packages/(hooks|den|mcap|mcap-support)/`,
String.raw`/studio/src/index\.ts`,
String.raw`/studio-base/src/index\.ts`,
String.raw`/studio-base/src/stories/`,
String.raw`/studio-base/src/test/`,
String.raw`/studio-base/src/i18n/`, // Doesn't work due to use of `export *` & `import *`
String.raw`/ThreeDeeRender/transforms/index\.ts`,
String.raw`/nodeTransformerWorker/typescript/userUtils`,
String.raw`\.stories\.ts`,
String.raw`/\.storybook/|/storySupport/`,
].join("|"),

// --skip means don't consider exports used if they are only used in these files
"--skip",
String.raw`\.test\.ts|\.stories\.tsx`,
],
{ ignoreReturnCode: true },
String.raw`\.stories\.tsx?$`,
String.raw`packages/studio-base/src/index\.ts`,
String.raw`packages/studio-base/src/panels/ThreeDeeRender/transforms/index\.ts`, // `export *` is not correctly analyzed <https://github.com/pzavolinsky/ts-unused-exports/issues/286>
String.raw`packages/studio-base/src/test/`,
String.raw`packages/studio-base/src/players/UserNodePlayer/nodeTransformerWorker/typescript/userUtils/`,
].join("|"),
);
const lines = stdout.trim().split("\n");
for (const line of lines) {
// cf. https://github.com/nadeesha/ts-prune/blob/45c6be7e8db44f2421cc57b52e50e8ea4e402782/src/presenter.ts#L8
const match = /^(.+):(\d+) - (.+)$/.exec(line);
if (match && (process.env.CI ?? "") !== "") {
info(`::error file=${match[1]},line=${match[2]}::Unused export ${match[3]} in ${match[1]}`);
} else {
info(line);

const repoRootPath = path.resolve(__dirname, "..");
let hasUnusedExports = false;
for (const [filePath, items] of Object.entries(results)) {
if (filePath === "unusedFiles") {
continue;
}
const pathFromRepoRoot = path.relative(repoRootPath, filePath);
if (ignorePathsRegex.test(pathFromRepoRoot)) {
continue;
}
for (const item of items) {
// In reality, sometimes item.location is undefined
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (item.location == undefined) {
info(
`::error file=${pathFromRepoRoot}::Unused export ${item.exportName} in ${pathFromRepoRoot}`,
);
} else {
info(
`::error file=${pathFromRepoRoot},line=${item.location.line},col=${item.location.character}::Unused export ${item.exportName} in ${pathFromRepoRoot}:${item.location.line}:${item.location.character}`,
);
}
hasUnusedExports = true;
}
}

for (const filePath of results.unusedFiles ?? []) {
const pathFromRepoRoot = path.relative(repoRootPath, filePath);
if (ignorePathsRegex.test(pathFromRepoRoot)) {
continue;
}
info(`::error file=${pathFromRepoRoot}::Unused file ${pathFromRepoRoot}`);
hasUnusedExports = true;
}
process.exit(status);
process.exit(hasUnusedExports ? 1 : 0);
}

if (require.main === module) {
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
"@types/node": "15.3.0",
"react-dom": "patch:react-dom@npm:17.0.2#./patches/react-dom.patch",
"react-use": "patch:react-use@17.4.0#./patches/react-use.patch",
"react-dnd": "patch:react-dnd@npm:16.0.1#./patches/react-dnd.patch"
"react-dnd": "patch:react-dnd@npm:16.0.1#./patches/react-dnd.patch",
"depcheck/@babel/parser": "^7.20.0"
},
"devDependencies": {
"@actions/core": "1.10.0",
Expand Down Expand Up @@ -90,7 +91,7 @@
"@typescript-eslint/parser": "5.62.0",
"babel-plugin-transform-import-meta": "2.2.0",
"cross-env": "7.0.3",
"depcheck": "1.4.3",
"depcheck": "patch:depcheck@npm:1.4.3#./patches/depcheck.patch",
"electron": "25.3.1",
"electron-builder": "24.6.2",
"eslint": "8.45.0",
Expand All @@ -114,7 +115,7 @@
"semver": "7.5.4",
"storybook": "7.1.0",
"ts-node": "10.9.1",
"ts-prune": "0.10.3",
"ts-unused-exports": "9.0.5",
"tslib": "2.6.0",
"typescript": "5.1.6",
"webpack": "5.88.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,5 @@ const AppBarDropdownButton = forwardRef<HTMLButtonElement, Props>((props, ref) =
});
AppBarDropdownButton.displayName = "AppBarDropdownButton";

// ts-unused-exports:disable-next-line
export { AppBarDropdownButton };
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default {
const canvas = within(canvasElement);
await userEvent.hover(await canvas.findByTestId(args.testId));
},
} as Meta<StoryArgs>;
} satisfies Meta<StoryArgs>;

// Connection
const playerSelection: PlayerSelection = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default {
);
},
],
} as Meta<StoryArgs>;
} satisfies Meta<StoryArgs>;

type Story = StoryObj<StoryArgs>;

Expand Down
6 changes: 1 addition & 5 deletions packages/studio-base/src/components/AppBar/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,13 @@ export default {
component: AppBar,
decorators: [StorybookDecorator],
args: {
signIn: action("signIn"),
onSelectDataSourceAction: action("onSelectDataSourceAction"),
onMinimizeWindow: action("onMinimizeWindow"),
onMaximizeWindow: action("onMaximizeWindow"),
onUnmaximizeWindow: action("onUnmaximizeWindow"),
onCloseWindow: action("onCloseWindow"),
prefsDialogOpen: false,
setPrefsDialogOpen: action("setPrefsDialogOpen"),
},
parameters: { colorScheme: "both-column" },
} as Meta<typeof AppBar>;
} satisfies Meta<typeof AppBar>;

type Story = StoryObj<typeof AppBar>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ const useStyles = makeStyles()((theme) => ({

type SectionKey = "resources" | "products" | "contact" | "legal";

export const aboutItems: Map<
const aboutItems: Map<
SectionKey,
{
subheader: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default {
</Stack>
),
],
} as Meta<typeof Autocomplete>;
} satisfies Meta<typeof Autocomplete>;

type Story = StoryObj<typeof Autocomplete>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default {
parameters: {
colorScheme: "light",
},
} as Meta<typeof CreateEventDialog>;
} satisfies Meta<typeof CreateEventDialog>;

export const Empty: StoryObj = {};

Expand Down
2 changes: 1 addition & 1 deletion packages/studio-base/src/components/JsonInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type ParseAndStringifyFn = {
stringify: (obj: unknown) => string;
parse: (val: string) => unknown;
};
export type BaseProps = {
type BaseProps = {
dataTestId?: string;
dataValidator?: (data: unknown) => ValidationResult | undefined;
onChange?: OnChange;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
import { MessageEvent } from "@foxglove/studio-base/players/types";
import { Fixture } from "@foxglove/studio-base/stories/PanelSetup";
import { RosDatatypes } from "@foxglove/studio-base/types/RosDatatypes";
// ts-prune-ignore-next

export const datatypes: RosDatatypes = new Map(
Object.entries({
"some/datatype": { definitions: [{ name: "index", type: "int32" }] },
}),
);

// ts-prune-ignore-next
export const messages = Object.freeze<MessageEvent[]>([
{
topic: "/some/topic",
Expand All @@ -46,7 +45,6 @@ export const messages = Object.freeze<MessageEvent[]>([
},
]);

// ts-prune-ignore-next
export const MessagePathInputStoryFixture: Fixture = {
datatypes: new Map(
Object.entries({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
PlayerPresence,
} from "@foxglove/studio-base/players/types";

// ts-prune-ignore-next
export default class FakePlayer implements Player {
#listener?: (arg0: PlayerState) => Promise<void>;
public playerId: string = "test";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,4 @@ function MockPanelContextProvider({
);
}

// ts-prune-ignore-next
export default MockPanelContextProvider;
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default {
}
args.events?.map(async (keypress) => await keyboard(keypress));
},
} as Meta<Args>;
} satisfies Meta<Args>;

type Story = StoryObj<Args>;

Expand Down

0 comments on commit 8171ae1

Please sign in to comment.