@cplieger/deadset-ts 8.0.0-dev.7 → 8.0.0-dev.9
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/node-host.js +21 -3
- package/dist/src/analysis.js +94 -299
- package/dist/src/config.js +6 -83
- package/dist/src/configuration-files.js +2 -9
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/consumers.js +3 -30
- package/dist/src/contract/config.generated.js +544 -0
- package/dist/src/contract/contract.generated.js +20 -10
- package/dist/src/contract/report.generated.js +3 -2
- package/dist/src/conventions.js +3 -8
- package/dist/src/edges.js +36 -112
- package/dist/src/exempt-classes.js +0 -2
- package/dist/src/exempt.js +1 -1
- package/dist/src/failure.js +36 -0
- package/dist/src/findings/completion.js +5 -4
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +1 -1
- package/dist/src/findings/non-code-artifacts.js +2 -2
- package/dist/src/findings/unused-declarations.js +1 -1
- package/dist/src/findings/visibility-narrowing.js +2 -2
- package/dist/src/findings-pass.js +1 -1
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +52 -0
- package/dist/src/fs/manifest.js +7 -0
- package/dist/src/injection-container.js +1 -1
- package/dist/src/json-document.js +194 -67
- package/dist/src/kinds.js +0 -7
- package/dist/src/load/classify.js +236 -0
- package/dist/src/load/diagnostics.js +79 -0
- package/dist/src/load/discover.js +230 -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 +22 -0
- package/dist/src/load/module-lookup.js +37 -0
- package/dist/src/load/plan.js +155 -0
- package/dist/src/{test-globals.js → load/test-globals.js} +51 -56
- package/dist/src/load/test-joins.js +82 -0
- package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +3 -3
- package/dist/src/{workspace-programs.js → load/workspace-programs.js} +37 -40
- package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +21 -59
- package/dist/src/{workspace-run.js → load/workspace-run.js} +4 -3
- package/dist/src/{workspace.js → load/workspace.js} +21 -17
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/print.js +10 -79
- package/dist/src/report-schema.js +3 -224
- package/dist/src/resolve.js +245 -715
- package/dist/src/run.js +59 -72
- package/dist/src/schema.js +320 -344
- package/dist/src/scope.js +37 -113
- package/dist/src/serialization-contract.js +1 -1
- package/dist/src/session.js +22 -28
- package/dist/src/source-files.js +1 -1
- package/dist/src/suppress-file.js +15 -17
- package/dist/src/suppress.js +3 -3
- package/dist/src/template-field.js +4 -4
- package/dist/src/type-errors.js +11 -216
- package/dist/src/usage.js +10 -0
- package/dist/src/verbs/analyze.js +100 -122
- package/dist/src/verbs/describe.js +5 -12
- package/dist/src/verbs/explain.js +3 -3
- package/dist/src/verbs/print-projects.js +2 -2
- package/dist/src/verbs/version.js +12 -2
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +95 -460
- package/src/config.ts +31 -298
- package/src/configuration-files.ts +8 -10
- package/src/configured-declarations.ts +18 -23
- package/src/consumers.ts +6 -57
- package/src/contract/config.generated.ts +798 -0
- package/src/contract/contract.generated.ts +20 -10
- package/src/contract/report.generated.ts +3 -2
- package/src/conventions.ts +6 -12
- package/src/edges.ts +39 -124
- package/src/exempt-classes.ts +0 -5
- package/src/exempt.ts +4 -4
- package/src/failure.ts +63 -0
- package/src/findings/completion.ts +5 -6
- package/src/findings/emitter.ts +2 -2
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +1 -1
- package/src/findings/non-code-artifacts.ts +4 -4
- package/src/findings/unused-declarations.ts +1 -1
- package/src/findings/visibility-narrowing.ts +2 -2
- package/src/findings-pass.ts +3 -3
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +70 -0
- package/src/fs/manifest.ts +8 -0
- package/src/host.ts +12 -5
- package/src/injection-container.ts +1 -1
- package/src/json-document.ts +236 -76
- package/src/kinds.ts +0 -8
- package/src/load/classify.ts +352 -0
- package/src/load/diagnostics.ts +141 -0
- package/src/load/discover.ts +378 -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 +61 -0
- package/src/load/module-lookup.ts +50 -0
- package/src/load/plan.ts +258 -0
- package/src/{test-globals.ts → load/test-globals.ts} +59 -65
- package/src/load/test-joins.ts +98 -0
- package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +4 -4
- package/src/{workspace-programs.ts → load/workspace-programs.ts} +59 -53
- package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +31 -78
- package/src/{workspace-run.ts → load/workspace-run.ts} +14 -11
- package/src/{workspace.ts → load/workspace.ts} +28 -21
- package/src/outside-tests.ts +1 -1
- package/src/partial.ts +1 -1
- package/src/print.ts +11 -92
- package/src/report-schema.ts +3 -299
- package/src/resolve.ts +281 -1105
- package/src/run.ts +68 -80
- package/src/schema.ts +391 -389
- package/src/scope.ts +45 -131
- package/src/serialization-contract.ts +1 -1
- package/src/session.ts +52 -53
- package/src/source-files.ts +1 -1
- package/src/suppress-file.ts +15 -20
- package/src/suppress.ts +3 -3
- package/src/template-field.ts +10 -7
- package/src/type-errors.ts +18 -288
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +139 -157
- package/src/verbs/describe.ts +4 -11
- package/src/verbs/explain.ts +3 -7
- package/src/verbs/print-projects.ts +2 -2
- package/src/verbs/verb.ts +2 -2
- package/src/verbs/version.ts +13 -2
- package/dist/src/base-url.js +0 -18
- package/dist/src/discover.js +0 -374
- package/dist/src/setup-failure.js +0 -19
- package/dist/src/test-joins.js +0 -101
- package/src/base-url.ts +0 -24
- package/src/discover.ts +0 -548
- package/src/setup-failure.ts +0 -37
- package/src/test-joins.ts +0 -118
|
@@ -1135,14 +1135,24 @@ export const EVIDENCE = [
|
|
|
1135
1135
|
},
|
|
1136
1136
|
] as const;
|
|
1137
1137
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
{ class: "incomplete-module-sum", languages: ["go"] },
|
|
1141
|
-
{ class: "missing-consumer", languages: ["go", "ts"] },
|
|
1142
|
-
{ class: "workspace-member-without-source", languages: ["ts"] },
|
|
1143
|
-
{ class: "convention-not-literal", languages: ["ts"] },
|
|
1144
|
-
{ class: "no-program", languages: ["go", "ts"] },
|
|
1145
|
-
] as const;
|
|
1138
|
+
/** Each group of failure classes, and the text opening the line that names one of its failures. */
|
|
1139
|
+
export const FAILURE_PREFIXES = { setup: "setup failure: ", run: "run failure: " } as const;
|
|
1146
1140
|
|
|
1147
|
-
/**
|
|
1148
|
-
export const
|
|
1141
|
+
/** Each failure class the Contract names, and its group. */
|
|
1142
|
+
export const FAILURE_CLASSES = {
|
|
1143
|
+
"missing-module": "setup",
|
|
1144
|
+
"incomplete-module-sum": "setup",
|
|
1145
|
+
"missing-consumer": "setup",
|
|
1146
|
+
"workspace-member-without-source": "setup",
|
|
1147
|
+
"convention-not-literal": "setup",
|
|
1148
|
+
"no-program": "setup",
|
|
1149
|
+
"unreadable-document": "setup",
|
|
1150
|
+
"unwritten-output": "run",
|
|
1151
|
+
"unreportable-path": "run",
|
|
1152
|
+
"memory-exhausted": "run",
|
|
1153
|
+
"missing-analyzer": "run",
|
|
1154
|
+
"unadmitted-analyzer": "run",
|
|
1155
|
+
"unresolved-edge": "run",
|
|
1156
|
+
"interrupted-run": "run",
|
|
1157
|
+
"product-fault": "run",
|
|
1158
|
+
} as const;
|
|
@@ -589,7 +589,7 @@ export const REPORT_SCHEMAS = {
|
|
|
589
589
|
additionalProperties: false,
|
|
590
590
|
required: ["edge", "side", "symbol", "state"],
|
|
591
591
|
properties: {
|
|
592
|
-
edge: {
|
|
592
|
+
edge: { $ref: "finding.schema.json#/$defs/edge_identifier" },
|
|
593
593
|
side: { type: "string", enum: ["provides", "used_by"] },
|
|
594
594
|
symbol: { type: "string" },
|
|
595
595
|
state: { type: "string", enum: ["live", "dead", "absent"] },
|
|
@@ -969,7 +969,7 @@ export const REPORT_SCHEMAS = {
|
|
|
969
969
|
uniqueItems: true,
|
|
970
970
|
items: { type: "string", minLength: 1 },
|
|
971
971
|
},
|
|
972
|
-
edge: {
|
|
972
|
+
edge: { $ref: "#/$defs/edge_identifier" },
|
|
973
973
|
sides: {
|
|
974
974
|
type: "array",
|
|
975
975
|
minItems: 1,
|
|
@@ -1125,6 +1125,7 @@ export const REPORT_SCHEMAS = {
|
|
|
1125
1125
|
},
|
|
1126
1126
|
],
|
|
1127
1127
|
$defs: {
|
|
1128
|
+
edge_identifier: { type: "string", pattern: "^[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*$" },
|
|
1128
1129
|
position: {
|
|
1129
1130
|
type: "object",
|
|
1130
1131
|
additionalProperties: false,
|
package/src/conventions.ts
CHANGED
|
@@ -61,14 +61,14 @@ import {
|
|
|
61
61
|
relativePath,
|
|
62
62
|
resolvePath,
|
|
63
63
|
} from "./paths.ts";
|
|
64
|
-
import type {
|
|
64
|
+
import type { Failure } from "./failure.ts";
|
|
65
65
|
import type { SourceFiles } from "./source-files.ts";
|
|
66
66
|
import { satisfies } from "./version-range.ts";
|
|
67
67
|
import { bytewise } from "./text/compare.ts";
|
|
68
68
|
import { isRecord } from "./json-document.ts";
|
|
69
69
|
import { unwrapped } from "./unwrap.ts";
|
|
70
70
|
import { filesIn } from "./fs/tree.ts";
|
|
71
|
-
import { INSTALLED_PACKAGES, MANIFEST, readJSON } from "./fs/manifest.ts";
|
|
71
|
+
import { DEPENDENCY_SECTIONS, INSTALLED_PACKAGES, MANIFEST, readJSON } from "./fs/manifest.ts";
|
|
72
72
|
import { globExpression } from "./text/glob.ts";
|
|
73
73
|
import { yamlDocument } from "./text/yaml.ts";
|
|
74
74
|
|
|
@@ -115,7 +115,7 @@ interface GeneratedDirectory {
|
|
|
115
115
|
/** A setup failure a row met, at the directory of the manifest that declared it. */
|
|
116
116
|
interface ConventionFailure {
|
|
117
117
|
readonly dir: string;
|
|
118
|
-
readonly failure:
|
|
118
|
+
readonly failure: Failure;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
/** What the rows decided for one run. */
|
|
@@ -169,12 +169,6 @@ const JSON_RC = /^\.[\w-]+rc$/u;
|
|
|
169
169
|
/** The sections an installed package is required by, so its absence fails the run. */
|
|
170
170
|
const REQUIRED_SECTIONS = ["dependencies", "devDependencies"] as const;
|
|
171
171
|
|
|
172
|
-
const DEPENDENCY_SECTIONS = [
|
|
173
|
-
...REQUIRED_SECTIONS,
|
|
174
|
-
"peerDependencies",
|
|
175
|
-
"optionalDependencies",
|
|
176
|
-
] as const;
|
|
177
|
-
|
|
178
172
|
function declares(
|
|
179
173
|
manifest: unknown,
|
|
180
174
|
name: string,
|
|
@@ -1094,7 +1088,7 @@ export function readConventions(
|
|
|
1094
1088
|
failures.push({
|
|
1095
1089
|
dir: manifest.dir,
|
|
1096
1090
|
failure: {
|
|
1097
|
-
|
|
1091
|
+
failureClass: "missing-module",
|
|
1098
1092
|
detail:
|
|
1099
1093
|
`${manifest.path} declares ${row.package}, which no node_modules at or ` +
|
|
1100
1094
|
`above its directory holds: run the package manager's install`,
|
|
@@ -1120,7 +1114,7 @@ export function readConventions(
|
|
|
1120
1114
|
failures.push({
|
|
1121
1115
|
dir: manifest.dir,
|
|
1122
1116
|
failure: {
|
|
1123
|
-
|
|
1117
|
+
failureClass: "convention-not-literal",
|
|
1124
1118
|
detail: where === "." ? failed : `${where}/${failed}`,
|
|
1125
1119
|
},
|
|
1126
1120
|
});
|
|
@@ -1241,7 +1235,7 @@ export function conventionFailuresFor(
|
|
|
1241
1235
|
conventions: Conventions,
|
|
1242
1236
|
configFile: string,
|
|
1243
1237
|
ownFiles: readonly string[],
|
|
1244
|
-
): readonly
|
|
1238
|
+
): readonly Failure[] {
|
|
1245
1239
|
return conventions.failures
|
|
1246
1240
|
.filter(
|
|
1247
1241
|
({ dir }) =>
|
package/src/edges.ts
CHANGED
|
@@ -5,17 +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 {
|
|
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";
|
|
17
18
|
import { bytewise } from "./text/compare.ts";
|
|
18
|
-
import {
|
|
19
|
+
import { UsageError } from "./usage.ts";
|
|
19
20
|
|
|
20
21
|
/** The name and the location the Contract fixes for the edges document. */
|
|
21
22
|
export const EDGES_FILE = "deadset-edges.json";
|
|
@@ -23,147 +24,61 @@ export const EDGES_FILE = "deadset-edges.json";
|
|
|
23
24
|
/** The language prefix of every reference this analyzer evaluates. */
|
|
24
25
|
const OWN_PREFIX = "ts://";
|
|
25
26
|
|
|
26
|
-
/** The closed key list of the document and of one edge. */
|
|
27
|
-
const DOCUMENT_KEYS: ReadonlySet<string> = new Set(["description", "edges"]);
|
|
28
|
-
const EDGE_KEYS: ReadonlySet<string> = new Set(["id", "because", "provides", "used_by"]);
|
|
29
|
-
|
|
30
|
-
/** An edge identifier: solidus-separated segments of ASCII letters, digits, `_`, `.` and `-`. */
|
|
31
|
-
const ID_FORM = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/u;
|
|
32
|
-
|
|
33
|
-
/** A reference naming one symbol of some language: `<language>://<scope>#<fragment>`. */
|
|
34
|
-
const REFERENCE_FORM = /^[a-z][a-z0-9]*:\/\/[^ \t\r\n#]+#[^ \t\r\n]*$/u;
|
|
35
|
-
|
|
36
|
-
const DOCUMENT_WANT = "an object carrying the edges array, and an optional description";
|
|
37
|
-
const EDGE_WANT = "an edge naming id, provides and used_by, each a string, and an optional because";
|
|
38
|
-
const ID_WANT =
|
|
39
|
-
"one or more solidus-separated segments of ASCII letters, digits, underscore, full stop and hyphen";
|
|
40
|
-
const REFERENCE_WANT =
|
|
41
|
-
"a stable symbol reference naming one symbol exactly, <language>://<scope>#<fragment>";
|
|
42
|
-
|
|
43
27
|
/** An edges document the Contract refuses: a malformed input, answered with the usage code. */
|
|
44
|
-
class EdgesError extends
|
|
45
|
-
constructor(at: string,
|
|
46
|
-
super(
|
|
28
|
+
class EdgesError extends UsageError {
|
|
29
|
+
constructor(at: string, reason: string) {
|
|
30
|
+
super(`${at}: ${reason}`);
|
|
47
31
|
this.name = "EdgesError";
|
|
48
32
|
}
|
|
49
33
|
}
|
|
50
34
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
* would name a set of symbols rather than one.
|
|
54
|
-
*/
|
|
55
|
-
function carriesWildcard(ref: string): boolean {
|
|
56
|
-
let quoted = false;
|
|
57
|
-
let computed = false;
|
|
58
|
-
let escaped = false;
|
|
59
|
-
for (const char of ref) {
|
|
60
|
-
if (escaped) {
|
|
61
|
-
escaped = false;
|
|
62
|
-
} else if (quoted && char === "\\") {
|
|
63
|
-
escaped = true;
|
|
64
|
-
} else if (quoted) {
|
|
65
|
-
quoted = char !== "'";
|
|
66
|
-
} else if (computed) {
|
|
67
|
-
computed = char !== "]";
|
|
68
|
-
} else if (char === "'") {
|
|
69
|
-
quoted = true;
|
|
70
|
-
} else if (char === "[") {
|
|
71
|
-
computed = true;
|
|
72
|
-
} else if (char === "*") {
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** One edge, checked: the closed key list, the value types and the value forms. */
|
|
80
|
-
function readEdge(value: unknown, at: string): { id: string; provides: string; usedBy: string } {
|
|
81
|
-
const refuse = (text: string, want: string): never => {
|
|
82
|
-
throw new EdgesError(at, text, want);
|
|
83
|
-
};
|
|
84
|
-
if (!isRecord(value)) {
|
|
85
|
-
return refuse(JSON.stringify(value), EDGE_WANT);
|
|
86
|
-
}
|
|
87
|
-
for (const [key, held] of Object.entries(value)) {
|
|
88
|
-
if (!EDGE_KEYS.has(key) || typeof held !== "string") {
|
|
89
|
-
refuse(`the member ${JSON.stringify(key)} holding ${JSON.stringify(held)}`, EDGE_WANT);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
const edge = value as Readonly<Record<string, string | undefined>>;
|
|
93
|
-
const id = edge["id"] ?? refuse("an edge naming no id", EDGE_WANT);
|
|
94
|
-
const provides = edge["provides"] ?? refuse("an edge naming no provides side", EDGE_WANT);
|
|
95
|
-
const usedBy = edge["used_by"] ?? refuse("an edge naming no used_by side", EDGE_WANT);
|
|
96
|
-
if (!ID_FORM.test(id)) {
|
|
97
|
-
refuse(JSON.stringify(id), ID_WANT);
|
|
98
|
-
}
|
|
99
|
-
for (const ref of [provides, usedBy]) {
|
|
100
|
-
if (!REFERENCE_FORM.test(ref) || carriesWildcard(ref)) {
|
|
101
|
-
refuse(JSON.stringify(ref), REFERENCE_WANT);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return { id, provides, usedBy };
|
|
105
|
-
}
|
|
35
|
+
/** The sides of one edge, in the order a reader yields them. */
|
|
36
|
+
const SIDE_NAMES: readonly EdgeSideName[] = ["provides", "used_by"];
|
|
106
37
|
|
|
107
38
|
/**
|
|
108
39
|
* Every side of a declared edge that names a symbol of this language, in document order,
|
|
109
40
|
* `provides` before `used_by` within an edge. An absent document declares no edge. The
|
|
110
|
-
* document is strict JSON
|
|
111
|
-
* {@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.
|
|
112
43
|
*/
|
|
113
44
|
export function readEdgeSides(host: Host, targetRoot: string): readonly EdgeSide[] {
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
45
|
+
const text = readTargetDocument(host, targetRoot, EDGES_FILE);
|
|
46
|
+
if (text === undefined) {
|
|
116
47
|
return [];
|
|
117
48
|
}
|
|
118
|
-
|
|
119
|
-
let parsed: unknown;
|
|
49
|
+
let read;
|
|
120
50
|
try {
|
|
121
|
-
|
|
51
|
+
read = readStrict(text, "edges");
|
|
122
52
|
} catch (error: unknown) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
throw new EdgesError(EDGES_FILE,
|
|
127
|
-
}
|
|
128
|
-
const walk = walkDocument(text, "edges");
|
|
129
|
-
if (walk.twice !== undefined) {
|
|
130
|
-
throw new EdgesError(
|
|
131
|
-
positionKey(positionAt(text, EDGES_FILE, walk.twice.offset)),
|
|
132
|
-
`the member ${JSON.stringify(walk.twice.name)} is written twice, so neither value is chosen`,
|
|
133
|
-
DOCUMENT_WANT,
|
|
134
|
-
);
|
|
53
|
+
if (!(error instanceof StrictJSONError)) {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
throw new EdgesError(positionKey(positionAt(text, EDGES_FILE, error.offset)), error.message);
|
|
135
57
|
}
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
throw new EdgesError(
|
|
145
|
-
EDGES_FILE,
|
|
146
|
-
unknown === undefined ? EDGES_FILE : `the member ${JSON.stringify(unknown)}`,
|
|
147
|
-
DOCUMENT_WANT,
|
|
148
|
-
);
|
|
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}`);
|
|
149
67
|
}
|
|
68
|
+
const edges = (
|
|
69
|
+
value as { readonly edges: Readonly<Record<string, Record<EdgeSideName, string>>> }
|
|
70
|
+
).edges;
|
|
150
71
|
|
|
151
72
|
const sides: EdgeSide[] = [];
|
|
152
|
-
(
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
["provides", edge.provides],
|
|
159
|
-
["used_by", edge.usedBy],
|
|
160
|
-
];
|
|
161
|
-
for (const [side, symbol] of named) {
|
|
162
|
-
if (symbol.startsWith(OWN_PREFIX)) {
|
|
163
|
-
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 });
|
|
164
79
|
}
|
|
165
80
|
}
|
|
166
|
-
}
|
|
81
|
+
}
|
|
167
82
|
return sides;
|
|
168
83
|
}
|
|
169
84
|
|
package/src/exempt-classes.ts
CHANGED
|
@@ -47,8 +47,3 @@ const VISIBILITY: ReadonlyMap<TSExemptionClass, TypeScriptVisibility> = new Map(
|
|
|
47
47
|
export function typescriptVisibilityOf(exemptionClass: TSExemptionClass): TypeScriptVisibility {
|
|
48
48
|
return VISIBILITY.get(exemptionClass) ?? { private: false, privateName: false };
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
/** Every class of the vocabulary, whichever language it runs on, in the vocabulary's order. */
|
|
52
|
-
export const EXEMPTION_CLASS_NAMES: readonly ExemptionClass[] = EXEMPTION_CLASSES.map(
|
|
53
|
-
(row) => row.class,
|
|
54
|
-
);
|
package/src/exempt.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { EXEMPTION_CLASSES } from "./contract/contract.generated.ts";
|
|
11
|
-
import type {
|
|
11
|
+
import type { ResolvedConfig } from "./config.ts";
|
|
12
12
|
import {
|
|
13
13
|
TS_EXEMPTION_CLASSES,
|
|
14
14
|
typescriptVisibilityOf,
|
|
@@ -48,7 +48,7 @@ export interface DetectorInput<Brand> {
|
|
|
48
48
|
/** The configured templates, read once for the run. */
|
|
49
49
|
readonly templates: Templates;
|
|
50
50
|
/** The `ts` section, whose declarations the configured classes read. */
|
|
51
|
-
readonly ts:
|
|
51
|
+
readonly ts: ResolvedConfig["ts"];
|
|
52
52
|
/** The absolute path of each declared consumer, whose modules are part of the analysis. */
|
|
53
53
|
readonly consumers: readonly string[];
|
|
54
54
|
/** The project's generated files, by path below the target root, each with the detail its retentions name. */
|
|
@@ -75,8 +75,8 @@ interface Holding {
|
|
|
75
75
|
* The classes `exemptions.disabled` switches off. A class that runs on another language
|
|
76
76
|
* only switches nothing off here, so one configuration serves every analyzer.
|
|
77
77
|
*/
|
|
78
|
-
export function disabledClasses(config:
|
|
79
|
-
return new Set(config.
|
|
78
|
+
export function disabledClasses(config: ResolvedConfig): ReadonlySet<ExemptionClass> {
|
|
79
|
+
return new Set(config.exemptions.disabled);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
package/src/failure.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { FAILURE_CLASSES, FAILURE_PREFIXES } from "./contract/contract.generated.ts";
|
|
2
|
+
import type { LocatedDiagnostic } from "./load/diagnostics.ts";
|
|
3
|
+
import { PRODUCT } from "./product.ts";
|
|
4
|
+
|
|
5
|
+
/** A class of failure the Contract's exit-code table names, of either group. */
|
|
6
|
+
type FailureClass = keyof typeof FAILURE_CLASSES;
|
|
7
|
+
|
|
8
|
+
/** One cause the run ends with the failure code for. */
|
|
9
|
+
export interface Failure {
|
|
10
|
+
readonly failureClass: FailureClass;
|
|
11
|
+
/** What fails and the fix, on one line. */
|
|
12
|
+
readonly detail: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The failure as a class line of the error stream, opened by its group's prefix. */
|
|
16
|
+
export function failureLine(failure: Failure): string {
|
|
17
|
+
return `${FAILURE_PREFIXES[FAILURE_CLASSES[failure.failureClass]]}${failure.failureClass}: ${failure.detail}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The lines of the error stream that name the failures: the first one's class line, the run's
|
|
22
|
+
* only one, then each further failure as its class and detail with no group prefix.
|
|
23
|
+
*/
|
|
24
|
+
export function failureLines([first, ...further]: readonly [Failure, ...Failure[]]): string[] {
|
|
25
|
+
return [failureLine(first), ...further.map((one) => `${one.failureClass}: ${one.detail}`)];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A run the analysis cannot answer. It ends the run with the failure code and no finding
|
|
30
|
+
* list, printing the compiler's located errors and then the failures' lines.
|
|
31
|
+
*/
|
|
32
|
+
export class FailureExit extends Error {
|
|
33
|
+
readonly failures: readonly [Failure, ...Failure[]];
|
|
34
|
+
/** The compiler's errors behind the failures, in the order it reported them. */
|
|
35
|
+
readonly diagnostics: readonly LocatedDiagnostic[];
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
failures: readonly [Failure, ...Failure[]],
|
|
39
|
+
diagnostics: readonly LocatedDiagnostic[] = [],
|
|
40
|
+
) {
|
|
41
|
+
super(failureLines(failures).join("\n"));
|
|
42
|
+
this.name = "FailureExit";
|
|
43
|
+
this.failures = failures;
|
|
44
|
+
this.diagnostics = diagnostics;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A failure exit of one failure. */
|
|
49
|
+
export function failureExit(
|
|
50
|
+
failureClass: FailureClass,
|
|
51
|
+
detail: string,
|
|
52
|
+
diagnostics: readonly LocatedDiagnostic[] = [],
|
|
53
|
+
): FailureExit {
|
|
54
|
+
return new FailureExit([{ failureClass, detail }], diagnostics);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A fault of this analyzer itself, `fault` saying what it cannot do. */
|
|
58
|
+
export function productFault(fault: string): FailureExit {
|
|
59
|
+
return failureExit(
|
|
60
|
+
"product-fault",
|
|
61
|
+
`${PRODUCT} ${fault}: reinstall ${PRODUCT}, and report the fault to its maintainers if a reinstalled ${PRODUCT} fails the same way`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -65,10 +65,9 @@ function everyDeclaredLoaded(boundary: Boundary): boolean {
|
|
|
65
65
|
* naming its family, else the code's default.
|
|
66
66
|
*/
|
|
67
67
|
function severityOf(input: EmitterInput, code: string, fallback: Severity): Severity {
|
|
68
|
-
const {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
);
|
|
68
|
+
const { severity } = input.config;
|
|
69
|
+
const key = [code, code.slice(0, FAMILY_KEY_LENGTH)].find((one) => Object.hasOwn(severity, one));
|
|
70
|
+
return key === undefined ? fallback : (severity[key] ?? fallback);
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
/**
|
|
@@ -225,7 +224,7 @@ function rootsCeiling(
|
|
|
225
224
|
): Confidence {
|
|
226
225
|
const unreachable = candidate?.relation === "reachability" || finding.unreachableOnly === true;
|
|
227
226
|
const unseen = new Set((input.files.unseenLoads ?? []).map((load) => load.configuration));
|
|
228
|
-
const complete = input.config.ts.
|
|
227
|
+
const complete = input.config.ts.roots_complete && !configurations.some((one) => unseen.has(one));
|
|
229
228
|
return unreachable && !complete ? "probable" : "certain";
|
|
230
229
|
}
|
|
231
230
|
|
|
@@ -585,7 +584,7 @@ export function dialsOf(
|
|
|
585
584
|
input: EmitterInput,
|
|
586
585
|
findings: readonly CompletedFinding[],
|
|
587
586
|
): Dials<CompletedFinding> {
|
|
588
|
-
const minConfidence = input.config.analysis.
|
|
587
|
+
const minConfidence = input.config.analysis.min_confidence;
|
|
589
588
|
const components = new Set(
|
|
590
589
|
findings
|
|
591
590
|
.filter(
|
package/src/findings/emitter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RunSweep } from "../analysis.ts";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ResolvedConfig } from "../config.ts";
|
|
3
3
|
import type { Dependencies } from "../dependencies.ts";
|
|
4
4
|
import type { FileFacts } from "../file-facts.ts";
|
|
5
5
|
import type { Finding } from "../finding.ts";
|
|
@@ -14,7 +14,7 @@ import type { SelfCheckFacts } from "./self-check.ts";
|
|
|
14
14
|
* the facts about the run each emitter reads beside the sweep.
|
|
15
15
|
*/
|
|
16
16
|
export interface EmitterInput {
|
|
17
|
-
readonly config:
|
|
17
|
+
readonly config: ResolvedConfig;
|
|
18
18
|
/** The production sweep a report is built from, beside the matrix it judged. */
|
|
19
19
|
readonly swept: RunSweep;
|
|
20
20
|
/** The declarations carrying the deprecation marker in any configuration of the run. */
|
package/src/findings/emitters.ts
CHANGED
|
@@ -145,7 +145,7 @@ function atPossible(input: EmitterInput): EmitterInput {
|
|
|
145
145
|
const { config } = input;
|
|
146
146
|
return {
|
|
147
147
|
...input,
|
|
148
|
-
config: { ...config, analysis: { ...config.analysis,
|
|
148
|
+
config: { ...config, analysis: { ...config.analysis, min_confidence: "possible" } },
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
|
|
@@ -127,7 +127,7 @@ export const intraFunction: Emitter = (input) => {
|
|
|
127
127
|
}),
|
|
128
128
|
);
|
|
129
129
|
const { declared, loaded } = input.boundary.consumers;
|
|
130
|
-
const closed = input.config.
|
|
130
|
+
const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
|
|
131
131
|
const published = (id: string): boolean => {
|
|
132
132
|
const symbol = byId.get(id);
|
|
133
133
|
return (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CODE } from "../contract/contract.generated.ts";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ResolvedConfig } from "../config.ts";
|
|
3
3
|
import type { Finding } from "../finding.ts";
|
|
4
4
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
5
5
|
|
|
@@ -15,10 +15,10 @@ const FILE_START = 1;
|
|
|
15
15
|
* completeness and listing no project declares it of a matrix the run derived, and a
|
|
16
16
|
* derived matrix is never complete.
|
|
17
17
|
*/
|
|
18
|
-
function completeMatrix(config:
|
|
18
|
+
function completeMatrix(config: ResolvedConfig): boolean {
|
|
19
19
|
return (
|
|
20
|
-
config.analysis.
|
|
21
|
-
config.analysis.configurations.some((entry) =>
|
|
20
|
+
config.analysis.matrix.complete &&
|
|
21
|
+
config.analysis.configurations.some((entry) => "project" in entry)
|
|
22
22
|
);
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -218,7 +218,7 @@ export const unusedDeclarations: Emitter = (input) => [
|
|
|
218
218
|
*/
|
|
219
219
|
function unusedImports(input: EmitterInput): Finding[] {
|
|
220
220
|
const generated =
|
|
221
|
-
input.config.analysis.
|
|
221
|
+
input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
|
|
222
222
|
return input.intraFunction.imports.flatMap((binding) =>
|
|
223
223
|
binding.used || generated?.has(binding.position.path) === true
|
|
224
224
|
? []
|
|
@@ -266,13 +266,13 @@ function unreachable(
|
|
|
266
266
|
export function narrowings(input: EmitterInput): readonly Finding[] {
|
|
267
267
|
const facts = factsOf(input.swept.matrix.union);
|
|
268
268
|
const { declared, loaded } = input.boundary.consumers;
|
|
269
|
-
const closed = input.config.
|
|
269
|
+
const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
|
|
270
270
|
const world = { closed, writeOnly: writeOnlyDeclarations(input) };
|
|
271
271
|
const candidates = new Map(
|
|
272
272
|
input.swept.sweep.candidates.map((candidate) => [candidate.id, candidate]),
|
|
273
273
|
);
|
|
274
274
|
const generated =
|
|
275
|
-
input.config.analysis.
|
|
275
|
+
input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
|
|
276
276
|
|
|
277
277
|
const findings: Finding[] = [];
|
|
278
278
|
facts.graph.symbols.forEach((symbol, at) => {
|
package/src/findings-pass.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { EXIT_CLEAN, EXIT_FINDINGS, EXIT_PENDING } from "./contract/contract.generated.ts";
|
|
8
8
|
import type { AnalysisInputs } from "./analysis.ts";
|
|
9
|
-
import { fails, type
|
|
9
|
+
import { fails, type ResolvedConfig } from "./config.ts";
|
|
10
10
|
import type { EdgeEvaluation } from "./edges.ts";
|
|
11
11
|
import type { CompletedFinding, Finding } from "./finding.ts";
|
|
12
12
|
import { decidedFindings, type Withheld } from "./findings/emitters.ts";
|
|
@@ -109,7 +109,7 @@ export function findingsPass(inputs: AnalysisInputs): PassResult {
|
|
|
109
109
|
* suppression is stale or a finding's severity reaches the configured failing severity;
|
|
110
110
|
* clean otherwise.
|
|
111
111
|
*/
|
|
112
|
-
export function verdictOf(result: PassResult, config:
|
|
112
|
+
export function verdictOf(result: PassResult, config: ResolvedConfig): number {
|
|
113
113
|
if (result.totals.pending > 0) {
|
|
114
114
|
return EXIT_PENDING;
|
|
115
115
|
}
|
|
@@ -117,7 +117,7 @@ export function verdictOf(result: PassResult, config: Config): number {
|
|
|
117
117
|
return EXIT_FINDINGS;
|
|
118
118
|
}
|
|
119
119
|
const failing = result.findings.some((finding) =>
|
|
120
|
-
fails(finding.severity, config.reporters.
|
|
120
|
+
fails(finding.severity, config.reporters.fail_on),
|
|
121
121
|
);
|
|
122
122
|
return failing ? EXIT_FINDINGS : EXIT_CLEAN;
|
|
123
123
|
}
|
|
@@ -55,8 +55,8 @@ function resolveContracts<Brand>(
|
|
|
55
55
|
contracts: readonly LifecycleContract[],
|
|
56
56
|
): readonly ResolvedContract[] {
|
|
57
57
|
const entries: DeclarationEntry[] = contracts.flatMap((contract) => [
|
|
58
|
-
...contract.components,
|
|
59
|
-
...contract.bases,
|
|
58
|
+
...(contract.components ?? []),
|
|
59
|
+
...(contract.bases ?? []),
|
|
60
60
|
]);
|
|
61
61
|
const resolved = resolveEntries(project, held, entries);
|
|
62
62
|
let next = 0;
|
|
@@ -67,8 +67,8 @@ function resolveContracts<Brand>(
|
|
|
67
67
|
};
|
|
68
68
|
return contracts.map((contract) => ({
|
|
69
69
|
contract,
|
|
70
|
-
components: take(contract.components
|
|
71
|
-
bases: take(contract.bases
|
|
70
|
+
components: take(contract.components?.length ?? 0),
|
|
71
|
+
bases: take(contract.bases?.length ?? 0),
|
|
72
72
|
}));
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -243,11 +243,11 @@ function byBases<Brand>(
|
|
|
243
243
|
*/
|
|
244
244
|
export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly Evidence[] {
|
|
245
245
|
const { project, held, targetRoot } = input;
|
|
246
|
-
if (input.ts.
|
|
246
|
+
if (input.ts.lifecycle_contracts.length === 0) {
|
|
247
247
|
return [];
|
|
248
248
|
}
|
|
249
249
|
const chains = aliasChains(project, held);
|
|
250
|
-
const contracts = resolveContracts(project, held, input.ts.
|
|
250
|
+
const contracts = resolveContracts(project, held, input.ts.lifecycle_contracts);
|
|
251
251
|
const components = [
|
|
252
252
|
...byComponents(project, held, chains, contracts),
|
|
253
253
|
...byBases(project, held, chains, contracts),
|
|
@@ -256,7 +256,7 @@ export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly
|
|
|
256
256
|
return [];
|
|
257
257
|
}
|
|
258
258
|
const ids = new Set(components.map((one) => one.id));
|
|
259
|
-
const sides: readonly ClassSide[] = input.ts.
|
|
259
|
+
const sides: readonly ClassSide[] = input.ts.lifecycle_contracts.some((contract) =>
|
|
260
260
|
contract.members.some((name) => name.endsWith(":static")),
|
|
261
261
|
)
|
|
262
262
|
? ["instance", "static"]
|