knip 2.9.0 → 2.10.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
@@ -44,12 +44,17 @@ Knip has good defaults and you can run it without any configuration. Here's the
44
44
 
45
45
  ```json
46
46
  {
47
- "entry": ["index.{js,ts}", "src/index.{js,ts}"],
48
- "project": ["**/*.{js,ts}"]
47
+ "entry": ["index.js", "src/index.js"],
48
+ "project": ["**/*.js"]
49
49
  }
50
50
  ```
51
51
 
52
- Well, almost, this is the full list of default extensions: `js`, `mjs`, `cjs`, `jsx`, `ts`, `mts`, `cts` and `tsx`.
52
+ In addition to `index.js`, the following file names and extensions are also considered entry files:
53
+
54
+ - `index`, `main` and `cli`
55
+ - `js`, `mjs`, `cjs`, `jsx`, `ts`, `mts`, `cts` and `tsx`
56
+
57
+ This means files like `main.cjs` and `src/cli.ts` are automatically added as entry files.
53
58
 
54
59
  ### Entry Files
55
60
 
@@ -58,10 +63,9 @@ Knip looks for entry files at those default locations, but also in other places:
58
63
  - The `main`, `bin` and `exports` fields of `package.json`.
59
64
  - [Plugins][2] such as for Next.js, Remix, Gatsby or Svelte add entry files.
60
65
  - The `scripts` in package.json or other scripts may provide entry files.
61
- - Knip does this for each [workspace][1] it finds.
62
66
 
63
- In other words, Knip looks in many places and you may not need much configuration. In a perfectly boring world where
64
- everything is according to defaults you don't even need a `knip.json` file.
67
+ Knip does this for each [workspace][1] it finds, trying to minimize the configuration to suit your project. In a
68
+ perfectly boring world where everything is according to defaults you wouldn't even need a `knip.json` file at all.
65
69
 
66
70
  Larger projects tend to have more things customized, and therefore probably get more out of Knip with a configuration
67
71
  file. Let's say you are using `.ts` files exclusively and have all source files only in the `src` directory:
@@ -49,6 +49,8 @@ export declare class ConfigurationChief {
49
49
  include: string[];
50
50
  exclude: string[];
51
51
  ignore: string[];
52
+ ignoreBinaries: string[];
53
+ ignoreDependencies: string[];
52
54
  ignoreWorkspaces: string[];
53
55
  syncCompilers: Map<string, (args_0: string, ...args_1: unknown[]) => string>;
54
56
  asyncCompilers: Map<string, (args_0: string, ...args_1: unknown[]) => Promise<string>>;
@@ -21,7 +21,7 @@ const workspaceArg = rawWorkspaceArg ? toPosix(rawWorkspaceArg).replace(/^\.\//,
21
21
  const getDefaultWorkspaceConfig = (extensions) => {
22
22
  const exts = [...DEFAULT_EXTENSIONS, ...(extensions ?? [])].map(ext => ext.slice(1)).join(',');
23
23
  return {
24
- entry: [`index.{${exts}}!`, `src/index.{${exts}}!`],
24
+ entry: [`{index,cli,main}.{${exts}}!`, `src/{index,cli,main}.{${exts}}!`],
25
25
  project: [`**/*.{${exts}}!`],
26
26
  paths: {},
27
27
  ignore: [],
@@ -34,6 +34,8 @@ const defaultConfig = {
34
34
  include: [],
35
35
  exclude: [],
36
36
  ignore: [],
37
+ ignoreBinaries: [],
38
+ ignoreDependencies: [],
37
39
  ignoreWorkspaces: [],
38
40
  syncCompilers: new Map(),
39
41
  asyncCompilers: new Map(),
@@ -122,8 +124,8 @@ export class ConfigurationChief {
122
124
  project,
123
125
  paths,
124
126
  ignore: arrayify(workspaceConfig.ignore),
125
- ignoreBinaries: compact([...ignoreBinaries, ...arrayify(workspaceConfig.ignoreBinaries)]),
126
- ignoreDependencies: compact([...ignoreDependencies, ...arrayify(workspaceConfig.ignoreDependencies)]),
127
+ ignoreBinaries: arrayify(workspaceConfig.ignoreBinaries),
128
+ ignoreDependencies: arrayify(workspaceConfig.ignoreDependencies),
127
129
  };
128
130
  for (const [name, pluginConfig] of Object.entries(workspaceConfig)) {
129
131
  const pluginName = toCamelCase(name);
@@ -151,6 +153,8 @@ export class ConfigurationChief {
151
153
  include,
152
154
  exclude,
153
155
  ignore,
156
+ ignoreBinaries,
157
+ ignoreDependencies,
154
158
  ignoreWorkspaces,
155
159
  syncCompilers: new Map(Object.entries(syncCompilers ?? {})),
156
160
  asyncCompilers: new Map(Object.entries(asyncCompilers ?? {})),
@@ -172,7 +176,6 @@ export class ConfigurationChief {
172
176
  const workspaces = await mapWorkspaces({
173
177
  pkg: this.manifest ?? {},
174
178
  cwd: this.cwd,
175
- ignore: this.config.ignoreWorkspaces,
176
179
  });
177
180
  const manifestWorkspaces = new Map();
178
181
  for (const [pkgName, dir] of workspaces.entries()) {
@@ -13,6 +13,8 @@ export declare class DependencyDeputy {
13
13
  referencedBinaries: Map<string, Set<string>>;
14
14
  peerDependencies: Map<string, PeerDependencies>;
15
15
  installedBinaries: Map<string, InstalledBinaries>;
16
+ ignoreBinaries: string[];
17
+ ignoreDependencies: string[];
16
18
  constructor({ isStrict }: Options);
17
19
  addWorkspace({ name, dir, manifestPath, manifest, ignoreDependencies, ignoreBinaries, }: {
18
20
  name: string;
@@ -22,6 +24,7 @@ export declare class DependencyDeputy {
22
24
  ignoreDependencies: string[];
23
25
  ignoreBinaries: string[];
24
26
  }): void;
27
+ addIgnored(ignoreBinaries: string[], ignoreDependencies: string[]): void;
25
28
  getWorkspaceManifest(workspaceName: string): {
26
29
  workspaceDir: string;
27
30
  manifestPath: string;
@@ -48,6 +51,8 @@ export declare class DependencyDeputy {
48
51
  settleDependencyIssues(): {
49
52
  dependencyIssues: Issue[];
50
53
  devDependencyIssues: Issue[];
54
+ };
55
+ getConfigurationHints(): {
51
56
  configurationHints: ConfigurationHints;
52
57
  };
53
58
  }
@@ -8,6 +8,8 @@ export class DependencyDeputy {
8
8
  referencedBinaries;
9
9
  peerDependencies;
10
10
  installedBinaries;
11
+ ignoreBinaries = [];
12
+ ignoreDependencies = [];
11
13
  constructor({ isStrict }) {
12
14
  this.isStrict = isStrict;
13
15
  this.referencedDependencies = new Map();
@@ -35,6 +37,10 @@ export class DependencyDeputy {
35
37
  allDependencies,
36
38
  });
37
39
  }
40
+ addIgnored(ignoreBinaries, ignoreDependencies) {
41
+ this.ignoreBinaries = ignoreBinaries;
42
+ this.ignoreDependencies = ignoreDependencies;
43
+ }
38
44
  getWorkspaceManifest(workspaceName) {
39
45
  return this._manifests.get(workspaceName);
40
46
  }
@@ -80,8 +86,6 @@ export class DependencyDeputy {
80
86
  return true;
81
87
  const workspaceNames = this.isStrict ? [workspace.name] : [workspace.name, ...[...workspace.ancestors].reverse()];
82
88
  const closestWorkspaceName = workspaceNames.find(name => this.isInDependencies(name, packageName));
83
- if (this.getWorkspaceManifest(workspace.name)?.ignoreDependencies.includes(packageName))
84
- return true;
85
89
  const typesPackageName = !isDefinitelyTyped(packageName) && getDefinitelyTypedFor(packageName);
86
90
  const closestWorkspaceNameForTypes = typesPackageName && workspaceNames.find(name => this.isInDependencies(name, typesPackageName));
87
91
  if (closestWorkspaceName || closestWorkspaceNameForTypes) {
@@ -89,14 +93,19 @@ export class DependencyDeputy {
89
93
  closestWorkspaceNameForTypes && this.addReferencedDependency(closestWorkspaceNameForTypes, typesPackageName);
90
94
  return true;
91
95
  }
96
+ else {
97
+ this.addReferencedDependency(workspace.name, packageName);
98
+ }
99
+ if (this.getWorkspaceManifest(workspace.name)?.ignoreDependencies.includes(packageName))
100
+ return true;
101
+ if (this.ignoreDependencies.includes(packageName))
102
+ return true;
92
103
  return false;
93
104
  }
94
105
  maybeAddReferencedBinary(workspace, binaryName) {
95
106
  if (IGNORED_GLOBAL_BINARIES.includes(binaryName))
96
107
  return true;
97
108
  this.addReferencedBinary(workspace.name, binaryName);
98
- if (this.getWorkspaceManifest(workspace.name)?.ignoreBinaries.includes(binaryName))
99
- return true;
100
109
  const workspaceNames = this.isStrict ? [workspace.name] : [workspace.name, ...[...workspace.ancestors].reverse()];
101
110
  for (const name of workspaceNames) {
102
111
  const binaries = this.getInstalledBinaries(name);
@@ -108,6 +117,10 @@ export class DependencyDeputy {
108
117
  }
109
118
  }
110
119
  }
120
+ if (this.getWorkspaceManifest(workspace.name)?.ignoreBinaries.includes(binaryName))
121
+ return true;
122
+ if (this.ignoreBinaries.includes(binaryName))
123
+ return true;
111
124
  return false;
112
125
  }
113
126
  isInDependencies(workspaceName, packageName) {
@@ -120,25 +133,28 @@ export class DependencyDeputy {
120
133
  settleDependencyIssues() {
121
134
  const dependencyIssues = [];
122
135
  const devDependencyIssues = [];
123
- const configurationHints = new Set();
124
136
  for (const [workspaceName, { manifestPath, ignoreDependencies, ignoreBinaries }] of this._manifests.entries()) {
125
137
  const referencedDependencies = this.referencedDependencies.get(workspaceName);
126
- const referencedBinaries = this.referencedBinaries.get(workspaceName);
127
138
  const installedBinaries = this.getInstalledBinaries(workspaceName);
128
- const ignoreBins = [...IGNORED_GLOBAL_BINARIES, ...ignoreBinaries];
129
- const ignoreDeps = [...IGNORED_DEPENDENCIES, ...ignoreDependencies];
139
+ const ignoreBins = [...IGNORED_GLOBAL_BINARIES, ...this.ignoreBinaries, ...ignoreBinaries];
140
+ const ignoreDeps = [...IGNORED_DEPENDENCIES, ...this.ignoreDependencies, ...ignoreDependencies];
130
141
  const isNotIgnoredDependency = (packageName) => !ignoreDeps.includes(packageName);
131
142
  const isNotIgnoredBinary = (packageName) => {
132
143
  if (installedBinaries?.has(packageName)) {
133
144
  const binaryNames = installedBinaries.get(packageName);
134
- if (binaryNames && ignoreBins.some(ignoredBinary => binaryNames.has(ignoredBinary)))
135
- return false;
145
+ if (binaryNames) {
146
+ if (ignoreBins.some(ignoredBinary => binaryNames.has(ignoredBinary)))
147
+ return false;
148
+ }
136
149
  }
137
150
  return true;
138
151
  };
139
- const isNotReferencedDependency = (dependency) => {
152
+ const peerDepRecs = {};
153
+ const isNotReferencedDependency = (dependency, isPeerDep) => {
140
154
  if (referencedDependencies?.has(dependency))
141
155
  return false;
156
+ if (isPeerDep && peerDepRecs[dependency])
157
+ return true;
142
158
  const [scope, typedDependency] = dependency.split('/');
143
159
  if (scope === '@types') {
144
160
  const typedPackageName = getPackageFromDefinitelyTyped(typedDependency);
@@ -146,35 +162,56 @@ export class DependencyDeputy {
146
162
  return false;
147
163
  const peerDependencies = this.getPeerDependencies(workspaceName, typedPackageName);
148
164
  if (peerDependencies.length) {
149
- return !peerDependencies.find(peerDependency => !isNotReferencedDependency(peerDependency));
165
+ return !peerDependencies.find(peerDependency => !isNotReferencedDependency(peerDependency, true));
150
166
  }
151
167
  return !referencedDependencies?.has(typedPackageName);
152
168
  }
153
- if (!referencedDependencies?.has(dependency)) {
154
- const peerDependencies = this.getPeerDependencies(workspaceName, dependency);
155
- return !peerDependencies.find(peerDependency => !isNotReferencedDependency(peerDependency));
156
- }
157
- return false;
169
+ const peerDependencies = this.getPeerDependencies(workspaceName, dependency);
170
+ peerDependencies.forEach(dep => (!peerDepRecs[dep] ? (peerDepRecs[dep] = 1) : peerDepRecs[dep]++));
171
+ return !peerDependencies.find(peerDependency => !isNotReferencedDependency(peerDependency, true));
158
172
  };
159
173
  const pd = this.getProductionDependencies(workspaceName);
160
174
  const dd = this.getDevDependencies(workspaceName);
161
175
  pd.filter(isNotIgnoredDependency)
162
176
  .filter(isNotIgnoredBinary)
163
- .filter(isNotReferencedDependency)
177
+ .filter(d => isNotReferencedDependency(d))
164
178
  .forEach(symbol => dependencyIssues.push({ type: 'dependencies', filePath: manifestPath, symbol }));
165
179
  dd.filter(isNotIgnoredDependency)
166
180
  .filter(isNotIgnoredBinary)
167
- .filter(isNotReferencedDependency)
181
+ .filter(d => isNotReferencedDependency(d))
168
182
  .forEach(symbol => devDependencyIssues.push({ type: 'devDependencies', filePath: manifestPath, symbol }));
169
- const isReferencedDep = (name) => !([...pd, ...dd].includes(name) && referencedDependencies?.has(name));
183
+ }
184
+ return { dependencyIssues, devDependencyIssues };
185
+ }
186
+ getConfigurationHints() {
187
+ const configurationHints = new Set();
188
+ const rootIgnoreBinaries = Object.fromEntries(this.ignoreBinaries.map(key => [key, 0]));
189
+ const rootIgnoreDependencies = Object.fromEntries(this.ignoreDependencies.map(key => [key, 0]));
190
+ for (const [workspaceName, { ignoreDependencies, ignoreBinaries }] of this._manifests.entries()) {
191
+ const referencedDependencies = this.referencedDependencies.get(workspaceName);
192
+ const referencedBinaries = this.referencedBinaries.get(workspaceName);
193
+ const installedBinaries = this.getInstalledBinaries(workspaceName);
194
+ referencedBinaries?.forEach(binaryName => binaryName in rootIgnoreBinaries && rootIgnoreBinaries[binaryName]++);
195
+ const allDeps = [...this.getProductionDependencies(workspaceName), ...this.getDevDependencies(workspaceName)];
196
+ const isReferencedDep = (name) => referencedDependencies?.has(name) || (allDeps.includes(name) && !referencedDependencies?.has(name));
170
197
  const isReferencedBin = (name) => !installedBinaries?.has(name) && referencedBinaries?.has(name);
171
198
  ignoreDependencies
172
- .filter(packageName => IGNORED_DEPENDENCIES.includes(packageName) || !isReferencedDep(packageName))
199
+ .filter(packageName => IGNORED_DEPENDENCIES.includes(packageName) ||
200
+ (workspaceName !== '.' && this.ignoreDependencies.includes(packageName)) ||
201
+ !isReferencedDep(packageName))
173
202
  .forEach(identifier => configurationHints.add({ workspaceName, identifier, type: 'ignoreDependencies' }));
174
203
  ignoreBinaries
175
- .filter(binaryName => IGNORED_GLOBAL_BINARIES.includes(binaryName) || !isReferencedBin(binaryName))
204
+ .filter(binaryName => IGNORED_GLOBAL_BINARIES.includes(binaryName) ||
205
+ (workspaceName !== '.' && this.ignoreBinaries.includes(binaryName)) ||
206
+ !isReferencedBin(binaryName))
176
207
  .forEach(identifier => configurationHints.add({ workspaceName, identifier, type: 'ignoreBinaries' }));
177
208
  }
178
- return { dependencyIssues, devDependencyIssues, configurationHints };
209
+ Object.keys(rootIgnoreBinaries)
210
+ .filter(key => rootIgnoreBinaries[key] === 0)
211
+ .forEach(identifier => configurationHints.add({ workspaceName: '.', identifier, type: 'ignoreBinaries' }));
212
+ Object.keys(rootIgnoreDependencies)
213
+ .filter(key => rootIgnoreDependencies[key] === 0)
214
+ .forEach(identifier => configurationHints.add({ workspaceName: '.', identifier, type: 'ignoreDependencies' }));
215
+ return { configurationHints };
179
216
  }
180
217
  }
@@ -21,7 +21,7 @@ export declare class IssueCollector {
21
21
  getIssues(): {
22
22
  issues: import("./types/issues.js").Issues;
23
23
  counters: import("./types/issues.js").Counters;
24
- configurationHints: Set<unknown>;
24
+ configurationHints: Set<ConfigurationHint>;
25
25
  };
26
26
  }
27
27
  export {};
@@ -1,5 +1,9 @@
1
1
  import { initIssues, initCounters } from './issues/initializers.js';
2
2
  import { relative } from './util/path.js';
3
+ function objectInSet(set, obj) {
4
+ const objJSON = JSON.stringify(obj);
5
+ return Array.from(set).some(item => JSON.stringify(item) === objJSON);
6
+ }
3
7
  export class IssueCollector {
4
8
  cwd;
5
9
  rules;
@@ -34,7 +38,9 @@ export class IssueCollector {
34
38
  }
35
39
  }
36
40
  addConfigurationHint(issue) {
37
- this.configurationHints.add(issue);
41
+ if (!objectInSet(this.configurationHints, issue)) {
42
+ this.configurationHints.add(issue);
43
+ }
38
44
  }
39
45
  getIssues() {
40
46
  return {
package/dist/index.d.ts CHANGED
@@ -6,5 +6,5 @@ export declare const main: (unresolvedConfiguration: CommandLineOptions) => Prom
6
6
  issues: import("./types/issues.js").Issues;
7
7
  counters: import("./types/issues.js").Counters;
8
8
  rules: import("./types/issues.js").Rules;
9
- configurationHints: Set<unknown>;
9
+ configurationHints: Set<import("./types/issues.js").ConfigurationHint>;
10
10
  }>;
package/dist/index.js CHANGED
@@ -35,6 +35,7 @@ export const main = async (unresolvedConfiguration) => {
35
35
  const isReportTypes = report.types || report.nsTypes || report.enumMembers;
36
36
  const collector = new IssueCollector({ cwd, rules });
37
37
  const enabledPluginsStore = new Map();
38
+ deputy.addIgnored(chief.config.ignoreBinaries, chief.config.ignoreDependencies);
38
39
  debugLogObject('Included workspaces', workspaces);
39
40
  const handleReferencedDependency = ({ specifier, containingFilePath, principal, workspace, }) => {
40
41
  if (isInternal(specifier)) {
@@ -102,7 +103,7 @@ export const main = async (unresolvedConfiguration) => {
102
103
  });
103
104
  await worker.init();
104
105
  const sharedGlobOptions = { cwd, workingDir: dir, gitignore, ignore: worker.getIgnorePatterns() };
105
- const entryPathsFromManifest = await getEntryPathFromManifest(dir, manifest);
106
+ const entryPathsFromManifest = await getEntryPathFromManifest(cwd, dir, manifest);
106
107
  debugLogArray(`Found entry paths from manifest (${name})`, entryPathsFromManifest);
107
108
  principal.addEntryPaths(entryPathsFromManifest);
108
109
  if (isProduction) {
@@ -199,8 +200,8 @@ export const main = async (unresolvedConfiguration) => {
199
200
  for (const identifier of importItems.symbols) {
200
201
  importedModule.symbols.add(identifier);
201
202
  }
202
- if (importItems.isReExported) {
203
- importedModule.isReExported = importItems.isReExported;
203
+ if (importItems.isReExport) {
204
+ importedModule.isReExport = importItems.isReExport;
204
205
  importedModule.isReExportedBy.add(filePath);
205
206
  }
206
207
  if (importItems.isDynamic) {
@@ -229,10 +230,10 @@ export const main = async (unresolvedConfiguration) => {
229
230
  const isExportedInEntryFile = (importedModule) => {
230
231
  if (!importedModule)
231
232
  return false;
232
- const { isReExported, isReExportedBy } = importedModule;
233
+ const { isReExport, isReExportedBy } = importedModule;
233
234
  const { entryPaths } = principal;
234
235
  const hasFile = (file) => entryPaths.has(file) || isExportedInEntryFile(importedSymbols.get(file));
235
- return isReExported ? Array.from(isReExportedBy).some(hasFile) : false;
236
+ return isReExport ? Array.from(isReExportedBy).some(hasFile) : false;
236
237
  };
237
238
  streamer.cast('Running async compilers...');
238
239
  await principal.runAsyncCompilers();
@@ -262,6 +263,8 @@ export const main = async (unresolvedConfiguration) => {
262
263
  if (principal.isPublicExport(exportedItem))
263
264
  continue;
264
265
  if (importedModule.symbols.has(symbol)) {
266
+ if (importedModule.isReExport && isExportedInEntryFile(importedModule))
267
+ continue;
265
268
  if (report.enumMembers && exportedItem.type === 'enum' && exportedItem.members) {
266
269
  principal.findUnusedMembers(filePath, exportedItem.members).forEach(member => {
267
270
  collector.addIssue({ type: 'enumMembers', filePath, symbol: member, parentSymbol: symbol });
@@ -274,7 +277,7 @@ export const main = async (unresolvedConfiguration) => {
274
277
  }
275
278
  continue;
276
279
  }
277
- if (importedModule.isReExported || importedModule.isStar) {
280
+ if (importedModule.isReExport || importedModule.isStar) {
278
281
  const isReExportedByEntryFile = isExportedInEntryFile(importedModule);
279
282
  if (!isReExportedByEntryFile && !principal.hasExternalReferences(filePath, exportedItem)) {
280
283
  if (['enum', 'type', 'interface'].includes(exportedItem.type)) {
@@ -299,7 +302,8 @@ export const main = async (unresolvedConfiguration) => {
299
302
  }
300
303
  }
301
304
  if (isReportDependencies) {
302
- const { dependencyIssues, devDependencyIssues, configurationHints } = deputy.settleDependencyIssues();
305
+ const { dependencyIssues, devDependencyIssues } = deputy.settleDependencyIssues();
306
+ const { configurationHints } = deputy.getConfigurationHints();
303
307
  dependencyIssues.forEach(issue => collector.addIssue(issue));
304
308
  if (!isProduction)
305
309
  devDependencyIssues.forEach(issue => collector.addIssue(issue));
@@ -60,19 +60,15 @@ const resolveExtendsSpecifier = (specifier) => {
60
60
  return;
61
61
  return resolvePackageName(specifier.startsWith('plugin:') ? 'eslint-plugin' : 'eslint-config', pluginName);
62
62
  };
63
- const getImportPluginDependencies = (settings) => {
64
- const knownKeys = ['typescript'];
65
- if (Array.isArray(settings))
66
- return [];
67
- return Object.keys(settings)
68
- .filter(key => key !== 'node')
69
- .map(key => (knownKeys.includes(key) ? `eslint-import-resolver-${key}` : key));
70
- };
71
63
  const getDependenciesFromSettings = (settings = {}) => {
72
- return compact(Object.entries(settings).reduce((packageNames, [settingKey, settings]) => {
73
- if (/^import\/(parsers|resolvers)?/.test(settingKey) && typeof settings !== 'string') {
74
- return [...packageNames, ...getImportPluginDependencies(settings)];
64
+ return compact(Object.entries(settings).flatMap(([settingKey, settings]) => {
65
+ if (settingKey === 'import/resolver') {
66
+ return (typeof settings === 'string' ? [settings] : Object.keys(settings))
67
+ .filter(key => key !== 'node')
68
+ .map(key => `eslint-import-resolver-${key}`);
69
+ }
70
+ if (settingKey === 'import/parsers') {
71
+ return typeof settings === 'string' ? [settings] : Object.keys(settings);
75
72
  }
76
- return packageNames;
77
- }, []));
73
+ }));
78
74
  };
@@ -27,6 +27,8 @@ export interface Configuration {
27
27
  include: string[];
28
28
  exclude: string[];
29
29
  ignore: NormalizedGlob;
30
+ ignoreBinaries: string[];
31
+ ignoreDependencies: string[];
30
32
  ignoreWorkspaces: string[];
31
33
  workspaces: Record<string, WorkspaceConfiguration>;
32
34
  syncCompilers: SyncCompilers;
@@ -6,7 +6,7 @@ export type ImportedModule = {
6
6
  specifier: Specifier;
7
7
  symbols: ImportItems;
8
8
  isStar: boolean;
9
- isReExported: boolean;
9
+ isReExport: boolean;
10
10
  isReExportedBy: Set<string>;
11
11
  isDynamic: boolean;
12
12
  };
@@ -11,6 +11,7 @@ export type AddImportOptions = {
11
11
  symbol?: ts.Symbol;
12
12
  identifier?: string;
13
13
  isDynamic?: boolean;
14
+ isReExport?: boolean;
14
15
  };
15
16
  export type AddExportOptions = ExportItem & {
16
17
  identifier: string;
@@ -20,32 +20,32 @@ export const getImportsAndExports = (sourceFile, options) => {
20
20
  const scripts = new Set();
21
21
  const importedInternalSymbols = new Map();
22
22
  const visitors = getVisitors(sourceFile);
23
- const addInternalImport = ({ identifier, specifier, symbol, filePath, isDynamic }) => {
23
+ const addInternalImport = (options) => {
24
+ const { identifier, specifier, symbol, filePath, isDynamic, isReExport } = options;
24
25
  const isStar = identifier === '*';
25
- const isReExported = Boolean(isStar && !symbol);
26
26
  const internalImport = getOrSet(internalImports, filePath, {
27
27
  specifier,
28
28
  isStar,
29
- isReExported,
29
+ isReExport,
30
30
  isReExportedBy: new Set(),
31
31
  symbols: new Set(),
32
32
  isDynamic,
33
33
  });
34
- if (isReExported) {
35
- internalImport.isReExported = isReExported;
34
+ if (isReExport) {
35
+ internalImport.isReExport = isReExport;
36
36
  internalImport.isReExportedBy.add(sourceFile.fileName);
37
37
  }
38
38
  if (isStar)
39
39
  internalImport.isStar = isStar;
40
40
  if (!isStar)
41
41
  internalImport.symbols.add(identifier);
42
- if (isStar && symbol) {
42
+ if (isStar && symbol)
43
43
  importedInternalSymbols.set(symbol, filePath);
44
- }
45
44
  if (isDynamic)
46
45
  internalImport.isDynamic = isDynamic;
47
46
  };
48
- const addImport = ({ specifier, symbol, identifier = '__anonymous', isDynamic = false }) => {
47
+ const addImport = (options) => {
48
+ const { specifier, symbol, identifier = '__anonymous', isDynamic = false, isReExport = false } = options;
49
49
  if (isBuiltin(specifier))
50
50
  return;
51
51
  const module = sourceFile.resolvedModules?.get(specifier, undefined);
@@ -54,7 +54,7 @@ export const getImportsAndExports = (sourceFile, options) => {
54
54
  if (filePath) {
55
55
  if (module.resolvedModule.isExternalLibraryImport) {
56
56
  if (!isInNodeModules(filePath)) {
57
- addInternalImport({ identifier, specifier, symbol, filePath, isDynamic });
57
+ addInternalImport({ identifier, specifier, symbol, filePath, isDynamic, isReExport });
58
58
  }
59
59
  else if (isDeclarationFileExtension(module.resolvedModule.extension)) {
60
60
  externalImports.add(specifier);
@@ -64,7 +64,7 @@ export const getImportsAndExports = (sourceFile, options) => {
64
64
  }
65
65
  }
66
66
  else {
67
- addInternalImport({ identifier, specifier, symbol, filePath, isDynamic });
67
+ addInternalImport({ identifier, specifier, symbol, filePath, isDynamic, isReExport });
68
68
  }
69
69
  }
70
70
  }
@@ -4,16 +4,16 @@ export default visit(() => true, node => {
4
4
  if (ts.isExportDeclaration(node)) {
5
5
  if (node.moduleSpecifier && ts.isStringLiteralLike(node.moduleSpecifier)) {
6
6
  if (!node.exportClause) {
7
- return { identifier: '*', specifier: node.moduleSpecifier.text };
7
+ return { identifier: '*', specifier: node.moduleSpecifier.text, isReExport: true };
8
8
  }
9
9
  else if (node.exportClause.kind === ts.SyntaxKind.NamespaceExport) {
10
- return { identifier: '*', specifier: node.moduleSpecifier.text };
10
+ return { identifier: '*', specifier: node.moduleSpecifier.text, isReExport: true };
11
11
  }
12
12
  else {
13
13
  const specifier = node.moduleSpecifier;
14
14
  return node.exportClause.elements.map(element => {
15
15
  const identifier = (element.propertyName ?? element.name).getText();
16
- return { identifier, specifier: specifier.text };
16
+ return { identifier, specifier: specifier.text, isReExport: true };
17
17
  });
18
18
  }
19
19
  }
@@ -4,4 +4,4 @@ export declare const getPackageNameFromFilePath: (value: string) => string;
4
4
  export declare const isDefinitelyTyped: (packageName: string) => boolean;
5
5
  export declare const getDefinitelyTypedFor: (packageName: string) => string;
6
6
  export declare const getPackageFromDefinitelyTyped: (typedDependency: string) => string;
7
- export declare const getEntryPathFromManifest: (dir: string, manifest: PackageJson) => Promise<string[]>;
7
+ export declare const getEntryPathFromManifest: (cwd: string, dir: string, manifest: PackageJson) => Promise<string[]>;
@@ -1,4 +1,4 @@
1
- import { _pureGlob } from './glob.js';
1
+ import { _glob } from './glob.js';
2
2
  import { getStringValues } from './object.js';
3
3
  import { toPosix } from './path.js';
4
4
  export const getPackageNameFromModuleSpecifier = (moduleSpecifier) => {
@@ -26,7 +26,7 @@ export const getPackageFromDefinitelyTyped = (typedDependency) => {
26
26
  }
27
27
  return typedDependency;
28
28
  };
29
- export const getEntryPathFromManifest = (dir, manifest) => {
29
+ export const getEntryPathFromManifest = (cwd, dir, manifest) => {
30
30
  const { main, bin, exports } = manifest;
31
31
  const entryPaths = new Set();
32
32
  if (typeof main === 'string')
@@ -45,5 +45,5 @@ export const getEntryPathFromManifest = (dir, manifest) => {
45
45
  getStringValues(exports).forEach(item => entryPaths.add(item));
46
46
  }
47
47
  }
48
- return _pureGlob({ cwd: dir, patterns: Array.from(entryPaths) });
48
+ return _glob({ cwd, workingDir: dir, patterns: Array.from(entryPaths) });
49
49
  };
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.9.0";
1
+ export declare const version = "2.10.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.9.0';
1
+ export const version = '2.10.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "2.9.0",
3
+ "version": "2.10.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",