knip 0.4.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
 
@@ -142,7 +142,7 @@ As always, make sure to backup files or use Git before deleting files or making
142
142
 
143
143
  The default configuration for Knip is very strict and targets production code. For best results, it is recommended to
144
144
  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.
145
+ they may import production files, which will prevent them from being reported as unused.
146
146
 
147
147
  Excluding non-production files from the `projectFiles` allows Knip to understand what production code can be removed
148
148
  (including dependent files!).
@@ -162,8 +162,7 @@ and add `dev: true` to a file named such as `knip.dev.json`:
162
162
  }
163
163
  ```
164
164
 
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.
165
+ Now use `-c knip.dev.json` to find unused files and exports for the combined set of files as configured in `entryFiles`.
167
166
 
168
167
  An alternative way to store `dev` configuration is in this example `package.json`:
169
168
 
@@ -192,7 +191,7 @@ This way, the `--dev` flag will use the `dev` options (and also add `devDependen
192
191
 
193
192
  #### Separate packages
194
193
 
195
- 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
196
195
  packages can be configured using globs:
197
196
 
198
197
  ```json
@@ -209,8 +208,8 @@ Packages can also be explicitly configured per package directory.
209
208
  To scan the packages separately, using the first match from the configuration file:
210
209
 
211
210
  ```
212
- knip --cwd packages/client
213
- knip --cwd packages/services
211
+ knip --dir packages/client
212
+ knip --dir packages/services
214
213
  ```
215
214
 
216
215
  #### Connected projects
@@ -296,16 +295,48 @@ src/components/Registration.tsx: Registration, default
296
295
  src/components/Products.tsx: ProductsList, default
297
296
  ```
298
297
 
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.
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,14 +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 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 { 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
13
  options: {
18
14
  help: { type: 'boolean' },
19
15
  config: { type: 'string', short: 'c' },
@@ -21,11 +17,11 @@ const { values: { help, dir, config: configFilePath = 'knip.json', tsConfig: tsC
21
17
  dir: { type: 'string' },
22
18
  include: { type: 'string', multiple: true },
23
19
  exclude: { type: 'string', multiple: true },
24
- dev: { type: 'boolean' },
25
- 'max-issues': { type: 'string' },
26
20
  ignore: { type: 'string', multiple: true },
27
21
  'no-gitignore': { type: 'boolean' },
22
+ dev: { type: 'boolean' },
28
23
  'no-progress': { type: 'boolean' },
24
+ 'max-issues': { type: 'string' },
29
25
  reporter: { type: 'string' },
30
26
  jsdoc: { type: 'string', multiple: true },
31
27
  },
@@ -38,69 +34,37 @@ const cwd = process.cwd();
38
34
  const workingDir = dir ? node_path_1.default.resolve(dir) : cwd;
39
35
  const isShowProgress = noProgress === false ? process.stdout.isTTY && typeof process.stdout.cursorTo === 'function' : !noProgress;
40
36
  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);
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
+ }
64
60
  }
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`);
61
+ catch (error) {
62
+ if (error instanceof Error) {
63
+ console.error(error.message + '\n');
72
64
  (0, help_1.printHelp)();
73
65
  process.exit(1);
74
66
  }
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);
67
+ throw error;
104
68
  }
105
69
  };
106
- main();
70
+ 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,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>>;
@@ -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;
@@ -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.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",
@@ -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;