@cplieger/deadset-ts 3.9.1 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/conformance-record.js +26 -0
- package/dist/src/conformance.js +8 -20
- package/dist/src/report.js +14 -1
- package/dist/src/verbs/analyze.js +3 -2
- package/dist/src/verbs/describe.js +8 -12
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/conformance-record.ts +29 -0
- package/src/conformance.ts +21 -23
- package/src/report.ts +25 -3
- package/src/verbs/analyze.ts +3 -2
- package/src/verbs/describe.ts +9 -13
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Written by the conformance run from conformance.json and conformance-results.json.
|
|
2
|
+
// Regenerate it with `UPDATE_GOLDEN=1 npx vitest --run src/conformance.corpus.test.ts`.
|
|
3
|
+
export const RECORD = {
|
|
4
|
+
conformance: {
|
|
5
|
+
corpusVersion: "1.9.0",
|
|
6
|
+
result: "fail",
|
|
7
|
+
digest: "sha256:8c29f19fc189f4f5d9ea210d556b62d12c3701c221eb7a8adde499b7060bec0d",
|
|
8
|
+
},
|
|
9
|
+
gaps: [
|
|
10
|
+
{
|
|
11
|
+
fixture: "private-member-unread",
|
|
12
|
+
capability: "reflective-lookup",
|
|
13
|
+
reason: "The reference pass resolves a string-literal element access to the member it names, so the member is live by that reference and the retained listing names no class for it.",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
fixture: "redundant-export-keyword",
|
|
17
|
+
capability: "DS1104",
|
|
18
|
+
reason: "No consumer is loaded beside the target, so the published surface is unknown and no export is narrowed; review an export only the target uses by hand.",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
fixture: "unused-exported-consumer",
|
|
22
|
+
capability: "DS1001",
|
|
23
|
+
reason: "No consumer is loaded beside the target, so an export nothing in the target references is reported at the possible class rather than the certain one.",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
package/dist/src/conformance.js
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What this analyzer states about its own conformance:
|
|
3
|
-
*
|
|
2
|
+
* What this analyzer states about its own conformance: its recorded result over the
|
|
3
|
+
* conformance corpus and every capability it declines, which `describe` and every report
|
|
4
|
+
* name.
|
|
4
5
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* The conformance run this analyzer records, or undefined where it records none. A
|
|
11
|
-
* description states exactly this, so a reader that requires a pass refuses an analyzer
|
|
12
|
-
* that has not answered the corpus.
|
|
13
|
-
*/
|
|
14
|
-
export const RECORDED_CONFORMANCE = undefined;
|
|
15
|
-
/**
|
|
16
|
-
* The conformance a report states. A report requires the block, so an analyzer that
|
|
17
|
-
* records no run states a failed one over the results document no run wrote, which a merge
|
|
18
|
-
* refuses as it refuses any result other than a pass.
|
|
19
|
-
*/
|
|
20
|
-
export function reportedConformance(recorded) {
|
|
21
|
-
return recorded ?? { corpusVersion: CORPUS_VERSION, result: "fail", digest: EMPTY_DIGEST };
|
|
22
|
-
}
|
|
6
|
+
import { RECORD } from "./conformance-record.js";
|
|
7
|
+
/** The conformance run this analyzer records. */
|
|
8
|
+
export const CONFORMANCE = RECORD.conformance;
|
|
9
|
+
/** Every capability this analyzer declines, in the order `conformance.json` declares them. */
|
|
10
|
+
export const DECLARED_GAPS = RECORD.gaps;
|
package/dist/src/report.js
CHANGED
|
@@ -133,6 +133,15 @@ function canonicalStale(a, b) {
|
|
|
133
133
|
compare(a.code, b.code) ||
|
|
134
134
|
compare(a.symbol, b.symbol));
|
|
135
135
|
}
|
|
136
|
+
/** One declared gap as a report carries it, its members in the schema's order. */
|
|
137
|
+
function wireGap(gap) {
|
|
138
|
+
return {
|
|
139
|
+
fixture: gap.fixture,
|
|
140
|
+
...(gap.symbol === undefined ? {} : { symbol: gap.symbol }),
|
|
141
|
+
capability: gap.capability,
|
|
142
|
+
reason: gap.reason,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
136
145
|
/** Edge evaluations by edge, then side, then their compact encodings. */
|
|
137
146
|
function byEdge(a, b) {
|
|
138
147
|
return (compare(a.edge, b.edge) ||
|
|
@@ -202,7 +211,11 @@ export function buildReport(input) {
|
|
|
202
211
|
findings,
|
|
203
212
|
edge_evaluations: input.result.edgeEvaluations.map(wireEvaluation).sort(byEdge),
|
|
204
213
|
stale_suppressions: input.result.staleSuppressions.map(wireStale).sort(canonicalStale),
|
|
205
|
-
|
|
214
|
+
// Every gap shares the one component of the canonical key a gap supplies, the analyzer's
|
|
215
|
+
// name, so the compact encodings alone order them.
|
|
216
|
+
declared_gaps: input.declaredGaps
|
|
217
|
+
.map(wireGap)
|
|
218
|
+
.sort((a, b) => compare(JSON.stringify(a), JSON.stringify(b))),
|
|
206
219
|
excluded_by_cgo: [],
|
|
207
220
|
test_file_rules: [...input.testFileRules]
|
|
208
221
|
.sort((a, b) => compare(a.rule, b.rule) || a.matched - b.matched)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { runAnalysis } from "../analysis.js";
|
|
2
2
|
import { ConfigError } from "../config.js";
|
|
3
|
-
import {
|
|
3
|
+
import { CONFORMANCE, DECLARED_GAPS } from "../conformance.js";
|
|
4
4
|
import { analyzerProvenance, recordedFindings, verdictOf } from "../findings-pass.js";
|
|
5
5
|
import { packageScope } from "../inventory.js";
|
|
6
6
|
import { normalizePath, relativePath, resolvePath } from "../paths.js";
|
|
@@ -129,7 +129,8 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
|
|
|
129
129
|
const built = buildReport({
|
|
130
130
|
contractVersion: CONTRACT_VERSION,
|
|
131
131
|
version,
|
|
132
|
-
conformance:
|
|
132
|
+
conformance: CONFORMANCE,
|
|
133
|
+
declaredGaps: DECLARED_GAPS,
|
|
133
134
|
target: { kind, root, identity: identityOf(scoped.target, host) },
|
|
134
135
|
configurations: configurationsOf(run.projects, targetRoot),
|
|
135
136
|
unavailable: scoped.consumers.map((consumer) => ({
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CONFORMANCE } from "../conformance.js";
|
|
2
2
|
import { ANALYZER_NAME, LANGUAGE, SCHEMA_VERSION } from "../report.js";
|
|
3
3
|
import { CONTRACT_VERSION } from "../version.js";
|
|
4
4
|
import { EXIT_CLEAN, EXIT_FAILURE, EXIT_USAGE } from "./verb.js";
|
|
5
5
|
/**
|
|
6
6
|
* The document `describe` writes: the analyzer, the Contract version it implements, the
|
|
7
7
|
* report schema versions it reads, the languages it claims, and its recorded result over
|
|
8
|
-
* the conformance corpus
|
|
8
|
+
* the conformance corpus.
|
|
9
9
|
*/
|
|
10
10
|
export function describeDocument(version, recorded) {
|
|
11
11
|
const document = {
|
|
@@ -14,15 +14,11 @@ export function describeDocument(version, recorded) {
|
|
|
14
14
|
contract_version: CONTRACT_VERSION,
|
|
15
15
|
schema_versions_accepted: [SCHEMA_VERSION],
|
|
16
16
|
languages: [LANGUAGE],
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
result: recorded.result,
|
|
23
|
-
digest: recorded.digest,
|
|
24
|
-
},
|
|
25
|
-
}),
|
|
17
|
+
conformance: {
|
|
18
|
+
corpus_version: recorded.corpusVersion,
|
|
19
|
+
result: recorded.result,
|
|
20
|
+
digest: recorded.digest,
|
|
21
|
+
},
|
|
26
22
|
};
|
|
27
23
|
return `${JSON.stringify(document, null, 2)}\n`;
|
|
28
24
|
}
|
|
@@ -40,6 +36,6 @@ export const describeVerb = ({ out, err, host, args }) => {
|
|
|
40
36
|
err.write(`deadset-ts: describe: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
41
37
|
return EXIT_FAILURE;
|
|
42
38
|
}
|
|
43
|
-
out.write(describeDocument(version,
|
|
39
|
+
out.write(describeDocument(version, CONFORMANCE));
|
|
44
40
|
return EXIT_CLEAN;
|
|
45
41
|
};
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Written by the conformance run from conformance.json and conformance-results.json.
|
|
2
|
+
// Regenerate it with `UPDATE_GOLDEN=1 npx vitest --run src/conformance.corpus.test.ts`.
|
|
3
|
+
export const RECORD = {
|
|
4
|
+
conformance: {
|
|
5
|
+
corpusVersion: "1.9.0",
|
|
6
|
+
result: "fail",
|
|
7
|
+
digest: "sha256:8c29f19fc189f4f5d9ea210d556b62d12c3701c221eb7a8adde499b7060bec0d",
|
|
8
|
+
},
|
|
9
|
+
gaps: [
|
|
10
|
+
{
|
|
11
|
+
fixture: "private-member-unread",
|
|
12
|
+
capability: "reflective-lookup",
|
|
13
|
+
reason:
|
|
14
|
+
"The reference pass resolves a string-literal element access to the member it names, so the member is live by that reference and the retained listing names no class for it.",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
fixture: "redundant-export-keyword",
|
|
18
|
+
capability: "DS1104",
|
|
19
|
+
reason:
|
|
20
|
+
"No consumer is loaded beside the target, so the published surface is unknown and no export is narrowed; review an export only the target uses by hand.",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
fixture: "unused-exported-consumer",
|
|
24
|
+
capability: "DS1001",
|
|
25
|
+
reason:
|
|
26
|
+
"No consumer is loaded beside the target, so an export nothing in the target references is reported at the possible class rather than the certain one.",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
} as const;
|
package/src/conformance.ts
CHANGED
|
@@ -1,34 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What this analyzer states about its own conformance:
|
|
3
|
-
*
|
|
2
|
+
* What this analyzer states about its own conformance: its recorded result over the
|
|
3
|
+
* conformance corpus and every capability it declines, which `describe` and every report
|
|
4
|
+
* name.
|
|
4
5
|
*/
|
|
6
|
+
import { RECORD } from "./conformance-record.ts";
|
|
5
7
|
|
|
6
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* The result one product reached over the conformance corpus. A declared gap never lowers
|
|
10
|
+
* it: the result is `pass` when no fixture fails, gaps or not, and `fail` otherwise.
|
|
11
|
+
*/
|
|
7
12
|
export interface Conformance {
|
|
8
13
|
readonly corpusVersion: string;
|
|
9
14
|
readonly result: "pass" | "fail";
|
|
10
|
-
/** `sha256:` and the lowercase hexadecimal SHA-256 of the results document the
|
|
15
|
+
/** `sha256:` and the lowercase hexadecimal SHA-256 of the results document the run wrote. */
|
|
11
16
|
readonly digest: string;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
19
|
+
/** One capability this analyzer declines, as `conformance.json` declares it. */
|
|
20
|
+
export interface DeclaredGap {
|
|
21
|
+
readonly fixture: string;
|
|
22
|
+
/** The one expectation the gap covers, where it is narrower than the fixture. */
|
|
23
|
+
readonly symbol?: string;
|
|
24
|
+
readonly capability: string;
|
|
25
|
+
readonly reason: string;
|
|
26
|
+
}
|
|
16
27
|
|
|
17
|
-
/** The
|
|
18
|
-
const
|
|
28
|
+
/** The conformance run this analyzer records. */
|
|
29
|
+
export const CONFORMANCE: Conformance = RECORD.conformance;
|
|
19
30
|
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
* description states exactly this, so a reader that requires a pass refuses an analyzer
|
|
23
|
-
* that has not answered the corpus.
|
|
24
|
-
*/
|
|
25
|
-
export const RECORDED_CONFORMANCE: Conformance | undefined = undefined;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The conformance a report states. A report requires the block, so an analyzer that
|
|
29
|
-
* records no run states a failed one over the results document no run wrote, which a merge
|
|
30
|
-
* refuses as it refuses any result other than a pass.
|
|
31
|
-
*/
|
|
32
|
-
export function reportedConformance(recorded: Conformance | undefined): Conformance {
|
|
33
|
-
return recorded ?? { corpusVersion: CORPUS_VERSION, result: "fail", digest: EMPTY_DIGEST };
|
|
34
|
-
}
|
|
31
|
+
/** Every capability this analyzer declines, in the order `conformance.json` declares them. */
|
|
32
|
+
export const DECLARED_GAPS: readonly DeclaredGap[] = RECORD.gaps;
|
package/src/report.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Severity, Sort, TargetKind } from "./config.ts";
|
|
8
|
-
import type { Conformance } from "./conformance.ts";
|
|
8
|
+
import type { Conformance, DeclaredGap } from "./conformance.ts";
|
|
9
9
|
import type { EdgeEvaluation } from "./edges.ts";
|
|
10
10
|
import type {
|
|
11
11
|
CompletedFinding,
|
|
@@ -105,6 +105,13 @@ export interface WireStaleSuppression {
|
|
|
105
105
|
readonly message: string;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
interface WireDeclaredGap {
|
|
109
|
+
readonly fixture: string;
|
|
110
|
+
readonly symbol?: string;
|
|
111
|
+
readonly capability: string;
|
|
112
|
+
readonly reason: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
108
115
|
export interface WireTotals {
|
|
109
116
|
readonly findings: number;
|
|
110
117
|
readonly by_severity: { readonly allow: number; readonly warn: number; readonly deny: number };
|
|
@@ -159,7 +166,7 @@ export interface Report {
|
|
|
159
166
|
readonly findings: readonly WireFinding[];
|
|
160
167
|
readonly edge_evaluations: readonly WireEdgeEvaluation[];
|
|
161
168
|
readonly stale_suppressions: readonly WireStaleSuppression[];
|
|
162
|
-
readonly declared_gaps: readonly
|
|
169
|
+
readonly declared_gaps: readonly WireDeclaredGap[];
|
|
163
170
|
readonly excluded_by_cgo: readonly never[];
|
|
164
171
|
readonly test_file_rules: readonly TestFileRule[];
|
|
165
172
|
readonly totals: WireTotals;
|
|
@@ -170,6 +177,7 @@ export interface ReportInput {
|
|
|
170
177
|
readonly contractVersion: string;
|
|
171
178
|
readonly version: string;
|
|
172
179
|
readonly conformance: Conformance;
|
|
180
|
+
readonly declaredGaps: readonly DeclaredGap[];
|
|
173
181
|
readonly target: { readonly kind: TargetKind; readonly root: string; readonly identity: string };
|
|
174
182
|
readonly configurations: readonly ReportConfiguration[];
|
|
175
183
|
readonly unavailable: readonly UnavailableConsumer[];
|
|
@@ -318,6 +326,16 @@ function canonicalStale(a: WireStaleSuppression, b: WireStaleSuppression): numbe
|
|
|
318
326
|
);
|
|
319
327
|
}
|
|
320
328
|
|
|
329
|
+
/** One declared gap as a report carries it, its members in the schema's order. */
|
|
330
|
+
function wireGap(gap: DeclaredGap): WireDeclaredGap {
|
|
331
|
+
return {
|
|
332
|
+
fixture: gap.fixture,
|
|
333
|
+
...(gap.symbol === undefined ? {} : { symbol: gap.symbol }),
|
|
334
|
+
capability: gap.capability,
|
|
335
|
+
reason: gap.reason,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
321
339
|
/** Edge evaluations by edge, then side, then their compact encodings. */
|
|
322
340
|
function byEdge(a: WireEdgeEvaluation, b: WireEdgeEvaluation): number {
|
|
323
341
|
return (
|
|
@@ -392,7 +410,11 @@ export function buildReport(input: ReportInput): Report {
|
|
|
392
410
|
findings,
|
|
393
411
|
edge_evaluations: input.result.edgeEvaluations.map(wireEvaluation).sort(byEdge),
|
|
394
412
|
stale_suppressions: input.result.staleSuppressions.map(wireStale).sort(canonicalStale),
|
|
395
|
-
|
|
413
|
+
// Every gap shares the one component of the canonical key a gap supplies, the analyzer's
|
|
414
|
+
// name, so the compact encodings alone order them.
|
|
415
|
+
declared_gaps: input.declaredGaps
|
|
416
|
+
.map(wireGap)
|
|
417
|
+
.sort((a, b) => compare(JSON.stringify(a), JSON.stringify(b))),
|
|
396
418
|
excluded_by_cgo: [],
|
|
397
419
|
test_file_rules: [...input.testFileRules]
|
|
398
420
|
.sort((a, b) => compare(a.rule, b.rule) || a.matched - b.matched)
|
package/src/verbs/analyze.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { runAnalysis, type RunProject } from "../analysis.ts";
|
|
2
2
|
import { ConfigError, type Format } from "../config.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { CONFORMANCE, DECLARED_GAPS } from "../conformance.ts";
|
|
4
4
|
import { analyzerProvenance, recordedFindings, verdictOf } from "../findings-pass.ts";
|
|
5
5
|
import { packageScope } from "../inventory.ts";
|
|
6
6
|
import { normalizePath, relativePath, resolvePath } from "../paths.ts";
|
|
@@ -175,7 +175,8 @@ export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option
|
|
|
175
175
|
const built = buildReport({
|
|
176
176
|
contractVersion: CONTRACT_VERSION,
|
|
177
177
|
version,
|
|
178
|
-
conformance:
|
|
178
|
+
conformance: CONFORMANCE,
|
|
179
|
+
declaredGaps: DECLARED_GAPS,
|
|
179
180
|
target: { kind, root, identity: identityOf(scoped.target, host) },
|
|
180
181
|
configurations: configurationsOf(run.projects, targetRoot),
|
|
181
182
|
unavailable: scoped.consumers.map((consumer) => ({
|
package/src/verbs/describe.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CONFORMANCE, type Conformance } from "../conformance.ts";
|
|
2
2
|
import { ANALYZER_NAME, LANGUAGE, SCHEMA_VERSION } from "../report.ts";
|
|
3
3
|
import { CONTRACT_VERSION } from "../version.ts";
|
|
4
4
|
import { EXIT_CLEAN, EXIT_FAILURE, EXIT_USAGE, type Verb } from "./verb.ts";
|
|
@@ -6,24 +6,20 @@ import { EXIT_CLEAN, EXIT_FAILURE, EXIT_USAGE, type Verb } from "./verb.ts";
|
|
|
6
6
|
/**
|
|
7
7
|
* The document `describe` writes: the analyzer, the Contract version it implements, the
|
|
8
8
|
* report schema versions it reads, the languages it claims, and its recorded result over
|
|
9
|
-
* the conformance corpus
|
|
9
|
+
* the conformance corpus.
|
|
10
10
|
*/
|
|
11
|
-
export function describeDocument(version: string, recorded: Conformance
|
|
11
|
+
export function describeDocument(version: string, recorded: Conformance): string {
|
|
12
12
|
const document = {
|
|
13
13
|
name: ANALYZER_NAME,
|
|
14
14
|
version,
|
|
15
15
|
contract_version: CONTRACT_VERSION,
|
|
16
16
|
schema_versions_accepted: [SCHEMA_VERSION],
|
|
17
17
|
languages: [LANGUAGE],
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
result: recorded.result,
|
|
24
|
-
digest: recorded.digest,
|
|
25
|
-
},
|
|
26
|
-
}),
|
|
18
|
+
conformance: {
|
|
19
|
+
corpus_version: recorded.corpusVersion,
|
|
20
|
+
result: recorded.result,
|
|
21
|
+
digest: recorded.digest,
|
|
22
|
+
},
|
|
27
23
|
};
|
|
28
24
|
return `${JSON.stringify(document, null, 2)}\n`;
|
|
29
25
|
}
|
|
@@ -41,6 +37,6 @@ export const describeVerb: Verb = ({ out, err, host, args }) => {
|
|
|
41
37
|
err.write(`deadset-ts: describe: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
42
38
|
return EXIT_FAILURE;
|
|
43
39
|
}
|
|
44
|
-
out.write(describeDocument(version,
|
|
40
|
+
out.write(describeDocument(version, CONFORMANCE));
|
|
45
41
|
return EXIT_CLEAN;
|
|
46
42
|
};
|