knip 6.0.1 → 6.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,6 +18,7 @@ export declare class ProjectPrincipal {
18
18
  syncCompilers: SyncCompilers;
19
19
  asyncCompilers: AsyncCompilers;
20
20
  private paths;
21
+ private rootDirs;
21
22
  private extensions;
22
23
  cache: CacheConsultant<FileNode>;
23
24
  toSourceFilePath: ToSourceFilePath;
@@ -28,6 +29,7 @@ export declare class ProjectPrincipal {
28
29
  constructor(options: MainOptions, toSourceFilePath: ToSourceFilePath);
29
30
  addCompilers(compilers: [SyncCompilers, AsyncCompilers]): void;
30
31
  addPaths(paths: Paths, basePath: string): void;
32
+ addRootDirs(rootDirs: string[]): void;
31
33
  init(): void;
32
34
  readFile(filePath: string): string;
33
35
  private hasAcceptedExtension;
@@ -27,6 +27,7 @@ export class ProjectPrincipal {
27
27
  syncCompilers = new Map();
28
28
  asyncCompilers = new Map();
29
29
  paths = {};
30
+ rootDirs = [];
30
31
  extensions = new Set(DEFAULT_EXTENSIONS);
31
32
  cache;
32
33
  toSourceFilePath;
@@ -69,6 +70,12 @@ export class ProjectPrincipal {
69
70
  }
70
71
  }
71
72
  }
73
+ addRootDirs(rootDirs) {
74
+ for (const dir of rootDirs) {
75
+ if (!this.rootDirs.includes(dir))
76
+ this.rootDirs.push(dir);
77
+ }
78
+ }
72
79
  init() {
73
80
  this.extensions = new Set([
74
81
  ...DEFAULT_EXTENSIONS,
@@ -76,7 +83,8 @@ export class ProjectPrincipal {
76
83
  ]);
77
84
  const customCompilerExtensions = getCompilerExtensions([this.syncCompilers, this.asyncCompilers]);
78
85
  const pathsOrUndefined = Object.keys(this.paths).length > 0 ? this.paths : undefined;
79
- this.resolveModule = createCustomModuleResolver({ paths: pathsOrUndefined }, customCompilerExtensions, this.toSourceFilePath);
86
+ const rootDirsOrUndefined = this.rootDirs.length > 1 ? this.rootDirs : undefined;
87
+ this.resolveModule = createCustomModuleResolver({ paths: pathsOrUndefined, rootDirs: rootDirsOrUndefined }, customCompilerExtensions, this.toSourceFilePath);
80
88
  }
81
89
  readFile(filePath) {
82
90
  return this.fileManager.readFile(filePath);
@@ -222,7 +230,7 @@ export class ProjectPrincipal {
222
230
  }
223
231
  }
224
232
  if (!this._visitor)
225
- this._visitor = buildVisitor(this.pluginVisitorObjects);
233
+ this._visitor = buildVisitor(this.pluginVisitorObjects, !!ignoreExportsUsedInFile);
226
234
  return _getImportsAndExports(filePath, sourceText, this.resolveModule, options, ignoreExportsUsedInFile, skipExports, this._visitor, this.pluginVisitorObjects.length > 0 ? this.pluginCtx : undefined, parseResult);
227
235
  }
228
236
  invalidateFile(filePath) {
@@ -116,6 +116,8 @@ export async function build({ chief, collector, counselor, deputy, principal, is
116
116
  for (const dep of getManifestImportDependencies(manifest))
117
117
  deputy.addReferencedDependency(name, dep);
118
118
  principal.addPaths(config.paths, dir);
119
+ if (compilerOptions.rootDirs)
120
+ principal.addRootDirs(compilerOptions.rootDirs);
119
121
  const inputsFromPlugins = await worker.runPlugins();
120
122
  for (const id of inputsFromPlugins)
121
123
  inputs.add(Object.assign(id, { skipExportsAnalysis: !id.allowIncludeExports }));
@@ -22,6 +22,7 @@ export interface CompilerOptions {
22
22
  name: string;
23
23
  } | string>;
24
24
  rootDir?: string;
25
+ rootDirs?: string[];
25
26
  skipDefaultLibCheck?: boolean;
26
27
  skipLibCheck?: boolean;
27
28
  sourceMap?: boolean;
@@ -7,7 +7,6 @@ import { dirname, isInNodeModules, resolve } from "../util/path.js";
7
7
  import { shouldIgnore } from "../util/tag.js";
8
8
  import { buildLineStarts, getLineAndCol, parseFile, shouldCountRefs, stripQuotes, } from "./visitors/helpers.js";
9
9
  import { buildJSDocTagLookup } from "./visitors/jsdoc.js";
10
- import { collectLocalRefs } from "./visitors/local-refs.js";
11
10
  import { walkAST } from "./visitors/walk.js";
12
11
  const jsDocImportRe = /import\(\s*['"]([^'"]+)['"]\s*\)(?:\.(\w+))?/g;
13
12
  const jsDocImportTagRe = /@import\s+(?:\{[^}]*\}|\*\s+as\s+\w+)\s+from\s+['"]([^'"]+)['"]/g;
@@ -244,7 +243,7 @@ const getImportsAndExports = (filePath, sourceText, resolveModule, options, igno
244
243
  pluginCtx.addScript = (s) => scripts.add(s);
245
244
  pluginCtx.addImport = (spec, pos, mod) => addImport(spec, undefined, undefined, undefined, pos, mod);
246
245
  }
247
- walkAST(result.program, sourceText, filePath, {
246
+ const localRefs = walkAST(result.program, sourceText, filePath, {
248
247
  lineStarts,
249
248
  skipExports,
250
249
  options,
@@ -261,6 +260,7 @@ const getImportsAndExports = (filePath, sourceText, resolveModule, options, igno
261
260
  importAliases,
262
261
  referencedInExport,
263
262
  skipBareExprRefs: !!ignoreExportsUsedInFile,
263
+ localRefs: ignoreExportsUsedInFile ? new Set() : undefined,
264
264
  destructuredExports,
265
265
  hasNodeModuleImport,
266
266
  resolveModule,
@@ -309,7 +309,6 @@ const getImportsAndExports = (filePath, sourceText, resolveModule, options, igno
309
309
  }
310
310
  }
311
311
  }
312
- const localRefs = ignoreExportsUsedInFile ? collectLocalRefs(result.program, localImportMap, exports) : undefined;
313
312
  for (const [id, item] of exports) {
314
313
  item.referencedIn = referencedInExport.get(id);
315
314
  if (localRefs && shouldCountRefs(ignoreExportsUsedInFile, item.type) && (localRefs.has(id) || item.isReExport)) {
@@ -2,4 +2,5 @@ import type { ToSourceFilePath } from '../util/to-source-path.ts';
2
2
  import type { ResolveModule } from './visitors/helpers.ts';
3
3
  export declare function createCustomModuleResolver(compilerOptions: {
4
4
  paths?: Record<string, string[]>;
5
+ rootDirs?: string[];
5
6
  }, customCompilerExtensions: string[], toSourceFilePath: ToSourceFilePath): ResolveModule;
@@ -12,6 +12,7 @@ export function createCustomModuleResolver(compilerOptions, customCompilerExtens
12
12
  const alias = convertPathsToAlias(compilerOptions.paths);
13
13
  const resolveSync = hasCustomExts ? _createSyncModuleResolver(extensions) : _resolveModuleSync;
14
14
  const resolveWithAlias = alias ? _createSyncModuleResolver(extensions, alias) : undefined;
15
+ const rootDirs = compilerOptions.rootDirs;
15
16
  function toSourcePath(resolvedFileName) {
16
17
  if (!hasCustomExts || !customCompilerExtensionsSet.has(extname(resolvedFileName))) {
17
18
  return toSourceFilePath(resolvedFileName) || resolvedFileName;
@@ -43,6 +44,22 @@ export function createCustomModuleResolver(compilerOptions, customCompilerExtens
43
44
  if (existsSync(candidate)) {
44
45
  return { resolvedFileName: candidate, isExternalLibraryImport: false };
45
46
  }
47
+ if (rootDirs && !isAbsolute(sanitizedSpecifier)) {
48
+ const containingDir = dirname(containingFile);
49
+ for (const srcRoot of rootDirs) {
50
+ if (!containingDir.startsWith(srcRoot))
51
+ continue;
52
+ const relPath = containingDir.slice(srcRoot.length);
53
+ for (const targetRoot of rootDirs) {
54
+ if (targetRoot === srcRoot)
55
+ continue;
56
+ const mapped = join(targetRoot, relPath, sanitizedSpecifier);
57
+ const resolved = resolveSync(mapped, containingFile);
58
+ if (resolved)
59
+ return toResult(resolved);
60
+ }
61
+ }
62
+ }
46
63
  }
47
64
  return timerify(resolveModuleName);
48
65
  }
@@ -133,7 +133,6 @@ export function handleExportNamed(node, s) {
133
133
  else if ((decl.type === 'FunctionDeclaration' || decl.type === 'TSDeclareFunction') && decl.id) {
134
134
  const fix = s.getFix(exportStart, exportStart + 7);
135
135
  s.addExport(decl.id.name, SYMBOL_TYPE.FUNCTION, decl.id.start, [], fix, false, s.getJSDocTags(exportStart));
136
- s.collectRefsInType(decl, decl.id.name, true);
137
136
  }
138
137
  else if (decl.type === 'ClassDeclaration' && decl.id) {
139
138
  const fix = s.getFix(exportStart, exportStart + 7);
@@ -148,6 +147,10 @@ export function handleExportNamed(node, s) {
148
147
  const fix = s.getTypeFix(exportStart, exportStart + 7);
149
148
  s.addExport(decl.id.name, SYMBOL_TYPE.INTERFACE, decl.id.start, [], fix, false, s.getJSDocTags(exportStart));
150
149
  s.collectRefsInType(decl.body, decl.id.name, false);
150
+ for (const ext of decl.extends ?? []) {
151
+ if (ext.expression?.type === 'Identifier')
152
+ s.addRefInExport(ext.expression.name, decl.id.name);
153
+ }
151
154
  }
152
155
  else if (decl.type === 'TSEnumDeclaration') {
153
156
  const members = extractEnumMembers(decl, s.options, s.lineStarts, s.getJSDocTags);
@@ -207,7 +210,6 @@ export function handleExportDefault(node, s) {
207
210
  if (decl.type === 'FunctionDeclaration') {
208
211
  type = SYMBOL_TYPE.FUNCTION;
209
212
  pos = decl.id?.start ?? decl.start;
210
- s.collectRefsInType(decl, 'default', false);
211
213
  }
212
214
  else if (decl.type === 'ClassDeclaration') {
213
215
  type = SYMBOL_TYPE.CLASS;
@@ -1,4 +1,4 @@
1
1
  import type { MemberExpression, JSXMemberExpression } from 'oxc-parser';
2
- import type { WalkState } from './walk.ts';
2
+ import { type WalkState } from './walk.ts';
3
3
  export declare function handleMemberExpression(node: MemberExpression, s: WalkState): void;
4
4
  export declare function handleJSXMemberExpression(node: JSXMemberExpression, s: WalkState): void;
@@ -1,13 +1,15 @@
1
1
  import { OPAQUE } from "../../constants.js";
2
2
  import { addValue } from "../../util/module-graph.js";
3
3
  import { getStringValue, isStringLiteral } from "./helpers.js";
4
+ import { isShadowed } from "./walk.js";
4
5
  export function handleMemberExpression(node, s) {
5
6
  if (node.object.type === 'MemberExpression' && node.object.object.type === 'MemberExpression') {
6
7
  s.chainedMemberExprs.add(node.object);
7
8
  }
8
9
  if (node.object.type === 'Identifier') {
9
10
  const localName = node.object.name;
10
- const _import = s.localImportMap.get(localName);
11
+ const shadowed = isShadowed(localName, node.object.start);
12
+ const _import = !shadowed ? s.localImportMap.get(localName) : undefined;
11
13
  if (_import) {
12
14
  const internalImport = s.internal.get(_import.filePath);
13
15
  if (internalImport) {
@@ -32,7 +34,7 @@ export function handleMemberExpression(node, s) {
32
34
  }
33
35
  }
34
36
  }
35
- else {
37
+ else if (!shadowed) {
36
38
  const memberName = node.computed === false && node.property.type === 'Identifier' ? node.property.name : undefined;
37
39
  if (memberName) {
38
40
  const exp = s.exports.get(localName);
@@ -71,34 +73,36 @@ export function handleMemberExpression(node, s) {
71
73
  !node.computed &&
72
74
  node.property.type === 'Identifier') {
73
75
  const rootName = node.object.object.name;
74
- const _import = s.localImportMap.get(rootName);
75
- if (_import) {
76
- const internalImport = s.internal.get(_import.filePath);
77
- if (internalImport) {
78
- const mid = node.object.property.name;
79
- s.addNsMemberRefs(internalImport, rootName, mid);
80
- if (!s.chainedMemberExprs.has(node)) {
81
- s.addNsMemberRefs(internalImport, rootName, `${mid}.${node.property.name}`);
76
+ if (!isShadowed(rootName, node.object.object.start)) {
77
+ const _import = s.localImportMap.get(rootName);
78
+ if (_import) {
79
+ const internalImport = s.internal.get(_import.filePath);
80
+ if (internalImport) {
81
+ const mid = node.object.property.name;
82
+ s.addNsMemberRefs(internalImport, rootName, mid);
83
+ if (!s.chainedMemberExprs.has(node)) {
84
+ s.addNsMemberRefs(internalImport, rootName, `${mid}.${node.property.name}`);
85
+ }
82
86
  }
83
- }
84
- if (!_import.isNamespace) {
85
- const mid = node.object.property.name;
86
- const _import = s.localImportMap.get(mid);
87
- if (_import) {
88
- const midImport = s.internal.get(_import.filePath);
89
- if (midImport)
90
- s.addNsMemberRefs(midImport, mid, node.property.name);
87
+ if (!_import.isNamespace) {
88
+ const mid = node.object.property.name;
89
+ const _import = s.localImportMap.get(mid);
90
+ if (_import) {
91
+ const midImport = s.internal.get(_import.filePath);
92
+ if (midImport)
93
+ s.addNsMemberRefs(midImport, mid, node.property.name);
94
+ }
91
95
  }
92
96
  }
93
- }
94
- else {
95
- const exp = s.exports.get(rootName);
96
- if (exp && exp.members.length > 0) {
97
- const mid = node.object.property.name;
98
- const dottedName = `${mid}.${node.property.name}`;
99
- for (const member of exp.members) {
100
- if (member.identifier === mid || member.identifier === dottedName)
101
- member.hasRefsInFile = true;
97
+ else {
98
+ const exp = s.exports.get(rootName);
99
+ if (exp && exp.members.length > 0) {
100
+ const mid = node.object.property.name;
101
+ const dottedName = `${mid}.${node.property.name}`;
102
+ for (const member of exp.members) {
103
+ if (member.identifier === mid || member.identifier === dottedName)
104
+ member.hasRefsInFile = true;
105
+ }
102
106
  }
103
107
  }
104
108
  }
@@ -113,28 +117,30 @@ export function handleMemberExpression(node, s) {
113
117
  !node.computed &&
114
118
  node.property.type === 'Identifier') {
115
119
  const rootName = node.object.object.object.name;
116
- const _import = s.localImportMap.get(rootName);
117
- if (_import) {
118
- const internalImport = s.internal.get(_import.filePath);
119
- if (internalImport) {
120
- const a = node.object.object.property.name;
121
- const b = node.object.property.name;
122
- const c = node.property.name;
123
- s.addNsMemberRefs(internalImport, rootName, a);
124
- s.addNsMemberRefs(internalImport, rootName, `${a}.${b}`);
125
- s.addNsMemberRefs(internalImport, rootName, `${a}.${b}.${c}`);
120
+ if (!isShadowed(rootName, node.object.object.object.start)) {
121
+ const _import = s.localImportMap.get(rootName);
122
+ if (_import) {
123
+ const internalImport = s.internal.get(_import.filePath);
124
+ if (internalImport) {
125
+ const a = node.object.object.property.name;
126
+ const b = node.object.property.name;
127
+ const c = node.property.name;
128
+ s.addNsMemberRefs(internalImport, rootName, a);
129
+ s.addNsMemberRefs(internalImport, rootName, `${a}.${b}`);
130
+ s.addNsMemberRefs(internalImport, rootName, `${a}.${b}.${c}`);
131
+ }
126
132
  }
127
- }
128
- else {
129
- const exp = s.exports.get(rootName);
130
- if (exp && exp.members.length > 0) {
131
- const a = node.object.object.property.name;
132
- const b = node.object.property.name;
133
- const c = node.property.name;
134
- const dottedName = `${a}.${b}.${c}`;
135
- for (const member of exp.members) {
136
- if (member.identifier === a || member.identifier === `${a}.${b}` || member.identifier === dottedName)
137
- member.hasRefsInFile = true;
133
+ else {
134
+ const exp = s.exports.get(rootName);
135
+ if (exp && exp.members.length > 0) {
136
+ const a = node.object.object.property.name;
137
+ const b = node.object.property.name;
138
+ const c = node.property.name;
139
+ const dottedName = `${a}.${b}.${c}`;
140
+ for (const member of exp.members) {
141
+ if (member.identifier === a || member.identifier === `${a}.${b}` || member.identifier === dottedName)
142
+ member.hasRefsInFile = true;
143
+ }
138
144
  }
139
145
  }
140
146
  }
@@ -30,6 +30,7 @@ interface WalkContext {
30
30
  }>>;
31
31
  referencedInExport: Map<string, Set<string>>;
32
32
  skipBareExprRefs: boolean;
33
+ localRefs: Set<string> | undefined;
33
34
  destructuredExports: Set<string>;
34
35
  hasNodeModuleImport: boolean;
35
36
  resolveModule: ResolveModule;
@@ -50,12 +51,18 @@ export interface WalkState extends WalkContext {
50
51
  chainedMemberExprs: WeakSet<object>;
51
52
  currentVarDeclStart: number;
52
53
  nsRanges: [number, number][];
54
+ scopeDepth: number;
55
+ scopeStarts: number[];
56
+ scopeEnds: number[];
57
+ shadowScopes: Map<string, [number, number][]>;
53
58
  addExport: (identifier: string, type: SymbolType, pos: number, members: ExportMember[], fix: Fix, isReExport: boolean, jsDocTags: Set<string>) => void;
54
59
  getFix: (start: number, end: number, flags?: number) => Fix;
55
60
  getTypeFix: (start: number, end: number) => Fix;
56
61
  collectRefsInType: (node: any, exportName: string, signatureOnly: boolean) => void;
62
+ addRefInExport: (name: string, exportName: string) => void;
57
63
  isInNamespace: (node: Span) => boolean;
58
64
  }
59
- export declare function buildVisitor(pluginVisitorObjects: PluginVisitorObject[]): Visitor;
60
- export declare function walkAST(program: Program, sourceText: string, filePath: string, ctx: WalkContext): void;
65
+ export declare const isShadowed: (name: string, pos: number) => boolean;
66
+ export declare function buildVisitor(pluginVisitorObjects: PluginVisitorObject[], includeLocalRefs?: boolean): Visitor;
67
+ export declare function walkAST(program: Program, sourceText: string, filePath: string, ctx: WalkContext): Set<string> | undefined;
61
68
  export {};
@@ -86,8 +86,50 @@ const _collectRefsInType = (node, exportName, signatureOnly) => {
86
86
  }
87
87
  }
88
88
  };
89
+ const _addRefInExport = (name, exportName) => {
90
+ const refs = state.referencedInExport.get(name);
91
+ if (refs)
92
+ refs.add(exportName);
93
+ else
94
+ state.referencedInExport.set(name, new Set([exportName]));
95
+ };
89
96
  const _isInNamespace = (node) => state.nsRanges.length > 0 && state.nsRanges.some(([start, end]) => node.start >= start && node.end <= end);
97
+ export const isShadowed = (name, pos) => {
98
+ if (state.shadowScopes.size === 0)
99
+ return false;
100
+ const ranges = state.shadowScopes.get(name);
101
+ if (!ranges)
102
+ return false;
103
+ if (state.localImportMap.get(name)?.isDynamicImport)
104
+ return false;
105
+ for (const range of ranges) {
106
+ if (pos >= range[0] && pos <= range[1])
107
+ return true;
108
+ }
109
+ return false;
110
+ };
111
+ const _addLocalRef = (name, pos) => {
112
+ if (!state.localImportMap.has(name) && !isShadowed(name, pos))
113
+ state.localRefs.add(name);
114
+ };
115
+ const _addShadow = (name) => {
116
+ const i = state.scopeDepth - 1;
117
+ const range = [state.scopeStarts[i], state.scopeEnds[i]];
118
+ const ranges = state.shadowScopes.get(name);
119
+ if (ranges)
120
+ ranges.push(range);
121
+ else
122
+ state.shadowScopes.set(name, [range]);
123
+ };
90
124
  const coreVisitorObject = {
125
+ BlockStatement(node) {
126
+ state.scopeStarts[state.scopeDepth] = node.start;
127
+ state.scopeEnds[state.scopeDepth] = node.end;
128
+ state.scopeDepth++;
129
+ },
130
+ 'BlockStatement:exit'() {
131
+ state.scopeDepth--;
132
+ },
91
133
  TSModuleDeclaration(node) {
92
134
  state.nsRanges.push([node.start, node.end]);
93
135
  },
@@ -96,14 +138,27 @@ const coreVisitorObject = {
96
138
  state.localDeclarationTypes.set(node.id.name, SYMBOL_TYPE.CLASS);
97
139
  },
98
140
  FunctionDeclaration(node) {
99
- if (node.id?.name)
141
+ if (node.id?.name) {
100
142
  state.localDeclarationTypes.set(node.id.name, SYMBOL_TYPE.FUNCTION);
143
+ if (state.scopeDepth > 0)
144
+ _addShadow(node.id.name);
145
+ }
101
146
  },
102
147
  VariableDeclaration(node) {
103
148
  state.currentVarDeclStart = node.start;
104
- for (const decl of node.declarations) {
105
- if (decl.id.type === 'Identifier')
106
- state.localDeclarationTypes.set(decl.id.name, SYMBOL_TYPE.VARIABLE);
149
+ if (state.scopeDepth > 0) {
150
+ for (const decl of node.declarations) {
151
+ if (decl.id.type === 'Identifier') {
152
+ state.localDeclarationTypes.set(decl.id.name, SYMBOL_TYPE.VARIABLE);
153
+ _addShadow(decl.id.name);
154
+ }
155
+ }
156
+ }
157
+ else {
158
+ for (const decl of node.declarations) {
159
+ if (decl.id.type === 'Identifier')
160
+ state.localDeclarationTypes.set(decl.id.name, SYMBOL_TYPE.VARIABLE);
161
+ }
107
162
  }
108
163
  },
109
164
  TSEnumDeclaration(node) {
@@ -141,7 +196,7 @@ const coreVisitorObject = {
141
196
  handleJSXMemberExpression(node, state);
142
197
  },
143
198
  ForInStatement(node) {
144
- if (node.right.type === 'Identifier') {
199
+ if (node.right.type === 'Identifier' && !isShadowed(node.right.name, node.right.start)) {
145
200
  const _import = state.localImportMap.get(node.right.name);
146
201
  if (_import?.isNamespace) {
147
202
  const internalImport = state.internal.get(_import.filePath);
@@ -151,7 +206,7 @@ const coreVisitorObject = {
151
206
  }
152
207
  },
153
208
  ForOfStatement(node) {
154
- if (node.right.type === 'Identifier') {
209
+ if (node.right.type === 'Identifier' && !isShadowed(node.right.name, node.right.start)) {
155
210
  const _import = state.localImportMap.get(node.right.name);
156
211
  if (_import?.isNamespace) {
157
212
  const internalImport = state.internal.get(_import.filePath);
@@ -168,7 +223,7 @@ const coreVisitorObject = {
168
223
  parts.unshift(left.right.name);
169
224
  left = left.left;
170
225
  }
171
- if (left.type === 'Identifier') {
226
+ if (left.type === 'Identifier' && !isShadowed(left.name, left.start)) {
172
227
  const rootName = left.name;
173
228
  const _import = state.localImportMap.get(rootName);
174
229
  if (_import) {
@@ -204,22 +259,26 @@ const coreVisitorObject = {
204
259
  TSTypeReference(node) {
205
260
  if (node.typeName.type === 'Identifier') {
206
261
  const name = node.typeName.name;
207
- const _import = state.localImportMap.get(name);
208
- if (_import) {
209
- const internalImport = state.internal.get(_import.filePath);
210
- if (internalImport)
211
- internalImport.refs.add(name);
262
+ if (!isShadowed(name, node.typeName.start)) {
263
+ const _import = state.localImportMap.get(name);
264
+ if (_import) {
265
+ const internalImport = state.internal.get(_import.filePath);
266
+ if (internalImport)
267
+ internalImport.refs.add(name);
268
+ }
212
269
  }
213
270
  }
214
271
  },
215
272
  TSTypeQuery(node) {
216
273
  if (node.exprName.type === 'Identifier') {
217
274
  const name = node.exprName.name;
218
- const _import = state.localImportMap.get(name);
219
- if (_import) {
220
- const internalImport = state.internal.get(_import.filePath);
221
- if (internalImport)
222
- internalImport.refs.add(name);
275
+ if (!isShadowed(name, node.exprName.start)) {
276
+ const _import = state.localImportMap.get(name);
277
+ if (_import) {
278
+ const internalImport = state.internal.get(_import.filePath);
279
+ if (internalImport)
280
+ internalImport.refs.add(name);
281
+ }
223
282
  }
224
283
  }
225
284
  },
@@ -264,9 +323,235 @@ const coreVisitorObject = {
264
323
  }
265
324
  },
266
325
  };
267
- export function buildVisitor(pluginVisitorObjects) {
268
- if (pluginVisitorObjects.length === 0)
269
- return new Visitor(coreVisitorObject);
326
+ const localRefsVisitorObject = {
327
+ ClassDeclaration(node) {
328
+ if (node.superClass?.type === 'Identifier')
329
+ _addLocalRef(node.superClass.name, node.superClass.start);
330
+ for (const impl of node.implements ?? []) {
331
+ if (impl.expression?.type === 'Identifier')
332
+ _addLocalRef(impl.expression.name, impl.expression.start);
333
+ }
334
+ },
335
+ TSInterfaceDeclaration(node) {
336
+ for (const ext of node.extends ?? []) {
337
+ if (ext.expression?.type === 'Identifier')
338
+ _addLocalRef(ext.expression.name, ext.expression.start);
339
+ }
340
+ },
341
+ Property(node) {
342
+ if (node.value?.type === 'Identifier')
343
+ _addLocalRef(node.value.name, node.value.start);
344
+ },
345
+ ReturnStatement(node) {
346
+ if (node.argument?.type === 'Identifier')
347
+ _addLocalRef(node.argument.name, node.argument.start);
348
+ },
349
+ AssignmentExpression(node) {
350
+ if (node.right?.type === 'Identifier')
351
+ _addLocalRef(node.right.name, node.right.start);
352
+ },
353
+ SpreadElement(node) {
354
+ if (node.argument?.type === 'Identifier')
355
+ _addLocalRef(node.argument.name, node.argument.start);
356
+ },
357
+ ConditionalExpression(node) {
358
+ if (node.test?.type === 'Identifier')
359
+ _addLocalRef(node.test.name, node.test.start);
360
+ if (node.consequent?.type === 'Identifier')
361
+ _addLocalRef(node.consequent.name, node.consequent.start);
362
+ if (node.alternate?.type === 'Identifier')
363
+ _addLocalRef(node.alternate.name, node.alternate.start);
364
+ },
365
+ ArrayExpression(node) {
366
+ for (const el of node.elements ?? []) {
367
+ if (el?.type === 'Identifier')
368
+ _addLocalRef(el.name, el.start);
369
+ }
370
+ },
371
+ TemplateLiteral(node) {
372
+ for (const expr of node.expressions ?? []) {
373
+ if (expr.type === 'Identifier')
374
+ _addLocalRef(expr.name, expr.start);
375
+ }
376
+ },
377
+ BinaryExpression(node) {
378
+ if (node.left?.type === 'Identifier')
379
+ _addLocalRef(node.left.name, node.left.start);
380
+ if (node.right?.type === 'Identifier')
381
+ _addLocalRef(node.right.name, node.right.start);
382
+ },
383
+ LogicalExpression(node) {
384
+ if (node.left?.type === 'Identifier')
385
+ _addLocalRef(node.left.name, node.left.start);
386
+ if (node.right?.type === 'Identifier')
387
+ _addLocalRef(node.right.name, node.right.start);
388
+ },
389
+ UnaryExpression(node) {
390
+ if (node.argument?.type === 'Identifier')
391
+ _addLocalRef(node.argument.name, node.argument.start);
392
+ },
393
+ SwitchStatement(node) {
394
+ if (node.discriminant?.type === 'Identifier')
395
+ _addLocalRef(node.discriminant.name, node.discriminant.start);
396
+ for (const c of node.cases ?? []) {
397
+ if (c.test?.type === 'Identifier')
398
+ _addLocalRef(c.test.name, c.test.start);
399
+ }
400
+ },
401
+ IfStatement(node) {
402
+ if (node.test?.type === 'Identifier')
403
+ _addLocalRef(node.test.name, node.test.start);
404
+ },
405
+ ThrowStatement(node) {
406
+ if (node.argument?.type === 'Identifier')
407
+ _addLocalRef(node.argument.name, node.argument.start);
408
+ },
409
+ WhileStatement(node) {
410
+ if (node.test?.type === 'Identifier')
411
+ _addLocalRef(node.test.name, node.test.start);
412
+ },
413
+ DoWhileStatement(node) {
414
+ if (node.test?.type === 'Identifier')
415
+ _addLocalRef(node.test.name, node.test.start);
416
+ },
417
+ YieldExpression(node) {
418
+ if (node.argument?.type === 'Identifier')
419
+ _addLocalRef(node.argument.name, node.argument.start);
420
+ },
421
+ AwaitExpression(node) {
422
+ if (node.argument?.type === 'Identifier')
423
+ _addLocalRef(node.argument.name, node.argument.start);
424
+ },
425
+ ArrowFunctionExpression(node) {
426
+ if (node.body?.type === 'Identifier')
427
+ _addLocalRef(node.body.name, node.body.start);
428
+ },
429
+ AssignmentPattern(node) {
430
+ if (node.right?.type === 'Identifier')
431
+ _addLocalRef(node.right.name, node.right.start);
432
+ },
433
+ SequenceExpression(node) {
434
+ for (const expr of node.expressions ?? []) {
435
+ if (expr.type === 'Identifier')
436
+ _addLocalRef(expr.name, expr.start);
437
+ }
438
+ },
439
+ TSAsExpression(node) {
440
+ if (node.expression?.type === 'Identifier')
441
+ _addLocalRef(node.expression.name, node.expression.start);
442
+ },
443
+ TSSatisfiesExpression(node) {
444
+ if (node.expression?.type === 'Identifier')
445
+ _addLocalRef(node.expression.name, node.expression.start);
446
+ },
447
+ TSNonNullExpression(node) {
448
+ if (node.expression?.type === 'Identifier')
449
+ _addLocalRef(node.expression.name, node.expression.start);
450
+ },
451
+ TSTypeAssertion(node) {
452
+ if (node.expression?.type === 'Identifier')
453
+ _addLocalRef(node.expression.name, node.expression.start);
454
+ },
455
+ ParenthesizedExpression(node) {
456
+ if (node.expression?.type === 'Identifier')
457
+ _addLocalRef(node.expression.name, node.expression.start);
458
+ },
459
+ PropertyDefinition(node) {
460
+ if (node.value?.type === 'Identifier')
461
+ _addLocalRef(node.value.name, node.value.start);
462
+ },
463
+ ForInStatement(node) {
464
+ if (node.right?.type === 'Identifier')
465
+ _addLocalRef(node.right.name, node.right.start);
466
+ },
467
+ ForOfStatement(node) {
468
+ if (node.right?.type === 'Identifier')
469
+ _addLocalRef(node.right.name, node.right.start);
470
+ },
471
+ JSXOpeningElement(node) {
472
+ if (node.name?.type === 'JSXIdentifier')
473
+ _addLocalRef(node.name.name, node.name.start);
474
+ for (const attr of node.attributes ?? []) {
475
+ if (attr.type === 'JSXSpreadAttribute' && attr.argument?.type === 'Identifier')
476
+ _addLocalRef(attr.argument.name, attr.argument.start);
477
+ }
478
+ },
479
+ JSXExpressionContainer(node) {
480
+ if (node.expression?.type === 'Identifier')
481
+ _addLocalRef(node.expression.name, node.expression.start);
482
+ },
483
+ VariableDeclarator(node) {
484
+ if (node.init?.type === 'Identifier')
485
+ _addLocalRef(node.init.name, node.init.start);
486
+ },
487
+ ExpressionStatement(node) {
488
+ if (node.expression?.type === 'Identifier')
489
+ _addLocalRef(node.expression.name, node.expression.start);
490
+ },
491
+ CallExpression(node) {
492
+ if (node.callee?.type === 'Identifier')
493
+ _addLocalRef(node.callee.name, node.callee.start);
494
+ for (const arg of node.arguments ?? []) {
495
+ if (arg.type === 'Identifier')
496
+ _addLocalRef(arg.name, arg.start);
497
+ }
498
+ },
499
+ NewExpression(node) {
500
+ if (node.callee?.type === 'Identifier')
501
+ _addLocalRef(node.callee.name, node.callee.start);
502
+ for (const arg of node.arguments ?? []) {
503
+ if (arg.type === 'Identifier')
504
+ _addLocalRef(arg.name, arg.start);
505
+ }
506
+ },
507
+ MemberExpression(node) {
508
+ if (node.object?.type === 'Identifier')
509
+ _addLocalRef(node.object.name, node.object.start);
510
+ if (node.computed && node.property?.type === 'Identifier')
511
+ _addLocalRef(node.property.name, node.property.start);
512
+ },
513
+ TaggedTemplateExpression(node) {
514
+ if (node.tag?.type === 'Identifier')
515
+ _addLocalRef(node.tag.name, node.tag.start);
516
+ },
517
+ TSQualifiedName(node) {
518
+ let left = node;
519
+ const parts = [];
520
+ while (left.type === 'TSQualifiedName') {
521
+ if (left.right.type === 'Identifier')
522
+ parts.unshift(left.right.name);
523
+ left = left.left;
524
+ }
525
+ if (left.type === 'Identifier') {
526
+ const rootName = left.name;
527
+ if (!state.localImportMap.has(rootName) && !isShadowed(rootName, left.start) && parts.length > 0) {
528
+ const exp = state.exports.get(rootName);
529
+ if (exp) {
530
+ state.localRefs.add(rootName);
531
+ for (const member of exp.members) {
532
+ if (member.identifier === parts[0])
533
+ member.hasRefsInFile = true;
534
+ }
535
+ }
536
+ }
537
+ }
538
+ },
539
+ TSTypeReference(node) {
540
+ if (node.typeName?.type === 'Identifier') {
541
+ const name = node.typeName.name;
542
+ if (!state.localImportMap.has(name))
543
+ _addLocalRef(name, node.typeName.start);
544
+ }
545
+ },
546
+ TSTypeQuery(node) {
547
+ if (node.exprName?.type === 'Identifier') {
548
+ const name = node.exprName.name;
549
+ if (!state.localImportMap.has(name))
550
+ _addLocalRef(name, node.exprName.start);
551
+ }
552
+ },
553
+ };
554
+ export function buildVisitor(pluginVisitorObjects, includeLocalRefs) {
270
555
  const handlerLists = new Map();
271
556
  const coreHandlers = coreVisitorObject;
272
557
  for (const key in coreHandlers) {
@@ -274,10 +559,12 @@ export function buildVisitor(pluginVisitorObjects) {
274
559
  if (fn)
275
560
  handlerLists.set(key, [fn]);
276
561
  }
277
- for (const obj of pluginVisitorObjects) {
278
- const handlers = obj;
279
- for (const key in handlers) {
280
- const fn = handlers[key];
562
+ const extras = includeLocalRefs
563
+ ? [localRefsVisitorObject, ...pluginVisitorObjects]
564
+ : pluginVisitorObjects;
565
+ for (const obj of extras) {
566
+ for (const key in obj) {
567
+ const fn = obj[key];
281
568
  if (!fn)
282
569
  continue;
283
570
  const list = handlerLists.get(key);
@@ -287,6 +574,8 @@ export function buildVisitor(pluginVisitorObjects) {
287
574
  handlerLists.set(key, [fn]);
288
575
  }
289
576
  }
577
+ if (extras.length === 0)
578
+ return new Visitor(coreVisitorObject);
290
579
  const merged = {};
291
580
  for (const [key, list] of handlerLists) {
292
581
  if (list.length === 1) {
@@ -317,10 +606,15 @@ export function walkAST(program, sourceText, filePath, ctx) {
317
606
  chainedMemberExprs: new WeakSet(),
318
607
  currentVarDeclStart: -1,
319
608
  nsRanges: [],
609
+ scopeDepth: 0,
610
+ scopeStarts: [],
611
+ scopeEnds: [],
612
+ shadowScopes: new Map(),
320
613
  addExport: _addExport,
321
614
  getFix: _getFix,
322
615
  getTypeFix: _getTypeFix,
323
616
  collectRefsInType: _collectRefsInType,
617
+ addRefInExport: _addRefInExport,
324
618
  isInNamespace: _isInNamespace,
325
619
  };
326
620
  ctx.visitor.visit(program);
@@ -357,5 +651,7 @@ export function walkAST(program, sourceText, filePath, ctx) {
357
651
  item.hasRefsInFile = true;
358
652
  }
359
653
  }
654
+ const localRefs = state.localRefs;
360
655
  state = undefined;
656
+ return localRefs;
361
657
  }
@@ -47,6 +47,19 @@ const expandFileNames = (dir, compilerOptions, include, exclude, files) => {
47
47
  }
48
48
  return result;
49
49
  };
50
+ const findRootDirsBase = (tsConfigFilePath) => {
51
+ try {
52
+ const raw = JSON.parse(stripJsonComments(readFileSync(tsConfigFilePath, 'utf8')));
53
+ if (raw.compilerOptions?.rootDirs)
54
+ return dirname(tsConfigFilePath);
55
+ if (raw.extends) {
56
+ const extPath = join(dirname(tsConfigFilePath), raw.extends);
57
+ return findRootDirsBase(extPath);
58
+ }
59
+ }
60
+ catch { }
61
+ return undefined;
62
+ };
50
63
  const resolveConfig = (tsConfigFilePath) => {
51
64
  try {
52
65
  return parseTsconfig(tsConfigFilePath);
@@ -77,6 +90,10 @@ export const loadTSConfig = async (tsConfigFilePath) => {
77
90
  if (compilerOptions.paths) {
78
91
  compilerOptions.pathsBasePath ??= dir;
79
92
  }
93
+ if (compilerOptions.rootDirs) {
94
+ const rootDirsBase = findRootDirsBase(tsConfigFilePath) ?? dir;
95
+ compilerOptions.rootDirs = compilerOptions.rootDirs.map((d) => isAbsolute(d) ? d : join(rootDirsBase, d));
96
+ }
80
97
  const include = resolvePatterns(config.include, dir, true);
81
98
  const exclude = resolvePatterns(config.exclude, dir, true);
82
99
  const files = resolvePatterns(config.files, dir);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "6.0.1";
1
+ export declare const version = "6.0.2";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '6.0.1';
1
+ export const version = '6.0.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "6.0.1",
3
+ "version": "6.0.2",
4
4
  "description": "Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects",
5
5
  "keywords": [
6
6
  "analysis",
@@ -80,7 +80,7 @@
80
80
  "@nodelib/fs.walk": "^1.2.3",
81
81
  "fast-glob": "^3.3.3",
82
82
  "formatly": "^0.3.0",
83
- "get-tsconfig": "4.13.6",
83
+ "get-tsconfig": "4.13.7",
84
84
  "jiti": "^2.6.0",
85
85
  "minimist": "^1.2.8",
86
86
  "oxc-parser": "^0.120.0",
@@ -1,7 +0,0 @@
1
- import { type Program } from 'oxc-parser';
2
- import type { Export } from '../../types/module-graph.ts';
3
- export declare function collectLocalRefs(program: Program, localImportMap: Map<string, {
4
- importedName: string;
5
- filePath: string;
6
- isNamespace: boolean;
7
- }>, fileExports: Map<string, Export>): Set<string>;
@@ -1,243 +0,0 @@
1
- import { Visitor } from 'oxc-parser';
2
- let refs;
3
- let importNames;
4
- let exportsMap;
5
- const add = (name) => {
6
- if (!importNames.has(name))
7
- refs.add(name);
8
- };
9
- const visitor = new Visitor({
10
- ClassDeclaration(node) {
11
- if (node.superClass?.type === 'Identifier')
12
- add(node.superClass.name);
13
- for (const impl of node.implements ?? []) {
14
- if (impl.expression?.type === 'Identifier')
15
- add(impl.expression.name);
16
- }
17
- },
18
- TSInterfaceDeclaration(node) {
19
- for (const ext of node.extends ?? []) {
20
- if (ext.expression?.type === 'Identifier')
21
- add(ext.expression.name);
22
- }
23
- },
24
- Property(node) {
25
- if (node.value?.type === 'Identifier')
26
- add(node.value.name);
27
- },
28
- ReturnStatement(node) {
29
- if (node.argument?.type === 'Identifier')
30
- add(node.argument.name);
31
- },
32
- AssignmentExpression(node) {
33
- if (node.right?.type === 'Identifier')
34
- add(node.right.name);
35
- },
36
- SpreadElement(node) {
37
- if (node.argument?.type === 'Identifier')
38
- add(node.argument.name);
39
- },
40
- ConditionalExpression(node) {
41
- if (node.test?.type === 'Identifier')
42
- add(node.test.name);
43
- if (node.consequent?.type === 'Identifier')
44
- add(node.consequent.name);
45
- if (node.alternate?.type === 'Identifier')
46
- add(node.alternate.name);
47
- },
48
- ArrayExpression(node) {
49
- for (const el of node.elements ?? []) {
50
- if (el?.type === 'Identifier')
51
- add(el.name);
52
- }
53
- },
54
- TemplateLiteral(node) {
55
- for (const expr of node.expressions ?? []) {
56
- if (expr.type === 'Identifier')
57
- add(expr.name);
58
- }
59
- },
60
- BinaryExpression(node) {
61
- if (node.left?.type === 'Identifier')
62
- add(node.left.name);
63
- if (node.right?.type === 'Identifier')
64
- add(node.right.name);
65
- },
66
- LogicalExpression(node) {
67
- if (node.left?.type === 'Identifier')
68
- add(node.left.name);
69
- if (node.right?.type === 'Identifier')
70
- add(node.right.name);
71
- },
72
- UnaryExpression(node) {
73
- if (node.argument?.type === 'Identifier')
74
- add(node.argument.name);
75
- },
76
- SwitchStatement(node) {
77
- if (node.discriminant?.type === 'Identifier')
78
- add(node.discriminant.name);
79
- for (const c of node.cases ?? []) {
80
- if (c.test?.type === 'Identifier')
81
- add(c.test.name);
82
- }
83
- },
84
- IfStatement(node) {
85
- if (node.test?.type === 'Identifier')
86
- add(node.test.name);
87
- },
88
- ThrowStatement(node) {
89
- if (node.argument?.type === 'Identifier')
90
- add(node.argument.name);
91
- },
92
- WhileStatement(node) {
93
- if (node.test?.type === 'Identifier')
94
- add(node.test.name);
95
- },
96
- DoWhileStatement(node) {
97
- if (node.test?.type === 'Identifier')
98
- add(node.test.name);
99
- },
100
- YieldExpression(node) {
101
- if (node.argument?.type === 'Identifier')
102
- add(node.argument.name);
103
- },
104
- AwaitExpression(node) {
105
- if (node.argument?.type === 'Identifier')
106
- add(node.argument.name);
107
- },
108
- ArrowFunctionExpression(node) {
109
- if (node.body?.type === 'Identifier')
110
- add(node.body.name);
111
- },
112
- AssignmentPattern(node) {
113
- if (node.right?.type === 'Identifier')
114
- add(node.right.name);
115
- },
116
- SequenceExpression(node) {
117
- for (const expr of node.expressions ?? []) {
118
- if (expr.type === 'Identifier')
119
- add(expr.name);
120
- }
121
- },
122
- TSAsExpression(node) {
123
- if (node.expression?.type === 'Identifier')
124
- add(node.expression.name);
125
- },
126
- TSSatisfiesExpression(node) {
127
- if (node.expression?.type === 'Identifier')
128
- add(node.expression.name);
129
- },
130
- TSNonNullExpression(node) {
131
- if (node.expression?.type === 'Identifier')
132
- add(node.expression.name);
133
- },
134
- TSTypeAssertion(node) {
135
- if (node.expression?.type === 'Identifier')
136
- add(node.expression.name);
137
- },
138
- ParenthesizedExpression(node) {
139
- if (node.expression?.type === 'Identifier')
140
- add(node.expression.name);
141
- },
142
- PropertyDefinition(node) {
143
- if (node.value?.type === 'Identifier')
144
- add(node.value.name);
145
- },
146
- ForInStatement(node) {
147
- if (node.right?.type === 'Identifier')
148
- add(node.right.name);
149
- },
150
- ForOfStatement(node) {
151
- if (node.right?.type === 'Identifier')
152
- add(node.right.name);
153
- },
154
- JSXOpeningElement(node) {
155
- if (node.name?.type === 'JSXIdentifier')
156
- add(node.name.name);
157
- for (const attr of node.attributes ?? []) {
158
- if (attr.type === 'JSXSpreadAttribute' && attr.argument?.type === 'Identifier')
159
- add(attr.argument.name);
160
- }
161
- },
162
- JSXExpressionContainer(node) {
163
- if (node.expression?.type === 'Identifier')
164
- add(node.expression.name);
165
- },
166
- VariableDeclarator(node) {
167
- if (node.init?.type === 'Identifier')
168
- add(node.init.name);
169
- },
170
- ExpressionStatement(node) {
171
- if (node.expression?.type === 'Identifier')
172
- add(node.expression.name);
173
- },
174
- CallExpression(node) {
175
- if (node.callee?.type === 'Identifier')
176
- add(node.callee.name);
177
- for (const arg of node.arguments ?? []) {
178
- if (arg.type === 'Identifier')
179
- add(arg.name);
180
- }
181
- },
182
- NewExpression(node) {
183
- if (node.callee?.type === 'Identifier')
184
- add(node.callee.name);
185
- for (const arg of node.arguments ?? []) {
186
- if (arg.type === 'Identifier')
187
- add(arg.name);
188
- }
189
- },
190
- MemberExpression(node) {
191
- if (node.object?.type === 'Identifier')
192
- add(node.object.name);
193
- if (node.computed && node.property?.type === 'Identifier')
194
- add(node.property.name);
195
- },
196
- TaggedTemplateExpression(node) {
197
- if (node.tag?.type === 'Identifier')
198
- add(node.tag.name);
199
- },
200
- TSQualifiedName(node) {
201
- let left = node;
202
- const parts = [];
203
- while (left.type === 'TSQualifiedName') {
204
- if (left.right.type === 'Identifier')
205
- parts.unshift(left.right.name);
206
- left = left.left;
207
- }
208
- if (left.type === 'Identifier') {
209
- const rootName = left.name;
210
- if (!importNames.has(rootName) && parts.length > 0) {
211
- const exp = exportsMap.get(rootName);
212
- if (exp) {
213
- refs.add(rootName);
214
- for (const member of exp.members) {
215
- if (member.identifier === parts[0])
216
- member.hasRefsInFile = true;
217
- }
218
- }
219
- }
220
- }
221
- },
222
- TSTypeReference(node) {
223
- if (node.typeName?.type === 'Identifier') {
224
- const name = node.typeName.name;
225
- if (!importNames.has(name))
226
- refs.add(name);
227
- }
228
- },
229
- TSTypeQuery(node) {
230
- if (node.exprName?.type === 'Identifier') {
231
- const name = node.exprName.name;
232
- if (!importNames.has(name))
233
- refs.add(name);
234
- }
235
- },
236
- });
237
- export function collectLocalRefs(program, localImportMap, fileExports) {
238
- refs = new Set();
239
- importNames = localImportMap;
240
- exportsMap = fileExports;
241
- visitor.visit(program);
242
- return refs;
243
- }