knip 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,14 +1,16 @@
1
1
  # ✂️ Knip
2
2
 
3
- Knip scans your TypeScript projects for **unused files and exports**. For comparison, ESLint finds unused variables
4
- inside files in isolation, but this will not be flagged:
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!
5
+
6
+ For comparison, ESLint finds unused variables inside files in isolation, but this will not be flagged:
5
7
 
6
8
  ```ts
7
9
  export const myVar = true;
8
10
  ```
9
11
 
10
- Unused files will also not be detected by ESLint. So how do you know which files and exports are no longer used? This
11
- requires an analysis of all the right files in the project.
12
+ Unused files will also not be detected by ESLint. So how do you know which files, dependencies and exports are no longer
13
+ used? This requires an analysis of all the right files in the project.
12
14
 
13
15
  This is where Knip comes in:
14
16
 
@@ -17,7 +19,7 @@ This is where Knip comes in:
17
19
  - [x] Finds dependencies not listed in `package.json`.
18
20
  - [x] Finds duplicate exports of the same symbol.
19
21
  - [x] Supports JavaScript inside TypeScript projects (`"allowJs": true`)
20
- - [ ] Supports JavaScript-only projects with CommonJS and ESM (no `tsconfig.json`) - TODO
22
+ - [x] Supports JavaScript-only projects using ESM (without a `tsconfig.json`)
21
23
 
22
24
  Knip really shines in larger projects where you have non-production files (such as `/docs`, `/tools` and `/scripts`).
23
25
  The `includes` setting in `tsconfig.json` is often too broad, resulting in too many false negatives. Similar projects
@@ -77,23 +79,27 @@ Please read on if you think you have too many results: [too many false positives
77
79
  knip [options]
78
80
 
79
81
  Options:
80
- -c/--config [file] Configuration file path (default: ./knip.json or package.json#knip)
81
- --cwd Working directory (default: current working directory)
82
- --include Report only listed issue group(s) (see below)
83
- --exclude Exclude issue group(s) from report (see below)
84
- --dev Include `devDependencies` in report(s) (default: false)
85
- --no-progress Don't show dynamic progress updates
86
- --max-issues Maximum number of issues before non-zero exit code (default: 0)
87
- --reporter Select reporter: symbols, compact (default: symbols)
88
- --jsdoc Enable JSDoc parsing, with options: public (default: disabled)
82
+ -c/--config [file] Configuration file path (default: ./knip.json or package.json#knip)
83
+ -t/--tsConfig [file] TypeScript configuration path (default: ./tsconfig.json)
84
+ --dir Working directory (default: current working directory)
85
+ --include Report only listed issue group(s) (see below)
86
+ --exclude Exclude issue group(s) from report (see below)
87
+ --ignore Ignore files matching this glob pattern (can be set multiple times)
88
+ --no-gitignore Don't use .gitignore
89
+ --dev Include `devDependencies` in report(s)
90
+ --no-progress Don't show dynamic progress updates
91
+ --max-issues Maximum number of issues before non-zero exit code (default: 0)
92
+ --reporter Select reporter: symbols, compact (default: symbols)
93
+ --jsdoc Enable JSDoc parsing, with options: public
89
94
 
90
95
  Issue groups: files, dependencies, unlisted, exports, nsExports, types, nsTypes, duplicates
91
96
 
92
97
  Examples:
93
98
 
94
99
  $ knip
95
- $ knip --cwd packages/client --include files
100
+ $ knip --dir packages/client --include files
96
101
  $ knip -c ./knip.js --reporter compact --jsdoc public
102
+ $ knip --ignore 'lib/**/*.ts' --ignore build
97
103
 
98
104
  More info: https://github.com/webpro/knip
99
105
  ```
@@ -143,8 +149,10 @@ Excluding non-production files from the `projectFiles` allows Knip to understand
143
149
 
144
150
  Non-production code includes files such as end-to-end tests, tooling, scripts, Storybook stories, etc.
145
151
 
152
+ Think of it the same way as you would split `dependencies` and `devDependencies` in `package.json`.
153
+
146
154
  To include both production and test files to analyze the project as a whole, include both sets of files to `entryFiles`,
147
- and add `dev: true`:
155
+ and add `dev: true` to a file named such as `knip.dev.json`:
148
156
 
149
157
  ```json
150
158
  {
@@ -154,7 +162,29 @@ and add `dev: true`:
154
162
  }
155
163
  ```
156
164
 
157
- Knip will now report unused files and exports for the combined set of files as configured in `entryFiles`.
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
+
168
+ An alternative way to store `dev` configuration is in this example `package.json`:
169
+
170
+ ```json
171
+ {
172
+ "name": "my-package",
173
+ "scripts": {
174
+ "knip": "knip"
175
+ },
176
+ "knip": {
177
+ "entryFiles": ["src/index.ts"],
178
+ "projectFiles": ["src/**/*.ts", "!**/*.spec.ts"],
179
+ "dev": {
180
+ "entryFiles": ["src/index.ts", "src/**/*.spec.ts", "src/**/*.e2e.ts"],
181
+ "projectFiles": ["src/**/*.ts"]
182
+ }
183
+ }
184
+ }
185
+ ```
186
+
187
+ This way, the `--dev` flag will use the `dev` options (and also add `devDependencies` to the `dependencies` report).
158
188
 
159
189
  ## More configuration examples
160
190
 
@@ -179,8 +209,8 @@ Packages can also be explicitly configured per package directory.
179
209
  To scan the packages separately, using the first match from the configuration file:
180
210
 
181
211
  ```
182
- knip --cwd packages/client --config knip.json
183
- knip --cwd packages/services --config knip.json
212
+ knip --cwd packages/client
213
+ knip --cwd packages/services
184
214
  ```
185
215
 
186
216
  #### Connected projects
@@ -217,10 +247,14 @@ can be tweaked further to the project structure.
217
247
  ### Default reporter
218
248
 
219
249
  ```
220
- $ knip --config ./knip.json
250
+ $ knip
221
251
  --- UNUSED FILES (2)
222
252
  src/chat/helpers.ts
223
253
  src/components/SideBar.tsx
254
+ --- UNUSED DEPENDENCIES (1)
255
+ moment
256
+ --- UNLISTED DEPENDENCIES (1)
257
+ react
224
258
  --- UNUSED EXPORTS (5)
225
259
  lowercaseFirstLetter src/common/src/string/index.ts
226
260
  RegistrationBox src/components/Registration.tsx
@@ -240,10 +274,14 @@ ProductsList, default src/components/Products.tsx
240
274
  ### Compact
241
275
 
242
276
  ```
243
- $ knip --config ./knip.json --reporter compact
277
+ $ knip --reporter compact
244
278
  --- UNUSED FILES (2)
245
279
  src/chat/helpers.ts
246
280
  src/components/SideBar.tsx
281
+ --- UNUSED DEPENDENCIES (1)
282
+ moment
283
+ --- UNLISTED DEPENDENCIES (1)
284
+ react
247
285
  --- UNUSED EXPORTS (4)
248
286
  src/common/src/string/index.ts: lowercaseFirstLetter
249
287
  src/components/Registration.tsx: RegistrationBox
package/dist/cli.js CHANGED
@@ -9,18 +9,22 @@ const node_util_1 = require("node:util");
9
9
  const typescript_1 = __importDefault(require("typescript"));
10
10
  const help_1 = require("./help");
11
11
  const config_1 = require("./util/config");
12
- const path_1 = require("./util/path");
12
+ const fs_1 = require("./util/fs");
13
+ const ignore_1 = require("./util/ignore");
13
14
  const reporters_1 = __importDefault(require("./reporters"));
14
15
  const _1 = require(".");
15
- const { values: { help, cwd: cwdArg, config: configFilePath = 'knip.json', include = [], exclude = [], dev: isDev = false, 'no-progress': noProgress = false, reporter = 'symbols', jsdoc = [], 'max-issues': maxIssues = '0', }, } = (0, node_util_1.parseArgs)({
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)({
16
17
  options: {
17
18
  help: { type: 'boolean' },
18
19
  config: { type: 'string', short: 'c' },
19
- cwd: { type: 'string' },
20
+ tsConfig: { type: 'string', short: 't' },
21
+ dir: { type: 'string' },
20
22
  include: { type: 'string', multiple: true },
21
23
  exclude: { type: 'string', multiple: true },
22
24
  dev: { type: 'boolean' },
23
25
  'max-issues': { type: 'string' },
26
+ ignore: { type: 'string', multiple: true },
27
+ 'no-gitignore': { type: 'boolean' },
24
28
  'no-progress': { type: 'boolean' },
25
29
  reporter: { type: 'string' },
26
30
  jsdoc: { type: 'string', multiple: true },
@@ -31,49 +35,58 @@ if (help) {
31
35
  process.exit(0);
32
36
  }
33
37
  const cwd = process.cwd();
34
- const workingDir = cwdArg ? node_path_1.default.resolve(cwdArg) : cwd;
38
+ const workingDir = dir ? node_path_1.default.resolve(dir) : cwd;
35
39
  const isShowProgress = noProgress === false ? process.stdout.isTTY && typeof process.stdout.cursorTo === 'function' : !noProgress;
36
40
  const printReport = reporter in reporters_1.default ? reporters_1.default[reporter] : require(node_path_1.default.join(workingDir, reporter));
37
41
  const main = async () => {
38
- const localConfigurationPath = await (0, path_1.findFile)(workingDir, configFilePath);
39
- const manifestPath = await (0, path_1.findFile)(workingDir, 'package.json');
40
- if (!manifestPath || !localConfigurationPath) {
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) {
41
47
  const location = workingDir === cwd ? 'current directory' : `${node_path_1.default.relative(cwd, workingDir)} or up.`;
42
- console.error(`Unable to find ${configFilePath} (or package.json#knip) in ${location}\n`);
48
+ console.error(`Unable to find ${configFilePath} or package.json#knip in ${location}\n`);
43
49
  (0, help_1.printHelp)();
44
50
  process.exit(1);
45
51
  }
46
- const localConfiguration = require(localConfigurationPath);
47
- const manifest = require(manifestPath);
48
- const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfiguration, cwdArg);
52
+ const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfiguration, { workingDir: dir, isDev });
49
53
  if (!resolvedConfig) {
50
54
  (0, help_1.printHelp)();
51
55
  process.exit(1);
52
56
  }
53
57
  const report = (0, config_1.resolveIncludedIssueGroups)(include, exclude, resolvedConfig);
54
- const tsConfigFile = await (0, path_1.findFile)(workingDir, 'tsconfig.json');
55
- if (!tsConfigFile) {
56
- const location = workingDir === cwd ? 'current directory' : `${node_path_1.default.relative(cwd, workingDir)} or up.`;
57
- console.error(`Unable to find ${tsConfigFile} or package.json in ${location}\n`);
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`);
58
62
  (0, help_1.printHelp)();
59
63
  process.exit(1);
60
64
  }
61
- const tsConfig = typescript_1.default.readConfigFile(tsConfigFile, typescript_1.default.sys.readFile);
62
- const tsConfigPaths = tsConfig.config.compilerOptions?.paths
63
- ? Object.keys(tsConfig.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))
64
- : [];
65
- if (tsConfig.error) {
66
- console.error(`An error occured when reading ${node_path_1.default.relative(cwd, tsConfigFile)}.\n`);
67
- (0, help_1.printHelp)();
68
- process.exit(1);
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`);
72
+ (0, help_1.printHelp)();
73
+ process.exit(1);
74
+ }
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));
69
80
  }
70
81
  const config = {
71
82
  workingDir,
72
83
  report,
73
84
  dependencies: Object.keys(manifest.dependencies ?? {}),
74
85
  devDependencies: Object.keys(manifest.devDependencies ?? {}),
75
- isDev: resolvedConfig.dev ?? isDev,
86
+ isDev: typeof resolvedConfig.dev === 'boolean' ? resolvedConfig.dev : isDev,
87
+ tsConfigFilePath,
76
88
  tsConfigPaths,
89
+ ignorePatterns,
77
90
  isShowProgress,
78
91
  jsDocOptions: {
79
92
  isReadPublicTag: jsdoc.includes('public'),
package/dist/help.js CHANGED
@@ -5,23 +5,27 @@ const printHelp = () => {
5
5
  console.log(`knip [options]
6
6
 
7
7
  Options:
8
- -c/--config [file] Configuration file path (default: ./knip.json or package.json#knip)
9
- --cwd Working directory (default: current working directory)
10
- --include Report only listed issue group(s) (see below)
11
- --exclude Exclude issue group(s) from report (see below)
12
- --dev Include \`devDependencies\` in report(s) (default: false)
13
- --no-progress Don't show dynamic progress updates
14
- --max-issues Maximum number of issues before non-zero exit code (default: 0)
15
- --reporter Select reporter: symbols, compact (default: symbols)
16
- --jsdoc Enable JSDoc parsing, with options: public (default: disabled)
8
+ -c/--config [file] Configuration file path (default: ./knip.json or package.json#knip)
9
+ -t/--tsConfig [file] TypeScript configuration path (default: ./tsconfig.json)
10
+ --dir Working directory (default: current working directory)
11
+ --include Report only listed issue group(s) (see below)
12
+ --exclude Exclude issue group(s) from report (see below)
13
+ --ignore Ignore files matching this glob pattern (can be set multiple times)
14
+ --no-gitignore Don't use .gitignore
15
+ --dev Include \`devDependencies\` in report(s)
16
+ --no-progress Don't show dynamic progress updates
17
+ --max-issues Maximum number of issues before non-zero exit code (default: 0)
18
+ --reporter Select reporter: symbols, compact (default: symbols)
19
+ --jsdoc Enable JSDoc parsing, with options: public
17
20
 
18
21
  Issue groups: files, dependencies, unlisted, exports, nsExports, types, nsTypes, duplicates
19
22
 
20
23
  Examples:
21
24
 
22
25
  $ knip
23
- $ knip --cwd packages/client --include files
26
+ $ knip --dir packages/client --include files
24
27
  $ knip -c ./knip.js --reporter compact --jsdoc public
28
+ $ knip --ignore 'lib/**/*.ts' --ignore build
25
29
 
26
30
  More info: https://github.com/webpro/knip`);
27
31
  };
package/dist/index.js CHANGED
@@ -15,11 +15,11 @@ const lineRewriter = new log_1.LineRewriter();
15
15
  async function run(configuration) {
16
16
  const { workingDir, isShowProgress, report, isDev, jsDocOptions } = configuration;
17
17
  const { getUnresolvedDependencies, getUnusedDependencies, getUnusedDevDependencies } = (0, dependencies_1.getDependencyAnalyzer)(configuration);
18
- const production = await (0, project_1.createProject)(workingDir, configuration.entryFiles);
18
+ const production = await (0, project_1.createProject)(configuration, configuration.entryFiles);
19
19
  const entryFiles = production.getSourceFiles();
20
20
  production.resolveSourceFileDependencies();
21
21
  const productionFiles = production.getSourceFiles();
22
- const project = await (0, project_1.createProject)(workingDir, configuration.projectFiles);
22
+ const project = await (0, project_1.createProject)(configuration, configuration.projectFiles);
23
23
  const projectFiles = project.getSourceFiles();
24
24
  const [usedProductionFiles, unreferencedProductionFiles] = (0, project_1.partitionSourceFiles)(projectFiles, productionFiles);
25
25
  const [usedEntryFiles, usedNonEntryFiles] = (0, project_1.partitionSourceFiles)(usedProductionFiles, entryFiles);
@@ -127,6 +127,8 @@ async function run(configuration) {
127
127
  if (declaration.isKind(ts_morph_1.ts.SyntaxKind.Identifier)) {
128
128
  identifier = declaration;
129
129
  }
130
+ else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.ArrowFunction)) {
131
+ }
130
132
  else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.FunctionDeclaration) ||
131
133
  declaration.isKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration) ||
132
134
  declaration.isKind(ts_morph_1.ts.SyntaxKind.TypeAliasDeclaration) ||
@@ -156,7 +158,7 @@ async function run(configuration) {
156
158
  }
157
159
  else {
158
160
  const refFiles = new Set(refs.map(r => r.compilerObject.references.map(r => r.fileName)).flat());
159
- const isReferencedOnlyBySelf = refFiles.size === 1 && [...refFiles][0] === sourceFile.getFilePath();
161
+ const isReferencedOnlyBySelf = refFiles.size === 1 && [...refFiles][0] === filePath;
160
162
  if (!isReferencedOnlyBySelf)
161
163
  return;
162
164
  if ((0, ts_morph_helpers_1.findReferencingNamespaceNodes)(sourceFile).length > 0) {
@@ -39,7 +39,7 @@ exports.default = ({ issues, config, workingDir }) => {
39
39
  }
40
40
  if (report.dependencies && isDev) {
41
41
  const unreferencedDevDependencies = Array.from(issues.devDependencies);
42
- logIssueGroupResult(unreferencedDevDependencies, workingDir, reportMultipleGroups && 'UNUSED DEV DEPENDENCIES');
42
+ logIssueGroupResult(unreferencedDevDependencies, workingDir, 'UNUSED DEV DEPENDENCIES');
43
43
  }
44
44
  if (report.unlisted) {
45
45
  const unreferencedDependencies = Object.values(issues.unresolved).map(issues => {
@@ -41,7 +41,7 @@ exports.default = ({ issues, config, workingDir }) => {
41
41
  }
42
42
  if (report.dependencies && isDev) {
43
43
  const unreferencedDevDependencies = Array.from(issues.devDependencies);
44
- logIssueGroupResult(unreferencedDevDependencies, workingDir, reportMultipleGroups && 'UNUSED DEV DEPENDENCIES');
44
+ logIssueGroupResult(unreferencedDevDependencies, workingDir, 'UNUSED DEV DEPENDENCIES');
45
45
  }
46
46
  if (report.unlisted) {
47
47
  const unresolvedDependencies = Object.values(issues.unresolved).map(Object.values).flat();
package/dist/types.d.ts CHANGED
@@ -20,12 +20,16 @@ export declare type Issues = {
20
20
  nsTypes: UnusedExportIssues;
21
21
  duplicates: UnusedExportIssues;
22
22
  };
23
- export declare type IssueType = keyof Issues;
23
+ declare type IssueType = keyof Issues;
24
24
  export declare type ProjectIssueType = Extract<IssueType, 'files' | 'dependencies' | 'devDependencies'>;
25
25
  export declare type SymbolIssueType = Exclude<IssueType, ProjectIssueType>;
26
26
  export declare type IssueGroup = 'files' | 'dependencies' | 'unlisted' | 'exports' | 'nsExports' | 'types' | 'nsTypes' | 'duplicates';
27
- export declare type LocalConfiguration = {
28
- dev?: boolean;
27
+ declare type BaseLocalConfiguration = {
28
+ entryFiles: string[];
29
+ projectFiles: string[];
30
+ };
31
+ export declare type LocalConfiguration = BaseLocalConfiguration & {
32
+ dev?: boolean | BaseLocalConfiguration;
29
33
  entryFiles: string[];
30
34
  projectFiles: string[];
31
35
  include?: string[];
@@ -40,7 +44,9 @@ export declare type Configuration = LocalConfiguration & {
40
44
  dependencies: string[];
41
45
  devDependencies: string[];
42
46
  isDev: boolean;
47
+ tsConfigFilePath: undefined | string;
43
48
  tsConfigPaths: string[];
49
+ ignorePatterns: string[];
44
50
  isShowProgress: boolean;
45
51
  jsDocOptions: {
46
52
  isReadPublicTag: boolean;
@@ -1,5 +1,8 @@
1
1
  import type { ImportedConfiguration, LocalConfiguration } from '../types';
2
- export declare const resolveConfig: (importedConfiguration: ImportedConfiguration, cwdArg?: string) => LocalConfiguration | undefined;
2
+ export declare const resolveConfig: (importedConfiguration: ImportedConfiguration, options?: {
3
+ workingDir?: string;
4
+ isDev?: boolean;
5
+ }) => LocalConfiguration | undefined;
3
6
  export declare const resolveIncludedIssueGroups: (includeArg: string[], excludeArg: string[], resolvedConfig?: LocalConfiguration) => {
4
7
  files: boolean;
5
8
  dependencies: boolean;
@@ -5,20 +5,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.resolveIncludedIssueGroups = exports.resolveConfig = void 0;
7
7
  const micromatch_1 = __importDefault(require("micromatch"));
8
- const resolveConfig = (importedConfiguration, cwdArg) => {
8
+ const resolveConfig = (importedConfiguration, options) => {
9
+ let resolvedConfig = importedConfiguration;
10
+ const { workingDir, isDev } = options ?? {};
9
11
  const configKeys = Object.keys(importedConfiguration);
10
- if (cwdArg && !('projectFiles' in importedConfiguration)) {
11
- const importedConfigKey = configKeys.find(pattern => micromatch_1.default.isMatch(cwdArg.replace(/\/$/, ''), pattern));
12
+ if (workingDir && !('projectFiles' in importedConfiguration)) {
13
+ const importedConfigKey = configKeys.find(pattern => micromatch_1.default.isMatch(workingDir.replace(/\/$/, ''), pattern));
12
14
  if (importedConfigKey) {
13
- return importedConfiguration[importedConfigKey];
15
+ resolvedConfig = importedConfiguration[importedConfigKey];
14
16
  }
15
17
  }
16
- if (!cwdArg && (!importedConfiguration.entryFiles || !importedConfiguration.projectFiles)) {
18
+ if (isDev && typeof resolvedConfig.dev === 'object' && 'projectFiles' in resolvedConfig.dev) {
19
+ resolvedConfig = resolvedConfig.dev;
20
+ }
21
+ if (!resolvedConfig.entryFiles || !resolvedConfig.projectFiles) {
17
22
  console.error('Unable to find `entryFiles` and/or `projectFiles` in configuration.');
18
- console.info(`Add it at root level, or use --cwd and match one of: ${configKeys.join(', ')}\n`);
23
+ console.info(`Add these properties at root level, or use --cwd and match one of: ${configKeys.join(', ')}\n`);
19
24
  return;
20
25
  }
21
- return importedConfiguration;
26
+ return resolvedConfig;
22
27
  };
23
28
  exports.resolveConfig = resolveConfig;
24
29
  const resolveIncludedIssueGroups = (includeArg, excludeArg, resolvedConfig) => {
@@ -0,0 +1,2 @@
1
+ export declare const isFile: (filePath: string) => Promise<boolean>;
2
+ export declare const findFile: (cwd: string, fileName: string) => Promise<string | undefined>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.findFile = exports.isFile = void 0;
7
+ const promises_1 = __importDefault(require("node:fs/promises"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const isFile = async (filePath) => {
10
+ try {
11
+ const stats = await promises_1.default.stat(filePath);
12
+ return stats.isFile();
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ };
18
+ exports.isFile = isFile;
19
+ const findFile = async (cwd, fileName) => {
20
+ const filePath = node_path_1.default.join(cwd, fileName);
21
+ if (await (0, exports.isFile)(filePath)) {
22
+ return filePath;
23
+ }
24
+ else {
25
+ const parentDir = node_path_1.default.resolve(cwd, '..');
26
+ return parentDir === '/' ? undefined : (0, exports.findFile)(parentDir, fileName);
27
+ }
28
+ };
29
+ exports.findFile = findFile;
@@ -0,0 +1,2 @@
1
+ export declare const convertPattern: (pattern: string) => string;
2
+ export declare const readIgnorePatterns: (cwd: string, workingDir: string) => Promise<string[]>;
@@ -0,0 +1,38 @@
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;
@@ -1 +1,3 @@
1
- export declare const findFile: (cwd: string, fileName: string) => Promise<string | undefined>;
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[];
package/dist/util/path.js CHANGED
@@ -3,26 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.findFile = void 0;
7
- const promises_1 = __importDefault(require("node:fs/promises"));
6
+ exports.resolvePaths = exports.addWorkingDirToPattern = void 0;
8
7
  const node_path_1 = __importDefault(require("node:path"));
9
- const isFile = async (filePath) => {
10
- try {
11
- const stats = await promises_1.default.stat(filePath);
12
- return stats.isFile();
13
- }
14
- catch {
15
- return false;
16
- }
8
+ const addWorkingDirToPattern = (workingDir, pattern) => {
9
+ if (pattern.startsWith('!'))
10
+ return '!' + node_path_1.default.join(workingDir, pattern.slice(1));
11
+ return node_path_1.default.join(workingDir, pattern);
17
12
  };
18
- const findFile = async (cwd, fileName) => {
19
- const filePath = node_path_1.default.join(cwd, fileName);
20
- if (await isFile(filePath)) {
21
- return filePath;
22
- }
23
- else {
24
- const parentDir = node_path_1.default.resolve(cwd, '..');
25
- return parentDir === '/' ? undefined : (0, exports.findFile)(parentDir, fileName);
26
- }
13
+ exports.addWorkingDirToPattern = addWorkingDirToPattern;
14
+ const resolvePaths = (configuration, patterns) => {
15
+ const { workingDir } = configuration;
16
+ return patterns.map(pattern => (0, exports.addWorkingDirToPattern)(workingDir, pattern));
27
17
  };
28
- exports.findFile = findFile;
18
+ exports.resolvePaths = resolvePaths;
@@ -1,4 +1,5 @@
1
1
  import { Project } from 'ts-morph';
2
2
  import type { SourceFile } from 'ts-morph';
3
- export declare const createProject: (cwd: string, paths?: string | string[]) => Promise<Project>;
3
+ import type { Configuration } from '../types';
4
+ export declare const createProject: (configuration: Configuration, paths?: string[]) => Promise<Project>;
4
5
  export declare const partitionSourceFiles: (projectFiles: SourceFile[], productionFiles: SourceFile[]) => SourceFile[][];
@@ -1,28 +1,20 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.partitionSourceFiles = exports.createProject = void 0;
7
- const node_path_1 = __importDefault(require("node:path"));
8
4
  const ts_morph_1 = require("ts-morph");
9
5
  const path_1 = require("./path");
10
- const resolvePaths = (cwd, patterns) => {
11
- return [patterns].flat().map(pattern => {
12
- if (pattern.startsWith('!'))
13
- return '!' + node_path_1.default.join(cwd, pattern.slice(1));
14
- return node_path_1.default.join(cwd, pattern);
15
- });
16
- };
17
- const createProject = async (cwd, paths) => {
18
- const tsConfigFilePath = await (0, path_1.findFile)(cwd, 'tsconfig.json');
6
+ const createProject = async (configuration, paths) => {
7
+ const { tsConfigFilePath, ignorePatterns } = configuration;
8
+ const tsConfig = tsConfigFilePath ? { tsConfigFilePath } : { compilerOptions: { allowJs: true } };
19
9
  const workspace = new ts_morph_1.Project({
20
- tsConfigFilePath,
10
+ ...tsConfig,
21
11
  skipAddingFilesFromTsConfig: true,
22
12
  skipFileDependencyResolution: true,
23
13
  });
24
- if (paths)
25
- workspace.addSourceFilesAtPaths(resolvePaths(cwd, paths));
14
+ if (paths) {
15
+ const resolvedPaths = (0, path_1.resolvePaths)(configuration, paths);
16
+ workspace.addSourceFilesAtPaths([...resolvedPaths, ...ignorePatterns]);
17
+ }
26
18
  return workspace;
27
19
  };
28
20
  exports.createProject = createProject;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Find unused files and exports in your TypeScript project",
5
5
  "keywords": [
6
6
  "find",