knip 2.21.2 → 2.22.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
@@ -640,7 +640,7 @@ for false positives, and how to handle them.
640
640
  --production Analyze only production source files (e.g. no tests, devDependencies, exported types)
641
641
  --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)
642
642
  --ignore-internal Ignore exports with tag @internal (JSDoc/TSDoc)
643
- --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)
643
+ -W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)
644
644
  --no-gitignore Don't use .gitignore
645
645
  --include Report only provided issue type(s), can be comma-separated or repeated (1)
646
646
  --exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)
@@ -32,6 +32,7 @@ export declare class DependencyDeputy {
32
32
  scripts: string[];
33
33
  dependencies: string[];
34
34
  peerDependencies: string[];
35
+ optionalPeerDependencies: string[];
35
36
  optionalDependencies: string[];
36
37
  devDependencies: string[];
37
38
  allDependencies: string[];
@@ -46,12 +47,15 @@ export declare class DependencyDeputy {
46
47
  addReferencedBinary(workspaceName: string, binaryName: string): void;
47
48
  addPeerDependencies(workspaceName: string, peerDependencies: Map<string, Set<string>>): void;
48
49
  getPeerDependenciesOf(workspaceName: string, dependency: string): string[];
50
+ getPeerDependencies(workspaceName: string): string[];
51
+ getOptionalPeerDependencies(workspaceName: string): string[];
49
52
  maybeAddReferencedExternalDependency(workspace: Workspace, packageName: string): boolean;
50
53
  maybeAddReferencedBinary(workspace: Workspace, binaryName: string): boolean;
51
54
  private isInDependencies;
52
55
  settleDependencyIssues(): {
53
56
  dependencyIssues: Issue[];
54
57
  devDependencyIssues: Issue[];
58
+ optionalPeerDependencyIssues: Issue[];
55
59
  };
56
60
  getConfigurationHints(): {
57
61
  configurationHints: ConfigurationHints;
@@ -22,6 +22,11 @@ export class DependencyDeputy {
22
22
  const dependencies = Object.keys(manifest.dependencies ?? {});
23
23
  const peerDependencies = Object.keys(manifest.peerDependencies ?? {});
24
24
  const optionalDependencies = Object.keys(manifest.optionalDependencies ?? {});
25
+ const optionalPeerDependencies = manifest.peerDependenciesMeta
26
+ ? peerDependencies.filter(peerDependency => manifest.peerDependenciesMeta &&
27
+ peerDependency in manifest.peerDependenciesMeta &&
28
+ manifest.peerDependenciesMeta[peerDependency].optional)
29
+ : [];
25
30
  const devDependencies = Object.keys(manifest.devDependencies ?? {});
26
31
  const allDependencies = [...dependencies, ...devDependencies, ...peerDependencies, ...optionalDependencies];
27
32
  this._manifests.set(name, {
@@ -32,6 +37,7 @@ export class DependencyDeputy {
32
37
  scripts,
33
38
  dependencies,
34
39
  peerDependencies,
40
+ optionalPeerDependencies,
35
41
  optionalDependencies,
36
42
  devDependencies,
37
43
  allDependencies,
@@ -79,6 +85,18 @@ export class DependencyDeputy {
79
85
  getPeerDependenciesOf(workspaceName, dependency) {
80
86
  return Array.from(this.peerDependencies.get(workspaceName)?.get(dependency) ?? []);
81
87
  }
88
+ getPeerDependencies(workspaceName) {
89
+ const manifest = this._manifests.get(workspaceName);
90
+ if (!manifest)
91
+ return [];
92
+ return manifest.peerDependencies;
93
+ }
94
+ getOptionalPeerDependencies(workspaceName) {
95
+ const manifest = this._manifests.get(workspaceName);
96
+ if (!manifest)
97
+ return [];
98
+ return manifest.optionalPeerDependencies;
99
+ }
82
100
  maybeAddReferencedExternalDependency(workspace, packageName) {
83
101
  if (isBuiltin(packageName))
84
102
  return true;
@@ -133,6 +151,7 @@ export class DependencyDeputy {
133
151
  settleDependencyIssues() {
134
152
  const dependencyIssues = [];
135
153
  const devDependencyIssues = [];
154
+ const optionalPeerDependencyIssues = [];
136
155
  for (const [workspaceName, { manifestPath, ignoreDependencies, ignoreBinaries }] of this._manifests.entries()) {
137
156
  const referencedDependencies = this.referencedDependencies.get(workspaceName);
138
157
  const installedBinaries = this.getInstalledBinaries(workspaceName);
@@ -175,6 +194,7 @@ export class DependencyDeputy {
175
194
  const isNotReferencedDependency = (dependency) => !isReferencedDependency(dependency);
176
195
  const pd = this.getProductionDependencies(workspaceName);
177
196
  const dd = this.getDevDependencies(workspaceName);
197
+ const od = this.getOptionalPeerDependencies(workspaceName);
178
198
  pd.filter(isNotIgnoredDependency)
179
199
  .filter(isNotIgnoredBinary)
180
200
  .filter(isNotReferencedDependency)
@@ -183,8 +203,12 @@ export class DependencyDeputy {
183
203
  .filter(isNotIgnoredBinary)
184
204
  .filter(isNotReferencedDependency)
185
205
  .forEach(symbol => devDependencyIssues.push({ type: 'devDependencies', filePath: manifestPath, symbol }));
206
+ od.filter(isNotIgnoredDependency)
207
+ .filter(isNotIgnoredBinary)
208
+ .filter(p => isReferencedDependency(p))
209
+ .forEach(symbol => optionalPeerDependencyIssues.push({ type: 'optionalPeerDependencies', filePath: manifestPath, symbol }));
186
210
  }
187
- return { dependencyIssues, devDependencyIssues };
211
+ return { dependencyIssues, devDependencyIssues, optionalPeerDependencyIssues };
188
212
  }
189
213
  getConfigurationHints() {
190
214
  const configurationHints = new Set();
@@ -199,12 +223,13 @@ export class DependencyDeputy {
199
223
  const dependencies = this.isStrict
200
224
  ? this.getProductionDependencies(workspaceName)
201
225
  : [...this.getProductionDependencies(workspaceName), ...this.getDevDependencies(workspaceName)];
226
+ const peerDependencies = this.getPeerDependencies(workspaceName);
202
227
  const isReferencedDep = (name) => referencedDependencies?.has(name) && dependencies.includes(name);
203
228
  const isReferencedBin = (name) => referencedBinaries?.has(name) && installedBinaries?.has(name);
204
229
  ignoreDependencies
205
230
  .filter(packageName => IGNORED_DEPENDENCIES.includes(packageName) ||
206
231
  (workspaceName !== ROOT_WORKSPACE_NAME && this.ignoreDependencies.includes(packageName)) ||
207
- isReferencedDep(packageName))
232
+ (!peerDependencies.includes(packageName) && isReferencedDep(packageName)))
208
233
  .forEach(identifier => configurationHints.add({ workspaceName, identifier, type: 'ignoreDependencies' }));
209
234
  ignoreBinaries
210
235
  .filter(binaryName => IGNORED_GLOBAL_BINARIES.includes(binaryName) ||
@@ -216,11 +241,13 @@ export class DependencyDeputy {
216
241
  const dependencies = this.isStrict
217
242
  ? this.getProductionDependencies(ROOT_WORKSPACE_NAME)
218
243
  : [...this.getProductionDependencies(ROOT_WORKSPACE_NAME), ...this.getDevDependencies(ROOT_WORKSPACE_NAME)];
244
+ const peerDependencies = this.getPeerDependencies(ROOT_WORKSPACE_NAME);
219
245
  Object.keys(rootIgnoreBinaries)
220
246
  .filter(key => IGNORED_GLOBAL_BINARIES.includes(key) || (rootIgnoreBinaries[key] !== 0 && installedBinaries?.has(key)))
221
247
  .forEach(identifier => configurationHints.add({ workspaceName: ROOT_WORKSPACE_NAME, identifier, type: 'ignoreBinaries' }));
222
248
  Object.keys(rootIgnoreDependencies)
223
- .filter(key => IGNORED_DEPENDENCIES.includes(key) || (rootIgnoreDependencies[key] !== 0 && dependencies.includes(key)))
249
+ .filter(key => IGNORED_DEPENDENCIES.includes(key) ||
250
+ (rootIgnoreDependencies[key] !== 0 && !peerDependencies.includes(key) && dependencies.includes(key)))
224
251
  .forEach(identifier => configurationHints.add({ workspaceName: ROOT_WORKSPACE_NAME, identifier, type: 'ignoreDependencies' }));
225
252
  return { configurationHints };
226
253
  }
@@ -60,7 +60,5 @@ export declare class ProjectPrincipal {
60
60
  };
61
61
  findUnusedMembers(filePath: string, members: ExportItemMember[]): string[];
62
62
  private findReferences;
63
- isPublicExport(exportedItem: ExportItem): boolean;
64
- getJSDocTags(exportedItem: ExportItem): string[];
65
63
  }
66
64
  export {};
@@ -2,7 +2,7 @@ import { isGitIgnoredSync } from 'globby';
2
2
  import ts from 'typescript';
3
3
  import { DEFAULT_EXTENSIONS } from './constants.js';
4
4
  import { IGNORED_FILE_EXTENSIONS } from './constants.js';
5
- import { isInModuleBlock } from './typescript/ast-helpers.js';
5
+ import { getJSDocTags, isInModuleBlock } from './typescript/ast-helpers.js';
6
6
  import { createHosts } from './typescript/createHosts.js';
7
7
  import { getImportsAndExports } from './typescript/getImportsAndExports.js';
8
8
  import { SourceFileManager } from './typescript/SourceFileManager.js';
@@ -178,7 +178,7 @@ export class ProjectPrincipal {
178
178
  findUnusedMembers(filePath, members) {
179
179
  return members
180
180
  .filter(member => {
181
- if (this.isPublicExport(member))
181
+ if (getJSDocTags(member.node).includes('@public'))
182
182
  return false;
183
183
  const referencedSymbols = this.findReferences(filePath, member.pos);
184
184
  const files = referencedSymbols
@@ -194,11 +194,4 @@ export class ProjectPrincipal {
194
194
  findReferences(filePath, pos) {
195
195
  return this.backend.lsFindReferences(filePath, pos) ?? [];
196
196
  }
197
- isPublicExport(exportedItem) {
198
- const tags = this.getJSDocTags(exportedItem);
199
- return tags.includes('@public');
200
- }
201
- getJSDocTags(exportedItem) {
202
- return ts.getJSDocTags(exportedItem.node).map(node => node.getText().match(/@\S+/)[0]);
203
- }
204
197
  }
@@ -15,7 +15,7 @@ export const getBinariesFromScript = (script, { cwd, manifest, knownGlobalsOnly
15
15
  if (commandExpansions.length > 0) {
16
16
  return commandExpansions.flatMap(expansion => getBinariesFromNodes(expansion.commandAST.commands)) ?? [];
17
17
  }
18
- if (!binary || binary === '.' || binary === 'source')
18
+ if (!binary || binary === '.' || binary === 'source' || binary === '[')
19
19
  return [];
20
20
  if (binary.startsWith('-') || binary.startsWith('"') || binary.startsWith('..'))
21
21
  return [];
@@ -34,13 +34,15 @@ export const getBinariesFromScript = (script, { cwd, manifest, knownGlobalsOnly
34
34
  case 'LogicalExpression':
35
35
  return getBinariesFromNodes([node.left, node.right]);
36
36
  case 'If':
37
- return getBinariesFromNodes([...node.clause.commands, ...node.then.commands, ...(node.else?.commands ?? [])]);
37
+ return getBinariesFromNodes([node.clause, node.then, ...(node.else ? [node.else] : [])]);
38
38
  case 'For':
39
39
  return getBinariesFromNodes(node.do.commands);
40
40
  case 'CompoundList':
41
41
  return getBinariesFromNodes(node.commands);
42
42
  case 'Pipeline':
43
43
  return getBinariesFromNodes(node.commands);
44
+ case 'Function':
45
+ return getBinariesFromNodes(node.body.commands);
44
46
  default:
45
47
  return [];
46
48
  }
@@ -53,7 +53,7 @@ const commands = [
53
53
  ];
54
54
  export const resolve = (_binary, args, { manifest }) => {
55
55
  const scripts = manifest.scripts ? Object.keys(manifest.scripts) : [];
56
- const parsed = parseArgs(args, {});
56
+ const parsed = parseArgs(args, { alias: { recursive: 'r' }, boolean: ['recursive'] });
57
57
  const [command, binary] = parsed._;
58
58
  if (scripts.includes(command) || commands.includes(command))
59
59
  return [];
package/dist/constants.js CHANGED
@@ -60,6 +60,7 @@ export const ISSUE_TYPES = [
60
60
  'files',
61
61
  'dependencies',
62
62
  'devDependencies',
63
+ 'optionalPeerDependencies',
63
64
  'unlisted',
64
65
  'binaries',
65
66
  'unresolved',
@@ -75,6 +76,7 @@ export const ISSUE_TYPE_TITLE = {
75
76
  files: 'Unused files',
76
77
  dependencies: 'Unused dependencies',
77
78
  devDependencies: 'Unused devDependencies',
79
+ optionalPeerDependencies: 'Referenced optional peerDependencies',
78
80
  unlisted: 'Unlisted dependencies',
79
81
  binaries: 'Unlisted binaries',
80
82
  unresolved: 'Unresolved imports',
package/dist/index.js CHANGED
@@ -208,8 +208,10 @@ export const main = async (unresolvedConfiguration) => {
208
208
  }
209
209
  }
210
210
  duplicate.forEach(symbols => {
211
- const symbol = symbols.join('|');
212
- collector.addIssue({ type: 'duplicates', filePath, symbol, symbols });
211
+ if (symbols.length > 1) {
212
+ const symbol = symbols.join('|');
213
+ collector.addIssue({ type: 'duplicates', filePath, symbol, symbols });
214
+ }
213
215
  });
214
216
  external.forEach(specifier => {
215
217
  const packageName = getPackageNameFromModuleSpecifier(specifier);
@@ -276,10 +278,9 @@ export const main = async (unresolvedConfiguration) => {
276
278
  continue;
277
279
  const importingModule = importedSymbols.get(filePath);
278
280
  for (const [symbol, exportedItem] of exportItems.entries()) {
279
- const jsDocTags = principal.getJSDocTags(exportedItem);
280
- if (jsDocTags.includes('@public') || jsDocTags.includes('@beta'))
281
+ if (exportedItem.jsDocTags.includes('@public') || exportedItem.jsDocTags.includes('@beta'))
281
282
  continue;
282
- if (isIgnoreInternal && jsDocTags.includes('@internal'))
283
+ if (isIgnoreInternal && exportedItem.jsDocTags.includes('@internal'))
283
284
  continue;
284
285
  if (importingModule && isSymbolImported(symbol, importingModule)) {
285
286
  if (importingModule.isReExport && isExportedInEntryFile(importingModule))
@@ -317,11 +318,12 @@ export const main = async (unresolvedConfiguration) => {
317
318
  }
318
319
  }
319
320
  if (isReportDependencies) {
320
- const { dependencyIssues, devDependencyIssues } = deputy.settleDependencyIssues();
321
+ const { dependencyIssues, devDependencyIssues, optionalPeerDependencyIssues } = deputy.settleDependencyIssues();
321
322
  const { configurationHints } = deputy.getConfigurationHints();
322
323
  dependencyIssues.forEach(issue => collector.addIssue(issue));
323
324
  if (!isProduction)
324
325
  devDependencyIssues.forEach(issue => collector.addIssue(issue));
326
+ optionalPeerDependencyIssues.forEach(issue => collector.addIssue(issue));
325
327
  configurationHints.forEach(hint => collector.addConfigurationHint(hint));
326
328
  }
327
329
  const unusedIgnoredWorkspaces = chief.getUnusedIgnoredWorkspaces();
@@ -5,7 +5,7 @@ import { _resolve } from '../../util/require.js';
5
5
  import { fallback } from './fallback.js';
6
6
  const getDependencies = (config) => {
7
7
  const extendsSpecifiers = config.extends ? [config.extends].flat().map(resolveExtendSpecifier) : [];
8
- if (extendsSpecifiers.includes('eslint-plugin-prettier'))
8
+ if (extendsSpecifiers.some(specifier => specifier?.startsWith('eslint-plugin-prettier')))
9
9
  extendsSpecifiers.push('eslint-config-prettier');
10
10
  const plugins = config.plugins ? config.plugins.map(resolvePluginSpecifier) : [];
11
11
  const parser = config.parser;
@@ -1,18 +1,13 @@
1
1
  import { readFileSync } from 'fs';
2
2
  import { _getDependenciesFromScripts } from '../../binaries/index.js';
3
+ import { getGitHookPaths } from '../../util/git.js';
3
4
  import { timerify } from '../../util/Performance.js';
4
5
  import { hasDependency } from '../../util/plugin.js';
5
- import { getGitHooksPath } from './helpers.js';
6
6
  export const NAME = 'husky';
7
7
  export const ENABLERS = ['husky'];
8
8
  export const isEnabled = ({ dependencies }) => hasDependency(dependencies, ENABLERS);
9
- const gitHooksPath = getGitHooksPath();
10
- export const CONFIG_FILE_PATTERNS = [
11
- `${gitHooksPath}/prepare-commit-msg`,
12
- `${gitHooksPath}/commit-msg`,
13
- `${gitHooksPath}/pre-{applypatch,commit,merge-commit,push,rebase,receive}`,
14
- `${gitHooksPath}/post-{checkout,commit,merge,rewrite}`,
15
- ];
9
+ const gitHookPaths = getGitHookPaths('.husky');
10
+ export const CONFIG_FILE_PATTERNS = [...gitHookPaths];
16
11
  const findHuskyDependencies = async (configFilePath, { cwd, manifest }) => {
17
12
  const script = readFileSync(configFilePath);
18
13
  return _getDependenciesFromScripts(String(script), {
@@ -1,20 +1,27 @@
1
+ import { readFileSync } from 'fs';
1
2
  import { _getDependenciesFromScripts } from '../../binaries/index.js';
3
+ import { fromBinary } from '../../binaries/util.js';
4
+ import { getGitHookPaths } from '../../util/git.js';
2
5
  import { getValuesByKeyDeep } from '../../util/object.js';
3
6
  import { timerify } from '../../util/Performance.js';
4
7
  import { hasDependency, load } from '../../util/plugin.js';
5
8
  export const NAME = 'Lefthook';
6
9
  export const ENABLERS = ['lefthook', '@arkweid/lefthook'];
7
10
  export const isEnabled = ({ dependencies }) => hasDependency(dependencies, ENABLERS);
8
- export const CONFIG_FILE_PATTERNS = ['lefthook.yml'];
11
+ const gitHookPaths = getGitHookPaths();
12
+ export const CONFIG_FILE_PATTERNS = ['lefthook.yml', ...gitHookPaths];
9
13
  const findLefthookDependencies = async (configFilePath, { cwd, manifest }) => {
10
- const config = await load(configFilePath);
11
- if (!config)
12
- return [];
13
- const scripts = getValuesByKeyDeep(config, 'run').filter((value) => typeof value === 'string');
14
- return _getDependenciesFromScripts(scripts, {
15
- cwd,
16
- manifest,
17
- knownGlobalsOnly: true,
18
- });
14
+ if (configFilePath.endsWith('.yml')) {
15
+ const config = await load(configFilePath);
16
+ if (!config)
17
+ return [];
18
+ const scripts = getValuesByKeyDeep(config, 'run').filter((value) => typeof value === 'string');
19
+ return _getDependenciesFromScripts(scripts, { cwd, manifest, knownGlobalsOnly: true });
20
+ }
21
+ const script = readFileSync(configFilePath, 'utf8');
22
+ const scriptDependencies = _getDependenciesFromScripts([script], { cwd, manifest, knownGlobalsOnly: false });
23
+ const dependencies = manifest.devDependencies ? Object.keys(manifest.devDependencies) : [];
24
+ const matches = scriptDependencies.find(dep => dependencies.includes(fromBinary(dep)));
25
+ return matches ? [matches] : [];
19
26
  };
20
27
  export const findDependencies = timerify(findLefthookDependencies);
@@ -22,6 +22,7 @@ export default async ({ report, issues, options }) => {
22
22
  ...(report.files && { files: false }),
23
23
  ...(report.dependencies && { dependencies: [] }),
24
24
  ...(report.devDependencies && { devDependencies: [] }),
25
+ ...(report.optionalPeerDependencies && { optionalPeerDependencies: [] }),
25
26
  ...(report.unlisted && { unlisted: [] }),
26
27
  ...(report.unresolved && { unresolved: [] }),
27
28
  ...((report.exports || report.nsExports) && { exports: [] }),
@@ -7,6 +7,7 @@ export type ExportItem = {
7
7
  pos: number;
8
8
  type: SymbolType;
9
9
  members?: ExportItemMember[];
10
+ jsDocTags?: string[];
10
11
  };
11
12
  export type ExportItemMember = {
12
13
  node: ts.Node;
@@ -14,6 +15,6 @@ export type ExportItemMember = {
14
15
  pos: number;
15
16
  type: SymbolType;
16
17
  };
17
- export type ExportItems = Map<string, ExportItem>;
18
+ export type ExportItems = Map<string, Required<ExportItem>>;
18
19
  export type Exports = Map<FilePath, ExportItems>;
19
20
  export {};
@@ -22,6 +22,7 @@ export type Issues = {
22
22
  files: IssueSet;
23
23
  dependencies: IssueRecords;
24
24
  devDependencies: IssueRecords;
25
+ optionalPeerDependencies: IssueRecords;
25
26
  unlisted: IssueRecords;
26
27
  binaries: IssueRecords;
27
28
  unresolved: IssueRecords;
@@ -4,6 +4,7 @@ type WorkspaceManifest = {
4
4
  scripts: string[];
5
5
  dependencies: string[];
6
6
  peerDependencies: string[];
7
+ optionalPeerDependencies: string[];
7
8
  optionalDependencies: string[];
8
9
  devDependencies: string[];
9
10
  allDependencies: string[];
@@ -23,4 +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
27
  export {};
@@ -98,3 +98,4 @@ 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]);
@@ -3,7 +3,7 @@ import ts from 'typescript';
3
3
  import { getOrSet } from '../util/map.js';
4
4
  import { isMaybePackageName } from '../util/modules.js';
5
5
  import { isInNodeModules } from '../util/path.js';
6
- import { isDeclarationFileExtension, isAccessExpression, getAccessExpressionName } from './ast-helpers.js';
6
+ import { isDeclarationFileExtension, isAccessExpression, getAccessExpressionName, getJSDocTags, } from './ast-helpers.js';
7
7
  import getExportVisitors from './visitors/exports/index.js';
8
8
  import { getJSXImplicitImportBase } from './visitors/helpers.js';
9
9
  import getImportVisitors from './visitors/imports/index.js';
@@ -86,20 +86,25 @@ export const getImportsAndExports = (sourceFile, options) => {
86
86
  }
87
87
  }
88
88
  };
89
- const addExport = ({ node, identifier, type, pos, members }) => {
89
+ const addExport = ({ node, identifier, type, pos, members = [] }) => {
90
90
  if (options.skipExports)
91
91
  return;
92
+ const jsDocTags = getJSDocTags(node);
92
93
  if (exports.has(identifier)) {
93
94
  const item = exports.get(identifier);
94
- exports.set(identifier, { ...item, node, type, pos, members });
95
+ const crew = [...item.members, ...members];
96
+ const tags = [...item.jsDocTags, ...jsDocTags];
97
+ exports.set(identifier, { ...item, node, type, pos, members: crew, jsDocTags: tags });
95
98
  }
96
99
  else {
97
- exports.set(identifier, { node, type, pos, members });
100
+ exports.set(identifier, { node, type, pos, members, jsDocTags });
101
+ }
102
+ if (!jsDocTags.includes('@alias')) {
103
+ if (ts.isExportAssignment(node))
104
+ maybeAddAliasedExport(node.expression, 'default');
105
+ if (ts.isVariableDeclaration(node))
106
+ maybeAddAliasedExport(node.initializer, identifier);
98
107
  }
99
- if (ts.isExportAssignment(node))
100
- maybeAddAliasedExport(node.expression, 'default');
101
- if (ts.isVariableDeclaration(node))
102
- maybeAddAliasedExport(node.initializer, identifier);
103
108
  };
104
109
  const maybeAddAliasedExport = (node, alias) => {
105
110
  const identifier = node?.getText();
@@ -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 --ignore-internal Ignore exports with tag @internal (JSDoc/TSDoc)\n --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)\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,unresolved\n --exports Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates\n --include-entry-exports Include entry files when reporting unused exports\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 --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 --debug-file-filter Filter for files in debug output (regex as string)\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\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 --debug --debug-file-filter '(specific|particular)-module'\n\nMore documentation and bug reports: https://github.com/webpro/knip";
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 --ignore-internal Ignore exports with tag @internal (JSDoc/TSDoc)\n -W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)\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,unresolved\n --exports Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates\n --include-entry-exports Include entry files when reporting unused exports\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 --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 --debug-file-filter Filter for files in debug output (regex as string)\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\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 --debug --debug-file-filter '(specific|particular)-module'\n\nMore documentation and bug reports: https://github.com/webpro/knip";
2
2
  declare const _default: {
3
3
  config: string | undefined;
4
4
  debug: boolean | undefined;
@@ -9,7 +9,7 @@ Options:
9
9
  --production Analyze only production source files (e.g. no tests, devDependencies, exported types)
10
10
  --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)
11
11
  --ignore-internal Ignore exports with tag @internal (JSDoc/TSDoc)
12
- --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)
12
+ -W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)
13
13
  --no-gitignore Don't use .gitignore
14
14
  --include Report only provided issue type(s), can be comma-separated or repeated (1)
15
15
  --exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)
@@ -68,7 +68,7 @@ try {
68
68
  strict: { type: 'boolean' },
69
69
  tsConfig: { type: 'string', short: 't' },
70
70
  version: { type: 'boolean', short: 'V' },
71
- workspace: { type: 'string' },
71
+ workspace: { type: 'string', short: 'W' },
72
72
  },
73
73
  });
74
74
  }
@@ -1,7 +1,14 @@
1
1
  import { ISSUE_TYPES } from '../constants.js';
2
2
  export const getIncludedIssueTypes = (cliArgs, { include = [], exclude = [], isProduction = false } = {}) => {
3
3
  if (cliArgs.dependencies) {
4
- cliArgs.include = [...cliArgs.include, 'dependencies', 'unlisted', 'binaries', 'unresolved'];
4
+ cliArgs.include = [
5
+ ...cliArgs.include,
6
+ 'dependencies',
7
+ 'optionalPeerDependencies',
8
+ 'unlisted',
9
+ 'binaries',
10
+ 'unresolved',
11
+ ];
5
12
  }
6
13
  if (cliArgs.exports) {
7
14
  const exports = ['exports', 'nsExports', 'classMembers', 'types', 'nsTypes', 'enumMembers', 'duplicates'];
@@ -21,9 +28,9 @@ export const getIncludedIssueTypes = (cliArgs, { include = [], exclude = [], isP
21
28
  }
22
29
  else {
23
30
  if (_include.includes('dependencies'))
24
- _include.push('devDependencies');
31
+ _include.push('devDependencies', 'optionalPeerDependencies');
25
32
  if (_exclude.includes('dependencies'))
26
- _exclude.push('devDependencies');
33
+ _exclude.push('devDependencies', 'optionalPeerDependencies');
27
34
  }
28
35
  const included = (_include.length > 0 ? _include : ISSUE_TYPES).filter(group => !_exclude.includes(group));
29
36
  return ISSUE_TYPES.reduce((types, group) => ((types[group] = included.includes(group)), types), {});
@@ -0,0 +1 @@
1
+ export declare const getGitHookPaths: (defaultPath?: string) => string[];
@@ -0,0 +1,20 @@
1
+ import { execSync } from 'child_process';
2
+ import { join } from './path.js';
3
+ const hookFileNames = [
4
+ `prepare-commit-msg`,
5
+ `commit-msg`,
6
+ `pre-{applypatch,commit,merge-commit,push,rebase,receive}`,
7
+ `post-{checkout,commit,merge,rewrite}`,
8
+ ];
9
+ const getGitHooksPath = (defaultPath = '.git/hooks') => {
10
+ try {
11
+ return execSync('git config --get core.hooksPath', { encoding: 'utf8' }).trim();
12
+ }
13
+ catch (error) {
14
+ return defaultPath;
15
+ }
16
+ };
17
+ export const getGitHookPaths = (defaultPath = '.git/hooks') => {
18
+ const gitHooksPath = getGitHooksPath(defaultPath);
19
+ return hookFileNames.map(fileName => join(gitHooksPath, fileName));
20
+ };
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.21.2";
1
+ export declare const version = "2.22.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.21.2';
1
+ export const version = '2.22.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "2.21.2",
3
+ "version": "2.22.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",
@@ -25,7 +25,7 @@
25
25
  "pretest": "node rmdir.js tmp && swc src -d tmp/src && swc tests -d tmp/tests",
26
26
  "test": "node --no-warnings --test tmp",
27
27
  "coverage": "c8 --reporter html node --no-warnings --loader tsx --test tests/*.test.ts tests/*/*.test.ts",
28
- "watch": "tsc --watch",
28
+ "watch": "rm $(which knip); npm link && tsc --watch",
29
29
  "prebuild": "node rmdir.js dist",
30
30
  "build": "tsc",
31
31
  "docs": "npm run docs:cli && npm run docs:plugins && npm run docs:format",
@@ -1 +0,0 @@
1
- export declare const getGitHooksPath: () => string;
@@ -1,9 +0,0 @@
1
- import { execSync } from 'child_process';
2
- export const getGitHooksPath = () => {
3
- try {
4
- return execSync('git config --get core.hooksPath', { encoding: 'utf8' }).trim();
5
- }
6
- catch (error) {
7
- return '.husky';
8
- }
9
- };