knip 0.3.0 → 0.4.1

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
@@ -1,7 +1,7 @@
1
1
  # ✂️ Knip
2
2
 
3
- Knip scans your JavaScript and TypeScript projects for **unused files, dependencies and exports**. Things that should be
4
- eliminated. Less code means better performance and less to maintain, important for both UX and DX!
3
+ Knip scans your JavaScript and TypeScript projects for **unused files, dependencies and exports**: things that can be
4
+ removed! Less code means better performance and less to maintain, important for both UX and DX!
5
5
 
6
6
  For comparison, ESLint finds unused variables inside files in isolation, but this will not be flagged:
7
7
 
@@ -79,16 +79,18 @@ Please read on if you think you have too many results: [too many false positives
79
79
  knip [options]
80
80
 
81
81
  Options:
82
- -c/--config [file] Configuration file path (default: ./knip.json or package.json#knip)
83
- -t/--tsConfig [file] TypeScript configuration path (default: ./tsconfig.json)
84
- --dir Working directory (default: current working directory)
85
- --include Report only listed issue group(s) (see below)
86
- --exclude Exclude issue group(s) from report (see below)
87
- --dev Include `devDependencies` in report(s) (default: false)
88
- --no-progress Don't show dynamic progress updates
89
- --max-issues Maximum number of issues before non-zero exit code (default: 0)
90
- --reporter Select reporter: symbols, compact (default: symbols)
91
- --jsdoc Enable JSDoc parsing, with options: public (default: disabled)
82
+ -c/--config [file] Configuration file path (default: ./knip.json or package.json#knip)
83
+ -t/--tsConfig [file] TypeScript configuration path (default: ./tsconfig.json)
84
+ --dir Working directory (default: current working directory)
85
+ --include Report only listed issue group(s) (see below)
86
+ --exclude Exclude issue group(s) from report (see below)
87
+ --ignore Ignore files matching this glob pattern (can be set multiple times)
88
+ --no-gitignore Don't use .gitignore
89
+ --dev Include `devDependencies` in report(s)
90
+ --no-progress Don't show dynamic progress updates
91
+ --max-issues Maximum number of issues before non-zero exit code (default: 0)
92
+ --reporter Select reporter: symbols, compact (default: symbols)
93
+ --jsdoc Enable JSDoc parsing, with options: public
92
94
 
93
95
  Issue groups: files, dependencies, unlisted, exports, nsExports, types, nsTypes, duplicates
94
96
 
@@ -97,6 +99,7 @@ Examples:
97
99
  $ knip
98
100
  $ knip --dir packages/client --include files
99
101
  $ knip -c ./knip.js --reporter compact --jsdoc public
102
+ $ knip --ignore 'lib/**/*.ts' --ignore build
100
103
 
101
104
  More info: https://github.com/webpro/knip
102
105
  ```
@@ -139,7 +142,7 @@ As always, make sure to backup files or use Git before deleting files or making
139
142
 
140
143
  The default configuration for Knip is very strict and targets production code. For best results, it is recommended to
141
144
  exclude files such as tests from the project files. Here's why: when including tests and other non-production files,
142
- they may prevent production files from being reported as unused.
145
+ they may import production files, which will prevent them from being reported as unused.
143
146
 
144
147
  Excluding non-production files from the `projectFiles` allows Knip to understand what production code can be removed
145
148
  (including dependent files!).
@@ -159,8 +162,7 @@ and add `dev: true` to a file named such as `knip.dev.json`:
159
162
  }
160
163
  ```
161
164
 
162
- Use `-c knip.dev.json` and unused files and exports for the combined set of files as configured in `entryFiles` will be
163
- reported.
165
+ Now use `-c knip.dev.json` to find unused files and exports for the combined set of files as configured in `entryFiles`.
164
166
 
165
167
  An alternative way to store `dev` configuration is in this example `package.json`:
166
168
 
@@ -189,7 +191,7 @@ This way, the `--dev` flag will use the `dev` options (and also add `devDependen
189
191
 
190
192
  #### Separate packages
191
193
 
192
- In repos with multiple (published) packages, the `--cwd` option comes in handy. With similar package structures, the
194
+ In repos with multiple (published) packages, the `--dir` option comes in handy. With similar package structures, the
193
195
  packages can be configured using globs:
194
196
 
195
197
  ```json
@@ -206,8 +208,8 @@ Packages can also be explicitly configured per package directory.
206
208
  To scan the packages separately, using the first match from the configuration file:
207
209
 
208
210
  ```
209
- knip --cwd packages/client
210
- knip --cwd packages/services
211
+ knip --dir packages/client
212
+ knip --dir packages/services
211
213
  ```
212
214
 
213
215
  #### Connected projects
@@ -293,16 +295,48 @@ src/components/Registration.tsx: Registration, default
293
295
  src/components/Products.tsx: ProductsList, default
294
296
  ```
295
297
 
296
- ## Why Yet Another unused file/export finder?
297
-
298
- There are some fine modules available in the same category:
299
-
300
- - [unimported](https://github.com/smeijer/unimported)
301
- - [ts-unused-exports](https://github.com/pzavolinsky/ts-unused-exports)
302
- - [no-unused-export](https://github.com/plantain-00/no-unused-export)
303
- - [ts-prune](https://github.com/nadeesha/ts-prune)
304
- - [find-unused-exports](https://github.com/jaydenseric/find-unused-exports)
305
-
306
- However, the results where not always accurate, and none of them tick my boxes to find both unused files and exports. Or
307
- let me configure entry files and scope the project files for clean results. Especially for larger projects this kind of
308
- configuration is necessary. That's why I took another stab at it.
298
+ ## Why Yet Another unused file/dependency/export finder?
299
+
300
+ There are already some great packages available. Getting good results when finding unused files, dependencies and
301
+ exports is not trivial. Repositories don't seem to get any smaller and with the rise of monorepos even more so. Tools
302
+ like this need to analyze potentially many and/or large files, which is memory and time-consuming. Although I normally
303
+ try to stick to the Unix philosophy, here I believe it's efficient to merge these issue reports into a single tool. When
304
+ building a dependency graph of the project, an abstract syntax tree for each file, and traversing all of this, why not
305
+ collect the various issues in one go?
306
+
307
+ ## Comparison
308
+
309
+ This table is a work in progress, but here's a first impression. Based on their docs (please report any mistakes):
310
+
311
+ | Feature | **knip** | [depcheck][1] | [unimported][2] | [ts-unused-exports][3] | [ts-prune][4] | [find-unused-exports][5] |
312
+ | --------------------------- | :--------: | :-----------: | :-------------: | :--------------------: | :-----------: | :----------------------: |
313
+ | Unused files | ✅ | - | ✅ | - | - | - |
314
+ | Unused dependencies | ✅ | ✅ | ✅ | - | - | - |
315
+ | Unlisted dependencies | ✅ | ✅ | ✅ | - | - | - |
316
+ | Custom dependency resolvers | ❌ [#7][6] | ✅ | ❌ (1) | - | - | - |
317
+ | Unused exports | ✅ | - | - | ✅ | ✅ | ✅ |
318
+ | Duplicate exports | ✅ | - | - | ❌ | ❌ | ❌ |
319
+ | Search namespaces | ✅ | - | - | ✅ | ❌ | ❌ |
320
+ | Custom reporters | ✅ | - | - | - | - | - |
321
+ | Pure JavaScript/ESM | ✅ | ✅ | ✅ | - | - | ✅ |
322
+ | Configure entry files | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
323
+ | Support monorepo | 🟠 (2) | - | - | - | - | - |
324
+ | ESLint plugin available | - | - | - | ✅ | - | - |
325
+
326
+ ✅ = Supported, ❌ = Not supported, - = Out of scope
327
+
328
+ 1. unimported is strict and works based on production files and `dependencies`, so does not have custom dependency
329
+ resolvers which are usually only needed for `devDependencies`.
330
+ 2. knip wants to [support monorepos](#monorepos) properly, the first steps in this direction are implemented.
331
+
332
+ ## Knip?!
333
+
334
+ Knip is Dutch for a "cut". A Dutch expression is "to be ge**knip**t for something", which means to be perfectly suited
335
+ for the job. I'm motivated to make knip perfectly suited for the job of cutting projects to perfection! ✂️
336
+
337
+ [1]: https://github.com/depcheck/depcheck
338
+ [2]: https://github.com/smeijer/unimported
339
+ [3]: https://github.com/pzavolinsky/ts-unused-exports
340
+ [4]: https://github.com/nadeesha/ts-prune
341
+ [5]: https://github.com/jaydenseric/find-unused-exports
342
+ [6]: https://github.com/webpro/knip/issues/7
package/dist/cli.js CHANGED
@@ -6,13 +6,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
8
  const node_util_1 = require("node:util");
9
- const typescript_1 = __importDefault(require("typescript"));
9
+ const _1 = require(".");
10
10
  const help_1 = require("./help");
11
- const config_1 = require("./util/config");
12
- const path_1 = require("./util/path");
13
11
  const reporters_1 = __importDefault(require("./reporters"));
14
- const _1 = require(".");
15
- const { values: { help, dir, config: configFilePath = 'knip.json', tsConfig: tsConfigFilePath, include = [], exclude = [], dev: isDev = false, 'no-progress': noProgress = false, reporter = 'symbols', jsdoc = [], 'max-issues': maxIssues = '0', }, } = (0, node_util_1.parseArgs)({
12
+ const { values: { help, dir, config: configFilePath = 'knip.json', tsConfig: tsConfigFilePath, include = [], exclude = [], ignore = [], 'no-gitignore': isNoGitIgnore = false, dev: isDev = false, 'no-progress': noProgress = false, reporter = 'symbols', 'max-issues': maxIssues = '0', jsdoc: jsDoc = [], }, } = (0, node_util_1.parseArgs)({
16
13
  options: {
17
14
  help: { type: 'boolean' },
18
15
  config: { type: 'string', short: 'c' },
@@ -20,9 +17,11 @@ const { values: { help, dir, config: configFilePath = 'knip.json', tsConfig: tsC
20
17
  dir: { type: 'string' },
21
18
  include: { type: 'string', multiple: true },
22
19
  exclude: { type: 'string', multiple: true },
20
+ ignore: { type: 'string', multiple: true },
21
+ 'no-gitignore': { type: 'boolean' },
23
22
  dev: { type: 'boolean' },
24
- 'max-issues': { type: 'string' },
25
23
  'no-progress': { type: 'boolean' },
24
+ 'max-issues': { type: 'string' },
26
25
  reporter: { type: 'string' },
27
26
  jsdoc: { type: 'string', multiple: true },
28
27
  },
@@ -35,63 +34,37 @@ const cwd = process.cwd();
35
34
  const workingDir = dir ? node_path_1.default.resolve(dir) : cwd;
36
35
  const isShowProgress = noProgress === false ? process.stdout.isTTY && typeof process.stdout.cursorTo === 'function' : !noProgress;
37
36
  const printReport = reporter in reporters_1.default ? reporters_1.default[reporter] : require(node_path_1.default.join(workingDir, reporter));
38
- const main = async () => {
39
- const localConfigurationPath = await (0, path_1.findFile)(workingDir, configFilePath);
40
- const manifestPath = await (0, path_1.findFile)(workingDir, 'package.json');
41
- const localConfiguration = localConfigurationPath && require(localConfigurationPath);
42
- const manifest = manifestPath && require(manifestPath);
43
- if (!localConfigurationPath && !manifest.knip) {
44
- const location = workingDir === cwd ? 'current directory' : `${node_path_1.default.relative(cwd, workingDir)} or up.`;
45
- console.error(`Unable to find ${configFilePath} or package.json#knip in ${location}\n`);
46
- (0, help_1.printHelp)();
47
- process.exit(1);
48
- }
49
- const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfiguration, { workingDir: dir, isDev });
50
- if (!resolvedConfig) {
51
- (0, help_1.printHelp)();
52
- process.exit(1);
53
- }
54
- const report = (0, config_1.resolveIncludedIssueGroups)(include, exclude, resolvedConfig);
55
- let tsConfigPaths = [];
56
- const tsConfigPath = await (0, path_1.findFile)(workingDir, tsConfigFilePath ?? 'tsconfig.json');
57
- if (tsConfigFilePath && !tsConfigPath) {
58
- console.error(`Unable to find ${tsConfigFilePath}\n`);
59
- (0, help_1.printHelp)();
60
- process.exit(1);
37
+ const run = async () => {
38
+ try {
39
+ const { report, issues, counters } = await (0, _1.main)({
40
+ cwd,
41
+ workingDir,
42
+ configFilePath,
43
+ tsConfigFilePath,
44
+ include,
45
+ exclude,
46
+ ignore,
47
+ gitignore: !isNoGitIgnore,
48
+ isDev,
49
+ isShowProgress,
50
+ jsDoc,
51
+ });
52
+ printReport({ report, issues, workingDir, isDev });
53
+ const reportGroup = report.files ? 'files' : Object.keys(report).find(key => report[key]);
54
+ const counterGroup = reportGroup === 'unlisted' ? 'unresolved' : reportGroup;
55
+ if (counterGroup) {
56
+ const count = counters[counterGroup];
57
+ if (count > Number(maxIssues))
58
+ process.exit(count);
59
+ }
61
60
  }
62
- if (tsConfigPath) {
63
- const tsConfig = typescript_1.default.readConfigFile(tsConfigPath, typescript_1.default.sys.readFile);
64
- tsConfigPaths = tsConfig.config.compilerOptions?.paths
65
- ? Object.keys(tsConfig.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))
66
- : [];
67
- if (tsConfig.error) {
68
- console.error(`An error occured when reading ${node_path_1.default.relative(cwd, tsConfigPath)}.\n`);
61
+ catch (error) {
62
+ if (error instanceof Error) {
63
+ console.error(error.message + '\n');
69
64
  (0, help_1.printHelp)();
70
65
  process.exit(1);
71
66
  }
72
- }
73
- const config = {
74
- workingDir,
75
- report,
76
- dependencies: Object.keys(manifest.dependencies ?? {}),
77
- devDependencies: Object.keys(manifest.devDependencies ?? {}),
78
- isDev: typeof resolvedConfig.dev === 'boolean' ? resolvedConfig.dev : isDev,
79
- tsConfigFilePath,
80
- tsConfigPaths,
81
- isShowProgress,
82
- jsDocOptions: {
83
- isReadPublicTag: jsdoc.includes('public'),
84
- },
85
- ...resolvedConfig,
86
- };
87
- const { issues, counters } = await (0, _1.run)(config);
88
- printReport({ issues, workingDir, config });
89
- const reportGroup = report.files ? 'files' : Object.keys(report).find(key => report[key]);
90
- const counterGroup = reportGroup === 'unlisted' ? 'unresolved' : reportGroup;
91
- if (counterGroup) {
92
- const count = counters[counterGroup];
93
- if (count > Number(maxIssues))
94
- process.exit(count);
67
+ throw error;
95
68
  }
96
69
  };
97
- main();
70
+ run();
package/dist/help.js CHANGED
@@ -5,16 +5,18 @@ const printHelp = () => {
5
5
  console.log(`knip [options]
6
6
 
7
7
  Options:
8
- -c/--config [file] Configuration file path (default: ./knip.json or package.json#knip)
9
- -t/--tsConfig [file] TypeScript configuration path (default: ./tsconfig.json)
10
- --dir Working directory (default: current working directory)
11
- --include Report only listed issue group(s) (see below)
12
- --exclude Exclude issue group(s) from report (see below)
13
- --dev Include \`devDependencies\` in report(s) (default: false)
14
- --no-progress Don't show dynamic progress updates
15
- --max-issues Maximum number of issues before non-zero exit code (default: 0)
16
- --reporter Select reporter: symbols, compact (default: symbols)
17
- --jsdoc Enable JSDoc parsing, with options: public (default: disabled)
8
+ -c/--config [file] Configuration file path (default: ./knip.json or package.json#knip)
9
+ -t/--tsConfig [file] TypeScript configuration path (default: ./tsconfig.json)
10
+ --dir Working directory (default: current working directory)
11
+ --include Report only listed issue group(s) (see below)
12
+ --exclude Exclude issue group(s) from report (see below)
13
+ --ignore Ignore files matching this glob pattern (can be set multiple times)
14
+ --no-gitignore Don't use .gitignore
15
+ --dev Include \`devDependencies\` in report(s)
16
+ --no-progress Don't show dynamic progress updates
17
+ --max-issues Maximum number of issues before non-zero exit code (default: 0)
18
+ --reporter Select reporter: symbols, compact (default: symbols)
19
+ --jsdoc Enable JSDoc parsing, with options: public
18
20
 
19
21
  Issue groups: files, dependencies, unlisted, exports, nsExports, types, nsTypes, duplicates
20
22
 
@@ -23,6 +25,7 @@ Examples:
23
25
  $ knip
24
26
  $ knip --dir packages/client --include files
25
27
  $ knip -c ./knip.js --reporter compact --jsdoc public
28
+ $ knip --ignore 'lib/**/*.ts' --ignore build
26
29
 
27
30
  More info: https://github.com/webpro/knip`);
28
31
  };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import type { Configuration, Issues } from './types';
2
- export declare function run(configuration: Configuration): Promise<{
3
- issues: Issues;
1
+ import type { UnresolvedConfiguration } from './types';
2
+ export declare const main: (options: UnresolvedConfiguration) => Promise<{
3
+ report: import("./types").Report;
4
+ issues: import("./types").Issues;
4
5
  counters: {
5
6
  files: number;
6
7
  dependencies: number;
package/dist/index.js CHANGED
@@ -3,196 +3,81 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.run = void 0;
6
+ exports.main = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
- const ts_morph_1 = require("ts-morph");
9
- const ts_morph_helpers_1 = require("ts-morph-helpers");
8
+ const typescript_1 = __importDefault(require("typescript"));
9
+ const config_1 = require("./util/config");
10
+ const fs_1 = require("./util/fs");
11
+ const path_1 = require("./util/path");
10
12
  const project_1 = require("./util/project");
11
- const type_1 = require("./util/type");
12
- const dependencies_1 = require("./util/dependencies");
13
- const log_1 = require("./log");
14
- const lineRewriter = new log_1.LineRewriter();
15
- async function run(configuration) {
16
- const { workingDir, isShowProgress, report, isDev, jsDocOptions } = configuration;
17
- const { getUnresolvedDependencies, getUnusedDependencies, getUnusedDevDependencies } = (0, dependencies_1.getDependencyAnalyzer)(configuration);
18
- const production = await (0, project_1.createProject)(configuration, configuration.entryFiles);
13
+ const runner_1 = require("./runner");
14
+ const main = async (options) => {
15
+ const { cwd, workingDir, configFilePath = 'knip.json', tsConfigFilePath, include, exclude, ignore, gitignore, isDev, isShowProgress, jsDoc, } = options;
16
+ const localConfigurationPath = configFilePath && (await (0, fs_1.findFile)(workingDir, configFilePath));
17
+ const manifestPath = await (0, fs_1.findFile)(workingDir, 'package.json');
18
+ const localConfiguration = localConfigurationPath && require(localConfigurationPath);
19
+ const manifest = manifestPath && require(manifestPath);
20
+ if (!localConfigurationPath && !manifest.knip) {
21
+ const location = workingDir === cwd ? 'current directory' : `${node_path_1.default.relative(cwd, workingDir)} or up.`;
22
+ throw new Error(`Unable to find ${configFilePath} or package.json#knip in ${location}`);
23
+ }
24
+ const dir = node_path_1.default.relative(cwd, workingDir);
25
+ const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfiguration, { workingDir: dir, isDev });
26
+ if (!resolvedConfig) {
27
+ throw new Error('Unable to find `entryFiles` and/or `projectFiles` in configuration.');
28
+ }
29
+ const report = (0, config_1.resolveIncludedIssueGroups)(include, exclude, resolvedConfig);
30
+ let tsConfigPaths = [];
31
+ const tsConfigPath = await (0, fs_1.findFile)(workingDir, tsConfigFilePath ?? 'tsconfig.json');
32
+ if (tsConfigFilePath && !tsConfigPath) {
33
+ throw new Error(`Unable to find ${tsConfigFilePath}`);
34
+ }
35
+ if (tsConfigPath) {
36
+ const tsConfig = typescript_1.default.readConfigFile(tsConfigPath, typescript_1.default.sys.readFile);
37
+ tsConfigPaths = tsConfig.config.compilerOptions?.paths
38
+ ? Object.keys(tsConfig.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))
39
+ : [];
40
+ if (tsConfig.error) {
41
+ throw new Error(`An error occured when reading ${node_path_1.default.relative(cwd, tsConfigPath)}`);
42
+ }
43
+ }
44
+ const projectOptions = tsConfigFilePath ? { tsConfigFilePath } : { compilerOptions: { allowJs: true } };
45
+ const entryPaths = await (0, path_1.resolvePaths)({
46
+ cwd,
47
+ workingDir,
48
+ patterns: resolvedConfig.entryFiles,
49
+ ignore,
50
+ gitignore,
51
+ });
52
+ const production = (0, project_1.createProject)({ projectOptions, paths: entryPaths });
19
53
  const entryFiles = production.getSourceFiles();
20
54
  production.resolveSourceFileDependencies();
21
55
  const productionFiles = production.getSourceFiles();
22
- const project = await (0, project_1.createProject)(configuration, configuration.projectFiles);
56
+ const projectPaths = await (0, path_1.resolvePaths)({
57
+ cwd,
58
+ workingDir,
59
+ patterns: resolvedConfig.projectFiles,
60
+ ignore,
61
+ gitignore,
62
+ });
63
+ const project = (0, project_1.createProject)({ projectOptions, paths: projectPaths });
23
64
  const projectFiles = project.getSourceFiles();
24
- const [usedProductionFiles, unreferencedProductionFiles] = (0, project_1.partitionSourceFiles)(projectFiles, productionFiles);
25
- const [usedEntryFiles, usedNonEntryFiles] = (0, project_1.partitionSourceFiles)(usedProductionFiles, entryFiles);
26
- const issues = {
27
- files: new Set(unreferencedProductionFiles.map(file => file.getFilePath())),
28
- dependencies: new Set(),
29
- devDependencies: new Set(),
30
- unresolved: {},
31
- exports: {},
32
- types: {},
33
- nsExports: {},
34
- nsTypes: {},
35
- duplicates: {},
36
- };
37
- const counters = {
38
- files: issues.files.size,
39
- dependencies: issues.dependencies.size,
40
- devDependencies: issues.dependencies.size,
41
- unresolved: 0,
42
- exports: 0,
43
- types: 0,
44
- nsExports: 0,
45
- nsTypes: 0,
46
- duplicates: 0,
47
- processed: issues.files.size,
48
- };
49
- const updateProcessingOutput = (item) => {
50
- if (!isShowProgress)
51
- return;
52
- const counter = unreferencedProductionFiles.length + counters.processed;
53
- const total = unreferencedProductionFiles.length + usedNonEntryFiles.length;
54
- const percentage = Math.floor((counter / total) * 100);
55
- const messages = [(0, log_1.getLine)(`${percentage}%`, `of files processed (${counter} of ${total})`)];
56
- report.files && messages.push((0, log_1.getLine)(unreferencedProductionFiles.length, 'unused files'));
57
- report.unlisted && messages.push((0, log_1.getLine)(counters.unresolved, 'unlisted dependencies'));
58
- report.exports && messages.push((0, log_1.getLine)(counters.exports, 'unused exports'));
59
- report.nsExports && messages.push((0, log_1.getLine)(counters.nsExports, 'unused exports in namespace'));
60
- report.types && messages.push((0, log_1.getLine)(counters.types, 'unused types'));
61
- report.nsTypes && messages.push((0, log_1.getLine)(counters.nsTypes, 'unused types in namespace'));
62
- report.duplicates && messages.push((0, log_1.getLine)(counters.duplicates, 'duplicate exports'));
63
- if (counter < total) {
64
- messages.push('');
65
- messages.push(`Processing: ${node_path_1.default.relative(workingDir, item.filePath)}`);
66
- }
67
- lineRewriter.update(messages);
65
+ const config = {
66
+ workingDir,
67
+ report,
68
+ entryFiles,
69
+ productionFiles,
70
+ projectFiles,
71
+ dependencies: Object.keys(manifest.dependencies ?? {}),
72
+ devDependencies: Object.keys(manifest.devDependencies ?? {}),
73
+ isDev: typeof resolvedConfig.dev === 'boolean' ? resolvedConfig.dev : isDev,
74
+ tsConfigPaths,
75
+ isShowProgress,
76
+ jsDocOptions: {
77
+ isReadPublicTag: jsDoc.includes('public'),
78
+ },
68
79
  };
69
- const addSymbolIssue = (issueType, issue) => {
70
- const { filePath, symbol } = issue;
71
- const key = node_path_1.default.relative(workingDir, filePath);
72
- issues[issueType][key] = issues[issueType][key] ?? {};
73
- issues[issueType][key][symbol] = issue;
74
- counters[issueType]++;
75
- updateProcessingOutput(issue);
76
- };
77
- const addProjectIssue = (issueType, issue) => {
78
- if (!issues[issueType].has(issue.symbol)) {
79
- issues[issueType].add(issue.symbol);
80
- counters[issueType]++;
81
- }
82
- updateProcessingOutput(issue);
83
- };
84
- if (report.dependencies || report.unlisted) {
85
- usedEntryFiles.forEach(sourceFile => {
86
- const unresolvedDependencies = getUnresolvedDependencies(sourceFile);
87
- unresolvedDependencies.forEach(issue => addSymbolIssue('unresolved', issue));
88
- });
89
- }
90
- if (report.dependencies ||
91
- report.unlisted ||
92
- report.exports ||
93
- report.types ||
94
- report.nsExports ||
95
- report.nsTypes ||
96
- report.duplicates) {
97
- usedNonEntryFiles.forEach(sourceFile => {
98
- const filePath = sourceFile.getFilePath();
99
- if (report.dependencies || report.unlisted) {
100
- const unresolvedDependencies = getUnresolvedDependencies(sourceFile);
101
- unresolvedDependencies.forEach(issue => addSymbolIssue('unresolved', issue));
102
- }
103
- const exportDeclarations = sourceFile.getExportedDeclarations();
104
- if (report.duplicates) {
105
- const duplicateExports = (0, ts_morph_helpers_1.findDuplicateExportedNames)(sourceFile);
106
- duplicateExports.forEach(symbols => {
107
- const symbol = symbols.join('|');
108
- addSymbolIssue('duplicates', { filePath, symbol, symbols });
109
- });
110
- }
111
- if (report.exports || report.types || report.nsExports || report.nsTypes) {
112
- const uniqueExportedSymbols = new Set([...exportDeclarations.values()].flat());
113
- if (uniqueExportedSymbols.size === 1)
114
- return;
115
- exportDeclarations.forEach(declarations => {
116
- declarations.forEach(declaration => {
117
- const type = (0, type_1.getType)(declaration);
118
- if (!report.nsExports && !report.nsTypes) {
119
- if (!report.types && type)
120
- return;
121
- if (!report.exports && !type)
122
- return;
123
- }
124
- if (jsDocOptions.isReadPublicTag && ts_morph_1.ts.getJSDocPublicTag(declaration.compilerNode))
125
- return;
126
- let identifier;
127
- if (declaration.isKind(ts_morph_1.ts.SyntaxKind.Identifier)) {
128
- identifier = declaration;
129
- }
130
- else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.ArrowFunction)) {
131
- }
132
- else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.FunctionDeclaration) ||
133
- declaration.isKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration) ||
134
- declaration.isKind(ts_morph_1.ts.SyntaxKind.TypeAliasDeclaration) ||
135
- declaration.isKind(ts_morph_1.ts.SyntaxKind.InterfaceDeclaration) ||
136
- declaration.isKind(ts_morph_1.ts.SyntaxKind.EnumDeclaration)) {
137
- identifier = declaration.getFirstChildByKindOrThrow(ts_morph_1.ts.SyntaxKind.Identifier);
138
- }
139
- else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.PropertyAccessExpression)) {
140
- identifier = declaration.getLastChildByKindOrThrow(ts_morph_1.ts.SyntaxKind.Identifier);
141
- }
142
- else {
143
- identifier = declaration.getFirstDescendantByKind(ts_morph_1.ts.SyntaxKind.Identifier);
144
- }
145
- if (identifier) {
146
- const identifierText = identifier.getText();
147
- if (report.exports && issues.exports[filePath]?.[identifierText])
148
- return;
149
- if (report.types && issues.types[filePath]?.[identifierText])
150
- return;
151
- if (report.nsExports && issues.nsExports[filePath]?.[identifierText])
152
- return;
153
- if (report.nsTypes && issues.nsTypes[filePath]?.[identifierText])
154
- return;
155
- const refs = identifier.findReferences();
156
- if (refs.length === 0) {
157
- addSymbolIssue('exports', { filePath, symbol: identifierText });
158
- }
159
- else {
160
- const refFiles = new Set(refs.map(r => r.compilerObject.references.map(r => r.fileName)).flat());
161
- const isReferencedOnlyBySelf = refFiles.size === 1 && [...refFiles][0] === filePath;
162
- if (!isReferencedOnlyBySelf)
163
- return;
164
- if ((0, ts_morph_helpers_1.findReferencingNamespaceNodes)(sourceFile).length > 0) {
165
- if (type) {
166
- addSymbolIssue('nsTypes', { filePath, symbol: identifierText, symbolType: type });
167
- }
168
- else {
169
- addSymbolIssue('nsExports', { filePath, symbol: identifierText });
170
- }
171
- }
172
- else if (type) {
173
- addSymbolIssue('types', { filePath, symbol: identifierText, symbolType: type });
174
- }
175
- else {
176
- addSymbolIssue('exports', { filePath, symbol: identifierText });
177
- }
178
- }
179
- }
180
- });
181
- });
182
- }
183
- counters.processed++;
184
- });
185
- }
186
- if (report.dependencies) {
187
- const unusedDependencies = getUnusedDependencies();
188
- unusedDependencies.forEach(symbol => addProjectIssue('dependencies', { filePath: '', symbol }));
189
- if (isDev) {
190
- const unusedDevDependencies = getUnusedDevDependencies();
191
- unusedDevDependencies.forEach(symbol => addProjectIssue('devDependencies', { filePath: '', symbol }));
192
- }
193
- }
194
- if (isShowProgress)
195
- lineRewriter.resetLines();
196
- return { issues, counters };
197
- }
198
- exports.run = run;
80
+ const { issues, counters } = await (0, runner_1.findIssues)(config);
81
+ return { report, issues, counters };
82
+ };
83
+ exports.main = main;
@@ -1,7 +1,8 @@
1
- import type { Issues, Configuration } from '../types';
2
- declare const _default: ({ issues, config, workingDir }: {
1
+ import type { Issues, Report } from '../types';
2
+ declare const _default: ({ report, issues, workingDir, isDev, }: {
3
+ report: Report;
3
4
  issues: Issues;
4
- config: Configuration;
5
5
  workingDir: string;
6
+ isDev: boolean;
6
7
  }) => void;
7
8
  export default _default;
@@ -26,8 +26,7 @@ const logIssueGroupResults = (issues, workingDir, title) => {
26
26
  console.log('Not found');
27
27
  }
28
28
  };
29
- exports.default = ({ issues, config, workingDir }) => {
30
- const { report, isDev } = config;
29
+ exports.default = ({ report, issues, workingDir, isDev, }) => {
31
30
  const reportMultipleGroups = Object.values(report).filter(Boolean).length > 1;
32
31
  if (report.files) {
33
32
  const unreferencedFiles = Array.from(issues.files);
@@ -1,13 +1,15 @@
1
1
  declare const _default: {
2
- symbols: ({ issues, config, workingDir }: {
2
+ symbols: ({ report, issues, workingDir, isDev, }: {
3
+ report: import("../types").Report;
3
4
  issues: import("../types").Issues;
4
- config: import("../types").Configuration;
5
5
  workingDir: string;
6
+ isDev: boolean;
6
7
  }) => void;
7
- compact: ({ issues, config, workingDir }: {
8
+ compact: ({ report, issues, workingDir, isDev, }: {
9
+ report: import("../types").Report;
8
10
  issues: import("../types").Issues;
9
- config: import("../types").Configuration;
10
11
  workingDir: string;
12
+ isDev: boolean;
11
13
  }) => void;
12
14
  };
13
15
  export default _default;
@@ -1,7 +1,8 @@
1
- import type { Issues, Configuration } from '../types';
2
- declare const _default: ({ issues, config, workingDir }: {
1
+ import type { Issues, Report } from '../types';
2
+ declare const _default: ({ report, issues, workingDir, isDev, }: {
3
+ report: Report;
3
4
  issues: Issues;
4
- config: Configuration;
5
5
  workingDir: string;
6
+ isDev: boolean;
6
7
  }) => void;
7
8
  export default _default;
@@ -28,8 +28,7 @@ const logIssueGroupResults = (issues, workingDir, title) => {
28
28
  console.log('Not found');
29
29
  }
30
30
  };
31
- exports.default = ({ issues, config, workingDir }) => {
32
- const { report, isDev } = config;
31
+ exports.default = ({ report, issues, workingDir, isDev, }) => {
33
32
  const reportMultipleGroups = Object.values(report).filter(Boolean).length > 1;
34
33
  if (report.files) {
35
34
  const unreferencedFiles = Array.from(issues.files);
@@ -0,0 +1,16 @@
1
+ import type { Configuration, Issues } from './types';
2
+ export declare function findIssues(configuration: Configuration): Promise<{
3
+ issues: Issues;
4
+ counters: {
5
+ files: number;
6
+ dependencies: number;
7
+ devDependencies: number;
8
+ unresolved: number;
9
+ exports: number;
10
+ types: number;
11
+ nsExports: number;
12
+ nsTypes: number;
13
+ duplicates: number;
14
+ processed: number;
15
+ };
16
+ }>;
package/dist/runner.js ADDED
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.findIssues = void 0;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const ts_morph_1 = require("ts-morph");
9
+ const ts_morph_helpers_1 = require("ts-morph-helpers");
10
+ const project_1 = require("./util/project");
11
+ const type_1 = require("./util/type");
12
+ const dependencies_1 = require("./util/dependencies");
13
+ const log_1 = require("./log");
14
+ const lineRewriter = new log_1.LineRewriter();
15
+ async function findIssues(configuration) {
16
+ const { workingDir, isShowProgress, report, isDev, jsDocOptions } = configuration;
17
+ const { entryFiles, productionFiles, projectFiles } = configuration;
18
+ const { getUnresolvedDependencies, getUnusedDependencies, getUnusedDevDependencies } = (0, dependencies_1.getDependencyAnalyzer)(configuration);
19
+ const [usedProductionFiles, unreferencedProductionFiles] = (0, project_1.partitionSourceFiles)(projectFiles, productionFiles);
20
+ const [usedEntryFiles, usedNonEntryFiles] = (0, project_1.partitionSourceFiles)(usedProductionFiles, entryFiles);
21
+ const issues = {
22
+ files: new Set(unreferencedProductionFiles.map(file => file.getFilePath())),
23
+ dependencies: new Set(),
24
+ devDependencies: new Set(),
25
+ unresolved: {},
26
+ exports: {},
27
+ types: {},
28
+ nsExports: {},
29
+ nsTypes: {},
30
+ duplicates: {},
31
+ };
32
+ const counters = {
33
+ files: issues.files.size,
34
+ dependencies: issues.dependencies.size,
35
+ devDependencies: issues.dependencies.size,
36
+ unresolved: 0,
37
+ exports: 0,
38
+ types: 0,
39
+ nsExports: 0,
40
+ nsTypes: 0,
41
+ duplicates: 0,
42
+ processed: issues.files.size,
43
+ };
44
+ const updateProcessingOutput = (item) => {
45
+ if (!isShowProgress)
46
+ return;
47
+ const counter = unreferencedProductionFiles.length + counters.processed;
48
+ const total = unreferencedProductionFiles.length + usedNonEntryFiles.length;
49
+ const percentage = Math.floor((counter / total) * 100);
50
+ const messages = [(0, log_1.getLine)(`${percentage}%`, `of files processed (${counter} of ${total})`)];
51
+ report.files && messages.push((0, log_1.getLine)(unreferencedProductionFiles.length, 'unused files'));
52
+ report.unlisted && messages.push((0, log_1.getLine)(counters.unresolved, 'unlisted dependencies'));
53
+ report.exports && messages.push((0, log_1.getLine)(counters.exports, 'unused exports'));
54
+ report.nsExports && messages.push((0, log_1.getLine)(counters.nsExports, 'unused exports in namespace'));
55
+ report.types && messages.push((0, log_1.getLine)(counters.types, 'unused types'));
56
+ report.nsTypes && messages.push((0, log_1.getLine)(counters.nsTypes, 'unused types in namespace'));
57
+ report.duplicates && messages.push((0, log_1.getLine)(counters.duplicates, 'duplicate exports'));
58
+ if (counter < total) {
59
+ messages.push('');
60
+ messages.push(`Processing: ${node_path_1.default.relative(workingDir, item.filePath)}`);
61
+ }
62
+ lineRewriter.update(messages);
63
+ };
64
+ const addSymbolIssue = (issueType, issue) => {
65
+ const { filePath, symbol } = issue;
66
+ const key = node_path_1.default.relative(workingDir, filePath);
67
+ issues[issueType][key] = issues[issueType][key] ?? {};
68
+ issues[issueType][key][symbol] = issue;
69
+ counters[issueType]++;
70
+ updateProcessingOutput(issue);
71
+ };
72
+ const addProjectIssue = (issueType, issue) => {
73
+ if (!issues[issueType].has(issue.symbol)) {
74
+ issues[issueType].add(issue.symbol);
75
+ counters[issueType]++;
76
+ }
77
+ updateProcessingOutput(issue);
78
+ };
79
+ if (report.dependencies || report.unlisted) {
80
+ usedEntryFiles.forEach(sourceFile => {
81
+ const unresolvedDependencies = getUnresolvedDependencies(sourceFile);
82
+ unresolvedDependencies.forEach(issue => addSymbolIssue('unresolved', issue));
83
+ });
84
+ }
85
+ if (report.dependencies ||
86
+ report.unlisted ||
87
+ report.exports ||
88
+ report.types ||
89
+ report.nsExports ||
90
+ report.nsTypes ||
91
+ report.duplicates) {
92
+ usedNonEntryFiles.forEach(sourceFile => {
93
+ const filePath = sourceFile.getFilePath();
94
+ if (report.dependencies || report.unlisted) {
95
+ const unresolvedDependencies = getUnresolvedDependencies(sourceFile);
96
+ unresolvedDependencies.forEach(issue => addSymbolIssue('unresolved', issue));
97
+ }
98
+ const exportDeclarations = sourceFile.getExportedDeclarations();
99
+ if (report.duplicates) {
100
+ const duplicateExports = (0, ts_morph_helpers_1.findDuplicateExportedNames)(sourceFile);
101
+ duplicateExports.forEach(symbols => {
102
+ const symbol = symbols.join('|');
103
+ addSymbolIssue('duplicates', { filePath, symbol, symbols });
104
+ });
105
+ }
106
+ if (report.exports || report.types || report.nsExports || report.nsTypes) {
107
+ const uniqueExportedSymbols = new Set([...exportDeclarations.values()].flat());
108
+ if (uniqueExportedSymbols.size === 1)
109
+ return;
110
+ exportDeclarations.forEach(declarations => {
111
+ declarations.forEach(declaration => {
112
+ const type = (0, type_1.getType)(declaration);
113
+ if (!report.nsExports && !report.nsTypes) {
114
+ if (!report.types && type)
115
+ return;
116
+ if (!report.exports && !type)
117
+ return;
118
+ }
119
+ if (jsDocOptions.isReadPublicTag && ts_morph_1.ts.getJSDocPublicTag(declaration.compilerNode))
120
+ return;
121
+ let identifier;
122
+ if (declaration.isKind(ts_morph_1.ts.SyntaxKind.Identifier)) {
123
+ identifier = declaration;
124
+ }
125
+ else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.ArrowFunction)) {
126
+ }
127
+ else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.FunctionDeclaration) ||
128
+ declaration.isKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration) ||
129
+ declaration.isKind(ts_morph_1.ts.SyntaxKind.TypeAliasDeclaration) ||
130
+ declaration.isKind(ts_morph_1.ts.SyntaxKind.InterfaceDeclaration) ||
131
+ declaration.isKind(ts_morph_1.ts.SyntaxKind.EnumDeclaration)) {
132
+ identifier = declaration.getFirstChildByKindOrThrow(ts_morph_1.ts.SyntaxKind.Identifier);
133
+ }
134
+ else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.PropertyAccessExpression)) {
135
+ identifier = declaration.getLastChildByKindOrThrow(ts_morph_1.ts.SyntaxKind.Identifier);
136
+ }
137
+ else {
138
+ identifier = declaration.getFirstDescendantByKind(ts_morph_1.ts.SyntaxKind.Identifier);
139
+ }
140
+ if (identifier) {
141
+ const identifierText = identifier.getText();
142
+ if (report.exports && issues.exports[filePath]?.[identifierText])
143
+ return;
144
+ if (report.types && issues.types[filePath]?.[identifierText])
145
+ return;
146
+ if (report.nsExports && issues.nsExports[filePath]?.[identifierText])
147
+ return;
148
+ if (report.nsTypes && issues.nsTypes[filePath]?.[identifierText])
149
+ return;
150
+ const refs = identifier.findReferences();
151
+ if (refs.length === 0) {
152
+ addSymbolIssue('exports', { filePath, symbol: identifierText });
153
+ }
154
+ else {
155
+ const refFiles = new Set(refs.map(r => r.compilerObject.references.map(r => r.fileName)).flat());
156
+ const isReferencedOnlyBySelf = refFiles.size === 1 && [...refFiles][0] === filePath;
157
+ if (!isReferencedOnlyBySelf)
158
+ return;
159
+ if ((0, ts_morph_helpers_1.findReferencingNamespaceNodes)(sourceFile).length > 0) {
160
+ if (type) {
161
+ addSymbolIssue('nsTypes', { filePath, symbol: identifierText, symbolType: type });
162
+ }
163
+ else {
164
+ addSymbolIssue('nsExports', { filePath, symbol: identifierText });
165
+ }
166
+ }
167
+ else if (type) {
168
+ addSymbolIssue('types', { filePath, symbol: identifierText, symbolType: type });
169
+ }
170
+ else {
171
+ addSymbolIssue('exports', { filePath, symbol: identifierText });
172
+ }
173
+ }
174
+ }
175
+ });
176
+ });
177
+ }
178
+ counters.processed++;
179
+ });
180
+ }
181
+ if (report.dependencies) {
182
+ const unusedDependencies = getUnusedDependencies();
183
+ unusedDependencies.forEach(symbol => addProjectIssue('dependencies', { filePath: '', symbol }));
184
+ if (isDev) {
185
+ const unusedDevDependencies = getUnusedDevDependencies();
186
+ unusedDevDependencies.forEach(symbol => addProjectIssue('devDependencies', { filePath: '', symbol }));
187
+ }
188
+ }
189
+ if (isShowProgress)
190
+ lineRewriter.resetLines();
191
+ return { issues, counters };
192
+ }
193
+ exports.findIssues = findIssues;
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { SourceFile } from 'ts-morph';
1
2
  declare type SymbolType = 'type' | 'interface' | 'enum';
2
3
  declare type UnusedFileIssues = Set<string>;
3
4
  declare type UnusedExportIssues = Record<string, Record<string, Issue>>;
@@ -20,31 +21,45 @@ export declare type Issues = {
20
21
  nsTypes: UnusedExportIssues;
21
22
  duplicates: UnusedExportIssues;
22
23
  };
23
- export declare type IssueType = keyof Issues;
24
+ declare type IssueType = keyof Issues;
24
25
  export declare type ProjectIssueType = Extract<IssueType, 'files' | 'dependencies' | 'devDependencies'>;
25
26
  export declare type SymbolIssueType = Exclude<IssueType, ProjectIssueType>;
26
27
  export declare type IssueGroup = 'files' | 'dependencies' | 'unlisted' | 'exports' | 'nsExports' | 'types' | 'nsTypes' | 'duplicates';
27
- export declare type BaseLocalConfiguration = {
28
+ declare type BaseLocalConfiguration = {
28
29
  entryFiles: string[];
29
30
  projectFiles: string[];
30
31
  };
31
32
  export declare type LocalConfiguration = BaseLocalConfiguration & {
32
33
  dev?: boolean | BaseLocalConfiguration;
33
- entryFiles: string[];
34
- projectFiles: string[];
35
34
  include?: string[];
36
35
  exclude?: string[];
37
36
  };
38
37
  export declare type ImportedConfiguration = LocalConfiguration | Record<string, LocalConfiguration>;
39
- export declare type Configuration = LocalConfiguration & {
38
+ export declare type UnresolvedConfiguration = {
39
+ cwd: string;
40
40
  workingDir: string;
41
- report: {
42
- [key in IssueGroup]: boolean;
43
- };
41
+ configFilePath?: string;
42
+ tsConfigFilePath?: string;
43
+ include: string[];
44
+ exclude: string[];
45
+ ignore: string[];
46
+ gitignore: boolean;
47
+ isDev: boolean;
48
+ isShowProgress: boolean;
49
+ jsDoc: string[];
50
+ };
51
+ export declare type Report = {
52
+ [key in IssueGroup]: boolean;
53
+ };
54
+ export declare type Configuration = {
55
+ workingDir: string;
56
+ report: Report;
57
+ projectFiles: SourceFile[];
58
+ productionFiles: SourceFile[];
59
+ entryFiles: SourceFile[];
44
60
  dependencies: string[];
45
61
  devDependencies: string[];
46
62
  isDev: boolean;
47
- tsConfigFilePath: undefined | string;
48
63
  tsConfigPaths: string[];
49
64
  isShowProgress: boolean;
50
65
  jsDocOptions: {
@@ -3,13 +3,4 @@ export declare const resolveConfig: (importedConfiguration: ImportedConfiguratio
3
3
  workingDir?: string;
4
4
  isDev?: boolean;
5
5
  }) => LocalConfiguration | undefined;
6
- export declare const resolveIncludedIssueGroups: (includeArg: string[], excludeArg: string[], resolvedConfig?: LocalConfiguration) => {
7
- files: boolean;
8
- dependencies: boolean;
9
- exports: boolean;
10
- types: boolean;
11
- nsExports: boolean;
12
- nsTypes: boolean;
13
- duplicates: boolean;
14
- unlisted: boolean;
15
- };
6
+ export declare const resolveIncludedIssueGroups: (includeArg: string[], excludeArg: string[], resolvedConfig?: LocalConfiguration) => import("../types").Report;
@@ -19,8 +19,7 @@ const resolveConfig = (importedConfiguration, options) => {
19
19
  resolvedConfig = resolvedConfig.dev;
20
20
  }
21
21
  if (!resolvedConfig.entryFiles || !resolvedConfig.projectFiles) {
22
- console.error('Unable to find `entryFiles` and/or `projectFiles` in configuration.');
23
- console.info(`Add these properties at root level, or use --cwd and match one of: ${configKeys.join(', ')}\n`);
22
+ console.info(`Add these properties at root level, or use --dir and match one of: ${configKeys.join(', ')}\n`);
24
23
  return;
25
24
  }
26
25
  return resolvedConfig;
@@ -0,0 +1,2 @@
1
+ export declare const isFile: (filePath: string) => Promise<boolean>;
2
+ export declare const findFile: (cwd: string, fileName: string) => Promise<string | undefined>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.findFile = exports.isFile = void 0;
7
+ const promises_1 = __importDefault(require("node:fs/promises"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const isFile = async (filePath) => {
10
+ try {
11
+ const stats = await promises_1.default.stat(filePath);
12
+ return stats.isFile();
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ };
18
+ exports.isFile = isFile;
19
+ const findFile = async (cwd, fileName) => {
20
+ const filePath = node_path_1.default.join(cwd, fileName);
21
+ if (await (0, exports.isFile)(filePath)) {
22
+ return filePath;
23
+ }
24
+ else {
25
+ const parentDir = node_path_1.default.resolve(cwd, '..');
26
+ return parentDir === '/' ? undefined : (0, exports.findFile)(parentDir, fileName);
27
+ }
28
+ };
29
+ exports.findFile = findFile;
@@ -1 +1,8 @@
1
- export declare const findFile: (cwd: string, fileName: string) => Promise<string | undefined>;
1
+ export declare const prependDirToPattern: (workingDir: string, pattern: string) => string;
2
+ export declare const resolvePaths: ({ cwd, workingDir, patterns, ignore, gitignore, }: {
3
+ cwd: string;
4
+ workingDir: string;
5
+ patterns: string[];
6
+ ignore: string[];
7
+ gitignore: boolean;
8
+ }) => Promise<string[]>;
package/dist/util/path.js CHANGED
@@ -3,26 +3,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.findFile = void 0;
7
- const promises_1 = __importDefault(require("node:fs/promises"));
6
+ exports.resolvePaths = exports.prependDirToPattern = void 0;
8
7
  const node_path_1 = __importDefault(require("node:path"));
9
- const isFile = async (filePath) => {
10
- try {
11
- const stats = await promises_1.default.stat(filePath);
12
- return stats.isFile();
13
- }
14
- catch {
15
- return false;
8
+ let _globby;
9
+ const glob = async function (patterns, options) {
10
+ if (!_globby) {
11
+ const { globby } = await eval('import("globby")');
12
+ _globby = globby;
16
13
  }
14
+ return _globby(patterns, options);
17
15
  };
18
- const findFile = async (cwd, fileName) => {
19
- const filePath = node_path_1.default.join(cwd, fileName);
20
- if (await isFile(filePath)) {
21
- return filePath;
22
- }
23
- else {
24
- const parentDir = node_path_1.default.resolve(cwd, '..');
25
- return parentDir === '/' ? undefined : (0, exports.findFile)(parentDir, fileName);
26
- }
16
+ const prependDirToPattern = (workingDir, pattern) => {
17
+ if (pattern.startsWith('!'))
18
+ return '!' + node_path_1.default.join(workingDir, pattern.slice(1));
19
+ return node_path_1.default.join(workingDir, pattern);
27
20
  };
28
- exports.findFile = findFile;
21
+ exports.prependDirToPattern = prependDirToPattern;
22
+ const resolvePaths = async ({ cwd, workingDir, patterns, ignore, gitignore, }) => glob(patterns.map(pattern => (0, exports.prependDirToPattern)(node_path_1.default.relative(cwd, workingDir), pattern)), {
23
+ cwd,
24
+ ignore,
25
+ gitignore,
26
+ absolute: true,
27
+ });
28
+ exports.resolvePaths = resolvePaths;
@@ -1,5 +1,7 @@
1
1
  import { Project } from 'ts-morph';
2
- import type { SourceFile } from 'ts-morph';
3
- import type { Configuration } from '../types';
4
- export declare const createProject: (configuration: Configuration, paths?: string | string[]) => Promise<Project>;
2
+ import type { ProjectOptions, SourceFile } from 'ts-morph';
3
+ export declare const createProject: ({ projectOptions, paths }: {
4
+ projectOptions: ProjectOptions;
5
+ paths?: string[] | undefined;
6
+ }) => Project;
5
7
  export declare const partitionSourceFiles: (projectFiles: SourceFile[], productionFiles: SourceFile[]) => SourceFile[][];
@@ -1,28 +1,15 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.partitionSourceFiles = exports.createProject = void 0;
7
- const node_path_1 = __importDefault(require("node:path"));
8
4
  const ts_morph_1 = require("ts-morph");
9
- const resolvePaths = (cwd, patterns) => {
10
- return [patterns].flat().map(pattern => {
11
- if (pattern.startsWith('!'))
12
- return '!' + node_path_1.default.join(cwd, pattern.slice(1));
13
- return node_path_1.default.join(cwd, pattern);
14
- });
15
- };
16
- const createProject = async (configuration, paths) => {
17
- const { tsConfigFilePath, workingDir } = configuration;
18
- const tsConfig = tsConfigFilePath ? { tsConfigFilePath } : { compilerOptions: { allowJs: true } };
5
+ const createProject = ({ projectOptions, paths }) => {
19
6
  const workspace = new ts_morph_1.Project({
20
- ...tsConfig,
7
+ ...projectOptions,
21
8
  skipAddingFilesFromTsConfig: true,
22
9
  skipFileDependencyResolution: true,
23
10
  });
24
11
  if (paths)
25
- workspace.addSourceFilesAtPaths(resolvePaths(workingDir, paths));
12
+ workspace.addSourceFilesAtPaths(paths);
26
13
  return workspace;
27
14
  };
28
15
  exports.createProject = createProject;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Find unused files and exports in your TypeScript project",
5
5
  "keywords": [
6
6
  "find",
@@ -38,6 +38,7 @@
38
38
  },
39
39
  "license": "ISC",
40
40
  "dependencies": {
41
+ "globby": "13.1.2",
41
42
  "is-builtin-module": "3.2.0",
42
43
  "micromatch": "4.0.5",
43
44
  "ts-morph": "16.0.0",