@cplieger/deadset-ts 6.1.1-dev.1 → 7.0.0-dev.2
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/dist/src/analysis.js +45 -32
- package/dist/src/component-layer.js +1 -1
- package/dist/src/configuration-files.js +227 -27
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +68 -10
- package/dist/src/convention-rows.js +48 -0
- package/dist/src/conventions.js +80 -0
- package/dist/src/dependencies.js +121 -46
- package/dist/src/discover.js +1 -1
- package/dist/src/edges.js +1 -1
- package/dist/src/exempt-classes.js +9 -22
- package/dist/src/exempt.js +3 -15
- package/dist/src/findings/completion.js +41 -11
- package/dist/src/findings/dependencies-and-module-machinery.js +6 -26
- package/dist/src/findings/emitters.js +0 -4
- package/dist/src/findings/self-check.js +14 -10
- package/dist/src/html-entries.js +29 -14
- package/dist/src/indexed-access.js +377 -81
- package/dist/src/intra-function-parts.js +57 -11
- package/dist/src/inventory.js +3 -12
- package/dist/src/kinds.js +6 -6
- package/dist/src/references.js +62 -23
- package/dist/src/report.js +3 -0
- package/dist/src/roots.js +96 -7
- package/dist/src/run.js +0 -2
- package/dist/src/schema.js +1 -1
- package/dist/src/scope.js +2 -2
- package/dist/src/session.js +6 -2
- package/dist/src/source-files.js +1 -0
- package/dist/src/suppress-inline.js +1 -1
- package/dist/src/suppress.js +15 -16
- package/dist/src/sweep.js +29 -17
- package/dist/src/symbol-keys.js +114 -0
- package/dist/src/test-joins.js +101 -0
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +21 -15
- package/dist/src/workspace-programs.js +1 -4
- package/dist/src/workspace-resolution.js +0 -2
- package/dist/src/workspace.js +0 -3
- package/jsr.json +1 -1
- package/package.json +2 -1
- package/src/analysis.ts +55 -47
- package/src/calls.ts +3 -3
- package/src/component-files.ts +7 -7
- package/src/component-layer.ts +2 -2
- package/src/components.ts +1 -1
- package/src/config.ts +4 -4
- package/src/configuration-files.ts +246 -28
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +99 -13
- package/src/convention-rows.ts +76 -0
- package/src/conventions.ts +102 -0
- package/src/dependencies.ts +148 -56
- package/src/discover.ts +3 -3
- package/src/edges.ts +3 -3
- package/src/entry-points.ts +1 -1
- package/src/exempt-classes.ts +10 -24
- package/src/exempt.ts +5 -29
- package/src/file-facts.ts +1 -1
- package/src/finding.ts +5 -1
- package/src/findings/completion.ts +53 -11
- package/src/findings/dependencies-and-module-machinery.ts +9 -28
- package/src/findings/emitters.ts +3 -8
- package/src/findings/self-check.ts +17 -14
- package/src/findings-pass.ts +2 -2
- package/src/graph.ts +2 -2
- package/src/html-entries.ts +34 -16
- package/src/indexed-access.ts +476 -102
- package/src/intra-function-parts.ts +84 -12
- package/src/inventory.ts +5 -33
- package/src/json-document.ts +1 -1
- package/src/kinds.ts +7 -6
- package/src/manifest.ts +1 -1
- package/src/namespace-objects.ts +1 -1
- package/src/pnpm-workspace.ts +1 -1
- package/src/references.ts +93 -33
- package/src/report.ts +8 -4
- package/src/roots.ts +129 -9
- package/src/run.ts +0 -2
- package/src/schema.ts +2 -2
- package/src/scope.ts +2 -2
- package/src/session.ts +7 -3
- package/src/source-files.ts +3 -0
- package/src/stores.ts +1 -1
- package/src/suppress-inline.ts +3 -3
- package/src/suppress.ts +21 -24
- package/src/sweep.ts +29 -17
- package/src/symbol-keys.ts +172 -0
- package/src/template-field.ts +1 -1
- package/src/test-joins.ts +120 -0
- package/src/value-reach.ts +1 -1
- package/src/verbs/verb.ts +1 -4
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +23 -16
- package/src/workspace-programs.ts +3 -10
- package/src/workspace-resolution.ts +0 -3
- package/src/workspace-run.ts +1 -1
- package/src/workspace.ts +1 -9
package/dist/src/analysis.js
CHANGED
|
@@ -23,7 +23,7 @@ import { evidenceRecords, heldRecords, disabledClasses, exemptionsOf, holdingWhi
|
|
|
23
23
|
import { heldByInclusion, sourcePaths, sourceTree } from "./file-facts.js";
|
|
24
24
|
import { globExpression } from "./glob.js";
|
|
25
25
|
import { findingsPass, recordedFindings, rowKey } from "./findings-pass.js";
|
|
26
|
-
import {
|
|
26
|
+
import { unmatchedRootFindings } from "./findings/self-check.js";
|
|
27
27
|
import { frameworkLifecycle } from "./framework-lifecycle.js";
|
|
28
28
|
import { implementations, mergeImplementations } from "./implementations.js";
|
|
29
29
|
import { injectionContainer } from "./injection-container.js";
|
|
@@ -32,7 +32,7 @@ import { intraFunctionFacts, projectParts } from "./intra-function-parts.js";
|
|
|
32
32
|
import { inventory } from "./inventory.js";
|
|
33
33
|
import { packageEntries, readManifest } from "./manifest.js";
|
|
34
34
|
import { liveFilesAt, matrixOf, referenceKey, sweepMatrix, } from "./matrix.js";
|
|
35
|
-
import { joinPath, normalizePath, relativePath, resolvePath } from "./paths.js";
|
|
35
|
+
import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.js";
|
|
36
36
|
import { byPosition, isComponentFile } from "./position.js";
|
|
37
37
|
import { SetupError, setupLine } from "./setup-failure.js";
|
|
38
38
|
import { pathsOf, readErrors, skippedRoots, skippedUnits, } from "./type-errors.js";
|
|
@@ -41,6 +41,7 @@ import { references, testFileRulesOf } from "./references.js";
|
|
|
41
41
|
import { roots, unmatchedEverywhere } from "./roots.js";
|
|
42
42
|
import { serializationContract } from "./serialization-contract.js";
|
|
43
43
|
import { outsideTestReferences, testFilesAt } from "./outside-tests.js";
|
|
44
|
+
import { joinedTestFiles, withJoinedTests } from "./test-joins.js";
|
|
44
45
|
import { supportReferences, testSupportFiles } from "./test-support.js";
|
|
45
46
|
import { diagnosticsOf, ownedAs, runSession } from "./session.js";
|
|
46
47
|
import { accessorsOf, storesOf } from "./stores.js";
|
|
@@ -119,6 +120,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
119
120
|
testFiles: config.ts.testFiles,
|
|
120
121
|
parse: (fileName, text) => engine.parseSourceFile(fileName, text),
|
|
121
122
|
publishedAPI: config.targetKind === "library",
|
|
123
|
+
publishedMembers: new Set((workspace?.entries ?? []).filter((entry) => entry.published).map((entry) => entry.dir)),
|
|
122
124
|
};
|
|
123
125
|
const notBuilt = [...discovered.notBuilt];
|
|
124
126
|
const dropped = (configFile, error) => {
|
|
@@ -144,6 +146,10 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
144
146
|
unbuildable.push(...met);
|
|
145
147
|
}
|
|
146
148
|
};
|
|
149
|
+
const testPatterns = config.ts.testFiles.map(globExpression);
|
|
150
|
+
const joins = discovered.derived
|
|
151
|
+
? joinedTestFiles(engine, host, absoluteRoot, discovered.configFiles, testPatterns)
|
|
152
|
+
: new Map();
|
|
147
153
|
const session = runSession(engine, [...discovered.configFiles, ...consumerOf.keys()], (opened) => {
|
|
148
154
|
const consumer = consumerOf.get(opened.configFile);
|
|
149
155
|
const setup = consumer === undefined ? workspace?.unbuildable(opened.configFile) : undefined;
|
|
@@ -237,15 +243,17 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
237
243
|
.filter((file) => referenceOnly(file.fileName))
|
|
238
244
|
.flatMap((file) => relativePath(absoluteRoot, file.fileName) ?? []),
|
|
239
245
|
generated: generatedFilesOf(own, generatedFiles(conventions, own)),
|
|
246
|
+
testFileNames: conventions.testFiles,
|
|
240
247
|
};
|
|
241
248
|
};
|
|
242
249
|
for (const file of project.ownSourceFiles()) {
|
|
243
250
|
heldFiles.add(relativePath(absoluteRoot, file.fileName) ?? file.fileName);
|
|
244
251
|
}
|
|
245
252
|
return stage(project, { configuration, read });
|
|
246
|
-
}, discovered.derived ? "record" : "refuse",
|
|
247
|
-
|
|
248
|
-
|
|
253
|
+
}, discovered.derived ? "record" : "refuse", joins.size === 0
|
|
254
|
+
? workspace?.programs
|
|
255
|
+
: withJoinedTests(engine, host, workspace?.programs ?? new Map(), joins), () => {
|
|
256
|
+
outsideTests = testFilesAt(host, targetRoot, sourcePaths(host, targetRoot).filter((path) => !heldFiles.has(path) && testPatterns.some((pattern) => pattern.test(path))), options.parse);
|
|
249
257
|
});
|
|
250
258
|
for (const configFile of session.unopened) {
|
|
251
259
|
if (consumerOf.has(configFile) || !isGuess(discovered, configFile)) {
|
|
@@ -339,7 +347,7 @@ export function runRoots(engine, host, scope, config, provenance) {
|
|
|
339
347
|
};
|
|
340
348
|
}
|
|
341
349
|
/** The exemption classes this analyzer detects, each by its detector; any other retains nothing. */
|
|
342
|
-
const DETECTORS = new Map([
|
|
350
|
+
export const DETECTORS = new Map([
|
|
343
351
|
["interface-satisfaction", interfaceSatisfaction],
|
|
344
352
|
["enum-group", enumGroup],
|
|
345
353
|
["generated-file", generatedFile],
|
|
@@ -367,6 +375,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
367
375
|
const projectRead = opened.read();
|
|
368
376
|
const resolved = references(project, projectRead.held, targetRoot, {
|
|
369
377
|
testFiles: config.ts.testFiles,
|
|
378
|
+
testFileNames: projectRead.testFileNames,
|
|
370
379
|
});
|
|
371
380
|
const evidence = evidenceRecords({
|
|
372
381
|
project,
|
|
@@ -412,7 +421,6 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
412
421
|
const { projectRead } = one;
|
|
413
422
|
const satisfied = settled[index];
|
|
414
423
|
const detected = heldRecords(satisfied?.evidence ?? one.evidence, {
|
|
415
|
-
disabled,
|
|
416
424
|
mode,
|
|
417
425
|
testFiles: new Set([...one.testFiles, ...support]),
|
|
418
426
|
});
|
|
@@ -616,7 +624,7 @@ export function runSweep(engine, host, scope, config, input, detectors = DETECTO
|
|
|
616
624
|
return sweptOf(readRun(engine, host, scope, config, input.mode, detectors, () => undefined), input);
|
|
617
625
|
}
|
|
618
626
|
/** Reads a run's projects with every per-project fact an emitter reads. */
|
|
619
|
-
function readEmitterRun(engine, host, scope, config, mode, detectors) {
|
|
627
|
+
export function readEmitterRun(engine, host, scope, config, mode, detectors) {
|
|
620
628
|
const targetRoot = scope.target.path;
|
|
621
629
|
const run = readRun(engine, host, scope, config, mode, detectors, (project, read, references) => ({
|
|
622
630
|
deprecation: deprecatedDeclarations(project, read.held),
|
|
@@ -638,32 +646,48 @@ function readEmitterRun(engine, host, scope, config, mode, detectors) {
|
|
|
638
646
|
: { ...run, unansweredHeld: [...new Set([...run.unansweredHeld, ...unread])] };
|
|
639
647
|
}
|
|
640
648
|
/**
|
|
641
|
-
* The workspace members whose
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
*
|
|
649
|
+
* The workspace members whose dependencies the run judges: `read` holds those a built
|
|
650
|
+
* project holds a file below, `unbuilt` the others whose own directory, the nearest
|
|
651
|
+
* manifest's above the configuration, holds a configuration the run could not build. A
|
|
652
|
+
* member no configuration holds a file of is in neither.
|
|
645
653
|
*/
|
|
646
|
-
function
|
|
654
|
+
function judgedMembers(host, targetRoot, read) {
|
|
647
655
|
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
648
656
|
const held = new Set(read.matrix.union.symbols.map((symbol) => symbol.position.path));
|
|
649
|
-
const
|
|
650
|
-
|
|
657
|
+
const owners = new Set(read.notBuilt.map((one) => nearestManifestDir(host, root, one.configFile)));
|
|
658
|
+
const judged = { read: [], unbuilt: [] };
|
|
659
|
+
for (const dir of read.memberDirs) {
|
|
651
660
|
const below = relativePath(root, dir);
|
|
652
661
|
if (below === undefined) {
|
|
653
|
-
|
|
662
|
+
continue;
|
|
654
663
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
664
|
+
if ([...held].some((path) => path.startsWith(`${below}/`))) {
|
|
665
|
+
judged.read.push(dir);
|
|
666
|
+
}
|
|
667
|
+
else if (owners.has(dir)) {
|
|
668
|
+
judged.unbuilt.push(dir);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return judged;
|
|
672
|
+
}
|
|
673
|
+
/** The directory of the nearest `package.json` at or above a file, inside `root`, else `root`. */
|
|
674
|
+
function nearestManifestDir(host, root, file) {
|
|
675
|
+
for (let dir = dirnamePath(file); relativePath(root, dir) !== undefined; dir = dirnamePath(dir)) {
|
|
676
|
+
if (host.kindOf(joinPath(dir, "package.json")) === "file") {
|
|
677
|
+
return dir;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return root;
|
|
658
681
|
}
|
|
659
682
|
/**
|
|
660
683
|
* The input every emitter reads over one sweep of a read run: the facts beside the sweep
|
|
661
684
|
* each kind family reads, every per-project one read in the same session as the sweep.
|
|
662
685
|
*/
|
|
663
|
-
function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
686
|
+
export function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
664
687
|
const targetRoot = scope.target.path;
|
|
665
688
|
const swept = sweptOf(read, input);
|
|
666
689
|
const { projects } = read;
|
|
690
|
+
const members = judgedMembers(host, targetRoot, read);
|
|
667
691
|
return {
|
|
668
692
|
config,
|
|
669
693
|
swept,
|
|
@@ -672,7 +696,7 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
|
672
696
|
references: one.configured.references,
|
|
673
697
|
accessors: one.extra.accessors,
|
|
674
698
|
})), swept.exempt, input.mode),
|
|
675
|
-
dependencies: dependenciesOf(host, targetRoot, [...projects.map((one) => one.extra.needs), configurationNeeds(read.conventionUses)], swept.sweep.candidates.map((candidate) => candidate.id),
|
|
699
|
+
dependencies: dependenciesOf(host, targetRoot, [...projects.map((one) => one.extra.needs), configurationNeeds(read.conventionUses)], swept.sweep.candidates.map((candidate) => candidate.id), members.read, members.unbuilt),
|
|
676
700
|
implementations: mergeImplementations(projects.map((one) => one.extra.implementations)),
|
|
677
701
|
files: {
|
|
678
702
|
tree: sourceTree(host, targetRoot),
|
|
@@ -691,17 +715,6 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
|
691
715
|
intraFunction: intraFunctionFacts(projects.map((one) => one.extra.parts), projects.flatMap((one) => one.configured.references), new Set([...swept.exempt.map((one) => one.id), ...swept.heldByUnanswered])),
|
|
692
716
|
};
|
|
693
717
|
}
|
|
694
|
-
/**
|
|
695
|
-
* What every emitter reads of the run the scope and the configuration describe, swept
|
|
696
|
-
* under the caller's marks: the run's sweep, the facts beside it each kind family reads,
|
|
697
|
-
* and the sides of the target's declared cross-language edges that name a symbol of this
|
|
698
|
-
* language. The run reads no suppression document, so the self-check family has nothing
|
|
699
|
-
* to report.
|
|
700
|
-
*/
|
|
701
|
-
export function runEmitterInput(engine, host, scope, config, input, detectors = DETECTORS) {
|
|
702
|
-
const read = readEmitterRun(engine, host, scope, config, input.mode, detectors);
|
|
703
|
-
return emitterInputOver(host, scope, config, read, input, NO_SELF_CHECK);
|
|
704
|
-
}
|
|
705
718
|
/**
|
|
706
719
|
* Reads the run the scope and the configuration describe for its findings. The inline
|
|
707
720
|
* directives, the ignore file's entries and the baseline's rows are bound before any
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { dirnamePath, joinPath } from "./paths.js";
|
|
9
9
|
/** The name the mapper's manifest declares and every configuration's entry names. */
|
|
10
|
-
|
|
10
|
+
const MAPPER_PACKAGE = "deadset-ts-component-files";
|
|
11
11
|
/** The manifest of the mapper's package, which runs `command`. */
|
|
12
12
|
export function mapperManifest(command) {
|
|
13
13
|
return `${JSON.stringify({
|
|
@@ -50,10 +50,16 @@ export const RESOLVED_EXTENSIONS = [
|
|
|
50
50
|
".mjs",
|
|
51
51
|
".cjs",
|
|
52
52
|
];
|
|
53
|
+
/** The directory a package manager installs packages into, whose files are no project's own. */
|
|
54
|
+
const MODULES_DIR = "node_modules";
|
|
53
55
|
/** The file's name, the last segment of its path. */
|
|
54
56
|
function basename(path) {
|
|
55
57
|
return path.slice(path.lastIndexOf("/") + 1);
|
|
56
58
|
}
|
|
59
|
+
/** Whether one path is the project's own: below the target root and in no `node_modules`. */
|
|
60
|
+
function isProjectPath(root, path) {
|
|
61
|
+
return relativePath(root, path)?.split("/").includes(MODULES_DIR) === false;
|
|
62
|
+
}
|
|
57
63
|
function isRecord(value) {
|
|
58
64
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
59
65
|
}
|
|
@@ -65,6 +71,160 @@ export function configurationModuleForm(name) {
|
|
|
65
71
|
export function isConfigurationDocumentName(name) {
|
|
66
72
|
return DOCUMENT_NAME.test(name);
|
|
67
73
|
}
|
|
74
|
+
/** The flags a tool's command line names its configuration file with. */
|
|
75
|
+
const CONFIG_FLAGS = new Set(["--config", "-c"]);
|
|
76
|
+
/** The extensions of a module a configuration flag may name. */
|
|
77
|
+
const MODULE_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"];
|
|
78
|
+
/** The characters that end a word outside quotes and separate nothing: blanks. */
|
|
79
|
+
const BLANK = /[ \t\r\f\v]/u;
|
|
80
|
+
/** The characters that end a word outside quotes and begin an operator: newline included. */
|
|
81
|
+
const OPERATOR = /[\n;&|()<>]/u;
|
|
82
|
+
/** The characters a backslash escapes inside double quotes; before any other it stands for itself. */
|
|
83
|
+
const DOUBLE_QUOTE_ESCAPES = new Set(["$", "`", '"', "\\"]);
|
|
84
|
+
/**
|
|
85
|
+
* The words of one command line, as POSIX token recognition reads quotes, backslashes,
|
|
86
|
+
* line continuations, comments and operators. A word an expansion may change (an unquoted
|
|
87
|
+
* `$`, backquote, glob character or leading `~`, or a `$` or backquote in double quotes)
|
|
88
|
+
* is computed, and so is the rest of a line whose quote is never closed. A computed word
|
|
89
|
+
* and each operator are undefined, so a flag's file is never read across them.
|
|
90
|
+
*/
|
|
91
|
+
function shellWords(command) {
|
|
92
|
+
const words = [];
|
|
93
|
+
let word;
|
|
94
|
+
let computed = false;
|
|
95
|
+
const end = () => {
|
|
96
|
+
if (word !== undefined) {
|
|
97
|
+
words.push(computed ? undefined : word);
|
|
98
|
+
}
|
|
99
|
+
word = undefined;
|
|
100
|
+
computed = false;
|
|
101
|
+
};
|
|
102
|
+
for (let at = 0; at < command.length; at += 1) {
|
|
103
|
+
const char = command.charAt(at);
|
|
104
|
+
if (char === "\\" && command.charAt(at + 1) === "\n") {
|
|
105
|
+
at += 1;
|
|
106
|
+
}
|
|
107
|
+
else if (BLANK.test(char)) {
|
|
108
|
+
end();
|
|
109
|
+
}
|
|
110
|
+
else if (OPERATOR.test(char)) {
|
|
111
|
+
end();
|
|
112
|
+
words.push(undefined);
|
|
113
|
+
}
|
|
114
|
+
else if (char === "#" && word === undefined) {
|
|
115
|
+
const newline = command.indexOf("\n", at);
|
|
116
|
+
at = newline < 0 ? command.length : newline - 1;
|
|
117
|
+
}
|
|
118
|
+
else if (char === "\\") {
|
|
119
|
+
at += 1;
|
|
120
|
+
word = (word ?? "") + command.charAt(at);
|
|
121
|
+
}
|
|
122
|
+
else if (char === "'" || char === '"') {
|
|
123
|
+
const close = closingQuote(command, at);
|
|
124
|
+
if (close < 0) {
|
|
125
|
+
word ??= "";
|
|
126
|
+
computed = true;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
const quoted = char === "'"
|
|
130
|
+
? { text: command.slice(at + 1, close), computed: false }
|
|
131
|
+
: doubleQuoted(command.slice(at + 1, close));
|
|
132
|
+
word = (word ?? "") + quoted.text;
|
|
133
|
+
computed ||= quoted.computed;
|
|
134
|
+
at = close;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
computed ||= /[$`*?[]/u.test(char) || (char === "~" && word === undefined);
|
|
138
|
+
word = (word ?? "") + char;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
end();
|
|
142
|
+
return words;
|
|
143
|
+
}
|
|
144
|
+
/** The text between one pair of double quotes, unescaped, and whether an expansion may change it. */
|
|
145
|
+
function doubleQuoted(inner) {
|
|
146
|
+
let text = "";
|
|
147
|
+
let computed = false;
|
|
148
|
+
for (let at = 0; at < inner.length; at += 1) {
|
|
149
|
+
const char = inner.charAt(at);
|
|
150
|
+
const next = inner.charAt(at + 1);
|
|
151
|
+
if (char === "\\" && next === "\n") {
|
|
152
|
+
at += 1;
|
|
153
|
+
}
|
|
154
|
+
else if (char === "\\" && DOUBLE_QUOTE_ESCAPES.has(next)) {
|
|
155
|
+
text += next;
|
|
156
|
+
at += 1;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
computed ||= char === "$" || char === "`";
|
|
160
|
+
text += char;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { text, computed };
|
|
164
|
+
}
|
|
165
|
+
/** The index of the quote that closes the one at `open`, or -1 where none does. */
|
|
166
|
+
function closingQuote(command, open) {
|
|
167
|
+
const quote = command.charAt(open);
|
|
168
|
+
for (let at = open + 1; at < command.length; at += 1) {
|
|
169
|
+
const char = command.charAt(at);
|
|
170
|
+
if (char === quote) {
|
|
171
|
+
return at;
|
|
172
|
+
}
|
|
173
|
+
if (quote === '"' && char === "\\") {
|
|
174
|
+
at += 1;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return -1;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The files the scripts of the manifest in one directory name after a configuration flag,
|
|
181
|
+
* `--config <file>`, `-c <file>` or `--config=<file>`, each a JSON file or a module below
|
|
182
|
+
* the target root and in no `node_modules`.
|
|
183
|
+
*/
|
|
184
|
+
function scriptConfigurations(host, root, dir) {
|
|
185
|
+
let scripts;
|
|
186
|
+
try {
|
|
187
|
+
const manifest = JSON.parse(host.readFile(joinPath(dir, MANIFEST)));
|
|
188
|
+
scripts = isRecord(manifest) ? manifest["scripts"] : undefined;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
if (!isRecord(scripts)) {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
const found = [];
|
|
197
|
+
for (const command of Object.values(scripts)) {
|
|
198
|
+
if (typeof command !== "string") {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const words = shellWords(command);
|
|
202
|
+
words.forEach((word, at) => {
|
|
203
|
+
const named = word !== undefined && CONFIG_FLAGS.has(word)
|
|
204
|
+
? words[at + 1]
|
|
205
|
+
: word === undefined
|
|
206
|
+
? undefined
|
|
207
|
+
: /^--config=(.+)$/su.exec(word)?.[1];
|
|
208
|
+
const path = named === undefined ? undefined : resolvePath(dir, named);
|
|
209
|
+
if (path !== undefined &&
|
|
210
|
+
[".json", ...MODULE_EXTENSIONS].some((extension) => path.endsWith(extension)) &&
|
|
211
|
+
isProjectPath(root, path) &&
|
|
212
|
+
host.kindOf(path) === "file") {
|
|
213
|
+
found.push(path);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return found;
|
|
218
|
+
}
|
|
219
|
+
/** The decoded value of one JSON file, or undefined where it cannot be read or parsed. */
|
|
220
|
+
function readDocument(host, path) {
|
|
221
|
+
try {
|
|
222
|
+
return JSON.parse(host.readFile(path));
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
68
228
|
/** Every string value one decoded JSON value holds at any depth. */
|
|
69
229
|
export function documentStrings(value) {
|
|
70
230
|
if (typeof value === "string") {
|
|
@@ -116,7 +276,8 @@ export function configurationFiles(host, targetRoot, files, parse) {
|
|
|
116
276
|
dirs.add(dir);
|
|
117
277
|
}
|
|
118
278
|
}
|
|
119
|
-
const
|
|
279
|
+
const pending = [];
|
|
280
|
+
const named = [];
|
|
120
281
|
for (const dir of [...dirs].sort()) {
|
|
121
282
|
if (!holdsManifest(dir)) {
|
|
122
283
|
continue;
|
|
@@ -130,24 +291,57 @@ export function configurationFiles(host, targetRoot, files, parse) {
|
|
|
130
291
|
.sort();
|
|
131
292
|
}
|
|
132
293
|
catch {
|
|
133
|
-
|
|
294
|
+
names = [];
|
|
134
295
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
296
|
+
pending.push(...names.map((name) => ({ path: joinPath(dir, name), manifestDir: dir })));
|
|
297
|
+
for (const path of scriptConfigurations(host, root, dir)) {
|
|
298
|
+
const held = files.byName.get(path);
|
|
299
|
+
if (path.endsWith(".json")) {
|
|
300
|
+
pending.push({ path, manifestDir: dir });
|
|
140
301
|
}
|
|
141
|
-
|
|
302
|
+
else if (held !== undefined) {
|
|
303
|
+
modules.push({ file: held, form: "a script's configuration flag" });
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
named.push({ path, manifestDir: dir });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// A document reached from two manifests is read against each, so a chain is keyed by both.
|
|
311
|
+
const documents = new Map();
|
|
312
|
+
const unreadable = new Set();
|
|
313
|
+
for (let next = pending.shift(); next !== undefined; next = pending.shift()) {
|
|
314
|
+
let document = documents.get(next.path);
|
|
315
|
+
if (unreadable.has(next.path) || document?.manifestDirs.includes(next.manifestDir) === true) {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (document === undefined) {
|
|
319
|
+
const value = readDocument(host, next.path);
|
|
320
|
+
if (value === undefined) {
|
|
321
|
+
unreadable.add(next.path);
|
|
142
322
|
continue;
|
|
143
323
|
}
|
|
144
|
-
|
|
324
|
+
document = { path: next.path, manifestDirs: [], strings: documentStrings(value) };
|
|
325
|
+
documents.set(next.path, document);
|
|
326
|
+
}
|
|
327
|
+
document.manifestDirs.push(next.manifestDir);
|
|
328
|
+
for (const text of document.strings) {
|
|
329
|
+
const path = RELATIVE_PREFIXES.some((prefix) => text.startsWith(prefix))
|
|
330
|
+
? resolvePath(dirnamePath(next.path), text)
|
|
331
|
+
: undefined;
|
|
332
|
+
if (path?.endsWith(".json") === true &&
|
|
333
|
+
isProjectPath(root, path) &&
|
|
334
|
+
host.kindOf(path) === "file") {
|
|
335
|
+
pending.push({ path, manifestDir: next.manifestDir });
|
|
336
|
+
}
|
|
145
337
|
}
|
|
146
338
|
}
|
|
147
339
|
return {
|
|
148
340
|
modules,
|
|
149
|
-
documents,
|
|
150
|
-
outside: parse === undefined
|
|
341
|
+
documents: [...documents.values()],
|
|
342
|
+
outside: parse === undefined
|
|
343
|
+
? []
|
|
344
|
+
: outsideModules(host, [...dirs], files, holdsManifest, parse, named),
|
|
151
345
|
};
|
|
152
346
|
}
|
|
153
347
|
/** The relative specifiers one file's imports and re-exports name. */
|
|
@@ -195,8 +389,8 @@ export function resolveRelativeSpecifier(host, dir, specifier) {
|
|
|
195
389
|
* does not hold, and the files they import by a relative specifier, until no further
|
|
196
390
|
* file joins. A file that cannot be read is skipped.
|
|
197
391
|
*/
|
|
198
|
-
function outsideModules(host, dirs, files, holdsManifest, parse) {
|
|
199
|
-
const pending = [];
|
|
392
|
+
function outsideModules(host, dirs, files, holdsManifest, parse, named) {
|
|
393
|
+
const pending = [...named];
|
|
200
394
|
for (const dir of [...dirs].sort()) {
|
|
201
395
|
if (!holdsManifest(dir)) {
|
|
202
396
|
continue;
|
|
@@ -214,30 +408,36 @@ function outsideModules(host, dirs, files, holdsManifest, parse) {
|
|
|
214
408
|
}
|
|
215
409
|
pending.push(...names.map((name) => ({ path: joinPath(dir, name), manifestDir: dir })));
|
|
216
410
|
}
|
|
217
|
-
const found =
|
|
218
|
-
const
|
|
411
|
+
const found = new Map();
|
|
412
|
+
const unreadable = new Set();
|
|
219
413
|
for (let next = pending.shift(); next !== undefined; next = pending.shift()) {
|
|
220
|
-
|
|
414
|
+
let outside = found.get(next.path);
|
|
415
|
+
if (unreadable.has(next.path) ||
|
|
416
|
+
files.byName.has(next.path) ||
|
|
417
|
+
outside?.manifestDirs.includes(next.manifestDir) === true) {
|
|
221
418
|
continue;
|
|
222
419
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
420
|
+
if (outside === undefined) {
|
|
421
|
+
let text;
|
|
422
|
+
try {
|
|
423
|
+
text = host.readFile(next.path);
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
unreadable.add(next.path);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
outside = { file: parse(next.path, text), manifestDirs: [] };
|
|
430
|
+
found.set(next.path, outside);
|
|
230
431
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
for (const specifier of relativeSpecifiers(file)) {
|
|
432
|
+
outside.manifestDirs.push(next.manifestDir);
|
|
433
|
+
for (const specifier of relativeSpecifiers(outside.file)) {
|
|
234
434
|
const reached = resolveRelativeSpecifier(host, dirnamePath(next.path), specifier);
|
|
235
435
|
if (reached !== undefined) {
|
|
236
436
|
pending.push({ path: reached, manifestDir: next.manifestDir });
|
|
237
437
|
}
|
|
238
438
|
}
|
|
239
439
|
}
|
|
240
|
-
return found;
|
|
440
|
+
return [...found.values()];
|
|
241
441
|
}
|
|
242
442
|
/** Every dependency the manifest in one directory declares, or none where it holds none to read. */
|
|
243
443
|
export function declaredIn(host, dir) {
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Regenerate it with `UPDATE_GOLDEN=1 npx vitest --run src/conformance.corpus.test.ts`.
|
|
3
3
|
export const RECORD = {
|
|
4
4
|
conformance: {
|
|
5
|
-
corpusVersion: "
|
|
5
|
+
corpusVersion: "5.0.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:26c486907df44fb8eaf3f749f60a6010ac94e7cfeeda70c4aa406445193ab80a",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
};
|