knip 1.0.0-alpha.1 → 1.0.0-alpha.2

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.
@@ -103,10 +103,12 @@ export default class ConfigurationChief {
103
103
  const config = isObject ? arrayify(pluginConfig.config) : arrayify(pluginConfig);
104
104
  const entryFiles = isObject && 'entryFiles' in pluginConfig ? arrayify(pluginConfig.entryFiles) : [];
105
105
  const projectFiles = isObject && 'projectFiles' in pluginConfig ? arrayify(pluginConfig.projectFiles) : entryFiles;
106
+ const sampleFiles = isObject && 'sampleFiles' in pluginConfig ? arrayify(pluginConfig.sampleFiles) : [];
106
107
  workspaces[workspaceName][pluginName] = {
107
108
  config,
108
109
  entryFiles,
109
110
  projectFiles,
111
+ sampleFiles,
110
112
  };
111
113
  }
112
114
  }
@@ -1,5 +1,6 @@
1
1
  import { isBuiltin } from 'node:module';
2
2
  import micromatch from 'micromatch';
3
+ import { isDefinitelyTyped, getDefinitelyTypedPackage } from './util/modules.js';
3
4
  const IGNORE_DEFINITELY_TYPED = ['node'];
4
5
  export default class DependencyDeputy {
5
6
  _manifests = new Map();
@@ -69,12 +70,14 @@ export default class DependencyDeputy {
69
70
  const packageName = this.resolvePackageName(moduleSpecifier);
70
71
  const workspaceNames = isStrict ? [workspace.name] : [workspace.name, ...[...workspace.ancestors].reverse()];
71
72
  const closestWorkspaceName = workspaceNames.find(name => this.isInDependencies(name, packageName));
72
- if (closestWorkspaceName) {
73
- this.addReferencedDependency(closestWorkspaceName, packageName);
74
- }
75
- else {
76
- return moduleSpecifier;
73
+ const typesPackageName = !isDefinitelyTyped(packageName) && getDefinitelyTypedPackage(packageName);
74
+ const closestWorkspaceNameForTypes = typesPackageName && workspaceNames.find(name => this.isInDependencies(name, typesPackageName));
75
+ if (closestWorkspaceName || closestWorkspaceNameForTypes) {
76
+ closestWorkspaceName && this.addReferencedDependency(closestWorkspaceName, packageName);
77
+ closestWorkspaceNameForTypes && this.addReferencedDependency(closestWorkspaceNameForTypes, typesPackageName);
78
+ return;
77
79
  }
80
+ return moduleSpecifier;
78
81
  }
79
82
  isInternalDependency(workspaceName, moduleSpecifier) {
80
83
  if (moduleSpecifier.startsWith('/') || moduleSpecifier.startsWith('.'))
@@ -104,15 +107,13 @@ export default class DependencyDeputy {
104
107
  continue;
105
108
  const referencedDependencies = this.referencedDependencies.get(workspaceName);
106
109
  const isUnreferencedDependency = (dependency) => {
110
+ if (referencedDependencies?.has(dependency))
111
+ return false;
107
112
  const [scope, typedDependency] = dependency.split('/');
108
113
  if (scope === '@types') {
109
114
  if (IGNORE_DEFINITELY_TYPED.includes(typedDependency))
110
115
  return false;
111
- if (referencedDependencies?.has(dependency))
112
- return false;
113
- const [scope, name] = typedDependency.split('__');
114
- const typedPackageName = scope && name ? `@${scope}/${name}` : typedDependency;
115
- return referencedDependencies ? !referencedDependencies.has(typedPackageName) : false;
116
+ return !referencedDependencies?.has(typedDependency);
116
117
  }
117
118
  if (!referencedDependencies?.has(dependency)) {
118
119
  const peerDependencies = Array.from(this.peerDependencies.get(workspaceName)?.get(dependency) ?? []);
package/dist/index.js CHANGED
@@ -7,8 +7,10 @@ import SourceLab from './source-lab.js';
7
7
  import { compact } from './util/array.js';
8
8
  import { ROOT_WORKSPACE_NAME } from './util/constants.js';
9
9
  import { debugLogObject, debugLogFiles } from './util/debug.js';
10
+ import { _findExternalImportModuleSpecifiers } from './util/externalImports.js';
10
11
  import { findFile, loadJSON } from './util/fs.js';
11
12
  import { _glob } from './util/glob.js';
13
+ import { _findDuplicateExportedNames } from './util/project.js';
12
14
  import { loadTSConfig } from './util/tsconfig-loader.js';
13
15
  import WorkspaceWorker from './workspace-worker.js';
14
16
  export const main = async (unresolvedConfiguration) => {
@@ -211,15 +213,23 @@ export const main = async (unresolvedConfiguration) => {
211
213
  const filePath = sourceFile.getFilePath();
212
214
  const workspaceDir = workspaceDirs.find(workspaceDir => filePath.startsWith(workspaceDir));
213
215
  const workspace = workspaces.find(workspace => workspace.dir === workspaceDir);
214
- const { externalModuleSpecifiers, issues } = lab.analyzeSourceFile(sourceFile);
215
- issues.forEach(issue => issue.type && collector.addIssue(issue));
216
- if (workspace) {
216
+ if (workspace && (report.dependencies || report.unlisted)) {
217
+ const externalModuleSpecifiers = _findExternalImportModuleSpecifiers(sourceFile);
217
218
  externalModuleSpecifiers.forEach(moduleSpecifier => {
218
219
  const unlistedDependency = deputy.maybeAddListedReferencedDependency(workspace, moduleSpecifier, isStrict);
219
220
  if (unlistedDependency)
220
221
  collector.addIssue({ type: 'unlisted', filePath, symbol: unlistedDependency });
221
222
  });
222
223
  }
224
+ if (report.duplicates) {
225
+ const duplicateExports = _findDuplicateExportedNames(sourceFile);
226
+ duplicateExports.forEach(symbols => {
227
+ const symbol = symbols.join('|');
228
+ collector.addIssue({ type: 'duplicates', filePath, symbol, symbols });
229
+ });
230
+ }
231
+ const issues = lab.analyzeSourceFile(sourceFile);
232
+ issues.forEach(issue => issue.type && collector.addIssue(issue));
223
233
  });
224
234
  collector.removeProgress();
225
235
  if (report.dependencies) {
@@ -8,6 +8,7 @@ export const isEnabled = ({ dependencies }) => {
8
8
  return dependencies.has('eslint');
9
9
  };
10
10
  export const CONFIG_FILE_PATTERNS = ['eslint.config.js', '.eslintrc', '.eslintrc.json', '.eslintrc.js'];
11
+ const SAMPLE_FILE_PATHS = ['__placeholder__.js', '__placeholder__.ts'];
11
12
  const resolvePluginPackageName = (pluginName) => {
12
13
  return pluginName.startsWith('@')
13
14
  ? pluginName.includes('/')
@@ -15,16 +16,15 @@ const resolvePluginPackageName = (pluginName) => {
15
16
  : `${pluginName}/eslint-plugin`
16
17
  : `eslint-plugin-${pluginName}`;
17
18
  };
18
- const findESLintDependencies = async (configFilePath, { cwd }) => {
19
+ const findESLintDependencies = async (configFilePath, { cwd, config }) => {
19
20
  if (path.basename(configFilePath) === 'eslint.config.js') {
20
21
  return [];
21
22
  }
22
23
  else {
23
24
  const engine = new ESLint({ cwd, overrideConfigFile: configFilePath, useEslintrc: false });
24
- const jsConfig = await engine.calculateConfigForFile('__placeholder__.js');
25
- const tsConfig = await engine.calculateConfigForFile('__placeholder__.ts');
26
- const tsxConfig = await engine.calculateConfigForFile('__placeholder__.spec.tsx');
27
- const dependencies = [jsConfig, tsConfig, tsxConfig].map(config => {
25
+ const calculateConfigForFile = async (sampleFile) => await engine.calculateConfigForFile(sampleFile);
26
+ const sampleFiles = config?.sampleFiles.length > 0 ? config.sampleFiles : SAMPLE_FILE_PATHS;
27
+ const dependencies = await Promise.all(sampleFiles.map(calculateConfigForFile)).then(configs => configs.flatMap(config => {
28
28
  if (!config)
29
29
  return [];
30
30
  const plugins = config.plugins?.map(resolvePluginPackageName) ?? [];
@@ -32,7 +32,7 @@ const findESLintDependencies = async (configFilePath, { cwd }) => {
32
32
  const extraParsers = config.parserOptions?.babelOptions?.presets ?? [];
33
33
  const settings = config.settings ? getDependenciesFromSettings(config.settings) : [];
34
34
  return [...parsers, ...extraParsers, ...plugins, ...settings].map(getPackageName);
35
- });
35
+ }));
36
36
  return compact(dependencies.flat());
37
37
  }
38
38
  };
@@ -1,3 +1,3 @@
1
1
  export const isEnabled = ({ dependencies }) => dependencies.has('rollup');
2
2
  export const CONFIG_FILE_PATTERNS = [];
3
- export const ENTRY_FILE_PATTERNS = ['rollup.config.{js,mjs}'];
3
+ export const ENTRY_FILE_PATTERNS = ['rollup.config.{js,mjs,ts}'];
@@ -13,10 +13,7 @@ export default class SourceLab {
13
13
  isReportTypes: boolean;
14
14
  constructor({ report, workspaceDirs }: FileLabOptions);
15
15
  skipExportsAnalysisFor(filePath: string | string[]): void;
16
- analyzeSourceFile(sourceFile: SourceFile): {
17
- externalModuleSpecifiers: string[];
18
- issues: Set<Issue>;
19
- };
16
+ analyzeSourceFile(sourceFile: SourceFile): Set<Issue>;
20
17
  private analyzeExports;
21
18
  }
22
19
  export {};
@@ -1,7 +1,6 @@
1
1
  import { ts, Node } from 'ts-morph';
2
- import { _findExternalImportModuleSpecifiers } from './util/externalImports.js';
3
2
  import { findUnusedClassMembers, findUnusedEnumMembers } from './util/members.js';
4
- import { _findDuplicateExportedNames, _hasReferencingDefaultImport, _findReferencingNamespaceNodes, _getExportedDeclarations, _findReferences, hasExternalReferences, } from './util/project.js';
3
+ import { _hasReferencingDefaultImport, _findReferencingNamespaceNodes, _getExportedDeclarations, _findReferences, hasExternalReferences, } from './util/project.js';
5
4
  import { getType } from './util/type.js';
6
5
  export default class SourceLab {
7
6
  report;
@@ -26,26 +25,14 @@ export default class SourceLab {
26
25
  }
27
26
  analyzeSourceFile(sourceFile) {
28
27
  const issues = new Set();
29
- const report = this.report;
30
28
  const filePath = sourceFile.getFilePath();
31
- let externalModuleSpecifiers = [];
32
- if (report.dependencies || report.unlisted) {
33
- externalModuleSpecifiers = _findExternalImportModuleSpecifiers(sourceFile);
34
- }
35
- if (report.duplicates) {
36
- const duplicateExports = _findDuplicateExportedNames(sourceFile);
37
- duplicateExports.forEach(symbols => {
38
- const symbol = symbols.join('|');
39
- issues.add({ type: 'duplicates', filePath, symbol, symbols });
40
- });
41
- }
42
29
  if (this.skipExportsAnalysis.has(filePath))
43
- return { externalModuleSpecifiers, issues };
30
+ return issues;
44
31
  if (this.isReportExports) {
45
32
  const exportsIssues = this.analyzeExports(sourceFile);
46
33
  exportsIssues.forEach(issue => issues.add(issue));
47
34
  }
48
- return { externalModuleSpecifiers, issues };
35
+ return issues;
49
36
  }
50
37
  analyzeExports(sourceFile) {
51
38
  const issues = new Set();
@@ -3,6 +3,7 @@ export interface PluginConfiguration {
3
3
  config: NormalizedGlob;
4
4
  entryFiles: NormalizedGlob;
5
5
  projectFiles: NormalizedGlob;
6
+ sampleFiles: NormalizedGlob;
6
7
  }
7
8
  interface PluginsConfiguration {
8
9
  babel: PluginConfiguration;
@@ -1,4 +1,5 @@
1
1
  import { PackageJson } from 'type-fest';
2
+ import { PluginConfiguration } from './config.js';
2
3
  type IsPluginEnabledCallbackOptions = {
3
4
  manifest: PackageJson;
4
5
  dependencies: Set<string>;
@@ -7,5 +8,6 @@ export type IsPluginEnabledCallback = (options: IsPluginEnabledCallbackOptions)
7
8
  export type GenericPluginCallback = (configFilePath: string, { cwd, manifest }: {
8
9
  cwd: string;
9
10
  manifest: PackageJson;
11
+ config: PluginConfiguration;
10
12
  }) => Promise<string[]> | string[];
11
13
  export {};
@@ -85,16 +85,19 @@ export declare const LocalConfiguration: z.ZodObject<z.extendShape<z.extendShape
85
85
  entryFiles: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
86
86
  productionEntryFiles: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
87
87
  projectFiles: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
88
+ sampleFiles: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
88
89
  }, "strip", z.ZodTypeAny, {
89
90
  config?: string | string[] | undefined;
90
91
  entryFiles?: string | string[] | undefined;
91
92
  projectFiles?: string | string[] | undefined;
92
93
  productionEntryFiles?: string | string[] | undefined;
94
+ sampleFiles?: string | string[] | undefined;
93
95
  }, {
94
96
  config?: string | string[] | undefined;
95
97
  entryFiles?: string | string[] | undefined;
96
98
  projectFiles?: string | string[] | undefined;
97
99
  productionEntryFiles?: string | string[] | undefined;
100
+ sampleFiles?: string | string[] | undefined;
98
101
  }>]>>;
99
102
  gatsby: z.ZodOptional<z.ZodUnion<[z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>, z.ZodObject<{
100
103
  config: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
@@ -290,6 +293,7 @@ export declare const LocalConfiguration: z.ZodObject<z.extendShape<z.extendShape
290
293
  entryFiles?: string | string[] | undefined;
291
294
  projectFiles?: string | string[] | undefined;
292
295
  productionEntryFiles?: string | string[] | undefined;
296
+ sampleFiles?: string | string[] | undefined;
293
297
  } | undefined;
294
298
  gatsby?: string | string[] | {
295
299
  config?: string | string[] | undefined;
@@ -385,6 +389,7 @@ export declare const LocalConfiguration: z.ZodObject<z.extendShape<z.extendShape
385
389
  entryFiles?: string | string[] | undefined;
386
390
  projectFiles?: string | string[] | undefined;
387
391
  productionEntryFiles?: string | string[] | undefined;
392
+ sampleFiles?: string | string[] | undefined;
388
393
  } | undefined;
389
394
  gatsby?: string | string[] | {
390
395
  config?: string | string[] | undefined;
@@ -517,16 +522,19 @@ export declare const LocalConfiguration: z.ZodObject<z.extendShape<z.extendShape
517
522
  entryFiles: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
518
523
  productionEntryFiles: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
519
524
  projectFiles: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
525
+ sampleFiles: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
520
526
  }, "strip", z.ZodTypeAny, {
521
527
  config?: string | string[] | undefined;
522
528
  entryFiles?: string | string[] | undefined;
523
529
  projectFiles?: string | string[] | undefined;
524
530
  productionEntryFiles?: string | string[] | undefined;
531
+ sampleFiles?: string | string[] | undefined;
525
532
  }, {
526
533
  config?: string | string[] | undefined;
527
534
  entryFiles?: string | string[] | undefined;
528
535
  projectFiles?: string | string[] | undefined;
529
536
  productionEntryFiles?: string | string[] | undefined;
537
+ sampleFiles?: string | string[] | undefined;
530
538
  }>]>>;
531
539
  gatsby: z.ZodOptional<z.ZodUnion<[z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>, z.ZodObject<{
532
540
  config: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
@@ -726,6 +734,7 @@ export declare const LocalConfiguration: z.ZodObject<z.extendShape<z.extendShape
726
734
  entryFiles?: string | string[] | undefined;
727
735
  projectFiles?: string | string[] | undefined;
728
736
  productionEntryFiles?: string | string[] | undefined;
737
+ sampleFiles?: string | string[] | undefined;
729
738
  } | undefined;
730
739
  gatsby?: string | string[] | {
731
740
  config?: string | string[] | undefined;
@@ -821,6 +830,7 @@ export declare const LocalConfiguration: z.ZodObject<z.extendShape<z.extendShape
821
830
  entryFiles?: string | string[] | undefined;
822
831
  projectFiles?: string | string[] | undefined;
823
832
  productionEntryFiles?: string | string[] | undefined;
833
+ sampleFiles?: string | string[] | undefined;
824
834
  } | undefined;
825
835
  gatsby?: string | string[] | {
826
836
  config?: string | string[] | undefined;
@@ -921,6 +931,7 @@ export declare const LocalConfiguration: z.ZodObject<z.extendShape<z.extendShape
921
931
  entryFiles?: string | string[] | undefined;
922
932
  projectFiles?: string | string[] | undefined;
923
933
  productionEntryFiles?: string | string[] | undefined;
934
+ sampleFiles?: string | string[] | undefined;
924
935
  } | undefined;
925
936
  gatsby?: string | string[] | {
926
937
  config?: string | string[] | undefined;
@@ -1016,6 +1027,7 @@ export declare const LocalConfiguration: z.ZodObject<z.extendShape<z.extendShape
1016
1027
  entryFiles?: string | string[] | undefined;
1017
1028
  projectFiles?: string | string[] | undefined;
1018
1029
  productionEntryFiles?: string | string[] | undefined;
1030
+ sampleFiles?: string | string[] | undefined;
1019
1031
  } | undefined;
1020
1032
  gatsby?: string | string[] | {
1021
1033
  config?: string | string[] | undefined;
@@ -21,12 +21,22 @@ const pluginWithEntryFilesSchema = z.union([
21
21
  projectFiles: globSchema.optional(),
22
22
  }),
23
23
  ]);
24
+ const pluginWithSampleFilesSchema = z.union([
25
+ globSchema,
26
+ z.object({
27
+ config: globSchema.optional(),
28
+ entryFiles: globSchema.optional(),
29
+ productionEntryFiles: globSchema.optional(),
30
+ projectFiles: globSchema.optional(),
31
+ sampleFiles: globSchema.optional(),
32
+ }),
33
+ ]);
24
34
  const pluginsConfigurationSchema = z.object({
25
35
  babel: pluginWithEntryFilesSchema,
26
36
  capacitor: pluginWithEntryFilesSchema,
27
37
  changesets: pluginWithEntryFilesSchema,
28
38
  cypress: pluginWithEntryFilesSchema,
29
- eslint: pluginWithEntryFilesSchema,
39
+ eslint: pluginWithSampleFilesSchema,
30
40
  gatsby: pluginWithEntryFilesSchema,
31
41
  jest: pluginWithEntryFilesSchema,
32
42
  next: pluginWithEntryFilesSchema,
@@ -1,4 +1,4 @@
1
1
  import { MethodDeclaration, PropertyDeclaration } from 'ts-morph';
2
2
  import type { ClassDeclaration, EnumDeclaration } from 'ts-morph';
3
- export declare const findUnusedClassMembers: (declaration: ClassDeclaration, filePath: string) => (PropertyDeclaration | MethodDeclaration)[];
3
+ export declare const findUnusedClassMembers: (declaration: ClassDeclaration, filePath: string) => (MethodDeclaration | PropertyDeclaration)[];
4
4
  export declare const findUnusedEnumMembers: (declaration: EnumDeclaration, filePath: string) => import("ts-morph").EnumMember[];
@@ -1 +1,3 @@
1
1
  export declare const getPackageName: (value: string) => string;
2
+ export declare const isDefinitelyTyped: (packageName: string) => boolean;
3
+ export declare const getDefinitelyTypedPackage: (packageName: string) => string;
@@ -8,3 +8,9 @@ export const getPackageName = (value) => {
8
8
  }
9
9
  return value.startsWith('/') ? value : value.split('/')[0];
10
10
  };
11
+ export const isDefinitelyTyped = (packageName) => packageName.startsWith('@types/');
12
+ export const getDefinitelyTypedPackage = (packageName) => {
13
+ if (isDefinitelyTyped(packageName))
14
+ return packageName;
15
+ return '@types/' + packageName.replace('@', '__');
16
+ };
@@ -51,7 +51,7 @@ export default class WorkspaceWorker {
51
51
  }
52
52
  getConfigForPlugin(pluginName) {
53
53
  return (this.config[pluginName] ??
54
- this.rootWorkspaceConfig[pluginName] ?? { config: [], entryFiles: [], projectFiles: [] });
54
+ this.rootWorkspaceConfig[pluginName] ?? { config: [], entryFiles: [], projectFiles: [], sampleFiles: [] });
55
55
  }
56
56
  async init() {
57
57
  this.setEnabledPlugins();
@@ -212,11 +212,16 @@ export default class WorkspaceWorker {
212
212
  const cwd = this.dir;
213
213
  const ignore = this.getWorkspaceIgnorePatterns();
214
214
  const configFilePaths = await _pureGlob({ patterns, cwd, ignore });
215
+ const pluginConfig = this.getConfigForPlugin(pluginName);
215
216
  debugLogFiles(1, `Globbed ${pluginName} config file paths`, configFilePaths);
216
217
  if (configFilePaths.length === 0)
217
218
  return [];
218
219
  const referencedDependencyIssues = (await Promise.all(configFilePaths.map(async (configFilePath) => {
219
- const dependencies = await pluginCallback(configFilePath, { cwd, manifest: this.manifest });
220
+ const dependencies = await pluginCallback(configFilePath, {
221
+ cwd,
222
+ manifest: this.manifest,
223
+ config: pluginConfig,
224
+ });
220
225
  return dependencies.map(symbol => ({ type: 'unlisted', filePath: configFilePath, symbol }));
221
226
  }))).flat();
222
227
  debugLogIssues(1, `Dependencies used by ${pluginName} configuration`, referencedDependencyIssues);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.2",
4
4
  "description": "Find unused files, dependencies and exports in your TypeScript and JavaScript project",
5
5
  "keywords": [
6
6
  "find",