@cplieger/deadset-ts 8.0.0-dev.1 → 8.0.0-dev.11
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 +1 -1
- package/dist/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +24 -5
- package/dist/src/analysis.js +181 -341
- package/dist/src/augmentations.js +106 -13
- package/dist/src/automatic-types.js +10 -29
- package/dist/src/build-scripts.js +425 -0
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +2 -3
- package/dist/src/config.js +6 -82
- package/dist/src/configuration-files.js +28 -176
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +3 -30
- package/dist/src/contextual-uses.js +56 -9
- package/dist/src/contract/config.generated.js +558 -0
- package/dist/src/contract/contract.generated.js +1244 -0
- package/dist/src/contract/report.generated.js +862 -0
- package/dist/src/convention-rows.js +73 -3
- package/dist/src/conventions.js +36 -66
- package/dist/src/dependencies.js +103 -40
- package/dist/src/edges.js +38 -122
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +2 -8
- package/dist/src/entry-points.js +11 -27
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +1 -63
- package/dist/src/exempt.js +21 -40
- package/dist/src/failure.js +36 -0
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +139 -72
- package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
- package/dist/src/findings/emitters.js +5 -5
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +13 -10
- package/dist/src/findings/non-code-artifacts.js +7 -8
- package/dist/src/findings/reads-and-writes.js +16 -13
- package/dist/src/findings/self-check.js +10 -12
- package/dist/src/findings/unused-declarations.js +41 -32
- package/dist/src/findings/visibility-narrowing.js +19 -16
- package/dist/src/findings-pass.js +8 -15
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +52 -0
- package/dist/src/fs/manifest.js +72 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/generated-file.js +7 -4
- package/dist/src/git-ignore.js +39 -26
- package/dist/src/graph.js +5 -3
- 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/injection-container.js +1 -1
- package/dist/src/intra-function-parts.js +63 -43
- package/dist/src/inventory.js +2 -21
- package/dist/src/javascript-file.js +33 -0
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +3 -366
- package/dist/src/load/classify.js +270 -0
- package/dist/src/load/diagnostics.js +80 -0
- package/dist/src/load/discover.js +277 -0
- package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
- package/dist/src/load/expected-module.js +136 -0
- package/dist/src/load/load.js +27 -0
- package/dist/src/load/module-lookup.js +37 -0
- package/dist/src/load/plan.js +313 -0
- package/dist/src/load/specifiers.js +115 -0
- package/dist/src/load/synthesize.js +140 -0
- package/dist/src/load/test-globals.js +207 -0
- package/dist/src/load/test-joins.js +82 -0
- package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
- package/dist/src/{workspace-programs.js → load/workspace-programs.js} +89 -66
- package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +23 -65
- package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
- package/dist/src/{workspace.js → load/workspace.js} +62 -124
- package/dist/src/manifest.js +49 -54
- package/dist/src/matrix.js +76 -4
- package/dist/src/mdx-entries.js +3 -26
- package/dist/src/outside-tests.js +1 -1
- 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 +11 -79
- package/dist/src/product.js +13 -0
- package/dist/src/query.js +1 -0
- package/dist/src/ref.js +1 -0
- package/dist/src/references.js +48 -30
- package/dist/src/report-schema.js +51 -0
- package/dist/src/report.js +57 -41
- package/dist/src/reporters/annotations.js +15 -8
- package/dist/src/reporters/sarif.js +16 -10
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +245 -729
- package/dist/src/roots.js +273 -87
- package/dist/src/run.js +66 -77
- package/dist/src/schema.js +320 -343
- package/dist/src/scope.js +38 -115
- package/dist/src/serialization-contract.js +595 -21
- package/dist/src/session.js +34 -29
- package/dist/src/source-files.js +1 -1
- package/dist/src/suppress-file.js +25 -28
- package/dist/src/suppress-inline.js +3 -2
- package/dist/src/suppress.js +33 -51
- package/dist/src/sweep.js +7 -9
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +9 -19
- package/dist/src/test-support.js +8 -11
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/named-files.js +54 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/text/yaml.js +393 -0
- package/dist/src/type-errors.js +11 -232
- package/dist/src/unseen-loads.js +100 -0
- package/dist/src/unwrap.js +23 -0
- package/dist/src/usage.js +10 -0
- package/dist/src/verbs/analyze.js +166 -126
- package/dist/src/verbs/describe.js +9 -16
- package/dist/src/verbs/explain.js +10 -7
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +6 -4
- 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 +14 -3
- package/dist/src/workflow-steps.js +62 -64
- package/jsr.json +1 -1
- package/package.json +5 -4
- package/src/analysis.ts +245 -502
- package/src/augmentations.ts +135 -17
- package/src/automatic-types.ts +11 -29
- package/src/build-scripts.ts +543 -0
- package/src/component-layer.ts +4 -2
- package/src/components.ts +2 -4
- package/src/config.ts +31 -302
- package/src/configuration-files.ts +34 -179
- package/src/configured-declarations.ts +18 -23
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +6 -57
- package/src/contextual-uses.ts +64 -12
- package/src/contract/config.generated.ts +818 -0
- package/src/contract/contract.generated.ts +1308 -0
- package/src/contract/report.generated.ts +1237 -0
- package/src/convention-rows.ts +85 -3
- package/src/conventions.ts +46 -84
- package/src/dependencies.ts +118 -46
- package/src/edges.ts +41 -140
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +3 -9
- package/src/entry-points.ts +12 -38
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +1 -75
- package/src/exempt.ts +36 -50
- package/src/failure.ts +63 -0
- package/src/file-facts.ts +9 -24
- package/src/finding.ts +14 -9
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +181 -84
- package/src/findings/dependencies-and-module-machinery.ts +4 -7
- package/src/findings/emitter.ts +8 -9
- package/src/findings/emitters.ts +7 -7
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +13 -11
- package/src/findings/non-code-artifacts.ts +9 -11
- package/src/findings/reads-and-writes.ts +20 -16
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +45 -32
- package/src/findings/visibility-narrowing.ts +20 -17
- package/src/findings-pass.ts +10 -19
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +70 -0
- package/src/fs/manifest.ts +92 -0
- package/src/fs/tree.ts +110 -0
- package/src/generated-file.ts +7 -4
- package/src/git-ignore.ts +50 -26
- package/src/graph.ts +5 -3
- package/src/host.ts +12 -5
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/index.ts +1 -1
- package/src/injection-container.ts +1 -1
- package/src/intra-function-parts.ts +73 -44
- package/src/inventory.ts +2 -20
- package/src/javascript-file.ts +37 -0
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +5 -413
- package/src/load/classify.ts +397 -0
- package/src/load/diagnostics.ts +144 -0
- package/src/load/discover.ts +455 -0
- package/src/{emit-map.ts → load/emit-map.ts} +1 -1
- package/src/load/expected-module.ts +188 -0
- package/src/load/load.ts +73 -0
- package/src/load/module-lookup.ts +50 -0
- package/src/load/plan.ts +504 -0
- package/src/load/specifiers.ts +168 -0
- package/src/load/synthesize.ts +211 -0
- package/src/load/test-globals.ts +251 -0
- package/src/load/test-joins.ts +98 -0
- package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
- package/src/{workspace-programs.ts → load/workspace-programs.ts} +128 -85
- package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +33 -76
- package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
- package/src/{workspace.ts → load/workspace.ts} +90 -134
- package/src/manifest.ts +81 -75
- package/src/matrix.ts +122 -4
- package/src/mdx-entries.ts +3 -26
- package/src/outside-tests.ts +1 -1
- package/src/partial.ts +3 -2
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +12 -92
- package/src/product.ts +18 -0
- package/src/query.ts +11 -0
- package/src/ref.ts +1 -0
- package/src/references.ts +52 -31
- package/src/report-schema.ts +68 -0
- package/src/report.ts +101 -224
- package/src/reporters/annotations.ts +22 -11
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -4
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +29 -16
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +283 -1114
- package/src/roots.ts +339 -104
- package/src/run.ts +75 -91
- package/src/schema.ts +391 -388
- package/src/scope.ts +46 -138
- package/src/serialization-contract.ts +763 -22
- package/src/session.ts +70 -54
- package/src/source-files.ts +1 -1
- package/src/suppress-file.ts +24 -33
- package/src/suppress-inline.ts +2 -2
- package/src/suppress.ts +43 -58
- package/src/sweep.ts +23 -9
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +15 -25
- package/src/test-support.ts +17 -13
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/named-files.inputs.json +1522 -0
- package/src/text/named-files.ts +68 -0
- package/src/text/shell.ts +161 -0
- package/src/text/yaml.ts +423 -0
- package/src/type-errors.ts +18 -288
- package/src/unseen-loads.ts +134 -0
- package/src/unwrap.ts +34 -0
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +224 -162
- package/src/verbs/describe.ts +14 -15
- package/src/verbs/explain.ts +10 -11
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +6 -4
- package/src/verbs/print-retained.ts +4 -2
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +2 -8
- package/src/verbs/version.ts +15 -3
- package/src/workflow-steps.ts +74 -64
- package/dist/src/base-url.js +0 -18
- package/dist/src/discover.js +0 -468
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/dist/src/setup-failure.js +0 -23
- package/dist/src/test-joins.js +0 -101
- package/dist/src/version.js +0 -7
- package/src/base-url.ts +0 -24
- package/src/discover.ts +0 -653
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
- package/src/setup-failure.ts +0 -39
- package/src/test-joins.ts +0 -120
- package/src/version.ts +0 -7
package/src/dependencies.ts
CHANGED
|
@@ -42,17 +42,26 @@ import {
|
|
|
42
42
|
import type { FindingPosition } from "./finding.ts";
|
|
43
43
|
import type { Host } from "./host.ts";
|
|
44
44
|
import { nodeKey, packageScope, type Inventory } from "./inventory.ts";
|
|
45
|
-
import {
|
|
46
|
-
|
|
45
|
+
import {
|
|
46
|
+
ancestors,
|
|
47
|
+
dirnamePath,
|
|
48
|
+
isAbsolutePath,
|
|
49
|
+
joinPath,
|
|
50
|
+
relativePath,
|
|
51
|
+
resolvePath,
|
|
52
|
+
} from "./paths.ts";
|
|
47
53
|
import type { DependencySection, Module } from "./ref.ts";
|
|
48
54
|
import { UNANSWERED } from "./query.ts";
|
|
49
55
|
import type { Handle, ProjectView } from "./session.ts";
|
|
50
56
|
import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
|
|
51
|
-
import {
|
|
57
|
+
import { manifestCommands } from "./manifest.ts";
|
|
58
|
+
import { workflowCommands, workflowTokens } from "./workflow-steps.ts";
|
|
59
|
+
import { bytewise } from "./text/compare.ts";
|
|
60
|
+
import { isRecord } from "./json-document.ts";
|
|
61
|
+
import { inInstalledPackages, INSTALLED_PACKAGES, MANIFEST, readDocument } from "./fs/manifest.ts";
|
|
62
|
+
import { commandsOf, scriptTokens } from "./text/shell.ts";
|
|
52
63
|
|
|
53
|
-
const
|
|
54
|
-
const MODULES_DIR = "node_modules";
|
|
55
|
-
const MODULES_SEGMENT = `/${MODULES_DIR}/`;
|
|
64
|
+
const MODULES_SEGMENT = `/${INSTALLED_PACKAGES}/`;
|
|
56
65
|
const TYPES_SCOPE = "@types/";
|
|
57
66
|
|
|
58
67
|
/** A script token naming a JavaScript file by a path no expansion changes. */
|
|
@@ -160,25 +169,6 @@ export interface Dependencies {
|
|
|
160
169
|
readonly heldBy: ReadonlyMap<string, readonly string[]>;
|
|
161
170
|
}
|
|
162
171
|
|
|
163
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
164
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** One JSON document's decoded value, or undefined where it cannot be read or parsed. */
|
|
168
|
-
function readJSON(host: Host, path: string): { text: string; value: unknown } | undefined {
|
|
169
|
-
let text: string;
|
|
170
|
-
try {
|
|
171
|
-
text = host.readFile(path);
|
|
172
|
-
} catch {
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
|
-
try {
|
|
176
|
-
return { text, value: JSON.parse(text) };
|
|
177
|
-
} catch {
|
|
178
|
-
return undefined;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
172
|
/**
|
|
183
173
|
* The package a bare module specifier names: its first segment, or its first two
|
|
184
174
|
* where it starts with a scope. A relative, absolute or scheme-prefixed specifier and
|
|
@@ -429,7 +419,7 @@ export function projectNeeds<Brand>(
|
|
|
429
419
|
}
|
|
430
420
|
for (const { strings, manifestDirs } of configuration.documents) {
|
|
431
421
|
for (const manifestDir of manifestDirs) {
|
|
432
|
-
const names = declaredAt(joinPath(manifestDir,
|
|
422
|
+
const names = declaredAt(joinPath(manifestDir, MANIFEST));
|
|
433
423
|
for (const text of strings) {
|
|
434
424
|
use([PROJECT_OWNER], dependenciesNamed(text, names), joinPath(manifestDir, MANIFEST));
|
|
435
425
|
}
|
|
@@ -444,7 +434,7 @@ export function projectNeeds<Brand>(
|
|
|
444
434
|
);
|
|
445
435
|
for (const { file, manifestDirs } of [...configuration.outside, ...ranFiles]) {
|
|
446
436
|
for (const manifestDir of manifestDirs) {
|
|
447
|
-
const names = declaredAt(joinPath(manifestDir,
|
|
437
|
+
const names = declaredAt(joinPath(manifestDir, MANIFEST));
|
|
448
438
|
for (const { text } of moduleStrings(file)) {
|
|
449
439
|
use([PROJECT_OWNER], dependenciesNamed(text, names), joinPath(manifestDir, MANIFEST));
|
|
450
440
|
}
|
|
@@ -467,8 +457,8 @@ function scriptRunFiles(
|
|
|
467
457
|
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
468
458
|
const dirs = new Set<string>([root]);
|
|
469
459
|
for (const name of files.byName.keys()) {
|
|
470
|
-
for (
|
|
471
|
-
if (
|
|
460
|
+
for (const dir of ancestors(dirnamePath(name), root)) {
|
|
461
|
+
if (dirs.has(dir)) {
|
|
472
462
|
break;
|
|
473
463
|
}
|
|
474
464
|
dirs.add(dir);
|
|
@@ -478,7 +468,7 @@ function scriptRunFiles(
|
|
|
478
468
|
[root, new Set(workflowTokens(host, root))],
|
|
479
469
|
]);
|
|
480
470
|
for (const dir of dirs) {
|
|
481
|
-
const read =
|
|
471
|
+
const read = readDocument(host, joinPath(dir, MANIFEST));
|
|
482
472
|
if (read !== undefined && isRecord(read.value)) {
|
|
483
473
|
const own = scriptTokensOf(read.value);
|
|
484
474
|
tokens.set(dir, dir === root ? new Set([...own, ...(tokens.get(root) ?? [])]) : own);
|
|
@@ -491,7 +481,8 @@ function scriptRunFiles(
|
|
|
491
481
|
const below = path === undefined ? undefined : relativePath(root, path);
|
|
492
482
|
if (
|
|
493
483
|
path !== undefined &&
|
|
494
|
-
below
|
|
484
|
+
below !== undefined &&
|
|
485
|
+
!inInstalledPackages(below) &&
|
|
495
486
|
!files.byName.has(path) &&
|
|
496
487
|
host.kindOf(path) === "file"
|
|
497
488
|
) {
|
|
@@ -797,8 +788,8 @@ function lineAndColumn(text: string, offset: number): { line: number; column: nu
|
|
|
797
788
|
|
|
798
789
|
/** The installed copy of one dependency, read where the package manager puts it for the target. */
|
|
799
790
|
function installedOf(host: Host, root: string, name: string): InstalledDependency | undefined {
|
|
800
|
-
for (
|
|
801
|
-
const read =
|
|
791
|
+
for (const dir of ancestors(root)) {
|
|
792
|
+
const read = readDocument(host, joinPath(dir, INSTALLED_PACKAGES, name, MANIFEST));
|
|
802
793
|
if (read !== undefined && isRecord(read.value)) {
|
|
803
794
|
const bin = read.value["bin"];
|
|
804
795
|
const peers = read.value["peerDependencies"];
|
|
@@ -815,10 +806,8 @@ function installedOf(host: Host, root: string, name: string): InstalledDependenc
|
|
|
815
806
|
: [],
|
|
816
807
|
};
|
|
817
808
|
}
|
|
818
|
-
if (dirnamePath(dir) === dir) {
|
|
819
|
-
return undefined;
|
|
820
|
-
}
|
|
821
809
|
}
|
|
810
|
+
return undefined;
|
|
822
811
|
}
|
|
823
812
|
|
|
824
813
|
/** Every dependency the manifest text at `path` declares, in the order the text writes them. */
|
|
@@ -871,6 +860,88 @@ function scriptTokensOf(manifest: Record<string, unknown>): ReadonlySet<string>
|
|
|
871
860
|
return found;
|
|
872
861
|
}
|
|
873
862
|
|
|
863
|
+
/**
|
|
864
|
+
* The runtime flags whose value is a module loaded before the entry, as `--flag value` or
|
|
865
|
+
* `--flag=value`: https://nodejs.org/api/cli.html#--importmodule,
|
|
866
|
+
* https://nodejs.org/api/cli.html#--experimental-loadermodule,
|
|
867
|
+
* https://nodejs.org/api/cli.html#-r---require-module
|
|
868
|
+
*/
|
|
869
|
+
const PRELOAD_FLAGS: ReadonlySet<string> = new Set([
|
|
870
|
+
"--experimental-loader",
|
|
871
|
+
"--import",
|
|
872
|
+
"--loader",
|
|
873
|
+
"--require",
|
|
874
|
+
"-r",
|
|
875
|
+
]);
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* The programs that pass a preload flag to the runtime they start: Node itself, and the
|
|
879
|
+
* runners that forward Node's flags or read the same `--require`:
|
|
880
|
+
* https://mochajs.org/#-require-module-r-module, https://tsx.is/node-enhancement,
|
|
881
|
+
* https://typestrong.org/ts-node/docs/options#require,
|
|
882
|
+
* https://github.com/remy/nodemon#running-non-node-scripts
|
|
883
|
+
*/
|
|
884
|
+
const PRELOADING_PROGRAMS: ReadonlySet<string> = new Set([
|
|
885
|
+
"mocha",
|
|
886
|
+
"node",
|
|
887
|
+
"nodemon",
|
|
888
|
+
"ts-node",
|
|
889
|
+
"tsx",
|
|
890
|
+
]);
|
|
891
|
+
|
|
892
|
+
/** The programs that run the command after them, and an environment assignment before it. */
|
|
893
|
+
const LAUNCHERS: ReadonlySet<string> = new Set(["cross-env", "env", "npx"]);
|
|
894
|
+
const ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/u;
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* The packages `scripts` preload by a runtime flag's bare module specifier, a flag
|
|
898
|
+
* counting only in a command whose program passes it to the runtime, and for Node only
|
|
899
|
+
* before the script it runs, whose own arguments come after it.
|
|
900
|
+
*/
|
|
901
|
+
function preloadsOf(scripts: readonly string[]): ReadonlySet<string> {
|
|
902
|
+
const found = new Set<string>();
|
|
903
|
+
for (const script of scripts) {
|
|
904
|
+
for (const command of commandsOf(script)) {
|
|
905
|
+
const tokens = scriptTokens(command);
|
|
906
|
+
let at = 0;
|
|
907
|
+
while (
|
|
908
|
+
at < tokens.length &&
|
|
909
|
+
(ASSIGNMENT.test(tokens[at] ?? "") || LAUNCHERS.has(tokens[at] ?? ""))
|
|
910
|
+
) {
|
|
911
|
+
at += 1;
|
|
912
|
+
}
|
|
913
|
+
const path = tokens[at] ?? "";
|
|
914
|
+
const program = path.slice(path.lastIndexOf("/") + 1);
|
|
915
|
+
if (!PRELOADING_PROGRAMS.has(program)) {
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
for (at += 1; at < tokens.length; at += 1) {
|
|
919
|
+
const token = tokens[at] ?? "";
|
|
920
|
+
if (!token.startsWith("-")) {
|
|
921
|
+
if (program === "node") {
|
|
922
|
+
break;
|
|
923
|
+
}
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const equals = token.indexOf("=");
|
|
927
|
+
const flag = equals < 0 ? token : token.slice(0, equals);
|
|
928
|
+
if (!PRELOAD_FLAGS.has(flag)) {
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
if (equals < 0) {
|
|
932
|
+
at += 1;
|
|
933
|
+
}
|
|
934
|
+
const value = equals < 0 ? tokens[at] : token.slice(equals + 1);
|
|
935
|
+
const name = value === undefined ? undefined : packageOfSpecifier(value);
|
|
936
|
+
if (name !== undefined) {
|
|
937
|
+
found.add(name);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return found;
|
|
943
|
+
}
|
|
944
|
+
|
|
874
945
|
/** Whether a path below the target root lies in the directory `dir` below it, `""` being the root. */
|
|
875
946
|
function isInside(path: string, dir: string): boolean {
|
|
876
947
|
return dir === "" || path.startsWith(`${dir}/`);
|
|
@@ -966,22 +1037,29 @@ export function dependenciesOf(
|
|
|
966
1037
|
}
|
|
967
1038
|
}
|
|
968
1039
|
const workflow = new Set(workflowTokens(host, targetRoot));
|
|
1040
|
+
const workflowPreloads = preloadsOf(workflowCommands(host, targetRoot));
|
|
969
1041
|
const declared: DeclaredDependency[] = [];
|
|
970
1042
|
const ran = new Set<string>();
|
|
971
1043
|
const peers = new Map<string, readonly string[]>();
|
|
972
1044
|
const uninstalledHere = new Set(nested);
|
|
973
1045
|
for (const [dir, path] of manifests) {
|
|
974
|
-
const read =
|
|
1046
|
+
const read = readDocument(host, joinPath(dir, MANIFEST));
|
|
975
1047
|
if (read === undefined || !isRecord(read.value)) {
|
|
976
1048
|
continue;
|
|
977
1049
|
}
|
|
978
1050
|
const tokens = scriptTokensOf(read.value);
|
|
1051
|
+
const preloads = preloadsOf(
|
|
1052
|
+
manifestCommands(host, dir, read.value).map(({ command }) => command),
|
|
1053
|
+
);
|
|
979
1054
|
for (const dependency of declaredIn(read.text, read.value, path, scopeOf(path))) {
|
|
980
1055
|
const found = installedOf(host, dir, dependency.name);
|
|
981
1056
|
if (found === undefined && uninstalledHere.has(dir)) {
|
|
982
1057
|
continue;
|
|
983
1058
|
}
|
|
984
1059
|
declared.push(dependency);
|
|
1060
|
+
if (preloads.has(dependency.name) || workflowPreloads.has(dependency.name)) {
|
|
1061
|
+
ran.add(declarationKey(dependency));
|
|
1062
|
+
}
|
|
985
1063
|
if (found === undefined) {
|
|
986
1064
|
continue;
|
|
987
1065
|
}
|
|
@@ -1045,10 +1123,7 @@ export function dependenciesOf(
|
|
|
1045
1123
|
const heldBy = new Map<string, readonly string[]>();
|
|
1046
1124
|
for (const [key, held] of holders) {
|
|
1047
1125
|
if (!live.has(key) && !ran.has(key)) {
|
|
1048
|
-
heldBy.set(
|
|
1049
|
-
key,
|
|
1050
|
-
[...held].sort((a, b) => (a < b ? -1 : 1)),
|
|
1051
|
-
);
|
|
1126
|
+
heldBy.set(key, [...held].sort(bytewise));
|
|
1052
1127
|
}
|
|
1053
1128
|
}
|
|
1054
1129
|
const users = new Map<string, number>();
|
|
@@ -1060,10 +1135,7 @@ export function dependenciesOf(
|
|
|
1060
1135
|
for (const id of candidates) {
|
|
1061
1136
|
const sole = [...(uses.get(id) ?? [])].filter((one) => users.get(one) === 1 && names.has(one));
|
|
1062
1137
|
if (sole.length > 0) {
|
|
1063
|
-
lastUses.set(
|
|
1064
|
-
id,
|
|
1065
|
-
sole.sort((a, b) => (a < b ? -1 : 1)),
|
|
1066
|
-
);
|
|
1138
|
+
lastUses.set(id, sole.sort(bytewise));
|
|
1067
1139
|
}
|
|
1068
1140
|
}
|
|
1069
1141
|
const owners = new Map<string, Set<string>>();
|
|
@@ -1075,7 +1147,7 @@ export function dependenciesOf(
|
|
|
1075
1147
|
}
|
|
1076
1148
|
}
|
|
1077
1149
|
const fileLastUses = new Map<string, string[]>();
|
|
1078
|
-
for (const [one, held] of [...owners].sort(([a], [b]) => (a
|
|
1150
|
+
for (const [one, held] of [...owners].sort(([a], [b]) => bytewise(a, b))) {
|
|
1079
1151
|
const paths = new Set([...held].map((owner) => owner.replace(/:\d+:\d+$/u, "")));
|
|
1080
1152
|
const [path] = paths;
|
|
1081
1153
|
if (paths.size === 1 && path !== undefined && path !== PROJECT_OWNER) {
|
package/src/edges.ts
CHANGED
|
@@ -5,15 +5,18 @@
|
|
|
5
5
|
* pairing is a use, and the merge reads the two evaluations together.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { EDGES_SCHEMA } from "./contract/config.generated.ts";
|
|
9
9
|
import type { Finding } from "./finding.ts";
|
|
10
10
|
import type { EdgeSide, EdgeSideName } from "./findings/boundary.ts";
|
|
11
|
+
import { readTargetDocument } from "./fs/document.ts";
|
|
11
12
|
import type { Host } from "./host.ts";
|
|
12
13
|
import type { InventorySymbol } from "./inventory.ts";
|
|
13
|
-
import { positionAt,
|
|
14
|
-
import { joinPath } from "./paths.ts";
|
|
14
|
+
import { dotted, placeIn, positionAt, readStrict, StrictJSONError } from "./json-document.ts";
|
|
15
15
|
import { positionKey } from "./position.ts";
|
|
16
|
+
import { firstRefusal } from "./schema.ts";
|
|
16
17
|
import { isPartSubject } from "./suppress.ts";
|
|
18
|
+
import { bytewise } from "./text/compare.ts";
|
|
19
|
+
import { UsageError } from "./usage.ts";
|
|
17
20
|
|
|
18
21
|
/** The name and the location the Contract fixes for the edges document. */
|
|
19
22
|
export const EDGES_FILE = "deadset-edges.json";
|
|
@@ -21,155 +24,61 @@ export const EDGES_FILE = "deadset-edges.json";
|
|
|
21
24
|
/** The language prefix of every reference this analyzer evaluates. */
|
|
22
25
|
const OWN_PREFIX = "ts://";
|
|
23
26
|
|
|
24
|
-
/** The closed key list of the document and of one edge. */
|
|
25
|
-
const DOCUMENT_KEYS: ReadonlySet<string> = new Set(["description", "edges"]);
|
|
26
|
-
const EDGE_KEYS: ReadonlySet<string> = new Set(["id", "because", "provides", "used_by"]);
|
|
27
|
-
|
|
28
|
-
/** An edge identifier: solidus-separated segments of ASCII letters, digits, `_`, `.` and `-`. */
|
|
29
|
-
const ID_FORM = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/u;
|
|
30
|
-
|
|
31
|
-
/** A reference naming one symbol of some language: `<language>://<scope>#<fragment>`. */
|
|
32
|
-
const REFERENCE_FORM = /^[a-z][a-z0-9]*:\/\/[^ \t\r\n#]+#[^ \t\r\n]*$/u;
|
|
33
|
-
|
|
34
|
-
const DOCUMENT_WANT = "an object carrying the edges array, and an optional description";
|
|
35
|
-
const EDGE_WANT = "an edge naming id, provides and used_by, each a string, and an optional because";
|
|
36
|
-
const ID_WANT =
|
|
37
|
-
"one or more solidus-separated segments of ASCII letters, digits, underscore, full stop and hyphen";
|
|
38
|
-
const REFERENCE_WANT =
|
|
39
|
-
"a stable symbol reference naming one symbol exactly, <language>://<scope>#<fragment>";
|
|
40
|
-
|
|
41
27
|
/** An edges document the Contract refuses: a malformed input, answered with the usage code. */
|
|
42
|
-
class EdgesError extends
|
|
43
|
-
constructor(at: string,
|
|
44
|
-
super(
|
|
28
|
+
class EdgesError extends UsageError {
|
|
29
|
+
constructor(at: string, reason: string) {
|
|
30
|
+
super(`${at}: ${reason}`);
|
|
45
31
|
this.name = "EdgesError";
|
|
46
32
|
}
|
|
47
33
|
}
|
|
48
34
|
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
* would name a set of symbols rather than one.
|
|
52
|
-
*/
|
|
53
|
-
function carriesWildcard(ref: string): boolean {
|
|
54
|
-
let quoted = false;
|
|
55
|
-
let computed = false;
|
|
56
|
-
let escaped = false;
|
|
57
|
-
for (const char of ref) {
|
|
58
|
-
if (escaped) {
|
|
59
|
-
escaped = false;
|
|
60
|
-
} else if (quoted && char === "\\") {
|
|
61
|
-
escaped = true;
|
|
62
|
-
} else if (quoted) {
|
|
63
|
-
quoted = char !== "'";
|
|
64
|
-
} else if (computed) {
|
|
65
|
-
computed = char !== "]";
|
|
66
|
-
} else if (char === "'") {
|
|
67
|
-
quoted = true;
|
|
68
|
-
} else if (char === "[") {
|
|
69
|
-
computed = true;
|
|
70
|
-
} else if (char === "*") {
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isObject(value: unknown): value is Record<string, unknown> {
|
|
78
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** One edge, checked: the closed key list, the value types and the value forms. */
|
|
82
|
-
function readEdge(value: unknown, at: string): { id: string; provides: string; usedBy: string } {
|
|
83
|
-
const refuse = (text: string, want: string): never => {
|
|
84
|
-
throw new EdgesError(at, text, want);
|
|
85
|
-
};
|
|
86
|
-
if (!isObject(value)) {
|
|
87
|
-
return refuse(JSON.stringify(value), EDGE_WANT);
|
|
88
|
-
}
|
|
89
|
-
for (const [key, held] of Object.entries(value)) {
|
|
90
|
-
if (!EDGE_KEYS.has(key) || typeof held !== "string") {
|
|
91
|
-
refuse(`the member ${JSON.stringify(key)} holding ${JSON.stringify(held)}`, EDGE_WANT);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
const edge = value as Readonly<Record<string, string | undefined>>;
|
|
95
|
-
const id = edge["id"] ?? refuse("an edge naming no id", EDGE_WANT);
|
|
96
|
-
const provides = edge["provides"] ?? refuse("an edge naming no provides side", EDGE_WANT);
|
|
97
|
-
const usedBy = edge["used_by"] ?? refuse("an edge naming no used_by side", EDGE_WANT);
|
|
98
|
-
if (!ID_FORM.test(id)) {
|
|
99
|
-
refuse(JSON.stringify(id), ID_WANT);
|
|
100
|
-
}
|
|
101
|
-
for (const ref of [provides, usedBy]) {
|
|
102
|
-
if (!REFERENCE_FORM.test(ref) || carriesWildcard(ref)) {
|
|
103
|
-
refuse(JSON.stringify(ref), REFERENCE_WANT);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return { id, provides, usedBy };
|
|
107
|
-
}
|
|
35
|
+
/** The sides of one edge, in the order a reader yields them. */
|
|
36
|
+
const SIDE_NAMES: readonly EdgeSideName[] = ["provides", "used_by"];
|
|
108
37
|
|
|
109
38
|
/**
|
|
110
39
|
* Every side of a declared edge that names a symbol of this language, in document order,
|
|
111
40
|
* `provides` before `used_by` within an edge. An absent document declares no edge. The
|
|
112
|
-
* document is strict JSON
|
|
113
|
-
* {@link EdgesError}.
|
|
41
|
+
* document is strict JSON held to the Contract's edges schema, and a defect in it is an
|
|
42
|
+
* {@link EdgesError} at the line and column of the edge it is in, where it is in one.
|
|
114
43
|
*/
|
|
115
44
|
export function readEdgeSides(host: Host, targetRoot: string): readonly EdgeSide[] {
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
45
|
+
const text = readTargetDocument(host, targetRoot, EDGES_FILE);
|
|
46
|
+
if (text === undefined) {
|
|
118
47
|
return [];
|
|
119
48
|
}
|
|
120
|
-
|
|
121
|
-
let parsed: unknown;
|
|
49
|
+
let read;
|
|
122
50
|
try {
|
|
123
|
-
|
|
51
|
+
read = readStrict(text, "edges");
|
|
124
52
|
} catch (error: unknown) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
if (!isObject(parsed)) {
|
|
132
|
-
throw new EdgesError(EDGES_FILE, JSON.stringify(parsed), DOCUMENT_WANT);
|
|
133
|
-
}
|
|
134
|
-
const walk = walkDocument(text, "edges");
|
|
135
|
-
if (walk.twice !== undefined) {
|
|
136
|
-
throw new EdgesError(
|
|
137
|
-
positionKey(positionAt(text, EDGES_FILE, walk.twice.offset)),
|
|
138
|
-
`the member ${JSON.stringify(walk.twice.name)} is written twice, so neither value is chosen`,
|
|
139
|
-
DOCUMENT_WANT,
|
|
140
|
-
);
|
|
53
|
+
if (!(error instanceof StrictJSONError)) {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
throw new EdgesError(positionKey(positionAt(text, EDGES_FILE, error.offset)), error.message);
|
|
141
57
|
}
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
throw new EdgesError(
|
|
151
|
-
EDGES_FILE,
|
|
152
|
-
unknown === undefined ? EDGES_FILE : `the member ${JSON.stringify(unknown)}`,
|
|
153
|
-
DOCUMENT_WANT,
|
|
154
|
-
);
|
|
58
|
+
const value = read.value;
|
|
59
|
+
const refused = firstRefusal(EDGES_SCHEMA, value);
|
|
60
|
+
if (refused !== undefined) {
|
|
61
|
+
const place = placeIn(value, refused.pointer, refused.member);
|
|
62
|
+
const offset = place.length > 1 ? read.starts.get(place[1] ?? "") : undefined;
|
|
63
|
+
const at =
|
|
64
|
+
offset === undefined ? EDGES_FILE : positionKey(positionAt(text, EDGES_FILE, offset));
|
|
65
|
+
const where = dotted(place);
|
|
66
|
+
throw new EdgesError(at, where === "" ? refused.reason : `${where}: ${refused.reason}`);
|
|
155
67
|
}
|
|
68
|
+
const edges = (
|
|
69
|
+
value as { readonly edges: Readonly<Record<string, Record<EdgeSideName, string>>> }
|
|
70
|
+
).edges;
|
|
156
71
|
|
|
157
72
|
const sides: EdgeSide[] = [];
|
|
158
|
-
(
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
["provides", edge.provides],
|
|
165
|
-
["used_by", edge.usedBy],
|
|
166
|
-
];
|
|
167
|
-
for (const [side, symbol] of named) {
|
|
168
|
-
if (symbol.startsWith(OWN_PREFIX)) {
|
|
169
|
-
sides.push({ edge: edge.id, side, symbol });
|
|
73
|
+
for (const id of read.starts.keys()) {
|
|
74
|
+
const edge = String(id);
|
|
75
|
+
for (const side of SIDE_NAMES) {
|
|
76
|
+
const symbol = edges[edge]?.[side];
|
|
77
|
+
if (symbol?.startsWith(OWN_PREFIX) === true) {
|
|
78
|
+
sides.push({ edge, side, symbol });
|
|
170
79
|
}
|
|
171
80
|
}
|
|
172
|
-
}
|
|
81
|
+
}
|
|
173
82
|
return sides;
|
|
174
83
|
}
|
|
175
84
|
|
|
@@ -190,14 +99,6 @@ interface Evaluated<F extends Finding> {
|
|
|
190
99
|
readonly evaluations: readonly EdgeEvaluation<F>[];
|
|
191
100
|
}
|
|
192
101
|
|
|
193
|
-
/** Two strings ordered bytewise. */
|
|
194
|
-
function compare(a: string, b: string): number {
|
|
195
|
-
if (a === b) {
|
|
196
|
-
return 0;
|
|
197
|
-
}
|
|
198
|
-
return a < b ? -1 : 1;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
102
|
/**
|
|
202
103
|
* Evaluates every declared side of this language over the run's findings. A side naming a
|
|
203
104
|
* symbol the run enumerates nothing under is `absent`; otherwise it is evaluated once per
|
|
@@ -242,6 +143,6 @@ export function evaluateEdges<F extends Finding>(
|
|
|
242
143
|
return { ...side, state: "dead", finding };
|
|
243
144
|
});
|
|
244
145
|
});
|
|
245
|
-
evaluations.sort((a, b) =>
|
|
146
|
+
evaluations.sort((a, b) => bytewise(a.edge, b.edge) || bytewise(a.side, b.side));
|
|
246
147
|
return { findings: findings.filter((_finding, at) => !pending.has(at)), evaluations };
|
|
247
148
|
}
|
package/src/element-access.ts
CHANGED
|
@@ -47,6 +47,7 @@ import { renderPosition } from "./position.ts";
|
|
|
47
47
|
import { UNANSWERED } from "./query.ts";
|
|
48
48
|
import type { Reference } from "./references.ts";
|
|
49
49
|
import type { ProjectView } from "./session.ts";
|
|
50
|
+
import { parenthesesRemoved } from "./unwrap.ts";
|
|
50
51
|
|
|
51
52
|
type Use = "read" | "write" | "both";
|
|
52
53
|
|
|
@@ -93,13 +94,9 @@ function useOf(node: Node): Use {
|
|
|
93
94
|
return "read";
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
function unwrapped(node: Expression): Expression {
|
|
97
|
-
return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
97
|
/** Whether an index is a literal the checker resolves to a member by its own text. */
|
|
101
98
|
function isLiteralIndex(node: Expression): boolean {
|
|
102
|
-
const at =
|
|
99
|
+
const at = parenthesesRemoved(node);
|
|
103
100
|
return isStringLiteral(at) || isNumericLiteral(at) || isNoSubstitutionTemplateLiteral(at);
|
|
104
101
|
}
|
|
105
102
|
|
|
@@ -293,7 +290,7 @@ export function elementAccessReferences<Brand>(
|
|
|
293
290
|
const parameterOf = new Map<Candidate, { callable: Callable; at: number }>();
|
|
294
291
|
const byCallable = new Map<Identifier, Candidate[]>();
|
|
295
292
|
for (const candidate of candidates) {
|
|
296
|
-
const index =
|
|
293
|
+
const index = parenthesesRemoved(candidate.access.argumentExpression);
|
|
297
294
|
const fn = isIdentifier(index) ? enclosingFunction(candidate.access) : undefined;
|
|
298
295
|
const callable = fn === undefined ? undefined : callableOf(fn);
|
|
299
296
|
if (callable === undefined || !isIdentifier(index)) {
|
|
@@ -353,7 +350,7 @@ export function elementAccessReferences<Brand>(
|
|
|
353
350
|
continue;
|
|
354
351
|
}
|
|
355
352
|
// An object that is a parameter too is read as the argument each call passes there.
|
|
356
|
-
const object =
|
|
353
|
+
const object = parenthesesRemoved(candidate.access.expression);
|
|
357
354
|
const objectAt = isIdentifier(object)
|
|
358
355
|
? deferred.callable.parameters.findIndex(
|
|
359
356
|
(parameter) => isIdentifier(parameter.name) && parameter.name.text === object.text,
|
package/src/entry-point-gaps.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* one at a time: beside a finding, or in a report's declared gaps.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { bytewise } from "./text/compare.ts";
|
|
12
|
+
|
|
11
13
|
/** One declined entry-point convention. */
|
|
12
14
|
export interface DeclinedConvention {
|
|
13
15
|
/** The tool the convention belongs to, by the name of the package or service. */
|
|
@@ -323,16 +325,8 @@ const PARTLY_ENTERED: readonly DeclinedConvention[] = [
|
|
|
323
325
|
},
|
|
324
326
|
];
|
|
325
327
|
|
|
326
|
-
/** Two strings ordered bytewise. */
|
|
327
|
-
function compare(a: string, b: string): number {
|
|
328
|
-
if (a === b) {
|
|
329
|
-
return 0;
|
|
330
|
-
}
|
|
331
|
-
return a < b ? -1 : 1;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
328
|
/** Every declined entry-point convention, ordered by tool and then by convention. */
|
|
335
329
|
export const DECLINED_CONVENTIONS: readonly DeclinedConvention[] = [
|
|
336
330
|
...UNREAD_TOOLS.map((tool) => ({ tool, convention: EVERY_CONVENTION, reason: UNREAD })),
|
|
337
331
|
...PARTLY_ENTERED,
|
|
338
|
-
].sort((a, b) =>
|
|
332
|
+
].sort((a, b) => bytewise(a.tool, b.tool) || bytewise(a.convention, b.convention));
|