knip 6.14.1 → 6.15.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 (84) hide show
  1. package/README.md +1 -1
  2. package/dist/CacheConsultant.d.ts +1 -0
  3. package/dist/CacheConsultant.js +2 -0
  4. package/dist/ConfigurationChief.d.ts +12 -0
  5. package/dist/ConfigurationChief.js +7 -10
  6. package/dist/DependencyDeputy.d.ts +0 -4
  7. package/dist/DependencyDeputy.js +6 -18
  8. package/dist/IssueCollector.d.ts +1 -0
  9. package/dist/IssueCollector.js +10 -20
  10. package/dist/IssueFixer.js +10 -15
  11. package/dist/ProjectPrincipal.d.ts +0 -2
  12. package/dist/ProjectPrincipal.js +1 -9
  13. package/dist/WorkspaceWorker.js +6 -1
  14. package/dist/binaries/resolvers/bun.js +9 -8
  15. package/dist/cli.js +3 -1
  16. package/dist/compilers/compilers.d.ts +3 -0
  17. package/dist/compilers/compilers.js +9 -13
  18. package/dist/compilers/index.d.ts +21 -0
  19. package/dist/compilers/scss.js +9 -3
  20. package/dist/graph/analyze.js +7 -1
  21. package/dist/graph/build.js +0 -2
  22. package/dist/manifest/index.js +11 -14
  23. package/dist/plugins/astro/index.js +1 -0
  24. package/dist/plugins/graphql-codegen/index.js +4 -0
  25. package/dist/plugins/index.d.ts +2 -0
  26. package/dist/plugins/index.js +4 -0
  27. package/dist/plugins/nano-spawn/index.d.ts +3 -0
  28. package/dist/plugins/nano-spawn/index.js +15 -0
  29. package/dist/plugins/nano-spawn/visitors/nano-spawn.d.ts +2 -0
  30. package/dist/plugins/nano-spawn/visitors/nano-spawn.js +25 -0
  31. package/dist/plugins/orval/index.d.ts +3 -0
  32. package/dist/plugins/orval/index.js +16 -0
  33. package/dist/plugins/orval/resolveFromAST.d.ts +2 -0
  34. package/dist/plugins/orval/resolveFromAST.js +11 -0
  35. package/dist/plugins/orval/types.d.ts +9 -0
  36. package/dist/plugins/orval/types.js +1 -0
  37. package/dist/plugins/serverless-framework/index.js +11 -6
  38. package/dist/plugins/serverless-framework/types.d.ts +7 -0
  39. package/dist/plugins/typescript/index.js +1 -1
  40. package/dist/plugins/vite/helpers.d.ts +1 -1
  41. package/dist/plugins/vite/helpers.js +43 -36
  42. package/dist/plugins/vite/index.js +2 -6
  43. package/dist/plugins/webdriver-io/index.js +3 -3
  44. package/dist/plugins/webdriver-io/types.d.ts +5 -1
  45. package/dist/reporters/github-actions.d.ts +1 -1
  46. package/dist/reporters/github-actions.js +19 -1
  47. package/dist/reporters/index.d.ts +1 -1
  48. package/dist/reporters/symbols.js +2 -1
  49. package/dist/reporters/trace.d.ts +3 -1
  50. package/dist/reporters/trace.js +48 -16
  51. package/dist/reporters/util/configuration-hints.d.ts +1 -1
  52. package/dist/reporters/util/configuration-hints.js +3 -2
  53. package/dist/schema/configuration.d.ts +32 -1
  54. package/dist/schema/configuration.js +1 -0
  55. package/dist/schema/plugins.d.ts +10 -0
  56. package/dist/schema/plugins.js +2 -0
  57. package/dist/types/PluginNames.d.ts +2 -2
  58. package/dist/types/PluginNames.js +2 -0
  59. package/dist/types/issues.d.ts +1 -0
  60. package/dist/types/project.d.ts +0 -1
  61. package/dist/typescript/resolve-module-names.d.ts +0 -1
  62. package/dist/typescript/resolve-module-names.js +0 -17
  63. package/dist/typescript/visitors/walk.js +7 -0
  64. package/dist/util/Performance.d.ts +13 -28
  65. package/dist/util/Performance.js +41 -72
  66. package/dist/util/cli-arguments.d.ts +3 -1
  67. package/dist/util/cli-arguments.js +4 -0
  68. package/dist/util/create-input-handler.js +3 -1
  69. package/dist/util/create-options.d.ts +22 -0
  70. package/dist/util/create-options.js +2 -1
  71. package/dist/util/disk-cache.d.ts +1 -0
  72. package/dist/util/disk-cache.js +8 -0
  73. package/dist/util/gitignore-cache.js +5 -11
  74. package/dist/util/glob-cache.js +2 -8
  75. package/dist/util/glob-core.js +4 -7
  76. package/dist/util/jiti.js +1 -0
  77. package/dist/util/load-tsconfig.js +0 -3
  78. package/dist/util/trace.js +1 -1
  79. package/dist/version.d.ts +1 -1
  80. package/dist/version.js +1 -1
  81. package/package.json +3 -4
  82. package/schema.json +12 -0
  83. package/dist/util/math.d.ts +0 -6
  84. package/dist/util/math.js +0 -11
@@ -59,6 +59,7 @@ export declare const Plugins: {
59
59
  mocha: import("../types/config.ts").Plugin;
60
60
  moonrepo: import("../types/config.ts").Plugin;
61
61
  msw: import("../types/config.ts").Plugin;
62
+ 'nano-spawn': import("../types/config.ts").Plugin;
62
63
  'nano-staged': import("../types/config.ts").Plugin;
63
64
  nest: import("../types/config.ts").Plugin;
64
65
  netlify: import("../types/config.ts").Plugin;
@@ -75,6 +76,7 @@ export declare const Plugins: {
75
76
  nyc: import("../types/config.ts").Plugin;
76
77
  oclif: import("../types/config.ts").Plugin;
77
78
  'openapi-ts': import("../types/config.ts").Plugin;
79
+ orval: import("../types/config.ts").Plugin;
78
80
  oxfmt: import("../types/config.ts").Plugin;
79
81
  oxlint: import("../types/config.ts").Plugin;
80
82
  'panda-css': import("../types/config.ts").Plugin;
@@ -53,6 +53,7 @@ import { default as metro } from './metro/index.js';
53
53
  import { default as mocha } from './mocha/index.js';
54
54
  import { default as moonrepo } from './moonrepo/index.js';
55
55
  import { default as msw } from './msw/index.js';
56
+ import { default as nanoSpawn } from './nano-spawn/index.js';
56
57
  import { default as nanoStaged } from './nano-staged/index.js';
57
58
  import { default as nest } from './nest/index.js';
58
59
  import { default as netlify } from './netlify/index.js';
@@ -69,6 +70,7 @@ import { default as nx } from './nx/index.js';
69
70
  import { default as nyc } from './nyc/index.js';
70
71
  import { default as oclif } from './oclif/index.js';
71
72
  import { default as openapiTs } from './openapi-ts/index.js';
73
+ import { default as orval } from './orval/index.js';
72
74
  import { default as oxfmt } from './oxfmt/index.js';
73
75
  import { default as oxlint } from './oxlint/index.js';
74
76
  import { default as pandaCss } from './panda-css/index.js';
@@ -204,6 +206,7 @@ export const Plugins = {
204
206
  mocha,
205
207
  moonrepo,
206
208
  msw,
209
+ 'nano-spawn': nanoSpawn,
207
210
  'nano-staged': nanoStaged,
208
211
  nest,
209
212
  netlify,
@@ -220,6 +223,7 @@ export const Plugins = {
220
223
  nyc,
221
224
  oclif,
222
225
  'openapi-ts': openapiTs,
226
+ orval,
223
227
  oxfmt,
224
228
  oxlint,
225
229
  'panda-css': pandaCss,
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from '../../types/config.ts';
2
+ declare const plugin: Plugin;
3
+ export default plugin;
@@ -0,0 +1,15 @@
1
+ import { hasDependency } from '../../util/plugin.js';
2
+ import { createNanoSpawnVisitor } from './visitors/nano-spawn.js';
3
+ const title = 'nano-spawn';
4
+ const enablers = ['nano-spawn'];
5
+ const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
6
+ const registerVisitors = ({ ctx, registerVisitor }) => {
7
+ registerVisitor(createNanoSpawnVisitor(ctx));
8
+ };
9
+ const plugin = {
10
+ title,
11
+ enablers,
12
+ isEnabled,
13
+ registerVisitors,
14
+ };
15
+ export default plugin;
@@ -0,0 +1,2 @@
1
+ import type { PluginVisitorContext, PluginVisitorObject } from '../../../types/config.ts';
2
+ export declare function createNanoSpawnVisitor(ctx: PluginVisitorContext): PluginVisitorObject;
@@ -0,0 +1,25 @@
1
+ import { getStringValue, isStringLiteral } from '../../../typescript/ast-nodes.js';
2
+ export function createNanoSpawnVisitor(ctx) {
3
+ return {
4
+ CallExpression(node) {
5
+ if (node.callee.type !== 'Identifier' || node.callee.name !== 'spawn')
6
+ return;
7
+ const executable = node.arguments[0];
8
+ if (!executable || !isStringLiteral(executable))
9
+ return;
10
+ const executableStr = getStringValue(executable);
11
+ const args = node.arguments[1];
12
+ if (args?.type === 'ArrayExpression') {
13
+ const argStrings = [];
14
+ for (const a of args.elements) {
15
+ if (a && isStringLiteral(a))
16
+ argStrings.push(getStringValue(a));
17
+ }
18
+ ctx.addScript([executableStr, ...argStrings].join(' '));
19
+ }
20
+ else {
21
+ ctx.addScript(executableStr);
22
+ }
23
+ },
24
+ };
25
+ }
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from '../../types/config.ts';
2
+ declare const plugin: Plugin;
3
+ export default plugin;
@@ -0,0 +1,16 @@
1
+ import { toEntry } from '../../util/input.js';
2
+ import { hasDependency } from '../../util/plugin.js';
3
+ import { getInputsFromAST } from './resolveFromAST.js';
4
+ const title = 'orval';
5
+ const enablers = ['orval'];
6
+ const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
7
+ const config = ['orval.config.{js,mjs,ts,mts}'];
8
+ const resolveFromAST = program => [...getInputsFromAST(program)].map(id => toEntry(id));
9
+ const plugin = {
10
+ title,
11
+ enablers,
12
+ isEnabled,
13
+ config,
14
+ resolveFromAST,
15
+ };
16
+ export default plugin;
@@ -0,0 +1,2 @@
1
+ import type { Program } from 'oxc-parser';
2
+ export declare const getInputsFromAST: (program: Program) => Set<string>;
@@ -0,0 +1,11 @@
1
+ import { collectPropertyValues } from '../../typescript/ast-helpers.js';
2
+ const PATH_KEY = 'path';
3
+ const TRANSFORMER_KEY = 'transformer';
4
+ export const getInputsFromAST = (program) => {
5
+ const values = new Set();
6
+ for (const v of collectPropertyValues(program, PATH_KEY))
7
+ values.add(v);
8
+ for (const v of collectPropertyValues(program, TRANSFORMER_KEY))
9
+ values.add(v);
10
+ return values;
11
+ };
@@ -0,0 +1,9 @@
1
+ export type OverrideInput = {
2
+ transformer?: InputTransformer;
3
+ };
4
+ export type MutatorObject = {
5
+ path: string;
6
+ name?: string;
7
+ };
8
+ type InputTransformer = string | ((...args: unknown[]) => unknown);
9
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,17 +1,22 @@
1
- import { toProductionEntry } from '../../util/input.js';
1
+ import { toDependency, toProductionEntry } from '../../util/input.js';
2
+ import { isInternal, join } from '../../util/path.js';
2
3
  import { hasDependency } from '../../util/plugin.js';
3
4
  const title = 'Serverless Framework';
4
5
  const enablers = ['serverless'];
5
6
  const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
6
- const config = ['serverless.{yml,yaml}'];
7
+ const config = ['serverless.{js,cjs,mjs,ts,cts,mts,yml,yaml}'];
7
8
  const handlerToEntry = (handler) => {
8
9
  const dot = handler.lastIndexOf('.');
9
10
  return toProductionEntry(`${handler.slice(0, dot)}.{js,ts}`);
10
11
  };
11
- const resolveConfig = async (config) => {
12
- if (!config.functions)
13
- return [];
14
- return Object.values(config.functions).flatMap(fn => (fn.handler ? [handlerToEntry(fn.handler)] : []));
12
+ const pluginToInput = (plugin, dir) => isInternal(plugin) ? toProductionEntry(join(dir, plugin)) : toDependency(plugin);
13
+ const resolveConfig = async (config, options) => {
14
+ const functions = config.functions
15
+ ? Object.values(config.functions).flatMap(fn => (fn.handler ? [handlerToEntry(fn.handler)] : []))
16
+ : [];
17
+ const plugins = config.plugins?.filter((plugin) => typeof plugin === 'string') ?? [];
18
+ const esbuild = config.custom?.esbuild || config.build?.esbuild ? [toDependency('esbuild', { optional: true })] : [];
19
+ return [...functions, ...plugins.map(plugin => pluginToInput(plugin, options.configFileDir)), ...esbuild];
15
20
  };
16
21
  const plugin = {
17
22
  title,
@@ -1,5 +1,12 @@
1
1
  export type PluginConfig = {
2
+ build?: {
3
+ esbuild?: unknown;
4
+ };
5
+ custom?: {
6
+ esbuild?: unknown;
7
+ };
2
8
  functions?: Record<string, ServerlessFunction>;
9
+ plugins?: unknown[];
3
10
  };
4
11
  type ServerlessFunction = {
5
12
  handler?: string;
@@ -32,7 +32,7 @@ const resolveConfig = async (localConfig, options) => {
32
32
  return compact([
33
33
  ...extend,
34
34
  ...references,
35
- ...types.map(id => toDeferResolve(id, { isTypeOnly: true })),
35
+ ...types.map(id => toDeferResolve(id, { isTypeOnly: true, dir: options.cwd })),
36
36
  ...[...plugins, ...importHelpers].map(id => toDeferResolve(id)),
37
37
  ...jsx,
38
38
  ...aliases,
@@ -1,4 +1,4 @@
1
1
  import type { Program } from 'oxc-parser';
2
2
  import { type Input } from '../../util/input.ts';
3
- export declare const getReactBabelInputs: (program: Program) => string[];
3
+ export declare const getBabelInputs: (program: Program) => Input[];
4
4
  export declare const getIndexHtmlEntries: (rootDir: string) => Promise<Input[]>;
@@ -1,56 +1,63 @@
1
1
  import { Visitor } from 'oxc-parser';
2
- import { findProperty, getDefaultImportName, getImportMap, getStringValues, resolveObjectArg, } from '../../typescript/ast-helpers.js';
2
+ import { blockCommentMatcher, lineCommentMatcher, scriptExtractor } from '../../compilers/compilers.js';
3
+ import { findProperty, getImportMap, getStringValues } from '../../typescript/ast-helpers.js';
3
4
  import { isFile, loadFile } from '../../util/fs.js';
4
5
  import { toProductionEntry } from '../../util/input.js';
5
6
  import { join } from '../../util/path.js';
6
- export const getReactBabelInputs = (program) => {
7
- const inputs = [];
8
- const importMap = getImportMap(program);
9
- const reactPluginNames = new Set();
10
- for (const [importName, importPath] of importMap) {
11
- if (importPath.includes('@vitejs/plugin-react'))
12
- reactPluginNames.add(importName);
13
- }
14
- if (reactPluginNames.size === 0) {
15
- const defaultImportName = getDefaultImportName(importMap, '@vitejs/plugin-react');
16
- if (defaultImportName)
17
- reactPluginNames.add(defaultImportName);
18
- else
19
- reactPluginNames.add('react');
7
+ import { getDependenciesFromConfig } from '../babel/index.js';
8
+ const babelPluginSources = ['@rolldown/plugin-babel', '@vitejs/plugin-react', 'vite-plugin-babel'];
9
+ const isBabelWrappingPlugin = (path) => babelPluginSources.some(source => path === source || path.startsWith(`${source}/`));
10
+ export const getBabelInputs = (program) => {
11
+ const pluginNames = new Set();
12
+ for (const [name, path] of getImportMap(program)) {
13
+ if (isBabelWrappingPlugin(path))
14
+ pluginNames.add(name);
20
15
  }
16
+ if (pluginNames.size === 0)
17
+ return [];
18
+ const inputs = [];
21
19
  const visitor = new Visitor({
22
20
  CallExpression(node) {
23
- if (node.callee?.type !== 'Identifier' || node.callee.name !== 'defineConfig')
24
- return;
25
- const config = resolveObjectArg(node.arguments?.[0]);
26
- const plugins = findProperty(config, 'plugins');
27
- if (plugins?.type !== 'ArrayExpression')
21
+ if (node.callee?.type !== 'Identifier' || !pluginNames.has(node.callee.name))
28
22
  return;
29
- for (const el of plugins.elements ?? []) {
30
- if (el?.type !== 'CallExpression' || el.callee?.type !== 'Identifier')
31
- continue;
32
- if (!reactPluginNames.has(el.callee.name))
33
- continue;
34
- const babel = findProperty(el.arguments?.[0], 'babel');
35
- for (const key of ['plugins', 'presets']) {
36
- for (const v of getStringValues(findProperty(babel, key)))
37
- inputs.push(v);
38
- }
23
+ const options = node.arguments?.[0];
24
+ const plugins = [];
25
+ const presets = [];
26
+ for (const config of [options, findProperty(options, 'babel'), findProperty(options, 'babelConfig')]) {
27
+ plugins.push(...getStringValues(findProperty(config, 'plugins')));
28
+ presets.push(...getStringValues(findProperty(config, 'presets')));
39
29
  }
30
+ inputs.push(...getDependenciesFromConfig({ plugins, presets }));
40
31
  },
41
32
  });
42
33
  visitor.visit(program);
43
34
  return inputs;
44
35
  };
45
- const moduleScriptPattern = /<script\b(?=[^>]*\btype\s*=\s*["']?module["']?)(?=[^>]*\bsrc\s*=\s*["']?([^"' >]+)["']?)[^>]*>/gi;
36
+ const moduleTypePattern = /\btype\s*=\s*["']?module["']?/i;
37
+ const srcAttrPattern = /\bsrc\s*=\s*["']([^"']+)["']/i;
38
+ const importSpecPattern = /\bimport\b(?:\s*\(\s*|(?:[\w$*,{}\s]*\bfrom\b)?\s*)(['"])([^'"]+)\1/g;
39
+ const isFilePath = (specifier) => specifier.startsWith('/') || specifier.startsWith('./') || specifier.startsWith('../');
46
40
  const normalizeModuleScriptSrc = (value) => value.trim().replace(/^\//, '');
47
41
  const getModuleScriptSources = (html) => {
48
- const matches = html.matchAll(moduleScriptPattern);
49
42
  const sources = [];
50
- for (const match of matches) {
51
- const src = normalizeModuleScriptSrc(match[1]);
52
- if (src)
53
- sources.push(src);
43
+ for (const [, attrs, body] of html.matchAll(scriptExtractor)) {
44
+ if (!moduleTypePattern.test(attrs))
45
+ continue;
46
+ const srcMatch = attrs.match(srcAttrPattern);
47
+ if (srcMatch) {
48
+ const src = normalizeModuleScriptSrc(srcMatch[1]);
49
+ if (src)
50
+ sources.push(src);
51
+ continue;
52
+ }
53
+ if (body) {
54
+ const code = body.replace(blockCommentMatcher, '').replace(lineCommentMatcher, '');
55
+ for (const importMatch of code.matchAll(importSpecPattern)) {
56
+ const specifier = importMatch[2];
57
+ if (isFilePath(specifier))
58
+ sources.push(normalizeModuleScriptSrc(specifier));
59
+ }
60
+ }
54
61
  }
55
62
  return sources;
56
63
  };
@@ -1,16 +1,12 @@
1
- import { toDependency } from '../../util/input.js';
2
1
  import { hasDependency } from '../../util/plugin.js';
3
2
  import { resolveConfig } from '../vitest/index.js';
4
- import { getIndexHtmlEntries, getReactBabelInputs } from './helpers.js';
3
+ import { getBabelInputs, getIndexHtmlEntries } from './helpers.js';
5
4
  import { createImportMetaGlobVisitor } from './visitors/importMetaGlob.js';
6
5
  const title = 'Vite';
7
6
  const enablers = ['vite', 'vitest'];
8
7
  const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
9
8
  export const config = ['vite.config.{js,mjs,ts,cjs,mts,cts}'];
10
- const resolveFromAST = program => {
11
- const inputs = getReactBabelInputs(program);
12
- return inputs.map(id => toDependency(id));
13
- };
9
+ const resolveFromAST = program => getBabelInputs(program);
14
10
  const registerVisitors = ({ ctx, registerVisitor }) => {
15
11
  registerVisitor(createImportMetaGlobVisitor(ctx));
16
12
  };
@@ -8,11 +8,11 @@ const resolveConfig = async (config) => {
8
8
  const cfg = config?.config;
9
9
  if (!cfg)
10
10
  return [];
11
- const frameworks = cfg?.framework ? [`@wdio/${cfg.framework}-framework`] : [];
12
- const runners = cfg?.runner && cfg.runner !== 'local'
11
+ const frameworks = cfg.framework ? [`@wdio/${cfg.framework}-framework`] : [];
12
+ const runners = cfg.runner && cfg.runner !== 'local'
13
13
  ? [`@wdio/${Array.isArray(cfg.runner) ? cfg.runner[0] : cfg.runner}-runner`]
14
14
  : [];
15
- const reporters = cfg?.reporters
15
+ const reporters = cfg.reporters
16
16
  ? cfg.reporters
17
17
  .flatMap(reporter => {
18
18
  if (typeof reporter === 'string')
@@ -1,3 +1,7 @@
1
1
  export type WebdriverIOConfig = {
2
- config: WebdriverIO.Config;
2
+ config: {
3
+ framework?: string;
4
+ runner?: string;
5
+ reporters?: string[];
6
+ };
3
7
  };
@@ -1,3 +1,3 @@
1
1
  import type { ReporterOptions } from '../types/issues.ts';
2
- declare const _default: ({ report, issues, cwd, configurationHints, isDisableConfigHints, isTreatConfigHintsAsErrors, configFilePath, }: ReporterOptions) => void;
2
+ declare const _default: ({ report, issues, cwd, configurationHints, tagHints, isDisableConfigHints, isDisableTagHints, isTreatConfigHintsAsErrors, isTreatTagHintsAsErrors, configFilePath, }: ReporterOptions) => void;
3
3
  export default _default;
@@ -24,7 +24,7 @@ const createGitHubActionsLogger = () => {
24
24
  notice: (message, options) => formatAnnotation('notice', message, options),
25
25
  };
26
26
  };
27
- export default ({ report, issues, cwd, configurationHints, isDisableConfigHints, isTreatConfigHintsAsErrors, configFilePath, }) => {
27
+ export default ({ report, issues, cwd, configurationHints, tagHints, isDisableConfigHints, isDisableTagHints, isTreatConfigHintsAsErrors, isTreatTagHintsAsErrors, configFilePath, }) => {
28
28
  const core = createGitHubActionsLogger();
29
29
  const reportMultipleGroups = Object.values(report).filter(Boolean).length > 1;
30
30
  for (const [reportType, isReportType] of Object.entries(report)) {
@@ -91,4 +91,22 @@ export default ({ report, issues, cwd, configurationHints, isDisableConfigHints,
91
91
  }
92
92
  }
93
93
  }
94
+ if (!isDisableTagHints && tagHints.size > 0) {
95
+ const TAG_HINTS_TITLE = 'Tag hints';
96
+ core.info(`${TAG_HINTS_TITLE} (${tagHints.size})`);
97
+ const sortedHints = [...tagHints].sort((a, b) => a.filePath.localeCompare(b.filePath));
98
+ for (const hint of sortedHints) {
99
+ const file = relative(cwd, hint.filePath);
100
+ const hintMessage = `Unused tag in ${file}: ${hint.identifier} → ${hint.tagName}`;
101
+ const log = isTreatTagHintsAsErrors ? core.error : core.notice;
102
+ log(hintMessage, {
103
+ file,
104
+ startLine: 1,
105
+ endLine: 1,
106
+ startColumn: 1,
107
+ endColumn: 1,
108
+ title: TAG_HINTS_TITLE,
109
+ });
110
+ }
111
+ }
94
112
  };
@@ -6,6 +6,6 @@ declare const _default: {
6
6
  codeclimate: ({ report, issues, cwd }: import("../types.ts").ReporterOptions) => Promise<void>;
7
7
  json: ({ report, issues, options, cwd }: import("../types.ts").ReporterOptions) => Promise<void>;
8
8
  markdown: ({ report, issues, cwd }: import("../types.ts").ReporterOptions) => void;
9
- 'github-actions': ({ report, issues, cwd, configurationHints, isDisableConfigHints, isTreatConfigHintsAsErrors, configFilePath, }: import("../types.ts").ReporterOptions) => void;
9
+ 'github-actions': ({ report, issues, cwd, configurationHints, tagHints, isDisableConfigHints, isDisableTagHints, isTreatConfigHintsAsErrors, isTreatTagHintsAsErrors, configFilePath, }: import("../types.ts").ReporterOptions) => void;
10
10
  };
11
11
  export default _default;
@@ -29,7 +29,8 @@ export default (options) => {
29
29
  }
30
30
  if (totalIssues === 0 &&
31
31
  isShowProgress &&
32
- (!options.isTreatConfigHintsAsErrors || options.configurationHints.length === 0)) {
32
+ (!options.isTreatConfigHintsAsErrors || options.configurationHints.length === 0) &&
33
+ (!options.isTreatTagHintsAsErrors || options.tagHints.size === 0)) {
33
34
  console.log('✂️ Excellent, Knip found no issues.');
34
35
  }
35
36
  };
@@ -1,4 +1,5 @@
1
1
  import type { GraphExplorer } from '../graph-explorer/explorer.ts';
2
+ import type { Issues } from '../types/issues.ts';
2
3
  import type { ModuleGraph } from '../types/module-graph.ts';
3
4
  import type { MainOptions } from '../util/create-options.ts';
4
5
  import type { WorkspaceFilePathFilter } from '../util/workspace-file-filter.ts';
@@ -7,6 +8,7 @@ interface TraceReporterOptions {
7
8
  explorer: GraphExplorer;
8
9
  options: MainOptions;
9
10
  workspaceFilePathFilter: WorkspaceFilePathFilter;
11
+ issues: Issues;
10
12
  }
11
- declare const _default: ({ graph, explorer, options, workspaceFilePathFilter }: TraceReporterOptions) => void;
13
+ declare const _default: ({ graph, explorer, options, workspaceFilePathFilter, issues }: TraceReporterOptions) => void;
12
14
  export default _default;
@@ -3,7 +3,7 @@ import { toRelative } from '../util/path.js';
3
3
  import { toRegexOrString } from '../util/regex.js';
4
4
  import { Table } from '../util/table.js';
5
5
  import { formatTrace } from '../util/trace.js';
6
- export default ({ graph, explorer, options, workspaceFilePathFilter }) => {
6
+ export default ({ graph, explorer, options, workspaceFilePathFilter, issues }) => {
7
7
  if (options.traceDependency) {
8
8
  const pattern = toRegexOrString(options.traceDependency);
9
9
  const toRel = (path) => toRelative(path, options.cwd);
@@ -23,8 +23,12 @@ export default ({ graph, explorer, options, workspaceFilePathFilter }) => {
23
23
  table.cell('package', st.cyanBright(packageName));
24
24
  }
25
25
  }
26
- for (const line of table.toRows())
27
- console.log(line);
26
+ const rows = table.toRows();
27
+ if (rows.length === 0)
28
+ console.log(`No imports found matching ${st.cyanBright(options.traceDependency)}`);
29
+ else
30
+ for (const line of rows)
31
+ console.log(line);
28
32
  }
29
33
  else {
30
34
  let nodes = explorer.buildExportsTree({ filePath: options.traceFile, identifier: options.traceExport });
@@ -32,27 +36,55 @@ export default ({ graph, explorer, options, workspaceFilePathFilter }) => {
32
36
  const nsName = options.traceExport.substring(0, options.traceExport.indexOf('.'));
33
37
  nodes = explorer.buildExportsTree({ filePath: options.traceFile, identifier: nsName });
34
38
  }
39
+ if (nodes.length === 0 && options.traceExport) {
40
+ const query = options.traceExport;
41
+ const member = query.slice(query.lastIndexOf('.') + 1);
42
+ const seen = new Set();
43
+ for (const [filePath, file] of graph) {
44
+ if (options.traceFile && filePath !== options.traceFile)
45
+ continue;
46
+ for (const [exportId, exp] of file.exports) {
47
+ const key = `${filePath}:${exportId}`;
48
+ if (seen.has(key))
49
+ continue;
50
+ if (exp.members.some(m => m.identifier === query || m.identifier === member || m.identifier.endsWith(`.${member}`))) {
51
+ seen.add(key);
52
+ nodes.push(...explorer.buildExportsTree({ filePath, identifier: exportId }));
53
+ }
54
+ }
55
+ }
56
+ }
35
57
  nodes.sort((a, b) => a.filePath.localeCompare(b.filePath) || a.identifier.localeCompare(b.identifier));
36
58
  const toRel = (path) => toRelative(path, options.cwd);
37
- const isReferenced = (node) => {
38
- if (explorer.isReferenced(node.filePath, node.identifier, { traverseEntries: false })[0])
39
- return true;
40
- if (explorer.hasStrictlyNsReferences(node.filePath, node.identifier)[0])
41
- return true;
42
- return !!graph.get(node.filePath)?.exports.get(node.identifier)?.hasRefsInFile;
43
- };
59
+ if (nodes.length === 0) {
60
+ if (options.traceFile && !graph.has(options.traceFile)) {
61
+ console.log(`File not found in module graph: ${toRel(options.traceFile)}`);
62
+ }
63
+ else {
64
+ const what = options.traceExport ? `export ${st.cyanBright(options.traceExport)}` : 'exports';
65
+ const where = options.traceFile ? ` in ${toRel(options.traceFile)}` : '';
66
+ console.log(`No ${what} found${where}`);
67
+ }
68
+ return;
69
+ }
70
+ const reportedExports = new Set();
71
+ for (const type of ['exports', 'types', 'nsExports', 'nsTypes'])
72
+ for (const byFile of Object.values(issues[type]))
73
+ for (const issue of Object.values(byFile))
74
+ reportedExports.add(`${issue.filePath}:${issue.symbol}`);
75
+ const reportedMembers = new Set();
76
+ for (const type of ['enumMembers', 'namespaceMembers'])
77
+ for (const byFile of Object.values(issues[type]))
78
+ for (const issue of Object.values(byFile))
79
+ reportedMembers.add(`${issue.filePath}:${issue.parentSymbol}.${issue.symbol}`);
80
+ const isReferenced = (node) => !reportedExports.has(`${node.filePath}:${node.identifier}`);
44
81
  for (const node of nodes) {
45
82
  const exp = graph.get(node.filePath)?.exports.get(node.identifier);
46
83
  let memberStatuses;
47
84
  if (exp && exp.members.length > 0) {
48
85
  memberStatuses = [];
49
86
  for (const m of exp.members) {
50
- const id = `${node.identifier}.${m.identifier}`;
51
- const referenced = m.hasRefsInFile ||
52
- explorer.isReferenced(node.filePath, id, {
53
- traverseEntries: true,
54
- treatStarAtEntryAsReferenced: true,
55
- })[0];
87
+ const referenced = !reportedMembers.has(`${node.filePath}:${node.identifier}.${m.identifier}`);
56
88
  memberStatuses.push({ identifier: m.identifier, referenced });
57
89
  }
58
90
  }
@@ -19,5 +19,5 @@ export declare const finalizeConfigurationHints: (results: Results, options: {
19
19
  configFilePath?: string;
20
20
  }) => ProcessedHint[];
21
21
  export declare const printConfigurationHints: ({ cwd, counters, issues, tagHints, configurationHints, enabledPlugins, isTreatConfigHintsAsErrors, includedWorkspaceDirs, selectedWorkspaces, configFilePath, }: ReporterOptions) => void;
22
- export declare const printTagHints: ({ cwd, tagHints }: ReporterOptions) => void;
22
+ export declare const printTagHints: ({ cwd, tagHints, isTreatTagHintsAsErrors }: ReporterOptions) => void;
23
23
  export {};
@@ -128,9 +128,10 @@ export const printConfigurationHints = ({ cwd, counters, issues, tagHints, confi
128
128
  console.warn(getTableForHints(rows).toString());
129
129
  }
130
130
  };
131
- export const printTagHints = ({ cwd, tagHints }) => {
131
+ export const printTagHints = ({ cwd, tagHints, isTreatTagHintsAsErrors }) => {
132
132
  if (tagHints.size > 0) {
133
- console.log(getDimmedTitle('Tag hints', tagHints.size));
133
+ const getTitle = isTreatTagHintsAsErrors ? getColoredTitle : getDimmedTitle;
134
+ console.warn(getTitle('Tag hints', tagHints.size));
134
135
  for (const hint of tagHints) {
135
136
  const { filePath, identifier, tagName } = hint;
136
137
  const message = `Unused tag in ${toRelative(filePath, cwd)}:`;