Skip to content

Commit

Permalink
Add isModule to narrow down visitor condition
Browse files Browse the repository at this point in the history
  • Loading branch information
webpro committed Dec 22, 2024
1 parent 3cb27eb commit 17f2224
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 168 deletions.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { d } from './default';
import { dx } from './default-x';
import { d } from './default.mjs';
import { dx } from './default-x.mjs';
import _ from 'lodash';
import { z, f, g, i } from './mod';
import { USED } from './access';
import { identifier } from './exports';
import { a } from './ignored';
import * as NS from './reexports';
import * as NS from './reexports.mjs';

d;
dx;
Expand Down
File renamed without changes.
26 changes: 12 additions & 14 deletions packages/knip/src/typescript/visitors/exports/exportAssignment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@ import ts from 'typescript';
import { FIX_FLAGS } from '../../../constants.js';
import type { Fix } from '../../../types/exports.js';
import { SymbolType } from '../../../types/issues.js';
import { isModule } from '../helpers.js';
import { exportVisitor as visit } from '../index.js';

export default visit(
() => true,
(node, { isFixExports }) => {
if (ts.isExportAssignment(node)) {
// Patterns:
// export default 1;
// export = identifier;
const pos = node.getChildAt(1).getStart();
const fix: Fix = isFixExports ? [node.getStart(), node.getEnd() + 1, FIX_FLAGS.NONE] : undefined;
// @ts-expect-error We need the symbol in `addExport`
const symbol = node.getSourceFile().locals?.get(node.expression.escapedText);
return { node, symbol, identifier: 'default', type: SymbolType.UNKNOWN, pos, fix };
}
export default visit(isModule, (node, { isFixExports }) => {
if (ts.isExportAssignment(node)) {
// Patterns:
// export default 1;
// export = identifier;
const pos = node.getChildAt(1).getStart();
const fix: Fix = isFixExports ? [node.getStart(), node.getEnd() + 1, FIX_FLAGS.NONE] : undefined;
// @ts-expect-error We need the symbol in `addExport`
const symbol = node.getSourceFile().locals?.get(node.expression.escapedText);
return { node, symbol, identifier: 'default', type: SymbolType.UNKNOWN, pos, fix };
}
);
});
52 changes: 25 additions & 27 deletions packages/knip/src/typescript/visitors/exports/exportDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,32 @@ import { FIX_FLAGS } from '../../../constants.js';
import type { Fix } from '../../../types/exports.js';
import { SymbolType } from '../../../types/issues.js';
import type { BoundSourceFile } from '../../SourceFile.js';
import { isModule } from '../helpers.js';
import { exportVisitor as visit } from '../index.js';

export default visit(
() => true,
(node, { isFixExports, isFixTypes }) => {
if (ts.isExportDeclaration(node)) {
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
// Patterns:
// export { identifier, type identifier2 };
// export type { Identifier, Identifier2 };
const nodeType = node.isTypeOnly ? SymbolType.TYPE : SymbolType.UNKNOWN;
const sourceFile: BoundSourceFile = node.getSourceFile();
const declarations = sourceFile.getNamedDeclarations?.();
return node.exportClause.elements.map(element => {
const identifier = String(element.name.text);
const propName = element.propertyName?.text;
// @ts-expect-error TODO Fix (convenience in addExport)
// const symbol = element.symbol ?? declarations?.get(identifier)?.find((d: ts.Node) => d !== element)?.symbol;
const symbol = declarations?.get(propName ?? identifier)?.[0]?.symbol;
const pos = element.name.pos;
const type = element.isTypeOnly ? SymbolType.TYPE : nodeType;
const fix: Fix =
(isFixExports && type !== SymbolType.TYPE) || (isFixTypes && type === SymbolType.TYPE)
? [element.getStart(), element.getEnd(), FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.EMPTY_DECLARATION]
: undefined;
return { node: element, symbol, identifier, type, pos, fix };
});
}
export default visit(isModule, (node, { isFixExports, isFixTypes }) => {
if (ts.isExportDeclaration(node)) {
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
// Patterns:
// export { identifier, type identifier2 };
// export type { Identifier, Identifier2 };
const nodeType = node.isTypeOnly ? SymbolType.TYPE : SymbolType.UNKNOWN;
const sourceFile: BoundSourceFile = node.getSourceFile();
const declarations = sourceFile.getNamedDeclarations?.();
return node.exportClause.elements.map(element => {
const identifier = String(element.name.text);
const propName = element.propertyName?.text;
// @ts-expect-error TODO Fix (convenience in addExport)
// const symbol = element.symbol ?? declarations?.get(identifier)?.find((d: ts.Node) => d !== element)?.symbol;
const symbol = declarations?.get(propName ?? identifier)?.[0]?.symbol;
const pos = element.name.pos;
const type = element.isTypeOnly ? SymbolType.TYPE : nodeType;
const fix: Fix =
(isFixExports && type !== SymbolType.TYPE) || (isFixTypes && type === SymbolType.TYPE)
? [element.getStart(), element.getEnd(), FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.EMPTY_DECLARATION]
: undefined;
return { node: element, symbol, identifier, type, pos, fix };
});
}
}
);
});
232 changes: 114 additions & 118 deletions packages/knip/src/typescript/visitors/exports/exportKeyword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,137 +10,133 @@ import {
isPrivateMember,
stripQuotes,
} from '../../ast-helpers.js';
import { isModule } from '../helpers.js';
import { exportVisitor as visit } from '../index.js';

export default visit(
() => true,
(node, { isFixExports, isFixTypes, isReportClassMembers }) => {
const exportKeyword = getExportKeywordNode(node);
export default visit(isModule, (node, { isFixExports, isFixTypes, isReportClassMembers }) => {
const exportKeyword = getExportKeywordNode(node);

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

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

if (ts.isVariableStatement(node)) {
// @ts-expect-error TODO Issue seems caused by mismatch between returned `node` types (but all ts.Node)
return node.declarationList.declarations.flatMap(declaration => {
if (ts.isObjectBindingPattern(declaration.name)) {
// Pattern: export const { name1, name2 } = {};
return compact(
declaration.name.elements.map(element => {
if (ts.isIdentifier(element.name)) {
const fix = isFixExports
? [element.getStart(), element.getEnd(), FIX_FLAGS.OBJECT_BINDING]
: undefined;
return {
node: element,
// @ts-expect-error We'll use the symbol in `findInternalReferences`
symbol: element.symbol,
identifier: element.name.escapedText.toString(),
type: SymbolType.UNKNOWN,
pos: element.name.getStart(),
fix,
};
}
})
);
}
if (ts.isArrayBindingPattern(declaration.name)) {
// Pattern: export const [name1, name2] = [];
return compact(
declaration.name.elements.map(element => {
if (ts.isBindingElement(element)) {
const fix = isFixExports ? [element.getStart(), element.getEnd(), FIX_FLAGS.NONE] : undefined;
return {
node: element,
// @ts-expect-error We'll use the symbol in `findInternalReferences`
symbol: element.symbol,
identifier: element.getText(),
type: SymbolType.UNKNOWN,
pos: element.getStart(),
fix,
};
}
})
);
}
if (ts.isVariableStatement(node)) {
// @ts-expect-error TODO Issue seems caused by mismatch between returned `node` types (but all ts.Node)
return node.declarationList.declarations.flatMap(declaration => {
if (ts.isObjectBindingPattern(declaration.name)) {
// Pattern: export const { name1, name2 } = {};
return compact(
declaration.name.elements.map(element => {
if (ts.isIdentifier(element.name)) {
const fix = isFixExports ? [element.getStart(), element.getEnd(), FIX_FLAGS.OBJECT_BINDING] : undefined;
return {
node: element,
// @ts-expect-error We'll use the symbol in `findInternalReferences`
symbol: element.symbol,
identifier: element.name.escapedText.toString(),
type: SymbolType.UNKNOWN,
pos: element.name.getStart(),
fix,
};
}
})
);
}
if (ts.isArrayBindingPattern(declaration.name)) {
// Pattern: export const [name1, name2] = [];
return compact(
declaration.name.elements.map(element => {
if (ts.isBindingElement(element)) {
const fix = isFixExports ? [element.getStart(), element.getEnd(), FIX_FLAGS.NONE] : undefined;
return {
node: element,
// @ts-expect-error We'll use the symbol in `findInternalReferences`
symbol: element.symbol,
identifier: element.getText(),
type: SymbolType.UNKNOWN,
pos: element.getStart(),
fix,
};
}
})
);
}

// Pattern: export const MyVar = 1;
const identifier = declaration.name.getText();
const pos = declaration.name.getStart();
const fix = getFix(exportKeyword);
return { node: declaration, identifier, type: SymbolType.UNKNOWN, pos, fix };
});
}
// Pattern: export const MyVar = 1;
const identifier = declaration.name.getText();
const pos = declaration.name.getStart();
const fix = getFix(exportKeyword);
return { node: declaration, identifier, type: SymbolType.UNKNOWN, pos, fix };
});
}

const defaultKeyword = getDefaultKeywordNode(node);
const defaultKeyword = getDefaultKeywordNode(node);

if (ts.isFunctionDeclaration(node) && node.name) {
const identifier = defaultKeyword ? 'default' : node.name.getText();
const pos = (node.name ?? node.body ?? node).getStart();
const fix = getFix(exportKeyword, defaultKeyword);
return { node, identifier, pos, type: SymbolType.FUNCTION, fix };
}
if (ts.isFunctionDeclaration(node) && node.name) {
const identifier = defaultKeyword ? 'default' : node.name.getText();
const pos = (node.name ?? node.body ?? node).getStart();
const fix = getFix(exportKeyword, defaultKeyword);
return { node, identifier, pos, type: SymbolType.FUNCTION, fix };
}

if (ts.isClassDeclaration(node) && node.name) {
const identifier = defaultKeyword ? 'default' : node.name.getText();
const pos = (node.name ?? node).getStart();
const fix = getFix(exportKeyword, defaultKeyword);
const members = isReportClassMembers
? node.members
.filter(
(member): member is ts.MethodDeclaration | ts.PropertyDeclaration =>
(ts.isPropertyDeclaration(member) ||
ts.isMethodDeclaration(member) ||
isGetOrSetAccessorDeclaration(member)) &&
!isPrivateMember(member)
)
.map(member => ({
node: member,
identifier: member.name.getText(),
// Naive, but [does.the.job()]
pos: member.name.getStart() + (ts.isComputedPropertyName(member.name) ? 1 : 0),
type: SymbolType.MEMBER,
fix: isFixTypes ? ([member.getStart(), member.getEnd(), FIX_FLAGS.NONE] as Fix) : undefined,
}))
: [];
if (ts.isClassDeclaration(node) && node.name) {
const identifier = defaultKeyword ? 'default' : node.name.getText();
const pos = (node.name ?? node).getStart();
const fix = getFix(exportKeyword, defaultKeyword);
const members = isReportClassMembers
? node.members
.filter(
(member): member is ts.MethodDeclaration | ts.PropertyDeclaration =>
(ts.isPropertyDeclaration(member) ||
ts.isMethodDeclaration(member) ||
isGetOrSetAccessorDeclaration(member)) &&
!isPrivateMember(member)
)
.map(member => ({
node: member,
identifier: member.name.getText(),
// Naive, but [does.the.job()]
pos: member.name.getStart() + (ts.isComputedPropertyName(member.name) ? 1 : 0),
type: SymbolType.MEMBER,
fix: isFixTypes ? ([member.getStart(), member.getEnd(), FIX_FLAGS.NONE] as Fix) : undefined,
}))
: [];

return { node, identifier, type: SymbolType.CLASS, pos, members, fix };
}
return { node, identifier, type: SymbolType.CLASS, pos, members, fix };
}

if (ts.isTypeAliasDeclaration(node)) {
const identifier = node.name.getText();
const pos = node.name.getStart();
const fix = getTypeFix(exportKeyword);
return { node, identifier, type: SymbolType.TYPE, pos, fix };
}
if (ts.isTypeAliasDeclaration(node)) {
const identifier = node.name.getText();
const pos = node.name.getStart();
const fix = getTypeFix(exportKeyword);
return { node, identifier, type: SymbolType.TYPE, pos, fix };
}

if (ts.isInterfaceDeclaration(node)) {
const identifier = defaultKeyword ? 'default' : node.name.getText();
const pos = node.name.getStart();
const fix = getTypeFix(exportKeyword);
return { node, identifier, type: SymbolType.INTERFACE, pos, fix };
}
if (ts.isInterfaceDeclaration(node)) {
const identifier = defaultKeyword ? 'default' : node.name.getText();
const pos = node.name.getStart();
const fix = getTypeFix(exportKeyword);
return { node, identifier, type: SymbolType.INTERFACE, pos, fix };
}

if (ts.isEnumDeclaration(node)) {
const identifier = node.name.getText();
const pos = node.name.getStart();
const fix = getTypeFix(exportKeyword);
const members = node.members.map(member => ({
node: member,
identifier: stripQuotes(member.name.getText()),
pos: member.name.getStart(),
type: SymbolType.MEMBER,
fix: isFixTypes
? ([member.getStart(), member.getEnd(), FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.WITH_NEWLINE] as Fix)
: undefined,
}));
if (ts.isEnumDeclaration(node)) {
const identifier = node.name.getText();
const pos = node.name.getStart();
const fix = getTypeFix(exportKeyword);
const members = node.members.map(member => ({
node: member,
identifier: stripQuotes(member.name.getText()),
pos: member.name.getStart(),
type: SymbolType.MEMBER,
fix: isFixTypes
? ([member.getStart(), member.getEnd(), FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.WITH_NEWLINE] as Fix)
: undefined,
}));

return { node, identifier, type: SymbolType.ENUM, pos, members, fix };
}
return { node, identifier, type: SymbolType.ENUM, pos, members, fix };
}
}
);
});
2 changes: 2 additions & 0 deletions packages/knip/src/typescript/visitors/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export const isNotJS = (sourceFile: BoundSourceFile) => !isJS(sourceFile);
export const isJS = (sourceFile: BoundSourceFile) =>
sourceFile.scriptKind === ts.ScriptKind.JS || sourceFile.scriptKind === ts.ScriptKind.JSX;

export const isModule = (sourceFile: BoundSourceFile) => ts.isExternalModule(sourceFile);

export function getImportsFromPragmas(sourceFile: BoundSourceFile) {
const importNodes: ImportNode[] = [];

Expand Down
Loading

0 comments on commit 17f2224

Please sign in to comment.