knip 6.23.0 → 6.24.0

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 (47) hide show
  1. package/LICENSE +1 -1
  2. package/dist/ProjectPrincipal.js +3 -5
  3. package/dist/WorkspaceWorker.d.ts +3 -1
  4. package/dist/WorkspaceWorker.js +5 -2
  5. package/dist/binaries/resolvers/yarn.js +4 -3
  6. package/dist/compilers/compilers.d.ts +2 -0
  7. package/dist/compilers/compilers.js +15 -4
  8. package/dist/compilers/index.d.ts +1 -1
  9. package/dist/compilers/index.js +25 -10
  10. package/dist/compilers/less.d.ts +7 -0
  11. package/dist/compilers/less.js +37 -0
  12. package/dist/compilers/mdx.d.ts +1 -2
  13. package/dist/compilers/mdx.js +14 -8
  14. package/dist/compilers/scss.d.ts +3 -2
  15. package/dist/compilers/scss.js +17 -14
  16. package/dist/compilers/shared.d.ts +6 -0
  17. package/dist/compilers/shared.js +9 -0
  18. package/dist/compilers/style-preprocessors.d.ts +2 -0
  19. package/dist/compilers/style-preprocessors.js +41 -0
  20. package/dist/compilers/stylus.d.ts +7 -0
  21. package/dist/compilers/stylus.js +36 -0
  22. package/dist/constants.js +2 -0
  23. package/dist/graph/build.js +14 -1
  24. package/dist/plugins/astro/compiler-mdx.js +11 -2
  25. package/dist/plugins/astro/compiler.js +8 -4
  26. package/dist/plugins/github-actions/index.js +3 -2
  27. package/dist/plugins/nuxt/index.js +4 -2
  28. package/dist/plugins/prisma/compiler.js +3 -0
  29. package/dist/plugins/stencil/index.js +59 -1
  30. package/dist/plugins/svelte/compiler.d.ts +2 -1
  31. package/dist/plugins/svelte/compiler.js +8 -1
  32. package/dist/plugins/tailwind/compiler.js +3 -0
  33. package/dist/plugins/tailwind/index.js +5 -1
  34. package/dist/plugins/vue/compiler.d.ts +2 -1
  35. package/dist/plugins/vue/compiler.js +8 -1
  36. package/dist/reporters/compact.js +1 -2
  37. package/dist/reporters/symbols.js +1 -2
  38. package/dist/types/config.d.ts +1 -0
  39. package/dist/types.d.ts +1 -1
  40. package/dist/typescript/SourceFileManager.js +3 -2
  41. package/dist/typescript/ast-nodes.d.ts +1 -0
  42. package/dist/typescript/ast-nodes.js +51 -1
  43. package/dist/typescript/comments.js +22 -3
  44. package/dist/typescript/visitors/walk.js +7 -1
  45. package/dist/version.d.ts +1 -1
  46. package/dist/version.js +1 -1
  47. package/package.json +1 -1
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  ISC License (ISC)
2
2
 
3
- Copyright 2022-2025 Lars Kappert
3
+ Copyright 2022-2026 Lars Kappert
4
4
 
5
5
  Permission to use, copy, modify, and/or distribute this software for any purpose
6
6
  with or without fee is hereby granted, provided that the above copyright notice
@@ -130,11 +130,9 @@ export class ProjectPrincipal {
130
130
  }
131
131
  async runAsyncCompilers() {
132
132
  const add = timerify(this.fileManager.compileAndAddSourceFile.bind(this.fileManager));
133
- const extensions = Array.from(this.asyncCompilers.keys());
134
- const files = Array.from(this.projectPaths).filter(filePath => extensions.includes(extname(filePath)));
135
- for (const filePath of files) {
136
- await add(filePath);
137
- }
133
+ for (const filePath of this.projectPaths)
134
+ if (this.asyncCompilers.has(extname(filePath)))
135
+ await add(filePath);
138
136
  }
139
137
  walkAndAnalyze(analyzeFile) {
140
138
  this.resolvedFiles.clear();
@@ -17,6 +17,7 @@ type WorkspaceManagerOptions = {
17
17
  rootManifest: Manifest | undefined;
18
18
  handleInput: HandleInput;
19
19
  findWorkspaceByFilePath: (filePath: string) => Workspace | undefined;
20
+ getManifest: (dir: string) => Manifest | undefined;
20
21
  readFile: (filePath: string) => string;
21
22
  negatedWorkspacePatterns: string[];
22
23
  ignoredWorkspacePatterns: string[];
@@ -38,6 +39,7 @@ export declare class WorkspaceWorker {
38
39
  dependencies: DependencySet;
39
40
  handleInput: HandleInput;
40
41
  findWorkspaceByFilePath: (filePath: string) => Workspace | undefined;
42
+ getManifest: (dir: string) => Manifest | undefined;
41
43
  readFile: (filePath: string) => string;
42
44
  negatedWorkspacePatterns: string[];
43
45
  ignoredWorkspacePatterns: string[];
@@ -47,7 +49,7 @@ export declare class WorkspaceWorker {
47
49
  enabledPluginsInAncestors: string[];
48
50
  cache: CacheConsultant<CacheItem>;
49
51
  configFilesMap: Map<string, Map<PluginName, Set<string>>>;
50
- constructor({ name, dir, config, manifest, dependencies, rootManifest, negatedWorkspacePatterns, ignoredWorkspacePatterns, enabledPluginsInAncestors, handleInput, findWorkspaceByFilePath, readFile, configFilesMap, options, }: WorkspaceManagerOptions);
52
+ constructor({ name, dir, config, manifest, dependencies, rootManifest, negatedWorkspacePatterns, ignoredWorkspacePatterns, enabledPluginsInAncestors, handleInput, findWorkspaceByFilePath, getManifest, readFile, configFilesMap, options, }: WorkspaceManagerOptions);
51
53
  init(): Promise<void>;
52
54
  private determineEnabledPlugins;
53
55
  private getConfigForPlugin;
@@ -32,6 +32,7 @@ export class WorkspaceWorker {
32
32
  dependencies;
33
33
  handleInput;
34
34
  findWorkspaceByFilePath;
35
+ getManifest;
35
36
  readFile;
36
37
  negatedWorkspacePatterns = [];
37
38
  ignoredWorkspacePatterns = [];
@@ -41,7 +42,7 @@ export class WorkspaceWorker {
41
42
  enabledPluginsInAncestors;
42
43
  cache;
43
44
  configFilesMap;
44
- constructor({ name, dir, config, manifest, dependencies, rootManifest, negatedWorkspacePatterns, ignoredWorkspacePatterns, enabledPluginsInAncestors, handleInput, findWorkspaceByFilePath, readFile, configFilesMap, options, }) {
45
+ constructor({ name, dir, config, manifest, dependencies, rootManifest, negatedWorkspacePatterns, ignoredWorkspacePatterns, enabledPluginsInAncestors, handleInput, findWorkspaceByFilePath, getManifest, readFile, configFilesMap, options, }) {
45
46
  this.name = name;
46
47
  this.dir = dir;
47
48
  this.config = config;
@@ -54,6 +55,7 @@ export class WorkspaceWorker {
54
55
  this.configFilesMap = configFilesMap;
55
56
  this.handleInput = handleInput;
56
57
  this.findWorkspaceByFilePath = findWorkspaceByFilePath;
58
+ this.getManifest = getManifest;
57
59
  this.readFile = readFile;
58
60
  this.options = options;
59
61
  this.cache = new CacheConsultant(`plugins-${name}`, options);
@@ -211,7 +213,8 @@ export class WorkspaceWorker {
211
213
  const isProduction = this.options.isProduction;
212
214
  const knownBinsOnly = false;
213
215
  const rootManifest = this.rootManifest;
214
- const baseOptions = { manifest, rootManifest, cwd, rootCwd, containingFilePath, knownBinsOnly };
216
+ const getManifest = this.getManifest;
217
+ const baseOptions = { manifest, rootManifest, cwd, rootCwd, containingFilePath, knownBinsOnly, getManifest };
215
218
  const baseScriptOptions = { ...baseOptions, isProduction, enabledPlugins: this.enabledPlugins };
216
219
  const [productionScripts, developmentScripts] = getFilteredScripts(manifest.scripts ?? {});
217
220
  const inputsFromManifest = _getInputsFromScripts(Object.values(developmentScripts), baseOptions);
@@ -49,15 +49,16 @@ const resolveDlx = (args, options) => {
49
49
  return [...packages.map(id => toDependency(id)), ...command].map(id => isDependency(id) || isBinary(id) ? Object.assign(id, { optional: true }) : id);
50
50
  };
51
51
  export const resolve = (_binary, args, options) => {
52
- const { manifest, fromArgs, cwd, rootCwd } = options;
52
+ const { manifest, fromArgs, cwd, rootCwd, getManifest } = options;
53
53
  const parsed = parseArgs(args, { boolean: ['top-level'], string: ['cwd'], '--': true });
54
54
  const dir = parsed['top-level'] ? rootCwd : parsed.cwd ? join(cwd, parsed.cwd) : undefined;
55
55
  const [command, binary] = parsed._;
56
56
  if (!command && !binary)
57
57
  return [];
58
+ const dirManifest = (dir && getManifest(dir)) || manifest;
58
59
  const _childArgs = parsed['--'] && parsed['--'].length > 0 ? fromArgs(parsed['--'], { knownBinsOnly: true }) : [];
59
60
  if (command === 'run') {
60
- if (manifest.scriptNames.has(binary))
61
+ if (dirManifest.scriptNames.has(binary))
61
62
  return _childArgs;
62
63
  const bin = toBinary(binary, { optional: true });
63
64
  if (dir)
@@ -70,7 +71,7 @@ export const resolve = (_binary, args, options) => {
70
71
  const argsForDlx = args.filter(arg => arg !== 'dlx');
71
72
  return resolveDlx(argsForDlx, options);
72
73
  }
73
- if ((!dir && manifest.scriptNames.has(command)) || commands.includes(command))
74
+ if (dirManifest.scriptNames.has(command) || commands.includes(command))
74
75
  return _childArgs;
75
76
  const opts = dir ? { cwd: dir } : {};
76
77
  return fromArgs(argsFrom(args, command === 'exec' ? binary : command), opts);
@@ -2,10 +2,12 @@ import type { CompilerSync } from './types.ts';
2
2
  export declare const fencedCodeBlockMatcher: RegExp;
3
3
  export declare const inlineCodeMatcher: RegExp;
4
4
  export declare const scriptExtractor: RegExp;
5
+ export declare const styleExtractor: RegExp;
5
6
  export declare const blockCommentMatcher: RegExp;
6
7
  export declare const lineCommentMatcher: RegExp;
7
8
  export declare const importMatcher: RegExp;
8
9
  export declare const importsWithinScripts: CompilerSync;
9
10
  export declare const scriptBodies: CompilerSync;
11
+ export declare const getStyleLang: (attrs: string) => string | undefined;
10
12
  export declare const frontmatterMatcher: RegExp;
11
13
  export declare const importsWithinFrontmatter: (text: string, keys?: string[]) => string;
@@ -1,26 +1,37 @@
1
1
  export const fencedCodeBlockMatcher = /```[\s\S]*?```/g;
2
2
  export const inlineCodeMatcher = /`[^`]+`/g;
3
3
  export const scriptExtractor = /<script\b((?:[^>"']|"[^"]*"|'[^']*')*)>([\s\S]*?)<\/script>/gi;
4
+ export const styleExtractor = /<style\b((?:[^>"']|"[^"]*"|'[^']*')*)>([\s\S]*?)<\/style>/gi;
5
+ const langAttrMatcher = /\blang\s*=\s*["']([^"']+)["']/i;
4
6
  export const blockCommentMatcher = /\/\*[\s\S]*?\*\//g;
5
7
  export const lineCommentMatcher = /^[ \t]*\/\/.*$/gm;
6
8
  export const importMatcher = /import(?:\s*\(\s*['"][^'"]+['"][^)]*\)|(?!\s*\()[^'"]+['"][^'"]+['"])/g;
7
9
  export const importsWithinScripts = (text) => {
8
10
  const scripts = [];
9
- for (const [, , scriptBody] of text.matchAll(scriptExtractor)) {
11
+ scriptExtractor.lastIndex = 0;
12
+ let scriptMatch;
13
+ while ((scriptMatch = scriptExtractor.exec(text))) {
14
+ const scriptBody = scriptMatch[2];
10
15
  const body = scriptBody.replace(blockCommentMatcher, '').replace(lineCommentMatcher, '');
11
- for (const importMatch of body.matchAll(importMatcher))
12
- scripts.push(importMatch);
16
+ let importMatch;
17
+ importMatcher.lastIndex = 0;
18
+ while ((importMatch = importMatcher.exec(body)))
19
+ scripts.push(importMatch[0]);
13
20
  }
14
21
  return scripts.join(';\n');
15
22
  };
16
23
  export const scriptBodies = (text) => {
17
24
  const scripts = [];
18
- for (const [, , body] of text.matchAll(scriptExtractor)) {
25
+ scriptExtractor.lastIndex = 0;
26
+ let match;
27
+ while ((match = scriptExtractor.exec(text))) {
28
+ const body = match[2];
19
29
  if (body)
20
30
  scripts.push(body);
21
31
  }
22
32
  return scripts.join(';\n');
23
33
  };
34
+ export const getStyleLang = (attrs) => attrs.match(langAttrMatcher)?.[1]?.toLowerCase();
24
35
  export const frontmatterMatcher = /^---\r?\n([\s\S]*?)\r?\n---/;
25
36
  export const importsWithinFrontmatter = (text, keys = []) => {
26
37
  const frontmatter = text.match(frontmatterMatcher)?.[1];
@@ -1611,5 +1611,5 @@ export declare const partitionCompilers: (rawLocalConfig: RawConfiguration) => {
1611
1611
  syncCompilers: Record<string, true | CompilerSync>;
1612
1612
  asyncCompilers: Record<string, CompilerAsync>;
1613
1613
  };
1614
- export declare const getIncludedCompilers: (syncCompilers: RawSyncCompilers, asyncCompilers: AsyncCompilers, dependencies: DependencySet) => Compilers;
1614
+ export declare const getIncludedCompilers: (syncCompilers: RawSyncCompilers, asyncCompilers: AsyncCompilers, dependencies: DependencySet, onReferencedDependency?: (packageName: string) => void) => Compilers;
1615
1615
  export declare const getCompilerExtensions: (compilers: [SyncCompilers, AsyncCompilers]) => string[];
@@ -1,5 +1,7 @@
1
+ import LESS from './less.js';
1
2
  import MDX from './mdx.js';
2
3
  import SCSS from './scss.js';
4
+ import STYLUS from './stylus.js';
3
5
  const isAsyncCompiler = (fn) => (fn ? fn.constructor.name === 'AsyncFunction' : false);
4
6
  export const normalizeCompilerExtension = (ext) => ext.replace(/^\.*/, '.');
5
7
  export const partitionCompilers = (rawLocalConfig) => {
@@ -26,16 +28,29 @@ export const partitionCompilers = (rawLocalConfig) => {
26
28
  }
27
29
  return { ...rawLocalConfig, syncCompilers, asyncCompilers };
28
30
  };
29
- const compilers = new Map([
30
- ['.mdx', MDX],
31
- ['.sass', SCSS],
32
- ['.scss', SCSS],
33
- ]);
34
- export const getIncludedCompilers = (syncCompilers, asyncCompilers, dependencies) => {
35
- const hasDependency = (packageName) => dependencies.has(packageName);
36
- for (const [extension, { condition, compiler }] of compilers) {
37
- if ((!syncCompilers.has(extension) && condition(hasDependency)) || syncCompilers.get(extension) === true) {
38
- syncCompilers.set(extension, compiler);
31
+ const compilers = [
32
+ { extensions: ['.mdx'], ...MDX },
33
+ { extensions: ['.sass', '.scss'], ...SCSS },
34
+ { extensions: ['.less'], ...LESS },
35
+ { extensions: ['.styl', '.stylus'], ...STYLUS },
36
+ ];
37
+ export const getIncludedCompilers = (syncCompilers, asyncCompilers, dependencies, onReferencedDependency) => {
38
+ for (const { extensions, dependencies: compilerDependencies, compiler } of compilers) {
39
+ let hasCompilerDependency = false;
40
+ for (const dependency of compilerDependencies) {
41
+ if (dependencies.has(dependency)) {
42
+ hasCompilerDependency = true;
43
+ if (onReferencedDependency)
44
+ onReferencedDependency(dependency);
45
+ else
46
+ break;
47
+ }
48
+ }
49
+ for (const extension of extensions) {
50
+ const existingCompiler = syncCompilers.get(extension);
51
+ if (existingCompiler === true || (existingCompiler === undefined && hasCompilerDependency)) {
52
+ syncCompilers.set(extension, compiler);
53
+ }
39
54
  }
40
55
  }
41
56
  return [syncCompilers, asyncCompilers];
@@ -0,0 +1,7 @@
1
+ import type { CompilerSync } from './types.ts';
2
+ export declare const compiler: CompilerSync;
3
+ declare const _default: {
4
+ dependencies: string[];
5
+ compiler: CompilerSync;
6
+ };
7
+ export default _default;
@@ -0,0 +1,37 @@
1
+ import { isScopedPackage, isTildePackage, splitSpec } from './shared.js';
2
+ const dependencies = ['less'];
3
+ const importMatcher = /@import\s+(?:\([^)]*\)\s+)?(?:url\(\s*['"]?([^'")\s]+)['"]?\s*\)|['"]([^'"]+)['"])/g;
4
+ const isExternalUrl = (s) => s.startsWith('//') || s.startsWith('http://') || s.startsWith('https://');
5
+ const candidates = (specifier) => {
6
+ const { dir, name } = splitSpec(specifier);
7
+ if (name.endsWith('.less') || name.endsWith('.css'))
8
+ return [`${dir}/${name}`];
9
+ return [`${dir}/${name}.less`];
10
+ };
11
+ export const compiler = text => {
12
+ if (!text.includes('@import'))
13
+ return '';
14
+ const out = [];
15
+ let i = 0;
16
+ let match;
17
+ importMatcher.lastIndex = 0;
18
+ while ((match = importMatcher.exec(text))) {
19
+ let spec = match[1] ?? match[2];
20
+ if (!spec || isExternalUrl(spec))
21
+ continue;
22
+ let isBare = isScopedPackage(spec);
23
+ if (isTildePackage(spec)) {
24
+ spec = spec.slice(1);
25
+ isBare = true;
26
+ }
27
+ if (isBare) {
28
+ out.push(`import _$${i++} from '${spec}';`);
29
+ }
30
+ else {
31
+ for (const s of candidates(spec))
32
+ out.push(`import _$${i++} from '${s}';`);
33
+ }
34
+ }
35
+ return out.join('\n');
36
+ };
37
+ export default { dependencies, compiler };
@@ -1,6 +1,5 @@
1
- import type { HasDependency } from './types.ts';
2
1
  declare const _default: {
3
- condition: (hasDependency: HasDependency) => boolean;
2
+ dependencies: string[];
4
3
  compiler: (text: string) => string;
5
4
  };
6
5
  export default _default;
@@ -1,5 +1,5 @@
1
1
  import { fencedCodeBlockMatcher, frontmatterMatcher, inlineCodeMatcher } from './compilers.js';
2
- const mdxDependencies = [
2
+ const dependencies = [
3
3
  '@mdx-js/esbuild',
4
4
  '@mdx-js/loader',
5
5
  '@mdx-js/mdx',
@@ -10,13 +10,19 @@ const mdxDependencies = [
10
10
  '@mdx-js/vue',
11
11
  'remark-mdx',
12
12
  ];
13
- const condition = (hasDependency) => mdxDependencies.some(hasDependency);
14
13
  const mdxImportMatcher = /^import[^'"]+['"][^'"]+['"]/gm;
15
- const compiler = (text) => [
16
- ...text
14
+ const compiler = (text) => {
15
+ if (!text.includes('import'))
16
+ return '';
17
+ const imports = [];
18
+ const source = text
17
19
  .replace(frontmatterMatcher, '')
18
20
  .replace(fencedCodeBlockMatcher, '')
19
- .replace(inlineCodeMatcher, '')
20
- .matchAll(mdxImportMatcher),
21
- ].join('\n');
22
- export default { condition, compiler };
21
+ .replace(inlineCodeMatcher, '');
22
+ let match;
23
+ mdxImportMatcher.lastIndex = 0;
24
+ while ((match = mdxImportMatcher.exec(source)))
25
+ imports.push(match[0]);
26
+ return imports.join('\n');
27
+ };
28
+ export default { dependencies, compiler };
@@ -1,6 +1,7 @@
1
- import type { CompilerSync, HasDependency } from './types.ts';
1
+ import type { CompilerSync } from './types.ts';
2
+ export declare const compiler: CompilerSync;
2
3
  declare const _default: {
3
- condition: (hasDependency: HasDependency) => boolean;
4
+ dependencies: string[];
4
5
  compiler: CompilerSync;
5
6
  };
6
7
  export default _default;
@@ -1,13 +1,8 @@
1
- import { basename, dirname } from '../util/path.js';
2
- const condition = (hasDependency) => hasDependency('sass') || hasDependency('sass-embedded') || hasDependency('node-sass');
1
+ import { isScopedPackage, isTildePackage, splitSpec } from './shared.js';
2
+ const dependencies = ['sass', 'sass-embedded', 'node-sass'];
3
3
  const importMatcher = /@(?:use|import|forward)\s+['"](pkg:)?([^'"]+)['"]/g;
4
- const isAlias = (s) => (s.charCodeAt(0) === 64 && s.charCodeAt(1) === 47) || s.charCodeAt(0) === 126 || s.charCodeAt(0) === 35;
5
- const isScopedPackage = (s) => s.charCodeAt(0) === 64 && s.charCodeAt(1) !== 47;
6
- const isTildePackage = (s) => s.charCodeAt(0) === 126 && s.charCodeAt(1) !== 47;
7
4
  const candidates = (specifier) => {
8
- const spec = specifier.startsWith('.') || isAlias(specifier) ? specifier : `./${specifier}`;
9
- const name = basename(spec);
10
- const dir = dirname(spec);
5
+ const { dir, name } = splitSpec(specifier);
11
6
  const hasExt = name.endsWith('.scss') || name.endsWith('.sass');
12
7
  const bases = hasExt ? [name] : [`${name}.scss`, `${name}.sass`];
13
8
  const out = [];
@@ -18,10 +13,14 @@ const candidates = (specifier) => {
18
13
  }
19
14
  return out;
20
15
  };
21
- const compiler = text => {
16
+ export const compiler = text => {
17
+ if (!text.includes('@use') && !text.includes('@import') && !text.includes('@forward'))
18
+ return '';
22
19
  const out = [];
23
20
  let i = 0;
24
- for (const match of text.matchAll(importMatcher)) {
21
+ let match;
22
+ importMatcher.lastIndex = 0;
23
+ while ((match = importMatcher.exec(text))) {
25
24
  let spec = match[2];
26
25
  if (!spec || spec.startsWith('sass:'))
27
26
  continue;
@@ -30,10 +29,14 @@ const compiler = text => {
30
29
  spec = spec.slice(1);
31
30
  isBare = true;
32
31
  }
33
- const specs = isBare ? [spec] : candidates(spec);
34
- for (const s of specs)
35
- out.push(`import _$${i++} from '${s}';`);
32
+ if (isBare) {
33
+ out.push(`import _$${i++} from '${spec}';`);
34
+ }
35
+ else {
36
+ for (const s of candidates(spec))
37
+ out.push(`import _$${i++} from '${s}';`);
38
+ }
36
39
  }
37
40
  return out.join('\n');
38
41
  };
39
- export default { condition, compiler };
42
+ export default { dependencies, compiler };
@@ -0,0 +1,6 @@
1
+ export declare const isScopedPackage: (s: string) => boolean;
2
+ export declare const isTildePackage: (s: string) => boolean;
3
+ export declare const splitSpec: (specifier: string) => {
4
+ dir: string;
5
+ name: string;
6
+ };
@@ -0,0 +1,9 @@
1
+ import { basename, dirname } from '../util/path.js';
2
+ const isAlias = (s) => (s.charCodeAt(0) === 64 && s.charCodeAt(1) === 47) || s.charCodeAt(0) === 126 || s.charCodeAt(0) === 35;
3
+ export const isScopedPackage = (s) => s.charCodeAt(0) === 64 && s.charCodeAt(1) !== 47;
4
+ export const isTildePackage = (s) => s.charCodeAt(0) === 126 && s.charCodeAt(1) !== 47;
5
+ const ensureRelative = (spec) => (spec.startsWith('.') || isAlias(spec) ? spec : `./${spec}`);
6
+ export const splitSpec = (specifier) => {
7
+ const spec = ensureRelative(specifier);
8
+ return { dir: dirname(spec), name: basename(spec) };
9
+ };
@@ -0,0 +1,2 @@
1
+ import type { CompilerSync } from './types.ts';
2
+ export declare const stylePreprocessorImports: CompilerSync;
@@ -0,0 +1,41 @@
1
+ import { getStyleLang, styleExtractor } from './compilers.js';
2
+ import { compiler as lessCompiler } from './less.js';
3
+ import { compiler as scssCompiler } from './scss.js';
4
+ import { compiler as stylusCompiler } from './stylus.js';
5
+ export const stylePreprocessorImports = (text, path) => {
6
+ let scss = '';
7
+ let less = '';
8
+ let stylus = '';
9
+ styleExtractor.lastIndex = 0;
10
+ let match;
11
+ while ((match = styleExtractor.exec(text))) {
12
+ const attrs = match[1];
13
+ const body = match[2];
14
+ if (!body)
15
+ continue;
16
+ switch (getStyleLang(attrs)) {
17
+ case 'scss':
18
+ case 'sass':
19
+ scss = scss ? `${scss}\n${body}` : body;
20
+ break;
21
+ case 'less':
22
+ less = less ? `${less}\n${body}` : body;
23
+ break;
24
+ case 'styl':
25
+ case 'stylus':
26
+ stylus = stylus ? `${stylus}\n${body}` : body;
27
+ }
28
+ }
29
+ let out = scss ? scssCompiler(scss, path) : '';
30
+ if (less) {
31
+ const imports = lessCompiler(less, path);
32
+ if (imports)
33
+ out = out ? `${out}\n${imports}` : imports;
34
+ }
35
+ if (stylus) {
36
+ const imports = stylusCompiler(stylus, path);
37
+ if (imports)
38
+ out = out ? `${out}\n${imports}` : imports;
39
+ }
40
+ return out;
41
+ };
@@ -0,0 +1,7 @@
1
+ import type { CompilerSync } from './types.ts';
2
+ export declare const compiler: CompilerSync;
3
+ declare const _default: {
4
+ dependencies: string[];
5
+ compiler: CompilerSync;
6
+ };
7
+ export default _default;
@@ -0,0 +1,36 @@
1
+ import { isScopedPackage, isTildePackage, splitSpec } from './shared.js';
2
+ const dependencies = ['stylus'];
3
+ const importMatcher = /@(?:import|require)\s+['"]([^'"]+)['"]/g;
4
+ const candidates = (specifier) => {
5
+ const { dir, name } = splitSpec(specifier);
6
+ if (name.endsWith('.styl') || name.endsWith('.stylus') || name.endsWith('.css'))
7
+ return [`${dir}/${name}`];
8
+ return [`${dir}/${name}.styl`, `${dir}/${name}/index.styl`];
9
+ };
10
+ export const compiler = text => {
11
+ if (!text.includes('@import') && !text.includes('@require'))
12
+ return '';
13
+ const out = [];
14
+ let i = 0;
15
+ let match;
16
+ importMatcher.lastIndex = 0;
17
+ while ((match = importMatcher.exec(text))) {
18
+ let spec = match[1];
19
+ if (!spec || spec.includes('*'))
20
+ continue;
21
+ let isBare = isScopedPackage(spec);
22
+ if (isTildePackage(spec)) {
23
+ spec = spec.slice(1);
24
+ isBare = true;
25
+ }
26
+ if (isBare) {
27
+ out.push(`import _$${i++} from '${spec}';`);
28
+ }
29
+ else {
30
+ for (const s of candidates(spec))
31
+ out.push(`import _$${i++} from '${s}';`);
32
+ }
33
+ }
34
+ return out.join('\n');
35
+ };
36
+ export default { dependencies, compiler };
package/dist/constants.js CHANGED
@@ -155,6 +155,8 @@ export const FOREIGN_FILE_EXTENSIONS = new Set([
155
155
  '.sass',
156
156
  '.scss',
157
157
  '.sh',
158
+ '.styl',
159
+ '.stylus',
158
160
  '.svg',
159
161
  '.ttf',
160
162
  '.webp',
@@ -28,6 +28,17 @@ export async function build({ chief, collector, counselor, deputy, principal, is
28
28
  const handleInput = createInputHandler(deputy, chief, isGitIgnored, addIssue, externalRefsFromInputs, options);
29
29
  const rawRootManifest = chief.getManifestForWorkspace('.');
30
30
  const rootManifest = rawRootManifest ? createManifest(rawRootManifest) : undefined;
31
+ const manifestsByWorkspaceName = new Map();
32
+ const getManifest = (dir) => {
33
+ const workspace = chief.findWorkspaceByFilePath(`${dir}/`);
34
+ if (!workspace)
35
+ return undefined;
36
+ if (!manifestsByWorkspaceName.has(workspace.name)) {
37
+ const raw = chief.getManifestForWorkspace(workspace.name);
38
+ manifestsByWorkspaceName.set(workspace.name, raw ? createManifest(raw) : undefined);
39
+ }
40
+ return manifestsByWorkspaceName.get(workspace.name);
41
+ };
31
42
  for (const workspace of workspaces) {
32
43
  const { name, dir, manifestPath, manifestStr } = workspace;
33
44
  const manifest = chief.getManifestForWorkspace(name);
@@ -70,6 +81,7 @@ export async function build({ chief, collector, counselor, deputy, principal, is
70
81
  rootManifest,
71
82
  handleInput: (input) => handleInput(input, workspace),
72
83
  findWorkspaceByFilePath: chief.findWorkspaceByFilePath.bind(chief),
84
+ getManifest,
73
85
  negatedWorkspacePatterns: chief.getNegatedWorkspacePatterns(name),
74
86
  ignoredWorkspacePatterns: chief.getIgnoredWorkspacesFor(name),
75
87
  enabledPluginsInAncestors: ancestors.flatMap(ancestor => enabledPluginsStore.get(ancestor) ?? []),
@@ -78,7 +90,7 @@ export async function build({ chief, collector, counselor, deputy, principal, is
78
90
  options,
79
91
  });
80
92
  await worker.init();
81
- const compilers = getIncludedCompilers(chief.config.syncCompilers, chief.config.asyncCompilers, dependencies);
93
+ const compilers = getIncludedCompilers(chief.config.syncCompilers, chief.config.asyncCompilers, dependencies, dep => deputy.addReferencedDependency(name, dep));
82
94
  const registerCompiler = async ({ extension, compiler }) => {
83
95
  const ext = normalizeCompilerExtension(extension);
84
96
  if (compilers[0].has(ext))
@@ -359,6 +371,7 @@ export async function build({ chief, collector, counselor, deputy, principal, is
359
371
  dependencies,
360
372
  manifest: createManifest(manifest),
361
373
  rootManifest,
374
+ getManifest,
362
375
  };
363
376
  const inputs = _getInputsFromScripts(file.scripts, opts);
364
377
  for (const input of inputs) {
@@ -1,8 +1,17 @@
1
1
  import { fencedCodeBlockMatcher, importMatcher, importsWithinFrontmatter, inlineCodeMatcher, } from '../../compilers/compilers.js';
2
2
  const frontmatterImportFields = ['layout'];
3
3
  const compiler = (text) => {
4
- const imports = text.replace(fencedCodeBlockMatcher, '').replace(inlineCodeMatcher, '').matchAll(importMatcher);
5
4
  const frontmatterImports = importsWithinFrontmatter(text, frontmatterImportFields);
6
- return [...imports, frontmatterImports].join('\n');
5
+ if (!text.includes('import'))
6
+ return frontmatterImports;
7
+ const imports = [];
8
+ const source = text.replace(fencedCodeBlockMatcher, '').replace(inlineCodeMatcher, '');
9
+ let match;
10
+ importMatcher.lastIndex = 0;
11
+ while ((match = importMatcher.exec(source)))
12
+ imports.push(match[0]);
13
+ if (frontmatterImports)
14
+ imports.push(frontmatterImports);
15
+ return imports.join('\n');
7
16
  };
8
17
  export default compiler;
@@ -1,17 +1,21 @@
1
1
  import { frontmatterMatcher, scriptBodies } from '../../compilers/compilers.js';
2
+ import { stylePreprocessorImports } from '../../compilers/style-preprocessors.js';
2
3
  const propsDeclMatcher = /(?:^|[\s;])(?:interface|type)\s+Props\b/;
3
4
  const compiler = (text, path) => {
4
- const scripts = [];
5
+ let out = '';
5
6
  const frontmatter = text.match(frontmatterMatcher);
6
7
  if (frontmatter?.[1]) {
7
8
  let fm = frontmatter[1];
8
9
  if (propsDeclMatcher.test(fm) && text.includes('Astro.props'))
9
10
  fm += '\ntype __knip_astro_props = Props;';
10
- scripts.push(fm);
11
+ out = fm;
11
12
  }
12
13
  const scriptContent = scriptBodies(text, path);
13
14
  if (scriptContent)
14
- scripts.push(scriptContent);
15
- return scripts.join('\n');
15
+ out = out ? `${out}\n${scriptContent}` : scriptContent;
16
+ const styleImports = stylePreprocessorImports(text, path);
17
+ if (styleImports)
18
+ out = out ? `${out}\n${styleImports}` : styleImports;
19
+ return out;
16
20
  };
17
21
  export default compiler;
@@ -18,7 +18,7 @@ export const getActionDependencies = (config, options) => {
18
18
  return scripts.map(script => join(configFileDir, script));
19
19
  };
20
20
  const resolveConfig = async (config, options) => {
21
- const { rootCwd, getInputsFromScripts, isProduction } = options;
21
+ const { rootCwd, getInputsFromScripts, isProduction, getManifest } = options;
22
22
  const inputs = new Set();
23
23
  const jobs = findByKeyDeep(config, 'steps');
24
24
  for (const steps of jobs) {
@@ -30,7 +30,8 @@ const resolveConfig = async (config, options) => {
30
30
  const workingDir = step['working-directory'];
31
31
  const dir = join(rootCwd, path && workingDir ? relative(workingDir, path) : workingDir ? workingDir : '.');
32
32
  if (step.run) {
33
- for (const input of getInputsFromScripts([step.run], { knownBinsOnly: true })) {
33
+ const manifest = getManifest(dir) ?? options.manifest;
34
+ for (const input of getInputsFromScripts([step.run], { knownBinsOnly: true, manifest })) {
34
35
  if (isDeferResolveEntry(input) && path && !workingDir) {
35
36
  input.specifier = relative(join(dir, path), join(rootCwd, input.specifier));
36
37
  }
@@ -82,10 +82,12 @@ const registerCompilers = async ({ cwd, hasDependency, registerCompiler }) => {
82
82
  if (store)
83
83
  store.push(...components);
84
84
  else
85
- componentMap.set(id, [...components]);
85
+ componentMap.set(id, components);
86
86
  }
87
87
  }
88
88
  const getSyntheticImports = (identifiers, templateTags) => {
89
+ if (importMap.size === 0 && (!templateTags || componentMap.size === 0))
90
+ return [];
89
91
  const syntheticImports = [];
90
92
  for (const [name, specifier] of importMap) {
91
93
  if (identifiers.has(name))
@@ -113,7 +115,7 @@ const registerCompilers = async ({ cwd, hasDependency, registerCompiler }) => {
113
115
  scripts.push(descriptor.script.content);
114
116
  if (descriptor.scriptSetup?.content)
115
117
  scripts.push(descriptor.scriptSetup.content);
116
- const identifiers = collectIdentifiers(scripts.join('\n'), path);
118
+ const identifiers = scripts.length === 0 ? new Set() : collectIdentifiers(scripts.join('\n'), path);
117
119
  let templateTags;
118
120
  if (descriptor.template?.ast) {
119
121
  const info = collectTemplateInfo(descriptor.template.ast);
@@ -1,7 +1,10 @@
1
1
  const directiveMatcher = /generator\s+(?!client)\w+\s*\{\s*provider\s*=\s*"([^"]+)"[^}]*\}/g;
2
2
  const compiler = (text) => {
3
+ if (!text.includes('generator'))
4
+ return '';
3
5
  const imports = [];
4
6
  let match;
7
+ directiveMatcher.lastIndex = 0;
5
8
  while ((match = directiveMatcher.exec(text))) {
6
9
  if (match[1]) {
7
10
  imports.push(`import '${match[1]}';`);
@@ -1,6 +1,10 @@
1
+ import { Visitor } from 'oxc-parser';
2
+ import scss from '../../compilers/scss.js';
3
+ import { IMPORT_FLAGS } from '../../constants.js';
1
4
  import { toConfig, toEntry, toProductionEntry } from '../../util/input.js';
5
+ import { dirname, join } from '../../util/path.js';
2
6
  import { hasDependency } from '../../util/plugin.js';
3
- import { collectPropertyValues } from '../../typescript/ast-helpers.js';
7
+ import { collectPropertyValues, getPropertyValues } from '../../typescript/ast-helpers.js';
4
8
  import { createCustomElementVisitor } from '../_custom-elements/custom-element-visitor.js';
5
9
  const title = 'Stencil';
6
10
  const enablers = ['@stencil/core'];
@@ -9,8 +13,49 @@ const config = ['stencil.config.{ts,js}'];
9
13
  const production = ['src/**/*.tsx'];
10
14
  const entry = ['**/*.spec.{ts,tsx}', '**/*.e2e.{ts,tsx}'];
11
15
  const isStencilSpecifier = (specifier) => specifier === '@stencil/core';
16
+ const registerCompilers = ({ hasDependency, registerCompiler }) => {
17
+ if (hasDependency('@stencil/sass'))
18
+ for (const ext of ['.scss', '.sass'])
19
+ registerCompiler({ extension: ext, compiler: scss.compiler });
20
+ };
12
21
  const registerVisitors = ({ ctx, registerVisitor }) => {
13
22
  registerVisitor(createCustomElementVisitor(ctx, isStencilSpecifier, { decoratorName: 'Component' }));
23
+ const componentNames = new Set();
24
+ registerVisitor({
25
+ Program() {
26
+ componentNames.clear();
27
+ },
28
+ ImportDeclaration(node) {
29
+ if (!isStencilSpecifier(node.source?.value))
30
+ return;
31
+ for (const spec of node.specifiers ?? []) {
32
+ if (spec.type === 'ImportSpecifier' &&
33
+ spec.imported.type === 'Identifier' &&
34
+ spec.imported.name === 'Component')
35
+ componentNames.add(spec.local.name);
36
+ }
37
+ },
38
+ ClassDeclaration(node) {
39
+ resolveStyleUrls(node);
40
+ },
41
+ });
42
+ function resolveStyleUrls(node) {
43
+ const dir = dirname(ctx.filePath);
44
+ for (const decorator of node.decorators ?? []) {
45
+ const expr = decorator.expression;
46
+ if (expr?.type !== 'CallExpression')
47
+ continue;
48
+ if (expr.callee?.type !== 'Identifier' || !componentNames.has(expr.callee.name))
49
+ continue;
50
+ const arg = expr.arguments?.[0];
51
+ if (arg?.type !== 'ObjectExpression')
52
+ continue;
53
+ for (const url of getPropertyValues(arg, 'styleUrl'))
54
+ ctx.addImport(join(dir, url), arg.start, IMPORT_FLAGS.NONE);
55
+ for (const url of getPropertyValues(arg, 'styleUrls'))
56
+ ctx.addImport(join(dir, url), arg.start, IMPORT_FLAGS.NONE);
57
+ }
58
+ }
14
59
  };
15
60
  const resolveFromAST = program => {
16
61
  const inputs = [];
@@ -22,6 +67,18 @@ const resolveFromAST = program => {
22
67
  for (const script of collectPropertyValues(program, 'globalScript')) {
23
68
  inputs.push(toProductionEntry(script));
24
69
  }
70
+ for (const style of collectPropertyValues(program, 'globalStyle')) {
71
+ inputs.push(toProductionEntry(style));
72
+ }
73
+ new Visitor({
74
+ ObjectExpression(node) {
75
+ const typeValues = getPropertyValues(node, 'type');
76
+ if (!typeValues.has('global-style'))
77
+ return;
78
+ for (const input of getPropertyValues(node, 'input'))
79
+ inputs.push(toProductionEntry(input));
80
+ },
81
+ }).visit(program);
25
82
  for (const tsconfig of collectPropertyValues(program, 'tsconfig')) {
26
83
  inputs.push(toConfig('typescript', tsconfig));
27
84
  }
@@ -41,6 +98,7 @@ const plugin = {
41
98
  entry,
42
99
  production,
43
100
  resolveFromAST,
101
+ registerCompilers,
44
102
  registerVisitors,
45
103
  };
46
104
  export default plugin;
@@ -1,2 +1,3 @@
1
- declare const compiler: import("../../compilers/types.ts").CompilerSync;
1
+ import type { CompilerSync } from '../../compilers/types.ts';
2
+ declare const compiler: CompilerSync;
2
3
  export default compiler;
@@ -1,3 +1,10 @@
1
1
  import { importsWithinScripts } from '../../compilers/compilers.js';
2
- const compiler = importsWithinScripts;
2
+ import { stylePreprocessorImports } from '../../compilers/style-preprocessors.js';
3
+ const compiler = (text, path) => {
4
+ const scripts = importsWithinScripts(text, path);
5
+ const styles = stylePreprocessorImports(text, path);
6
+ if (!scripts)
7
+ return styles;
8
+ return styles ? `${scripts};\n${styles}` : scripts;
9
+ };
3
10
  export default compiler;
@@ -1,8 +1,11 @@
1
1
  const directiveMatcher = /@(?:import|config|plugin)\s+['"]([^'"]+)['"][^;]*;/g;
2
2
  const compiler = (text) => {
3
+ if (!text.includes('@import') && !text.includes('@config') && !text.includes('@plugin'))
4
+ return '';
3
5
  const imports = [];
4
6
  let match;
5
7
  let index = 0;
8
+ directiveMatcher.lastIndex = 0;
6
9
  while ((match = directiveMatcher.exec(text)))
7
10
  if (match[1])
8
11
  imports.push(`import _$${index++} from '${match[1]}';`);
@@ -5,8 +5,12 @@ const enablers = ['tailwindcss', '@tailwindcss/vite', '@tailwindcss/postcss', '@
5
5
  const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
6
6
  const entry = ['tailwind.config.{js,cjs,mjs,ts,cts,mts}'];
7
7
  const registerCompilers = ({ registerCompiler, hasDependency }) => {
8
- if (enablers.some(enabler => hasDependency(enabler)))
8
+ for (const enabler of enablers) {
9
+ if (!hasDependency(enabler))
10
+ continue;
9
11
  registerCompiler({ extension: '.css', compiler });
12
+ break;
13
+ }
10
14
  };
11
15
  const plugin = {
12
16
  title,
@@ -1,2 +1,3 @@
1
- declare const compiler: import("../../compilers/types.ts").CompilerSync;
1
+ import type { CompilerSync } from '../../compilers/types.ts';
2
+ declare const compiler: CompilerSync;
2
3
  export default compiler;
@@ -1,3 +1,10 @@
1
1
  import { scriptBodies } from '../../compilers/compilers.js';
2
- const compiler = scriptBodies;
2
+ import { stylePreprocessorImports } from '../../compilers/style-preprocessors.js';
3
+ const compiler = (text, path) => {
4
+ const scripts = scriptBodies(text, path);
5
+ const styles = stylePreprocessorImports(text, path);
6
+ if (!scripts)
7
+ return styles;
8
+ return styles ? `${scripts};\n${styles}` : scripts;
9
+ };
3
10
  export default compiler;
@@ -5,11 +5,10 @@ const logIssueRecord = (issues, cwd) => {
5
5
  console.log(getIssueLine(issue, cwd));
6
6
  };
7
7
  export default ({ report, issues, isShowProgress, cwd }) => {
8
- const reportMultipleGroups = Object.values(report).filter(Boolean).length > 1;
9
8
  let totalIssues = 0;
10
9
  for (const [reportType, isReportType] of Object.entries(report)) {
11
10
  if (isReportType) {
12
- const title = reportMultipleGroups && getIssueTypeTitle(reportType);
11
+ const title = getIssueTypeTitle(reportType);
13
12
  const issuesForType = reportType === 'duplicates'
14
13
  ? flattenIssues(issues[reportType])
15
14
  : Object.values(issues[reportType])
@@ -2,11 +2,10 @@ import { printConfigurationHints, printTagHints } from './util/configuration-hin
2
2
  import { dim, flattenIssues, getColoredTitle, getIssueTypeTitle, getTableForType } from './util/util.js';
3
3
  export default (options) => {
4
4
  const { report, issues, isDisableConfigHints, isDisableTagHints, isShowProgress } = options;
5
- const reportMultipleGroups = Object.values(report).filter(Boolean).length > 1;
6
5
  let totalIssues = 0;
7
6
  for (const [reportType, isReportType] of Object.entries(report)) {
8
7
  if (isReportType) {
9
- const title = reportMultipleGroups && getIssueTypeTitle(reportType);
8
+ const title = getIssueTypeTitle(reportType);
10
9
  const issuesForType = flattenIssues(issues[reportType]);
11
10
  if (issuesForType.length > 0) {
12
11
  title && console.log(getColoredTitle(title, issuesForType.length));
@@ -76,6 +76,7 @@ interface BaseOptions {
76
76
  cwd: string;
77
77
  manifest: Manifest;
78
78
  rootManifest: Manifest | undefined;
79
+ getManifest: (dir: string) => Manifest | undefined;
79
80
  }
80
81
  type IsPluginEnabledOptions = {
81
82
  cwd: string;
package/dist/types.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export type { RawConfigurationOrFn as KnipConfig, RawConfiguration as KnipConfiguration, WorkspaceProjectConfig, } from './types/config.ts';
2
- export type { Preprocessor, Reporter, ReporterOptions } from './types/issues.ts';
2
+ export type { Issue, IssueRecords, IssueType, Preprocessor, Reporter, ReporterOptions } from './types/issues.ts';
3
3
  export type { JSONReport, JSONReportEntry, JSONReportItem, JSONReportNamedItem } from './reporters/json.ts';
@@ -11,8 +11,9 @@ export class SourceFileManager {
11
11
  this.asyncCompilers = compilers[1];
12
12
  }
13
13
  readFile(filePath) {
14
- if (this.sourceTextCache.has(filePath))
15
- return this.sourceTextCache.get(filePath);
14
+ const cachedSourceText = this.sourceTextCache.get(filePath);
15
+ if (cachedSourceText !== undefined)
16
+ return cachedSourceText;
16
17
  const ext = extname(filePath);
17
18
  const compiler = this.syncCompilers.get(ext);
18
19
  if (FOREIGN_FILE_EXTENSIONS.has(ext) && !compiler) {
@@ -19,4 +19,5 @@ export declare const getStringValue: (node: any) => string | undefined;
19
19
  export declare const getSafeScriptFromArgs: (executable: any, argsArray: any) => string | undefined;
20
20
  export declare const shouldCountRefs: (ignoreExportsUsedInFile: IgnoreExportsUsedInFile, type: SymbolType) => boolean | undefined;
21
21
  export declare function extractNamespaceMembers(decl: TSModuleDeclaration, options: GetImportsAndExportsOptions, lineStarts: number[], getJSDocTags: (start: number) => Set<string>, prefix?: string): ExportMember[];
22
+ export declare const collectAugmentationRefs: (node: TSModuleDeclaration) => string[];
22
23
  export declare function extractEnumMembers(decl: TSEnumDeclaration, options: GetImportsAndExportsOptions, lineStarts: number[], getJSDocTags: (start: number) => Set<string>): ExportMember[];
@@ -1,4 +1,4 @@
1
- import { parseSync, rawTransferSupported, } from 'oxc-parser';
1
+ import { parseSync, rawTransferSupported, visitorKeys, } from 'oxc-parser';
2
2
  import { DEFAULT_EXTENSIONS, FIX_FLAGS, SYMBOL_TYPE } from '../constants.js';
3
3
  import { extname } from '../util/path.js';
4
4
  import { timerify } from '../util/Performance.js';
@@ -131,6 +131,56 @@ export function extractNamespaceMembers(decl, options, lineStarts, getJSDocTags,
131
131
  }
132
132
  return members;
133
133
  }
134
+ export const collectAugmentationRefs = (node) => {
135
+ if (!node.body || node.body.type !== 'TSModuleBlock')
136
+ return [];
137
+ const body = node.body.body;
138
+ const declared = new Set();
139
+ for (const stmt of body) {
140
+ const decl = stmt.type === 'ExportNamedDeclaration' && stmt.declaration ? stmt.declaration : stmt;
141
+ if ('id' in decl && decl.id?.type === 'Identifier')
142
+ declared.add(decl.id.name);
143
+ else if (decl.type === 'VariableDeclaration')
144
+ for (const d of decl.declarations)
145
+ if (d.id.type === 'Identifier')
146
+ declared.add(d.id.name);
147
+ }
148
+ const refs = [];
149
+ const seen = new Set();
150
+ const add = (ref) => {
151
+ if (ref?.type === 'Identifier' && !declared.has(ref.name) && !seen.has(ref.name)) {
152
+ seen.add(ref.name);
153
+ refs.push(ref.name);
154
+ }
155
+ };
156
+ const visit = (n) => {
157
+ const type = n?.type;
158
+ if (!type)
159
+ return;
160
+ if (type === 'TSTypeReference')
161
+ add(n.typeName);
162
+ else if (type === 'TSInterfaceHeritage')
163
+ add(n.expression);
164
+ const keys = visitorKeys[type];
165
+ if (!keys)
166
+ return;
167
+ for (const key of keys) {
168
+ const val = n[key];
169
+ if (!val)
170
+ continue;
171
+ if (Array.isArray(val)) {
172
+ for (const item of val)
173
+ if (item)
174
+ visit(item);
175
+ }
176
+ else
177
+ visit(val);
178
+ }
179
+ };
180
+ for (const stmt of body)
181
+ visit(stmt);
182
+ return refs;
183
+ };
134
184
  export function extractEnumMembers(decl, options, lineStarts, getJSDocTags) {
135
185
  if (!decl.body?.members)
136
186
  return [];
@@ -1,6 +1,7 @@
1
1
  import { IMPORT_FLAGS } from '../constants.js';
2
2
  const jsDocImportRe = /import\(\s*['"]([^'"]+)['"]\s*\)(?:\.(\w+))?/g;
3
3
  const jsDocImportTagRe = /@import\s+(?:\{[^}]*\}|\*\s+as\s+\w+)\s+from\s+['"]([^'"]+)['"]/g;
4
+ const jsDocTypeTagRe = /@(?:type|typedef|callback|param|arg|argument|property|prop|returns?|yields?|throws?|exception|this|extends|augments|implements|enum|template|satisfies|const|constant|member|var|namespace|module)\b/;
4
5
  const jsxImportSourceRe = /@jsxImportSource\s+(\S+)/;
5
6
  const referenceRe = /\s*<reference\s+(types|path)\s*=\s*"([^"]+)"[^/]*\/>/;
6
7
  const envPragmaRe = /@(vitest|jest)-environment\s+([@\w./-]+)/g;
@@ -18,6 +19,26 @@ const resolveEnvironmentPragma = (tool, value) => {
18
19
  return '@edge-runtime/vm';
19
20
  return value;
20
21
  };
22
+ const isInJsDocTypeExpression = (text, index) => {
23
+ let depth = 0;
24
+ for (let pos = index - 1; pos >= 0; pos--) {
25
+ const char = text.charCodeAt(pos);
26
+ if (char === 125) {
27
+ depth++;
28
+ }
29
+ else if (char === 123) {
30
+ if (depth > 0) {
31
+ depth--;
32
+ }
33
+ else {
34
+ const line = text.slice(text.lastIndexOf('\n', pos - 1) + 1, pos);
35
+ if (jsDocTypeTagRe.test(line))
36
+ return true;
37
+ }
38
+ }
39
+ }
40
+ return false;
41
+ };
21
42
  export const extractImportsFromComments = (comments, firstStmtStart, addImport) => {
22
43
  for (const comment of comments) {
23
44
  const text = comment.value;
@@ -25,9 +46,7 @@ export const extractImportsFromComments = (comments, firstStmtStart, addImport)
25
46
  if (comment.type === 'Block') {
26
47
  jsDocImportRe.lastIndex = 0;
27
48
  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)
49
+ if (!isInJsDocTypeExpression(text, results.index))
31
50
  continue;
32
51
  const specifier = results[1];
33
52
  const member = results[2];
@@ -3,7 +3,7 @@ import { FIX_FLAGS, IMPORT_FLAGS, OPAQUE, SYMBOL_TYPE } from '../../constants.js
3
3
  import { addValue } from '../../util/module-graph.js';
4
4
  import { isInNodeModules } from '../../util/path.js';
5
5
  import { timerify } from '../../util/Performance.js';
6
- import { getLineAndCol, getStringValue, isStringLiteral } from '../ast-nodes.js';
6
+ import { collectAugmentationRefs, getLineAndCol, getStringValue, isStringLiteral, } from '../ast-nodes.js';
7
7
  import { EMPTY_TAGS } from './jsdoc.js';
8
8
  import { handleCallExpression, handleNewExpression, trackCustomElementRegistry } from './calls.js';
9
9
  import { handleExportAssignment, handleExportDefault, handleExportNamed, handleExpressionStatement, } from './exports.js';
@@ -211,6 +211,12 @@ const coreVisitorObject = {
211
211
  },
212
212
  TSModuleDeclaration(node) {
213
213
  state.nsRanges.push([node.start, node.end]);
214
+ if (node.kind !== 'global' && isStringLiteral(node.id)) {
215
+ const specifier = getStringValue(node.id);
216
+ if (specifier.startsWith('.'))
217
+ for (const name of collectAugmentationRefs(node))
218
+ state.addImport(specifier, name, undefined, undefined, node.id.start, IMPORT_FLAGS.TYPE_ONLY);
219
+ }
214
220
  },
215
221
  ClassDeclaration(node) {
216
222
  state.classNameStack.push(node.id?.name ?? '');
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "6.23.0";
1
+ export declare const version = "6.24.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '6.23.0';
1
+ export const version = '6.24.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "6.23.0",
3
+ "version": "6.24.0",
4
4
  "description": "Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects",
5
5
  "keywords": [
6
6
  "analysis",