Skip to content
This repository has been archived by the owner on Dec 17, 2023. It is now read-only.

Commit

Permalink
feat(core): Add config option to skip files considered for imports (#140
Browse files Browse the repository at this point in the history
)

* Add option to skip src files with pattern

* improve skip option explanation

Co-authored-by: Nadeesha Cabral <[email protected]>

* Improve explanation of skip opt in readme

* Refactor getPotentiallyUnused

* Add an integration test for skip pattern

* Fix integration test for skip pattern

Co-authored-by: Nadeesha Cabral <[email protected]>
  • Loading branch information
sauntimo and nadeesha authored Aug 23, 2021
1 parent 8a35f28 commit 8a1b2f8
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 29 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ Thumbs.db
*.log
package-lock.json
.eslintcache
./integration/testproject/outfile # this will be generated by integration tests
integration/testproject/outfile # this will be generated by integration tests
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ ts-prune supports CLI and file configuration via [cosmiconfig](https://github.co
- `-p, --project` - __tsconfig.json__ path(`tsconfig.json` by default)
- `-i, --ignore` - errors ignore RegExp pattern
- `-e, --error` - return error code if unused exports are found
- `-s, --skip` - skip these files when determining whether code is used. (For example, `*.test.ts?` will stop ts-prune from considering an export in test file usages)

CLI configuration options:
```bash
Expand Down
1 change: 1 addition & 0 deletions integration/outfile.base
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ src/internal-uses.ts:13 - Row (used in module)
src/internal-uses.ts:17 - UnusedProps
src/dynamic/fail.ts:1 - foo
src/dynamic/fail.ts:3 - bar
src/skipPattern/foo.ts:1 - foo
src/wildcard/foo.ts:5 - vUnused
4 changes: 2 additions & 2 deletions integration/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ step "Linking ts-prune from step 1"
npm link ts-prune

step "Run ts-prune"
ts-prune | tee outfile
ts-prune --skip ".test.ts" | tee outfile

step "Diff between outputs"
DIFF=$(diff outfile ../outfile.base)
Expand Down Expand Up @@ -48,7 +48,7 @@ fi

step "Cleanup"
rm ../../package-lock.json # remnants of the npm link
rm ./integration/testproject/outfile # generated outfile
rm outfile # generated outfile

echo "🏁"
exit $EXIT_CODE
15 changes: 0 additions & 15 deletions integration/testproject/outfile

This file was deleted.

7 changes: 7 additions & 0 deletions integration/testproject/src/skipPattern/foo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { foo } from "./foo";

describe("foo", () => {
it("should return false", () => {
expect(foo()).toBeFalsy;
})
})
1 change: 1 addition & 0 deletions integration/testproject/src/skipPattern/foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo = () => false;
36 changes: 36 additions & 0 deletions src/analyzer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,27 @@ export function UseFoo(foo: string) {
}
`;

const barSrc = `
export const bar = () => false;
`;

const testBarSrc = `
import { bar } from './bar';
describe("bar", () => {
it("should return false", () => {
expect(bar()).toBe.toBeFalsy;
});
});
`;

describe("analyzer", () => {
const project = new Project();
const foo = project.createSourceFile("/project/foo.ts", fooSrc);
const useFoo = project.createSourceFile("/project/use-foo.ts", useFooSrc);
const star = project.createSourceFile("/project/star.ts", starImportSrc);
const bar = project.createSourceFile("/project/bar.ts", barSrc);
const testBar = project.createSourceFile("/project/bar.test.ts", testBarSrc);

it("should track import wildcards", () => {
// TODO(danvk): rename this to importSideEffects()
Expand Down Expand Up @@ -70,6 +86,26 @@ describe("analyzer", () => {
});
});

it("should not skip source files without a pattern", () => {
// while bar.test.ts is included, bar is used
expect(getPotentiallyUnused(bar)).toEqual({
file: "/project/bar.ts",
symbols: [],
type: 0,
});
});

it("should skip source files matching a pattern", () => {
// when bar.test.ts is exclude by the skip pattern, bar is unused
expect(getPotentiallyUnused(bar, ".test.ts")).toEqual({
file: "/project/bar.ts",
symbols: [
{ line: 2, name: "bar", usedInModule: false },
],
type: 0,
});
});

it("should track usage through star imports", () => {
const importNode = star.getFirstDescendantByKindOrThrow(
ts.SyntaxKind.ImportDeclaration
Expand Down
32 changes: 23 additions & 9 deletions src/analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { getNodesOfKind } from './util/getNodesOfKind';
import countBy from "lodash/fp/countBy";
import last from "lodash/fp/last";
import { realpathSync } from "fs";
import { IConfigInterface } from "./configurator";

type OnResultType = (result: IAnalysedResult) => void;

Expand Down Expand Up @@ -191,26 +192,39 @@ const getDefinitelyUsed = (file: SourceFile): IAnalysedResult[] => ([
...exportWildCards(file),
]);

export const getPotentiallyUnused = (file: SourceFile): IAnalysedResult => {
const getReferences = (
originalList: SourceFileReferencingNodes[],
skipPattern?: string
): SourceFileReferencingNodes[] => {
if(skipPattern){
const regExp = new RegExp(skipPattern);
return originalList.filter(file =>
!regExp.test(file.getSourceFile().compilerNode.fileName)
);
}
return originalList;
}
export const getPotentiallyUnused = (file: SourceFile, skipPattern?: string): IAnalysedResult => {
const exported = getExported(file);

const idsInFile = file.getDescendantsOfKind(ts.SyntaxKind.Identifier);
const referenceCounts = countBy(x => x)((idsInFile || []).map(node => node.getText()));
const referencedInFile = Object.entries(referenceCounts)
.reduce(
(previous, [name, count]) => previous.concat(count > 1 ? [name] : []),
(previous, [name, count]) => previous.concat(count > 1 ? [name] : []),
[]
);

const referenced = file
.getReferencingNodesInOtherSourceFiles()
.reduce(
const referenced = getReferences(
file.getReferencingNodesInOtherSourceFiles(),
skipPattern
).reduce(
(previous, node: SourceFileReferencingNodes) => {
const kind = node.getKind().toString();
const value = nodeHandlers?.[kind]?.(node) ?? [];

return previous.concat(value);
},
},
[]
);

Expand All @@ -232,10 +246,10 @@ const emitTsConfigEntrypoints = (entrypoints: string[], onResult: OnResultType)
type: AnalysisResultTypeEnum.DEFINITELY_USED,
})).forEach(emittable => onResult(emittable))

export const analyze = (project: Project, onResult: OnResultType, entrypoints: string[]) => {
export const analyze = (project: Project, onResult: OnResultType, entrypoints: string[], skipPattern?: string) => {
project.getSourceFiles().forEach(file => {
[
getPotentiallyUnused(file),
getPotentiallyUnused(file, skipPattern),
...getDefinitelyUsed(file),
].forEach(result => {
if (!result.file) return // Prevent passing along a "null" filepath. Fixes #105
Expand Down
5 changes: 4 additions & 1 deletion src/configurator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ export interface IConfigInterface {
project?: string;
ignore?: string;
error?: string;
skip?: string;
}

const defaultConfig: IConfigInterface = {
project: "tsconfig.json",
ignore: undefined,
error: undefined,
skip: undefined,
}

const onlyKnownConfigOptions = pick(Object.keys(defaultConfig));
Expand All @@ -23,6 +25,7 @@ export const getConfig = () => {
.option('-p, --project [project]', 'TS project configuration file (tsconfig.json)', 'tsconfig.json')
.option('-i, --ignore [regexp]', 'Path ignore RegExp pattern')
.option('-e, --error', 'Return error code if unused exports are found')
.option('-s, --skip [regexp]', 'skip these files when determining whether code is used')
.parse(process.argv))

const defaultConfig = {
Expand All @@ -40,4 +43,4 @@ export const getConfig = () => {
};

return config;
}
}
2 changes: 1 addition & 1 deletion src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const run = (config: IConfigInterface, output = console.log) => {

const state = new State();

analyze(project, state.onResult, entrypoints);
analyze(project, state.onResult, entrypoints, config.skip);

const presented = present(state);

Expand Down

0 comments on commit 8a1b2f8

Please sign in to comment.