knip 5.1.5 → 5.2.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.
@@ -45,7 +45,10 @@ export class IssueFixer {
45
45
  ].sort((a, b) => b[0] - a[0]);
46
46
  if (exportPositions.length > 0) {
47
47
  const sourceFileText = exportPositions.reduce((text, [start, end]) => text.substring(0, start) + text.substring(end), await readFile(filePath, 'utf-8'));
48
- await writeFile(filePath, sourceFileText);
48
+ const withoutEmptyReExports = sourceFileText
49
+ .replaceAll(/export \{[ ,]+\} from ('|")[^'"]+('|");?\n?/g, '')
50
+ .replaceAll(/export \{[ ,]+\};?\n?/g, '');
51
+ await writeFile(filePath, withoutEmptyReExports);
49
52
  }
50
53
  }
51
54
  }
@@ -1,4 +1,5 @@
1
1
  import ts from 'typescript';
2
+ import { type GetImportsAndExportsOptions } from './typescript/getImportsAndExports.js';
2
3
  import { createCustomModuleResolver } from './typescript/resolveModuleNames.js';
3
4
  import { SourceFileManager } from './typescript/SourceFileManager.js';
4
5
  import type { SyncCompilers, AsyncCompilers } from './compilers/types.js';
@@ -7,12 +8,6 @@ import type { SerializableExportMember } from './types/exports.js';
7
8
  import type { UnresolvedImport } from './types/imports.js';
8
9
  import type { ProgramMaybe53 } from './typescript/SourceFile.js';
9
10
  import type { ReferencedDependencies } from './WorkspaceWorker.js';
10
- type AnalyzeSourceFileOptions = {
11
- skipTypeOnly: boolean;
12
- isFixExports: boolean;
13
- isFixTypes: boolean;
14
- ignoreExportsUsedInFile: boolean;
15
- };
16
11
  export declare class ProjectPrincipal {
17
12
  entryPaths: Set<string>;
18
13
  projectPaths: Set<string>;
@@ -52,7 +47,7 @@ export declare class ProjectPrincipal {
52
47
  getUsedResolvedFiles(): string[];
53
48
  private getProgramSourceFiles;
54
49
  getUnreferencedFiles(): string[];
55
- analyzeSourceFile(filePath: string, options: AnalyzeSourceFileOptions): {
50
+ analyzeSourceFile(filePath: string, options: Omit<GetImportsAndExportsOptions, 'skipExports'>): {
56
51
  imports: {
57
52
  internal: import("./types/imports.js").SerializableImportMap;
58
53
  unresolved: Set<UnresolvedImport>;
@@ -67,4 +62,3 @@ export declare class ProjectPrincipal {
67
62
  resolveModule(specifier: string, filePath?: string): ts.ResolvedModuleFull | undefined;
68
63
  findUnusedMembers(filePath: string, members: SerializableExportMember[]): SerializableExportMember[];
69
64
  }
70
- export {};
package/dist/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import './util/register.js';
2
+ import picocolors from 'picocolors';
2
3
  import prettyMilliseconds from 'pretty-ms';
3
4
  import parsedArgValues, { helpText } from './util/cli-arguments.js';
4
5
  import { isKnownError, getKnownError, isConfigurationError, hasCause } from './util/errors.js';
@@ -8,7 +9,7 @@ import { runPreprocessors, runReporters } from './util/reporter.js';
8
9
  import { splitTags } from './util/tag.js';
9
10
  import { version } from './version.js';
10
11
  import { main } from './index.js';
11
- const { debug: isDebug = false, trace: isTrace = false, help: isHelp, 'max-issues': maxIssues = '0', 'no-config-hints': noConfigHints = false, 'no-exit-code': noExitCode = false, 'no-gitignore': isNoGitIgnore = false, 'no-progress': isNoProgress = isDebug || isTrace || false, 'include-entry-exports': isIncludeEntryExports = false, 'isolate-workspaces': isIsolateWorkspaces = false, performance: isObservePerf = false, production: isProduction = false, 'reporter-options': reporterOptions = '', 'preprocessor-options': preprocessorOptions = '', strict: isStrict = false, fix: isFix = false, 'fix-type': fixTypes = [], tsConfig, version: isVersion, 'experimental-tags': tags = [], } = parsedArgValues;
12
+ const { debug: isDebug = false, trace: isTrace = false, help: isHelp, 'max-issues': maxIssues = '0', 'no-config-hints': noConfigHints = false, 'no-exit-code': noExitCode = false, 'no-gitignore': isNoGitIgnore = false, 'no-progress': isNoProgress = isDebug || isTrace || false, 'include-entry-exports': isIncludeEntryExports = false, 'isolate-workspaces': isIsolateWorkspaces = false, performance: isObservePerf = false, production: isProduction = false, 'reporter-options': reporterOptions = '', 'preprocessor-options': preprocessorOptions = '', strict: isStrict = false, fix: isFix = false, 'fix-type': fixTypes = [], tsConfig, version: isVersion, 'experimental-tags': experimentalTags = [], tags = [], } = parsedArgValues;
12
13
  if (isHelp) {
13
14
  console.log(helpText);
14
15
  process.exit(0);
@@ -30,7 +31,7 @@ const run = async () => {
30
31
  isShowProgress,
31
32
  isIncludeEntryExports,
32
33
  isIsolateWorkspaces,
33
- tags: splitTags(tags),
34
+ tags: tags.length > 0 ? splitTags(tags) : splitTags(experimentalTags),
34
35
  isFix: isFix || fixTypes.length > 0,
35
36
  fixTypes: fixTypes.flatMap(type => type.split(',')),
36
37
  });
@@ -58,6 +59,9 @@ const run = async () => {
58
59
  console.log('\nTotal running time:', prettyMilliseconds(perfObserver.getTotalTime()), `(mem: ${mem}MB)`);
59
60
  perfObserver.reset();
60
61
  }
62
+ if (experimentalTags.length > 0) {
63
+ console.warn(`\n${picocolors.yellow('DEPRECATION WARNING:')} --experimental-tags is deprecated, please start using --tags instead`);
64
+ }
61
65
  if (!noExitCode && totalErrorCount > Number(maxIssues)) {
62
66
  process.exit(1);
63
67
  }
package/dist/index.js CHANGED
@@ -227,6 +227,7 @@ export const main = async (unresolvedConfiguration) => {
227
227
  isFixExports: fixer.isEnabled && fixer.isFixUnusedExports,
228
228
  isFixTypes: fixer.isEnabled && fixer.isFixUnusedTypes,
229
229
  ignoreExportsUsedInFile: Boolean(chief.config.ignoreExportsUsedInFile),
230
+ tags,
230
231
  });
231
232
  const { internal, external, unresolved } = imports;
232
233
  const { exported, duplicate } = exports;
@@ -423,7 +424,7 @@ export const main = async (unresolvedConfiguration) => {
423
424
  continue;
424
425
  if (exportedItem.jsDocTags.includes('@alias'))
425
426
  continue;
426
- if (shouldIgnore(exportedItem, tags))
427
+ if (shouldIgnore(exportedItem.jsDocTags, tags))
427
428
  continue;
428
429
  if (isProduction && exportedItem.jsDocTags.includes('@internal'))
429
430
  continue;
@@ -437,7 +438,7 @@ export const main = async (unresolvedConfiguration) => {
437
438
  if (isIdentifierReferenced(filePath, identifier, importsForExport)) {
438
439
  if (exportedItem.type === 'enum') {
439
440
  exportedItem.members?.forEach(member => {
440
- if (shouldIgnore(member, tags))
441
+ if (shouldIgnore(member.jsDocTags, tags))
441
442
  return;
442
443
  if (member.refs === 0) {
443
444
  exportLookupLog(-1, `Looking up export member ${identifier}.${member.identifier} from`, filePath);
@@ -507,7 +508,7 @@ export const main = async (unresolvedConfiguration) => {
507
508
  const workspace = chief.findWorkspaceByFilePath(filePath);
508
509
  const principal = workspace && factory.getPrincipalByPackageName(workspace.pkgName);
509
510
  if (principal) {
510
- const members = exportedItem.members.filter(member => !shouldIgnore(member, tags));
511
+ const members = exportedItem.members.filter(member => !shouldIgnore(member.jsDocTags, tags));
511
512
  principal.findUnusedMembers(filePath, members).forEach(member => {
512
513
  collector.addIssue({
513
514
  type: 'classMembers',
@@ -89,6 +89,9 @@ export const getJSDocTags = (node) => {
89
89
  else if (ts.isEnumMember(node) || ts.isClassElement(node)) {
90
90
  tagNodes = [...tagNodes, ...ts.getJSDocTags(node.parent)];
91
91
  }
92
+ else if (ts.isCallExpression(node)) {
93
+ tagNodes = [...tagNodes, ...ts.getJSDocTags(node.parent)];
94
+ }
92
95
  for (const tagNode of tagNodes) {
93
96
  const match = tagNode.getText()?.match(/@\S+/);
94
97
  if (match)
@@ -1,5 +1,6 @@
1
1
  import ts from 'typescript';
2
2
  import type { BoundSourceFile, GetResolvedModule } from './SourceFile.js';
3
+ import type { Tags } from '../types/cli.js';
3
4
  import type { SerializableExports } from '../types/exports.js';
4
5
  import type { SerializableImportMap, UnresolvedImport } from '../types/imports.js';
5
6
  import type { IssueSymbol } from '../types/issues.js';
@@ -9,6 +10,7 @@ export type GetImportsAndExportsOptions = {
9
10
  isFixExports: boolean;
10
11
  isFixTypes: boolean;
11
12
  ignoreExportsUsedInFile: boolean;
13
+ tags: Tags;
12
14
  };
13
15
  export declare const _getImportsAndExports: (sourceFile: BoundSourceFile, getResolvedModule: GetResolvedModule, typeChecker: ts.TypeChecker, options: GetImportsAndExportsOptions) => {
14
16
  imports: {
@@ -3,6 +3,7 @@ import ts from 'typescript';
3
3
  import { isStartsLikePackageName, sanitizeSpecifier } from '../util/modules.js';
4
4
  import { isInNodeModules } from '../util/path.js';
5
5
  import { timerify } from '../util/Performance.js';
6
+ import { shouldIgnore } from '../util/tag.js';
6
7
  import { isAccessExpression, getJSDocTags, getLineAndCharacterOfPosition, getMemberStringLiterals, } from './ast-helpers.js';
7
8
  import getDynamicImportVisitors from './visitors/dynamic-imports/index.js';
8
9
  import getExportVisitors from './visitors/exports/index.js';
@@ -30,7 +31,7 @@ const createSerializableMember = (node, member, pos) => {
30
31
  };
31
32
  };
32
33
  const getImportsAndExports = (sourceFile, getResolvedModule, typeChecker, options) => {
33
- const { skipTypeOnly } = options;
34
+ const { skipTypeOnly, tags } = options;
34
35
  const internalImports = {};
35
36
  const externalImports = new Set();
36
37
  const unresolvedImports = new Set();
@@ -75,7 +76,7 @@ const getImportsAndExports = (sourceFile, getResolvedModule, typeChecker, option
75
76
  if (symbol)
76
77
  importedInternalSymbols.set(symbol, filePath);
77
78
  };
78
- const addImport = (options) => {
79
+ const addImport = (options, node) => {
79
80
  const { specifier, isTypeOnly, pos, identifier = '__anonymous', isReExport = false } = options;
80
81
  if (isBuiltin(specifier))
81
82
  return;
@@ -103,6 +104,8 @@ const getImportsAndExports = (sourceFile, getResolvedModule, typeChecker, option
103
104
  else {
104
105
  if (skipTypeOnly && isTypeOnly)
105
106
  return;
107
+ if (shouldIgnore(getJSDocTags(node), tags))
108
+ return;
106
109
  if (typeof pos === 'number') {
107
110
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(pos);
108
111
  unresolvedImports.add({ specifier, pos, line: line + 1, col: character + 1 });
@@ -208,13 +211,13 @@ const getImportsAndExports = (sourceFile, getResolvedModule, typeChecker, option
208
211
  const visit = (node) => {
209
212
  for (const visitor of visitors.dynamicImport) {
210
213
  const result = visitor(node, options);
211
- result && (Array.isArray(result) ? result.forEach(addImport) : addImport(result));
214
+ result && (Array.isArray(result) ? result.forEach(r => addImport(r, node)) : addImport(result, node));
212
215
  }
213
216
  const isTopLevel = node.parent === sourceFile || node.parent?.parent === sourceFile;
214
217
  if (isTopLevel) {
215
218
  for (const visitor of visitors.import) {
216
219
  const result = visitor(node, options);
217
- result && (Array.isArray(result) ? result.forEach(addImport) : addImport(result));
220
+ result && (Array.isArray(result) ? result.forEach(r => addImport(r, node)) : addImport(result, node));
218
221
  }
219
222
  for (const visitor of visitors.export) {
220
223
  const result = visitor(node, options);
@@ -1,4 +1,4 @@
1
- export declare const helpText = "\u2702\uFE0F Find unused files, dependencies and exports in your JavaScript and TypeScript projects\n\nUsage: knip [options]\n\nOptions:\n -c, --config [file] Configuration file path (default: [.]knip.json[c], knip.js, knip.ts or package.json#knip)\n -t, --tsConfig [file] TypeScript configuration path (default: tsconfig.json)\n --production Analyze only production source files (e.g. no tests, devDependencies, exported types)\n --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)\n -W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)\n --directory [dir] Run process from a different directory (default: cwd)\n --no-gitignore Don't use .gitignore\n --include Report only provided issue type(s), can be comma-separated or repeated (1)\n --exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)\n --dependencies Shortcut for --include dependencies,unlisted,binaries,unresolved\n --exports Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates\n --fix Fix issues\n --fix-type Fix only issues of type, can be comma-separated or repeated (2)\n --include-entry-exports Include entry files when reporting unused exports\n --isolate-workspaces Isolate workspaces into separate programs (default: false)\n -n, --no-progress Don't show dynamic progress updates (automatically enabled in CI environments)\n --preprocessor Preprocess the results before providing it to the reporter(s), can be repeated\n --preprocessor-options Pass extra options to the preprocessor (as JSON string, see --reporter-options example)\n --reporter Select reporter: symbols, compact, codeowners, json, can be repeated (default: symbols)\n --reporter-options Pass extra options to the reporter (as JSON string, see example)\n --no-config-hints Suppress configuration hints\n --no-exit-code Always exit with code zero (0)\n --max-issues Maximum number of issues before non-zero exit code (default: 0)\n -d, --debug Show debug output\n --performance Measure count and running time of expensive functions and display stats table\n -h, --help Print this help text\n -V, --version Print version\n\n(1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, classMembers, types, nsTypes, enumMembers, duplicates\n(2) Fixable issue types: dependencies, exports, types\n\nExamples:\n\n$ knip\n$ knip --production\n$ knip --workspace packages/client --include files,dependencies\n$ knip -c ./config/knip.json --reporter compact\n$ knip --reporter codeowners --reporter-options '{\"path\":\".github/CODEOWNERS\"}'\n\nWebsite: https://knip.dev";
1
+ export declare const helpText = "\u2702\uFE0F Find unused files, dependencies and exports in your JavaScript and TypeScript projects\n\nUsage: knip [options]\n\nOptions:\n -c, --config [file] Configuration file path (default: [.]knip.json[c], knip.js, knip.ts or package.json#knip)\n -t, --tsConfig [file] TypeScript configuration path (default: tsconfig.json)\n --production Analyze only production source files (e.g. no tests, devDependencies, exported types)\n --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)\n -W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)\n --directory [dir] Run process from a different directory (default: cwd)\n --no-gitignore Don't use .gitignore\n --include Report only provided issue type(s), can be comma-separated or repeated (1)\n --exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)\n --dependencies Shortcut for --include dependencies,unlisted,binaries,unresolved\n --exports Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates\n --fix Fix issues\n --fix-type Fix only issues of type, can be comma-separated or repeated (2)\n --include-entry-exports Include entry files when reporting unused exports\n --isolate-workspaces Isolate workspaces into separate programs (default: false)\n -n, --no-progress Don't show dynamic progress updates (automatically enabled in CI environments)\n --preprocessor Preprocess the results before providing it to the reporter(s), can be repeated\n --preprocessor-options Pass extra options to the preprocessor (as JSON string, see --reporter-options example)\n --reporter Select reporter: symbols, compact, codeowners, json, can be repeated (default: symbols)\n --reporter-options Pass extra options to the reporter (as JSON string, see example)\n --tags Include or exclude tagged exports\n --no-config-hints Suppress configuration hints\n --no-exit-code Always exit with code zero (0)\n --max-issues Maximum number of issues before non-zero exit code (default: 0)\n -d, --debug Show debug output\n --performance Measure count and running time of expensive functions and display stats table\n -h, --help Print this help text\n -V, --version Print version\n\n(1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, classMembers, types, nsTypes, enumMembers, duplicates\n(2) Fixable issue types: dependencies, exports, types\n\nExamples:\n\n$ knip\n$ knip --production\n$ knip --workspace packages/client --include files,dependencies\n$ knip -c ./config/knip.json --reporter compact\n$ knip --reporter codeowners --reporter-options '{\"path\":\".github/CODEOWNERS\"}'\n$ knip --tags=-knipignore\n\nWebsite: https://knip.dev";
2
2
  declare const _default: {
3
3
  config: string | undefined;
4
4
  debug: boolean | undefined;
@@ -6,6 +6,7 @@ declare const _default: {
6
6
  directory: string | undefined;
7
7
  exclude: string[] | undefined;
8
8
  exports: boolean | undefined;
9
+ tags: string[] | undefined;
9
10
  'experimental-tags': string[] | undefined;
10
11
  fix: boolean | undefined;
11
12
  'fix-type': string[] | undefined;
@@ -24,6 +24,7 @@ Options:
24
24
  --preprocessor-options Pass extra options to the preprocessor (as JSON string, see --reporter-options example)
25
25
  --reporter Select reporter: symbols, compact, codeowners, json, can be repeated (default: symbols)
26
26
  --reporter-options Pass extra options to the reporter (as JSON string, see example)
27
+ --tags Include or exclude tagged exports
27
28
  --no-config-hints Suppress configuration hints
28
29
  --no-exit-code Always exit with code zero (0)
29
30
  --max-issues Maximum number of issues before non-zero exit code (default: 0)
@@ -42,6 +43,7 @@ $ knip --production
42
43
  $ knip --workspace packages/client --include files,dependencies
43
44
  $ knip -c ./config/knip.json --reporter compact
44
45
  $ knip --reporter codeowners --reporter-options '{"path":".github/CODEOWNERS"}'
46
+ $ knip --tags=-knipignore
45
47
 
46
48
  Website: https://knip.dev`;
47
49
  let parsedArgs;
@@ -54,6 +56,7 @@ try {
54
56
  directory: { type: 'string' },
55
57
  exclude: { type: 'string', multiple: true },
56
58
  exports: { type: 'boolean' },
59
+ tags: { type: 'string', multiple: true },
57
60
  'experimental-tags': { type: 'string', multiple: true },
58
61
  fix: { type: 'boolean' },
59
62
  'fix-type': { type: 'string', multiple: true },
@@ -1,4 +1,3 @@
1
1
  import type { Tags } from '../types/cli.js';
2
- import type { SerializableExport, SerializableExportMember } from '../types/exports.js';
3
2
  export declare const splitTags: (tags: string[]) => Tags;
4
- export declare const shouldIgnore: (exportedItem: SerializableExport | SerializableExportMember, tags: Tags) => boolean;
3
+ export declare const shouldIgnore: (jsDocTags: string[], tags: Tags) => boolean;
package/dist/util/tag.js CHANGED
@@ -5,11 +5,11 @@ export const splitTags = (tags) => tags
5
5
  return [incl, excl];
6
6
  }, [[], []]);
7
7
  const hasTag = (tags, jsDocTags) => tags.some(tag => jsDocTags.includes('@' + tag));
8
- export const shouldIgnore = (exportedItem, tags) => {
8
+ export const shouldIgnore = (jsDocTags, tags) => {
9
9
  const [includeJSDocTags, excludeJSDocTags] = tags;
10
- if (includeJSDocTags.length > 0 && !hasTag(includeJSDocTags, exportedItem.jsDocTags))
10
+ if (includeJSDocTags.length > 0 && !hasTag(includeJSDocTags, jsDocTags))
11
11
  return true;
12
- if (excludeJSDocTags.length > 0 && hasTag(excludeJSDocTags, exportedItem.jsDocTags))
12
+ if (excludeJSDocTags.length > 0 && hasTag(excludeJSDocTags, jsDocTags))
13
13
  return true;
14
14
  return false;
15
15
  };
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "5.1.5";
1
+ export declare const version = "5.2.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '5.1.5';
1
+ export const version = '5.2.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "5.1.5",
3
+ "version": "5.2.0",
4
4
  "description": "Find unused files, dependencies and exports in your TypeScript and JavaScript projects",
5
5
  "homepage": "https://knip.dev",
6
6
  "repository": {