@cplieger/deadset-ts 8.0.0-dev.6 → 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 +25 -43
- 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 +2 -1
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/contract/contract.generated.js +13 -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 +2 -4
- package/dist/src/exempt.js +6 -22
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/reads-and-writes.js +2 -1
- package/dist/src/findings/self-check.js +3 -2
- package/dist/src/findings-pass.js +6 -11
- 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/intra-function-parts.js +10 -13
- 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/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 +15 -16
- package/dist/src/report.js +22 -31
- package/dist/src/reporters/sarif.js +4 -2
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +11 -20
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +8 -7
- 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/suppress-file.js +8 -10
- 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 +15 -13
- package/dist/src/verbs/describe.js +4 -4
- package/dist/src/verbs/explain.js +5 -3
- package/dist/src/verbs/print-projects.js +2 -1
- package/dist/src/verbs/print-retained.js +2 -1
- package/dist/src/verbs/print-roots.js +2 -1
- package/dist/src/verbs/version.js +2 -1
- 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 +1 -1
- package/src/analysis.ts +26 -51
- 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 +4 -2
- package/src/configuration-files.ts +27 -170
- package/src/contract/contract.generated.ts +25 -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 +4 -4
- package/src/exempt.ts +5 -27
- package/src/file-facts.ts +6 -24
- package/src/findings/reads-and-writes.ts +2 -1
- package/src/findings/self-check.ts +3 -2
- package/src/findings-pass.ts +6 -12
- 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/intra-function-parts.ts +10 -14
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +5 -0
- package/src/keyof-comparisons.ts +3 -7
- 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 +15 -17
- package/src/report.ts +24 -34
- package/src/reporters/sarif.ts +4 -4
- package/src/reporters/template.ts +4 -20
- package/src/resolve.ts +20 -27
- package/src/roots.ts +7 -20
- package/src/run.ts +8 -13
- 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/suppress-file.ts +8 -11
- 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 +22 -21
- package/src/verbs/describe.ts +4 -4
- package/src/verbs/explain.ts +5 -3
- package/src/verbs/print-projects.ts +2 -1
- package/src/verbs/print-retained.ts +2 -1
- package/src/verbs/print-roots.ts +2 -1
- package/src/verbs/version.ts +2 -1
- 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/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
package/src/report-schema.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { REPORT_SCHEMAS, type Report } from "./contract/report.generated.ts";
|
|
10
|
+
import { bytewise } from "./text/compare.ts";
|
|
11
|
+
import { isRecord } from "./json-document.ts";
|
|
10
12
|
|
|
11
13
|
/** One value a schema refuses: its JSON Pointer, the keyword, and why. */
|
|
12
14
|
interface SchemaRefusal {
|
|
@@ -41,10 +43,6 @@ function pattern(source: string): RegExp {
|
|
|
41
43
|
return held;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
function isObject(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
45
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
46
|
function typeOf(value: unknown): string {
|
|
49
47
|
if (value === null) {
|
|
50
48
|
return "null";
|
|
@@ -68,9 +66,9 @@ function canonical(value: unknown): string {
|
|
|
68
66
|
if (Array.isArray(value)) {
|
|
69
67
|
return `[${value.map(canonical).join(",")}]`;
|
|
70
68
|
}
|
|
71
|
-
if (
|
|
69
|
+
if (isRecord(value)) {
|
|
72
70
|
return `{${Object.keys(value)
|
|
73
|
-
.sort()
|
|
71
|
+
.sort(bytewise)
|
|
74
72
|
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
75
73
|
.join(",")}}`;
|
|
76
74
|
}
|
|
@@ -101,11 +99,11 @@ class Checker {
|
|
|
101
99
|
const target = file === "" ? doc : file;
|
|
102
100
|
let schema: unknown = this.documents[target];
|
|
103
101
|
for (const token of pointer.split("/").filter((one) => one !== "")) {
|
|
104
|
-
schema =
|
|
102
|
+
schema = isRecord(schema)
|
|
105
103
|
? schema[token.replaceAll("~1", "/").replaceAll("~0", "~")]
|
|
106
104
|
: undefined;
|
|
107
105
|
}
|
|
108
|
-
if (!
|
|
106
|
+
if (!isRecord(schema)) {
|
|
109
107
|
throw new Error(`report schema: ${ref} names no schema`);
|
|
110
108
|
}
|
|
111
109
|
return [target, schema];
|
|
@@ -141,7 +139,7 @@ class Checker {
|
|
|
141
139
|
this.checkValue(schema, value, refuse);
|
|
142
140
|
if (Array.isArray(value)) {
|
|
143
141
|
this.checkArray(doc, schema, value, at, found, refuse);
|
|
144
|
-
} else if (
|
|
142
|
+
} else if (isRecord(value)) {
|
|
145
143
|
this.checkObject(doc, schema, value, at, found, refuse);
|
|
146
144
|
}
|
|
147
145
|
this.checkApplicators(doc, schema, value, at, found, refuse);
|
|
@@ -188,19 +186,19 @@ class Checker {
|
|
|
188
186
|
found: SchemaRefusal[],
|
|
189
187
|
refuse: (keyword: string, reason: string) => void,
|
|
190
188
|
): void {
|
|
191
|
-
const properties =
|
|
189
|
+
const properties = isRecord(schema["properties"]) ? schema["properties"] : {};
|
|
192
190
|
const additional = schema["additionalProperties"];
|
|
193
|
-
for (const name of Object.keys(value).sort()) {
|
|
191
|
+
for (const name of Object.keys(value).sort(bytewise)) {
|
|
194
192
|
const member = `${at}/${escape(name)}`;
|
|
195
193
|
const declared = properties[name];
|
|
196
|
-
if (
|
|
194
|
+
if (isRecord(declared)) {
|
|
197
195
|
this.check(doc, declared, value[name], member, found);
|
|
198
196
|
} else if (additional === false) {
|
|
199
197
|
refuse(
|
|
200
198
|
"additionalProperties",
|
|
201
199
|
`the member ${JSON.stringify(name)} is not one the schema declares`,
|
|
202
200
|
);
|
|
203
|
-
} else if (
|
|
201
|
+
} else if (isRecord(additional)) {
|
|
204
202
|
this.check(doc, additional, value[name], member, found);
|
|
205
203
|
}
|
|
206
204
|
}
|
|
@@ -227,7 +225,7 @@ class Checker {
|
|
|
227
225
|
refuse: (keyword: string, reason: string) => void,
|
|
228
226
|
): void {
|
|
229
227
|
const items = schema["items"];
|
|
230
|
-
if (
|
|
228
|
+
if (isRecord(items)) {
|
|
231
229
|
value.forEach((item, i) => {
|
|
232
230
|
this.check(doc, items, item, `${at}/${String(i)}`, found);
|
|
233
231
|
});
|
|
@@ -273,12 +271,12 @@ class Checker {
|
|
|
273
271
|
`the value matches ${String(matching("oneOf"))} of the alternatives, want exactly one`,
|
|
274
272
|
);
|
|
275
273
|
}
|
|
276
|
-
if (
|
|
274
|
+
if (isRecord(schema["not"]) && this.matches(doc, schema["not"], value, at)) {
|
|
277
275
|
refuse("not", "the value matches a schema it must not match");
|
|
278
276
|
}
|
|
279
|
-
if (
|
|
277
|
+
if (isRecord(schema["if"])) {
|
|
280
278
|
const branch = this.matches(doc, schema["if"], value, at) ? schema["then"] : schema["else"];
|
|
281
|
-
if (
|
|
279
|
+
if (isRecord(branch)) {
|
|
282
280
|
this.check(doc, branch, value, at, found);
|
|
283
281
|
}
|
|
284
282
|
}
|
package/src/report.ts
CHANGED
|
@@ -19,12 +19,8 @@ import type { AppliedConvention } from "./conventions.ts";
|
|
|
19
19
|
import type { PassResult } from "./findings-pass.ts";
|
|
20
20
|
import type { StaleSuppression } from "./findings/self-check.ts";
|
|
21
21
|
import type { TestFileRule } from "./references.ts";
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export const ANALYZER_NAME = "deadset-ts";
|
|
25
|
-
|
|
26
|
-
/** The language this analyzer claims. */
|
|
27
|
-
export const LANGUAGE = "ts";
|
|
22
|
+
import { bytewise } from "./text/compare.ts";
|
|
23
|
+
import { LANGUAGE, PRODUCT } from "./product.ts";
|
|
28
24
|
|
|
29
25
|
/** One project configuration of the matrix a run analyzed. */
|
|
30
26
|
export interface ReportConfiguration {
|
|
@@ -131,14 +127,6 @@ function componentsOf(
|
|
|
131
127
|
return [...listed.values()];
|
|
132
128
|
}
|
|
133
129
|
|
|
134
|
-
/** Two strings ordered bytewise. */
|
|
135
|
-
function compare(a: string, b: string): number {
|
|
136
|
-
if (a === b) {
|
|
137
|
-
return 0;
|
|
138
|
-
}
|
|
139
|
-
return a < b ? -1 : 1;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
130
|
function wirePosition(position: FindingPosition): wire.Position {
|
|
143
131
|
return {
|
|
144
132
|
path: position.path,
|
|
@@ -257,22 +245,22 @@ function wireStale(stale: StaleSuppression): wire.StaleSuppressionsEntry {
|
|
|
257
245
|
/** The canonical key over two findings: path, line, column, code, then the symbol reference. */
|
|
258
246
|
function canonical(a: wire.Finding, b: wire.Finding): number {
|
|
259
247
|
return (
|
|
260
|
-
|
|
248
|
+
bytewise(a.position.path, b.position.path) ||
|
|
261
249
|
a.position.line - b.position.line ||
|
|
262
250
|
a.position.column - b.position.column ||
|
|
263
|
-
|
|
264
|
-
|
|
251
|
+
bytewise(a.code, b.code) ||
|
|
252
|
+
bytewise(a.symbol.ref, b.symbol.ref)
|
|
265
253
|
);
|
|
266
254
|
}
|
|
267
255
|
|
|
268
256
|
/** The canonical key over two stale suppressions, each component from the record's own member. */
|
|
269
257
|
function canonicalStale(a: wire.StaleSuppressionsEntry, b: wire.StaleSuppressionsEntry): number {
|
|
270
258
|
return (
|
|
271
|
-
|
|
259
|
+
bytewise(a.position.path, b.position.path) ||
|
|
272
260
|
a.position.line - b.position.line ||
|
|
273
261
|
a.position.column - b.position.column ||
|
|
274
|
-
|
|
275
|
-
|
|
262
|
+
bytewise(a.code, b.code) ||
|
|
263
|
+
bytewise(a.symbol, b.symbol)
|
|
276
264
|
);
|
|
277
265
|
}
|
|
278
266
|
|
|
@@ -289,9 +277,9 @@ function wireGap(gap: DeclaredGap): wire.DeclaredGapsEntry {
|
|
|
289
277
|
/** Edge evaluations by edge, then side, then their compact encodings. */
|
|
290
278
|
function byEdge(a: wire.EdgeEvaluationsEntry, b: wire.EdgeEvaluationsEntry): number {
|
|
291
279
|
return (
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
280
|
+
bytewise(a.edge, b.edge) ||
|
|
281
|
+
bytewise(a.side, b.side) ||
|
|
282
|
+
bytewise(JSON.stringify(a), JSON.stringify(b))
|
|
295
283
|
);
|
|
296
284
|
}
|
|
297
285
|
|
|
@@ -346,16 +334,16 @@ export function buildReport(input: ReportInput): wire.Report {
|
|
|
346
334
|
one.finding === undefined ? [] : [[wireFinding(one.finding), one.finding] as const],
|
|
347
335
|
);
|
|
348
336
|
const unavailable = [...new Map(input.unavailable.map((one) => [one.id, one])).values()]
|
|
349
|
-
.sort((a, b) =>
|
|
337
|
+
.sort((a, b) => bytewise(a.id, b.id))
|
|
350
338
|
.map((one) => ({ id: one.id, role: "consumer" as const, reason: one.reason }));
|
|
351
339
|
const loaded = [...input.loaded]
|
|
352
|
-
.sort((a, b) =>
|
|
340
|
+
.sort((a, b) => bytewise(a.id, b.id) || bytewise(a.path, b.path))
|
|
353
341
|
.map((one) => ({ id: one.id, role: "consumer" as const, path: one.path }));
|
|
354
342
|
return {
|
|
355
343
|
schema_version: SCHEMA_VERSION,
|
|
356
344
|
contract_version: input.contractVersion,
|
|
357
345
|
analyzer: {
|
|
358
|
-
name:
|
|
346
|
+
name: PRODUCT,
|
|
359
347
|
version: input.version,
|
|
360
348
|
languages: [LANGUAGE],
|
|
361
349
|
schema_versions_accepted: SCHEMA_VERSIONS,
|
|
@@ -367,10 +355,10 @@ export function buildReport(input: ReportInput): wire.Report {
|
|
|
367
355
|
},
|
|
368
356
|
target: { kind: input.target.kind, root: input.target.root, identity: input.target.identity },
|
|
369
357
|
configurations: [...input.configurations]
|
|
370
|
-
.sort((a, b) =>
|
|
358
|
+
.sort((a, b) => bytewise(a.id, b.id))
|
|
371
359
|
.map((one) => ({ id: one.id, project: one.project })),
|
|
372
360
|
configurations_not_built: [...input.notBuilt]
|
|
373
|
-
.sort((a, b) =>
|
|
361
|
+
.sort((a, b) => bytewise(a.id, b.id))
|
|
374
362
|
.map((one) => ({ id: one.id, project: one.project, error: one.error })),
|
|
375
363
|
consumers: { declared: loaded.length + unavailable.length, loaded, unavailable },
|
|
376
364
|
findings,
|
|
@@ -383,10 +371,10 @@ export function buildReport(input: ReportInput): wire.Report {
|
|
|
383
371
|
// name, so the compact encodings alone order them.
|
|
384
372
|
declared_gaps: input.declaredGaps
|
|
385
373
|
.map(wireGap)
|
|
386
|
-
.sort((a, b) =>
|
|
374
|
+
.sort((a, b) => bytewise(JSON.stringify(a), JSON.stringify(b))),
|
|
387
375
|
excluded_by_cgo: [],
|
|
388
376
|
test_file_rules: [...input.testFileRules]
|
|
389
|
-
.sort((a, b) =>
|
|
377
|
+
.sort((a, b) => bytewise(a.rule, b.rule) || a.matched - b.matched)
|
|
390
378
|
.map((one) => ({ rule: one.rule, matched: one.matched })),
|
|
391
379
|
type_error_skips: [
|
|
392
380
|
...new Map(
|
|
@@ -396,16 +384,18 @@ export function buildReport(input: ReportInput): wire.Report {
|
|
|
396
384
|
}),
|
|
397
385
|
),
|
|
398
386
|
]
|
|
399
|
-
.sort(([a, x], [b, y]) =>
|
|
387
|
+
.sort(([a, x], [b, y]) => bytewise(x.path, y.path) || x.line - y.line || bytewise(a, b))
|
|
400
388
|
.map(([, skip]) => skip),
|
|
401
389
|
notes: [...new Map((input.notes ?? []).map((one) => [JSON.stringify(one), one])).entries()]
|
|
402
|
-
.sort(
|
|
390
|
+
.sort(
|
|
391
|
+
([a, x], [b, y]) => bytewise(x.kind, y.kind) || bytewise(x.path, y.path) || bytewise(a, b),
|
|
392
|
+
)
|
|
403
393
|
.map(([, note]) => note),
|
|
404
394
|
unanswered_questions: [...input.unanswered]
|
|
405
395
|
.sort(
|
|
406
396
|
(a, b) =>
|
|
407
|
-
|
|
408
|
-
|
|
397
|
+
bytewise(a.configuration, b.configuration) ||
|
|
398
|
+
bytewise(JSON.stringify(a), JSON.stringify(b)),
|
|
409
399
|
)
|
|
410
400
|
.map((one) => ({
|
|
411
401
|
configuration: one.configuration,
|
package/src/reporters/sarif.ts
CHANGED
|
@@ -12,6 +12,8 @@ import { lineHashes, symbolFingerprint } from "./fingerprint.ts";
|
|
|
12
12
|
import { RenderError, type RenderOptions } from "./reporter.ts";
|
|
13
13
|
import { utf8Bytes } from "./sha256.ts";
|
|
14
14
|
import { withheldLine } from "./text.ts";
|
|
15
|
+
import { bytewise } from "../text/compare.ts";
|
|
16
|
+
import { message } from "../product.ts";
|
|
15
17
|
|
|
16
18
|
const SCHEMA =
|
|
17
19
|
"https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json";
|
|
@@ -133,9 +135,7 @@ function lineHashCache(read: (path: string) => string): (at: Located) => string
|
|
|
133
135
|
try {
|
|
134
136
|
text = read(at.path);
|
|
135
137
|
} catch (error: unknown) {
|
|
136
|
-
throw new RenderError(
|
|
137
|
-
`read ${at.path} for its line fingerprint: ${error instanceof Error ? error.message : String(error)}`,
|
|
138
|
-
);
|
|
138
|
+
throw new RenderError(`read ${at.path} for its line fingerprint: ${message(error)}`);
|
|
139
139
|
}
|
|
140
140
|
hashes = lineHashes(text);
|
|
141
141
|
held.set(at.path, hashes);
|
|
@@ -292,7 +292,7 @@ export function sarif(report: Report, options: RenderOptions): Iterable<string>
|
|
|
292
292
|
...report.findings.map((found) => findingResult(found, index, hashOf, members)),
|
|
293
293
|
...report.stale_suppressions.map((stale) => staleResult(stale, index, hashOf)),
|
|
294
294
|
];
|
|
295
|
-
const languages = [...report.analyzer.languages].sort().join("+");
|
|
295
|
+
const languages = [...report.analyzer.languages].sort(bytewise).join("+");
|
|
296
296
|
const withheld = withheldLine(report.totals);
|
|
297
297
|
const log = {
|
|
298
298
|
$schema: SCHEMA,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { CHUNK_LENGTH } from "../json-chunks.ts";
|
|
11
11
|
import { RenderError } from "./reporter.ts";
|
|
12
12
|
import { utf8Bytes } from "./sha256.ts";
|
|
13
|
+
import { bytewise } from "../text/compare.ts";
|
|
13
14
|
|
|
14
15
|
/** A template that does not parse, which refuses the invocation before any analysis. */
|
|
15
16
|
export class TemplateError extends Error {
|
|
@@ -618,7 +619,7 @@ function formatted(value: Value, nested: boolean): string {
|
|
|
618
619
|
}
|
|
619
620
|
const object: ValueObject = value;
|
|
620
621
|
const entries = Object.keys(object)
|
|
621
|
-
.sort()
|
|
622
|
+
.sort(bytewise)
|
|
622
623
|
.map((key) => `${key}:${formatted(object[key] ?? null, true)}`);
|
|
623
624
|
return `map[${entries.join(" ")}]`;
|
|
624
625
|
}
|
|
@@ -647,28 +648,11 @@ function compareBasic(name: string, a: Value, b: Value): number {
|
|
|
647
648
|
return Number.NaN;
|
|
648
649
|
}
|
|
649
650
|
if (kind === "string") {
|
|
650
|
-
return
|
|
651
|
+
return bytewise(a as string, b as string);
|
|
651
652
|
}
|
|
652
653
|
return (a as number) < (b as number) ? -1 : 1;
|
|
653
654
|
}
|
|
654
655
|
|
|
655
|
-
/**
|
|
656
|
-
* Two strings ordered by the bytes of their UTF-8 encodings, which is the order of their
|
|
657
|
-
* code points: the order of their UTF-16 code units differs above U+FFFF.
|
|
658
|
-
*/
|
|
659
|
-
function byCodePoints(a: string, b: string): number {
|
|
660
|
-
let at = 0;
|
|
661
|
-
while (at < a.length && at < b.length) {
|
|
662
|
-
const x = a.codePointAt(at) ?? 0;
|
|
663
|
-
const y = b.codePointAt(at) ?? 0;
|
|
664
|
-
if (x !== y) {
|
|
665
|
-
return x < y ? -1 : 1;
|
|
666
|
-
}
|
|
667
|
-
at += x > 0xffff ? 2 : 1;
|
|
668
|
-
}
|
|
669
|
-
return a.length - b.length;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
656
|
/** The escapes `%q` writes for the control characters that have one of their own. */
|
|
673
657
|
const QUOTED_ESCAPES: Readonly<Record<string, string>> = {
|
|
674
658
|
'"': '\\"',
|
|
@@ -1022,7 +1006,7 @@ class Execution {
|
|
|
1022
1006
|
entries = value.map((one, i) => [i, one]);
|
|
1023
1007
|
} else if (isObject(value)) {
|
|
1024
1008
|
entries = Object.keys(value)
|
|
1025
|
-
.sort()
|
|
1009
|
+
.sort(bytewise)
|
|
1026
1010
|
.map((key) => [key, value[key] ?? null]);
|
|
1027
1011
|
} else {
|
|
1028
1012
|
throw new RenderError(`range cannot iterate over a ${kindOf(value)}`);
|
package/src/resolve.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type TemplateDelimiters,
|
|
31
31
|
} from "./config.ts";
|
|
32
32
|
import { CONVENTION_ROWS } from "./convention-rows.ts";
|
|
33
|
+
import { EXEMPTION_CLASS_NAMES, type ExemptionClass } from "./exempt-classes.ts";
|
|
33
34
|
import { FIXED_SEVERITY_CODES, LIVE_CODES } from "./kinds.ts";
|
|
34
35
|
import {
|
|
35
36
|
checkDocument,
|
|
@@ -39,12 +40,14 @@ import {
|
|
|
39
40
|
SEVERITY_SECTION,
|
|
40
41
|
type KeyNode,
|
|
41
42
|
} from "./schema.ts";
|
|
43
|
+
import { bytewise } from "./text/compare.ts";
|
|
44
|
+
import { isRecord } from "./json-document.ts";
|
|
45
|
+
import { message } from "./product.ts";
|
|
42
46
|
|
|
43
47
|
/** Names the command-line flags in a refusal, where a document is named by its path. */
|
|
44
48
|
const FLAGS_LABEL = "the command-line flags";
|
|
45
49
|
|
|
46
50
|
const SEVERITY_KEY = /^DS[0-9]{2}([0-9]{2})?$/u;
|
|
47
|
-
const EXEMPTION_CLASS = /^[a-z][a-z0-9-]*$/u;
|
|
48
51
|
const CONTRACT_VERSION_PATTERN = /^[0-9]+\.[0-9]+\.[0-9]+$/u;
|
|
49
52
|
const ANALYZER_NAME = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/u;
|
|
50
53
|
const PROVIDER_SOURCE = /^(go|npm):[^ \t\r\n]+$/u;
|
|
@@ -98,7 +101,7 @@ interface Doc {
|
|
|
98
101
|
readonly consumersComplete: boolean | undefined;
|
|
99
102
|
readonly rootPatterns: readonly string[] | undefined;
|
|
100
103
|
readonly severity: ReadonlyMap<string, Severity> | undefined;
|
|
101
|
-
readonly exemptionsDisabled: readonly
|
|
104
|
+
readonly exemptionsDisabled: readonly ExemptionClass[] | undefined;
|
|
102
105
|
readonly formats: readonly Format[] | undefined;
|
|
103
106
|
readonly sort: Sort | undefined;
|
|
104
107
|
readonly cascade: Cascade | undefined;
|
|
@@ -134,10 +137,6 @@ function originOf(source: DocSource, path: string): Origin {
|
|
|
134
137
|
return { source: source.kind, label: source.label };
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
138
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
140
|
function member(parent: Record<string, unknown> | undefined, name: string): unknown {
|
|
142
141
|
if (parent === undefined || !Object.hasOwn(parent, name)) {
|
|
143
142
|
return undefined;
|
|
@@ -505,7 +504,7 @@ function fixedByContract(code: string): string[] {
|
|
|
505
504
|
return FIXED_SEVERITY_CODES.filter(
|
|
506
505
|
(candidate) =>
|
|
507
506
|
code === candidate || (code.length === FAMILY_KEY_LENGTH && candidate.startsWith(code)),
|
|
508
|
-
).sort();
|
|
507
|
+
).sort(bytewise);
|
|
509
508
|
}
|
|
510
509
|
|
|
511
510
|
/** The codes a refusal names: one on its own, and several with the last joined by and. */
|
|
@@ -543,7 +542,7 @@ function readSeverity(
|
|
|
543
542
|
return undefined;
|
|
544
543
|
}
|
|
545
544
|
const severity = new Map<string, Severity>();
|
|
546
|
-
for (const code of Object.keys(object).sort()) {
|
|
545
|
+
for (const code of Object.keys(object).sort(bytewise)) {
|
|
547
546
|
const path = `${SEVERITY_SECTION}.${code}`;
|
|
548
547
|
if (!SEVERITY_KEY.test(code)) {
|
|
549
548
|
throw unimplementedSeverityKey(
|
|
@@ -577,26 +576,20 @@ function readSeverity(
|
|
|
577
576
|
return severity;
|
|
578
577
|
}
|
|
579
578
|
|
|
580
|
-
/** The exemption classes named for switching off. */
|
|
579
|
+
/** The exemption classes named for switching off, each one the vocabulary holds. */
|
|
581
580
|
function readExemptions(
|
|
582
581
|
document: Record<string, unknown> | undefined,
|
|
583
582
|
label: string,
|
|
584
|
-
): readonly
|
|
583
|
+
): readonly ExemptionClass[] | undefined {
|
|
585
584
|
const exemptions = section(document, "exemptions", "exemptions", label);
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
"exemptions.disabled",
|
|
595
|
-
`${JSON.stringify(entry)} is not an exemption class name`,
|
|
596
|
-
);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
return disabled;
|
|
585
|
+
return readStrings(
|
|
586
|
+
exemptions,
|
|
587
|
+
"disabled",
|
|
588
|
+
"exemptions.disabled",
|
|
589
|
+
label,
|
|
590
|
+
0,
|
|
591
|
+
EXEMPTION_CLASS_NAMES,
|
|
592
|
+
);
|
|
600
593
|
}
|
|
601
594
|
|
|
602
595
|
/** The members that name a declaration, each belonging to exactly one shape. */
|
|
@@ -905,7 +898,7 @@ function parseDocument(text: string, node: KeyNode, label: string): unknown {
|
|
|
905
898
|
try {
|
|
906
899
|
value = JSON.parse(text);
|
|
907
900
|
} catch (error: unknown) {
|
|
908
|
-
throw malformed(label, "",
|
|
901
|
+
throw malformed(label, "", message(error));
|
|
909
902
|
}
|
|
910
903
|
checkDocument(text, node, label);
|
|
911
904
|
return value;
|
|
@@ -1002,7 +995,7 @@ function settleSeverity(
|
|
|
1002
995
|
}
|
|
1003
996
|
}
|
|
1004
997
|
const severity = new Map<string, Severity>();
|
|
1005
|
-
for (const code of [...codes].sort()) {
|
|
998
|
+
for (const code of [...codes].sort(bytewise)) {
|
|
1006
999
|
const path = `${SEVERITY_SECTION}.${code}`;
|
|
1007
1000
|
for (const source of sources) {
|
|
1008
1001
|
const value = source.doc.severity?.get(code);
|
|
@@ -1044,7 +1037,7 @@ function describeSource(
|
|
|
1044
1037
|
* print a provenance value the closed key list refuses to read back.
|
|
1045
1038
|
*/
|
|
1046
1039
|
function checkLabels(provenance: ReadonlyMap<string, Origin>): void {
|
|
1047
|
-
for (const path of [...provenance.keys()].sort()) {
|
|
1040
|
+
for (const path of [...provenance.keys()].sort(bytewise)) {
|
|
1048
1041
|
const origin = provenance.get(path);
|
|
1049
1042
|
if (origin !== undefined && origin.source !== "default" && origin.label === "") {
|
|
1050
1043
|
throw malformed(
|
package/src/roots.ts
CHANGED
|
@@ -43,17 +43,19 @@ import {
|
|
|
43
43
|
} from "./configuration-files.ts";
|
|
44
44
|
import { conventionEntries, type Conventions } from "./conventions.ts";
|
|
45
45
|
import { entryPoints, type EntryRule } from "./entry-points.ts";
|
|
46
|
-
import { globExpression } from "./glob.ts";
|
|
47
46
|
import type { Host } from "./host.ts";
|
|
48
47
|
import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
49
48
|
import type { Manifest, ManifestEntry } from "./manifest.ts";
|
|
50
|
-
import {
|
|
49
|
+
import { ancestors, dirnamePath, relativePath, resolvePath } from "./paths.ts";
|
|
51
50
|
import { byPosition } from "./position.ts";
|
|
52
51
|
import { UNANSWERED } from "./query.ts";
|
|
53
52
|
import type { ProjectView } from "./session.ts";
|
|
54
53
|
import type { Symbol as TSSymbol, Type } from "@typescript/native/sync";
|
|
55
54
|
import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
|
|
56
55
|
import { typeQueryAliases } from "./type-query-alias.ts";
|
|
56
|
+
import { bytewise } from "./text/compare.ts";
|
|
57
|
+
import { holdsManifest } from "./fs/manifest.ts";
|
|
58
|
+
import { globExpression } from "./text/glob.ts";
|
|
57
59
|
|
|
58
60
|
/** Why one declaration is a root. */
|
|
59
61
|
export type RootKind =
|
|
@@ -197,15 +199,8 @@ const SOURCE_EXTENSION = /\.(?:d\.)?[cm]?[jt]sx?$/u;
|
|
|
197
199
|
|
|
198
200
|
/** Whether no `package.json` sits between one file and the manifest's directory, which holds it. */
|
|
199
201
|
function ofPackage(host: Host, dir: string, file: string): boolean {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
relativePath(dir, at) === undefined ||
|
|
203
|
-
host.kindOf(joinPath(at, "package.json")) === "file"
|
|
204
|
-
) {
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return true;
|
|
202
|
+
const between = ancestors(dirnamePath(file), dir);
|
|
203
|
+
return between.length > 0 && !between.slice(0, -1).some((at) => holdsManifest(host, at));
|
|
209
204
|
}
|
|
210
205
|
|
|
211
206
|
/**
|
|
@@ -331,14 +326,6 @@ export function matchRef(pattern: string, ref: string): boolean {
|
|
|
331
326
|
return matchGlob(Array.from(pattern), Array.from(ref));
|
|
332
327
|
}
|
|
333
328
|
|
|
334
|
-
/** Two strings ordered bytewise, which is the order every set of a run is read in. */
|
|
335
|
-
function compare(a: string, b: string): number {
|
|
336
|
-
if (a === b) {
|
|
337
|
-
return 0;
|
|
338
|
-
}
|
|
339
|
-
return a < b ? -1 : 1;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
329
|
/** The members of one declaration, indexed by the container that holds them. */
|
|
343
330
|
function childrenOf(held: Inventory): ReadonlyMap<string, readonly InventorySymbol[]> {
|
|
344
331
|
const byParent = new Map<string, InventorySymbol[]>();
|
|
@@ -561,7 +548,7 @@ export function roots<Brand>(
|
|
|
561
548
|
const left = sites.get(a.id);
|
|
562
549
|
const right = sites.get(b.id);
|
|
563
550
|
const site = left === undefined || right === undefined ? 0 : byPosition(left, right);
|
|
564
|
-
return site ||
|
|
551
|
+
return site || bytewise(a.kind, b.kind) || bytewise(a.source, b.source);
|
|
565
552
|
});
|
|
566
553
|
|
|
567
554
|
return {
|
package/src/run.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { describeVerb } from "./verbs/describe.ts";
|
|
|
15
15
|
import { EXPLAIN_OPTIONS, explainVerb } from "./verbs/explain.ts";
|
|
16
16
|
import { type Verb, type VerbOptions } from "./verbs/verb.ts";
|
|
17
17
|
import { versionVerb } from "./verbs/version.ts";
|
|
18
|
+
import { diagnose, message, PRODUCT } from "./product.ts";
|
|
18
19
|
|
|
19
20
|
/** One output stream of the command line. `process.stdout` and `process.stderr` satisfy it. */
|
|
20
21
|
export interface Writer {
|
|
@@ -37,7 +38,7 @@ const VERBS: readonly {
|
|
|
37
38
|
{ name: "version", run: versionVerb },
|
|
38
39
|
];
|
|
39
40
|
|
|
40
|
-
const USAGE = `usage:
|
|
41
|
+
const USAGE = `usage: ${PRODUCT} <verb> [options]\nverbs: ${VERBS.map((verb) => verb.name).join(", ")}\n`;
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* The tokens whose presence in an option's name makes that option a request to
|
|
@@ -141,11 +142,7 @@ function readFileOr(host: Host, path: string, orUndefined: boolean): string | un
|
|
|
141
142
|
if (orUndefined) {
|
|
142
143
|
return undefined;
|
|
143
144
|
}
|
|
144
|
-
throw new ConfigError(
|
|
145
|
-
"malformed",
|
|
146
|
-
"",
|
|
147
|
-
`${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
148
|
-
);
|
|
145
|
+
throw new ConfigError("malformed", "", `${path}: ${message(error)}`);
|
|
149
146
|
}
|
|
150
147
|
}
|
|
151
148
|
|
|
@@ -209,9 +206,7 @@ export function run(
|
|
|
209
206
|
): number {
|
|
210
207
|
const editing = sourceEditOption(args);
|
|
211
208
|
if (editing !== undefined) {
|
|
212
|
-
err
|
|
213
|
-
`deadset-ts: ${editing} is not supported: deadset-ts reports and never edits a source file\n`,
|
|
214
|
-
);
|
|
209
|
+
diagnose(err, `${editing} is not supported: ${PRODUCT} reports and never edits a source file`);
|
|
215
210
|
err.write(USAGE);
|
|
216
211
|
return EXIT_USAGE;
|
|
217
212
|
}
|
|
@@ -228,7 +223,7 @@ export function run(
|
|
|
228
223
|
);
|
|
229
224
|
}
|
|
230
225
|
if (name !== undefined) {
|
|
231
|
-
err
|
|
226
|
+
diagnose(err, `unknown verb ${JSON.stringify(name)}`);
|
|
232
227
|
}
|
|
233
228
|
err.write(USAGE);
|
|
234
229
|
return EXIT_USAGE;
|
|
@@ -274,7 +269,7 @@ function invoke(
|
|
|
274
269
|
/** Turns one refusal into the exit code the Contract's table gives it. */
|
|
275
270
|
function refuse(error: unknown, err: Writer): number {
|
|
276
271
|
if (error instanceof ConfigError) {
|
|
277
|
-
err
|
|
272
|
+
diagnose(err, error.message);
|
|
278
273
|
err.write(USAGE);
|
|
279
274
|
return EXIT_USAGE;
|
|
280
275
|
}
|
|
@@ -282,7 +277,7 @@ function refuse(error: unknown, err: Writer): number {
|
|
|
282
277
|
for (const diagnostic of error.diagnostics) {
|
|
283
278
|
err.write(`${renderDiagnostic(diagnostic)}\n`);
|
|
284
279
|
}
|
|
285
|
-
err
|
|
280
|
+
diagnose(err, error.message);
|
|
286
281
|
return EXIT_FAILURE;
|
|
287
282
|
}
|
|
288
283
|
if (error instanceof SetupError) {
|
|
@@ -292,7 +287,7 @@ function refuse(error: unknown, err: Writer): number {
|
|
|
292
287
|
return EXIT_FAILURE;
|
|
293
288
|
}
|
|
294
289
|
if (error instanceof ScopeError) {
|
|
295
|
-
err
|
|
290
|
+
diagnose(err, error.message);
|
|
296
291
|
return EXIT_FAILURE;
|
|
297
292
|
}
|
|
298
293
|
throw error;
|
package/src/schema.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConfigError } from "./config.ts";
|
|
2
|
+
import { bytewise } from "./text/compare.ts";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* How one node of the closed key list is written: `leaf`, a scalar or an array of
|
|
@@ -157,7 +158,7 @@ function collect(node: KeyNode, at: string, keys: string[], settingsOnly: boolea
|
|
|
157
158
|
export function declaredKeys(): string[] {
|
|
158
159
|
const keys: string[] = [];
|
|
159
160
|
collect(SCHEMA_ROOT, "", keys, false);
|
|
160
|
-
return keys.sort();
|
|
161
|
+
return keys.sort(bytewise);
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
/**
|
|
@@ -169,7 +170,7 @@ export function declaredKeys(): string[] {
|
|
|
169
170
|
export function settingPaths(): string[] {
|
|
170
171
|
const paths: string[] = [];
|
|
171
172
|
collect(SCHEMA_ROOT, "", paths, true);
|
|
172
|
-
return paths.sort();
|
|
173
|
+
return paths.sort(bytewise);
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
/**
|
|
@@ -445,7 +446,7 @@ export function checkDocument(text: string, node: KeyNode, label: string): void
|
|
|
445
446
|
*/
|
|
446
447
|
export function nestFlags(flat: Readonly<Record<string, unknown>>, label: string): unknown {
|
|
447
448
|
const nested: Record<string, unknown> = {};
|
|
448
|
-
for (const path of Object.keys(flat).sort()) {
|
|
449
|
+
for (const path of Object.keys(flat).sort(bytewise)) {
|
|
449
450
|
if (!declaresSetting(path)) {
|
|
450
451
|
throw unimplementedKey(label, path);
|
|
451
452
|
}
|
package/src/scope.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { ConfigError } from "./config.ts";
|
|
|
2
2
|
import type { Host } from "./host.ts";
|
|
3
3
|
import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.ts";
|
|
4
4
|
import { checkDocument, type KeyNode } from "./schema.ts";
|
|
5
|
+
import { isRecord } from "./json-document.ts";
|
|
6
|
+
import { message } from "./product.ts";
|
|
5
7
|
|
|
6
8
|
/** The roles a scope document declares for a module. */
|
|
7
9
|
const ROLE_TARGET = "target";
|
|
@@ -60,10 +62,6 @@ const SCOPE_ROOT: KeyNode = {
|
|
|
60
62
|
},
|
|
61
63
|
};
|
|
62
64
|
|
|
63
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
64
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
65
|
/**
|
|
68
66
|
* One string member, or undefined where the document omits it. A member the
|
|
69
67
|
* document writes is a string holding at least one character, so `null` and `""`
|
|
@@ -145,9 +143,7 @@ export function readScope(host: Host, file: string): Scope {
|
|
|
145
143
|
try {
|
|
146
144
|
text = host.readFile(absolute);
|
|
147
145
|
} catch (error: unknown) {
|
|
148
|
-
throw new ScopeError(
|
|
149
|
-
`scope: ${file}: ${error instanceof Error ? error.message : String(error)}`,
|
|
150
|
-
);
|
|
146
|
+
throw new ScopeError(`scope: ${file}: ${message(error)}`);
|
|
151
147
|
}
|
|
152
148
|
if (text.length > MAX_DOCUMENT_LENGTH) {
|
|
153
149
|
throw new ScopeError(`scope: ${file} is longer than ${String(MAX_DOCUMENT_LENGTH)} characters`);
|
|
@@ -156,9 +152,7 @@ export function readScope(host: Host, file: string): Scope {
|
|
|
156
152
|
try {
|
|
157
153
|
value = JSON.parse(text);
|
|
158
154
|
} catch (error: unknown) {
|
|
159
|
-
throw new ScopeError(
|
|
160
|
-
`scope: ${file}: ${error instanceof Error ? error.message : String(error)}`,
|
|
161
|
-
);
|
|
155
|
+
throw new ScopeError(`scope: ${file}: ${message(error)}`);
|
|
162
156
|
}
|
|
163
157
|
if (!isRecord(value)) {
|
|
164
158
|
throw new ScopeError(`scope: ${file} is not one JSON object`);
|