knip 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
 
@@ -22,12 +22,14 @@ This is where Knip comes in:
22
22
  - [x] Supports JavaScript-only projects using ESM (without a `tsconfig.json`)
23
23
 
24
24
  Knip really shines in larger projects where you have non-production files (such as `/docs`, `/tools` and `/scripts`).
25
- The `includes` setting in `tsconfig.json` is often too broad, resulting in too many false negatives. Similar projects
26
- either detect only unimported files, or only unused exports. Most of them don't work by configuring entry files, an
27
- essential feature to produce good results. This also allows to unleash knip on a specific part of your project, and work
28
- these separately.
25
+ The `includes` setting in `tsconfig.json` is often too broad, resulting in too many false negatives. To produce good
26
+ results it's essential to configure entry files. A comparison with similar toos answers the question
27
+ [Why yet another unused file/dependency/export finder?](#why-yet-another-unused-filedependencyexport-finder)
29
28
 
30
- ✂️ Knip is another fresh take on keeping your projects clean & tidy!
29
+ Knip is a fresh take on keeping your projects clean & tidy!
30
+
31
+ [![An orange cow with scissors, Van Gogh style](./assets/cow-with-orange-scissors-van-gogh-style.webp)](https://labs.openai.com/s/xZQACaLepaKya0PRUPtIN5dC)
32
+ <sup>_“An orange cow with scissors, Van Gogh style” - generated with OpenAI_</sup>
31
33
 
32
34
  ## Installation
33
35
 
@@ -142,7 +144,7 @@ As always, make sure to backup files or use Git before deleting files or making
142
144
 
143
145
  The default configuration for Knip is very strict and targets production code. For best results, it is recommended to
144
146
  exclude files such as tests from the project files. Here's why: when including tests and other non-production files,
145
- they may prevent production files from being reported as unused.
147
+ they may import production files, which will prevent them from being reported as unused.
146
148
 
147
149
  Excluding non-production files from the `projectFiles` allows Knip to understand what production code can be removed
148
150
  (including dependent files!).
@@ -162,8 +164,7 @@ and add `dev: true` to a file named such as `knip.dev.json`:
162
164
  }
163
165
  ```
164
166
 
165
- Use `-c knip.dev.json` and unused files and exports for the combined set of files as configured in `entryFiles` will be
166
- reported.
167
+ Now use `-c knip.dev.json` to find unused files and exports for the combined set of files as configured in `entryFiles`.
167
168
 
168
169
  An alternative way to store `dev` configuration is in this example `package.json`:
169
170
 
@@ -192,7 +193,7 @@ This way, the `--dev` flag will use the `dev` options (and also add `devDependen
192
193
 
193
194
  #### Separate packages
194
195
 
195
- In repos with multiple (published) packages, the `--cwd` option comes in handy. With similar package structures, the
196
+ In repos with multiple (published) packages, the `--dir` option comes in handy. With similar package structures, the
196
197
  packages can be configured using globs:
197
198
 
198
199
  ```json
@@ -209,8 +210,8 @@ Packages can also be explicitly configured per package directory.
209
210
  To scan the packages separately, using the first match from the configuration file:
210
211
 
211
212
  ```
212
- knip --cwd packages/client
213
- knip --cwd packages/services
213
+ knip --dir packages/client
214
+ knip --dir packages/services
214
215
  ```
215
216
 
216
217
  #### Connected projects
@@ -296,16 +297,48 @@ src/components/Registration.tsx: Registration, default
296
297
  src/components/Products.tsx: ProductsList, default
297
298
  ```
298
299
 
299
- ## Why Yet Another unused file/export finder?
300
-
301
- There are some fine modules available in the same category:
302
-
303
- - [unimported](https://github.com/smeijer/unimported)
304
- - [ts-unused-exports](https://github.com/pzavolinsky/ts-unused-exports)
305
- - [no-unused-export](https://github.com/plantain-00/no-unused-export)
306
- - [ts-prune](https://github.com/nadeesha/ts-prune)
307
- - [find-unused-exports](https://github.com/jaydenseric/find-unused-exports)
308
-
309
- However, the results where not always accurate, and none of them tick my boxes to find both unused files and exports. Or
310
- let me configure entry files and scope the project files for clean results. Especially for larger projects this kind of
311
- configuration is necessary. That's why I took another stab at it.
300
+ ## Why Yet Another unused file/dependency/export finder?
301
+
302
+ There are already some great packages available. Getting good results when finding unused files, dependencies and
303
+ exports is not trivial. Repositories don't seem to get any smaller and with the rise of monorepos even more so. Tools
304
+ like this need to analyze potentially many and/or large files, which is memory and time-consuming. Although I normally
305
+ try to stick to the Unix philosophy, here I believe it's efficient to merge these issue reports into a single tool. When
306
+ building a dependency graph of the project, an abstract syntax tree for each file, and traversing all of this, why not
307
+ collect the various issues in one go?
308
+
309
+ ## Comparison
310
+
311
+ This table is a work in progress, but here's a first impression. Based on their docs (please report any mistakes):
312
+
313
+ | Feature | **knip** | [depcheck][1] | [unimported][2] | [ts-unused-exports][3] | [ts-prune][4] | [find-unused-exports][5] |
314
+ | --------------------------- | :--------: | :-----------: | :-------------: | :--------------------: | :-----------: | :----------------------: |
315
+ | Unused files | ✅ | - | ✅ | - | - | - |
316
+ | Unused dependencies | ✅ | ✅ | ✅ | - | - | - |
317
+ | Unlisted dependencies | ✅ | ✅ | ✅ | - | - | - |
318
+ | Custom dependency resolvers | ❌ [#7][6] | ✅ | ❌ (1) | - | - | - |
319
+ | Unused exports | ✅ | - | - | ✅ | ✅ | ✅ |
320
+ | Duplicate exports | ✅ | - | - | ❌ | ❌ | ❌ |
321
+ | Search namespaces | ✅ | - | - | ✅ | ❌ | ❌ |
322
+ | Custom reporters | ✅ | - | - | - | - | - |
323
+ | Pure JavaScript/ESM | ✅ | ✅ | ✅ | - | - | ✅ |
324
+ | Configure entry files | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
325
+ | Support monorepo | 🟠 (2) | - | - | - | - | - |
326
+ | ESLint plugin available | - | - | - | ✅ | - | - |
327
+
328
+ ✅ = Supported, ❌ = Not supported, - = Out of scope
329
+
330
+ 1. unimported is strict and works based on production files and `dependencies`, so does not have custom dependency
331
+ resolvers which are usually only needed for `devDependencies`.
332
+ 2. knip wants to [support monorepos](#monorepos) properly, the first steps in this direction are implemented.
333
+
334
+ ## Knip?!
335
+
336
+ Knip is Dutch for a "cut". A Dutch expression is "to be ge**knip**t for something", which means to be perfectly suited
337
+ for the job. I'm motivated to make knip perfectly suited for the job of cutting projects to perfection! ✂️
338
+
339
+ [1]: https://github.com/depcheck/depcheck
340
+ [2]: https://github.com/smeijer/unimported
341
+ [3]: https://github.com/pzavolinsky/ts-unused-exports
342
+ [4]: https://github.com/nadeesha/ts-prune
343
+ [5]: https://github.com/jaydenseric/find-unused-exports
344
+ [6]: https://github.com/webpro/knip/issues/7
package/dist/cli.js CHANGED
@@ -6,14 +6,11 @@ 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 fs_1 = require("./util/fs");
13
- const ignore_1 = require("./util/ignore");
14
11
  const reporters_1 = __importDefault(require("./reporters"));
15
- const _1 = require(".");
16
- const { values: { help, dir, config: configFilePath = 'knip.json', tsConfig: tsConfigFilePath, include = [], exclude = [], dev: isDev = false, 'no-progress': noProgress = false, ignore = [], 'no-gitignore': isNoGitIgnore = false, reporter = 'symbols', jsdoc = [], 'max-issues': maxIssues = '0', }, } = (0, node_util_1.parseArgs)({
12
+ const errors_1 = require("./util/errors");
13
+ 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)({
17
14
  options: {
18
15
  help: { type: 'boolean' },
19
16
  config: { type: 'string', short: 'c' },
@@ -21,11 +18,11 @@ const { values: { help, dir, config: configFilePath = 'knip.json', tsConfig: tsC
21
18
  dir: { type: 'string' },
22
19
  include: { type: 'string', multiple: true },
23
20
  exclude: { type: 'string', multiple: true },
24
- dev: { type: 'boolean' },
25
- 'max-issues': { type: 'string' },
26
21
  ignore: { type: 'string', multiple: true },
27
22
  'no-gitignore': { type: 'boolean' },
23
+ dev: { type: 'boolean' },
28
24
  'no-progress': { type: 'boolean' },
25
+ 'max-issues': { type: 'string' },
29
26
  reporter: { type: 'string' },
30
27
  jsdoc: { type: 'string', multiple: true },
31
28
  },
@@ -38,69 +35,37 @@ const cwd = process.cwd();
38
35
  const workingDir = dir ? node_path_1.default.resolve(dir) : cwd;
39
36
  const isShowProgress = noProgress === false ? process.stdout.isTTY && typeof process.stdout.cursorTo === 'function' : !noProgress;
40
37
  const printReport = reporter in reporters_1.default ? reporters_1.default[reporter] : require(node_path_1.default.join(workingDir, reporter));
41
- const main = async () => {
42
- const localConfigurationPath = await (0, fs_1.findFile)(workingDir, configFilePath);
43
- const manifestPath = await (0, fs_1.findFile)(workingDir, 'package.json');
44
- const localConfiguration = localConfigurationPath && require(localConfigurationPath);
45
- const manifest = manifestPath && require(manifestPath);
46
- if (!localConfigurationPath && !manifest.knip) {
47
- const location = workingDir === cwd ? 'current directory' : `${node_path_1.default.relative(cwd, workingDir)} or up.`;
48
- console.error(`Unable to find ${configFilePath} or package.json#knip in ${location}\n`);
49
- (0, help_1.printHelp)();
50
- process.exit(1);
51
- }
52
- const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfiguration, { workingDir: dir, isDev });
53
- if (!resolvedConfig) {
54
- (0, help_1.printHelp)();
55
- process.exit(1);
56
- }
57
- const report = (0, config_1.resolveIncludedIssueGroups)(include, exclude, resolvedConfig);
58
- let tsConfigPaths = [];
59
- const tsConfigPath = await (0, fs_1.findFile)(workingDir, tsConfigFilePath ?? 'tsconfig.json');
60
- if (tsConfigFilePath && !tsConfigPath) {
61
- console.error(`Unable to find ${tsConfigFilePath}\n`);
62
- (0, help_1.printHelp)();
63
- process.exit(1);
38
+ const run = async () => {
39
+ try {
40
+ const { report, issues, counters } = await (0, _1.main)({
41
+ cwd,
42
+ workingDir,
43
+ configFilePath,
44
+ tsConfigFilePath,
45
+ include,
46
+ exclude,
47
+ ignore,
48
+ gitignore: !isNoGitIgnore,
49
+ isDev,
50
+ isShowProgress,
51
+ jsDoc,
52
+ });
53
+ printReport({ report, issues, workingDir, isDev });
54
+ const reportGroup = report.files ? 'files' : Object.keys(report).find(key => report[key]);
55
+ const counterGroup = reportGroup === 'unlisted' ? 'unresolved' : reportGroup;
56
+ if (counterGroup) {
57
+ const count = counters[counterGroup];
58
+ if (count > Number(maxIssues))
59
+ process.exit(count);
60
+ }
64
61
  }
65
- if (tsConfigPath) {
66
- const tsConfig = typescript_1.default.readConfigFile(tsConfigPath, typescript_1.default.sys.readFile);
67
- tsConfigPaths = tsConfig.config.compilerOptions?.paths
68
- ? Object.keys(tsConfig.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))
69
- : [];
70
- if (tsConfig.error) {
71
- console.error(`An error occured when reading ${node_path_1.default.relative(cwd, tsConfigPath)}.\n`);
62
+ catch (error) {
63
+ if (error instanceof errors_1.ConfigurationError) {
64
+ console.error(error.message + '\n');
72
65
  (0, help_1.printHelp)();
73
66
  process.exit(1);
74
67
  }
75
- }
76
- const ignorePatterns = ignore.map(ignore_1.convertPattern);
77
- if (!isNoGitIgnore) {
78
- const patterns = await (0, ignore_1.readIgnorePatterns)(cwd, workingDir);
79
- patterns.forEach(pattern => ignorePatterns.push(pattern));
80
- }
81
- const config = {
82
- workingDir,
83
- report,
84
- dependencies: Object.keys(manifest.dependencies ?? {}),
85
- devDependencies: Object.keys(manifest.devDependencies ?? {}),
86
- isDev: typeof resolvedConfig.dev === 'boolean' ? resolvedConfig.dev : isDev,
87
- tsConfigFilePath,
88
- tsConfigPaths,
89
- ignorePatterns,
90
- isShowProgress,
91
- jsDocOptions: {
92
- isReadPublicTag: jsdoc.includes('public'),
93
- },
94
- ...resolvedConfig,
95
- };
96
- const { issues, counters } = await (0, _1.run)(config);
97
- printReport({ issues, workingDir, config });
98
- const reportGroup = report.files ? 'files' : Object.keys(report).find(key => report[key]);
99
- const counterGroup = reportGroup === 'unlisted' ? 'unresolved' : reportGroup;
100
- if (counterGroup) {
101
- const count = counters[counterGroup];
102
- if (count > Number(maxIssues))
103
- process.exit(count);
68
+ throw error;
104
69
  }
105
70
  };
106
- main();
71
+ run();
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,82 @@ 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 errors_1 = require("./util/errors");
15
+ const main = async (options) => {
16
+ const { cwd, workingDir, configFilePath = 'knip.json', tsConfigFilePath, include, exclude, ignore, gitignore, isDev, isShowProgress, jsDoc, } = options;
17
+ const localConfigurationPath = configFilePath && (await (0, fs_1.findFile)(workingDir, configFilePath));
18
+ const manifestPath = await (0, fs_1.findFile)(workingDir, 'package.json');
19
+ const localConfiguration = localConfigurationPath && require(localConfigurationPath);
20
+ const manifest = manifestPath && require(manifestPath);
21
+ if (!localConfigurationPath && !manifest.knip) {
22
+ const location = workingDir === cwd ? 'current directory' : `${node_path_1.default.relative(cwd, workingDir)} or up.`;
23
+ throw new errors_1.ConfigurationError(`Unable to find ${configFilePath} or package.json#knip in ${location}`);
24
+ }
25
+ const dir = node_path_1.default.relative(cwd, workingDir);
26
+ const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfiguration, { workingDir: dir, isDev });
27
+ if (!resolvedConfig) {
28
+ throw new errors_1.ConfigurationError('Unable to find `entryFiles` and/or `projectFiles` in configuration.');
29
+ }
30
+ const report = (0, config_1.resolveIncludedIssueGroups)(include, exclude, resolvedConfig);
31
+ let tsConfigPaths = [];
32
+ const tsConfigPath = await (0, fs_1.findFile)(workingDir, tsConfigFilePath ?? 'tsconfig.json');
33
+ if (tsConfigFilePath && !tsConfigPath) {
34
+ throw new errors_1.ConfigurationError(`Unable to find ${tsConfigFilePath}`);
35
+ }
36
+ if (tsConfigPath) {
37
+ const tsConfig = typescript_1.default.readConfigFile(tsConfigPath, typescript_1.default.sys.readFile);
38
+ tsConfigPaths = tsConfig.config.compilerOptions?.paths
39
+ ? Object.keys(tsConfig.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))
40
+ : [];
41
+ if (tsConfig.error) {
42
+ throw new errors_1.ConfigurationError(`An error occured when reading ${node_path_1.default.relative(cwd, tsConfigPath)}`);
43
+ }
44
+ }
45
+ const projectOptions = tsConfigPath ? { tsConfigFilePath: tsConfigPath } : { compilerOptions: { allowJs: true } };
46
+ const entryPaths = await (0, path_1.resolvePaths)({
47
+ cwd,
48
+ workingDir,
49
+ patterns: resolvedConfig.entryFiles,
50
+ ignore,
51
+ gitignore,
52
+ });
53
+ const production = (0, project_1.createProject)({ projectOptions, paths: entryPaths });
19
54
  const entryFiles = production.getSourceFiles();
20
55
  production.resolveSourceFileDependencies();
21
56
  const productionFiles = production.getSourceFiles();
22
- const project = await (0, project_1.createProject)(configuration, configuration.projectFiles);
57
+ const projectPaths = await (0, path_1.resolvePaths)({
58
+ cwd,
59
+ workingDir,
60
+ patterns: resolvedConfig.projectFiles,
61
+ ignore,
62
+ gitignore,
63
+ });
64
+ const project = (0, project_1.createProject)({ projectOptions, paths: projectPaths });
23
65
  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);
66
+ const config = {
67
+ workingDir,
68
+ report,
69
+ entryFiles,
70
+ productionFiles,
71
+ projectFiles,
72
+ dependencies: Object.keys(manifest.dependencies ?? {}),
73
+ devDependencies: Object.keys(manifest.devDependencies ?? {}),
74
+ isDev: typeof resolvedConfig.dev === 'boolean' ? resolvedConfig.dev : isDev,
75
+ tsConfigPaths,
76
+ isShowProgress,
77
+ jsDocOptions: {
78
+ isReadPublicTag: jsDoc.includes('public'),
79
+ },
68
80
  };
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;
81
+ const { issues, counters } = await (0, runner_1.findIssues)(config);
82
+ return { report, issues, counters };
83
+ };
84
+ 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>>;
@@ -30,23 +31,36 @@ declare type BaseLocalConfiguration = {
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
- ignorePatterns: string[];
50
64
  isShowProgress: boolean;
51
65
  jsDocOptions: {
52
66
  isReadPublicTag: boolean;
@@ -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 class ConfigurationError extends Error {
2
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfigurationError = void 0;
4
+ class ConfigurationError extends Error {
5
+ }
6
+ exports.ConfigurationError = ConfigurationError;
@@ -1,3 +1,8 @@
1
- import type { Configuration } from '../types';
2
- export declare const addWorkingDirToPattern: (workingDir: string, pattern: string) => string;
3
- export declare const resolvePaths: (configuration: Configuration, patterns: string[]) => string[];
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,16 +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.resolvePaths = exports.addWorkingDirToPattern = void 0;
6
+ exports.resolvePaths = exports.prependDirToPattern = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
- const addWorkingDirToPattern = (workingDir, pattern) => {
8
+ let _globby;
9
+ const glob = async function (patterns, options) {
10
+ if (!_globby) {
11
+ const { globby } = await eval('import("globby")');
12
+ _globby = globby;
13
+ }
14
+ return _globby(patterns, options);
15
+ };
16
+ const prependDirToPattern = (workingDir, pattern) => {
9
17
  if (pattern.startsWith('!'))
10
18
  return '!' + node_path_1.default.join(workingDir, pattern.slice(1));
11
19
  return node_path_1.default.join(workingDir, pattern);
12
20
  };
13
- exports.addWorkingDirToPattern = addWorkingDirToPattern;
14
- const resolvePaths = (configuration, patterns) => {
15
- const { workingDir } = configuration;
16
- return patterns.map(pattern => (0, exports.addWorkingDirToPattern)(workingDir, pattern));
17
- };
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
+ });
18
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[]) => 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[][];
@@ -2,19 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.partitionSourceFiles = exports.createProject = void 0;
4
4
  const ts_morph_1 = require("ts-morph");
5
- const path_1 = require("./path");
6
- const createProject = async (configuration, paths) => {
7
- const { tsConfigFilePath, ignorePatterns } = configuration;
8
- const tsConfig = tsConfigFilePath ? { tsConfigFilePath } : { compilerOptions: { allowJs: true } };
5
+ const createProject = ({ projectOptions, paths }) => {
9
6
  const workspace = new ts_morph_1.Project({
10
- ...tsConfig,
7
+ ...projectOptions,
11
8
  skipAddingFilesFromTsConfig: true,
12
9
  skipFileDependencyResolution: true,
13
10
  });
14
- if (paths) {
15
- const resolvedPaths = (0, path_1.resolvePaths)(configuration, paths);
16
- workspace.addSourceFilesAtPaths([...resolvedPaths, ...ignorePatterns]);
17
- }
11
+ if (paths)
12
+ workspace.addSourceFilesAtPaths(paths);
18
13
  return workspace;
19
14
  };
20
15
  exports.createProject = createProject;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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",
@@ -1,2 +0,0 @@
1
- export declare const convertPattern: (pattern: string) => string;
2
- export declare const readIgnorePatterns: (cwd: string, workingDir: string) => Promise<string[]>;
@@ -1,38 +0,0 @@
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.readIgnorePatterns = exports.convertPattern = void 0;
7
- const promises_1 = __importDefault(require("node:fs/promises"));
8
- const node_path_1 = __importDefault(require("node:path"));
9
- const fs_1 = require("./fs");
10
- const path_1 = require("./path");
11
- const convertPattern = (pattern) => (pattern.startsWith('!') ? pattern.substring(1) : `!${pattern}`);
12
- exports.convertPattern = convertPattern;
13
- const readIgnoreFile = async (filePath) => {
14
- let contents = '';
15
- try {
16
- contents = (await promises_1.default.readFile(filePath)).toString();
17
- }
18
- catch (error) {
19
- }
20
- return contents.split(/\r?\n/).filter(line => line && !line.startsWith('#'));
21
- };
22
- const traverseDirs = async (rootDir, currentDir, patterns = []) => {
23
- const gitIgnorePath = node_path_1.default.join(currentDir, '.gitignore');
24
- const parentDir = node_path_1.default.resolve(currentDir, '..');
25
- if (await (0, fs_1.isFile)(gitIgnorePath)) {
26
- (await readIgnoreFile(gitIgnorePath))
27
- .map(pattern => (0, path_1.addWorkingDirToPattern)(currentDir, pattern))
28
- .forEach(pattern => patterns.push(pattern));
29
- }
30
- if (rootDir === currentDir || parentDir === '/')
31
- return patterns;
32
- return traverseDirs(rootDir, parentDir, patterns);
33
- };
34
- const readIgnorePatterns = async (cwd, workingDir) => {
35
- const patterns = await traverseDirs(cwd, workingDir);
36
- return patterns.map(exports.convertPattern);
37
- };
38
- exports.readIgnorePatterns = readIgnorePatterns;