Skip to content

Commit 17f2224

Browse files
committed
Add isModule to narrow down visitor condition
1 parent 3cb27eb commit 17f2224

File tree

9 files changed

+162
-168
lines changed

9 files changed

+162
-168
lines changed

packages/knip/fixtures/fix/index.js renamed to packages/knip/fixtures/fix/index.mjs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { d } from './default';
2-
import { dx } from './default-x';
1+
import { d } from './default.mjs';
2+
import { dx } from './default-x.mjs';
33
import _ from 'lodash';
44
import { z, f, g, i } from './mod';
55
import { USED } from './access';
66
import { identifier } from './exports';
77
import { a } from './ignored';
8-
import * as NS from './reexports';
8+
import * as NS from './reexports.mjs';
99

1010
d;
1111
dx;

packages/knip/src/typescript/visitors/exports/exportAssignment.ts

+12-14
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,18 @@ import ts from 'typescript';
22
import { FIX_FLAGS } from '../../../constants.js';
33
import type { Fix } from '../../../types/exports.js';
44
import { SymbolType } from '../../../types/issues.js';
5+
import { isModule } from '../helpers.js';
56
import { exportVisitor as visit } from '../index.js';
67

7-
export default visit(
8-
() => true,
9-
(node, { isFixExports }) => {
10-
if (ts.isExportAssignment(node)) {
11-
// Patterns:
12-
// export default 1;
13-
// export = identifier;
14-
const pos = node.getChildAt(1).getStart();
15-
const fix: Fix = isFixExports ? [node.getStart(), node.getEnd() + 1, FIX_FLAGS.NONE] : undefined;
16-
// @ts-expect-error We need the symbol in `addExport`
17-
const symbol = node.getSourceFile().locals?.get(node.expression.escapedText);
18-
return { node, symbol, identifier: 'default', type: SymbolType.UNKNOWN, pos, fix };
19-
}
8+
export default visit(isModule, (node, { isFixExports }) => {
9+
if (ts.isExportAssignment(node)) {
10+
// Patterns:
11+
// export default 1;
12+
// export = identifier;
13+
const pos = node.getChildAt(1).getStart();
14+
const fix: Fix = isFixExports ? [node.getStart(), node.getEnd() + 1, FIX_FLAGS.NONE] : undefined;
15+
// @ts-expect-error We need the symbol in `addExport`
16+
const symbol = node.getSourceFile().locals?.get(node.expression.escapedText);
17+
return { node, symbol, identifier: 'default', type: SymbolType.UNKNOWN, pos, fix };
2018
}
21-
);
19+
});

packages/knip/src/typescript/visitors/exports/exportDeclaration.ts

+25-27
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,32 @@ import { FIX_FLAGS } from '../../../constants.js';
33
import type { Fix } from '../../../types/exports.js';
44
import { SymbolType } from '../../../types/issues.js';
55
import type { BoundSourceFile } from '../../SourceFile.js';
6+
import { isModule } from '../helpers.js';
67
import { exportVisitor as visit } from '../index.js';
78

8-
export default visit(
9-
() => true,
10-
(node, { isFixExports, isFixTypes }) => {
11-
if (ts.isExportDeclaration(node)) {
12-
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
13-
// Patterns:
14-
// export { identifier, type identifier2 };
15-
// export type { Identifier, Identifier2 };
16-
const nodeType = node.isTypeOnly ? SymbolType.TYPE : SymbolType.UNKNOWN;
17-
const sourceFile: BoundSourceFile = node.getSourceFile();
18-
const declarations = sourceFile.getNamedDeclarations?.();
19-
return node.exportClause.elements.map(element => {
20-
const identifier = String(element.name.text);
21-
const propName = element.propertyName?.text;
22-
// @ts-expect-error TODO Fix (convenience in addExport)
23-
// const symbol = element.symbol ?? declarations?.get(identifier)?.find((d: ts.Node) => d !== element)?.symbol;
24-
const symbol = declarations?.get(propName ?? identifier)?.[0]?.symbol;
25-
const pos = element.name.pos;
26-
const type = element.isTypeOnly ? SymbolType.TYPE : nodeType;
27-
const fix: Fix =
28-
(isFixExports && type !== SymbolType.TYPE) || (isFixTypes && type === SymbolType.TYPE)
29-
? [element.getStart(), element.getEnd(), FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.EMPTY_DECLARATION]
30-
: undefined;
31-
return { node: element, symbol, identifier, type, pos, fix };
32-
});
33-
}
9+
export default visit(isModule, (node, { isFixExports, isFixTypes }) => {
10+
if (ts.isExportDeclaration(node)) {
11+
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
12+
// Patterns:
13+
// export { identifier, type identifier2 };
14+
// export type { Identifier, Identifier2 };
15+
const nodeType = node.isTypeOnly ? SymbolType.TYPE : SymbolType.UNKNOWN;
16+
const sourceFile: BoundSourceFile = node.getSourceFile();
17+
const declarations = sourceFile.getNamedDeclarations?.();
18+
return node.exportClause.elements.map(element => {
19+
const identifier = String(element.name.text);
20+
const propName = element.propertyName?.text;
21+
// @ts-expect-error TODO Fix (convenience in addExport)
22+
// const symbol = element.symbol ?? declarations?.get(identifier)?.find((d: ts.Node) => d !== element)?.symbol;
23+
const symbol = declarations?.get(propName ?? identifier)?.[0]?.symbol;
24+
const pos = element.name.pos;
25+
const type = element.isTypeOnly ? SymbolType.TYPE : nodeType;
26+
const fix: Fix =
27+
(isFixExports && type !== SymbolType.TYPE) || (isFixTypes && type === SymbolType.TYPE)
28+
? [element.getStart(), element.getEnd(), FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.EMPTY_DECLARATION]
29+
: undefined;
30+
return { node: element, symbol, identifier, type, pos, fix };
31+
});
3432
}
3533
}
36-
);
34+
});

packages/knip/src/typescript/visitors/exports/exportKeyword.ts

+114-118
Original file line numberDiff line numberDiff line change
@@ -10,137 +10,133 @@ import {
1010
isPrivateMember,
1111
stripQuotes,
1212
} from '../../ast-helpers.js';
13+
import { isModule } from '../helpers.js';
1314
import { exportVisitor as visit } from '../index.js';
1415

15-
export default visit(
16-
() => true,
17-
(node, { isFixExports, isFixTypes, isReportClassMembers }) => {
18-
const exportKeyword = getExportKeywordNode(node);
16+
export default visit(isModule, (node, { isFixExports, isFixTypes, isReportClassMembers }) => {
17+
const exportKeyword = getExportKeywordNode(node);
1918

20-
if (exportKeyword) {
21-
const getFix = (node: ts.Node, defaultKeyword?: ts.Node): Fix =>
22-
isFixExports ? [node.getStart(), (defaultKeyword ?? node).getEnd() + 1, FIX_FLAGS.NONE] : undefined;
19+
if (exportKeyword) {
20+
const getFix = (node: ts.Node, defaultKeyword?: ts.Node): Fix =>
21+
isFixExports ? [node.getStart(), (defaultKeyword ?? node).getEnd() + 1, FIX_FLAGS.NONE] : undefined;
2322

24-
const getTypeFix = (node: ts.Node): Fix =>
25-
isFixTypes ? [node.getStart(), node.getEnd() + 1, FIX_FLAGS.NONE] : undefined;
23+
const getTypeFix = (node: ts.Node): Fix =>
24+
isFixTypes ? [node.getStart(), node.getEnd() + 1, FIX_FLAGS.NONE] : undefined;
2625

27-
if (ts.isVariableStatement(node)) {
28-
// @ts-expect-error TODO Issue seems caused by mismatch between returned `node` types (but all ts.Node)
29-
return node.declarationList.declarations.flatMap(declaration => {
30-
if (ts.isObjectBindingPattern(declaration.name)) {
31-
// Pattern: export const { name1, name2 } = {};
32-
return compact(
33-
declaration.name.elements.map(element => {
34-
if (ts.isIdentifier(element.name)) {
35-
const fix = isFixExports
36-
? [element.getStart(), element.getEnd(), FIX_FLAGS.OBJECT_BINDING]
37-
: undefined;
38-
return {
39-
node: element,
40-
// @ts-expect-error We'll use the symbol in `findInternalReferences`
41-
symbol: element.symbol,
42-
identifier: element.name.escapedText.toString(),
43-
type: SymbolType.UNKNOWN,
44-
pos: element.name.getStart(),
45-
fix,
46-
};
47-
}
48-
})
49-
);
50-
}
51-
if (ts.isArrayBindingPattern(declaration.name)) {
52-
// Pattern: export const [name1, name2] = [];
53-
return compact(
54-
declaration.name.elements.map(element => {
55-
if (ts.isBindingElement(element)) {
56-
const fix = isFixExports ? [element.getStart(), element.getEnd(), FIX_FLAGS.NONE] : undefined;
57-
return {
58-
node: element,
59-
// @ts-expect-error We'll use the symbol in `findInternalReferences`
60-
symbol: element.symbol,
61-
identifier: element.getText(),
62-
type: SymbolType.UNKNOWN,
63-
pos: element.getStart(),
64-
fix,
65-
};
66-
}
67-
})
68-
);
69-
}
26+
if (ts.isVariableStatement(node)) {
27+
// @ts-expect-error TODO Issue seems caused by mismatch between returned `node` types (but all ts.Node)
28+
return node.declarationList.declarations.flatMap(declaration => {
29+
if (ts.isObjectBindingPattern(declaration.name)) {
30+
// Pattern: export const { name1, name2 } = {};
31+
return compact(
32+
declaration.name.elements.map(element => {
33+
if (ts.isIdentifier(element.name)) {
34+
const fix = isFixExports ? [element.getStart(), element.getEnd(), FIX_FLAGS.OBJECT_BINDING] : undefined;
35+
return {
36+
node: element,
37+
// @ts-expect-error We'll use the symbol in `findInternalReferences`
38+
symbol: element.symbol,
39+
identifier: element.name.escapedText.toString(),
40+
type: SymbolType.UNKNOWN,
41+
pos: element.name.getStart(),
42+
fix,
43+
};
44+
}
45+
})
46+
);
47+
}
48+
if (ts.isArrayBindingPattern(declaration.name)) {
49+
// Pattern: export const [name1, name2] = [];
50+
return compact(
51+
declaration.name.elements.map(element => {
52+
if (ts.isBindingElement(element)) {
53+
const fix = isFixExports ? [element.getStart(), element.getEnd(), FIX_FLAGS.NONE] : undefined;
54+
return {
55+
node: element,
56+
// @ts-expect-error We'll use the symbol in `findInternalReferences`
57+
symbol: element.symbol,
58+
identifier: element.getText(),
59+
type: SymbolType.UNKNOWN,
60+
pos: element.getStart(),
61+
fix,
62+
};
63+
}
64+
})
65+
);
66+
}
7067

71-
// Pattern: export const MyVar = 1;
72-
const identifier = declaration.name.getText();
73-
const pos = declaration.name.getStart();
74-
const fix = getFix(exportKeyword);
75-
return { node: declaration, identifier, type: SymbolType.UNKNOWN, pos, fix };
76-
});
77-
}
68+
// Pattern: export const MyVar = 1;
69+
const identifier = declaration.name.getText();
70+
const pos = declaration.name.getStart();
71+
const fix = getFix(exportKeyword);
72+
return { node: declaration, identifier, type: SymbolType.UNKNOWN, pos, fix };
73+
});
74+
}
7875

79-
const defaultKeyword = getDefaultKeywordNode(node);
76+
const defaultKeyword = getDefaultKeywordNode(node);
8077

81-
if (ts.isFunctionDeclaration(node) && node.name) {
82-
const identifier = defaultKeyword ? 'default' : node.name.getText();
83-
const pos = (node.name ?? node.body ?? node).getStart();
84-
const fix = getFix(exportKeyword, defaultKeyword);
85-
return { node, identifier, pos, type: SymbolType.FUNCTION, fix };
86-
}
78+
if (ts.isFunctionDeclaration(node) && node.name) {
79+
const identifier = defaultKeyword ? 'default' : node.name.getText();
80+
const pos = (node.name ?? node.body ?? node).getStart();
81+
const fix = getFix(exportKeyword, defaultKeyword);
82+
return { node, identifier, pos, type: SymbolType.FUNCTION, fix };
83+
}
8784

88-
if (ts.isClassDeclaration(node) && node.name) {
89-
const identifier = defaultKeyword ? 'default' : node.name.getText();
90-
const pos = (node.name ?? node).getStart();
91-
const fix = getFix(exportKeyword, defaultKeyword);
92-
const members = isReportClassMembers
93-
? node.members
94-
.filter(
95-
(member): member is ts.MethodDeclaration | ts.PropertyDeclaration =>
96-
(ts.isPropertyDeclaration(member) ||
97-
ts.isMethodDeclaration(member) ||
98-
isGetOrSetAccessorDeclaration(member)) &&
99-
!isPrivateMember(member)
100-
)
101-
.map(member => ({
102-
node: member,
103-
identifier: member.name.getText(),
104-
// Naive, but [does.the.job()]
105-
pos: member.name.getStart() + (ts.isComputedPropertyName(member.name) ? 1 : 0),
106-
type: SymbolType.MEMBER,
107-
fix: isFixTypes ? ([member.getStart(), member.getEnd(), FIX_FLAGS.NONE] as Fix) : undefined,
108-
}))
109-
: [];
85+
if (ts.isClassDeclaration(node) && node.name) {
86+
const identifier = defaultKeyword ? 'default' : node.name.getText();
87+
const pos = (node.name ?? node).getStart();
88+
const fix = getFix(exportKeyword, defaultKeyword);
89+
const members = isReportClassMembers
90+
? node.members
91+
.filter(
92+
(member): member is ts.MethodDeclaration | ts.PropertyDeclaration =>
93+
(ts.isPropertyDeclaration(member) ||
94+
ts.isMethodDeclaration(member) ||
95+
isGetOrSetAccessorDeclaration(member)) &&
96+
!isPrivateMember(member)
97+
)
98+
.map(member => ({
99+
node: member,
100+
identifier: member.name.getText(),
101+
// Naive, but [does.the.job()]
102+
pos: member.name.getStart() + (ts.isComputedPropertyName(member.name) ? 1 : 0),
103+
type: SymbolType.MEMBER,
104+
fix: isFixTypes ? ([member.getStart(), member.getEnd(), FIX_FLAGS.NONE] as Fix) : undefined,
105+
}))
106+
: [];
110107

111-
return { node, identifier, type: SymbolType.CLASS, pos, members, fix };
112-
}
108+
return { node, identifier, type: SymbolType.CLASS, pos, members, fix };
109+
}
113110

114-
if (ts.isTypeAliasDeclaration(node)) {
115-
const identifier = node.name.getText();
116-
const pos = node.name.getStart();
117-
const fix = getTypeFix(exportKeyword);
118-
return { node, identifier, type: SymbolType.TYPE, pos, fix };
119-
}
111+
if (ts.isTypeAliasDeclaration(node)) {
112+
const identifier = node.name.getText();
113+
const pos = node.name.getStart();
114+
const fix = getTypeFix(exportKeyword);
115+
return { node, identifier, type: SymbolType.TYPE, pos, fix };
116+
}
120117

121-
if (ts.isInterfaceDeclaration(node)) {
122-
const identifier = defaultKeyword ? 'default' : node.name.getText();
123-
const pos = node.name.getStart();
124-
const fix = getTypeFix(exportKeyword);
125-
return { node, identifier, type: SymbolType.INTERFACE, pos, fix };
126-
}
118+
if (ts.isInterfaceDeclaration(node)) {
119+
const identifier = defaultKeyword ? 'default' : node.name.getText();
120+
const pos = node.name.getStart();
121+
const fix = getTypeFix(exportKeyword);
122+
return { node, identifier, type: SymbolType.INTERFACE, pos, fix };
123+
}
127124

128-
if (ts.isEnumDeclaration(node)) {
129-
const identifier = node.name.getText();
130-
const pos = node.name.getStart();
131-
const fix = getTypeFix(exportKeyword);
132-
const members = node.members.map(member => ({
133-
node: member,
134-
identifier: stripQuotes(member.name.getText()),
135-
pos: member.name.getStart(),
136-
type: SymbolType.MEMBER,
137-
fix: isFixTypes
138-
? ([member.getStart(), member.getEnd(), FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.WITH_NEWLINE] as Fix)
139-
: undefined,
140-
}));
125+
if (ts.isEnumDeclaration(node)) {
126+
const identifier = node.name.getText();
127+
const pos = node.name.getStart();
128+
const fix = getTypeFix(exportKeyword);
129+
const members = node.members.map(member => ({
130+
node: member,
131+
identifier: stripQuotes(member.name.getText()),
132+
pos: member.name.getStart(),
133+
type: SymbolType.MEMBER,
134+
fix: isFixTypes
135+
? ([member.getStart(), member.getEnd(), FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.WITH_NEWLINE] as Fix)
136+
: undefined,
137+
}));
141138

142-
return { node, identifier, type: SymbolType.ENUM, pos, members, fix };
143-
}
139+
return { node, identifier, type: SymbolType.ENUM, pos, members, fix };
144140
}
145141
}
146-
);
142+
});

packages/knip/src/typescript/visitors/helpers.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export const isNotJS = (sourceFile: BoundSourceFile) => !isJS(sourceFile);
77
export const isJS = (sourceFile: BoundSourceFile) =>
88
sourceFile.scriptKind === ts.ScriptKind.JS || sourceFile.scriptKind === ts.ScriptKind.JSX;
99

10+
export const isModule = (sourceFile: BoundSourceFile) => ts.isExternalModule(sourceFile);
11+
1012
export function getImportsFromPragmas(sourceFile: BoundSourceFile) {
1113
const importNodes: ImportNode[] = [];
1214

0 commit comments

Comments
 (0)