knip 6.3.1 → 6.4.1

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.
Files changed (49) hide show
  1. package/LICENSE +15 -0
  2. package/dist/ConfigurationChief.d.ts +6 -0
  3. package/dist/ProjectPrincipal.d.ts +2 -2
  4. package/dist/ProjectPrincipal.js +32 -13
  5. package/dist/WorkspaceWorker.d.ts +1 -1
  6. package/dist/WorkspaceWorker.js +19 -2
  7. package/dist/compilers/index.d.ts +10 -0
  8. package/dist/compilers/scss.js +22 -16
  9. package/dist/graph/analyze.js +2 -0
  10. package/dist/graph/build.d.ts +2 -2
  11. package/dist/graph/build.js +5 -5
  12. package/dist/plugins/bun/index.js +2 -0
  13. package/dist/plugins/index.d.ts +1 -0
  14. package/dist/plugins/index.js +2 -0
  15. package/dist/plugins/panda-css/index.d.ts +3 -0
  16. package/dist/plugins/panda-css/index.js +18 -0
  17. package/dist/plugins/panda-css/types.d.ts +3 -0
  18. package/dist/plugins/panda-css/types.js +1 -0
  19. package/dist/plugins/tailwind/index.js +2 -2
  20. package/dist/plugins/webpack/index.js +21 -1
  21. package/dist/reporters/util/configuration-hints.js +5 -0
  22. package/dist/run.js +5 -0
  23. package/dist/schema/configuration.d.ts +15 -0
  24. package/dist/schema/plugins.d.ts +5 -0
  25. package/dist/schema/plugins.js +1 -0
  26. package/dist/types/PluginNames.d.ts +2 -2
  27. package/dist/types/PluginNames.js +1 -0
  28. package/dist/types/issues.d.ts +1 -1
  29. package/dist/types/module-graph.d.ts +1 -0
  30. package/dist/typescript/comments.d.ts +4 -0
  31. package/dist/typescript/comments.js +64 -0
  32. package/dist/typescript/get-imports-and-exports.js +23 -46
  33. package/dist/typescript/resolve-module-names.d.ts +1 -0
  34. package/dist/typescript/resolve-module-names.js +22 -0
  35. package/dist/typescript/visitors/calls.js +74 -1
  36. package/dist/typescript/visitors/walk.d.ts +4 -0
  37. package/dist/util/create-options.d.ts +10 -0
  38. package/dist/util/file-entry-cache.js +10 -3
  39. package/dist/util/glob-cache.d.ts +12 -0
  40. package/dist/util/glob-cache.js +124 -0
  41. package/dist/util/glob.js +12 -1
  42. package/dist/util/module-graph.js +7 -0
  43. package/dist/util/pattern-extensions.d.ts +1 -0
  44. package/dist/util/pattern-extensions.js +32 -0
  45. package/dist/util/watch.js +4 -0
  46. package/dist/version.d.ts +1 -1
  47. package/dist/version.js +1 -1
  48. package/package.json +1 -1
  49. package/schema.json +4 -0
@@ -0,0 +1,4 @@
1
+ import type { Comment } from 'oxc-parser';
2
+ type CommentImportAdder = (specifier: string, identifier: string | undefined, alias: string | undefined, namespace: string | undefined, pos: number, modifiers: number) => void;
3
+ export declare const extractImportsFromComments: (comments: readonly Comment[], firstStmtStart: number, addImport: CommentImportAdder) => void;
4
+ export {};
@@ -0,0 +1,64 @@
1
+ import { IMPORT_FLAGS } from "../constants.js";
2
+ const jsDocImportRe = /import\(\s*['"]([^'"]+)['"]\s*\)(?:\.(\w+))?/g;
3
+ const jsDocImportTagRe = /@import\s+(?:\{[^}]*\}|\*\s+as\s+\w+)\s+from\s+['"]([^'"]+)['"]/g;
4
+ const jsxImportSourceRe = /@jsxImportSource\s+(\S+)/;
5
+ const referenceTypesRe = /\s*<reference\s+types\s*=\s*"([^"]+)"[^/]*\/>/;
6
+ const envPragmaRe = /@(vitest|jest)-environment\s+([@\w./-]+)/g;
7
+ const resolveEnvironmentPragma = (tool, value) => {
8
+ if (value === 'node')
9
+ return undefined;
10
+ if (value.startsWith('.') || value.startsWith('/'))
11
+ return value;
12
+ if (tool === 'jest') {
13
+ if (value === 'jsdom')
14
+ return 'jest-environment-jsdom';
15
+ return value;
16
+ }
17
+ if (value === 'edge-runtime')
18
+ return '@edge-runtime/vm';
19
+ return value;
20
+ };
21
+ export const extractImportsFromComments = (comments, firstStmtStart, addImport) => {
22
+ for (const comment of comments) {
23
+ const text = comment.value;
24
+ let results;
25
+ if (comment.type === 'Block') {
26
+ jsDocImportRe.lastIndex = 0;
27
+ while ((results = jsDocImportRe.exec(text)) !== null) {
28
+ const before = text.slice(0, results.index);
29
+ const lastOpen = before.lastIndexOf('{');
30
+ if (lastOpen === -1 || before.indexOf('}', lastOpen) !== -1)
31
+ continue;
32
+ const specifier = results[1];
33
+ const member = results[2];
34
+ addImport(specifier, member, undefined, undefined, comment.start + results.index, IMPORT_FLAGS.TYPE_ONLY);
35
+ }
36
+ jsDocImportTagRe.lastIndex = 0;
37
+ while ((results = jsDocImportTagRe.exec(text)) !== null) {
38
+ const specifier = results[1];
39
+ addImport(specifier, undefined, undefined, undefined, comment.start + results.index, IMPORT_FLAGS.TYPE_ONLY);
40
+ }
41
+ }
42
+ const jsxMatch = text.match(jsxImportSourceRe);
43
+ if (jsxMatch) {
44
+ addImport(jsxMatch[1], undefined, undefined, undefined, comment.start, IMPORT_FLAGS.TYPE_ONLY);
45
+ }
46
+ if (comment.end <= firstStmtStart) {
47
+ envPragmaRe.lastIndex = 0;
48
+ while ((results = envPragmaRe.exec(text)) !== null) {
49
+ const id = resolveEnvironmentPragma(results[1], results[2]);
50
+ if (!id)
51
+ continue;
52
+ const isLocal = id.startsWith('.') || id.startsWith('/');
53
+ const modifiers = isLocal ? IMPORT_FLAGS.ENTRY : IMPORT_FLAGS.NONE;
54
+ addImport(id, undefined, undefined, undefined, comment.start + results.index, modifiers);
55
+ }
56
+ }
57
+ if (comment.type === 'Line') {
58
+ const refMatch = comment.value.match(referenceTypesRe);
59
+ if (refMatch) {
60
+ addImport(refMatch[1], undefined, undefined, undefined, comment.start, IMPORT_FLAGS.TYPE_ONLY);
61
+ }
62
+ }
63
+ }
64
+ };
@@ -5,14 +5,10 @@ import { getPackageNameFromFilePath, isStartsLikePackageName, sanitizeSpecifier
5
5
  import { timerify } from "../util/Performance.js";
6
6
  import { dirname, isInNodeModules, resolve } from "../util/path.js";
7
7
  import { shouldIgnore } from "../util/tag.js";
8
- import { buildLineStarts, getLineAndCol, parseFile, shouldCountRefs, stripQuotes, } from "./visitors/helpers.js";
8
+ import { extractImportsFromComments } from "./comments.js";
9
+ import { buildLineStarts, getLineAndCol, parseFile, shouldCountRefs, } from "./visitors/helpers.js";
9
10
  import { buildJSDocTagLookup } from "./visitors/jsdoc.js";
10
11
  import { walkAST } from "./visitors/walk.js";
11
- const jsDocImportRe = /import\(\s*['"]([^'"]+)['"]\s*\)(?:\.(\w+))?/g;
12
- const jsDocImportTagRe = /@import\s+(?:\{[^}]*\}|\*\s+as\s+\w+)\s+from\s+['"]([^'"]+)['"]/g;
13
- const jsxImportSourceRe = /@jsxImportSource\s+(\S+)/;
14
- const referenceTypesRe = /\s*<reference\s+types\s*=\s*"([^"]+)"[^/]*\/>/;
15
- const envRe = /@(?:vitest|jest)-environment\s+(\S+)/g;
16
12
  const getImportsAndExports = (filePath, sourceText, resolveModule, options, ignoreExportsUsedInFile, skipExportsForFile, visitor, pluginCtx, cachedParseResult) => {
17
13
  const skipExports = skipExportsForFile || !options.isReportExports;
18
14
  const internal = new Map();
@@ -166,10 +162,19 @@ const getImportsAndExports = (filePath, sourceText, resolveModule, options, igno
166
162
  const lineStarts = buildLineStarts(sourceText);
167
163
  const getJSDocTags = buildJSDocTagLookup(result.comments, sourceText);
168
164
  let hasNodeModuleImport = false;
165
+ let hasWorkerThreadsImport = false;
166
+ let hasChildProcessImport = false;
167
+ let hasPathJoinImport = false;
168
+ let hasPathResolveImport = false;
169
169
  for (const _imports of result.module.staticImports) {
170
170
  const specifier = _imports.moduleRequest.value;
171
171
  if (specifier === 'node:module' || specifier === 'module')
172
172
  hasNodeModuleImport = true;
173
+ else if (specifier === 'node:worker_threads' || specifier === 'worker_threads')
174
+ hasWorkerThreadsImport = true;
175
+ else if (specifier === 'node:child_process' || specifier === 'child_process')
176
+ hasChildProcessImport = true;
177
+ const isPathImport = specifier === 'node:path' || specifier === 'path';
173
178
  const pos = _imports.moduleRequest.start;
174
179
  const jsdocTags = getJSDocTags(_imports.start);
175
180
  if (_imports.entries.length === 0) {
@@ -199,6 +204,12 @@ const getImportsAndExports = (filePath, sourceText, resolveModule, options, igno
199
204
  const importedName = entry.importName.name;
200
205
  const localName = entry.localName.value;
201
206
  const alias = localName !== importedName ? localName : undefined;
207
+ if (isPathImport && !alias) {
208
+ if (importedName === 'join')
209
+ hasPathJoinImport = true;
210
+ else if (importedName === 'resolve')
211
+ hasPathResolveImport = true;
212
+ }
202
213
  addImport(specifier, importedName, alias, undefined, entry.localName.start, modifiers, pos, jsdocTags, resolved);
203
214
  if (internalPath)
204
215
  localImportMap.set(localName, { importedName, filePath: internalPath, isNamespace: false });
@@ -263,52 +274,18 @@ const getImportsAndExports = (filePath, sourceText, resolveModule, options, igno
263
274
  localRefs: ignoreExportsUsedInFile ? new Set() : undefined,
264
275
  destructuredExports,
265
276
  hasNodeModuleImport,
277
+ hasWorkerThreadsImport,
278
+ hasChildProcessImport,
279
+ hasPathJoinImport,
280
+ hasPathResolveImport,
266
281
  resolveModule,
267
282
  programFiles,
268
283
  entryFiles,
269
284
  visitor,
270
285
  getJSDocTags,
271
286
  });
272
- for (const comment of result.comments) {
273
- const text = comment.value;
274
- let results;
275
- if (comment.type === 'Block') {
276
- jsDocImportRe.lastIndex = 0;
277
- while ((results = jsDocImportRe.exec(text)) !== null) {
278
- const before = text.slice(0, results.index);
279
- const lastOpen = before.lastIndexOf('{');
280
- if (lastOpen === -1 || before.indexOf('}', lastOpen) !== -1)
281
- continue;
282
- const specifier = results[1];
283
- const member = results[2];
284
- addImport(specifier, member, undefined, undefined, comment.start + results.index, IMPORT_FLAGS.TYPE_ONLY);
285
- }
286
- jsDocImportTagRe.lastIndex = 0;
287
- while ((results = jsDocImportTagRe.exec(text)) !== null) {
288
- const specifier = results[1];
289
- addImport(specifier, undefined, undefined, undefined, comment.start + results.index, IMPORT_FLAGS.TYPE_ONLY);
290
- }
291
- }
292
- const jsxMatch = text.match(jsxImportSourceRe);
293
- if (jsxMatch) {
294
- addImport(jsxMatch[1], undefined, undefined, undefined, comment.start, IMPORT_FLAGS.TYPE_ONLY);
295
- }
296
- envRe.lastIndex = 0;
297
- while ((results = envRe.exec(text)) !== null) {
298
- const id = stripQuotes(results[1]);
299
- if (!id)
300
- continue;
301
- const isLocal = id.startsWith('.') || id.startsWith('/');
302
- const modifiers = isLocal ? IMPORT_FLAGS.ENTRY : IMPORT_FLAGS.NONE;
303
- addImport(id, undefined, undefined, undefined, comment.start + results.index, modifiers);
304
- }
305
- if (comment.type === 'Line') {
306
- const refMatch = comment.value.match(referenceTypesRe);
307
- if (refMatch) {
308
- addImport(refMatch[1], undefined, undefined, undefined, comment.start, IMPORT_FLAGS.TYPE_ONLY);
309
- }
310
- }
311
- }
287
+ const firstStmtStart = result.program.body[0]?.start ?? Number.POSITIVE_INFINITY;
288
+ extractImportsFromComments(result.comments, firstStmtStart, addImport);
312
289
  for (const [id, item] of exports) {
313
290
  item.referencedIn = referencedInExport.get(id);
314
291
  if (localRefs && shouldCountRefs(ignoreExportsUsedInFile, item.type) && (localRefs.has(id) || item.isReExport)) {
@@ -1,5 +1,6 @@
1
1
  import type { ToSourceFilePath } from '../util/to-source-path.ts';
2
2
  import type { ResolveModule } from './visitors/helpers.ts';
3
+ export declare function clearModuleResolutionCaches(): void;
3
4
  export declare function createCustomModuleResolver(compilerOptions: {
4
5
  paths?: Record<string, string[]>;
5
6
  rootDirs?: string[];
@@ -5,6 +5,11 @@ import { sanitizeSpecifier } from "../util/modules.js";
5
5
  import { timerify } from "../util/Performance.js";
6
6
  import { dirname, extname, isAbsolute, isInNodeModules, join } from "../util/path.js";
7
7
  import { _createSyncModuleResolver, _resolveModuleSync } from "../util/resolve.js";
8
+ const moduleResolutionCaches = [];
9
+ export function clearModuleResolutionCaches() {
10
+ for (const cache of moduleResolutionCaches)
11
+ cache.clear();
12
+ }
8
13
  function compilePathMappings(paths) {
9
14
  if (!paths)
10
15
  return undefined;
@@ -40,7 +45,24 @@ export function createCustomModuleResolver(compilerOptions, customCompilerExtens
40
45
  isExternalLibraryImport: mapped === resolvedFileName && isInNodeModules(resolvedFileName),
41
46
  };
42
47
  }
48
+ const cache = new Map();
49
+ moduleResolutionCaches.push(cache);
43
50
  function resolveModuleName(name, containingFile) {
51
+ const dir = dirname(containingFile);
52
+ let byName = cache.get(dir);
53
+ if (byName) {
54
+ if (byName.has(name))
55
+ return byName.get(name);
56
+ }
57
+ else {
58
+ byName = new Map();
59
+ cache.set(dir, byName);
60
+ }
61
+ const result = resolveModuleNameUncached(name, containingFile);
62
+ byName.set(name, result);
63
+ return result;
64
+ }
65
+ function resolveModuleNameUncached(name, containingFile) {
44
66
  const specifier = sanitizeSpecifier(name);
45
67
  if (isBuiltin(specifier))
46
68
  return undefined;
@@ -1,6 +1,47 @@
1
1
  import { IMPORT_FLAGS, OPAQUE } from "../../constants.js";
2
2
  import { addValue } from "../../util/module-graph.js";
3
3
  import { getStringValue, isStringLiteral } from "./helpers.js";
4
+ function extractInlineDirnamePath(node, s) {
5
+ if (node?.type !== 'CallExpression')
6
+ return undefined;
7
+ const callee = node.callee;
8
+ let isPathHelper = false;
9
+ if (callee?.type === 'MemberExpression' &&
10
+ !callee.computed &&
11
+ callee.object?.type === 'Identifier' &&
12
+ callee.object.name === 'path' &&
13
+ callee.property?.type === 'Identifier' &&
14
+ (callee.property.name === 'join' || callee.property.name === 'resolve')) {
15
+ isPathHelper = true;
16
+ }
17
+ else if (callee?.type === 'Identifier') {
18
+ if (callee.name === 'join' && s.hasPathJoinImport)
19
+ isPathHelper = true;
20
+ else if (callee.name === 'resolve' && s.hasPathResolveImport)
21
+ isPathHelper = true;
22
+ }
23
+ if (!isPathHelper)
24
+ return undefined;
25
+ const args = node.arguments;
26
+ if (!args || args.length < 2)
27
+ return undefined;
28
+ if (args[0]?.type !== 'Identifier' || args[0].name !== '__dirname')
29
+ return undefined;
30
+ const parts = [];
31
+ for (let i = 1; i < args.length; i++) {
32
+ if (!isStringLiteral(args[i]))
33
+ return undefined;
34
+ const value = getStringValue(args[i]);
35
+ if (value == null)
36
+ return undefined;
37
+ parts.push(value);
38
+ }
39
+ if (parts.length === 0)
40
+ return undefined;
41
+ const joined = parts.join('/').replace(/\/+/g, '/');
42
+ return joined.startsWith('.') || joined.startsWith('/') ? joined : `./${joined}`;
43
+ }
44
+ const CHILD_PROCESS_ENTRY_METHODS = new Set(['fork', 'spawn', 'execFile']);
4
45
  export function handleCallExpression(node, s) {
5
46
  if (node.callee.type === 'Identifier' &&
6
47
  node.callee.name === 'require' &&
@@ -53,6 +94,25 @@ export function handleCallExpression(node, s) {
53
94
  return;
54
95
  }
55
96
  }
97
+ if (s.hasChildProcessImport && node.arguments.length >= 1) {
98
+ let isChildProcessEntry = false;
99
+ if (node.callee.type === 'Identifier' && CHILD_PROCESS_ENTRY_METHODS.has(node.callee.name)) {
100
+ isChildProcessEntry = true;
101
+ }
102
+ else if (node.callee.type === 'MemberExpression' &&
103
+ !node.callee.computed &&
104
+ node.callee.property.type === 'Identifier' &&
105
+ CHILD_PROCESS_ENTRY_METHODS.has(node.callee.property.name)) {
106
+ isChildProcessEntry = true;
107
+ }
108
+ if (isChildProcessEntry) {
109
+ const specifier = extractInlineDirnamePath(node.arguments[0], s);
110
+ if (specifier) {
111
+ s.addImport(specifier, undefined, undefined, undefined, node.arguments[0].start, IMPORT_FLAGS.ENTRY);
112
+ return;
113
+ }
114
+ }
115
+ }
56
116
  if (node.callee.type === 'MemberExpression' &&
57
117
  !node.callee.computed &&
58
118
  node.callee.object.type === 'Identifier' &&
@@ -70,12 +130,15 @@ export function handleCallExpression(node, s) {
70
130
  if (internalImport) {
71
131
  if (_import.isNamespace)
72
132
  addValue(internalImport.import, OPAQUE, s.filePath);
73
- else
133
+ else {
74
134
  internalImport.refs.add(arg.name);
135
+ (internalImport.enumerated ??= new Set()).add(arg.name);
136
+ }
75
137
  }
76
138
  }
77
139
  }
78
140
  }
141
+ return;
79
142
  }
80
143
  const markRefIfNs = (name) => {
81
144
  const _import = s.localImportMap.get(name);
@@ -116,5 +179,15 @@ export function handleNewExpression(node, s) {
116
179
  const specifier = getStringValue(node.arguments[0]);
117
180
  if (specifier)
118
181
  s.addImport(specifier, undefined, undefined, undefined, node.arguments[0].start, IMPORT_FLAGS.ENTRY | IMPORT_FLAGS.OPTIONAL);
182
+ return;
183
+ }
184
+ if (s.hasWorkerThreadsImport &&
185
+ node.callee.type === 'Identifier' &&
186
+ node.callee.name === 'Worker' &&
187
+ node.arguments.length >= 1) {
188
+ const specifier = extractInlineDirnamePath(node.arguments[0], s);
189
+ if (specifier) {
190
+ s.addImport(specifier, undefined, undefined, undefined, node.arguments[0].start, IMPORT_FLAGS.ENTRY);
191
+ }
119
192
  }
120
193
  }
@@ -33,6 +33,10 @@ interface WalkContext {
33
33
  localRefs: Set<string> | undefined;
34
34
  destructuredExports: Set<string>;
35
35
  hasNodeModuleImport: boolean;
36
+ hasWorkerThreadsImport: boolean;
37
+ hasChildProcessImport: boolean;
38
+ hasPathJoinImport: boolean;
39
+ hasPathResolveImport: boolean;
36
40
  resolveModule: ResolveModule;
37
41
  programFiles: Set<string>;
38
42
  entryFiles: Set<string>;
@@ -407,6 +407,11 @@ export declare const createOptions: (options: CreateOptions) => Promise<{
407
407
  entry?: string | string[] | undefined;
408
408
  project?: string | string[] | undefined;
409
409
  } | undefined;
410
+ 'panda-css'?: string | boolean | string[] | {
411
+ config?: string | string[] | undefined;
412
+ entry?: string | string[] | undefined;
413
+ project?: string | string[] | undefined;
414
+ } | undefined;
410
415
  parcel?: string | boolean | string[] | {
411
416
  config?: string | string[] | undefined;
412
417
  entry?: string | string[] | undefined;
@@ -1123,6 +1128,11 @@ export declare const createOptions: (options: CreateOptions) => Promise<{
1123
1128
  entry?: string | string[] | undefined;
1124
1129
  project?: string | string[] | undefined;
1125
1130
  } | undefined;
1131
+ 'panda-css'?: string | boolean | string[] | {
1132
+ config?: string | string[] | undefined;
1133
+ entry?: string | string[] | undefined;
1134
+ project?: string | string[] | undefined;
1135
+ } | undefined;
1126
1136
  parcel?: string | boolean | string[] | {
1127
1137
  config?: string | string[] | undefined;
1128
1138
  entry?: string | string[] | undefined;
@@ -85,14 +85,21 @@ export class FileEntryCache {
85
85
  return meta;
86
86
  }
87
87
  reconcile() {
88
- this.removeNotFoundFiles();
89
88
  for (const [entryName, cacheEntry] of this.normalizedEntries) {
90
89
  try {
91
- const meta = this._getMetaForFileUsingMtimeAndSize(cacheEntry);
90
+ const stat = fs.statSync(entryName);
91
+ const meta = Object.assign(cacheEntry.meta ?? {}, {
92
+ size: stat.size,
93
+ mtime: stat.mtime.getTime(),
94
+ });
92
95
  this.cache.set(entryName, meta);
93
96
  }
94
97
  catch (error) {
95
- if (error.code !== 'ENOENT')
98
+ if (error.code === 'ENOENT') {
99
+ this.normalizedEntries.delete(entryName);
100
+ this.cache.delete(entryName);
101
+ }
102
+ else
96
103
  throw error;
97
104
  }
98
105
  }
@@ -0,0 +1,12 @@
1
+ export declare const initGlobCache: (cacheLocation: string) => void;
2
+ export declare const isGlobCacheEnabled: () => boolean;
3
+ export declare const computeGlobCacheKey: (input: {
4
+ patterns: string[];
5
+ cwd: string;
6
+ dir: string;
7
+ gitignore: boolean;
8
+ }) => string;
9
+ export declare const getCachedGlob: (key: string) => string[] | undefined;
10
+ export declare const setCachedGlob: (key: string, paths: string[], baseDir: string) => void;
11
+ export declare const clearGlobCache: () => void;
12
+ export declare const flushGlobCache: () => void;
@@ -0,0 +1,124 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { deserialize, serialize } from 'node:v8';
5
+ import { version } from "../version.js";
6
+ import { debugLog } from "./debug.js";
7
+ import { isDirectory, isFile } from "./fs.js";
8
+ import { dirname } from "./path.js";
9
+ const CACHE_FILENAME = `glob-${version}.cache`;
10
+ let cacheFilePath;
11
+ let cache;
12
+ let isDirty = false;
13
+ export const initGlobCache = (cacheLocation) => {
14
+ cacheFilePath = path.resolve(cacheLocation, CACHE_FILENAME);
15
+ if (isFile(cacheFilePath)) {
16
+ try {
17
+ cache = deserialize(fs.readFileSync(cacheFilePath));
18
+ }
19
+ catch {
20
+ debugLog('*', `Error reading glob cache from ${cacheFilePath}`);
21
+ cache = new Map();
22
+ }
23
+ }
24
+ else {
25
+ cache = new Map();
26
+ }
27
+ };
28
+ export const isGlobCacheEnabled = () => cache !== undefined;
29
+ export const computeGlobCacheKey = (input) => {
30
+ const h = createHash('sha1');
31
+ h.update(input.cwd);
32
+ h.update('\0');
33
+ h.update(input.dir);
34
+ h.update('\0');
35
+ h.update(input.gitignore ? '1' : '0');
36
+ h.update('\0');
37
+ for (const p of input.patterns) {
38
+ h.update(p);
39
+ h.update('\0');
40
+ }
41
+ return h.digest('base64url');
42
+ };
43
+ const validateEntry = (entry) => {
44
+ for (const dir in entry.dirMtimes) {
45
+ try {
46
+ const stat = fs.statSync(dir);
47
+ if (stat.mtimeMs !== entry.dirMtimes[dir])
48
+ return false;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ return true;
55
+ };
56
+ export const getCachedGlob = (key) => {
57
+ if (!cache)
58
+ return undefined;
59
+ const entry = cache.get(key);
60
+ if (!entry)
61
+ return undefined;
62
+ if (!validateEntry(entry)) {
63
+ cache.delete(key);
64
+ isDirty = true;
65
+ return undefined;
66
+ }
67
+ return entry.paths;
68
+ };
69
+ const captureDirMtimes = (paths, baseDir) => {
70
+ const dirs = new Set();
71
+ dirs.add(baseDir);
72
+ for (const p of paths) {
73
+ let d = dirname(p);
74
+ while (d.length >= baseDir.length) {
75
+ if (dirs.has(d))
76
+ break;
77
+ dirs.add(d);
78
+ const parent = dirname(d);
79
+ if (parent === d)
80
+ break;
81
+ d = parent;
82
+ }
83
+ }
84
+ const result = {};
85
+ for (const d of dirs) {
86
+ try {
87
+ const stat = fs.statSync(d);
88
+ if (stat.isDirectory())
89
+ result[d] = stat.mtimeMs;
90
+ }
91
+ catch {
92
+ }
93
+ }
94
+ return result;
95
+ };
96
+ export const setCachedGlob = (key, paths, baseDir) => {
97
+ if (!cache)
98
+ return;
99
+ cache.set(key, {
100
+ paths,
101
+ dirMtimes: captureDirMtimes(paths, baseDir),
102
+ });
103
+ isDirty = true;
104
+ };
105
+ export const clearGlobCache = () => {
106
+ if (cache) {
107
+ cache.clear();
108
+ isDirty = true;
109
+ }
110
+ };
111
+ export const flushGlobCache = () => {
112
+ if (!cache || !cacheFilePath || !isDirty)
113
+ return;
114
+ try {
115
+ const dir = dirname(cacheFilePath);
116
+ if (!isDirectory(dir))
117
+ fs.mkdirSync(dir, { recursive: true });
118
+ fs.writeFileSync(cacheFilePath, serialize(cache));
119
+ isDirty = false;
120
+ }
121
+ catch {
122
+ debugLog('*', `Error writing glob cache to ${cacheFilePath}`);
123
+ }
124
+ };
package/dist/util/glob.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import fg from 'fast-glob';
2
2
  import { compact } from "./array.js";
3
+ import { computeGlobCacheKey, getCachedGlob, isGlobCacheEnabled, setCachedGlob } from "./glob-cache.js";
3
4
  import { glob } from "./glob-core.js";
4
5
  import { timerify } from "./Performance.js";
5
6
  import { isAbsolute, join, relative } from "./path.js";
@@ -24,7 +25,14 @@ const defaultGlob = async ({ cwd, dir = cwd, patterns, gitignore = true, label }
24
25
  const globPatterns = prependDirToPatterns(cwd, dir, patterns);
25
26
  if (globPatterns[0].startsWith('!'))
26
27
  return [];
27
- return glob(globPatterns, {
28
+ const cacheEnabled = isGlobCacheEnabled();
29
+ const cacheKey = cacheEnabled ? computeGlobCacheKey({ patterns: globPatterns, cwd, dir, gitignore }) : '';
30
+ if (cacheEnabled) {
31
+ const cached = getCachedGlob(cacheKey);
32
+ if (cached)
33
+ return cached;
34
+ }
35
+ const paths = await glob(globPatterns, {
28
36
  cwd,
29
37
  dir,
30
38
  gitignore,
@@ -32,6 +40,9 @@ const defaultGlob = async ({ cwd, dir = cwd, patterns, gitignore = true, label }
32
40
  dot: true,
33
41
  label,
34
42
  });
43
+ if (cacheEnabled && paths.length > 0)
44
+ setCachedGlob(cacheKey, paths, dir);
45
+ return paths;
35
46
  };
36
47
  const syncGlob = ({ cwd, patterns }) => fg.sync(patterns, { cwd, followSymbolicLinks: false });
37
48
  const dirGlob = async ({ cwd, patterns, gitignore = true }) => glob(patterns, {
@@ -1,6 +1,12 @@
1
1
  const updateImportMaps = (fromImportMaps, toImportMaps) => {
2
2
  for (const id of fromImportMaps.refs)
3
3
  toImportMaps.refs.add(id);
4
+ if (fromImportMaps.enumerated) {
5
+ if (!toImportMaps.enumerated)
6
+ toImportMaps.enumerated = new Set();
7
+ for (const id of fromImportMaps.enumerated)
8
+ toImportMaps.enumerated.add(id);
9
+ }
4
10
  for (const [id, v] of fromImportMaps.import)
5
11
  addValues(toImportMaps.import, id, v);
6
12
  for (const [id, v] of fromImportMaps.importAs)
@@ -46,6 +52,7 @@ export const createFileNode = () => ({
46
52
  });
47
53
  export const createImports = () => ({
48
54
  refs: new Set(),
55
+ enumerated: undefined,
49
56
  import: new Map(),
50
57
  importAs: new Map(),
51
58
  importNs: new Map(),
@@ -0,0 +1 @@
1
+ export declare const extractPatternExtensions: (pattern: string) => string[];
@@ -0,0 +1,32 @@
1
+ const TRAILING_BRACE_RE = /\.\{([^{}]+)\}$/;
2
+ const TRAILING_PAREN_RE = /\.[@+*?]?\(([^()]+)\)$/;
3
+ const TRAILING_LITERAL_RE = /\.([a-zA-Z0-9]+)$/;
4
+ const SIMPLE_EXT_RE = /^[a-zA-Z0-9]+$/;
5
+ const collectExtensions = (inner, delim) => {
6
+ const parts = inner.split(delim);
7
+ const exts = [];
8
+ for (let i = 0; i < parts.length; i++) {
9
+ const t = parts[i].trim();
10
+ if (SIMPLE_EXT_RE.test(t))
11
+ exts.push(`.${t}`);
12
+ }
13
+ return exts;
14
+ };
15
+ export const extractPatternExtensions = (pattern) => {
16
+ const end = pattern.endsWith('!') ? pattern.length - 1 : pattern.length;
17
+ const slashIdx = pattern.lastIndexOf('/', end - 1);
18
+ const segment = pattern.slice(slashIdx + 1, end);
19
+ if (segment.length === 0)
20
+ return [];
21
+ const last = segment[segment.length - 1];
22
+ if (last === '}') {
23
+ const m = TRAILING_BRACE_RE.exec(segment);
24
+ return m ? collectExtensions(m[1], ',') : [];
25
+ }
26
+ if (last === ')') {
27
+ const m = TRAILING_PAREN_RE.exec(segment);
28
+ return m ? collectExtensions(m[1], '|') : [];
29
+ }
30
+ const m = TRAILING_LITERAL_RE.exec(segment);
31
+ return m ? [`.${m[1]}`] : [];
32
+ };
@@ -4,6 +4,8 @@ import { debugLog } from "./debug.js";
4
4
  import { isFile } from "./fs.js";
5
5
  import { updateImportMap } from "./module-graph.js";
6
6
  import { toAbsolute, toPosix, toRelative } from "./path.js";
7
+ import { clearModuleResolutionCaches } from "../typescript/resolve-module-names.js";
8
+ import { clearGlobCache } from "./glob-cache.js";
7
9
  import { clearResolverCache } from "./resolve.js";
8
10
  const createUpdate = (options) => {
9
11
  const duration = performance.now() - options.startTime;
@@ -56,6 +58,8 @@ export const getSessionHandler = async (options, { analyzedFiles, analyzeSourceF
56
58
  if (added.size === 0 && deleted.size === 0 && modified.size === 0)
57
59
  return;
58
60
  clearResolverCache();
61
+ clearModuleResolutionCaches();
62
+ clearGlobCache();
59
63
  invalidateCache(graph);
60
64
  unreferencedFiles.clear();
61
65
  const cachedUnusedFiles = collector.purge();
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "6.3.1";
1
+ export declare const version = "6.4.1";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '6.3.1';
1
+ export const version = '6.4.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "6.3.1",
3
+ "version": "6.4.1",
4
4
  "description": "Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects",
5
5
  "keywords": [
6
6
  "analysis",
package/schema.json CHANGED
@@ -632,6 +632,10 @@
632
632
  "title": "oxlint plugin configuration (https://knip.dev/reference/plugins/oxlint)",
633
633
  "$ref": "#/definitions/plugin"
634
634
  },
635
+ "panda-css": {
636
+ "title": "panda-css plugin configuration (https://knip.dev/reference/plugins/panda-css)",
637
+ "$ref": "#/definitions/plugin"
638
+ },
635
639
  "payload": {
636
640
  "title": "payload plugin configuration (https://knip.dev/reference/plugins/payload)",
637
641
  "$ref": "#/definitions/plugin"