knip 6.0.3 → 6.0.5

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.
@@ -60,7 +60,7 @@ export const analyze = async ({ analyzedFiles, counselor, chief, collector, depu
60
60
  if (isIgnored &&
61
61
  (isReferenced || isReferencedInUsedExport(exportedItem, filePath, isIncludeEntryExports))) {
62
62
  for (const tagName of exportedItem.jsDocTags) {
63
- if (options.tags[1].includes(tagName.replace(/^@/, ''))) {
63
+ if (options.tags[1].includes(tagName)) {
64
64
  collector.addTagHint({ type: 'tag', filePath, identifier, tagName });
65
65
  }
66
66
  }
@@ -114,7 +114,7 @@ export const analyze = async ({ analyzedFiles, counselor, chief, collector, depu
114
114
  }
115
115
  else if (isIgnored) {
116
116
  for (const tagName of exportedItem.jsDocTags) {
117
- if (options.tags[1].includes(tagName.replace(/^@/, ''))) {
117
+ if (options.tags[1].includes(tagName)) {
118
118
  collector.addTagHint({ type: 'tag', filePath, identifier: id, tagName });
119
119
  }
120
120
  }
@@ -45,7 +45,7 @@ const resolveConfig = async (localConfig, options) => {
45
45
  commands = [target.options.command];
46
46
  else if (target.options?.commands)
47
47
  commands = target.options.commands.map(commandConfig => typeof commandConfig === 'string' ? commandConfig : commandConfig.command);
48
- const cwd = target.options?.cwd ? join(options.cwd, target.options.cwd) : undefined;
48
+ const cwd = target.options?.cwd ? join(options.cwd, target.options.cwd) : options.cwd;
49
49
  return options.getInputsFromScripts(commands, { cwd });
50
50
  });
51
51
  const configInputs = targets.flatMap(target => {
@@ -27,7 +27,11 @@ export default ({ graph, explorer, options, workspaceFilePathFilter }) => {
27
27
  console.log(line);
28
28
  }
29
29
  else {
30
- const nodes = explorer.buildExportsTree({ filePath: options.traceFile, identifier: options.traceExport });
30
+ let nodes = explorer.buildExportsTree({ filePath: options.traceFile, identifier: options.traceExport });
31
+ if (nodes.length === 0 && options.traceExport?.includes('.')) {
32
+ const nsName = options.traceExport.substring(0, options.traceExport.indexOf('.'));
33
+ nodes = explorer.buildExportsTree({ filePath: options.traceFile, identifier: nsName });
34
+ }
31
35
  nodes.sort((a, b) => a.filePath.localeCompare(b.filePath) || a.identifier.localeCompare(b.identifier));
32
36
  const toRel = (path) => toRelative(path, options.cwd);
33
37
  const isReferenced = (node) => {
@@ -37,7 +41,18 @@ export default ({ graph, explorer, options, workspaceFilePathFilter }) => {
37
41
  return true;
38
42
  return !!graph.get(node.filePath)?.exports.get(node.identifier)?.hasRefsInFile;
39
43
  };
40
- for (const node of nodes)
41
- console.log(formatTrace(node, toRel, isReferenced(node)));
44
+ for (const node of nodes) {
45
+ const exp = graph.get(node.filePath)?.exports.get(node.identifier);
46
+ let memberStatuses;
47
+ if (exp && exp.members.length > 0) {
48
+ memberStatuses = [];
49
+ for (const m of exp.members) {
50
+ const id = `${node.identifier}.${m.identifier}`;
51
+ const referenced = m.hasRefsInFile || explorer.isReferenced(node.filePath, id, { includeEntryExports: true })[0];
52
+ memberStatuses.push({ identifier: m.identifier, referenced });
53
+ }
54
+ }
55
+ console.log(formatTrace(node, toRel, isReferenced(node), memberStatuses));
56
+ }
42
57
  }
43
58
  };
@@ -47,7 +47,7 @@ export interface ExportMember extends Position {
47
47
  readonly identifier: Identifier;
48
48
  readonly type: SymbolType;
49
49
  readonly fix: Fix;
50
- readonly jsDocTags: Tags;
50
+ jsDocTags: Tags;
51
51
  readonly flags: number;
52
52
  hasRefsInFile: boolean;
53
53
  }
@@ -11,7 +11,7 @@ import { walkAST } from "./visitors/walk.js";
11
11
  const jsDocImportRe = /import\(\s*['"]([^'"]+)['"]\s*\)(?:\.(\w+))?/g;
12
12
  const jsDocImportTagRe = /@import\s+(?:\{[^}]*\}|\*\s+as\s+\w+)\s+from\s+['"]([^'"]+)['"]/g;
13
13
  const jsxImportSourceRe = /@jsxImportSource\s+(\S+)/;
14
- const referenceTypesRe = /\/\s*<reference\s+types\s*=\s*"([^"]+)"\s*\/>/;
14
+ const referenceTypesRe = /\s*<reference\s+types\s*=\s*"([^"]+)"\s*\/>/;
15
15
  const envRe = /@(?:vitest|jest)-environment\s+(\S+)/g;
16
16
  const getImportsAndExports = (filePath, sourceText, resolveModule, options, ignoreExportsUsedInFile, skipExportsForFile, visitor, pluginCtx, cachedParseResult) => {
17
17
  const skipExports = skipExportsForFile || !options.isReportExports;
@@ -303,7 +303,7 @@ const getImportsAndExports = (filePath, sourceText, resolveModule, options, igno
303
303
  addImport(id, undefined, undefined, undefined, comment.start + results.index, modifiers);
304
304
  }
305
305
  if (comment.type === 'Line') {
306
- const refMatch = ('/' + comment.value).match(referenceTypesRe);
306
+ const refMatch = comment.value.match(referenceTypesRe);
307
307
  if (refMatch) {
308
308
  addImport(refMatch[1], undefined, undefined, undefined, comment.start, IMPORT_FLAGS.TYPE_ONLY);
309
309
  }
@@ -1,6 +1,7 @@
1
1
  import { parseSync, rawTransferSupported, } from 'oxc-parser';
2
2
  import { DEFAULT_EXTENSIONS, FIX_FLAGS, SYMBOL_TYPE } from "../../constants.js";
3
3
  import { extname } from "../../util/path.js";
4
+ import { EMPTY_TAGS } from "./jsdoc.js";
4
5
  const defaultParseOptions = {
5
6
  sourceType: 'unambiguous',
6
7
  experimentalRawTransfer: rawTransferSupported(),
@@ -57,6 +58,18 @@ export function extractNamespaceMembers(decl, options, lineStarts, getJSDocTags,
57
58
  const members = [];
58
59
  const addMember = (name, pos, stmtStart, stmtEnd) => {
59
60
  const fullName = prefix ? `${prefix}.${name}` : name;
61
+ const tags = getJSDocTags(stmtStart);
62
+ const existing = members.find(m => m.identifier === fullName);
63
+ if (existing) {
64
+ if (tags.size) {
65
+ if (existing.jsDocTags === EMPTY_TAGS)
66
+ existing.jsDocTags = new Set(tags);
67
+ else
68
+ for (const t of tags)
69
+ existing.jsDocTags.add(t);
70
+ }
71
+ return;
72
+ }
60
73
  const { line, col } = getLineAndCol(lineStarts, pos);
61
74
  const fix = options.isFixExports
62
75
  ? [stmtStart, stmtEnd, FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.WITH_NEWLINE]
@@ -69,7 +82,7 @@ export function extractNamespaceMembers(decl, options, lineStarts, getJSDocTags,
69
82
  col,
70
83
  fix,
71
84
  hasRefsInFile: false,
72
- jsDocTags: getJSDocTags(stmtStart),
85
+ jsDocTags: tags,
73
86
  flags: 0,
74
87
  });
75
88
  };
@@ -37,13 +37,7 @@ export function handleMemberExpression(node, s) {
37
37
  else if (!shadowed) {
38
38
  const memberName = node.computed === false && node.property.type === 'Identifier' ? node.property.name : undefined;
39
39
  if (memberName) {
40
- const exp = s.exports.get(localName);
41
- if (exp) {
42
- for (const member of exp.members) {
43
- if (member.identifier === memberName)
44
- member.hasRefsInFile = true;
45
- }
46
- }
40
+ s.memberRefsInFile.push(localName, memberName);
47
41
  }
48
42
  }
49
43
  const aliases = s.importAliases.get(localName);
@@ -95,15 +89,8 @@ export function handleMemberExpression(node, s) {
95
89
  }
96
90
  }
97
91
  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
- }
106
- }
92
+ const mid = node.object.property.name;
93
+ s.memberRefsInFile.push(rootName, mid, rootName, `${mid}.${node.property.name}`);
107
94
  }
108
95
  }
109
96
  }
@@ -131,17 +118,10 @@ export function handleMemberExpression(node, s) {
131
118
  }
132
119
  }
133
120
  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
- }
144
- }
121
+ const a = node.object.object.property.name;
122
+ const b = node.object.property.name;
123
+ const c = node.property.name;
124
+ s.memberRefsInFile.push(rootName, a, rootName, `${a}.${b}`, rootName, `${a}.${b}.${c}`);
145
125
  }
146
126
  }
147
127
  }
@@ -51,6 +51,7 @@ export interface WalkState extends WalkContext {
51
51
  chainedMemberExprs: WeakSet<object>;
52
52
  currentVarDeclStart: number;
53
53
  nsRanges: [number, number][];
54
+ memberRefsInFile: string[];
54
55
  scopeDepth: number;
55
56
  scopeStarts: number[];
56
57
  scopeEnds: number[];
@@ -112,15 +112,48 @@ const _addLocalRef = (name, pos) => {
112
112
  if (!state.localImportMap.has(name) && !isShadowed(name, pos))
113
113
  state.localRefs.add(name);
114
114
  };
115
- const _addShadow = (name) => {
116
- const i = state.scopeDepth - 1;
117
- const range = [state.scopeStarts[i], state.scopeEnds[i]];
115
+ const _addShadowRange = (name, range) => {
118
116
  const ranges = state.shadowScopes.get(name);
119
117
  if (ranges)
120
118
  ranges.push(range);
121
119
  else
122
120
  state.shadowScopes.set(name, [range]);
123
121
  };
122
+ const _addShadow = (name) => {
123
+ const i = state.scopeDepth - 1;
124
+ _addShadowRange(name, [state.scopeStarts[i], state.scopeEnds[i]]);
125
+ };
126
+ const _collectBindingNames = (pattern, range) => {
127
+ if (!pattern)
128
+ return;
129
+ if (pattern.type === 'Identifier') {
130
+ _addShadowRange(pattern.name, range);
131
+ }
132
+ else if (pattern.type === 'ObjectPattern') {
133
+ for (const prop of pattern.properties ?? []) {
134
+ _collectBindingNames(prop.value ?? prop.argument, range);
135
+ }
136
+ }
137
+ else if (pattern.type === 'ArrayPattern') {
138
+ for (const el of pattern.elements ?? []) {
139
+ _collectBindingNames(el, range);
140
+ }
141
+ }
142
+ else if (pattern.type === 'AssignmentPattern') {
143
+ _collectBindingNames(pattern.left, range);
144
+ }
145
+ else if (pattern.type === 'RestElement') {
146
+ _collectBindingNames(pattern.argument, range);
147
+ }
148
+ };
149
+ const _addParamShadows = (params, body) => {
150
+ if (!body || !params)
151
+ return;
152
+ const range = [body.start, body.end];
153
+ const items = Array.isArray(params) ? params : params.items ?? params;
154
+ for (const param of items)
155
+ _collectBindingNames(param, range);
156
+ };
124
157
  const coreVisitorObject = {
125
158
  BlockStatement(node) {
126
159
  state.scopeStarts[state.scopeDepth] = node.start;
@@ -143,6 +176,18 @@ const coreVisitorObject = {
143
176
  if (state.scopeDepth > 0)
144
177
  _addShadow(node.id.name);
145
178
  }
179
+ _addParamShadows(node.params, node.body);
180
+ },
181
+ FunctionExpression(node) {
182
+ _addParamShadows(node.params, node.body);
183
+ },
184
+ ArrowFunctionExpression(node) {
185
+ _addParamShadows(node.params, node.body);
186
+ },
187
+ CatchClause(node) {
188
+ if (node.param?.type === 'Identifier' && node.body) {
189
+ _addShadowRange(node.param.name, [node.body.start, node.body.end]);
190
+ }
146
191
  },
147
192
  VariableDeclaration(node) {
148
193
  state.currentVarDeclStart = node.start;
@@ -196,6 +241,10 @@ const coreVisitorObject = {
196
241
  handleJSXMemberExpression(node, state);
197
242
  },
198
243
  ForInStatement(node) {
244
+ if (node.left.type === 'VariableDeclaration' && node.body) {
245
+ for (const decl of node.left.declarations)
246
+ _collectBindingNames(decl.id, [node.body.start, node.body.end]);
247
+ }
199
248
  if (node.right.type === 'Identifier' && !isShadowed(node.right.name, node.right.start)) {
200
249
  const _import = state.localImportMap.get(node.right.name);
201
250
  if (_import?.isNamespace) {
@@ -206,6 +255,10 @@ const coreVisitorObject = {
206
255
  }
207
256
  },
208
257
  ForOfStatement(node) {
258
+ if (node.left.type === 'VariableDeclaration' && node.body) {
259
+ for (const decl of node.left.declarations)
260
+ _collectBindingNames(decl.id, [node.body.start, node.body.end]);
261
+ }
209
262
  if (node.right.type === 'Identifier' && !isShadowed(node.right.name, node.right.start)) {
210
263
  const _import = state.localImportMap.get(node.right.name);
211
264
  if (_import?.isNamespace) {
@@ -242,16 +295,10 @@ const coreVisitorObject = {
242
295
  }
243
296
  }
244
297
  else if (parts.length > 0) {
245
- const exp = state.exports.get(rootName);
246
- if (exp) {
247
- let path = '';
248
- for (const part of parts) {
249
- path = path ? `${path}.${part}` : part;
250
- for (const member of exp.members) {
251
- if (member.identifier === path)
252
- member.hasRefsInFile = true;
253
- }
254
- }
298
+ let path = '';
299
+ for (const part of parts) {
300
+ path = path ? `${path}.${part}` : part;
301
+ state.memberRefsInFile.push(rootName, path);
255
302
  }
256
303
  }
257
304
  }
@@ -525,14 +572,7 @@ const localRefsVisitorObject = {
525
572
  if (left.type === 'Identifier') {
526
573
  const rootName = left.name;
527
574
  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
- }
575
+ state.localRefs.add(rootName);
536
576
  }
537
577
  }
538
578
  },
@@ -606,6 +646,7 @@ export function walkAST(program, sourceText, filePath, ctx) {
606
646
  chainedMemberExprs: new WeakSet(),
607
647
  currentVarDeclStart: -1,
608
648
  nsRanges: [],
649
+ memberRefsInFile: [],
609
650
  scopeDepth: 0,
610
651
  scopeStarts: [],
611
652
  scopeEnds: [],
@@ -618,6 +659,16 @@ export function walkAST(program, sourceText, filePath, ctx) {
618
659
  isInNamespace: _isInNamespace,
619
660
  };
620
661
  ctx.visitor.visit(program);
662
+ for (let i = 0; i < state.memberRefsInFile.length; i += 2) {
663
+ const exp = state.exports.get(state.memberRefsInFile[i]);
664
+ if (exp) {
665
+ const id = state.memberRefsInFile[i + 1];
666
+ for (const member of exp.members) {
667
+ if (member.identifier === id)
668
+ member.hasRefsInFile = true;
669
+ }
670
+ }
671
+ }
621
672
  for (const [aliasName, aliasSet] of state.importAliases) {
622
673
  if (!state.accessedAliases.has(aliasName)) {
623
674
  for (const alias of aliasSet) {
@@ -3,7 +3,7 @@ import { parseTsconfig } from 'get-tsconfig';
3
3
  import stripJsonComments from 'strip-json-comments';
4
4
  import { isFile as _isFile } from "./fs.js";
5
5
  import { _syncGlob } from "./glob.js";
6
- import { dirname, isAbsolute, join } from "./path.js";
6
+ import { dirname, isAbsolute, join, toAbsolute } from "./path.js";
7
7
  const hasGlobChar = (p) => p.includes('*') || p.includes('?');
8
8
  const hasExtension = (p) => {
9
9
  const last = p.lastIndexOf('/');
@@ -81,12 +81,10 @@ export const loadTSConfig = async (tsConfigFilePath) => {
81
81
  return { isFile: true, compilerOptions: {}, fileNames: [] };
82
82
  const dir = dirname(tsConfigFilePath);
83
83
  const compilerOptions = (config.compilerOptions ?? {});
84
- if (compilerOptions.outDir && !isAbsolute(compilerOptions.outDir)) {
85
- compilerOptions.outDir = join(dir, compilerOptions.outDir);
86
- }
87
- if (compilerOptions.rootDir && !isAbsolute(compilerOptions.rootDir)) {
88
- compilerOptions.rootDir = join(dir, compilerOptions.rootDir);
89
- }
84
+ if (compilerOptions.outDir)
85
+ compilerOptions.outDir = toAbsolute(compilerOptions.outDir, dir).replace(/\/+$/, '');
86
+ if (compilerOptions.rootDir)
87
+ compilerOptions.rootDir = toAbsolute(compilerOptions.rootDir, dir).replace(/\/+$/, '');
90
88
  if (compilerOptions.paths) {
91
89
  compilerOptions.pathsBasePath ??= dir;
92
90
  }
package/dist/util/tag.js CHANGED
@@ -4,11 +4,11 @@ export const splitTags = (rawTags) => {
4
4
  return tags.reduce(([incl, excl], tag) => {
5
5
  const match = tag.match(/[a-zA-Z]+/);
6
6
  if (match)
7
- (tag.startsWith('-') ? excl : incl).push(match[0]);
7
+ (tag.startsWith('-') ? excl : incl).push(`@${match[0]}`);
8
8
  return [incl, excl];
9
9
  }, [[], []]);
10
10
  };
11
- const hasTag = (tags, jsDocTags) => tags.some(tag => jsDocTags.has(`@${tag}`));
11
+ const hasTag = (tags, jsDocTags) => tags.some(tag => jsDocTags.has(tag));
12
12
  export const shouldIgnore = (jsDocTags, tags) => {
13
13
  const [includeJSDocTags, excludeJSDocTags] = tags;
14
14
  if (includeJSDocTags.length > 0 && !hasTag(includeJSDocTags, jsDocTags))
@@ -1,2 +1,6 @@
1
1
  import type { ExportsTreeNode } from '../graph-explorer/operations/build-exports-tree.ts';
2
- export declare const formatTrace: (node: ExportsTreeNode, toRelative: (path: string) => string, isReferenced: boolean) => string;
2
+ export interface TraceMemberStatus {
3
+ identifier: string;
4
+ referenced: boolean;
5
+ }
6
+ export declare const formatTrace: (node: ExportsTreeNode, toRelative: (path: string) => string, isReferenced: boolean, memberStatuses?: TraceMemberStatus[]) => string;
@@ -1,5 +1,5 @@
1
1
  import pc from 'picocolors';
2
- export const formatTrace = (node, toRelative, isReferenced) => {
2
+ export const formatTrace = (node, toRelative, isReferenced, memberStatuses) => {
3
3
  const lines = [];
4
4
  const file = pc.white;
5
5
  const id = pc.cyanBright;
@@ -41,5 +41,12 @@ export const formatTrace = (node, toRelative, isReferenced) => {
41
41
  const leafMarker = isReferenced ? ok(' ✓') : fail(' ✗');
42
42
  lines.push(`${dim('└── (no imports found)')}${leafMarker}`);
43
43
  }
44
+ if (memberStatuses && memberStatuses.length > 0) {
45
+ const parts = memberStatuses.map(m => {
46
+ const marker = m.referenced ? ok(' ✓') : fail(' ✗');
47
+ return `${id(m.identifier)}${marker}`;
48
+ });
49
+ lines.push(`${dim(' members: [')}${parts.join(dim(', '))}${dim(']')}`);
50
+ }
44
51
  return lines.join('\n');
45
52
  };
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "6.0.3";
1
+ export declare const version = "6.0.5";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '6.0.3';
1
+ export const version = '6.0.5';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "6.0.3",
3
+ "version": "6.0.5",
4
4
  "description": "Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects",
5
5
  "keywords": [
6
6
  "analysis",