knip 2.24.0 → 2.25.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.
package/README.md CHANGED
@@ -799,7 +799,7 @@ Special thanks to the wonderful people who have contributed to this project:
799
799
  [7]: #jsdoc-tags
800
800
  [8]: https://labs.openai.com/s/xZQACaLepaKya0PRUPtIN5dC
801
801
  [9]: ./assets/cow-with-orange-scissors-van-gogh-style.webp
802
- [10]: https://discord.gg/ya5yktTq
802
+ [10]: https://discord.gg/r5uXTtbTpc
803
803
  [11]: https://twitter.com/webprolific
804
804
  [12]: https://fosstodon.org/@webpro
805
805
  [13]: https://github.com/webpro/knip/issues
@@ -178,7 +178,7 @@ export class ProjectPrincipal {
178
178
  findUnusedMembers(filePath, members) {
179
179
  return members
180
180
  .filter(member => {
181
- if (getJSDocTags(member.node).includes('@public'))
181
+ if (getJSDocTags(member.node).has('@public'))
182
182
  return false;
183
183
  const referencedSymbols = this.findReferences(filePath, member.pos);
184
184
  const files = referencedSymbols
@@ -3,6 +3,7 @@ export * as dotenv from './dotenv.js';
3
3
  export * as node from './node.js';
4
4
  export * as nodemon from './nodemon.js';
5
5
  export * as npx from './npx.js';
6
+ export * as nx from './nx.js';
6
7
  export * as pnpm from './pnpm.js';
7
8
  export * as rollup from './rollup.js';
8
9
  export * as yarn from './yarn.js';
@@ -3,6 +3,7 @@ export * as dotenv from './dotenv.js';
3
3
  export * as node from './node.js';
4
4
  export * as nodemon from './nodemon.js';
5
5
  export * as npx from './npx.js';
6
+ export * as nx from './nx.js';
6
7
  export * as pnpm from './pnpm.js';
7
8
  export * as rollup from './rollup.js';
8
9
  export * as yarn from './yarn.js';
@@ -0,0 +1,2 @@
1
+ import type { Resolver } from '../types.js';
2
+ export declare const resolve: Resolver;
@@ -0,0 +1,9 @@
1
+ import parseArgs from 'minimist';
2
+ import { toBinary } from '../util.js';
3
+ export const resolve = (binary, args, { fromArgs }) => {
4
+ const parsed = parseArgs(args);
5
+ const [command] = parsed._;
6
+ if (command === 'exec')
7
+ return [toBinary(binary), ...fromArgs(parsed._.slice(1))];
8
+ return [toBinary(binary)];
9
+ };
package/dist/index.js CHANGED
@@ -278,9 +278,9 @@ export const main = async (unresolvedConfiguration) => {
278
278
  continue;
279
279
  const importingModule = importedSymbols.get(filePath);
280
280
  for (const [symbol, exportedItem] of exportItems.entries()) {
281
- if (exportedItem.jsDocTags.includes('@public') || exportedItem.jsDocTags.includes('@beta'))
281
+ if (exportedItem.jsDocTags.has('@public') || exportedItem.jsDocTags.has('@beta'))
282
282
  continue;
283
- if (isIgnoreInternal && exportedItem.jsDocTags.includes('@internal'))
283
+ if (isIgnoreInternal && exportedItem.jsDocTags.has('@internal'))
284
284
  continue;
285
285
  if (importingModule && isSymbolImported(symbol, importingModule)) {
286
286
  if (importingModule.isReExport && isExportedInEntryFile(importingModule))
@@ -62,7 +62,8 @@ const findJestDependencies = async (configFilePath, { cwd, manifest }) => {
62
62
  config = await config();
63
63
  if (!config)
64
64
  return [];
65
- const replaceRootDir = (name) => name.includes('<rootDir>') ? join(cwd, name.replace(/^.*<rootDir>/, '')) : name;
65
+ const rootDir = config.rootDir ?? '';
66
+ const replaceRootDir = (name) => name.includes('<rootDir>') ? join(cwd, name.replace(/^.*<rootDir>/, rootDir)) : name;
66
67
  return resolveDependencies(config).map(replaceRootDir);
67
68
  };
68
69
  export const findDependencies = timerify(findJestDependencies);
@@ -3,9 +3,17 @@ export const NAME = 'Next.js';
3
3
  export const ENABLERS = ['next'];
4
4
  export const isEnabled = ({ dependencies }) => hasDependency(dependencies, ENABLERS);
5
5
  export const ENTRY_FILE_PATTERNS = ['next.config.{js,ts,cjs,mjs}'];
6
- export const PRODUCTION_ENTRY_FILE_PATTERNS = [
7
- '{app,pages}/**/*.{js,jsx,ts,tsx}',
8
- 'src/{app,pages}/**/*.{js,jsx,ts,tsx}',
6
+ const productionEntryFilePatternsWithoutSrc = [
9
7
  'middleware.{js,ts}',
10
- 'src/middleware.{js,ts}',
8
+ 'app/**/route.{js,ts}',
9
+ 'app/**/{error,layout,loading,not-found,page,template}.{js,jsx,tsx}',
10
+ 'instrumentation.{js,ts}',
11
+ 'app/{manifest,sitemap,robots}.{js,ts}',
12
+ 'app/**/{icon,apple-icon}-image.{js,ts,tsx}',
13
+ 'app/**/{opengraph,twitter}-image.{js,ts,tsx}',
14
+ 'pages/**/*.{js,jsx,ts,tsx}',
15
+ ];
16
+ export const PRODUCTION_ENTRY_FILE_PATTERNS = [
17
+ ...productionEntryFilePatternsWithoutSrc,
18
+ ...productionEntryFilePatternsWithoutSrc.map(pattern => `src/${pattern}`),
11
19
  ];
@@ -1,3 +1,4 @@
1
+ import ts from 'typescript';
1
2
  import { compact } from '../../util/array.js';
2
3
  import { dirname, isInternal, toAbsolute } from '../../util/path.js';
3
4
  import { timerify } from '../../util/Performance.js';
@@ -21,21 +22,23 @@ const resolveExtensibleConfig = async (configFilePath) => {
21
22
  }
22
23
  return config;
23
24
  };
25
+ const jsxWithReact = [ts.JsxEmit.React, ts.JsxEmit.ReactJSX, ts.JsxEmit.ReactJSXDev, ts.JsxEmit.ReactNative];
24
26
  const findTypeScriptDependencies = async (configFilePath) => {
25
27
  const compilerOptions = await loadTSConfig(configFilePath);
26
28
  const config = await resolveExtensibleConfig(configFilePath);
27
29
  if (!compilerOptions || !config)
28
30
  return [];
29
31
  const extend = config.extends ? [config.extends].flat().filter(extend => !isInternal(extend)) : [];
32
+ const types = compilerOptions.types ?? [];
30
33
  const plugins = Array.isArray(compilerOptions?.plugins)
31
34
  ? compilerOptions.plugins.map(plugin => (typeof plugin === 'object' && 'name' in plugin ? plugin.name : ''))
32
35
  : [];
33
36
  const importHelpers = compilerOptions?.importHelpers ? ['tslib'] : [];
34
37
  const jsx = compilerOptions?.jsxImportSource
35
38
  ? [compilerOptions.jsxImportSource]
36
- : compilerOptions?.jsx
39
+ : compilerOptions?.jsx && jsxWithReact.includes(compilerOptions.jsx)
37
40
  ? ['react']
38
41
  : [];
39
- return compact([...extend, ...plugins, ...importHelpers, ...jsx]);
42
+ return compact([...extend, ...types, ...plugins, ...importHelpers, ...jsx]);
40
43
  };
41
44
  export const findDependencies = timerify(findTypeScriptDependencies);
@@ -7,7 +7,7 @@ export type ExportItem = {
7
7
  pos: number;
8
8
  type: SymbolType;
9
9
  members?: ExportItemMember[];
10
- jsDocTags?: string[];
10
+ jsDocTags?: Set<string>;
11
11
  };
12
12
  export type ExportItemMember = {
13
13
  node: ts.Node;
@@ -10,7 +10,7 @@ export declare function isDefaultImport(node: ts.ImportDeclaration | ts.ImportEq
10
10
  export declare function isAccessExpression(node: ts.Node): node is ts.PropertyAccessExpression | ts.ElementAccessExpression;
11
11
  export declare function isImportCall(node: ts.Node): node is ts.ImportCall;
12
12
  export declare function isRequireCall(callExpression: ts.Node): callExpression is ts.CallExpression;
13
- export declare function isRequireResolveCall(node: ts.Node): node is ts.CallExpression;
13
+ export declare function isPropertyAccessCall(node: ts.Node, identifier: string): node is ts.CallExpression;
14
14
  export declare function getAccessExpressionName(node: ts.PropertyAccessExpression | ts.ElementAccessExpression): string;
15
15
  type LiteralLikeElementAccessExpression = ts.ElementAccessExpression & ts.Declaration & {
16
16
  readonly argumentExpression: ts.StringLiteralLike | ts.NumericLiteral;
@@ -23,5 +23,5 @@ export declare function findAncestor<T>(node: ts.Node | undefined, callback: (el
23
23
  export declare function findDescendants<T>(node: ts.Node | undefined, callback: (element: ts.Node) => boolean | 'STOP'): T[];
24
24
  export declare const isDeclarationFileExtension: (extension: string) => boolean;
25
25
  export declare const isInModuleBlock: (node: ts.Node) => boolean;
26
- export declare const getJSDocTags: (node: ts.Node) => string[];
26
+ export declare const getJSDocTags: (node: ts.Node) => Set<string>;
27
27
  export {};
@@ -25,10 +25,10 @@ export function isRequireCall(callExpression) {
25
25
  }
26
26
  return args.length === 1;
27
27
  }
28
- export function isRequireResolveCall(node) {
28
+ export function isPropertyAccessCall(node, identifier) {
29
29
  return (ts.isCallExpression(node) &&
30
30
  ts.isPropertyAccessExpression(node.expression) &&
31
- node.expression.getText() === 'require.resolve');
31
+ node.expression.getText() === identifier);
32
32
  }
33
33
  export function getAccessExpressionName(node) {
34
34
  return 'argumentExpression' in node ? stripQuotes(node.argumentExpression.getText()) : node.name.getText();
@@ -98,4 +98,12 @@ export const isInModuleBlock = (node) => {
98
98
  }
99
99
  return false;
100
100
  };
101
- export const getJSDocTags = (node) => ts.getJSDocTags(node).map(node => node.getText().match(/@\S+/)[0]);
101
+ export const getJSDocTags = (node) => {
102
+ const tags = new Set();
103
+ for (const tagNode of ts.getJSDocTags(node)) {
104
+ const match = tagNode.getText()?.match(/@\S+/);
105
+ if (match)
106
+ tags.add(match[0]);
107
+ }
108
+ return tags;
109
+ };
@@ -1,4 +1,6 @@
1
+ import { existsSync } from 'node:fs';
1
2
  import { isBuiltin } from 'node:module';
3
+ import { resolve, dirname } from 'node:path';
2
4
  import ts from 'typescript';
3
5
  import { getOrSet } from '../util/map.js';
4
6
  import { isMaybePackageName } from '../util/modules.js';
@@ -73,7 +75,13 @@ export const getImportsAndExports = (sourceFile, options) => {
73
75
  }
74
76
  }
75
77
  else {
76
- unresolvedImports.add(specifier);
78
+ const filePath = resolve(dirname(sourceFile.fileName), specifier);
79
+ if (existsSync(filePath)) {
80
+ unresolvedImports.add(filePath);
81
+ }
82
+ else {
83
+ unresolvedImports.add(specifier);
84
+ }
77
85
  }
78
86
  };
79
87
  const maybeAddNamespaceAccessAsImport = ({ namespace, member }) => {
@@ -93,13 +101,13 @@ export const getImportsAndExports = (sourceFile, options) => {
93
101
  if (exports.has(identifier)) {
94
102
  const item = exports.get(identifier);
95
103
  const crew = [...item.members, ...members];
96
- const tags = [...item.jsDocTags, ...jsDocTags];
104
+ const tags = new Set([...item.jsDocTags, ...jsDocTags]);
97
105
  exports.set(identifier, { ...item, node, type, pos, members: crew, jsDocTags: tags });
98
106
  }
99
107
  else {
100
108
  exports.set(identifier, { node, type, pos, members, jsDocTags });
101
109
  }
102
- if (!jsDocTags.includes('@alias')) {
110
+ if (!jsDocTags.has('@alias')) {
103
111
  if (ts.isExportAssignment(node))
104
112
  maybeAddAliasedExport(node.expression, 'default');
105
113
  if (ts.isVariableDeclaration(node))
@@ -3,16 +3,16 @@ import importCall from './importCall.js';
3
3
  import importDeclaration from './importDeclaration.js';
4
4
  import importEqualsDeclaration from './importEqualsDeclaration.js';
5
5
  import jsDocType from './jsDocType.js';
6
+ import propertyAccessCall from './propertyAccessCall.js';
6
7
  import reExportDeclaration from './reExportDeclaration.js';
7
8
  import requireCall from './requireCall.js';
8
- import requireResolveCall from './requireResolveCall.js';
9
9
  const visitors = [
10
10
  importCall,
11
11
  importDeclaration,
12
12
  importEqualsDeclaration,
13
13
  jsDocType,
14
+ propertyAccessCall,
14
15
  reExportDeclaration,
15
16
  requireCall,
16
- requireResolveCall,
17
17
  ];
18
18
  export default (sourceFile) => visitors.map(v => v(sourceFile));
@@ -1,8 +1,8 @@
1
1
  import ts from 'typescript';
2
- import { isRequireResolveCall } from '../../ast-helpers.js';
2
+ import { isPropertyAccessCall } from '../../ast-helpers.js';
3
3
  import { importVisitor as visit } from '../index.js';
4
4
  export default visit(() => true, node => {
5
- if (isRequireResolveCall(node)) {
5
+ if (isPropertyAccessCall(node, 'require.resolve')) {
6
6
  if (node.arguments[0] && ts.isStringLiteralLike(node.arguments[0])) {
7
7
  const specifier = node.arguments[0].text;
8
8
  if (specifier)
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.24.0";
1
+ export declare const version = "2.25.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.24.0';
1
+ export const version = '2.25.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "2.24.0",
3
+ "version": "2.25.0",
4
4
  "description": "Find unused files, dependencies and exports in your TypeScript and JavaScript projects",
5
5
  "homepage": "https://github.com/webpro/knip",
6
6
  "repository": "github:webpro/knip",
@@ -35,7 +35,7 @@
35
35
  "qa": "npm run lint && npm run build && npm run knip && npm run knip:production && npm test",
36
36
  "release": "release-it",
37
37
  "create-plugin": "tsx --no-warnings ./scripts/create-new-plugin.ts",
38
- "postcreate-plugin": "prettier schema.json src/ConfigurationValidator.ts --write --config .prettierrc --loglevel silent"
38
+ "postcreate-plugin": "prettier schema.json src/ConfigurationValidator.ts --write --config .prettierrc --log-level silent"
39
39
  },
40
40
  "files": [
41
41
  "dist",
@@ -65,12 +65,12 @@
65
65
  "@npmcli/package-json": "5.0.0",
66
66
  "@release-it/bumper": "5.1.0",
67
67
  "@swc/cli": "0.1.62",
68
- "@swc/core": "1.3.84",
68
+ "@swc/core": "1.3.85",
69
69
  "@types/eslint": "8.44.2",
70
- "@types/js-yaml": "4.0.5",
70
+ "@types/js-yaml": "4.0.6",
71
71
  "@types/micromatch": "4.0.2",
72
72
  "@types/minimist": "1.2.2",
73
- "@types/node": "20.6.0",
73
+ "@types/node": "20.6.2",
74
74
  "@types/npmcli__map-workspaces": "3.0.1",
75
75
  "@types/webpack": "5.28.2",
76
76
  "@typescript-eslint/eslint-plugin": "6.7.0",
@@ -84,7 +84,7 @@
84
84
  "release-it": "16.1.5",
85
85
  "remark-cli": "11.0.0",
86
86
  "remark-preset-webpro": "0.0.3",
87
- "tsx": "3.12.8",
87
+ "tsx": "3.12.10",
88
88
  "type-fest": "4.3.1"
89
89
  },
90
90
  "engines": {