@cplieger/deadset-ts 8.0.0-dev.5 → 8.0.0-dev.7
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/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +3 -2
- package/dist/src/analysis.js +28 -46
- package/dist/src/automatic-types.js +10 -29
- package/dist/src/build-scripts.js +17 -29
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +2 -3
- package/dist/src/config.js +3 -2
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contract/contract.generated.js +1084 -0
- package/dist/src/contract/report.generated.js +796 -0
- package/dist/src/conventions.js +20 -60
- package/dist/src/dependencies.js +25 -43
- package/dist/src/discover.js +13 -39
- package/dist/src/edges.js +7 -15
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +2 -8
- package/dist/src/entry-points.js +3 -10
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +3 -63
- package/dist/src/exempt.js +13 -40
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +11 -20
- package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
- package/dist/src/findings/emitters.js +4 -4
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +8 -7
- package/dist/src/findings/non-code-artifacts.js +5 -6
- package/dist/src/findings/reads-and-writes.js +11 -9
- package/dist/src/findings/self-check.js +10 -12
- package/dist/src/findings/unused-declarations.js +28 -27
- package/dist/src/findings/visibility-narrowing.js +9 -11
- package/dist/src/findings-pass.js +7 -14
- package/dist/src/fs/manifest.js +43 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/git-ignore.js +34 -25
- package/dist/src/html-entries.js +6 -33
- package/dist/src/in-operands.js +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/intra-function-parts.js +18 -26
- package/dist/src/inventory.js +2 -21
- package/dist/src/json-document.js +4 -0
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +4 -360
- package/dist/src/manifest.js +5 -43
- package/dist/src/mdx-entries.js +3 -26
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +26 -15
- package/dist/src/position.js +2 -1
- package/dist/src/print.js +3 -2
- package/dist/src/product.js +13 -0
- package/dist/src/references.js +14 -27
- package/dist/src/report-schema.js +272 -0
- package/dist/src/report.js +39 -42
- package/dist/src/reporters/sarif.js +4 -2
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +12 -26
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +10 -8
- package/dist/src/schema.js +4 -3
- package/dist/src/scope.js +4 -5
- package/dist/src/serialization-contract.js +4 -8
- package/dist/src/session.js +2 -1
- package/dist/src/setup-failure.js +2 -6
- package/dist/src/suppress-file.js +12 -13
- package/dist/src/suppress-inline.js +3 -2
- package/dist/src/suppress.js +29 -48
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +5 -15
- package/dist/src/test-globals.js +6 -28
- package/dist/src/test-joins.js +6 -6
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
- package/dist/src/type-errors.js +14 -38
- package/dist/src/unseen-loads.js +2 -9
- package/dist/src/unwrap.js +23 -0
- package/dist/src/verbs/analyze.js +22 -15
- package/dist/src/verbs/describe.js +7 -7
- package/dist/src/verbs/explain.js +7 -4
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +4 -2
- package/dist/src/verbs/print-retained.js +4 -2
- package/dist/src/verbs/print-roots.js +4 -2
- package/dist/src/verbs/verb.js +1 -5
- package/dist/src/verbs/version.js +4 -3
- package/dist/src/workflow-steps.js +9 -46
- package/dist/src/workspace-manifests.js +2 -2
- package/dist/src/workspace-programs.js +5 -3
- package/dist/src/workspace-resolution.js +13 -17
- package/dist/src/workspace.js +28 -116
- package/jsr.json +1 -1
- package/package.json +2 -1
- package/src/analysis.ts +29 -54
- package/src/automatic-types.ts +11 -29
- package/src/build-scripts.ts +17 -38
- package/src/component-layer.ts +4 -2
- package/src/components.ts +2 -4
- package/src/config.ts +11 -15
- package/src/configuration-files.ts +27 -170
- package/src/conformance-record.ts +1 -1
- package/src/contract/contract.generated.ts +1148 -0
- package/src/contract/report.generated.ts +1157 -0
- package/src/conventions.ts +26 -74
- package/src/dependencies.ts +32 -50
- package/src/discover.ts +14 -39
- package/src/edges.ts +7 -21
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +3 -9
- package/src/entry-points.ts +2 -13
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +5 -74
- package/src/exempt.ts +17 -47
- package/src/file-facts.ts +6 -24
- package/src/finding.ts +9 -9
- package/src/findings/completion.ts +22 -23
- package/src/findings/dependencies-and-module-machinery.ts +4 -7
- package/src/findings/emitter.ts +6 -7
- package/src/findings/emitters.ts +6 -6
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +8 -8
- package/src/findings/non-code-artifacts.ts +5 -7
- package/src/findings/reads-and-writes.ts +15 -12
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +28 -27
- package/src/findings/visibility-narrowing.ts +9 -12
- package/src/findings-pass.ts +7 -16
- package/src/fs/manifest.ts +52 -0
- package/src/fs/tree.ts +110 -0
- package/src/git-ignore.ts +38 -25
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/index.ts +1 -1
- package/src/intra-function-parts.ts +18 -28
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +5 -0
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +6 -406
- package/src/manifest.ts +5 -46
- package/src/mdx-entries.ts +3 -26
- package/src/partial.ts +2 -1
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +3 -2
- package/src/product.ts +18 -0
- package/src/references.ts +14 -29
- package/src/report-schema.ts +364 -0
- package/src/report.ts +66 -229
- package/src/reporters/annotations.ts +2 -2
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -2
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +17 -12
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +31 -38
- package/src/roots.ts +7 -20
- package/src/run.ts +10 -14
- package/src/schema.ts +4 -3
- package/src/scope.ts +4 -10
- package/src/serialization-contract.ts +4 -9
- package/src/session.ts +2 -1
- package/src/setup-failure.ts +5 -7
- package/src/suppress-file.ts +11 -15
- package/src/suppress-inline.ts +2 -2
- package/src/suppress.ts +39 -55
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +5 -18
- package/src/test-globals.ts +9 -27
- package/src/test-joins.ts +6 -8
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/shell.ts +161 -0
- package/src/{yaml-document.ts → text/yaml.ts} +46 -0
- package/src/type-errors.ts +25 -36
- package/src/unseen-loads.ts +1 -12
- package/src/unwrap.ts +34 -0
- package/src/verbs/analyze.ts +38 -25
- package/src/verbs/describe.ts +13 -7
- package/src/verbs/explain.ts +7 -4
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +4 -2
- package/src/verbs/print-retained.ts +4 -2
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +0 -6
- package/src/verbs/version.ts +4 -3
- package/src/workflow-steps.ts +9 -44
- package/src/workspace-manifests.ts +2 -2
- package/src/workspace-programs.ts +5 -3
- package/src/workspace-resolution.ts +21 -17
- package/src/workspace.ts +35 -121
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/dist/src/version.js +0 -7
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
- package/src/version.ts +0 -7
package/src/build-scripts.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
isArrayBindingPattern,
|
|
10
10
|
isArrayLiteralExpression,
|
|
11
11
|
isArrowFunction,
|
|
12
|
-
isAsExpression,
|
|
13
12
|
isCallExpression,
|
|
14
13
|
isConstructorDeclaration,
|
|
15
14
|
isFunctionDeclaration,
|
|
@@ -25,10 +24,8 @@ import {
|
|
|
25
24
|
isNoSubstitutionTemplateLiteral,
|
|
26
25
|
isObjectBindingPattern,
|
|
27
26
|
isObjectLiteralExpression,
|
|
28
|
-
isParenthesizedExpression,
|
|
29
27
|
isPropertyAccessExpression,
|
|
30
28
|
isPropertyAssignment,
|
|
31
|
-
isSatisfiesExpression,
|
|
32
29
|
isSetAccessorDeclaration,
|
|
33
30
|
isStringLiteral,
|
|
34
31
|
isVariableDeclaration,
|
|
@@ -39,11 +36,15 @@ import {
|
|
|
39
36
|
type SourceFile,
|
|
40
37
|
} from "@typescript/native/ast";
|
|
41
38
|
import { filesNamedBy, type ParseFile } from "./configuration-files.ts";
|
|
42
|
-
import { globExpression } from "./glob.ts";
|
|
43
39
|
import type { Host } from "./host.ts";
|
|
44
|
-
import {
|
|
45
|
-
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
40
|
+
import { ancestors, dirnamePath, relativePath, resolvePath } from "./paths.ts";
|
|
46
41
|
import type { SourceFiles } from "./source-files.ts";
|
|
42
|
+
import { bytewise } from "./text/compare.ts";
|
|
43
|
+
import { isRecord } from "./json-document.ts";
|
|
44
|
+
import { unwrapped } from "./unwrap.ts";
|
|
45
|
+
import { holdsManifest, inInstalledPackages, manifestAt } from "./fs/manifest.ts";
|
|
46
|
+
import { globExpression } from "./text/glob.ts";
|
|
47
|
+
import { scriptTokens } from "./text/shell.ts";
|
|
47
48
|
|
|
48
49
|
/** One bundler API: the module, the functions it exports that build, and the option naming entries. */
|
|
49
50
|
interface BundlerAPI {
|
|
@@ -78,8 +79,6 @@ const BUNDLERS: readonly BundlerAPI[] = [
|
|
|
78
79
|
|
|
79
80
|
const MODULE_EXTENSION = /\.(?:js|mjs|cjs|ts|mts|cts)$/u;
|
|
80
81
|
|
|
81
|
-
const MANIFEST = "package.json";
|
|
82
|
-
|
|
83
82
|
/** One own file a build script's bundler call names as an entry. */
|
|
84
83
|
interface BuildEntry {
|
|
85
84
|
readonly file: SourceFile;
|
|
@@ -87,19 +86,6 @@ interface BuildEntry {
|
|
|
87
86
|
readonly source: string;
|
|
88
87
|
}
|
|
89
88
|
|
|
90
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
91
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** The expression a wrapper that changes no value holds. */
|
|
95
|
-
function unwrapped(node: Node): Node {
|
|
96
|
-
let at = node;
|
|
97
|
-
while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
|
|
98
|
-
at = at.expression;
|
|
99
|
-
}
|
|
100
|
-
return at;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
89
|
function literalOf(node: Node): string | undefined {
|
|
104
90
|
const at = unwrapped(node);
|
|
105
91
|
return isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at) ? at.text : undefined;
|
|
@@ -462,7 +448,7 @@ function scriptEntries(file: SourceFile, dir: string, files: SourceFiles): reado
|
|
|
462
448
|
continue;
|
|
463
449
|
}
|
|
464
450
|
const [argument] = call.arguments;
|
|
465
|
-
let options = argument === undefined ? undefined : unwrapped(argument);
|
|
451
|
+
let options: Node | undefined = argument === undefined ? undefined : unwrapped(argument);
|
|
466
452
|
if (options !== undefined && isIdentifier(options)) {
|
|
467
453
|
options = shadowed.has(options.text) ? undefined : declared.get(options.text);
|
|
468
454
|
options = options === undefined ? undefined : unwrapped(options);
|
|
@@ -489,13 +475,7 @@ function scriptEntries(file: SourceFile, dir: string, files: SourceFiles): reado
|
|
|
489
475
|
|
|
490
476
|
/** The modules one manifest's scripts run, by absolute path, each below the target root. */
|
|
491
477
|
function scriptModules(host: Host, root: string, dir: string): readonly string[] {
|
|
492
|
-
|
|
493
|
-
try {
|
|
494
|
-
manifest = JSON.parse(host.readFile(joinPath(dir, MANIFEST)));
|
|
495
|
-
} catch {
|
|
496
|
-
return [];
|
|
497
|
-
}
|
|
498
|
-
const scripts = isRecord(manifest) ? manifest["scripts"] : undefined;
|
|
478
|
+
const scripts = manifestAt(host, dir)?.["scripts"];
|
|
499
479
|
if (!isRecord(scripts)) {
|
|
500
480
|
return [];
|
|
501
481
|
}
|
|
@@ -509,14 +489,14 @@ function scriptModules(host: Host, root: string, dir: string): readonly string[]
|
|
|
509
489
|
if (
|
|
510
490
|
MODULE_EXTENSION.test(path) &&
|
|
511
491
|
relativePath(root, path) !== undefined &&
|
|
512
|
-
!path
|
|
492
|
+
!inInstalledPackages(path) &&
|
|
513
493
|
host.kindOf(path) === "file"
|
|
514
494
|
) {
|
|
515
495
|
found.add(path);
|
|
516
496
|
}
|
|
517
497
|
}
|
|
518
498
|
}
|
|
519
|
-
return [...found].sort();
|
|
499
|
+
return [...found].sort(bytewise);
|
|
520
500
|
}
|
|
521
501
|
|
|
522
502
|
/**
|
|
@@ -533,17 +513,16 @@ export function buildScriptEntries(
|
|
|
533
513
|
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
534
514
|
const dirs = new Set<string>([root]);
|
|
535
515
|
for (const file of files.byName.values()) {
|
|
536
|
-
for (
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
) {
|
|
516
|
+
for (const dir of ancestors(dirnamePath(file.fileName), root)) {
|
|
517
|
+
if (dirs.has(dir)) {
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
541
520
|
dirs.add(dir);
|
|
542
521
|
}
|
|
543
522
|
}
|
|
544
523
|
const found: BuildEntry[] = [];
|
|
545
|
-
for (const dir of [...dirs].sort()) {
|
|
546
|
-
if (host
|
|
524
|
+
for (const dir of [...dirs].sort(bytewise)) {
|
|
525
|
+
if (!holdsManifest(host, dir)) {
|
|
547
526
|
continue;
|
|
548
527
|
}
|
|
549
528
|
for (const path of scriptModules(host, root, dir)) {
|
package/src/component-layer.ts
CHANGED
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
import type { CreateSnapshotParams } from "@typescript/native/sync";
|
|
10
10
|
import type { Host } from "./host.ts";
|
|
11
|
+
import { INSTALLED_PACKAGES } from "./fs/manifest.ts";
|
|
11
12
|
import { dirnamePath, joinPath } from "./paths.ts";
|
|
13
|
+
import { PRODUCT } from "./product.ts";
|
|
12
14
|
|
|
13
15
|
/** The name the mapper's manifest declares and every configuration's entry names. */
|
|
14
|
-
const MAPPER_PACKAGE =
|
|
16
|
+
const MAPPER_PACKAGE = `${PRODUCT}-component-files`;
|
|
15
17
|
|
|
16
18
|
/** The file system one snapshot is created with. */
|
|
17
19
|
type SnapshotFileSystem = NonNullable<CreateSnapshotParams["fileSystem"]>;
|
|
@@ -150,7 +152,7 @@ export function componentLayer(
|
|
|
150
152
|
mappers,
|
|
151
153
|
);
|
|
152
154
|
if (mapper !== undefined) {
|
|
153
|
-
symlinks[joinPath(dirnamePath(configFile),
|
|
155
|
+
symlinks[joinPath(dirnamePath(configFile), INSTALLED_PACKAGES, MAPPER_PACKAGE)] = {
|
|
154
156
|
target: mapper.packageDirectory,
|
|
155
157
|
host: true,
|
|
156
158
|
};
|
package/src/components.ts
CHANGED
|
@@ -7,16 +7,14 @@
|
|
|
7
7
|
import type { Cascade } from "./config.ts";
|
|
8
8
|
import { OUTSIDE, type Graph } from "./graph.ts";
|
|
9
9
|
import type { InventorySymbol } from "./inventory.ts";
|
|
10
|
-
|
|
11
|
-
/** The name every component identifier this analyzer mints starts with. */
|
|
12
|
-
const ANALYZER = "deadset-ts";
|
|
10
|
+
import { PRODUCT } from "./product.ts";
|
|
13
11
|
|
|
14
12
|
/** The fewest digits a component identifier's number is written with. */
|
|
15
13
|
const ID_DIGITS = 4;
|
|
16
14
|
|
|
17
15
|
/** The identifier of the component at one place of the run's order, counted from one. */
|
|
18
16
|
export function componentId(place: number): string {
|
|
19
|
-
return `${
|
|
17
|
+
return `${PRODUCT}/c-${String(place).padStart(ID_DIGITS, "0")}`;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
/** The index of a declaration the component walk has not reached. */
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CONTRACT_VERSION,
|
|
3
|
+
type Confidence,
|
|
4
|
+
type Format,
|
|
5
|
+
type Language,
|
|
6
|
+
type Severity,
|
|
7
|
+
} from "./contract/contract.generated.ts";
|
|
8
|
+
import type { ExemptionClass } from "./exempt-classes.ts";
|
|
9
|
+
import { LANGUAGE, PRODUCT } from "./product.ts";
|
|
2
10
|
|
|
3
11
|
/**
|
|
4
12
|
* The name of the repository configuration at the target root, which is also the
|
|
@@ -21,21 +29,12 @@ export function kindOf(setting: TargetKindSetting, language: Language): TargetKi
|
|
|
21
29
|
return typeof setting === "string" ? setting : setting[language];
|
|
22
30
|
}
|
|
23
31
|
|
|
24
|
-
/** The lowest reachability class a finding must reach to be reported. */
|
|
25
|
-
export type Confidence = "certain" | "probable" | "possible";
|
|
26
|
-
|
|
27
32
|
/** Whether declarations in generated files are analyzed. */
|
|
28
33
|
export type GeneratedFiles = "exclude" | "include";
|
|
29
34
|
|
|
30
35
|
/** How a reference from a consumer's test file counts. */
|
|
31
36
|
export type ConsumerTests = "test" | "production";
|
|
32
37
|
|
|
33
|
-
/** One language an analysis covers. */
|
|
34
|
-
export type Language = "go" | "ts";
|
|
35
|
-
|
|
36
|
-
/** What a finding of one issue kind does to a run. */
|
|
37
|
-
export type Severity = "allow" | "warn" | "deny";
|
|
38
|
-
|
|
39
38
|
/** The severities ranked, the strongest highest. */
|
|
40
39
|
const SEVERITY_RANK: Readonly<Record<Severity, number>> = { allow: 0, warn: 1, deny: 2 };
|
|
41
40
|
|
|
@@ -44,9 +43,6 @@ export function fails(severity: Severity, failOn: Severity): boolean {
|
|
|
44
43
|
return SEVERITY_RANK[severity] >= SEVERITY_RANK[failOn];
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
/** One rendering of the finding list. */
|
|
48
|
-
export type Format = "text" | "json" | "github" | "sarif" | "template";
|
|
49
|
-
|
|
50
46
|
/** The order findings are rendered in. */
|
|
51
47
|
export type Sort = "position" | "size";
|
|
52
48
|
|
|
@@ -198,7 +194,7 @@ export interface Config {
|
|
|
198
194
|
readonly consumersComplete: boolean;
|
|
199
195
|
readonly rootPatterns: readonly string[];
|
|
200
196
|
readonly severity: ReadonlyMap<string, Severity>;
|
|
201
|
-
readonly exemptionsDisabled: readonly
|
|
197
|
+
readonly exemptionsDisabled: readonly ExemptionClass[];
|
|
202
198
|
readonly reporters: Reporters;
|
|
203
199
|
readonly providers: Providers;
|
|
204
200
|
readonly ts: TSSection;
|
|
@@ -236,7 +232,7 @@ export function defaultConfig(): Config {
|
|
|
236
232
|
providers: {
|
|
237
233
|
analyzers: [
|
|
238
234
|
{ shape: "installed", name: "deadset-go", languages: ["go"], command: "deadset-go" },
|
|
239
|
-
{ shape: "installed", name:
|
|
235
|
+
{ shape: "installed", name: PRODUCT, languages: [LANGUAGE], command: PRODUCT },
|
|
240
236
|
],
|
|
241
237
|
},
|
|
242
238
|
ts: {
|
|
@@ -19,10 +19,13 @@ import {
|
|
|
19
19
|
type SourceFile,
|
|
20
20
|
} from "@typescript/native/ast";
|
|
21
21
|
import type { Host } from "./host.ts";
|
|
22
|
-
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
22
|
+
import { ancestors, dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
23
23
|
import type { SourceFiles } from "./source-files.ts";
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
import { bytewise } from "./text/compare.ts";
|
|
25
|
+
import { isRecord } from "./json-document.ts";
|
|
26
|
+
import { filesIn } from "./fs/tree.ts";
|
|
27
|
+
import { holdsManifest, inInstalledPackages, manifestAt, readJSON } from "./fs/manifest.ts";
|
|
28
|
+
import { shellWords } from "./text/shell.ts";
|
|
26
29
|
|
|
27
30
|
/** A configuration module's name, each form with the spelling a root reports it by. */
|
|
28
31
|
const MODULE_FORMS: readonly { readonly name: RegExp; readonly form: string }[] = [
|
|
@@ -72,9 +75,6 @@ export const RESOLVED_EXTENSIONS = [
|
|
|
72
75
|
".cjs",
|
|
73
76
|
] as const;
|
|
74
77
|
|
|
75
|
-
/** The directory a package manager installs packages into, whose files are no project's own. */
|
|
76
|
-
const MODULES_DIR = "node_modules";
|
|
77
|
-
|
|
78
78
|
/** The file's name, the last segment of its path. */
|
|
79
79
|
function basename(path: string): string {
|
|
80
80
|
return path.slice(path.lastIndexOf("/") + 1);
|
|
@@ -82,11 +82,8 @@ function basename(path: string): string {
|
|
|
82
82
|
|
|
83
83
|
/** Whether one path is the project's own: below the target root and in no `node_modules`. */
|
|
84
84
|
function isProjectPath(root: string, path: string): boolean {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
89
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
85
|
+
const below = relativePath(root, path);
|
|
86
|
+
return below !== undefined && !inInstalledPackages(below);
|
|
90
87
|
}
|
|
91
88
|
|
|
92
89
|
/** The form one file name is as a configuration module, or undefined where it is none. */
|
|
@@ -126,120 +123,13 @@ const CONFIG_FLAGS: ReadonlySet<string> = new Set(["--config", "-c"]);
|
|
|
126
123
|
/** The extensions of a module a configuration flag may name. */
|
|
127
124
|
const MODULE_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"] as const;
|
|
128
125
|
|
|
129
|
-
/** The characters that end a word outside quotes and separate nothing: blanks. */
|
|
130
|
-
// Stryker disable next-line Regex: its \v to \V mutant is no valid escape under the u flag, so the whole module fails to parse
|
|
131
|
-
const BLANK = /[ \t\r\f\v]/u;
|
|
132
|
-
|
|
133
|
-
/** The characters that end a word outside quotes and begin an operator: newline included. */
|
|
134
|
-
const OPERATOR = /[\n;&|()<>]/u;
|
|
135
|
-
|
|
136
|
-
/** The characters a backslash escapes inside double quotes; before any other it stands for itself. */
|
|
137
|
-
const DOUBLE_QUOTE_ESCAPES = new Set(["$", "`", '"', "\\"]);
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* The words of one command line, as POSIX token recognition reads quotes, backslashes,
|
|
141
|
-
* line continuations, comments and operators. A word an expansion may change (an unquoted
|
|
142
|
-
* `$`, backquote, glob character or leading `~`, or a `$` or backquote in double quotes)
|
|
143
|
-
* is computed, and so is the rest of a line whose quote is never closed. A computed word
|
|
144
|
-
* and each operator are undefined, so a flag's file is never read across them.
|
|
145
|
-
*/
|
|
146
|
-
function shellWords(command: string): readonly (string | undefined)[] {
|
|
147
|
-
const words: (string | undefined)[] = [];
|
|
148
|
-
let word: string | undefined;
|
|
149
|
-
let computed = false;
|
|
150
|
-
const end = (): void => {
|
|
151
|
-
if (word !== undefined) {
|
|
152
|
-
words.push(computed ? undefined : word);
|
|
153
|
-
}
|
|
154
|
-
word = undefined;
|
|
155
|
-
computed = false;
|
|
156
|
-
};
|
|
157
|
-
for (let at = 0; at < command.length; at += 1) {
|
|
158
|
-
const char = command.charAt(at);
|
|
159
|
-
if (char === "\\" && command.charAt(at + 1) === "\n") {
|
|
160
|
-
at += 1;
|
|
161
|
-
} else if (BLANK.test(char)) {
|
|
162
|
-
end();
|
|
163
|
-
} else if (OPERATOR.test(char)) {
|
|
164
|
-
end();
|
|
165
|
-
words.push(undefined);
|
|
166
|
-
} else if (char === "#" && word === undefined) {
|
|
167
|
-
const newline = command.indexOf("\n", at);
|
|
168
|
-
at = newline < 0 ? command.length : newline - 1;
|
|
169
|
-
} else if (char === "\\") {
|
|
170
|
-
at += 1;
|
|
171
|
-
word = (word ?? "") + command.charAt(at);
|
|
172
|
-
} else if (char === "'" || char === '"') {
|
|
173
|
-
const close = closingQuote(command, at);
|
|
174
|
-
if (close < 0) {
|
|
175
|
-
word ??= "";
|
|
176
|
-
computed = true;
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
const quoted =
|
|
180
|
-
char === "'"
|
|
181
|
-
? { text: command.slice(at + 1, close), computed: false }
|
|
182
|
-
: doubleQuoted(command.slice(at + 1, close));
|
|
183
|
-
word = (word ?? "") + quoted.text;
|
|
184
|
-
computed ||= quoted.computed;
|
|
185
|
-
at = close;
|
|
186
|
-
} else {
|
|
187
|
-
computed ||= /[$`*?[]/u.test(char) || (char === "~" && word === undefined);
|
|
188
|
-
word = (word ?? "") + char;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
end();
|
|
192
|
-
return words;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** The text between one pair of double quotes, unescaped, and whether an expansion may change it. */
|
|
196
|
-
function doubleQuoted(inner: string): { readonly text: string; readonly computed: boolean } {
|
|
197
|
-
let text = "";
|
|
198
|
-
let computed = false;
|
|
199
|
-
for (let at = 0; at < inner.length; at += 1) {
|
|
200
|
-
const char = inner.charAt(at);
|
|
201
|
-
const next = inner.charAt(at + 1);
|
|
202
|
-
if (char === "\\" && next === "\n") {
|
|
203
|
-
at += 1;
|
|
204
|
-
} else if (char === "\\" && DOUBLE_QUOTE_ESCAPES.has(next)) {
|
|
205
|
-
text += next;
|
|
206
|
-
at += 1;
|
|
207
|
-
} else {
|
|
208
|
-
computed ||= char === "$" || char === "`";
|
|
209
|
-
text += char;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return { text, computed };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/** The index of the quote that closes the one at `open`, or -1 where none does. */
|
|
216
|
-
function closingQuote(command: string, open: number): number {
|
|
217
|
-
const quote = command.charAt(open);
|
|
218
|
-
for (let at = open + 1; at < command.length; at += 1) {
|
|
219
|
-
const char = command.charAt(at);
|
|
220
|
-
if (char === quote) {
|
|
221
|
-
return at;
|
|
222
|
-
}
|
|
223
|
-
if (quote === '"' && char === "\\") {
|
|
224
|
-
at += 1;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return -1;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
126
|
/**
|
|
231
127
|
* The files the scripts of the manifest in one directory name after a configuration flag,
|
|
232
128
|
* `--config <file>`, `-c <file>` or `--config=<file>`, each a JSON file or a module below
|
|
233
129
|
* the target root and in no `node_modules`.
|
|
234
130
|
*/
|
|
235
131
|
function scriptConfigurations(host: Host, root: string, dir: string): readonly string[] {
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
const manifest: unknown = JSON.parse(host.readFile(joinPath(dir, MANIFEST)));
|
|
239
|
-
scripts = isRecord(manifest) ? manifest["scripts"] : undefined;
|
|
240
|
-
} catch {
|
|
241
|
-
return [];
|
|
242
|
-
}
|
|
132
|
+
const scripts = manifestAt(host, dir)?.["scripts"];
|
|
243
133
|
if (!isRecord(scripts)) {
|
|
244
134
|
return [];
|
|
245
135
|
}
|
|
@@ -270,15 +160,6 @@ function scriptConfigurations(host: Host, root: string, dir: string): readonly s
|
|
|
270
160
|
return found;
|
|
271
161
|
}
|
|
272
162
|
|
|
273
|
-
/** The decoded value of one JSON file, or undefined where it cannot be read or parsed. */
|
|
274
|
-
function readDocument(host: Host, path: string): unknown {
|
|
275
|
-
try {
|
|
276
|
-
return JSON.parse(host.readFile(path)) as unknown;
|
|
277
|
-
} catch {
|
|
278
|
-
return undefined;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
163
|
/** The configuration files one project holds and sits beside. */
|
|
283
164
|
interface ConfigurationFiles {
|
|
284
165
|
/**
|
|
@@ -346,10 +227,10 @@ export function configurationFiles(
|
|
|
346
227
|
): ConfigurationFiles {
|
|
347
228
|
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
348
229
|
const manifests = new Map<string, boolean>();
|
|
349
|
-
const
|
|
230
|
+
const manifestHeld = (dir: string): boolean => {
|
|
350
231
|
let held = manifests.get(dir);
|
|
351
232
|
if (held === undefined) {
|
|
352
|
-
held = host
|
|
233
|
+
held = holdsManifest(host, dir);
|
|
353
234
|
manifests.set(dir, held);
|
|
354
235
|
}
|
|
355
236
|
return held;
|
|
@@ -360,34 +241,24 @@ export function configurationFiles(
|
|
|
360
241
|
for (const file of files.byPath.values()) {
|
|
361
242
|
const form = configurationModuleForm(basename(file.fileName));
|
|
362
243
|
const fileDir = dirnamePath(file.fileName);
|
|
363
|
-
if (form !== undefined &&
|
|
244
|
+
if (form !== undefined && manifestHeld(fileDir)) {
|
|
364
245
|
modules.push({ file, form, manifestDir: fileDir });
|
|
365
246
|
}
|
|
366
|
-
for (
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
) {
|
|
247
|
+
for (const dir of ancestors(dirnamePath(file.fileName), root)) {
|
|
248
|
+
if (dirs.has(dir)) {
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
371
251
|
dirs.add(dir);
|
|
372
252
|
}
|
|
373
253
|
}
|
|
374
254
|
|
|
375
255
|
const pending: { path: string; manifestDir: string }[] = [];
|
|
376
256
|
const named: { path: string; manifestDir: string }[] = [];
|
|
377
|
-
for (const dir of [...dirs].sort()) {
|
|
378
|
-
if (!
|
|
257
|
+
for (const dir of [...dirs].sort(bytewise)) {
|
|
258
|
+
if (!manifestHeld(dir)) {
|
|
379
259
|
continue;
|
|
380
260
|
}
|
|
381
|
-
|
|
382
|
-
try {
|
|
383
|
-
names = host
|
|
384
|
-
.readDirectory(dir)
|
|
385
|
-
.filter((entry) => !entry.directory && isConfigurationDocumentName(entry.name))
|
|
386
|
-
.map((entry) => entry.name)
|
|
387
|
-
.sort();
|
|
388
|
-
} catch {
|
|
389
|
-
names = [];
|
|
390
|
-
}
|
|
261
|
+
const names = filesIn(host, dir, isConfigurationDocumentName);
|
|
391
262
|
pending.push(...names.map((name) => ({ path: joinPath(dir, name), manifestDir: dir })));
|
|
392
263
|
for (const path of scriptConfigurations(host, root, dir)) {
|
|
393
264
|
const held = files.byName.get(path);
|
|
@@ -410,7 +281,7 @@ export function configurationFiles(
|
|
|
410
281
|
continue;
|
|
411
282
|
}
|
|
412
283
|
if (document === undefined) {
|
|
413
|
-
const value =
|
|
284
|
+
const value = readJSON(host, next.path);
|
|
414
285
|
if (value === undefined) {
|
|
415
286
|
unreadable.add(next.path);
|
|
416
287
|
continue;
|
|
@@ -438,7 +309,7 @@ export function configurationFiles(
|
|
|
438
309
|
outside:
|
|
439
310
|
parse === undefined
|
|
440
311
|
? []
|
|
441
|
-
: outsideModules(host, root, [...dirs], files,
|
|
312
|
+
: outsideModules(host, root, [...dirs], files, manifestHeld, parse, named),
|
|
442
313
|
};
|
|
443
314
|
}
|
|
444
315
|
|
|
@@ -551,25 +422,16 @@ function outsideModules(
|
|
|
551
422
|
root: string,
|
|
552
423
|
dirs: readonly string[],
|
|
553
424
|
files: SourceFiles,
|
|
554
|
-
|
|
425
|
+
manifestHeld: (dir: string) => boolean,
|
|
555
426
|
parse: ParseFile,
|
|
556
427
|
named: readonly { readonly path: string; readonly manifestDir: string }[],
|
|
557
428
|
): readonly OutsideModule[] {
|
|
558
429
|
const pending: { path: string; manifestDir: string }[] = [...named];
|
|
559
|
-
for (const dir of [...dirs].sort()) {
|
|
560
|
-
if (!
|
|
561
|
-
continue;
|
|
562
|
-
}
|
|
563
|
-
let names: readonly string[];
|
|
564
|
-
try {
|
|
565
|
-
names = host
|
|
566
|
-
.readDirectory(dir)
|
|
567
|
-
.filter((entry) => !entry.directory && configurationModuleForm(entry.name) !== undefined)
|
|
568
|
-
.map((entry) => entry.name)
|
|
569
|
-
.sort();
|
|
570
|
-
} catch {
|
|
430
|
+
for (const dir of [...dirs].sort(bytewise)) {
|
|
431
|
+
if (!manifestHeld(dir)) {
|
|
571
432
|
continue;
|
|
572
433
|
}
|
|
434
|
+
const names = filesIn(host, dir, (name) => configurationModuleForm(name) !== undefined);
|
|
573
435
|
pending.push(...names.map((name) => ({ path: joinPath(dir, name), manifestDir: dir })));
|
|
574
436
|
}
|
|
575
437
|
const found = new Map<string, { file: SourceFile; manifestDirs: string[] }>();
|
|
@@ -607,13 +469,8 @@ function outsideModules(
|
|
|
607
469
|
|
|
608
470
|
/** Every dependency the manifest in one directory declares, or none where it holds none to read. */
|
|
609
471
|
export function declaredIn(host: Host, dir: string): readonly string[] {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
value = JSON.parse(host.readFile(joinPath(dir, MANIFEST)));
|
|
613
|
-
} catch {
|
|
614
|
-
return [];
|
|
615
|
-
}
|
|
616
|
-
if (!isRecord(value)) {
|
|
472
|
+
const value = manifestAt(host, dir);
|
|
473
|
+
if (value === undefined) {
|
|
617
474
|
return [];
|
|
618
475
|
}
|
|
619
476
|
const names = new Set<string>();
|
|
@@ -4,7 +4,7 @@ export const RECORD = {
|
|
|
4
4
|
conformance: {
|
|
5
5
|
corpusVersion: "6.0.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:34a8ab2311bbd063b1f341d0e01e35c7fdd059ca3bcd288a60df4491dc177734",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
} as const;
|