knip 1.0.0-beta.3 → 1.0.0-beta.4

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
@@ -57,7 +57,9 @@ Knip supports LTS versions of Node.js, and currently requires at least Node.js v
57
57
 
58
58
  ## Usage
59
59
 
60
- Create a configuration file, let's give it the default name `knip.json` with these contents:
60
+ Knip has good defaults and you can run it without any configuration, but especially larger projects get more out of Knip
61
+ with a configuration file (or a `knip` property in `package.json`). Let's name this file `knip.json` with these contents
62
+ (you might want to adjust right away for your project):
61
63
 
62
64
  ```json
63
65
  {
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import parsedArgs from './util/parseArgs.js';
8
8
  import { measure } from './util/performance.js';
9
9
  import { main } from './index.js';
10
10
  register();
11
- const { values: { debug: isDebug = false, help, 'max-issues': maxIssues = '0', 'no-exit-code': noExitCode = false, 'no-gitignore': isNoGitIgnore = false, 'no-progress': noProgress = false, production: isProduction = false, reporter = 'symbols', 'reporter-options': reporterOptions = '', strict: isStrict = false, tsConfig, }, } = parsedArgs;
11
+ const { values: { debug: isDebug = false, help, 'ignore-entry-exports': isIgnoreEntryExports = false, 'max-issues': maxIssues = '0', 'no-exit-code': noExitCode = false, 'no-gitignore': isNoGitIgnore = false, 'no-progress': noProgress = false, production: isProduction = false, reporter = 'symbols', 'reporter-options': reporterOptions = '', strict: isStrict = false, tsConfig, }, } = parsedArgs;
12
12
  if (help) {
13
13
  printHelp();
14
14
  process.exit(0);
@@ -25,6 +25,7 @@ const run = async () => {
25
25
  isStrict,
26
26
  isProduction,
27
27
  isShowProgress,
28
+ isIgnoreEntryExports,
28
29
  });
29
30
  await printReport({ report, issues, cwd, isProduction, options: reporterOptions });
30
31
  const totalErrorCount = Object.keys(report)
@@ -7,6 +7,7 @@ import * as plugins from './plugins/index.js';
7
7
  import { arrayify } from './util/array.js';
8
8
  import { ConfigurationError } from './util/errors.js';
9
9
  import { findFile, loadJSON } from './util/fs.js';
10
+ import { ensurePosixPath } from './util/glob.js';
10
11
  import parsedArgs from './util/parseArgs.js';
11
12
  import { resolveIncludedIssueTypes } from './util/resolve-included-issue-types.js';
12
13
  import { byPathDepth } from './util/workspace.js';
@@ -84,13 +85,14 @@ export default class ConfigurationChief {
84
85
  .reduce((workspaces, workspace) => {
85
86
  const [workspaceName, workspaceConfig] = workspace;
86
87
  const entry = workspaceConfig.entry ? arrayify(workspaceConfig.entry) : defaultWorkspaceConfig.entry;
88
+ const project = workspaceConfig.project
89
+ ? arrayify(workspaceConfig.project)
90
+ : workspaceConfig.entry
91
+ ? entry
92
+ : defaultWorkspaceConfig.project;
87
93
  workspaces[workspaceName] = {
88
94
  entry,
89
- project: workspaceConfig.project
90
- ? arrayify(workspaceConfig.project)
91
- : workspaceConfig.entry
92
- ? entry
93
- : defaultWorkspaceConfig.project,
95
+ project,
94
96
  ignore: arrayify(workspaceConfig.ignore),
95
97
  };
96
98
  for (const [pluginName, pluginConfig] of Object.entries(workspaceConfig)) {
@@ -125,7 +127,9 @@ export default class ConfigurationChief {
125
127
  ignore: this.config.ignoreWorkspaces,
126
128
  absolute: false,
127
129
  });
128
- this.manifestWorkspaces = Array.from(workspaces.values()).map(dir => path.relative(this.cwd, dir));
130
+ this.manifestWorkspaces = Array.from(workspaces.values())
131
+ .map(dir => path.relative(this.cwd, dir))
132
+ .map(ensurePosixPath);
129
133
  return this.manifestWorkspaces;
130
134
  }
131
135
  return [];
@@ -185,8 +189,9 @@ export default class ConfigurationChief {
185
189
  }
186
190
  async getNegatedWorkspacePatterns(name) {
187
191
  const descendentWorkspaces = await this.getDescendentWorkspaces(name);
192
+ const matchName = new RegExp(`^${name}/`);
188
193
  return descendentWorkspaces
189
- .map(workspaceName => path.relative(path.join(this.cwd, name), workspaceName))
194
+ .map(workspaceName => workspaceName.replace(matchName, ''))
190
195
  .map(workspaceName => `!${workspaceName}`);
191
196
  }
192
197
  getConfigKeyForWorkspace(workspaceName) {
@@ -856,10 +856,10 @@ export declare const ConfigurationValidator: z.ZodObject<z.extendShape<z.extendS
856
856
  }>, "strip", z.ZodTypeAny, {
857
857
  exclude?: string[] | undefined;
858
858
  ignore?: string | string[] | undefined;
859
- ignoreBinaries?: string[] | undefined;
860
859
  include?: string[] | undefined;
861
860
  entry?: string | string[] | undefined;
862
861
  project?: string | string[] | undefined;
862
+ ignoreBinaries?: string[] | undefined;
863
863
  ignoreDependencies?: string[] | undefined;
864
864
  ignoreWorkspaces?: string[] | undefined;
865
865
  babel?: string | false | string[] | {
@@ -1100,10 +1100,10 @@ export declare const ConfigurationValidator: z.ZodObject<z.extendShape<z.extendS
1100
1100
  }, {
1101
1101
  exclude?: string[] | undefined;
1102
1102
  ignore?: string | string[] | undefined;
1103
- ignoreBinaries?: string[] | undefined;
1104
1103
  include?: string[] | undefined;
1105
1104
  entry?: string | string[] | undefined;
1106
1105
  project?: string | string[] | undefined;
1106
+ ignoreBinaries?: string[] | undefined;
1107
1107
  ignoreDependencies?: string[] | undefined;
1108
1108
  ignoreWorkspaces?: string[] | undefined;
1109
1109
  babel?: string | false | string[] | {
package/dist/index.js CHANGED
@@ -9,14 +9,14 @@ import { compact } from './util/array.js';
9
9
  import { debugLogObject, debugLogFiles } from './util/debug.js';
10
10
  import { _findImportModuleSpecifiers } from './util/find-import-specifiers.js';
11
11
  import { findFile, loadJSON } from './util/fs.js';
12
- import { _glob } from './util/glob.js';
12
+ import { _glob, ensurePosixPath } from './util/glob.js';
13
13
  import { getPackageNameFromModuleSpecifier } from './util/modules.js';
14
14
  import { _findDuplicateExportedNames } from './util/project.js';
15
15
  import { loadTSConfig } from './util/tsconfig-loader.js';
16
16
  import { byPathDepth } from './util/workspace.js';
17
17
  import WorkspaceWorker from './workspace-worker.js';
18
18
  export const main = async (unresolvedConfiguration) => {
19
- const { cwd, tsConfigFile, gitignore, isStrict, isProduction, isShowProgress } = unresolvedConfiguration;
19
+ const { cwd, tsConfigFile, gitignore, isStrict, isProduction, isShowProgress, isIgnoreEntryExports } = unresolvedConfiguration;
20
20
  const chief = new ConfigurationChief({ cwd, isStrict, isProduction });
21
21
  debugLogObject('Unresolved configuration', unresolvedConfiguration);
22
22
  const collector = new IssueCollector({ cwd, isShowProgress });
@@ -81,6 +81,8 @@ export const main = async (unresolvedConfiguration) => {
81
81
  });
82
82
  debugLogFiles(`Globbed entry paths${suffix}`, workspaceEntryPaths);
83
83
  workspaceEntryPaths.forEach(entryPath => principal.addEntryPath(entryPath));
84
+ if (isIgnoreEntryExports)
85
+ workspaceEntryPaths.forEach(entryPath => lab.skipExportsAnalysisFor(entryPath));
84
86
  collector.updateMessage(`Resolving production plugin entry files${suffix}...`);
85
87
  const pluginWorkspaceEntryPaths = await _glob({
86
88
  cwd,
@@ -120,6 +122,8 @@ export const main = async (unresolvedConfiguration) => {
120
122
  });
121
123
  debugLogFiles(`Globbed entry paths${suffix}`, workspaceEntryPaths);
122
124
  workspaceEntryPaths.forEach(entryPath => principal.addEntryPath(entryPath));
125
+ if (isIgnoreEntryExports)
126
+ workspaceEntryPaths.forEach(entryPath => lab.skipExportsAnalysisFor(entryPath));
123
127
  }
124
128
  {
125
129
  collector.updateMessage(`Resolving project files${suffix}...`);
@@ -232,7 +236,7 @@ export const main = async (unresolvedConfiguration) => {
232
236
  const filePath = sourceFile.getFilePath();
233
237
  if (report.dependencies || report.unlisted) {
234
238
  const filePath = sourceFile.getFilePath();
235
- const workspaceDir = workspaceDirs.find(workspaceDir => filePath.startsWith(workspaceDir));
239
+ const workspaceDir = workspaceDirs.find(workspaceDir => filePath.startsWith(ensurePosixPath(workspaceDir)));
236
240
  const workspace = workspaces.find(workspace => workspace.dir === workspaceDir);
237
241
  if (workspace) {
238
242
  const [, externalModuleSpecifiers] = moduleSpecifierCache.get(sourceFile) ?? _findImportModuleSpecifiers(sourceFile, { skipInternal: true });
@@ -52,23 +52,17 @@ export default class SourceLab {
52
52
  if (declaration.isKind(ts.SyntaxKind.EnumDeclaration)) {
53
53
  identifier = declaration.getFirstChildByKind(ts.SyntaxKind.Identifier);
54
54
  if (report.enumMembers) {
55
- findUnusedEnumMembers(declaration, filePath).forEach(member => issues.add({
56
- type: 'enumMembers',
57
- filePath,
58
- symbol: member.getName().replace(/['"`]/g, ''),
59
- parentSymbol: identifier?.getText(),
60
- }));
55
+ findUnusedEnumMembers(declaration, filePath).forEach(symbol => {
56
+ issues.add({ type: 'enumMembers', filePath, symbol, parentSymbol: identifier?.getText() });
57
+ });
61
58
  }
62
59
  }
63
60
  else if (declaration.isKind(ts.SyntaxKind.ClassDeclaration)) {
64
61
  identifier = declaration.getFirstChildByKind(ts.SyntaxKind.Identifier);
65
62
  if (report.classMembers) {
66
- findUnusedClassMembers(declaration, filePath).forEach(member => issues.add({
67
- type: 'classMembers',
68
- filePath,
69
- symbol: member.getName(),
70
- parentSymbol: identifier?.getText(),
71
- }));
63
+ findUnusedClassMembers(declaration, filePath).forEach(symbol => {
64
+ issues.add({ type: 'classMembers', filePath, symbol, parentSymbol: identifier?.getText() });
65
+ });
72
66
  }
73
67
  }
74
68
  if (Node.isExportGetable(declaration) &&
@@ -5,4 +5,5 @@ export interface CommandLineOptions {
5
5
  isStrict: boolean;
6
6
  isProduction: boolean;
7
7
  isShowProgress: boolean;
8
+ isIgnoreEntryExports: boolean;
8
9
  }
@@ -1,5 +1,6 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  /// <reference types="nano-memoize" />
3
+ export declare const ensurePosixPath: (value: string) => string;
3
4
  export declare const negate: (pattern: string) => string;
4
5
  export declare const hasProductionSuffix: (pattern: string) => boolean;
5
6
  export declare const hasNoProductionSuffix: (pattern: string) => boolean;
package/dist/util/glob.js CHANGED
@@ -5,7 +5,7 @@ import memoize from 'nano-memoize';
5
5
  import { compact } from './array.js';
6
6
  import { debugLogObject } from './debug.js';
7
7
  import { timerify } from './performance.js';
8
- const ensurePosixPath = (value) => value.split(path.sep).join(path.posix.sep);
8
+ export const ensurePosixPath = (value) => value.split(path.sep).join(path.posix.sep);
9
9
  const prependDirToPattern = (workingDir, pattern) => {
10
10
  if (pattern.startsWith('!'))
11
11
  return '!' + path.posix.join(workingDir, pattern.slice(1));
package/dist/util/help.js CHANGED
@@ -10,6 +10,7 @@ Options:
10
10
  --include Report only listed issue type(s), can be comma-separated or repeated
11
11
  --exclude Exclude issue type(s) from report, can be comma-separated or repeated
12
12
  --ignore Ignore files matching this glob pattern, can be repeated
13
+ --ignore-entry-exports Ignore exports from entry files
13
14
  --no-gitignore Don't use .gitignore
14
15
  --no-progress Don't show dynamic progress updates
15
16
  --no-exit-code Always exit with code zero (0)
@@ -1,4 +1,3 @@
1
- import { MethodDeclaration, PropertyDeclaration } from 'ts-morph';
2
1
  import type { ClassDeclaration, EnumDeclaration } from 'ts-morph';
3
- export declare const findUnusedClassMembers: (declaration: ClassDeclaration, filePath: string) => (MethodDeclaration | PropertyDeclaration)[];
4
- export declare const findUnusedEnumMembers: (declaration: EnumDeclaration, filePath: string) => import("ts-morph").EnumMember[];
2
+ export declare const findUnusedClassMembers: (declaration: ClassDeclaration, filePath: string) => string[];
3
+ export declare const findUnusedEnumMembers: (declaration: EnumDeclaration, filePath: string) => string[];
@@ -2,7 +2,8 @@ import { ts } from 'ts-morph';
2
2
  import { _findReferences, hasExternalReferences, hasInternalReferences } from './project.js';
3
3
  export const findUnusedClassMembers = (declaration, filePath) => {
4
4
  const members = declaration.getMembers();
5
- return members.filter((member) => {
5
+ return members
6
+ .filter((member) => {
6
7
  const isPrivate = Boolean(member.getCombinedModifierFlags() & ts.ModifierFlags.Private);
7
8
  if (!isPrivate &&
8
9
  (member.isKind(ts.SyntaxKind.PropertyDeclaration) || member.isKind(ts.SyntaxKind.MethodDeclaration))) {
@@ -10,12 +11,15 @@ export const findUnusedClassMembers = (declaration, filePath) => {
10
11
  return !hasExternalReferences(refs, filePath) && !hasInternalReferences(refs);
11
12
  }
12
13
  return false;
13
- });
14
+ })
15
+ .map(member => member.getName());
14
16
  };
15
17
  export const findUnusedEnumMembers = (declaration, filePath) => {
16
18
  const members = declaration.getMembers();
17
- return members.filter(member => {
19
+ return members
20
+ .filter(member => {
18
21
  const refs = _findReferences(member);
19
22
  return !hasExternalReferences(refs, filePath) && !hasInternalReferences(refs);
20
- });
23
+ })
24
+ .map(member => member.getName().replace(/['"`]/g, ''));
21
25
  };
@@ -1,9 +1,10 @@
1
+ import { ensurePosixPath } from './glob.js';
1
2
  export const getPackageNameFromModuleSpecifier = (moduleSpecifier) => {
2
3
  const parts = moduleSpecifier.split('/').slice(0, 2);
3
4
  return moduleSpecifier.startsWith('@') ? parts.join('/') : parts[0];
4
5
  };
5
6
  export const getPackageName = (value) => {
6
- const match = value.replace(/\\/g, '/').match(/(?<=node_modules\/)(@[^/]+\/[^/]+|[^/]+)/);
7
+ const match = ensurePosixPath(value).match(/(?<=node_modules\/)(@[^/]+\/[^/]+|[^/]+)/);
7
8
  if (match)
8
9
  return match[1];
9
10
  if (value.startsWith('@')) {
@@ -6,7 +6,7 @@ declare const _default: {
6
6
  exclude: string[] | undefined;
7
7
  help: boolean | undefined;
8
8
  ignore: string[] | undefined;
9
- ignoreBinaries: string[] | undefined;
9
+ 'ignore-entry-exports': boolean | undefined;
10
10
  include: string[] | undefined;
11
11
  'max-issues': string | undefined;
12
12
  'no-exit-code': boolean | undefined;
@@ -7,7 +7,7 @@ export default parseArgs({
7
7
  exclude: { type: 'string', multiple: true },
8
8
  help: { type: 'boolean', short: 'h' },
9
9
  ignore: { type: 'string', multiple: true },
10
- ignoreBinaries: { type: 'string', multiple: true },
10
+ 'ignore-entry-exports': { type: 'boolean' },
11
11
  include: { type: 'string', multiple: true },
12
12
  'max-issues': { type: 'string' },
13
13
  'no-exit-code': { type: 'boolean' },
package/dist/util/path.js CHANGED
@@ -1,3 +1,4 @@
1
1
  import path from 'node:path';
2
+ import { ensurePosixPath } from './glob.js';
2
3
  const cwd = process.cwd();
3
- export const relative = (to) => path.relative(cwd, to).replace(/\\/g, '/');
4
+ export const relative = (to) => ensurePosixPath(path.relative(cwd, to));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "Find unused files, dependencies and exports in your TypeScript and JavaScript project",
5
5
  "keywords": [
6
6
  "find",
@@ -61,7 +61,7 @@
61
61
  "@snyk/github-codeowners": "1.1.0",
62
62
  "chalk": "5.2.0",
63
63
  "easy-table": "1.2.0",
64
- "esbuild": "0.16.11",
64
+ "esbuild": "0.16.12",
65
65
  "esbuild-register": "3.4.2",
66
66
  "eslint": "8.30.0",
67
67
  "fast-glob": "3.2.12",
@@ -91,7 +91,7 @@
91
91
  "eslint-import-resolver-typescript": "3.5.2",
92
92
  "eslint-plugin-import": "2.26.0",
93
93
  "globstar": "1.0.0",
94
- "release-it": "15.5.1",
94
+ "release-it": "15.6.0",
95
95
  "remark-cli": "11.0.0",
96
96
  "remark-preset-webpro": "0.0.1",
97
97
  "tsx": "3.12.1",