knip 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -125,10 +125,12 @@ following types of issues:
125
125
  - `nsTypes` - Unused types in namespaces: did not find direct references to this exported variable (2)
126
126
  - `duplicates` - Duplicate exports: the same thing is exported more than once with different names from the same file
127
127
 
128
- 1. This may also include dependencies that could not be resolved properly (such as non-relative `local/dir/file.ts` not
129
- and `local` not being in `node_modules`).
128
+ 1. This includes dependencies that could not be resolved. For instance, what does `unresolved/dir/module` mean?
129
+ - To target something in the (missing) `node_modules/unresolved` package?
130
+ - Target a local module that should have a relative path?
131
+ - It does not match any `paths` entry in `tsconfig.json#compilerOptions`.
130
132
  2. The variable or type is not referenced directly, and has become a member of a namespace. That's why Knip is not sure
131
- whether this export can be removed, so please look into it:
133
+ whether this export can be removed, so please look into it.
132
134
 
133
135
  You can `--include` or `--exclude` any of the types to slice & dice the report to your needs. Alternatively, they can be
134
136
  added to the configuration (e.g. `"exclude": ["dependencies"]`).
@@ -140,8 +142,8 @@ As always, make sure to backup files or use Git before deleting files or making
140
142
  - Unused files can be removed.
141
143
  - Unused dependencies can be removed from `package.json`.
142
144
  - Unlisted dependencies should be added to `package.json`.
143
- - Unused exports and types: remove the `export` keyword in front of unused exports. Then you (or tools such as the
144
- TypeScript language server in VS Code and/or ESLint) can see whether the variable or type is used within the same
145
+ - Unused exports and types: remove the `export` keyword in front of unused exports. Then you (or tools such as
146
+ TypeScript language services in VS Code and/or ESLint) can see whether the variable or type is used within the same
145
147
  file. If this is not the case, it can be removed.
146
148
 
147
149
  🔁 Repeat the process to reveal new unused files and exports. Sometimes it's so liberating to remove things!
package/dist/cli.js CHANGED
@@ -62,7 +62,6 @@ const run = async () => {
62
62
  await printReport({ report, issues, cwd, workingDir, isDev, options: reporterOptions });
63
63
  const totalErrorCount = Object.keys(report)
64
64
  .filter(reportGroup => report[reportGroup])
65
- .map(reportGroup => (reportGroup === 'unlisted' ? 'unresolved' : reportGroup))
66
65
  .reduce((errorCount, reportGroup) => errorCount + counters[reportGroup], 0);
67
66
  if (totalErrorCount > Number(maxIssues))
68
67
  process.exit(totalErrorCount);
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { UnresolvedConfiguration } from './types';
2
- export declare const main: (options: UnresolvedConfiguration) => Promise<{
2
+ export declare const main: (unresolvedConfiguration: UnresolvedConfiguration) => Promise<{
3
3
  report: import("./types").Report;
4
4
  issues: import("./types").Issues;
5
5
  counters: import("./types").Counters;
package/dist/index.js CHANGED
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.main = void 0;
7
- const node_path_1 = __importDefault(require("node:path"));
8
7
  const typescript_1 = __importDefault(require("typescript"));
9
8
  const config_1 = require("./util/config");
10
9
  const fs_1 = require("./util/fs");
@@ -14,10 +13,10 @@ const runner_1 = require("./runner");
14
13
  const errors_1 = require("./util/errors");
15
14
  const debug_1 = require("./util/debug");
16
15
  const progress_1 = require("./progress");
17
- const main = async (options) => {
18
- const { cwd, workingDir, configFilePath: configFilePathArg, tsConfigFilePath: tsConfigFilePathArg, include, exclude, ignore, gitignore, isIncludeEntryFiles, isDev, isShowProgress, jsDoc, debug, } = options;
19
- const updateMessage = (0, progress_1.getMessageUpdater)(options);
20
- (0, debug_1.debugLogObject)(options, 1, 'Unresolved onfiguration', options);
16
+ const main = async (unresolvedConfiguration) => {
17
+ const { cwd, workingDir, configFilePath: configFilePathArg, tsConfigFilePath: tsConfigFilePathArg, include, exclude, ignore, gitignore, isIncludeEntryFiles, isDev, isShowProgress, jsDoc, debug, } = unresolvedConfiguration;
18
+ const updateMessage = (0, progress_1.getMessageUpdater)(unresolvedConfiguration);
19
+ (0, debug_1.debugLogObject)(debug, 1, 'Unresolved configuration', unresolvedConfiguration);
21
20
  updateMessage('Reading configuration and manifest files...');
22
21
  const manifestPath = await (0, fs_1.findFile)(cwd, workingDir, 'package.json');
23
22
  const manifest = manifestPath && require(manifestPath);
@@ -32,19 +31,19 @@ const main = async (options) => {
32
31
  if (tsConfigFilePathArg && !resolvedTsConfigFilePath) {
33
32
  throw new errors_1.ConfigurationError(`Unable to find ${tsConfigFilePathArg}`);
34
33
  }
35
- let tsConfigPaths = [];
34
+ let tsConfigPathGlobs = [];
36
35
  if (resolvedTsConfigFilePath) {
37
36
  const config = typescript_1.default.readConfigFile(resolvedTsConfigFilePath, typescript_1.default.sys.readFile);
38
- tsConfigPaths = config.config.compilerOptions?.paths
37
+ tsConfigPathGlobs = config.config.compilerOptions?.paths
39
38
  ? Object.keys(config.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))
40
39
  : [];
41
40
  if (config.error) {
42
- throw new errors_1.ConfigurationError(`Unable to read ${node_path_1.default.relative(cwd, resolvedTsConfigFilePath)}`);
41
+ throw new errors_1.ConfigurationError(`Unable to read ${(0, path_1.relative)(resolvedTsConfigFilePath)}`);
43
42
  }
44
43
  }
45
- const dir = node_path_1.default.relative(cwd, workingDir);
44
+ const dir = (0, path_1.relative)(workingDir);
46
45
  const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfig, { workingDir: dir, isDev });
47
- (0, debug_1.debugLogObject)(options, 1, 'Resolved onfiguration', resolvedConfig);
46
+ (0, debug_1.debugLogObject)(debug, 1, 'Resolved configuration', resolvedConfig);
48
47
  if (!resolvedConfigFilePath && !manifest.knip && !resolvedTsConfigFilePath) {
49
48
  throw new errors_1.ConfigurationError(`Unable to find ${configFilePath} or package.json#knip or ${tsConfigFilePath}`);
50
49
  }
@@ -62,13 +61,13 @@ const main = async (options) => {
62
61
  ignore,
63
62
  gitignore,
64
63
  });
65
- (0, debug_1.debugLogFiles)(options, 1, 'Globbed entry paths', entryPaths);
64
+ (0, debug_1.debugLogFiles)(debug, 1, 'Globbed entry paths', entryPaths);
66
65
  const production = (0, project_1.createProject)({ ...projectOptions, ...skipAddFiles }, entryPaths);
67
66
  const entryFiles = production.getSourceFiles();
68
- (0, debug_1.debugLogSourceFiles)(options, 1, 'Included entry source files', entryFiles);
67
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Included entry source files', entryFiles);
69
68
  production.resolveSourceFileDependencies();
70
69
  const productionFiles = production.getSourceFiles();
71
- (0, debug_1.debugLogSourceFiles)(options, 1, 'Included production source files', productionFiles);
70
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Included production source files', productionFiles);
72
71
  updateMessage('Resolving project files...');
73
72
  const projectPaths = await (0, path_1.resolvePaths)({
74
73
  cwd,
@@ -77,10 +76,10 @@ const main = async (options) => {
77
76
  ignore,
78
77
  gitignore,
79
78
  });
80
- (0, debug_1.debugLogFiles)(options, 1, 'Globbed project paths', projectPaths);
79
+ (0, debug_1.debugLogFiles)(debug, 1, 'Globbed project paths', projectPaths);
81
80
  const project = (0, project_1.createProject)({ ...projectOptions, ...skipAddFiles }, projectPaths);
82
81
  const projectFiles = project.getSourceFiles();
83
- (0, debug_1.debugLogSourceFiles)(options, 1, 'Included project source files', projectFiles);
82
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Included project source files', projectFiles);
84
83
  return { entryFiles, productionFiles, projectFiles };
85
84
  }
86
85
  else {
@@ -90,7 +89,7 @@ const main = async (options) => {
90
89
  return { entryFiles: files, productionFiles: files, projectFiles: files };
91
90
  }
92
91
  })();
93
- const report = (0, config_1.resolveIncludedIssueGroups)(include, resolvedConfig ? exclude : ['files'], resolvedConfig);
92
+ const report = (0, config_1.resolveIncludedIssueTypes)(include, resolvedConfig ? exclude : ['files'], resolvedConfig);
94
93
  const config = {
95
94
  workingDir,
96
95
  report,
@@ -102,8 +101,8 @@ const main = async (options) => {
102
101
  peerDependencies: Object.keys(manifest.peerDependencies ?? {}),
103
102
  optionalDependencies: Object.keys(manifest.optionalDependencies ?? {}),
104
103
  devDependencies: Object.keys(manifest.devDependencies ?? {}),
105
- isDev: typeof resolvedConfig?.dev === 'boolean' ? resolvedConfig.dev : isDev,
106
- tsConfigPaths,
104
+ isDev: Boolean(resolvedConfig?.dev),
105
+ tsConfigPathGlobs: tsConfigPathGlobs,
107
106
  isShowProgress,
108
107
  jsDocOptions: {
109
108
  isReadPublicTag: jsDoc.includes('public'),
@@ -111,7 +110,7 @@ const main = async (options) => {
111
110
  debug,
112
111
  };
113
112
  const { issues, counters } = await (0, runner_1.findIssues)(config);
114
- (0, debug_1.debugLogObject)(options, 2, 'Issues', issues);
113
+ (0, debug_1.debugLogObject)(debug, 2, 'Issues', issues);
115
114
  return { report, issues, counters };
116
115
  };
117
116
  exports.main = main;
package/dist/progress.js CHANGED
@@ -1,11 +1,8 @@
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.getCountersUpdater = exports.getMessageUpdater = void 0;
7
- const node_path_1 = __importDefault(require("node:path"));
8
4
  const log_1 = require("./log");
5
+ const path_1 = require("./util/path");
9
6
  const lineRewriter = new log_1.LineRewriter();
10
7
  const getMessageUpdater = (configuration) => {
11
8
  const { isShowProgress } = configuration;
@@ -15,7 +12,7 @@ const getMessageUpdater = (configuration) => {
15
12
  };
16
13
  exports.getMessageUpdater = getMessageUpdater;
17
14
  const getCountersUpdater = (configuration, counters) => {
18
- const { workingDir, isShowProgress, report } = configuration;
15
+ const { isShowProgress, report } = configuration;
19
16
  if (!isShowProgress)
20
17
  return () => { };
21
18
  return (issue) => {
@@ -25,7 +22,7 @@ const getCountersUpdater = (configuration, counters) => {
25
22
  const percentage = Math.floor((processed / total) * 100);
26
23
  const messages = [(0, log_1.getLine)(`${percentage}%`, `of files processed (${processed} of ${total})`)];
27
24
  report.files && messages.push((0, log_1.getLine)(counters.files, 'unused files'));
28
- report.unlisted && messages.push((0, log_1.getLine)(counters.unresolved, 'unlisted dependencies'));
25
+ report.unlisted && messages.push((0, log_1.getLine)(counters.unlisted, 'unlisted dependencies'));
29
26
  report.exports && messages.push((0, log_1.getLine)(counters.exports, 'unused exports'));
30
27
  report.nsExports && messages.push((0, log_1.getLine)(counters.nsExports, 'unused exports in namespace'));
31
28
  report.types && messages.push((0, log_1.getLine)(counters.types, 'unused types'));
@@ -33,7 +30,7 @@ const getCountersUpdater = (configuration, counters) => {
33
30
  report.duplicates && messages.push((0, log_1.getLine)(counters.duplicates, 'duplicate exports'));
34
31
  if (processed < total) {
35
32
  messages.push('');
36
- messages.push(`Processing: ${node_path_1.default.relative(workingDir, issue.filePath)}`);
33
+ messages.push(`Processing: ${(0, path_1.relative)(issue.filePath)}`);
37
34
  }
38
35
  lineRewriter.update(messages);
39
36
  };
@@ -1,3 +1,3 @@
1
1
  import type { ReporterOptions } from '../types';
2
- declare const _default: ({ report, issues, cwd, isDev, options }: ReporterOptions) => void;
2
+ declare const _default: ({ report, issues, options }: ReporterOptions) => void;
3
3
  export default _default;
@@ -5,31 +5,33 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const node_path_1 = __importDefault(require("node:path"));
7
7
  const ownership_1 = require("@snyk/github-codeowners/dist/lib/ownership");
8
- const logIssueLine = (owner, cwd, filePath, symbols) => {
9
- console.log(`${owner} ${node_path_1.default.relative(cwd, filePath)}${symbols ? `: ${symbols.join(', ')}` : ''}`);
8
+ const path_1 = require("../util/path");
9
+ const constants_1 = require("./constants");
10
+ const logIssueLine = (owner, filePath, symbols) => {
11
+ console.log(`${owner} ${(0, path_1.relative)(filePath)}${symbols ? `: ${symbols.join(', ')}` : ''}`);
10
12
  };
11
- const logIssueGroupResult = (issues, cwd, title) => {
13
+ const logIssueSet = (issues, title) => {
12
14
  title && console.log(`--- ${title} (${issues.length})`);
13
15
  if (issues.length) {
14
16
  issues
15
17
  .sort((a, b) => (a.owner < b.owner ? -1 : 1))
16
- .forEach(issue => console.log(issue.owner, issue.symbol.startsWith('/') ? node_path_1.default.relative(cwd, issue.symbol) : issue.symbol));
18
+ .forEach(issue => console.log(issue.owner, issue.symbol.startsWith('/') ? (0, path_1.relative)(issue.symbol) : issue.symbol));
17
19
  }
18
20
  else {
19
21
  console.log('Not found');
20
22
  }
21
23
  };
22
- const logIssueGroupResults = (issues, cwd, title) => {
24
+ const logIssueRecord = (issues, title) => {
23
25
  title && console.log(`--- ${title} (${issues.length})`);
24
26
  if (issues.length) {
25
27
  const sortedByFilePath = issues.sort((a, b) => (a.owner < b.owner ? -1 : 1));
26
- sortedByFilePath.forEach(({ filePath, symbols, owner }) => logIssueLine(owner, cwd, filePath, symbols));
28
+ sortedByFilePath.forEach(({ filePath, symbols, owner }) => logIssueLine(owner, filePath, symbols));
27
29
  }
28
30
  else {
29
31
  console.log('Not found');
30
32
  }
31
33
  };
32
- exports.default = ({ report, issues, cwd, isDev, options }) => {
34
+ exports.default = ({ report, issues, options }) => {
33
35
  let opts = {};
34
36
  try {
35
37
  opts = options ? JSON.parse(options) : opts;
@@ -42,57 +44,27 @@ exports.default = ({ report, issues, cwd, isDev, options }) => {
42
44
  const reportMultipleGroups = Object.values(report).filter(Boolean).length > 1;
43
45
  const [dependenciesOwner = '[no-owner]'] = codeownersEngine.calcFileOwnership('package.json');
44
46
  const fallbackOwner = dependenciesOwner;
45
- const calcFileOwnership = (filePath) => codeownersEngine.calcFileOwnership(node_path_1.default.relative(cwd, filePath))[0] ?? fallbackOwner;
46
- const toIssueWithOwner = (issues) => {
47
- const items = Object.values(issues);
48
- return { ...items[0], symbols: items.map(i => i.symbol), owner: calcFileOwnership(items[0].filePath) };
49
- };
50
- if (report.files) {
51
- const unreferencedFilesByOwner = Array.from(issues.files).map(filePath => ({
52
- symbol: filePath,
53
- owner: calcFileOwnership(filePath),
54
- }));
55
- logIssueGroupResult(unreferencedFilesByOwner, cwd, reportMultipleGroups && 'UNUSED FILES');
56
- }
57
- if (report.dependencies) {
58
- const unreferencedDependencies = Array.from(issues.dependencies).map(dependency => ({
59
- symbol: dependency,
60
- owner: dependenciesOwner,
61
- }));
62
- logIssueGroupResult(unreferencedDependencies, cwd, reportMultipleGroups && 'UNUSED DEPENDENCIES');
63
- }
64
- if (report.dependencies && isDev) {
65
- const unreferencedDevDependencies = Array.from(issues.devDependencies).map(dependency => ({
66
- symbol: dependency,
67
- owner: dependenciesOwner,
68
- }));
69
- logIssueGroupResult(unreferencedDevDependencies, cwd, 'UNUSED DEV DEPENDENCIES');
70
- }
71
- if (report.unlisted) {
72
- const unreferencedDependencies = Object.values(issues.unresolved).map(toIssueWithOwner);
73
- logIssueGroupResults(unreferencedDependencies, cwd, reportMultipleGroups && 'UNLISTED DEPENDENCIES');
74
- }
75
- if (report.exports) {
76
- const unreferencedExports = Object.values(issues.exports).map(toIssueWithOwner);
77
- logIssueGroupResults(unreferencedExports, cwd, reportMultipleGroups && 'UNUSED EXPORTS');
78
- }
79
- if (report.nsExports) {
80
- const unreferencedNsExports = Object.values(issues.nsExports).map(toIssueWithOwner);
81
- logIssueGroupResults(unreferencedNsExports, cwd, reportMultipleGroups && 'UNUSED EXPORTS IN NAMESPACE');
82
- }
83
- if (report.types) {
84
- const unreferencedTypes = Object.values(issues.types).map(toIssueWithOwner);
85
- logIssueGroupResults(unreferencedTypes, cwd, reportMultipleGroups && 'UNUSED TYPES');
86
- }
87
- if (report.nsTypes) {
88
- const unreferencedNsTypes = Object.values(issues.nsTypes).map(toIssueWithOwner);
89
- logIssueGroupResults(unreferencedNsTypes, cwd, reportMultipleGroups && 'UNUSED TYPES IN NAMESPACE');
90
- }
91
- if (report.duplicates) {
92
- const unreferencedDuplicates = Object.values(issues.duplicates)
93
- .map(issues => Object.values(issues))
94
- .flat()
95
- .map(issue => ({ ...issue, owner: calcFileOwnership(issue.filePath) }));
96
- logIssueGroupResults(unreferencedDuplicates, cwd, reportMultipleGroups && 'DUPLICATE EXPORTS');
47
+ const calcFileOwnership = (filePath) => codeownersEngine.calcFileOwnership((0, path_1.relative)(filePath))[0] ?? fallbackOwner;
48
+ const addOwner = (issue) => ({ ...issue, owner: calcFileOwnership(issue.filePath) });
49
+ for (const [reportType, isReportType] of Object.entries(report)) {
50
+ if (isReportType) {
51
+ const title = reportMultipleGroups && constants_1.ISSUE_TYPE_TITLE[reportType];
52
+ if (issues[reportType] instanceof Set) {
53
+ const toIssue = (filePath) => ({ filePath, symbol: filePath });
54
+ const issuesForType = Array.from(issues[reportType]).map(toIssue);
55
+ logIssueSet(issuesForType.map(addOwner), title);
56
+ }
57
+ else if (reportType === 'duplicates') {
58
+ const issuesForType = Object.values(issues[reportType]).map(Object.values).flat().map(addOwner);
59
+ logIssueRecord(issuesForType, title);
60
+ }
61
+ else {
62
+ const issuesForType = Object.values(issues[reportType]).map(issues => {
63
+ const items = Object.values(issues);
64
+ return addOwner({ ...items[0], symbols: items.map(issue => issue.symbol) });
65
+ });
66
+ logIssueRecord(issuesForType, title);
67
+ }
68
+ }
97
69
  }
98
70
  };
@@ -1,3 +1,3 @@
1
1
  import type { ReporterOptions } from '../types';
2
- declare const _default: ({ report, issues, workingDir, isDev }: ReporterOptions) => void;
2
+ declare const _default: ({ report, issues }: ReporterOptions) => void;
3
3
  export default _default;
@@ -1,84 +1,48 @@
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
- const node_path_1 = __importDefault(require("node:path"));
7
- const logIssueLine = (workingDir, filePath, symbols) => {
8
- console.log(`${node_path_1.default.relative(workingDir, filePath)}${symbols ? `: ${symbols.join(', ')}` : ''}`);
3
+ const path_1 = require("../util/path");
4
+ const constants_1 = require("./constants");
5
+ const logIssueLine = (filePath, symbols) => {
6
+ console.log(`${(0, path_1.relative)(filePath)}${symbols ? `: ${symbols.join(', ')}` : ''}`);
9
7
  };
10
- const logIssueGroupResult = (issues, workingDir, title) => {
8
+ const logIssueSet = (issues, title) => {
11
9
  title && console.log(`--- ${title} (${issues.length})`);
12
10
  if (issues.length) {
13
- issues.sort().forEach(value => console.log(value.startsWith('/') ? node_path_1.default.relative(workingDir, value) : value));
11
+ issues.sort().forEach(value => console.log(value.startsWith('/') ? (0, path_1.relative)(value) : value));
14
12
  }
15
13
  else {
16
14
  console.log('Not found');
17
15
  }
18
16
  };
19
- const logIssueGroupResults = (issues, workingDir, title) => {
17
+ const logIssueRecord = (issues, title) => {
20
18
  title && console.log(`--- ${title} (${issues.length})`);
21
19
  if (issues.length) {
22
20
  const sortedByFilePath = issues.sort((a, b) => (a.filePath > b.filePath ? 1 : -1));
23
- sortedByFilePath.forEach(({ filePath, symbols }) => logIssueLine(workingDir, filePath, symbols));
21
+ sortedByFilePath.forEach(({ filePath, symbols }) => logIssueLine(filePath, symbols));
24
22
  }
25
23
  else {
26
24
  console.log('Not found');
27
25
  }
28
26
  };
29
- exports.default = ({ report, issues, workingDir, isDev }) => {
27
+ exports.default = ({ report, issues }) => {
30
28
  const reportMultipleGroups = Object.values(report).filter(Boolean).length > 1;
31
- if (report.files) {
32
- const unreferencedFiles = Array.from(issues.files);
33
- logIssueGroupResult(unreferencedFiles, workingDir, reportMultipleGroups && 'UNUSED FILES');
34
- }
35
- if (report.dependencies) {
36
- const unreferencedDependencies = Array.from(issues.dependencies);
37
- logIssueGroupResult(unreferencedDependencies, workingDir, reportMultipleGroups && 'UNUSED DEPENDENCIES');
38
- }
39
- if (report.dependencies && isDev) {
40
- const unreferencedDevDependencies = Array.from(issues.devDependencies);
41
- logIssueGroupResult(unreferencedDevDependencies, workingDir, 'UNUSED DEV DEPENDENCIES');
42
- }
43
- if (report.unlisted) {
44
- const unreferencedDependencies = Object.values(issues.unresolved).map(issues => {
45
- const items = Object.values(issues);
46
- return { ...items[0], symbols: items.map(i => i.symbol) };
47
- });
48
- logIssueGroupResults(unreferencedDependencies, workingDir, reportMultipleGroups && 'UNLISTED DEPENDENCIES');
49
- }
50
- if (report.exports) {
51
- const unreferencedExports = Object.values(issues.exports).map(issues => {
52
- const items = Object.values(issues);
53
- return { ...items[0], symbols: items.map(i => i.symbol) };
54
- });
55
- logIssueGroupResults(unreferencedExports, workingDir, reportMultipleGroups && 'UNUSED EXPORTS');
56
- }
57
- if (report.nsExports) {
58
- const unreferencedNsExports = Object.values(issues.nsExports).map(issues => {
59
- const items = Object.values(issues);
60
- return { ...items[0], symbols: items.map(i => i.symbol) };
61
- });
62
- logIssueGroupResults(unreferencedNsExports, workingDir, reportMultipleGroups && 'UNUSED EXPORTS IN NAMESPACE');
63
- }
64
- if (report.types) {
65
- const unreferencedTypes = Object.values(issues.types).map(issues => {
66
- const items = Object.values(issues);
67
- return { ...items[0], symbols: items.map(i => i.symbol) };
68
- });
69
- logIssueGroupResults(unreferencedTypes, workingDir, reportMultipleGroups && 'UNUSED TYPES');
70
- }
71
- if (report.nsTypes) {
72
- const unreferencedNsTypes = Object.values(issues.nsTypes).map(issues => {
73
- const items = Object.values(issues);
74
- return { ...items[0], symbols: items.map(i => i.symbol) };
75
- });
76
- logIssueGroupResults(unreferencedNsTypes, workingDir, reportMultipleGroups && 'UNUSED TYPES IN NAMESPACE');
77
- }
78
- if (report.duplicates) {
79
- const unreferencedDuplicates = Object.values(issues.duplicates)
80
- .map(issues => Object.values(issues))
81
- .flat();
82
- logIssueGroupResults(unreferencedDuplicates, workingDir, reportMultipleGroups && 'DUPLICATE EXPORTS');
29
+ for (const [reportType, isReportType] of Object.entries(report)) {
30
+ if (isReportType) {
31
+ const title = reportMultipleGroups && constants_1.ISSUE_TYPE_TITLE[reportType];
32
+ if (issues[reportType] instanceof Set) {
33
+ logIssueSet(Array.from(issues[reportType]), title);
34
+ }
35
+ else if (reportType === 'duplicates') {
36
+ const issuesForType = Object.values(issues[reportType]).map(Object.values).flat();
37
+ logIssueRecord(issuesForType, title);
38
+ }
39
+ else {
40
+ const issuesForType = Object.values(issues[reportType]).map(issues => {
41
+ const items = Object.values(issues);
42
+ return { ...items[0], symbols: items.map(issue => issue.symbol) };
43
+ });
44
+ logIssueRecord(issuesForType, title);
45
+ }
46
+ }
83
47
  }
84
48
  };
@@ -0,0 +1,11 @@
1
+ export declare const ISSUE_TYPE_TITLE: {
2
+ files: string;
3
+ dependencies: string;
4
+ devDependencies: string;
5
+ unlisted: string;
6
+ exports: string;
7
+ nsExports: string;
8
+ types: string;
9
+ nsTypes: string;
10
+ duplicates: string;
11
+ };
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ISSUE_TYPE_TITLE = void 0;
4
+ exports.ISSUE_TYPE_TITLE = {
5
+ files: 'UNUSED FILES',
6
+ dependencies: 'UNUSED DEPENDENCIES',
7
+ devDependencies: 'UNUSED DEV DEPENDENCIES',
8
+ unlisted: 'UNLISTED DEPENDENCIES',
9
+ exports: 'UNUSED EXPORTS',
10
+ nsExports: 'UNUSED EXPORTS IN NAMESPACE',
11
+ types: 'UNUSED TYPES',
12
+ nsTypes: 'UNUSED TYPES IN NAMESPACE',
13
+ duplicates: 'DUPLICATE EXPORTS',
14
+ };
@@ -1,7 +1,7 @@
1
1
  declare const _default: {
2
- symbols: ({ report, issues, workingDir, isDev }: import("../types").ReporterOptions) => void;
3
- compact: ({ report, issues, workingDir, isDev }: import("../types").ReporterOptions) => void;
4
- codeowners: ({ report, issues, cwd, isDev, options }: import("../types").ReporterOptions) => void;
5
- json: ({ report, issues, cwd, options }: import("../types").ReporterOptions) => Promise<void>;
2
+ symbols: ({ report, issues }: import("../types").ReporterOptions) => void;
3
+ compact: ({ report, issues }: import("../types").ReporterOptions) => void;
4
+ codeowners: ({ report, issues, options }: import("../types").ReporterOptions) => void;
5
+ json: ({ report, issues, options }: import("../types").ReporterOptions) => Promise<void>;
6
6
  };
7
7
  export default _default;
@@ -1,3 +1,3 @@
1
1
  import type { ReporterOptions } from '../types';
2
- declare const _default: ({ report, issues, cwd, options }: ReporterOptions) => Promise<void>;
2
+ declare const _default: ({ report, issues, options }: ReporterOptions) => Promise<void>;
3
3
  export default _default;
@@ -5,8 +5,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const node_path_1 = __importDefault(require("node:path"));
7
7
  const fs_1 = require("../util/fs");
8
+ const path_1 = require("../util/path");
8
9
  const ownership_1 = require("@snyk/github-codeowners/dist/lib/ownership");
9
- exports.default = async ({ report, issues, cwd, options }) => {
10
+ const mergeTypes = (type) => type === 'exports' || type === 'nsExports' ? 'exports' : type === 'types' || type === 'nsTypes' ? 'types' : type;
11
+ exports.default = async ({ report, issues, options }) => {
10
12
  let opts = {};
11
13
  try {
12
14
  opts = options ? JSON.parse(options) : opts;
@@ -19,7 +21,7 @@ exports.default = async ({ report, issues, cwd, options }) => {
19
21
  const codeownersEngine = (await (0, fs_1.isFile)(codeownersFilePath)) && ownership_1.OwnershipEngine.FromCodeownersFile(codeownersFilePath);
20
22
  const flatten = (issues) => Object.values(issues).map(Object.values).flat();
21
23
  const initRow = (filePath) => {
22
- const file = node_path_1.default.relative(cwd, filePath);
24
+ const file = (0, path_1.relative)(filePath);
23
25
  const row = {
24
26
  file,
25
27
  ...(codeownersEngine && { owners: codeownersEngine.calcFileOwnership(file) }),
@@ -31,48 +33,22 @@ exports.default = async ({ report, issues, cwd, options }) => {
31
33
  };
32
34
  return row;
33
35
  };
34
- if (report.files) {
35
- issues.files.forEach(filePath => {
36
- json[filePath] = json[filePath] ?? initRow(filePath);
37
- json[filePath].files = true;
38
- });
39
- }
40
- if (report.unlisted) {
41
- flatten(issues.unresolved).forEach(({ filePath, symbol }) => {
42
- json[filePath] = json[filePath] ?? initRow(filePath);
43
- json[filePath].unlisted?.push(symbol);
44
- });
45
- }
46
- if (report.exports) {
47
- flatten(issues.exports).forEach(({ filePath, symbol }) => {
48
- json[filePath] = json[filePath] ?? initRow(filePath);
49
- json[filePath].exports?.push(symbol);
50
- });
51
- }
52
- if (report.nsExports) {
53
- flatten(issues.nsExports).forEach(({ filePath, symbol }) => {
54
- json[filePath] = json[filePath] ?? initRow(filePath);
55
- json[filePath].exports?.push(symbol);
56
- });
57
- }
58
- if (report.types) {
59
- flatten(issues.types).forEach(({ filePath, symbol }) => {
60
- json[filePath] = json[filePath] ?? initRow(filePath);
61
- json[filePath].types = json[filePath].types ?? [];
62
- json[filePath].types?.push(symbol);
63
- });
64
- }
65
- if (report.nsTypes) {
66
- flatten(issues.nsTypes).forEach(({ filePath, symbol }) => {
67
- json[filePath] = json[filePath] ?? initRow(filePath);
68
- json[filePath].types?.push(symbol);
69
- });
70
- }
71
- if (report.duplicates) {
72
- flatten(issues.duplicates).forEach(({ filePath, symbols }) => {
73
- json[filePath] = json[filePath] ?? initRow(filePath);
74
- json[filePath].duplicates?.push(...symbols);
75
- });
36
+ for (const [reportType, isReportType] of Object.entries(report)) {
37
+ if (isReportType) {
38
+ if (reportType === 'files') {
39
+ Array.from(issues[reportType]).forEach(filePath => {
40
+ json[filePath] = json[filePath] ?? initRow(filePath);
41
+ json[filePath][reportType] = true;
42
+ });
43
+ }
44
+ else {
45
+ const type = mergeTypes(reportType);
46
+ flatten(issues[reportType]).forEach(({ filePath, symbol }) => {
47
+ json[filePath] = json[filePath] ?? initRow(filePath);
48
+ json[filePath][type]?.push(symbol);
49
+ });
50
+ }
51
+ }
76
52
  }
77
53
  console.log(JSON.stringify(Object.values(json)));
78
54
  };
@@ -1,3 +1,3 @@
1
1
  import type { ReporterOptions } from '../types';
2
- declare const _default: ({ report, issues, workingDir, isDev }: ReporterOptions) => void;
2
+ declare const _default: ({ report, issues }: ReporterOptions) => void;
3
3
  export default _default;
@@ -1,72 +1,47 @@
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
- const node_path_1 = __importDefault(require("node:path"));
3
+ const path_1 = require("../util/path");
4
+ const constants_1 = require("./constants");
7
5
  const TRUNCATE_WIDTH = 40;
8
- const logIssueLine = (issue, workingDir, maxWidth) => {
6
+ const logIssueLine = (issue, maxWidth) => {
9
7
  const symbols = issue.symbols ? issue.symbols.join(', ') : issue.symbol;
10
8
  const truncatedSymbol = symbols.length > maxWidth ? symbols.slice(0, maxWidth - 3) + '...' : symbols;
11
- const filePath = node_path_1.default.relative(workingDir, issue.filePath);
9
+ const filePath = (0, path_1.relative)(issue.filePath);
12
10
  console.log(`${truncatedSymbol.padEnd(maxWidth + 2)}${issue.symbolType?.padEnd(11) || ''}${filePath}`);
13
11
  };
14
- const logIssueGroupResult = (issues, workingDir, title) => {
12
+ const logIssueSet = (issues, title) => {
15
13
  title && console.log(`--- ${title} (${issues.length})`);
16
14
  if (issues.length) {
17
- issues.sort().forEach(value => console.log(value.startsWith('/') ? node_path_1.default.relative(workingDir, value) : value));
15
+ issues.sort().forEach(value => console.log(value.startsWith('/') ? (0, path_1.relative)(value) : value));
18
16
  }
19
17
  else {
20
18
  console.log('Not found');
21
19
  }
22
20
  };
23
- const logIssueGroupResults = (issues, workingDir, title, isTruncate = false) => {
21
+ const logIssueRecord = (issues, title, isTruncate = false) => {
24
22
  title && console.log(`--- ${title} (${issues.length})`);
25
23
  if (issues.length) {
26
24
  const sortedByFilePath = issues.sort((a, b) => (a.filePath > b.filePath ? 1 : -1));
27
25
  const maxWidth = isTruncate ? TRUNCATE_WIDTH : issues.reduce((max, issue) => Math.max(issue.symbol.length, max), 0);
28
- sortedByFilePath.forEach(issue => logIssueLine(issue, workingDir, maxWidth));
26
+ sortedByFilePath.forEach(issue => logIssueLine(issue, maxWidth));
29
27
  }
30
28
  else {
31
29
  console.log('Not found');
32
30
  }
33
31
  };
34
- exports.default = ({ report, issues, workingDir, isDev }) => {
32
+ exports.default = ({ report, issues }) => {
35
33
  const reportMultipleGroups = Object.values(report).filter(Boolean).length > 1;
36
- if (report.files) {
37
- const unreferencedFiles = Array.from(issues.files);
38
- logIssueGroupResult(unreferencedFiles, workingDir, reportMultipleGroups && 'UNUSED FILES');
39
- }
40
- if (report.dependencies) {
41
- const unreferencedDependencies = Array.from(issues.dependencies);
42
- logIssueGroupResult(unreferencedDependencies, workingDir, reportMultipleGroups && 'UNUSED DEPENDENCIES');
43
- }
44
- if (report.dependencies && isDev) {
45
- const unreferencedDevDependencies = Array.from(issues.devDependencies);
46
- logIssueGroupResult(unreferencedDevDependencies, workingDir, 'UNUSED DEV DEPENDENCIES');
47
- }
48
- if (report.unlisted) {
49
- const unresolvedDependencies = Object.values(issues.unresolved).map(Object.values).flat();
50
- logIssueGroupResults(unresolvedDependencies, workingDir, reportMultipleGroups && 'UNLISTED DEPENDENCIES');
51
- }
52
- if (report.exports) {
53
- const unreferencedExports = Object.values(issues.exports).map(Object.values).flat();
54
- logIssueGroupResults(unreferencedExports, workingDir, reportMultipleGroups && 'UNUSED EXPORTS');
55
- }
56
- if (report.nsExports) {
57
- const unreferencedNsExports = Object.values(issues.nsExports).map(Object.values).flat();
58
- logIssueGroupResults(unreferencedNsExports, workingDir, reportMultipleGroups && 'UNUSED EXPORTS IN NAMESPACE');
59
- }
60
- if (report.types) {
61
- const unreferencedTypes = Object.values(issues.types).map(Object.values).flat();
62
- logIssueGroupResults(unreferencedTypes, workingDir, reportMultipleGroups && 'UNUSED TYPES');
63
- }
64
- if (report.nsTypes) {
65
- const unreferencedNsTypes = Object.values(issues.nsTypes).map(Object.values).flat();
66
- logIssueGroupResults(unreferencedNsTypes, workingDir, reportMultipleGroups && 'UNUSED TYPES IN NAMESPACE');
67
- }
68
- if (report.duplicates) {
69
- const unreferencedDuplicates = Object.values(issues.duplicates).map(Object.values).flat();
70
- logIssueGroupResults(unreferencedDuplicates, workingDir, reportMultipleGroups && 'DUPLICATE EXPORTS', true);
34
+ for (const [reportType, isReportType] of Object.entries(report)) {
35
+ if (isReportType) {
36
+ const title = reportMultipleGroups && constants_1.ISSUE_TYPE_TITLE[reportType];
37
+ if (issues[reportType] instanceof Set) {
38
+ logIssueSet(Array.from(issues[reportType]), title);
39
+ }
40
+ else {
41
+ const issuesForType = Object.values(issues[reportType]).map(Object.values).flat();
42
+ const isTruncate = Boolean(issuesForType[0]?.symbols?.length);
43
+ logIssueRecord(issuesForType, title, isTruncate);
44
+ }
45
+ }
71
46
  }
72
47
  };
package/dist/runner.js CHANGED
@@ -13,21 +13,21 @@ const dependencies_1 = require("./util/dependencies");
13
13
  const debug_1 = require("./util/debug");
14
14
  const progress_1 = require("./progress");
15
15
  async function findIssues(configuration) {
16
- const { workingDir, report, isDev, jsDocOptions } = configuration;
16
+ const { workingDir, report, isDev, jsDocOptions, debug } = configuration;
17
17
  const { entryFiles, productionFiles, projectFiles, isIncludeEntryFiles } = configuration;
18
18
  const updateMessage = (0, progress_1.getMessageUpdater)(configuration);
19
19
  const { getUnresolvedDependencies, getUnusedDependencies, getUnusedDevDependencies } = (0, dependencies_1.getDependencyAnalyzer)(configuration);
20
20
  const [usedProductionFiles, unreferencedProductionFiles] = (0, project_1.partitionSourceFiles)(projectFiles, productionFiles);
21
21
  const [usedEntryFiles, usedNonEntryFiles] = (0, project_1.partitionSourceFiles)(usedProductionFiles, entryFiles);
22
- (0, debug_1.debugLogSourceFiles)(configuration, 1, 'Used production files', usedProductionFiles);
23
- (0, debug_1.debugLogSourceFiles)(configuration, 1, 'Unreferenced production files', unreferencedProductionFiles);
24
- (0, debug_1.debugLogSourceFiles)(configuration, 1, 'Used entry files', usedEntryFiles);
25
- (0, debug_1.debugLogSourceFiles)(configuration, 1, 'Used non-entry files', usedNonEntryFiles);
22
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Used production files', usedProductionFiles);
23
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Unreferenced production files', unreferencedProductionFiles);
24
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Used entry files', usedEntryFiles);
25
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Used non-entry files', usedNonEntryFiles);
26
26
  const issues = {
27
27
  files: new Set(unreferencedProductionFiles.map(file => file.getFilePath())),
28
28
  dependencies: new Set(),
29
29
  devDependencies: new Set(),
30
- unresolved: {},
30
+ unlisted: {},
31
31
  exports: {},
32
32
  types: {},
33
33
  nsExports: {},
@@ -38,7 +38,7 @@ async function findIssues(configuration) {
38
38
  files: issues.files.size,
39
39
  dependencies: issues.dependencies.size,
40
40
  devDependencies: issues.dependencies.size,
41
- unresolved: 0,
41
+ unlisted: 0,
42
42
  exports: 0,
43
43
  types: 0,
44
44
  nsExports: 0,
@@ -50,7 +50,7 @@ async function findIssues(configuration) {
50
50
  const updateCounters = (0, progress_1.getCountersUpdater)(configuration, counters);
51
51
  const addSymbolIssue = (issueType, issue) => {
52
52
  const { filePath, symbol } = issue;
53
- const key = node_path_1.default.relative(workingDir, filePath);
53
+ const key = node_path_1.default.relative(workingDir, filePath).replace(/\\/g, '/');
54
54
  issues[issueType][key] = issues[issueType][key] ?? {};
55
55
  issues[issueType][key][symbol] = issue;
56
56
  counters[issueType]++;
@@ -76,7 +76,7 @@ async function findIssues(configuration) {
76
76
  const filePath = sourceFile.getFilePath();
77
77
  if (report.dependencies || report.unlisted) {
78
78
  const unresolvedDependencies = getUnresolvedDependencies(sourceFile);
79
- unresolvedDependencies.forEach(issue => addSymbolIssue('unresolved', issue));
79
+ unresolvedDependencies.forEach(issue => addSymbolIssue('unlisted', issue));
80
80
  }
81
81
  const exportDeclarations = sourceFile.getExportedDeclarations();
82
82
  if (report.duplicates) {
package/dist/types.d.ts CHANGED
@@ -1,30 +1,30 @@
1
1
  import { SourceFile } from 'ts-morph';
2
2
  declare type SymbolType = 'type' | 'interface' | 'enum';
3
- declare type UnusedFileIssues = Set<string>;
4
- declare type UnusedExportIssues = Record<string, Record<string, Issue>>;
5
- declare type UnresolvedDependencyIssues = Record<string, Record<string, Issue>>;
6
- declare type UnusedDependencyIssues = Set<string>;
7
3
  export declare type Issue = {
8
4
  filePath: string;
9
5
  symbol: string;
10
6
  symbols?: string[];
11
7
  symbolType?: SymbolType;
12
8
  };
9
+ export declare type IssueSet = Set<string>;
10
+ export declare type IssueRecords = Record<string, Record<string, Issue>>;
13
11
  export declare type Issues = {
14
- files: UnusedFileIssues;
15
- dependencies: UnusedDependencyIssues;
16
- devDependencies: UnusedDependencyIssues;
17
- unresolved: UnresolvedDependencyIssues;
18
- exports: UnusedExportIssues;
19
- types: UnusedExportIssues;
20
- nsExports: UnusedExportIssues;
21
- nsTypes: UnusedExportIssues;
22
- duplicates: UnusedExportIssues;
12
+ files: IssueSet;
13
+ dependencies: IssueSet;
14
+ devDependencies: IssueSet;
15
+ unlisted: IssueRecords;
16
+ exports: IssueRecords;
17
+ types: IssueRecords;
18
+ nsExports: IssueRecords;
19
+ nsTypes: IssueRecords;
20
+ duplicates: IssueRecords;
23
21
  };
24
- declare type IssueType = keyof Issues;
22
+ export declare type IssueType = keyof Issues;
25
23
  export declare type ProjectIssueType = Extract<IssueType, 'files' | 'dependencies' | 'devDependencies'>;
26
24
  export declare type SymbolIssueType = Exclude<IssueType, ProjectIssueType>;
27
- export declare type IssueGroup = 'files' | 'dependencies' | 'unlisted' | 'exports' | 'nsExports' | 'types' | 'nsTypes' | 'duplicates';
25
+ export declare type Report = {
26
+ [key in keyof Issues]: boolean;
27
+ };
28
28
  declare type BaseLocalConfiguration = {
29
29
  entryFiles: string[];
30
30
  projectFiles: string[];
@@ -53,9 +53,6 @@ export declare type UnresolvedConfiguration = {
53
53
  level: number;
54
54
  };
55
55
  };
56
- export declare type Report = {
57
- [key in IssueGroup]: boolean;
58
- };
59
56
  export declare type Configuration = {
60
57
  workingDir: string;
61
58
  report: Report;
@@ -68,7 +65,7 @@ export declare type Configuration = {
68
65
  optionalDependencies: string[];
69
66
  devDependencies: string[];
70
67
  isDev: boolean;
71
- tsConfigPaths: string[];
68
+ tsConfigPathGlobs: string[];
72
69
  isShowProgress: boolean;
73
70
  jsDocOptions: {
74
71
  isReadPublicTag: boolean;
@@ -1,6 +1,6 @@
1
- import type { ImportedConfiguration, LocalConfiguration } from '../types';
1
+ import type { ImportedConfiguration, LocalConfiguration, Report } from '../types';
2
2
  export declare const resolveConfig: (importedConfiguration: ImportedConfiguration, options?: {
3
3
  workingDir?: string;
4
4
  isDev?: boolean;
5
5
  }) => LocalConfiguration | undefined;
6
- export declare const resolveIncludedIssueGroups: (includeArg: string[], excludeArg: string[], resolvedConfig?: LocalConfiguration) => import("../types").Report;
6
+ export declare const resolveIncludedIssueTypes: (includeArg: string[], excludeArg: string[], resolvedConfig?: LocalConfiguration) => Report;
@@ -3,7 +3,7 @@ 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.resolveIncludedIssueGroups = exports.resolveConfig = void 0;
6
+ exports.resolveIncludedIssueTypes = exports.resolveConfig = void 0;
7
7
  const micromatch_1 = __importDefault(require("micromatch"));
8
8
  const resolveConfig = (importedConfiguration, options) => {
9
9
  if (!importedConfiguration)
@@ -24,29 +24,20 @@ const resolveConfig = (importedConfiguration, options) => {
24
24
  console.info(`Add these properties at root level, or use --dir and match one of: ${configKeys.join(', ')}\n`);
25
25
  return;
26
26
  }
27
+ resolvedConfig.dev = Boolean(typeof resolvedConfig.dev === 'boolean' ? resolvedConfig.dev : isDev);
27
28
  return resolvedConfig;
28
29
  };
29
30
  exports.resolveConfig = resolveConfig;
30
- const resolveIncludedIssueGroups = (includeArg, excludeArg, resolvedConfig) => {
31
- const groups = [
32
- 'files',
33
- 'dependencies',
34
- 'unlisted',
35
- 'exports',
36
- 'types',
37
- 'nsExports',
38
- 'nsTypes',
39
- 'duplicates',
40
- ];
41
- const include = [includeArg, resolvedConfig?.include ?? []]
42
- .flat()
43
- .map(value => value.split(','))
44
- .flat();
45
- const exclude = [excludeArg, resolvedConfig?.exclude ?? []]
46
- .flat()
47
- .map(value => value.split(','))
48
- .flat();
49
- const includes = (include.length > 0 ? include : groups).filter((group) => !exclude.includes(group));
50
- return groups.reduce((r, group) => ((r[group] = includes.includes(group)), r), {});
31
+ const resolveIncludedIssueTypes = (includeArg, excludeArg, resolvedConfig) => {
32
+ const deps = resolvedConfig?.dev ? ['dependencies', 'devDependencies'] : ['dependencies'];
33
+ const groups = ['files', ...deps, 'unlisted', 'exports', 'types', 'nsExports', 'nsTypes', 'duplicates'];
34
+ const normalizedIncludesArg = includeArg.map(value => value.split(',')).flat();
35
+ const normalizedExcludesArg = excludeArg.map(value => value.split(',')).flat();
36
+ const excludes = (resolvedConfig?.exclude ?? []).filter(exclude => !normalizedIncludesArg.includes(exclude));
37
+ const includes = (resolvedConfig?.include ?? []).filter(include => !normalizedExcludesArg.includes(include));
38
+ const include = [normalizedIncludesArg, includes].flat();
39
+ const exclude = [normalizedExcludesArg, excludes].flat();
40
+ const included = (include.length > 0 ? include : groups).filter(group => !exclude.includes(group));
41
+ return groups.reduce((types, group) => ((types[group] = included.includes(group)), types), {});
51
42
  };
52
- exports.resolveIncludedIssueGroups = resolveIncludedIssueGroups;
43
+ exports.resolveIncludedIssueTypes = resolveIncludedIssueTypes;
@@ -1,12 +1,9 @@
1
1
  import type { SourceFile } from 'ts-morph';
2
- declare type Config = {
3
- debug: {
4
- isEnabled: boolean;
5
- level: number;
6
- };
2
+ declare type Debug = {
3
+ isEnabled: boolean;
4
+ level: number;
7
5
  };
8
- export declare const debugLogObject: (config: Config, minimumLevel: number, name: string, obj: unknown) => void;
9
- export declare const debugLogFiles: (config: Config, minimumLevel: number, name: string, filePaths: string[]) => void;
10
- export declare const debugLogSourceFiles: (config: Config, minimumLevel: number, name: string, sourceFiles: SourceFile[]) => void;
11
- export declare const debugLogDiff: (config: Config, minimumLevel: number, name: string, arrA: string[], arrB: string[]) => void;
6
+ export declare const debugLogObject: (debug: Debug, minimumLevel: number, name: string, obj: unknown) => void;
7
+ export declare const debugLogFiles: (debug: Debug, minimumLevel: number, name: string, filePaths: string[]) => void;
8
+ export declare const debugLogSourceFiles: (debug: Debug, minimumLevel: number, name: string, sourceFiles: SourceFile[]) => void;
12
9
  export {};
@@ -3,20 +3,19 @@ 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.debugLogDiff = exports.debugLogSourceFiles = exports.debugLogFiles = exports.debugLogObject = void 0;
6
+ exports.debugLogSourceFiles = exports.debugLogFiles = exports.debugLogObject = void 0;
7
7
  const node_util_1 = __importDefault(require("node:util"));
8
8
  const logArray = (collection) => console.log(node_util_1.default.inspect(collection, { maxArrayLength: null }));
9
- const debugLogObject = (config, minimumLevel, name, obj) => {
10
- if (minimumLevel > config.debug.level)
9
+ const debugLogObject = (debug, minimumLevel, name, obj) => {
10
+ if (minimumLevel > debug.level)
11
11
  return;
12
12
  console.log(`[knip] ${name}:`);
13
13
  console.log(node_util_1.default.inspect(obj, { depth: null, colors: true }));
14
14
  };
15
15
  exports.debugLogObject = debugLogObject;
16
- const debugLogFiles = (config, minimumLevel, name, filePaths) => {
17
- if (minimumLevel > config.debug.level)
16
+ const debugLogFiles = (debug, minimumLevel, name, filePaths) => {
17
+ if (minimumLevel > debug.level)
18
18
  return;
19
- const { debug } = config;
20
19
  if (debug.level > 1) {
21
20
  console.debug(`[knip] ${name} (${filePaths.length}):`);
22
21
  logArray(filePaths);
@@ -26,10 +25,9 @@ const debugLogFiles = (config, minimumLevel, name, filePaths) => {
26
25
  }
27
26
  };
28
27
  exports.debugLogFiles = debugLogFiles;
29
- const debugLogSourceFiles = (config, minimumLevel, name, sourceFiles) => {
30
- if (minimumLevel > config.debug.level)
28
+ const debugLogSourceFiles = (debug, minimumLevel, name, sourceFiles) => {
29
+ if (minimumLevel > debug.level)
31
30
  return;
32
- const { debug } = config;
33
31
  if (debug.level > 1) {
34
32
  console.debug(`[knip] ${name} (${sourceFiles.length}):`);
35
33
  logArray(sourceFiles.map(sourceFile => sourceFile.getFilePath()));
@@ -39,8 +37,8 @@ const debugLogSourceFiles = (config, minimumLevel, name, sourceFiles) => {
39
37
  }
40
38
  };
41
39
  exports.debugLogSourceFiles = debugLogSourceFiles;
42
- const debugLogDiff = (config, minimumLevel, name, arrA, arrB) => {
43
- if (minimumLevel > config.debug.level)
40
+ const debugLogDiff = (debug, minimumLevel, name, arrA, arrB) => {
41
+ if (minimumLevel > debug.level)
44
42
  return;
45
43
  const onlyInA = arrA.filter(itemA => !arrB.includes(itemA)).sort();
46
44
  const onlyInB = arrB.filter(itemB => !arrA.includes(itemB)).sort();
@@ -51,4 +49,3 @@ const debugLogDiff = (config, minimumLevel, name, arrA, arrB) => {
51
49
  console.log(`[knip] Only in right:`);
52
50
  logArray(onlyInB);
53
51
  };
54
- exports.debugLogDiff = debugLogDiff;
@@ -7,29 +7,35 @@ exports.getDependencyAnalyzer = void 0;
7
7
  const ts_morph_1 = require("ts-morph");
8
8
  const is_builtin_module_1 = __importDefault(require("is-builtin-module"));
9
9
  const micromatch_1 = __importDefault(require("micromatch"));
10
+ const ts_morph_helpers_1 = require("ts-morph-helpers");
10
11
  const compact = (collection) => Array.from(new Set(collection)).filter((value) => Boolean(value));
12
+ const findRequireModuleSpecifiers = (sourceFile) => (0, ts_morph_helpers_1.findCallExpressionsByName)(sourceFile, 'require').map(expression => expression.getFirstDescendantByKind(ts_morph_1.ts.SyntaxKind.StringLiteral));
13
+ const isExternalDependency = (moduleSpecifier, tsConfigPathGlobs) => {
14
+ if (moduleSpecifier.startsWith('.'))
15
+ return false;
16
+ if ((0, is_builtin_module_1.default)(moduleSpecifier))
17
+ return false;
18
+ if (tsConfigPathGlobs.length > 0 && micromatch_1.default.isMatch(moduleSpecifier, tsConfigPathGlobs))
19
+ return false;
20
+ return true;
21
+ };
22
+ const resolvePackageName = (moduleSpecifier) => {
23
+ const parts = moduleSpecifier.split('/').slice(0, 2);
24
+ return moduleSpecifier.startsWith('@') ? parts.join('/') : parts[0];
25
+ };
11
26
  const getDependencyAnalyzer = (configuration) => {
12
- const { dependencies, devDependencies, peerDependencies, optionalDependencies, tsConfigPaths } = configuration;
27
+ const { dependencies, devDependencies, peerDependencies, optionalDependencies, tsConfigPathGlobs } = configuration;
13
28
  const productionDependencies = [...dependencies, ...peerDependencies, ...optionalDependencies];
14
29
  const referencedDependencies = new Set();
15
30
  const getUnresolvedDependencies = (sourceFile) => {
16
31
  const unresolvedDependencies = new Set();
17
32
  const importLiterals = sourceFile.getImportStringLiterals();
18
- const requires = sourceFile
19
- .getDescendantsOfKind(ts_morph_1.ts.SyntaxKind.CallExpression)
20
- .filter(callExpression => callExpression.getExpression().getText() === 'require')
21
- .map(expression => expression.getFirstDescendantByKind(ts_morph_1.ts.SyntaxKind.StringLiteral));
22
- const literals = compact([importLiterals, requires].flat());
23
- literals.forEach(importLiteral => {
24
- const moduleSpecifier = importLiteral.getLiteralText();
25
- if (moduleSpecifier.startsWith('.'))
26
- return;
27
- if ((0, is_builtin_module_1.default)(moduleSpecifier))
28
- return;
29
- if (tsConfigPaths.length > 0 && micromatch_1.default.isMatch(moduleSpecifier, tsConfigPaths))
33
+ const requireCallExpressions = findRequireModuleSpecifiers(sourceFile);
34
+ const moduleSpecifiers = compact([importLiterals, requireCallExpressions].flat()).map(i => i.getLiteralText());
35
+ moduleSpecifiers.forEach(moduleSpecifier => {
36
+ if (!isExternalDependency(moduleSpecifier, tsConfigPathGlobs))
30
37
  return;
31
- const parts = moduleSpecifier.split('/').slice(0, 2);
32
- const packageName = moduleSpecifier.startsWith('@') ? parts.join('/') : parts[0];
38
+ const packageName = resolvePackageName(moduleSpecifier);
33
39
  if (!productionDependencies.includes(packageName) && !devDependencies.includes(packageName)) {
34
40
  unresolvedDependencies.add({ filePath: sourceFile.getFilePath(), symbol: moduleSpecifier });
35
41
  }
@@ -1,3 +1,4 @@
1
+ export declare const relative: (to: string) => string;
1
2
  export declare const resolvePaths: ({ cwd, workingDir, patterns, ignore, gitignore, }: {
2
3
  cwd: string;
3
4
  workingDir: string;
package/dist/util/path.js CHANGED
@@ -3,8 +3,11 @@ 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 = void 0;
6
+ exports.resolvePaths = exports.relative = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
+ const cwd = process.cwd();
9
+ const relative = (to) => node_path_1.default.relative(cwd, to);
10
+ exports.relative = relative;
8
11
  let _globby;
9
12
  const glob = async function (patterns, options) {
10
13
  if (!_globby) {
@@ -15,12 +18,12 @@ const glob = async function (patterns, options) {
15
18
  };
16
19
  const prependDirToPattern = (workingDir, pattern) => {
17
20
  if (pattern.startsWith('!'))
18
- return '!' + node_path_1.default.join(workingDir, pattern.slice(1));
19
- return node_path_1.default.join(workingDir, pattern);
21
+ return '!' + node_path_1.default.posix.join(workingDir, pattern.slice(1));
22
+ return node_path_1.default.posix.join(workingDir, pattern);
20
23
  };
21
- const resolvePaths = async ({ cwd, workingDir, patterns, ignore, gitignore, }) => glob(patterns.map(pattern => prependDirToPattern(node_path_1.default.relative(cwd, workingDir), pattern)), {
24
+ const resolvePaths = async ({ cwd, workingDir, patterns, ignore, gitignore, }) => glob(patterns.map(pattern => prependDirToPattern(node_path_1.default.posix.relative(cwd, workingDir), pattern)), {
22
25
  cwd,
23
- ignore,
26
+ ignore: [...ignore, '**/node_modules'],
24
27
  gitignore,
25
28
  absolute: true,
26
29
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Find unused files, dependencies and exports in your TypeScript and JavaScript project",
5
5
  "keywords": [
6
6
  "find",
@@ -24,8 +24,8 @@
24
24
  "knip": "dist/cli.js"
25
25
  },
26
26
  "scripts": {
27
- "knip": "node ./dist/cli.js --include files,dependencies,unlisted",
28
- "test": "node --loader tsx --test test/*.spec.ts",
27
+ "knip": "node ./dist/cli.js --jsdoc public",
28
+ "test": "globstar -- node --loader tsx --test \"test/*.spec.ts\"",
29
29
  "watch": "tsc --watch",
30
30
  "build": "rm -rf dist && tsc",
31
31
  "prepublishOnly": "npm test && npm run build && npm run knip",
@@ -50,9 +50,11 @@
50
50
  "devDependencies": {
51
51
  "@types/micromatch": "4.0.2",
52
52
  "@types/node": "18.11.2",
53
+ "globstar": "1.0.0",
53
54
  "prettier": "2.7.1",
54
55
  "release-it": "15.5.0",
55
56
  "tsx": "3.10.3",
57
+ "type-fest": "3.1.0",
56
58
  "typescript": "4.8.4"
57
59
  },
58
60
  "release-it": {