Skip to content

Commit

Permalink
Merge 47eb502 into 32bf3d3
Browse files Browse the repository at this point in the history
  • Loading branch information
mrseanryan committed Dec 30, 2019
2 parents 32bf3d3 + 47eb502 commit b8a2a9a
Show file tree
Hide file tree
Showing 22 changed files with 832 additions and 97 deletions.
16 changes: 16 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
}
]
}
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
[Unreleased] - ReleaseDate

### Added
- Handle exports from within a namespace. Disabled by default, unless option --searchNamespaces is given. Note: this can affect performance on large codebases.

## [5.2.0] - 23 Dec 2019

### Changed
- Find exports from within namespaces (for performance, this feature is disabled by default. To enable, use the option --enableSearchNamespaces.)
- (Internal) Update dependency TypeScript to 3.7.3
- (Internal) Simplify some logic, using the new optional chaining operator (?.)
- (Internal) Increase code coverage and simplify code (baseUrl defaults to '.')
Expand Down
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@ Some things that will increase the chance that your pull request is accepted:

# notes

## Debugging `ts-unused-exports` VSCode
## Debugging `ts-unused-exports` with Visual Code

To debug with VSCode, make sure you run the following in a terminal:
To debug, in **Visual Code**, press `CTRL + SHIFT + B`.

Or make sure you run the following in a terminal:

npm run watch

Back in VSCode, open a file, add a breakpoint (`F9`).
Back in **Visual Code**, open a file, add a breakpoint (`F9`).
Open a `.feature` file, put the cursor over a `Scenario:` line and press `F5`.

If you don't know where to put the breakpoint, you can always put it in the first line of the default export of `app.ts`.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ Options:

| Option name | Description | Example |
| ------------------------- | ---------------------------------------------------------------------------- | --------------------------- |
| ` --searchNamespaces` | Enable searching for unused exports within namespaces. Note: this can affect performance on large codebases. | `--searchNamespaces` |
| `excludeDeclarationFiles` | Exclude `.d.ts` files when looking for unused exports. | `--excludeDeclarationFiles` |
| `exitWithCount` | Set the process exit code to be the count of files that have unused exports. | `--exitWithCount` |
| `ignorePaths` | Exclude files that match the given path segments. | `--ignorePaths=math;utils` |
| `excludeDeclarationFiles` | Exclude `.d.ts` files when looking for unused exports. | `--excludeDeclarationFiles` |
| `showLineNumber` | Show the line number and column of the unused export. | `--showLineNumber` |

Note that if `ts-unused-exports` is called without files, the files will be read from the tsconfig's `files` or `include` key which must be present. If called with files, then those file paths should be relative to the `tsconfig.json`, just like you would specify them in your tsconfig's `files` key.
Expand Down
39 changes: 39 additions & 0 deletions features/cli.feature
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
Feature: CLI

Scenario: Search Namespaces ON
Given file "a.ts" is
"""
// This is line 1
export namespace ns
{
export const ns_unused = 1;
}
"""
And file "b.ts" is
"""
import { ns } from './a';
export const B_unused = 2;
"""
When running ts-unused-exports "tsconfig.json" --searchNamespaces
Then the CLI result at status is 1
And the CLI result at stdout contains "a.ts: ns.ns_unused"
And the CLI result at stdout contains "b.ts: B_unused"

Scenario: Search Namespaces OFF
Given file "a.ts" is
"""
// This is line 1
export namespace ns
{
export const ns_unused = 1;
}
export const A_unused = 2;
"""
And file "b.ts" is
"""
import { ns } from './a';
export const B_unused = 2;
"""
When running ts-unused-exports "tsconfig.json"
Then the CLI result at status is 1
And the CLI result at stdout contains "a.ts: A_unused"
And the CLI result at stdout contains "b.ts: B_unused"

Scenario: Line numbers
Given file "a.ts" is
"""
Expand Down
72 changes: 72 additions & 0 deletions features/exported-namespace.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
Feature: export namespace

Background:
Given file "a.ts" is
"""
export namespace constants {
export enum flag {
int: "int"
}
}
export const A = 1;
export const A_unused = 1;
"""

Scenario: Import A only
Given file "b.ts" is import { A } from './a';
When analyzing "tsconfig.json" with files ["--searchNamespaces"]
Then the result is { "a.ts": ["constants", "constants.flag", "A_unused"] }

Scenario: Import namespace only
Given file "b.ts" is import { A, constants } from './a';
When analyzing "tsconfig.json" with files ["--searchNamespaces"]
Then the result is { "a.ts": ["constants.flag", "A_unused"] }

Scenario: Import namespace and use the inner type
Given file "b.ts" is
"""
import { A, constants } from './a';
const b: constants.flag;
"""
When analyzing "tsconfig.json" with files ["--searchNamespaces"]
Then the result is { "a.ts": ["A_unused"] }

# note: TypeScript cannot export default with or from within a namespace:
# "A default export can only be used in an ECMAScript-style module."
#
# So - no need for a test like "Dynamically import namespace and use the inner type"

Scenario: Import from nested namespace and use the inner type
Given file "b.ts" is
"""
import { A, constants } from './a';
export namespace B_top
{
export const B_inner_1 = 1;
export namespace B_inner
{
export const B_inner_2 = 1;
export const B_inner_unused = 1;
}
export namespace B_unused
{
export type B_unused_unused = {};
}
}
const b: constants.flag;
"""
And file "c.ts" is
"""
import { B_top } from './b';
const c1: B_top.B_inner_1;
const c2: B_top.B_inner.B_inner_2;
"""
When analyzing "tsconfig.json" with files ["--searchNamespaces"]
Then the result is { "a.ts": ["A_unused"], "b.ts": [ "B_top.B_inner.B_inner_unused", "B_top.B_unused", "B_top.B_unused.B_unused_unused"] }
17 changes: 16 additions & 1 deletion features/include-dynamic-imports.feature
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Scenario: Include dynamic import as promise - in a function
When analyzing "tsconfig.json"
Then the result is { "b.ts": ["B_unused"], "a.ts": ["A_unused"] }

Scenario: Include dynamic import via await - in a function,
Scenario: Include dynamic import via await - in a function
Given file "a.ts" is
"""
export default type A = 1;
Expand All @@ -50,3 +50,18 @@ Scenario: Include dynamic import via await - in a function,
"""
When analyzing "tsconfig.json"
Then the result is { "b.ts": ["B_unused"], "a.ts": ["A_unused"] }

Scenario: Dynamically import default function
Given file "a.ts" is
"""
export default function iAmDefault() { return 2; };
"""
And file "b.ts" is
"""
import("./a").then(A_imported => {
console.log(A_imported);
});
export const B_unused: A = 0
"""
When analyzing "tsconfig.json"
Then the result is { "b.ts": ["B_unused"] }
4 changes: 2 additions & 2 deletions src/analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const expandExportFromStar = (files: File[], exportMap: ExportMap): void => {
files.forEach(file => {
const fileExports = exportMap[file.path];
file.exports
.filter(ex => ex.indexOf('*:') === 0)
.filter(ex => ex.startsWith('*:'))
.forEach(ex => {
delete fileExports.exports[ex];

Expand Down Expand Up @@ -123,7 +123,7 @@ const shouldPathBeIgnored = (
return false;
}

return extraOptions.pathsToIgnore.some(ignore => path.indexOf(ignore) >= 0);
return extraOptions.pathsToIgnore.some(ignore => path.includes(ignore));
};

export default (
Expand Down
2 changes: 1 addition & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const parseTsConfig = (tsconfigPath: string): TsConfig => {
}
};

export const loadTsConfig = (
const loadTsConfig = (
tsconfigPath: string,
explicitFiles?: string[],
): TsConfig => {
Expand Down
5 changes: 4 additions & 1 deletion src/argsParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ function processOptions(
case '--excludeDeclarationFiles':
newOptions.excludeDeclarationFiles = true;
break;
case '--searchNamespaces':
newOptions.searchNamespaces = true;
break;
case '--showLineNumber':
newOptions.showLineNumber = true;
break;
Expand All @@ -58,7 +61,7 @@ export function extractOptionsFromFiles(files?: string[]): TsFilesAndOptions {
};

const isOption = (opt: string): boolean => {
return opt.indexOf('--') === 0;
return opt.startsWith('--');
};

if (files) {
Expand Down
2 changes: 1 addition & 1 deletion src/parser/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface FromWhat {
what: string[];
}

export const TRIM_QUOTES = /^['"](.*)['"]$/;
const TRIM_QUOTES = /^['"](.*)['"]$/;

export const getFromText = (moduleSpecifier: string): string =>
moduleSpecifier.replace(TRIM_QUOTES, '$1').replace(/\/index$/, '');
Expand Down
14 changes: 10 additions & 4 deletions src/parser/dynamic.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import * as ts from 'typescript';

import { FromWhat, getFromText } from './common';

import { namespaceBlacklist } from './namespaceBlacklist';

// Parse Dynamic Imports

export const mayContainDynamicImports = (node: ts.Node): boolean =>
node.getText().indexOf('import(') > -1;
!namespaceBlacklist.includes(node.kind) && node.getText().includes('import(');

type WithExpression = ts.Node & {
expression: ts.Expression;
};

export function isWithExpression(node: ts.Node): node is WithExpression {
function isWithExpression(node: ts.Node): node is WithExpression {
const myInterface = node as WithExpression;
return !!myInterface.expression;
}
Expand All @@ -19,7 +22,7 @@ type WithArguments = ts.Node & {
arguments: ts.NodeArray<ts.Expression>;
};

export function isWithArguments(node: ts.Node): node is WithArguments {
function isWithArguments(node: ts.Node): node is WithArguments {
const myInterface = node as WithArguments;
return !!myInterface.arguments;
}
Expand Down Expand Up @@ -63,7 +66,10 @@ export const addDynamicImports = (
const recurseIntoChildren = (next: ts.Node): void => {
addImportsInAnyExpression(next);

next.getChildren().forEach(recurseIntoChildren);
next
.getChildren()
.filter(c => !namespaceBlacklist.includes(c.kind))
.forEach(recurseIntoChildren);
};

recurseIntoChildren(node);
Expand Down
4 changes: 4 additions & 0 deletions src/parser/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export const addExportCore = (
exportLocations: LocationInFile[],
exports: string[],
): void => {
if (exports.includes(exportName)) {
return;
}

exports.push(exportName);

const location = file.getLineAndCharacterOfPosition(node.getStart());
Expand Down
10 changes: 7 additions & 3 deletions src/parser/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as ts from 'typescript';

import { getFrom, FromWhat, STAR } from './common';
import { Imports } from '../types';
import { isUnique } from './util';

// Parse Imports

Expand Down Expand Up @@ -48,6 +49,9 @@ export const extractImport = (decl: ts.ImportDeclaration): FromWhat => {
e => (e.propertyName || e.name).text,
);

// note on namespaces: when importing a namespace, we cannot differentiate that from another element.
// (we differentiate on *export*)

return {
from,
what: importDefault.concat(importNames),
Expand Down Expand Up @@ -86,9 +90,9 @@ export const addImportCore = (
}
};

const key = getKey(from);
if (!key) return undefined;
const key = getKey(from) || from;
const items = imports[key] || [];
imports[key] = items.concat(what);

imports[key] = items.concat(what).filter(isUnique);
return key;
};

0 comments on commit b8a2a9a

Please sign in to comment.