Skip to content

Commit 3834713

Browse files
committed
Add some basic debugging and comments for future self (closes #6)
1 parent a6ae54c commit 3834713

File tree

12 files changed

+124
-6
lines changed

12 files changed

+124
-6
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ Options:
9393
--max-issues Maximum number of issues before non-zero exit code (default: 0)
9494
--reporter Select reporter: symbols, compact (default: symbols)
9595
--jsdoc Enable JSDoc parsing, with options: public
96+
--debug Show debug output
97+
--debug-level Set verbosity of debug output (default: 1, max: 2)
9698
9799
Issue groups: files, dependencies, unlisted, exports, nsExports, types, nsTypes, duplicates
98100

src/cli.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const {
2323
reporter = 'symbols',
2424
'max-issues': maxIssues = '0',
2525
jsdoc: jsDoc = [],
26+
debug: isDebug = false,
27+
'debug-level': debugLevel = '1',
2628
},
2729
} = parseArgs({
2830
options: {
@@ -39,6 +41,8 @@ const {
3941
'max-issues': { type: 'string' },
4042
reporter: { type: 'string' },
4143
jsdoc: { type: 'string', multiple: true },
44+
debug: { type: 'boolean' },
45+
'debug-level': { type: 'string' },
4246
},
4347
});
4448

@@ -70,6 +74,10 @@ const run = async () => {
7074
isDev,
7175
isShowProgress,
7276
jsDoc,
77+
debug: {
78+
isEnabled: isDebug,
79+
level: isDebug ? Number(debugLevel) : 0,
80+
},
7381
});
7482

7583
printReport({ report, issues, workingDir, isDev });

src/help.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Options:
1414
--max-issues Maximum number of issues before non-zero exit code (default: 0)
1515
--reporter Select reporter: symbols, compact (default: symbols)
1616
--jsdoc Enable JSDoc parsing, with options: public
17+
--debug Show debug output
18+
--debug-level Set verbosity of debug output (default: 1, max: 2)
1719
1820
Issue groups: files, dependencies, unlisted, exports, nsExports, types, nsTypes, duplicates
1921

src/index.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { resolvePaths } from './util/path';
66
import { createProject } from './util/project';
77
import { findIssues } from './runner';
88
import { ConfigurationError } from './util/errors';
9+
import { debugLogObject, debugLogFiles, debugLogSourceFiles } from './util/debug';
910
import type { UnresolvedConfiguration, Configuration } from './types';
1011

1112
export const main = async (options: UnresolvedConfiguration) => {
@@ -21,8 +22,11 @@ export const main = async (options: UnresolvedConfiguration) => {
2122
isDev,
2223
isShowProgress,
2324
jsDoc,
25+
debug,
2426
} = options;
2527

28+
debugLogObject(options, 1, 'Unresolved onfiguration', options);
29+
2630
const localConfigurationPath = configFilePath && (await findFile(workingDir, configFilePath));
2731
const manifestPath = await findFile(workingDir, 'package.json');
2832
const localConfiguration = localConfigurationPath && require(localConfigurationPath);
@@ -36,6 +40,8 @@ export const main = async (options: UnresolvedConfiguration) => {
3640
const dir = path.relative(cwd, workingDir);
3741
const resolvedConfig = resolveConfig(manifest.knip ?? localConfiguration, { workingDir: dir, isDev });
3842

43+
debugLogObject(options, 1, 'Resolved onfiguration', resolvedConfig);
44+
3945
if (!resolvedConfig) {
4046
throw new ConfigurationError('Unable to find `entryFiles` and/or `projectFiles` in configuration.');
4147
}
@@ -61,29 +67,38 @@ export const main = async (options: UnresolvedConfiguration) => {
6167

6268
const projectOptions = tsConfigPath ? { tsConfigFilePath: tsConfigPath } : { compilerOptions: { allowJs: true } };
6369

64-
// Create workspace for entry files + resolved dependencies
6570
const entryPaths = await resolvePaths({
6671
cwd,
6772
workingDir,
6873
patterns: resolvedConfig.entryFiles,
6974
ignore,
7075
gitignore,
7176
});
77+
debugLogFiles(options, 1, 'Globbed entry paths', entryPaths);
78+
79+
// Create workspace for entry files, but don't resolve dependencies yet
7280
const production = createProject({ projectOptions, paths: entryPaths });
7381
const entryFiles = production.getSourceFiles();
82+
debugLogSourceFiles(options, 1, 'Included entry source files', entryFiles);
83+
84+
// Now resolve dependencies of entry files to find all production files
7485
production.resolveSourceFileDependencies();
7586
const productionFiles = production.getSourceFiles();
87+
debugLogSourceFiles(options, 1, 'Included production source files', productionFiles);
7688

77-
// Create workspace for the entire project
7889
const projectPaths = await resolvePaths({
7990
cwd,
8091
workingDir,
8192
patterns: resolvedConfig.projectFiles,
8293
ignore,
8394
gitignore,
8495
});
96+
debugLogFiles(options, 1, 'Globbed project paths', projectPaths);
97+
98+
// Create workspace for the entire project
8599
const project = createProject({ projectOptions, paths: projectPaths });
86100
const projectFiles = project.getSourceFiles();
101+
debugLogSourceFiles(options, 1, 'Included project source files', projectFiles);
87102

88103
const config: Configuration = {
89104
workingDir,
@@ -99,9 +114,12 @@ export const main = async (options: UnresolvedConfiguration) => {
99114
jsDocOptions: {
100115
isReadPublicTag: jsDoc.includes('public'),
101116
},
117+
debug,
102118
};
103119

104120
const { issues, counters } = await findIssues(config);
105121

122+
debugLogObject(options, 2, 'Issues', issues);
123+
106124
return { report, issues, counters };
107125
};

src/runner.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { partitionSourceFiles } from './util/project';
55
import { getType } from './util/type';
66
import { getDependencyAnalyzer } from './util/dependencies';
77
import { getLine, LineRewriter } from './log';
8+
import { debugLogSourceFiles } from './util/debug';
89
import type { Identifier } from 'ts-morph';
910
import type { Configuration, Issues, Issue, ProjectIssueType, SymbolIssueType } from './types';
1011

@@ -21,6 +22,11 @@ export async function findIssues(configuration: Configuration) {
2122
const [usedProductionFiles, unreferencedProductionFiles] = partitionSourceFiles(projectFiles, productionFiles);
2223
const [usedEntryFiles, usedNonEntryFiles] = partitionSourceFiles(usedProductionFiles, entryFiles);
2324

25+
debugLogSourceFiles(configuration, 1, 'usedProductionFiles', usedProductionFiles);
26+
debugLogSourceFiles(configuration, 1, 'unreferencedProductionFiles', unreferencedProductionFiles);
27+
debugLogSourceFiles(configuration, 1, 'usedEntryFiles', usedEntryFiles);
28+
debugLogSourceFiles(configuration, 1, 'usedNonEntryFiles', usedNonEntryFiles);
29+
2430
// Set up the results
2531
const issues: Issues = {
2632
files: new Set(unreferencedProductionFiles.map(file => file.getFilePath())),
@@ -86,14 +92,16 @@ export async function findIssues(configuration: Configuration) {
8692
};
8793

8894
if (report.dependencies || report.unlisted) {
95+
// Performance optimization: a separate traversal over only the entry files to find unused/unlisted dependencies,
96+
// the rest will be done during the non-entry files traversal.
8997
usedEntryFiles.forEach(sourceFile => {
9098
counters.processed++;
9199
const unresolvedDependencies = getUnresolvedDependencies(sourceFile);
92100
unresolvedDependencies.forEach(issue => addSymbolIssue('unresolved', issue));
93101
});
94102
}
95103

96-
// Skip when only interested in unreferenced files
104+
// Skip expensive traversal when only reporting unreferenced files
97105
if (
98106
report.dependencies ||
99107
report.unlisted ||
@@ -103,6 +111,7 @@ export async function findIssues(configuration: Configuration) {
103111
report.nsTypes ||
104112
report.duplicates
105113
) {
114+
// We only traverse the non-entry production files, since entry files and any exports are marked as used.
106115
usedNonEntryFiles.forEach(sourceFile => {
107116
counters.processed++;
108117
const filePath = sourceFile.getFilePath();
@@ -177,7 +186,8 @@ export async function findIssues(configuration: Configuration) {
177186

178187
if (!isReferencedOnlyBySelf) return; // This identifier is used somewhere else
179188

180-
// No more reasons left to think this identifier is used somewhere else, report it as unreferenced
189+
// No more reasons left to think this identifier is used somewhere else, report it as unreferenced. If
190+
// it's on a namespace somewhere, report it in a separate issue type.
181191
if (findReferencingNamespaceNodes(sourceFile).length > 0) {
182192
if (type) {
183193
addSymbolIssue('nsTypes', { filePath, symbol: identifierText, symbolType: type });

src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export type UnresolvedConfiguration = {
6161
isDev: boolean;
6262
isShowProgress: boolean;
6363
jsDoc: string[];
64+
debug: {
65+
isEnabled: boolean;
66+
level: number;
67+
};
6468
};
6569

6670
export type Report = {
@@ -81,4 +85,8 @@ export type Configuration = {
8185
jsDocOptions: {
8286
isReadPublicTag: boolean;
8387
};
88+
debug: {
89+
isEnabled: boolean;
90+
level: number;
91+
};
8492
};

src/util/debug.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import util from 'node:util';
2+
import type { SourceFile } from 'ts-morph';
3+
4+
type Config = { debug: { isEnabled: boolean; level: number } };
5+
6+
// Inspect arrays, otherwise Node [will, knip, ...n-100 more items]
7+
const logArray = (collection: string[]) => console.log(util.inspect(collection, { maxArrayLength: null }));
8+
9+
export const debugLogObject = (config: Config, minimumLevel: number, name: string, obj: unknown) => {
10+
if (minimumLevel > config.debug.level) return;
11+
console.log(`[exportman] ${name}:`);
12+
console.log(util.inspect(obj, { depth: null, colors: true }));
13+
};
14+
15+
export const debugLogFiles = (config: Config, minimumLevel: number, name: string, filePaths: string[]) => {
16+
if (minimumLevel > config.debug.level) return;
17+
const { debug } = config;
18+
if (debug.level > 1) {
19+
console.debug(`[exportman] ${name} (${filePaths.length}):`);
20+
logArray(filePaths);
21+
} else {
22+
console.debug(`[exportman] ${name} (${filePaths.length})`);
23+
}
24+
};
25+
26+
export const debugLogSourceFiles = (config: Config, minimumLevel: number, name: string, sourceFiles: SourceFile[]) => {
27+
if (minimumLevel > config.debug.level) return;
28+
const { debug } = config;
29+
if (debug.level > 1) {
30+
// let files = Array.from(sourceFiles);
31+
console.debug(`[exportman] ${name} (${sourceFiles.length}):`);
32+
logArray(sourceFiles.map(sourceFile => sourceFile.getFilePath()));
33+
} else {
34+
console.debug(`[exportman] ${name} (${sourceFiles.length})`);
35+
}
36+
};
37+
38+
export const debugLogDiff = (config: Config, minimumLevel: number, name: string, arrA: string[], arrB: string[]) => {
39+
if (minimumLevel > config.debug.level) return;
40+
const onlyInA = arrA.filter(itemA => !arrB.includes(itemA)).sort();
41+
const onlyInB = arrB.filter(itemB => !arrA.includes(itemB)).sort();
42+
console.log(`[exportman] ${name}`);
43+
console.log(`[exportman] Only in left:`);
44+
logArray(onlyInA);
45+
console.log();
46+
console.log(`[exportman] Only in right:`);
47+
logArray(onlyInB);
48+
};

src/util/path.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export const resolvePaths = async ({
2929
gitignore: boolean;
3030
}) =>
3131
glob(
32+
// Prepend relative --dir to patterns to use cwd (not workingDir), because
33+
// we want to glob everything to include all (git)ignore patterns
3234
patterns.map(pattern => prependDirToPattern(path.relative(cwd, workingDir), pattern)),
3335
{
3436
cwd,

test/basic.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ test('Find unused files and exports', async () => {
1111
include: [],
1212
exclude: [],
1313
ignore: [],
14-
isNoGitIgnore: true,
14+
gitignore: false,
1515
isDev: false,
1616
isShowProgress: false,
1717
jsDoc: [],
18+
debug: {
19+
isEnabled: false,
20+
level: 0,
21+
},
1822
});
1923

2024
assert(issues.files.size === 1);

test/dependencies.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ test('Find unused dependencies', async () => {
1111
include: [],
1212
exclude: [],
1313
ignore: [],
14-
isNoGitIgnore: true,
14+
gitignore: false,
1515
isDev: false,
1616
isShowProgress: false,
1717
jsDoc: [],
18+
debug: {
19+
isEnabled: false,
20+
level: 0,
21+
},
1822
});
1923

2024
assert(Array.from(issues.files)[0].endsWith('unused.ts'));

0 commit comments

Comments
 (0)