depvitals 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -10
- package/dist/cli/index.js +13 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/parseCliArgs.d.ts +37 -0
- package/dist/cli/parseCliArgs.d.ts.map +1 -0
- package/dist/cli/parseCliArgs.js +85 -0
- package/dist/cli/parseCliArgs.js.map +1 -0
- package/dist/cli/runCli.d.ts +31 -13
- package/dist/cli/runCli.d.ts.map +1 -1
- package/dist/cli/runCli.js +76 -84
- package/dist/cli/runCli.js.map +1 -1
- package/dist/core/analyzeProject.d.ts +92 -0
- package/dist/core/analyzeProject.d.ts.map +1 -0
- package/dist/core/analyzeProject.js +175 -0
- package/dist/core/analyzeProject.js.map +1 -0
- package/dist/reporters/terminalReporter.d.ts +49 -0
- package/dist/reporters/terminalReporter.d.ts.map +1 -0
- package/dist/reporters/terminalReporter.js +98 -0
- package/dist/reporters/terminalReporter.js.map +1 -0
- package/dist/scanner/moduleSpecifiers.d.ts +60 -0
- package/dist/scanner/moduleSpecifiers.d.ts.map +1 -0
- package/dist/scanner/moduleSpecifiers.js +824 -0
- package/dist/scanner/moduleSpecifiers.js.map +1 -0
- package/dist/scanner/packageSpecifier.d.ts +45 -0
- package/dist/scanner/packageSpecifier.d.ts.map +1 -0
- package/dist/scanner/packageSpecifier.js +98 -0
- package/dist/scanner/packageSpecifier.js.map +1 -0
- package/dist/scanner/projectManifest.d.ts +72 -0
- package/dist/scanner/projectManifest.d.ts.map +1 -0
- package/dist/scanner/projectManifest.js +176 -0
- package/dist/scanner/projectManifest.js.map +1 -0
- package/dist/scanner/scriptUsage.d.ts +54 -0
- package/dist/scanner/scriptUsage.d.ts.map +1 -0
- package/dist/scanner/scriptUsage.js +490 -0
- package/dist/scanner/scriptUsage.js.map +1 -0
- package/dist/scanner/sourceFiles.d.ts +52 -0
- package/dist/scanner/sourceFiles.d.ts.map +1 -0
- package/dist/scanner/sourceFiles.js +190 -0
- package/dist/scanner/sourceFiles.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzeProject.d.ts","sourceRoot":"","sources":["../../src/core/analyzeProject.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAUH;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,uDAAuD;IACvD,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,+FAA+F;IAC/F,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB,CAAC;AAEvE;;;;;;;GAOG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;gBAErB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,CAAC,EAAE,OAAO;CAKtE;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA6CjF"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency analysis.
|
|
3
|
+
*
|
|
4
|
+
* This module combines the outputs of the manifest loader, source discovery, module-specifier
|
|
5
|
+
* extraction, package-specifier classification and script-usage detection into the first real
|
|
6
|
+
* dependency-analysis engine.
|
|
7
|
+
*
|
|
8
|
+
* It answers exactly three questions about a single Node.js project:
|
|
9
|
+
* - which `dependencies` are never referenced (neither by source files nor by `package.json`
|
|
10
|
+
* scripts)?
|
|
11
|
+
* - which `devDependencies` are never referenced?
|
|
12
|
+
* - which external packages are referenced by source files but are not declared anywhere?
|
|
13
|
+
*
|
|
14
|
+
* It deliberately does not:
|
|
15
|
+
* - decide whether a dependency is misplaced, a misconfigured peer/optional, or a scoped
|
|
16
|
+
* dependency (that belongs to later releases);
|
|
17
|
+
* - resolve specifiers against `node_modules`, `tsconfig` `paths`, `package.json#imports` or
|
|
18
|
+
* bundler aliases (Step 10 only classifies a raw specifier);
|
|
19
|
+
* - render terminal output, JSON, evidence or confidence (that belongs to later steps);
|
|
20
|
+
* - execute or import any analyzed source, run package scripts, or mutate the analyzed project;
|
|
21
|
+
* - consult the network or the npm registry.
|
|
22
|
+
*
|
|
23
|
+
* The pipeline is intentionally sequential and single-pass: each discovered source file is read
|
|
24
|
+
* once and parsed once. No concurrency pool, cache or index is introduced before a measured need
|
|
25
|
+
* appears.
|
|
26
|
+
*
|
|
27
|
+
* Usage/missing formulas (`usedNames` is the union of source-external package roots and script-used
|
|
28
|
+
* package names; `declaredDependencyNames` is the union of every declared dependency section):
|
|
29
|
+
*
|
|
30
|
+
* ```text
|
|
31
|
+
* sourceUsedNames = external package roots extracted from source files
|
|
32
|
+
* scriptUsedNames = findPackageScriptUsage(scripts, dependencies ∪ devDependencies)
|
|
33
|
+
* usedNames = sourceUsedNames ∪ scriptUsedNames
|
|
34
|
+
* unusedDependencies = dependencies − usedNames
|
|
35
|
+
* unusedDevDependencies = devDependencies − usedNames
|
|
36
|
+
* missingDependencies = sourceUsedNames − declaredDependencyNames
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* Scripts never generate missing-dependency findings, and peer/optional declarations suppress false
|
|
40
|
+
* missing results through `declaredDependencyNames`.
|
|
41
|
+
*/
|
|
42
|
+
import { readFile } from 'node:fs/promises';
|
|
43
|
+
import { loadProjectManifest } from '../scanner/projectManifest.js';
|
|
44
|
+
import { discoverSourceFiles } from '../scanner/sourceFiles.js';
|
|
45
|
+
import { extractModuleSpecifiers } from '../scanner/moduleSpecifiers.js';
|
|
46
|
+
import { classifyPackageSpecifier } from '../scanner/packageSpecifier.js';
|
|
47
|
+
import { findPackageScriptUsage } from '../scanner/scriptUsage.js';
|
|
48
|
+
/**
|
|
49
|
+
* Operational error for the dependency-analysis engine.
|
|
50
|
+
*
|
|
51
|
+
* The `code` lets callers (the CLI in a later step) render a clean message without matching on error
|
|
52
|
+
* strings, and the preserved `cause` keeps the original filesystem or parsing failure. The affected
|
|
53
|
+
* source-file path is always included in the message so an unreadable or invalid file is never
|
|
54
|
+
* silently skipped.
|
|
55
|
+
*/
|
|
56
|
+
export class AnalysisError extends Error {
|
|
57
|
+
code;
|
|
58
|
+
constructor(message, code, cause) {
|
|
59
|
+
super(message, cause === undefined ? undefined : { cause });
|
|
60
|
+
this.name = 'AnalysisError';
|
|
61
|
+
this.code = code;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Analyze the single project rooted at `projectRoot` and return the structured unused/missing result.
|
|
66
|
+
*
|
|
67
|
+
* The supplied root is resolved to an absolute path; `process.cwd()` is never changed and the target
|
|
68
|
+
* project is never written to.
|
|
69
|
+
*
|
|
70
|
+
* @throws {AnalysisError} when a discovered source file cannot be read or cannot be safely analyzed.
|
|
71
|
+
* @throws {ProjectManifestError} when the manifest is missing, unreadable, invalid or malformed.
|
|
72
|
+
* @throws {SourceFilesError} when the root is missing, is not a directory, or cannot be read.
|
|
73
|
+
*/
|
|
74
|
+
export async function analyzeProject(projectRoot) {
|
|
75
|
+
const manifest = await loadProjectManifest(projectRoot);
|
|
76
|
+
const sourceFiles = await discoverSourceFiles(manifest.root);
|
|
77
|
+
// External package roots referenced by source files, collapsed to their package root.
|
|
78
|
+
const sourceUsedNames = new Set();
|
|
79
|
+
for (const file of sourceFiles) {
|
|
80
|
+
const specifiers = await readAndExtract(file);
|
|
81
|
+
for (const specifier of specifiers) {
|
|
82
|
+
const classified = classifyPackageSpecifier(specifier);
|
|
83
|
+
if (classified.kind === 'external') {
|
|
84
|
+
sourceUsedNames.add(classified.packageName);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Script usage is only meaningful for the packages that may be reported as unused: dependencies
|
|
89
|
+
// and devDependencies. Peer/optional declarations are excluded from this input by design.
|
|
90
|
+
const declaredCandidates = unionNames([
|
|
91
|
+
Object.keys(manifest.dependencies),
|
|
92
|
+
Object.keys(manifest.devDependencies),
|
|
93
|
+
]);
|
|
94
|
+
const scriptUsedNames = findPackageScriptUsage(manifest.scripts, declaredCandidates);
|
|
95
|
+
const usedNames = unionNames([[...sourceUsedNames], [...scriptUsedNames]]);
|
|
96
|
+
const unusedDependencies = declaredMinus(Object.keys(manifest.dependencies), usedNames);
|
|
97
|
+
const unusedDevDependencies = declaredMinus(Object.keys(manifest.devDependencies), usedNames);
|
|
98
|
+
// An external root is missing only when it is absent from every declared dependency section.
|
|
99
|
+
// Peer/optional declarations therefore suppress false missing results.
|
|
100
|
+
const missingDependencies = declaredMinus([...sourceUsedNames], manifest.declaredDependencyNames);
|
|
101
|
+
return {
|
|
102
|
+
projectRoot: manifest.root,
|
|
103
|
+
unusedDependencies,
|
|
104
|
+
unusedDevDependencies,
|
|
105
|
+
missingDependencies,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Read a discovered source file and extract its statically known specifiers.
|
|
110
|
+
*
|
|
111
|
+
* A filesystem read failure becomes {@link AnalysisError} with code `unreadable-source`; a lexical
|
|
112
|
+
* failure from the specifier extractor becomes {@link AnalysisError} with code `invalid-source`. Both
|
|
113
|
+
* preserve the original error as `cause` and include the affected path, so a broken file never
|
|
114
|
+
* yields a partial result or a misleading "clean" analysis.
|
|
115
|
+
*/
|
|
116
|
+
async function readAndExtract(file) {
|
|
117
|
+
let source;
|
|
118
|
+
try {
|
|
119
|
+
source = await readFile(file, 'utf8');
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
throw new AnalysisError(`Could not read source file ${file}: ${messageOf(error)}`, 'unreadable-source', error);
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const occurrences = extractModuleSpecifiers(source);
|
|
126
|
+
return occurrences.map((occurrence) => occurrence.specifier);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (error instanceof Error && error.name === 'ModuleSpecifierError') {
|
|
130
|
+
throw new AnalysisError(`Source file ${file} could not be analyzed: ${messageOf(error)}`, 'invalid-source', error);
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Union of the names of every supplied list, sorted alphabetically.
|
|
137
|
+
*/
|
|
138
|
+
function unionNames(lists) {
|
|
139
|
+
const names = new Set();
|
|
140
|
+
for (const list of lists) {
|
|
141
|
+
for (const name of list) {
|
|
142
|
+
names.add(name);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return new Set([...names].sort(compareNames));
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The sorted names of `declared` that are not contained in `used`.
|
|
149
|
+
*/
|
|
150
|
+
function declaredMinus(declared, used) {
|
|
151
|
+
const unused = declared.filter((name) => !used.has(name));
|
|
152
|
+
return [...new Set(unused)].sort(compareNames);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* A short, human-readable reason extracted from an unknown thrown value, for error messages only.
|
|
156
|
+
*/
|
|
157
|
+
function messageOf(error) {
|
|
158
|
+
if (error instanceof Error) {
|
|
159
|
+
return error.message;
|
|
160
|
+
}
|
|
161
|
+
return String(error);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Locale-independent lexicographic ordering, so the same project always yields the same order.
|
|
165
|
+
*/
|
|
166
|
+
function compareNames(a, b) {
|
|
167
|
+
if (a < b) {
|
|
168
|
+
return -1;
|
|
169
|
+
}
|
|
170
|
+
if (a > b) {
|
|
171
|
+
return 1;
|
|
172
|
+
}
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=analyzeProject.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzeProject.js","sourceRoot":"","sources":["../../src/core/analyzeProject.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic terminal reporter.
|
|
3
|
+
*
|
|
4
|
+
* Renders an {@link AnalysisResult} as plain terminal text and nothing else: the module performs no
|
|
5
|
+
* process I/O, never imports `node:process`, never writes to a stream, never assigns an exit code,
|
|
6
|
+
* and never reads the filesystem. It is a pure `AnalysisResult -> string` transformation, so the
|
|
7
|
+
* exact output is fully testable and can be reused by any future presentation layer.
|
|
8
|
+
*
|
|
9
|
+
* The `v0.1.0` output is intentionally plain. It contains exactly:
|
|
10
|
+
* - the `DepVitals` heading;
|
|
11
|
+
* - one section per non-empty findings category, in a fixed order;
|
|
12
|
+
* - the clean-project message when there are no findings at all.
|
|
13
|
+
*
|
|
14
|
+
* It deliberately does not contain colors, symbols, spinners, tables, terminal-width detection,
|
|
15
|
+
* links, prompts, source locations, advice, remediation commands, dependency counts, project paths
|
|
16
|
+
* or any summary footer. Those belong to later releases, if they are added at all.
|
|
17
|
+
*
|
|
18
|
+
* Output contract:
|
|
19
|
+
* - sections appear in the order `Unused dependencies`, `Unused devDependencies`,
|
|
20
|
+
* `Missing dependencies`;
|
|
21
|
+
* - empty sections are omitted entirely (a `Category (0)` heading is never printed);
|
|
22
|
+
* - package names are sorted alphabetically inside each section and counted exactly as rendered;
|
|
23
|
+
* - every package name is indented with exactly two spaces;
|
|
24
|
+
* - exactly one blank line follows the heading and separates consecutive sections;
|
|
25
|
+
* - no blank line follows the last section;
|
|
26
|
+
* - the returned string ends with exactly one `\n`, always LF regardless of platform.
|
|
27
|
+
*
|
|
28
|
+
* The reporter guarantees its own ordering by sorting a copy of every findings array with a
|
|
29
|
+
* private codepoint comparator: it never relies on the caller having sorted the input, never mutates
|
|
30
|
+
* the received arrays, and never uses locale-sensitive comparison. Deduplication stays the analyzer's
|
|
31
|
+
* responsibility, so a duplicated name inside a category would be rendered twice by design.
|
|
32
|
+
*/
|
|
33
|
+
import type { AnalysisResult } from '../core/analyzeProject.js';
|
|
34
|
+
/**
|
|
35
|
+
* Whether the analysis produced at least one unused or missing dependency finding.
|
|
36
|
+
*
|
|
37
|
+
* Pure and side-effect free. It reports nothing about process exit codes: deciding when a CLI run
|
|
38
|
+
* should fail belongs to the caller that owns the process.
|
|
39
|
+
*/
|
|
40
|
+
export declare function hasDependencyIssues(result: AnalysisResult): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Render the complete terminal report for `result`, including its single trailing newline.
|
|
43
|
+
*
|
|
44
|
+
* @param result - The structured analysis produced by the dependency analyzer.
|
|
45
|
+
* @returns The full terminal text: the clean message, or the heading followed by the non-empty
|
|
46
|
+
* findings sections in deterministic order.
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatTerminalReport(result: AnalysisResult): string;
|
|
49
|
+
//# sourceMappingURL=terminalReporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminalReporter.d.ts","sourceRoot":"","sources":["../../src/reporters/terminalReporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAchE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAMnE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAQnE"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic terminal reporter.
|
|
3
|
+
*
|
|
4
|
+
* Renders an {@link AnalysisResult} as plain terminal text and nothing else: the module performs no
|
|
5
|
+
* process I/O, never imports `node:process`, never writes to a stream, never assigns an exit code,
|
|
6
|
+
* and never reads the filesystem. It is a pure `AnalysisResult -> string` transformation, so the
|
|
7
|
+
* exact output is fully testable and can be reused by any future presentation layer.
|
|
8
|
+
*
|
|
9
|
+
* The `v0.1.0` output is intentionally plain. It contains exactly:
|
|
10
|
+
* - the `DepVitals` heading;
|
|
11
|
+
* - one section per non-empty findings category, in a fixed order;
|
|
12
|
+
* - the clean-project message when there are no findings at all.
|
|
13
|
+
*
|
|
14
|
+
* It deliberately does not contain colors, symbols, spinners, tables, terminal-width detection,
|
|
15
|
+
* links, prompts, source locations, advice, remediation commands, dependency counts, project paths
|
|
16
|
+
* or any summary footer. Those belong to later releases, if they are added at all.
|
|
17
|
+
*
|
|
18
|
+
* Output contract:
|
|
19
|
+
* - sections appear in the order `Unused dependencies`, `Unused devDependencies`,
|
|
20
|
+
* `Missing dependencies`;
|
|
21
|
+
* - empty sections are omitted entirely (a `Category (0)` heading is never printed);
|
|
22
|
+
* - package names are sorted alphabetically inside each section and counted exactly as rendered;
|
|
23
|
+
* - every package name is indented with exactly two spaces;
|
|
24
|
+
* - exactly one blank line follows the heading and separates consecutive sections;
|
|
25
|
+
* - no blank line follows the last section;
|
|
26
|
+
* - the returned string ends with exactly one `\n`, always LF regardless of platform.
|
|
27
|
+
*
|
|
28
|
+
* The reporter guarantees its own ordering by sorting a copy of every findings array with a
|
|
29
|
+
* private codepoint comparator: it never relies on the caller having sorted the input, never mutates
|
|
30
|
+
* the received arrays, and never uses locale-sensitive comparison. Deduplication stays the analyzer's
|
|
31
|
+
* responsibility, so a duplicated name inside a category would be rendered twice by design.
|
|
32
|
+
*/
|
|
33
|
+
/** Heading printed as the first line of every report. */
|
|
34
|
+
const HEADER = 'DepVitals';
|
|
35
|
+
/** Exact report returned when the analysis contains no findings at all. */
|
|
36
|
+
const CLEAN_REPORT = `${HEADER}\n\nNo dependency issues found.\n`;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the analysis produced at least one unused or missing dependency finding.
|
|
39
|
+
*
|
|
40
|
+
* Pure and side-effect free. It reports nothing about process exit codes: deciding when a CLI run
|
|
41
|
+
* should fail belongs to the caller that owns the process.
|
|
42
|
+
*/
|
|
43
|
+
export function hasDependencyIssues(result) {
|
|
44
|
+
return (result.unusedDependencies.length > 0 ||
|
|
45
|
+
result.unusedDevDependencies.length > 0 ||
|
|
46
|
+
result.missingDependencies.length > 0);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Render the complete terminal report for `result`, including its single trailing newline.
|
|
50
|
+
*
|
|
51
|
+
* @param result - The structured analysis produced by the dependency analyzer.
|
|
52
|
+
* @returns The full terminal text: the clean message, or the heading followed by the non-empty
|
|
53
|
+
* findings sections in deterministic order.
|
|
54
|
+
*/
|
|
55
|
+
export function formatTerminalReport(result) {
|
|
56
|
+
if (!hasDependencyIssues(result)) {
|
|
57
|
+
return CLEAN_REPORT;
|
|
58
|
+
}
|
|
59
|
+
const sections = findingsSections(result).filter((section) => section.names.length > 0);
|
|
60
|
+
return `${HEADER}\n\n${sections.map(renderSection).join('\n\n')}\n`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The findings categories in their fixed rendering order. Keeping the order here means the section
|
|
64
|
+
* sequence can never depend on the shape or key order of the incoming result.
|
|
65
|
+
*/
|
|
66
|
+
function findingsSections(result) {
|
|
67
|
+
return [
|
|
68
|
+
{ heading: 'Unused dependencies', names: result.unusedDependencies },
|
|
69
|
+
{ heading: 'Unused devDependencies', names: result.unusedDevDependencies },
|
|
70
|
+
{ heading: 'Missing dependencies', names: result.missingDependencies },
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Render one non-empty section: a heading carrying the number of rendered packages, followed by one
|
|
75
|
+
* two-space-indented line per package name.
|
|
76
|
+
*
|
|
77
|
+
* The count is taken from the sorted copy, so it always matches the lines printed below it.
|
|
78
|
+
*/
|
|
79
|
+
function renderSection(section) {
|
|
80
|
+
const names = [...section.names].sort(compareNames);
|
|
81
|
+
const count = String(names.length);
|
|
82
|
+
return [`${section.heading} (${count})`, ...names.map((name) => ` ${name}`)].join('\n');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Locale-independent lexicographic (codepoint) ordering, matching the comparator convention already
|
|
86
|
+
* used by the analyzer. `localeCompare` is deliberately avoided: its result depends on the runtime
|
|
87
|
+
* locale, which would break deterministic terminal output across machines.
|
|
88
|
+
*/
|
|
89
|
+
function compareNames(a, b) {
|
|
90
|
+
if (a < b) {
|
|
91
|
+
return -1;
|
|
92
|
+
}
|
|
93
|
+
if (a > b) {
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=terminalReporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminalReporter.js","sourceRoot":"","sources":["../../src/reporters/terminalReporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH,yDAAyD;AACzD,MAAM,MAAM,GAAG,WAAW,CAAC;AAE3B,2EAA2E;AAC3E,MAAM,YAAY,GAAG,GAAG,MAAM,mCAAmC,CAAC;AAQlE;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAsB;IACxD,OAAO,CACL,MAAM,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC;QACpC,MAAM,CAAC,qBAAqB,CAAC,MAAM,GAAG,CAAC;QACvC,MAAM,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,CACtC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAsB;IACzD,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC;QACjC,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAExF,OAAO,GAAG,MAAM,OAAO,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;AACtE,CAAC;AAED;;;GAGG;AACH,SAAS,gBAAgB,CAAC,MAAsB;IAC9C,OAAO;QACL,EAAE,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE,MAAM,CAAC,kBAAkB,EAAE;QACpE,EAAE,OAAO,EAAE,wBAAwB,EAAE,KAAK,EAAE,MAAM,CAAC,qBAAqB,EAAE;QAC1E,EAAE,OAAO,EAAE,sBAAsB,EAAE,KAAK,EAAE,MAAM,CAAC,mBAAmB,EAAE;KACvE,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,OAAgB;IACrC,MAAM,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAEpD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAEnC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,KAAK,KAAK,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3F,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACxC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACV,OAAO,CAAC,CAAC,CAAC;IACZ,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACV,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static module specifier extraction.
|
|
3
|
+
*
|
|
4
|
+
* This module reads JavaScript/TypeScript source text and returns the statically knowable module
|
|
5
|
+
* specifiers it contains, without executing the code and without a heavyweight parser dependency.
|
|
6
|
+
*
|
|
7
|
+
* It deliberately does not:
|
|
8
|
+
* - decide whether a specifier is an external package, a Node builtin, relative, missing or used
|
|
9
|
+
* (that belongs to later steps);
|
|
10
|
+
* - normalize specifiers, sort or deduplicate them;
|
|
11
|
+
* - resolve anything on disk or through path aliases / bundler aliases.
|
|
12
|
+
*
|
|
13
|
+
* The occurrences are returned in source encounter order and are not deduplicated: deduplication
|
|
14
|
+
* belongs to the analysis step, not to extraction.
|
|
15
|
+
*
|
|
16
|
+
* Instead of a few global regexes over raw source, this module runs a small lexical scanner with
|
|
17
|
+
* just enough JavaScript awareness to identify the required forms while ignoring comments, strings,
|
|
18
|
+
* template literal text and regex literals so they never produce false findings. JavaScript inside
|
|
19
|
+
* template interpolations (`${...}`) is still lexed, so specifiers there are found.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The kind of module construct a specifier came from.
|
|
23
|
+
*/
|
|
24
|
+
export type ModuleSpecifierKind = 'import' | 'export-from' | 'dynamic-import' | 'require';
|
|
25
|
+
/**
|
|
26
|
+
* A single statically extracted specifier together with the construct that produced it.
|
|
27
|
+
*/
|
|
28
|
+
export type ModuleSpecifierOccurrence = {
|
|
29
|
+
readonly specifier: string;
|
|
30
|
+
readonly kind: ModuleSpecifierKind;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Error code for the module specifier scanner.
|
|
34
|
+
*
|
|
35
|
+
* - `invalid-source`: the source reached a lexical state from which the scanner cannot safely
|
|
36
|
+
* determine where the current lexical construct ends (an unterminated string/comment/template/
|
|
37
|
+
* regex, or a malformed string escape).
|
|
38
|
+
*/
|
|
39
|
+
export type ModuleSpecifierErrorCode = 'invalid-source';
|
|
40
|
+
/**
|
|
41
|
+
* Operational error for module specifier extraction.
|
|
42
|
+
*
|
|
43
|
+
* It is thrown only when the source cannot be scanned safely, so callers (later steps) can treat a
|
|
44
|
+
* file as un-analysable rather than trusting a partial result.
|
|
45
|
+
*/
|
|
46
|
+
export declare class ModuleSpecifierError extends Error {
|
|
47
|
+
readonly code: ModuleSpecifierErrorCode;
|
|
48
|
+
readonly offset: number;
|
|
49
|
+
constructor(message: string, code: ModuleSpecifierErrorCode, offset: number);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extract the statically knowable module specifiers from `source`.
|
|
53
|
+
*
|
|
54
|
+
* The returned occurrences preserve source order and are not deduplicated.
|
|
55
|
+
*
|
|
56
|
+
* @throws {ModuleSpecifierError} when the source reaches an unterminated or malformed lexical state
|
|
57
|
+
* from which the scanner cannot safely continue.
|
|
58
|
+
*/
|
|
59
|
+
export declare function extractModuleSpecifiers(source: string): readonly ModuleSpecifierOccurrence[];
|
|
60
|
+
//# sourceMappingURL=moduleSpecifiers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moduleSpecifiers.d.ts","sourceRoot":"","sources":["../../src/scanner/moduleSpecifiers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,aAAa,GAAG,gBAAgB,GAAG,SAAS,CAAC;AAE1F;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;CACpC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,wBAAwB,GAAG,gBAAgB,CAAC;AAExD;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,EAAE,MAAM,EAAE,MAAM;CAM5E;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,yBAAyB,EAAE,CAE5F"}
|