Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ignoreLocallyUsed Flag #282

Merged
merged 1 commit into from
Jun 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [9.0.5] - 23 Jun 2023

- Add --ignoreLocallyUsed flag which means that exports which are used in the same file they are defined in won't be reported as unused

## [9.0.4] - 12 Feb 2023

### Changed
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ ts-unused-exports path/to/tsconfig.json [file1.ts ...] [options]
| `ignoreFiles` | Ignore files with filenames that match the given regex. Use this to exclude groups of files - for example test files and their utilities. | `--ignoreFiles=.*spec` |
| `ignoreProductionFiles` | Only scan **test** files (so ignore non-test 'production' files). | `--ignoreProductionFiles` |
| `ignoreTestFiles` | Only scan **production** files (ignore all test files, like `spec.ts(x)` or `test.ts(x)` or `TestUtils.ts`). Use this to detect production code that is only used in tests (so is dead code). Note: this will NOT detect unused exports in test code - for that, you can run `ts-unused-exports` separately with the `--ignoreProductionFiles` option. | `--ignoreTestFiles` |
| `ignoreLocallyUsed` | Exports which are used in the same file they are defined in won't be reported as unused. Note that this may have an impact on performance in larger codebases. | `--ignoreLocallyUsed` |
| `maxIssues` | Return successfully for up to a given number of modules with unused exports. | `--maxIssues=7` |
| `searchNamespaces` | Enable searching for unused exports within namespaces. Note: this can affect performance on large codebases. | `--searchNamespaces` |
| `showLineNumber` | Show the line number and column of the unused export. | `--showLineNumber` |
Expand Down
42 changes: 42 additions & 0 deletions features/ignore-locally-used.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Feature: ignoreLocallyUsed option

Scenario: IgnoreLocallyUsed on
Given file "exports.ts" is
"""
export const a = 1;
export const b = a + 1;
"""
And file "import.ts" is import {b} from "./exports";
When running ts-unused-exports "tsconfig.json" --ignoreLocallyUsed
Then the CLI result at status is 0

Scenario: IgnoreLocallyUsed off
Given file "exports.ts" is
"""
export const a = 1;
export const b = a + 1;
"""
And file "import.ts" is import {b} from "./exports";
When running ts-unused-exports "tsconfig.json"
Then the CLI result at status is 1
And the CLI result at stdout contains "exports.ts: a"

Scenario: Used locally in a namespace
Given file "namespace.ts" is
"""
export const a = 1;
namespace foo {
const b = a + 1;
}
"""
When running ts-unused-exports "tsconfig.json" --ignoreLocallyUsed
Then the CLI result at status is 0

Scenario: ignoreLocallyUsed works with more complex file extensions
Given file "local.test.ts" is
"""
export const a = 1;
const b = a + 1;
"""
When running ts-unused-exports "tsconfig.json" --ignoreLocallyUsed
Then the CLI result at status is 0
4 changes: 4 additions & 0 deletions src/argsParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ function processOptions(
ignoreFilesRegex.push(`(spec|test|Test)`);
}
break;
case '--ignoreLocallyUsed': {
newOptions.ignoreLocallyUsed = true;
break;
}
case '--maxIssues':
{
newFilesAndOptions.options = {
Expand Down
4 changes: 4 additions & 0 deletions src/parser/namespaceBlacklist.ts → src/parser/blacklists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,7 @@ export const namespaceBlacklist: ReadonlyArray<ts.SyntaxKind> = [
ts.SyntaxKind.FirstJSDocTagNode,
ts.SyntaxKind.LastJSDocTagNode,
];

export const ignoreLocalBlacklist = namespaceBlacklist.filter(
(kind) => kind !== ts.SyntaxKind.Identifier,
);
2 changes: 1 addition & 1 deletion src/parser/dynamic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
recurseIntoChildren,
} from './util';

import { namespaceBlacklist } from './namespaceBlacklist';
import { namespaceBlacklist } from './blacklists';

// Parse Dynamic Imports

Expand Down
5 changes: 3 additions & 2 deletions src/parser/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ export const extractExportFromImport = (
extractPropertyOrAliasFromBindings(exportClause)
: STAR;

const from = getFrom(moduleSpecifier);
return {
exported: {
from: getFrom(moduleSpecifier),
from,
what: whatExported,
},
imported: {
from: getFrom(moduleSpecifier),
from,
what: whatImported,
isExportStarAs: exportClause?.kind === ts.SyntaxKind.NamespaceExport,
},
Expand Down
2 changes: 1 addition & 1 deletion src/parser/imports-from-namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as ts from 'typescript';

import { FromWhat } from './common';
import { Imports } from '../types';
import { namespaceBlacklist } from './namespaceBlacklist';
import { namespaceBlacklist } from './blacklists';

// Parse use of imports from namespace

Expand Down
23 changes: 20 additions & 3 deletions src/parser/nodeProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
import { ENUM_NODE_KINDS } from './kinds';
import { addImportsFromNamespace } from './imports-from-namespace';
import { extractImport } from './import';
import { namespaceBlacklist } from './namespaceBlacklist';
import { ignoreLocalBlacklist, namespaceBlacklist } from './blacklists';
import { removeTsFileExtension } from './util';

type NamespaceHolder = {
namespace: string;
Expand Down Expand Up @@ -160,6 +161,18 @@ export const processNode = (
addImportsFromNamespace(node, imports, addImport);
}

if (
extraOptions?.ignoreLocallyUsed &&
kind === ts.SyntaxKind.Identifier &&
node.parent.kind !== ts.SyntaxKind.VariableDeclaration &&
node.parent.kind !== ts.SyntaxKind.FunctionDeclaration
) {
addImport({
from: removeTsFileExtension(node.getSourceFile().fileName),
what: [node.getText()],
});
}

if (hasModifier(node, ts.SyntaxKind.ExportKeyword)) {
const nsHolder = {
namespace,
Expand All @@ -175,11 +188,15 @@ export const processNode = (
namespace = nsHolder.namespace;
}

if (namespace.length > 0) {
if (namespace.length > 0 || extraOptions?.ignoreLocallyUsed) {
// In namespace: need to process children, in case they *import* any types
// If ignoreLocallyUsed: need to iterate through whole AST to find local uses of exported variables
const blacklist = extraOptions?.ignoreLocallyUsed
? ignoreLocalBlacklist
: namespaceBlacklist;
node
.getChildren()
.filter((c) => !namespaceBlacklist.includes(c.kind))
.filter((c) => !blacklist.includes(c.kind))
.forEach((c) => {
processSubNode(c, namespace);
});
Expand Down
22 changes: 17 additions & 5 deletions src/parser/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ts = require('typescript');

import { namespaceBlacklist } from './namespaceBlacklist';
import { namespaceBlacklist } from './blacklists';

export function isUnique<T>(value: T, index: number, self: T[]): boolean {
return self.indexOf(value) === index;
Expand Down Expand Up @@ -31,11 +31,23 @@ export function removeFileExtensionToAllowForJs(path: string): string {
// ref: https://www.typescriptlang.org/docs/handbook/esm-node.html
const extensionsToStrip = ['.js', '.cjs', '.mjs'];

for (let i = 0; i < extensionsToStrip.length; i++) {
const ext = extensionsToStrip[i];
if (path.endsWith(ext)) return path.substring(0, path.length - ext.length);
}
return stripExtensionsFromPath(extensionsToStrip, path);
}

export function removeTsFileExtension(path: string): string {
const ext = ['.ts'];
return stripExtensionsFromPath(ext, path);
}

export function stripExtensionsFromPath(
extensions: string[],
path: string,
): string {
for (const extension of extensions) {
if (path.endsWith(extension)) {
return path.substring(0, path.length - extension.length);
}
}
return path;
}

Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface ExtraCommandLineOptions {
exitWithCount?: boolean;
exitWithUnusedTypesCount?: boolean;
ignoreFilesRegex?: string[];
ignoreLocallyUsed?: boolean;
maxIssues?: number;
pathsToExcludeFromReport?: string[];
searchNamespaces?: boolean;
Expand Down