knip 5.3.1 → 5.5.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.
@@ -1,5 +1,5 @@
1
1
  import { createPkgGraph } from '@pnpm/workspace.pkgs-graph';
2
- import type { Configuration, WorkspaceConfiguration } from './types/config.js';
2
+ import type { Configuration, WorkspaceConfiguration, IgnorePatterns } from './types/config.js';
3
3
  import type { PackageJson } from './types/package-json.js';
4
4
  type ConfigurationManagerOptions = {
5
5
  cwd: string;
@@ -14,6 +14,7 @@ export type Workspace = {
14
14
  ancestors: string[];
15
15
  config: WorkspaceConfiguration;
16
16
  manifestPath: string;
17
+ ignoreMembers: IgnorePatterns;
17
18
  };
18
19
  export declare class ConfigurationChief {
19
20
  cwd: string;
@@ -63,6 +64,7 @@ export declare class ConfigurationChief {
63
64
  private getIgnoredWorkspacesFor;
64
65
  getNegatedWorkspacePatterns(name: string): string[];
65
66
  private getConfigKeyForWorkspace;
67
+ getWorkspaceConfig(workspaceName: string): any;
66
68
  getIgnores(workspaceName: string): {
67
69
  ignoreBinaries: (string | RegExp)[];
68
70
  ignoreDependencies: (string | RegExp)[];
@@ -37,6 +37,7 @@ const defaultConfig = {
37
37
  ignore: [],
38
38
  ignoreBinaries: [],
39
39
  ignoreDependencies: [],
40
+ ignoreMembers: [],
40
41
  ignoreExportsUsedInFile: false,
41
42
  ignoreWorkspaces: [],
42
43
  isIncludeEntryExports: false,
@@ -123,6 +124,7 @@ export class ConfigurationChief {
123
124
  const ignore = arrayify(rawConfig.ignore ?? defaultConfig.ignore);
124
125
  const ignoreBinaries = (rawConfig.ignoreBinaries ?? []).map(toRegexOrString);
125
126
  const ignoreDependencies = (rawConfig.ignoreDependencies ?? []).map(toRegexOrString);
127
+ const ignoreMembers = (rawConfig.ignoreMembers ?? []).map(toRegexOrString);
126
128
  const ignoreExportsUsedInFile = rawConfig.ignoreExportsUsedInFile ?? false;
127
129
  const ignoreWorkspaces = rawConfig.ignoreWorkspaces ?? defaultConfig.ignoreWorkspaces;
128
130
  const isIncludeEntryExports = rawConfig.includeEntryExports ?? this.isIncludeEntryExports;
@@ -141,6 +143,7 @@ export class ConfigurationChief {
141
143
  ignore,
142
144
  ignoreBinaries,
143
145
  ignoreDependencies,
146
+ ignoreMembers,
144
147
  ignoreExportsUsedInFile,
145
148
  ignoreWorkspaces,
146
149
  isIncludeEntryExports,
@@ -271,6 +274,8 @@ export class ConfigurationChief {
271
274
  .map((name) => {
272
275
  const dir = join(this.cwd, name);
273
276
  const pkgName = this.availableWorkspaceManifests.find(p => p.dir === dir)?.manifest.name ?? `NOT_FOUND_${name}`;
277
+ const workspaceConfig = this.getWorkspaceConfig(name);
278
+ const ignoreMembers = arrayify(workspaceConfig.ignoreMembers).map(toRegexOrString);
274
279
  return {
275
280
  name,
276
281
  pkgName,
@@ -278,6 +283,7 @@ export class ConfigurationChief {
278
283
  config: this.getConfigForWorkspace(name),
279
284
  ancestors: this.availableWorkspaceNames.reduce(getAncestors(name), []),
280
285
  manifestPath: join(dir, 'package.json'),
286
+ ignoreMembers,
281
287
  };
282
288
  });
283
289
  }
@@ -311,27 +317,24 @@ export class ConfigurationChief {
311
317
  .reverse()
312
318
  .find(pattern => micromatch.isMatch(workspaceName, pattern));
313
319
  }
314
- getIgnores(workspaceName) {
320
+ getWorkspaceConfig(workspaceName) {
315
321
  const key = this.getConfigKeyForWorkspace(workspaceName);
316
322
  const workspaces = this.rawConfig?.workspaces ?? {};
317
- const workspaceConfig = (key
323
+ return ((key
318
324
  ? key === ROOT_WORKSPACE_NAME && !(ROOT_WORKSPACE_NAME in workspaces)
319
325
  ? this.rawConfig
320
326
  : workspaces[key]
321
- : {}) ?? {};
327
+ : {}) ?? {});
328
+ }
329
+ getIgnores(workspaceName) {
330
+ const workspaceConfig = this.getWorkspaceConfig(workspaceName);
322
331
  const ignoreBinaries = arrayify(workspaceConfig.ignoreBinaries).map(toRegexOrString);
323
332
  const ignoreDependencies = arrayify(workspaceConfig.ignoreDependencies).map(toRegexOrString);
324
333
  return { ignoreBinaries, ignoreDependencies };
325
334
  }
326
335
  getConfigForWorkspace(workspaceName, extensions) {
327
336
  const baseConfig = getDefaultWorkspaceConfig(extensions);
328
- const key = this.getConfigKeyForWorkspace(workspaceName);
329
- const workspaces = this.rawConfig?.workspaces ?? {};
330
- const workspaceConfig = (key
331
- ? key === ROOT_WORKSPACE_NAME && !(ROOT_WORKSPACE_NAME in workspaces)
332
- ? this.rawConfig
333
- : workspaces[key]
334
- : {}) ?? {};
337
+ const workspaceConfig = this.getWorkspaceConfig(workspaceName);
335
338
  const entry = workspaceConfig.entry ? arrayify(workspaceConfig.entry) : baseConfig.entry;
336
339
  const project = workspaceConfig.project ? arrayify(workspaceConfig.project) : baseConfig.project;
337
340
  const paths = workspaceConfig.paths ?? {};
@@ -21,6 +21,7 @@ export declare const ConfigurationValidator: z.ZodObject<{
21
21
  ignore: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
22
22
  ignoreBinaries: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodType<RegExp, z.ZodTypeDef, RegExp>]>, "many">>;
23
23
  ignoreDependencies: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodType<RegExp, z.ZodTypeDef, RegExp>]>, "many">>;
24
+ ignoreMembers: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodType<RegExp, z.ZodTypeDef, RegExp>]>, "many">>;
24
25
  ignoreExportsUsedInFile: z.ZodOptional<z.ZodUnion<[z.ZodBoolean, z.ZodRecord<z.ZodUnion<[z.ZodLiteral<"class">, z.ZodLiteral<"enum">, z.ZodLiteral<"function">, z.ZodLiteral<"interface">, z.ZodLiteral<"member">, z.ZodLiteral<"type">]>, z.ZodBoolean>]>>;
25
26
  ignoreWorkspaces: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
26
27
  includeEntryExports: z.ZodOptional<z.ZodBoolean>;
@@ -35,6 +36,7 @@ export declare const ConfigurationValidator: z.ZodObject<{
35
36
  ignore: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
36
37
  ignoreBinaries: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodType<RegExp, z.ZodTypeDef, RegExp>]>, "many">>;
37
38
  ignoreDependencies: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodType<RegExp, z.ZodTypeDef, RegExp>]>, "many">>;
39
+ ignoreMembers: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodType<RegExp, z.ZodTypeDef, RegExp>]>, "many">>;
38
40
  includeEntryExports: z.ZodOptional<z.ZodBoolean>;
39
41
  astro: z.ZodOptional<z.ZodUnion<[z.ZodBoolean, z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>, z.ZodObject<{
40
42
  config: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
@@ -758,6 +760,7 @@ export declare const ConfigurationValidator: z.ZodObject<{
758
760
  ignore?: string | string[] | undefined;
759
761
  ignoreBinaries?: (string | RegExp)[] | undefined;
760
762
  ignoreDependencies?: (string | RegExp)[] | undefined;
763
+ ignoreMembers?: (string | RegExp)[] | undefined;
761
764
  includeEntryExports?: boolean | undefined;
762
765
  astro?: string | boolean | string[] | {
763
766
  config?: string | string[] | undefined;
@@ -1041,6 +1044,7 @@ export declare const ConfigurationValidator: z.ZodObject<{
1041
1044
  ignore?: string | string[] | undefined;
1042
1045
  ignoreBinaries?: (string | RegExp)[] | undefined;
1043
1046
  ignoreDependencies?: (string | RegExp)[] | undefined;
1047
+ ignoreMembers?: (string | RegExp)[] | undefined;
1044
1048
  includeEntryExports?: boolean | undefined;
1045
1049
  astro?: string | boolean | string[] | {
1046
1050
  config?: string | string[] | undefined;
@@ -2042,6 +2046,7 @@ export declare const ConfigurationValidator: z.ZodObject<{
2042
2046
  ignore?: string | string[] | undefined;
2043
2047
  ignoreBinaries?: (string | RegExp)[] | undefined;
2044
2048
  ignoreDependencies?: (string | RegExp)[] | undefined;
2049
+ ignoreMembers?: (string | RegExp)[] | undefined;
2045
2050
  ignoreExportsUsedInFile?: boolean | Partial<Record<"function" | "type" | "enum" | "class" | "interface" | "member", boolean>> | undefined;
2046
2051
  ignoreWorkspaces?: string[] | undefined;
2047
2052
  includeEntryExports?: boolean | undefined;
@@ -2056,6 +2061,7 @@ export declare const ConfigurationValidator: z.ZodObject<{
2056
2061
  ignore?: string | string[] | undefined;
2057
2062
  ignoreBinaries?: (string | RegExp)[] | undefined;
2058
2063
  ignoreDependencies?: (string | RegExp)[] | undefined;
2064
+ ignoreMembers?: (string | RegExp)[] | undefined;
2059
2065
  includeEntryExports?: boolean | undefined;
2060
2066
  astro?: string | boolean | string[] | {
2061
2067
  config?: string | string[] | undefined;
@@ -2617,6 +2623,7 @@ export declare const ConfigurationValidator: z.ZodObject<{
2617
2623
  ignore?: string | string[] | undefined;
2618
2624
  ignoreBinaries?: (string | RegExp)[] | undefined;
2619
2625
  ignoreDependencies?: (string | RegExp)[] | undefined;
2626
+ ignoreMembers?: (string | RegExp)[] | undefined;
2620
2627
  ignoreExportsUsedInFile?: boolean | Partial<Record<"function" | "type" | "enum" | "class" | "interface" | "member", boolean>> | undefined;
2621
2628
  ignoreWorkspaces?: string[] | undefined;
2622
2629
  includeEntryExports?: boolean | undefined;
@@ -2631,6 +2638,7 @@ export declare const ConfigurationValidator: z.ZodObject<{
2631
2638
  ignore?: string | string[] | undefined;
2632
2639
  ignoreBinaries?: (string | RegExp)[] | undefined;
2633
2640
  ignoreDependencies?: (string | RegExp)[] | undefined;
2641
+ ignoreMembers?: (string | RegExp)[] | undefined;
2634
2642
  includeEntryExports?: boolean | undefined;
2635
2643
  astro?: string | boolean | string[] | {
2636
2644
  config?: string | string[] | undefined;
@@ -41,6 +41,7 @@ const rootConfigurationSchema = z.object({
41
41
  ignore: globSchema.optional(),
42
42
  ignoreBinaries: stringOrRegexSchema.optional(),
43
43
  ignoreDependencies: stringOrRegexSchema.optional(),
44
+ ignoreMembers: stringOrRegexSchema.optional(),
44
45
  ignoreExportsUsedInFile: ignoreExportsUsedInFileSchema.optional(),
45
46
  ignoreWorkspaces: z.array(z.string()).optional(),
46
47
  includeEntryExports: z.boolean().optional(),
@@ -125,6 +126,7 @@ const baseWorkspaceConfigurationSchema = z.object({
125
126
  ignore: globSchema.optional(),
126
127
  ignoreBinaries: stringOrRegexSchema.optional(),
127
128
  ignoreDependencies: stringOrRegexSchema.optional(),
129
+ ignoreMembers: stringOrRegexSchema.optional(),
128
130
  includeEntryExports: z.boolean().optional(),
129
131
  });
130
132
  const workspaceConfigurationSchema = baseWorkspaceConfigurationSchema.merge(pluginsSchema.partial());
@@ -12,6 +12,7 @@ export declare const partitionCompilers: (rawLocalConfig: RawConfiguration) => {
12
12
  ignore?: string | string[] | undefined;
13
13
  ignoreBinaries?: (string | RegExp)[] | undefined;
14
14
  ignoreDependencies?: (string | RegExp)[] | undefined;
15
+ ignoreMembers?: (string | RegExp)[] | undefined;
15
16
  ignoreExportsUsedInFile?: boolean | Partial<Record<"function" | "type" | "enum" | "class" | "interface" | "member", boolean>> | undefined;
16
17
  ignoreWorkspaces?: string[] | undefined;
17
18
  includeEntryExports?: boolean | undefined;
@@ -24,6 +25,7 @@ export declare const partitionCompilers: (rawLocalConfig: RawConfiguration) => {
24
25
  ignore?: string | string[] | undefined;
25
26
  ignoreBinaries?: (string | RegExp)[] | undefined;
26
27
  ignoreDependencies?: (string | RegExp)[] | undefined;
28
+ ignoreMembers?: (string | RegExp)[] | undefined;
27
29
  includeEntryExports?: boolean | undefined;
28
30
  astro?: string | boolean | string[] | {
29
31
  config?: string | string[] | undefined;
package/dist/index.js CHANGED
@@ -8,12 +8,15 @@ import { IssueFixer } from './IssueFixer.js';
8
8
  import { getFilteredScripts } from './manifest/helpers.js';
9
9
  import { PrincipalFactory } from './PrincipalFactory.js';
10
10
  import { ProjectPrincipal } from './ProjectPrincipal.js';
11
- import { debugLogObject, debugLogArray, debugLog, exportLookupLog } from './util/debug.js';
11
+ import { debugLogObject, debugLogArray, debugLog } from './util/debug.js';
12
+ import { getReExportingEntryFileHandler } from './util/get-reexporting-entry-file.js';
12
13
  import { _glob, negate } from './util/glob.js';
13
14
  import { getGitIgnoredFn } from './util/globby.js';
14
- import { getHandler } from './util/handleReferencedDependency.js';
15
+ import { getHandler } from './util/handle-dependency.js';
16
+ import { getIsIdentifierReferencedHandler } from './util/is-identifier-referenced.js';
15
17
  import { getEntryPathFromManifest, getPackageNameFromModuleSpecifier } from './util/modules.js';
16
18
  import { dirname, join } from './util/path.js';
19
+ import { hasMatch } from './util/regex.js';
17
20
  import { shouldIgnore } from './util/tag.js';
18
21
  import { loadTSConfig } from './util/tsconfig-loader.js';
19
22
  import { getType, getHasStrictlyNsReferences } from './util/type.js';
@@ -167,7 +170,6 @@ export const main = async (unresolvedConfiguration) => {
167
170
  const importedSymbols = {};
168
171
  const unreferencedFiles = new Set();
169
172
  const entryPaths = new Set();
170
- const exportedClasses = new Set();
171
173
  for (const principal of principals) {
172
174
  principal.init();
173
175
  const handleReferencedDependency = getHandler(collector, deputy, chief);
@@ -273,96 +275,11 @@ export const main = async (unresolvedConfiguration) => {
273
275
  });
274
276
  principal.getUnreferencedFiles().forEach(filePath => unreferencedFiles.add(filePath));
275
277
  principal.entryPaths.forEach(filePath => entryPaths.add(filePath));
276
- if (!isReportClassMembers)
278
+ if (isSkipLibs)
277
279
  factory.deletePrincipal(principal);
278
280
  }
279
- const isIdentifierReferenced = (filePath, id, importsForExport, depth = 0) => {
280
- if (!importsForExport) {
281
- exportLookupLog(depth, `no imports found from`, filePath);
282
- return false;
283
- }
284
- if (importsForExport.identifiers.has(id)) {
285
- exportLookupLog(depth, `imported from`, filePath);
286
- return true;
287
- }
288
- for (const ns of importsForExport.importedNs) {
289
- if (importsForExport.identifiers.has(`${ns}.${id}`)) {
290
- exportLookupLog(depth, `imported on ${ns} from`, filePath);
291
- return true;
292
- }
293
- }
294
- if (importsForExport.isReExport) {
295
- for (const filePath of importsForExport.isReExportedBy) {
296
- if (isIdentifierReferenced(filePath, id, importedSymbols[filePath], depth + 1)) {
297
- exportLookupLog(depth, `re-exported by`, filePath);
298
- return true;
299
- }
300
- }
301
- for (const [filePath, alias] of importsForExport.isReExportedAs) {
302
- if (isIdentifierReferenced(filePath, alias, importedSymbols[filePath], depth + 1)) {
303
- exportLookupLog(depth, `re-exported as ${alias} by`, filePath);
304
- return true;
305
- }
306
- }
307
- for (const [filePath, ns] of importsForExport.isReExportedNs) {
308
- if (isIdentifierReferenced(filePath, `${ns}.${id}`, importedSymbols[filePath], depth + 1)) {
309
- exportLookupLog(depth, `re-exported on ${ns} by`, filePath);
310
- return true;
311
- }
312
- }
313
- }
314
- exportLookupLog(depth, `not imported from`, filePath);
315
- return false;
316
- };
317
- const getReExportingEntryFile = (importedModule, id, depth = 0) => {
318
- if (!importedModule)
319
- return undefined;
320
- if (importedModule.isReExport) {
321
- for (const filePath of importedModule.isReExportedBy) {
322
- if (entryPaths.has(filePath)) {
323
- if (filePath in exportedSymbols && id in exportedSymbols[filePath]) {
324
- exportLookupLog(depth, `re-exported by entry`, filePath);
325
- return filePath;
326
- }
327
- else if (importedModule.hasStar) {
328
- exportLookupLog(depth, `re-exported (*) by entry`, filePath);
329
- return filePath;
330
- }
331
- }
332
- else {
333
- exportLookupLog(depth, `re-exported by`, filePath);
334
- const file = getReExportingEntryFile(importedSymbols[filePath], id, depth + 1);
335
- if (file)
336
- return file;
337
- }
338
- }
339
- for (const [filePath, namespace] of importedModule.isReExportedNs) {
340
- if (entryPaths.has(filePath)) {
341
- exportLookupLog(depth, `re-exported on ${namespace} by entry`, filePath);
342
- return filePath;
343
- }
344
- else {
345
- exportLookupLog(depth, `re-exported on ${namespace} by`, filePath);
346
- const file = getReExportingEntryFile(importedSymbols[filePath], namespace, depth + 1);
347
- if (file)
348
- return file;
349
- }
350
- }
351
- for (const [filePath, alias] of importedModule.isReExportedAs) {
352
- if (entryPaths.has(filePath)) {
353
- exportLookupLog(depth, `re-exported as ${alias} by entry`, filePath);
354
- return filePath;
355
- }
356
- else {
357
- exportLookupLog(depth, `re-exported as ${alias} by`, filePath);
358
- const file = getReExportingEntryFile(importedSymbols[filePath], alias, depth + 1);
359
- if (file)
360
- return file;
361
- }
362
- }
363
- }
364
- exportLookupLog(depth, `${id} is not re-exported by entry file`, '');
365
- };
281
+ const isIdentifierReferenced = getIsIdentifierReferencedHandler(importedSymbols);
282
+ const getReExportingEntryFile = getReExportingEntryFileHandler(entryPaths, exportedSymbols, importedSymbols);
366
283
  const isExportedItemReferenced = (exportedItem) => {
367
284
  return (exportedItem.refs > 0 &&
368
285
  (typeof chief.config.ignoreExportsUsedInFile === 'object'
@@ -373,6 +290,7 @@ export const main = async (unresolvedConfiguration) => {
373
290
  streamer.cast('Analyzing source files...');
374
291
  for (const [filePath, exportItems] of Object.entries(exportedSymbols)) {
375
292
  const workspace = chief.findWorkspaceByFilePath(filePath);
293
+ const principal = workspace && factory.getPrincipalByPackageName(workspace.pkgName);
376
294
  if (workspace) {
377
295
  const { isIncludeEntryExports } = workspace.config;
378
296
  if (!isIncludeEntryExports && entryPaths.has(filePath))
@@ -389,18 +307,17 @@ export const main = async (unresolvedConfiguration) => {
389
307
  continue;
390
308
  if (importsForExport) {
391
309
  if (!isIncludeEntryExports) {
392
- exportLookupLog(-1, `Looking up re-exporting file for ${identifier} from`, filePath);
393
- if (getReExportingEntryFile(importsForExport, identifier))
310
+ if (getReExportingEntryFile(importsForExport, identifier, 0, filePath))
394
311
  continue;
395
312
  }
396
- exportLookupLog(-1, `Looking up ${identifier} export from`, filePath);
397
313
  if (isIdentifierReferenced(filePath, identifier, importsForExport)) {
398
- if (exportedItem.type === 'enum') {
314
+ if (report.enumMembers && exportedItem.type === 'enum') {
399
315
  exportedItem.members?.forEach(member => {
316
+ if (hasMatch(workspace.ignoreMembers, member.identifier))
317
+ return;
400
318
  if (shouldIgnore(member.jsDocTags, tags))
401
319
  return;
402
320
  if (member.refs === 0) {
403
- exportLookupLog(-1, `Looking up export member ${identifier}.${member.identifier} from`, filePath);
404
321
  if (!isIdentifierReferenced(filePath, `${identifier}.${member.identifier}`, importsForExport)) {
405
322
  collector.addIssue({
406
323
  type: 'enumMembers',
@@ -416,7 +333,18 @@ export const main = async (unresolvedConfiguration) => {
416
333
  });
417
334
  }
418
335
  if (isReportClassMembers && exportedItem.type === 'class') {
419
- exportedClasses.add([filePath, exportedItem]);
336
+ const members = exportedItem.members.filter(member => !hasMatch(workspace.ignoreMembers, member.identifier) && !shouldIgnore(member.jsDocTags, tags));
337
+ principal?.findUnusedMembers(filePath, members).forEach(member => {
338
+ collector.addIssue({
339
+ type: 'classMembers',
340
+ filePath,
341
+ symbol: member.identifier,
342
+ parentSymbol: exportedItem.identifier,
343
+ pos: member.pos,
344
+ line: member.line,
345
+ col: member.col,
346
+ });
347
+ });
420
348
  }
421
349
  continue;
422
350
  }
@@ -426,11 +354,8 @@ export const main = async (unresolvedConfiguration) => {
426
354
  if (hasStrictlyNsReferences && ((!report.nsTypes && isType) || (!report.nsExports && !isType)))
427
355
  continue;
428
356
  if (!isExportedItemReferenced(exportedItem)) {
429
- if (!isSkipLibs) {
430
- const principal = factory.getPrincipalByPackageName(workspace.pkgName);
431
- if (principal?.hasReferences(filePath, exportedItem))
432
- continue;
433
- }
357
+ if (!isSkipLibs && principal?.hasReferences(filePath, exportedItem))
358
+ continue;
434
359
  const type = getType(hasStrictlyNsReferences, isType);
435
360
  collector.addIssue({
436
361
  type,
@@ -466,27 +391,6 @@ export const main = async (unresolvedConfiguration) => {
466
391
  const unusedIgnoredWorkspaces = chief.getUnusedIgnoredWorkspaces();
467
392
  unusedIgnoredWorkspaces.forEach(identifier => collector.addConfigurationHint({ type: 'ignoreWorkspaces', identifier }));
468
393
  const { issues, counters, configurationHints } = collector.getIssues();
469
- if (isReportClassMembers) {
470
- streamer.cast('Validating expensive classy memberships...');
471
- for (const [filePath, exportedItem] of exportedClasses) {
472
- const workspace = chief.findWorkspaceByFilePath(filePath);
473
- const principal = workspace && factory.getPrincipalByPackageName(workspace.pkgName);
474
- if (principal) {
475
- const members = exportedItem.members.filter(member => !shouldIgnore(member.jsDocTags, tags));
476
- principal.findUnusedMembers(filePath, members).forEach(member => {
477
- collector.addIssue({
478
- type: 'classMembers',
479
- filePath,
480
- symbol: member.identifier,
481
- parentSymbol: exportedItem.identifier,
482
- pos: member.pos,
483
- line: member.line,
484
- col: member.col,
485
- });
486
- });
487
- }
488
- }
489
- }
490
394
  if (isFix) {
491
395
  await fixer.fixIssues(issues);
492
396
  }
@@ -1,7 +1,7 @@
1
1
  import { isDefinitelyTyped } from '../util/modules.js';
2
2
  import { timerify } from '../util/Performance.js';
3
3
  import { loadPackageManifest } from './helpers.js';
4
- const _getDependencyMetaData = ({ cwd, dir, packageNames }) => {
4
+ const getMetaDataFromPackageJson = ({ cwd, dir, packageNames }) => {
5
5
  const hostDependencies = new Map();
6
6
  const installedBinaries = new Map();
7
7
  const hasTypesIncluded = new Set();
@@ -47,4 +47,4 @@ const _getDependencyMetaData = ({ cwd, dir, packageNames }) => {
47
47
  hasTypesIncluded,
48
48
  };
49
49
  };
50
- export const getDependencyMetaData = timerify(_getDependencyMetaData);
50
+ export const getDependencyMetaData = timerify(getMetaDataFromPackageJson);
@@ -34,6 +34,7 @@ export interface Configuration {
34
34
  ignoreBinaries: IgnorePatterns;
35
35
  ignoreDependencies: IgnorePatterns;
36
36
  ignoreExportsUsedInFile: boolean | Partial<Record<IgnorableExport, boolean>>;
37
+ ignoreMembers: IgnorePatterns;
37
38
  ignoreWorkspaces: string[];
38
39
  isIncludeEntryExports: boolean;
39
40
  syncCompilers: SyncCompilers;
@@ -5,7 +5,15 @@ type SymbolWithExports = ts.Symbol & {
5
5
  };
6
6
  type PragmaMap = {
7
7
  arguments: {
8
- factory: string;
8
+ factory?: string;
9
+ path?: {
10
+ value?: string;
11
+ pos?: number;
12
+ };
13
+ types?: {
14
+ value?: string;
15
+ pos?: number;
16
+ };
9
17
  };
10
18
  };
11
19
  export interface BoundSourceFile extends ts.SourceFile {
@@ -7,7 +7,7 @@ import { shouldIgnore } from '../util/tag.js';
7
7
  import { isAccessExpression, getJSDocTags, getLineAndCharacterOfPosition, getMemberStringLiterals, } from './ast-helpers.js';
8
8
  import getDynamicImportVisitors from './visitors/dynamic-imports/index.js';
9
9
  import getExportVisitors from './visitors/exports/index.js';
10
- import { getJSXImplicitImportBase } from './visitors/helpers.js';
10
+ import { getImportsFromPragmas } from './visitors/helpers.js';
11
11
  import getImportVisitors from './visitors/imports/index.js';
12
12
  import getScriptVisitors from './visitors/scripts/index.js';
13
13
  const getVisitors = (sourceFile) => ({
@@ -39,9 +39,6 @@ const getImportsAndExports = (sourceFile, getResolvedModule, typeChecker, option
39
39
  const aliasedExports = new Map();
40
40
  const scripts = new Set();
41
41
  const importedInternalSymbols = new Map();
42
- const jsxImport = getJSXImplicitImportBase(sourceFile);
43
- if (jsxImport)
44
- externalImports.add(jsxImport);
45
42
  const visitors = getVisitors(sourceFile);
46
43
  const addInternalImport = (options) => {
47
44
  const { identifier, specifier, symbol, filePath, namespace, isReExport } = options;
@@ -288,6 +285,9 @@ const getImportsAndExports = (sourceFile, getResolvedModule, typeChecker, option
288
285
  ts.forEachChild(node, visit);
289
286
  };
290
287
  visit(sourceFile);
288
+ const pragmaImports = getImportsFromPragmas(sourceFile);
289
+ if (pragmaImports)
290
+ pragmaImports.forEach(node => addImport(node, sourceFile));
291
291
  const setRefs = (item) => {
292
292
  if (!item.symbol)
293
293
  return;
@@ -42,7 +42,7 @@ export function createCustomModuleResolver(customSys, compilerOptions, virtualFi
42
42
  const resolvedFileName = tsResolvedModule.resolvedFileName.replace(/\.d\.mts$/, '.mjs');
43
43
  return { resolvedFileName, extension: '.mjs', isExternalLibraryImport: false, resolvedUsingTsExtension: false };
44
44
  }
45
- if (tsResolvedModule.extension === '.d.cts') {
45
+ else if (tsResolvedModule.extension === '.d.cts') {
46
46
  const resolvedFileName = tsResolvedModule.resolvedFileName.replace(/\.d\.cts$/, '.cjs');
47
47
  return { resolvedFileName, extension: '.cjs', isExternalLibraryImport: false, resolvedUsingTsExtension: false };
48
48
  }
@@ -1,9 +1,9 @@
1
1
  import ts from 'typescript';
2
2
  import { importVisitor as visit } from '../index.js';
3
3
  export default visit(() => true, node => {
4
- if (ts.isImportTypeNode(node) && node.isTypeOf) {
4
+ if (ts.isImportTypeNode(node)) {
5
5
  if (ts.isLiteralTypeNode(node.argument) && ts.isStringLiteral(node.argument.literal)) {
6
- return { specifier: node.argument.literal.text, identifier: undefined, pos: 0 };
6
+ return { specifier: node.argument.literal.text, identifier: undefined, pos: 0, isTypeOnly: true };
7
7
  }
8
8
  }
9
9
  });
@@ -1,6 +1,7 @@
1
1
  import ts from 'typescript';
2
+ import type { ImportNode } from '../../types/imports.js';
2
3
  import type { BoundSourceFile } from '../SourceFile.js';
3
4
  export declare const isNotJS: (sourceFile: BoundSourceFile) => boolean;
4
5
  export declare const isJS: (sourceFile: BoundSourceFile) => boolean;
5
- export declare function getJSXImplicitImportBase(sourceFile: BoundSourceFile): string | undefined;
6
+ export declare function getImportsFromPragmas(sourceFile: BoundSourceFile): ImportNode[];
6
7
  export declare function hasImportSpecifier(node: ts.Statement, name: string): boolean;
@@ -1,12 +1,31 @@
1
1
  import ts from 'typescript';
2
2
  export const isNotJS = (sourceFile) => sourceFile.scriptKind !== ts.ScriptKind.JS && sourceFile.scriptKind !== ts.ScriptKind.JSX;
3
3
  export const isJS = (sourceFile) => sourceFile.scriptKind === ts.ScriptKind.JS || sourceFile.scriptKind === ts.ScriptKind.JSX;
4
- export function getJSXImplicitImportBase(sourceFile) {
5
- const jsxImportSourcePragmas = sourceFile.pragmas?.get('jsximportsource');
6
- const jsxImportSourcePragma = Array.isArray(jsxImportSourcePragmas)
7
- ? jsxImportSourcePragmas[jsxImportSourcePragmas.length - 1]
8
- : jsxImportSourcePragmas;
9
- return jsxImportSourcePragma?.arguments.factory;
4
+ export function getImportsFromPragmas(sourceFile) {
5
+ const importNodes = [];
6
+ if (sourceFile.pragmas) {
7
+ const jsxImportSourcePragmas = sourceFile.pragmas.get('jsximportsource');
8
+ if (jsxImportSourcePragmas) {
9
+ const jsxImportSourcePragma = Array.isArray(jsxImportSourcePragmas)
10
+ ? jsxImportSourcePragmas[jsxImportSourcePragmas.length - 1]
11
+ : jsxImportSourcePragmas;
12
+ const { factory: specifier } = jsxImportSourcePragma?.arguments ?? {};
13
+ if (specifier)
14
+ importNodes.push({ specifier, isTypeOnly: true, identifier: '__jsx', pos: 0 });
15
+ }
16
+ const referencePragma = sourceFile.pragmas.get('reference');
17
+ if (referencePragma) {
18
+ const refs = [referencePragma].flat();
19
+ for (const ref of refs) {
20
+ if (ref.arguments?.types) {
21
+ const { value: specifier, pos } = ref.arguments.types;
22
+ if (specifier)
23
+ importNodes.push({ specifier, isTypeOnly: true, identifier: undefined, pos });
24
+ }
25
+ }
26
+ }
27
+ }
28
+ return importNodes;
10
29
  }
11
30
  export function hasImportSpecifier(node, name) {
12
31
  return (ts.isImportDeclaration(node) &&
@@ -0,0 +1,3 @@
1
+ import type { SerializableExportMap } from '../types/exports.js';
2
+ import type { SerializableImportMap, SerializableImports } from '../types/imports.js';
3
+ export declare const getReExportingEntryFileHandler: (entryPaths: Set<string>, exportedSymbols: SerializableExportMap, importedSymbols: SerializableImportMap) => (importedModule: SerializableImports | undefined, id: string, depth?: number, filePath?: string) => string | undefined;
@@ -0,0 +1,55 @@
1
+ import { exportLookupLog } from './debug.js';
2
+ export const getReExportingEntryFileHandler = (entryPaths, exportedSymbols, importedSymbols) => {
3
+ const getReExportingEntryFile = (importedModule, id, depth = 0, filePath) => {
4
+ if (depth === 0 && filePath)
5
+ exportLookupLog(-1, `Looking up re-exporting file for ${id} from`, filePath);
6
+ if (!importedModule)
7
+ return undefined;
8
+ if (importedModule.isReExport) {
9
+ for (const filePath of importedModule.isReExportedBy) {
10
+ if (entryPaths.has(filePath)) {
11
+ if (filePath in exportedSymbols && id in exportedSymbols[filePath]) {
12
+ exportLookupLog(depth, `re-exported by entry`, filePath);
13
+ return filePath;
14
+ }
15
+ else if (importedModule.hasStar) {
16
+ exportLookupLog(depth, `re-exported (*) by entry`, filePath);
17
+ return filePath;
18
+ }
19
+ }
20
+ else {
21
+ exportLookupLog(depth, `re-exported by`, filePath);
22
+ const file = getReExportingEntryFile(importedSymbols[filePath], id, depth + 1);
23
+ if (file)
24
+ return file;
25
+ }
26
+ }
27
+ for (const [filePath, namespace] of importedModule.isReExportedNs) {
28
+ if (entryPaths.has(filePath)) {
29
+ exportLookupLog(depth, `re-exported on ${namespace} by entry`, filePath);
30
+ return filePath;
31
+ }
32
+ else {
33
+ exportLookupLog(depth, `re-exported on ${namespace} by`, filePath);
34
+ const file = getReExportingEntryFile(importedSymbols[filePath], namespace, depth + 1);
35
+ if (file)
36
+ return file;
37
+ }
38
+ }
39
+ for (const [filePath, alias] of importedModule.isReExportedAs) {
40
+ if (entryPaths.has(filePath)) {
41
+ exportLookupLog(depth, `re-exported as ${alias} by entry`, filePath);
42
+ return filePath;
43
+ }
44
+ else {
45
+ exportLookupLog(depth, `re-exported as ${alias} by`, filePath);
46
+ const file = getReExportingEntryFile(importedSymbols[filePath], alias, depth + 1);
47
+ if (file)
48
+ return file;
49
+ }
50
+ }
51
+ }
52
+ exportLookupLog(depth, `${id} is not re-exported by entry file`, '');
53
+ };
54
+ return getReExportingEntryFile;
55
+ };
@@ -21,7 +21,7 @@ function convertGitignoreToMicromatch(pattern) {
21
21
  pattern = pattern.slice(5);
22
22
  if (pattern.startsWith('/'))
23
23
  pattern = pattern.slice(1);
24
- else
24
+ else if (!pattern.startsWith('**/'))
25
25
  pattern = '**/' + pattern;
26
26
  if (pattern.endsWith('/*'))
27
27
  extPattern = pattern;
@@ -36,7 +36,7 @@ function parseGitignoreFile(filePath) {
36
36
  return file
37
37
  .split(/\r?\n/)
38
38
  .filter(line => line && !line.startsWith('#'))
39
- .map(pattern => convertGitignoreToMicromatch(pattern));
39
+ .map(pattern => convertGitignoreToMicromatch(pattern.replace(/#.*/, '').trim()));
40
40
  }
41
41
  async function parseFindGitignores(options) {
42
42
  const ignores = ['.git', ...GLOBAL_IGNORE_PATTERNS];
@@ -0,0 +1,2 @@
1
+ import type { SerializableImportMap, SerializableImports } from '../types/imports.js';
2
+ export declare const getIsIdentifierReferencedHandler: (importedSymbols: SerializableImportMap) => (filePath: string, id: string, importsForExport?: SerializableImports, depth?: number) => boolean;
@@ -0,0 +1,44 @@
1
+ import { exportLookupLog } from './debug.js';
2
+ export const getIsIdentifierReferencedHandler = (importedSymbols) => {
3
+ const isIdentifierReferenced = (filePath, id, importsForExport, depth = 0) => {
4
+ if (depth === 0)
5
+ exportLookupLog(-1, `Looking up export "${id}" from`, filePath);
6
+ if (!importsForExport) {
7
+ exportLookupLog(depth, `no imports found from`, filePath);
8
+ return false;
9
+ }
10
+ if (importsForExport.identifiers.has(id)) {
11
+ exportLookupLog(depth, `imported from`, filePath);
12
+ return true;
13
+ }
14
+ for (const ns of importsForExport.importedNs) {
15
+ if (importsForExport.identifiers.has(`${ns}.${id}`)) {
16
+ exportLookupLog(depth, `imported on ${ns} from`, filePath);
17
+ return true;
18
+ }
19
+ }
20
+ if (importsForExport.isReExport) {
21
+ for (const filePath of importsForExport.isReExportedBy) {
22
+ if (isIdentifierReferenced(filePath, id, importedSymbols[filePath], depth + 1)) {
23
+ exportLookupLog(depth, `re-exported by`, filePath);
24
+ return true;
25
+ }
26
+ }
27
+ for (const [filePath, alias] of importsForExport.isReExportedAs) {
28
+ if (isIdentifierReferenced(filePath, alias, importedSymbols[filePath], depth + 1)) {
29
+ exportLookupLog(depth, `re-exported as ${alias} by`, filePath);
30
+ return true;
31
+ }
32
+ }
33
+ for (const [filePath, ns] of importsForExport.isReExportedNs) {
34
+ if (isIdentifierReferenced(filePath, `${ns}.${id}`, importedSymbols[filePath], depth + 1)) {
35
+ exportLookupLog(depth, `re-exported on ${ns} by`, filePath);
36
+ return true;
37
+ }
38
+ }
39
+ }
40
+ exportLookupLog(depth, `not imported from`, filePath);
41
+ return false;
42
+ };
43
+ return isIdentifierReferenced;
44
+ };
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "5.3.1";
1
+ export declare const version = "5.5.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '5.3.1';
1
+ export const version = '5.5.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "5.3.1",
3
+ "version": "5.5.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": {
package/schema.json CHANGED
@@ -31,6 +31,11 @@
31
31
  "examples": ["husky", "lint-staged"],
32
32
  "$ref": "#/definitions/list"
33
33
  },
34
+ "ignoreMembers": {
35
+ "title": "Class and enum members to ignore (regex allowed)",
36
+ "examples": ["render", "on.*"],
37
+ "$ref": "#/definitions/list"
38
+ },
34
39
  "ignoreWorkspaces": {
35
40
  "title": "Workspaces to ignore",
36
41
  "examples": ["packages/ignore-me"],