@cplieger/deadset-ts 8.0.0-dev.5 → 8.0.0-dev.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +3 -2
- package/dist/src/analysis.js +28 -46
- package/dist/src/automatic-types.js +10 -29
- package/dist/src/build-scripts.js +17 -29
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +2 -3
- package/dist/src/config.js +3 -2
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contract/contract.generated.js +1084 -0
- package/dist/src/contract/report.generated.js +796 -0
- package/dist/src/conventions.js +20 -60
- package/dist/src/dependencies.js +25 -43
- package/dist/src/discover.js +13 -39
- package/dist/src/edges.js +7 -15
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +2 -8
- package/dist/src/entry-points.js +3 -10
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +3 -63
- package/dist/src/exempt.js +13 -40
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +11 -20
- package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
- package/dist/src/findings/emitters.js +4 -4
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +8 -7
- package/dist/src/findings/non-code-artifacts.js +5 -6
- package/dist/src/findings/reads-and-writes.js +11 -9
- package/dist/src/findings/self-check.js +10 -12
- package/dist/src/findings/unused-declarations.js +28 -27
- package/dist/src/findings/visibility-narrowing.js +9 -11
- package/dist/src/findings-pass.js +7 -14
- package/dist/src/fs/manifest.js +43 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/git-ignore.js +34 -25
- package/dist/src/html-entries.js +6 -33
- package/dist/src/in-operands.js +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/intra-function-parts.js +18 -26
- package/dist/src/inventory.js +2 -21
- package/dist/src/json-document.js +4 -0
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +4 -360
- package/dist/src/manifest.js +5 -43
- package/dist/src/mdx-entries.js +3 -26
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +26 -15
- package/dist/src/position.js +2 -1
- package/dist/src/print.js +3 -2
- package/dist/src/product.js +13 -0
- package/dist/src/references.js +14 -27
- package/dist/src/report-schema.js +272 -0
- package/dist/src/report.js +39 -42
- package/dist/src/reporters/sarif.js +4 -2
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +12 -26
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +10 -8
- package/dist/src/schema.js +4 -3
- package/dist/src/scope.js +4 -5
- package/dist/src/serialization-contract.js +4 -8
- package/dist/src/session.js +2 -1
- package/dist/src/setup-failure.js +2 -6
- package/dist/src/suppress-file.js +12 -13
- package/dist/src/suppress-inline.js +3 -2
- package/dist/src/suppress.js +29 -48
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +5 -15
- package/dist/src/test-globals.js +6 -28
- package/dist/src/test-joins.js +6 -6
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
- package/dist/src/type-errors.js +14 -38
- package/dist/src/unseen-loads.js +2 -9
- package/dist/src/unwrap.js +23 -0
- package/dist/src/verbs/analyze.js +22 -15
- package/dist/src/verbs/describe.js +7 -7
- package/dist/src/verbs/explain.js +7 -4
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +4 -2
- package/dist/src/verbs/print-retained.js +4 -2
- package/dist/src/verbs/print-roots.js +4 -2
- package/dist/src/verbs/verb.js +1 -5
- package/dist/src/verbs/version.js +4 -3
- package/dist/src/workflow-steps.js +9 -46
- package/dist/src/workspace-manifests.js +2 -2
- package/dist/src/workspace-programs.js +5 -3
- package/dist/src/workspace-resolution.js +13 -17
- package/dist/src/workspace.js +28 -116
- package/jsr.json +1 -1
- package/package.json +2 -1
- package/src/analysis.ts +29 -54
- package/src/automatic-types.ts +11 -29
- package/src/build-scripts.ts +17 -38
- package/src/component-layer.ts +4 -2
- package/src/components.ts +2 -4
- package/src/config.ts +11 -15
- package/src/configuration-files.ts +27 -170
- package/src/conformance-record.ts +1 -1
- package/src/contract/contract.generated.ts +1148 -0
- package/src/contract/report.generated.ts +1157 -0
- package/src/conventions.ts +26 -74
- package/src/dependencies.ts +32 -50
- package/src/discover.ts +14 -39
- package/src/edges.ts +7 -21
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +3 -9
- package/src/entry-points.ts +2 -13
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +5 -74
- package/src/exempt.ts +17 -47
- package/src/file-facts.ts +6 -24
- package/src/finding.ts +9 -9
- package/src/findings/completion.ts +22 -23
- package/src/findings/dependencies-and-module-machinery.ts +4 -7
- package/src/findings/emitter.ts +6 -7
- package/src/findings/emitters.ts +6 -6
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +8 -8
- package/src/findings/non-code-artifacts.ts +5 -7
- package/src/findings/reads-and-writes.ts +15 -12
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +28 -27
- package/src/findings/visibility-narrowing.ts +9 -12
- package/src/findings-pass.ts +7 -16
- package/src/fs/manifest.ts +52 -0
- package/src/fs/tree.ts +110 -0
- package/src/git-ignore.ts +38 -25
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/index.ts +1 -1
- package/src/intra-function-parts.ts +18 -28
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +5 -0
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +6 -406
- package/src/manifest.ts +5 -46
- package/src/mdx-entries.ts +3 -26
- package/src/partial.ts +2 -1
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +3 -2
- package/src/product.ts +18 -0
- package/src/references.ts +14 -29
- package/src/report-schema.ts +364 -0
- package/src/report.ts +66 -229
- package/src/reporters/annotations.ts +2 -2
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -2
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +17 -12
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +31 -38
- package/src/roots.ts +7 -20
- package/src/run.ts +10 -14
- package/src/schema.ts +4 -3
- package/src/scope.ts +4 -10
- package/src/serialization-contract.ts +4 -9
- package/src/session.ts +2 -1
- package/src/setup-failure.ts +5 -7
- package/src/suppress-file.ts +11 -15
- package/src/suppress-inline.ts +2 -2
- package/src/suppress.ts +39 -55
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +5 -18
- package/src/test-globals.ts +9 -27
- package/src/test-joins.ts +6 -8
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/shell.ts +161 -0
- package/src/{yaml-document.ts → text/yaml.ts} +46 -0
- package/src/type-errors.ts +25 -36
- package/src/unseen-loads.ts +1 -12
- package/src/unwrap.ts +34 -0
- package/src/verbs/analyze.ts +38 -25
- package/src/verbs/describe.ts +13 -7
- package/src/verbs/explain.ts +7 -4
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +4 -2
- package/src/verbs/print-retained.ts +4 -2
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +0 -6
- package/src/verbs/version.ts +4 -3
- package/src/workflow-steps.ts +9 -44
- package/src/workspace-manifests.ts +2 -2
- package/src/workspace-programs.ts +5 -3
- package/src/workspace-resolution.ts +21 -17
- package/src/workspace.ts +35 -121
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/dist/src/version.js +0 -7
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
- package/src/version.ts +0 -7
package/dist/src/position.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { relativePath } from "./paths.js";
|
|
2
|
+
import { bytewise } from "./text/compare.js";
|
|
2
3
|
/**
|
|
3
4
|
* A file the program holds whose path is not below the target root, which leaves
|
|
4
5
|
* the analysis unable to name a declaration it found. It ends the run rather than
|
|
@@ -58,7 +59,7 @@ export function positionKey(position) {
|
|
|
58
59
|
*/
|
|
59
60
|
export function byPosition(a, b) {
|
|
60
61
|
if (a.path !== b.path) {
|
|
61
|
-
return a.path
|
|
62
|
+
return bytewise(a.path, b.path);
|
|
62
63
|
}
|
|
63
64
|
if (a.line !== b.line) {
|
|
64
65
|
return a.line - b.line;
|
package/dist/src/print.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { renderOrigin, } from "./config.js";
|
|
2
|
+
import { bytewise } from "./text/compare.js";
|
|
2
3
|
/** One declaration entry as a document writes it: the members of its shape alone. */
|
|
3
4
|
function declarationOf(entry) {
|
|
4
5
|
switch (entry.shape) {
|
|
@@ -50,7 +51,7 @@ export function printConfig(config, provenance) {
|
|
|
50
51
|
},
|
|
51
52
|
consumers: { complete: config.consumersComplete },
|
|
52
53
|
roots: { patterns: config.rootPatterns },
|
|
53
|
-
severity: Object.fromEntries([...config.severity].sort(([a], [b]) => (a
|
|
54
|
+
severity: Object.fromEntries([...config.severity].sort(([a], [b]) => bytewise(a, b))),
|
|
54
55
|
exemptions: { disabled: config.exemptionsDisabled },
|
|
55
56
|
reporters: {
|
|
56
57
|
formats: config.reporters.formats,
|
|
@@ -76,7 +77,7 @@ export function printConfig(config, provenance) {
|
|
|
76
77
|
roots_complete: config.ts.rootsComplete,
|
|
77
78
|
},
|
|
78
79
|
provenance: Object.fromEntries([...provenance]
|
|
79
|
-
.sort(([a], [b]) => (a
|
|
80
|
+
.sort(([a], [b]) => bytewise(a, b))
|
|
80
81
|
.map(([path, origin]) => [path, renderOrigin(origin)])),
|
|
81
82
|
};
|
|
82
83
|
return `${JSON.stringify(document, undefined, 2)}\n`;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LANGUAGE_ROWS } from "./contract/contract.generated.js";
|
|
2
|
+
/** The language this analyzer reads. */
|
|
3
|
+
export const LANGUAGE = "ts";
|
|
4
|
+
/** The analyzer's name: the one its language's row names, which every output spells it with. */
|
|
5
|
+
export const PRODUCT = LANGUAGE_ROWS[LANGUAGE].analyzer;
|
|
6
|
+
/** Writes one diagnostic line to the error stream, named for the analyzer. */
|
|
7
|
+
export function diagnose(err, text) {
|
|
8
|
+
err.write(`${PRODUCT}: ${text}\n`);
|
|
9
|
+
}
|
|
10
|
+
/** The text one thrown value carries. */
|
|
11
|
+
export function message(error) {
|
|
12
|
+
return error instanceof Error ? error.message : String(error);
|
|
13
|
+
}
|
package/dist/src/references.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isInterfaceDeclaration, isMetaProperty, isModuleBlock, isModuleDeclaration, isNewExpression, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVoidExpression, SyntaxKind, } from "@typescript/native/ast";
|
|
2
|
+
import { parenthesesRemoved } from "./unwrap.js";
|
|
2
3
|
import { aliasChains } from "./alias-chain.js";
|
|
3
4
|
import { contextualUses } from "./contextual-uses.js";
|
|
4
5
|
import { destructuring, propertyReadsOf } from "./destructuring.js";
|
|
5
6
|
import { specifierCandidates } from "./configuration-files.js";
|
|
6
7
|
import { namespaceUses } from "./namespace-objects.js";
|
|
7
|
-
import { globExpression } from "./glob.js";
|
|
8
8
|
import { declarationsByName, isParameterProperty, nodeKey, } from "./inventory.js";
|
|
9
9
|
import { ambientGlobalInterfaces, augmentationReferences } from "./augmentations.js";
|
|
10
10
|
import { elementAccessReferences } from "./element-access.js";
|
|
@@ -20,6 +20,8 @@ import { typeParameterReads } from "./type-parameter-reads.js";
|
|
|
20
20
|
import { typeQueryAliases } from "./type-query-alias.js";
|
|
21
21
|
import { computedKeyNames, iterationReferences, keyNameOf, keyedUses, } from "./symbol-keys.js";
|
|
22
22
|
import { handedOn, valuesOf } from "./value-flow.js";
|
|
23
|
+
import { bytewise } from "./text/compare.js";
|
|
24
|
+
import { globExpression } from "./text/glob.js";
|
|
23
25
|
/**
|
|
24
26
|
* The operators whose left side one assignment stores into. A compound assignment is
|
|
25
27
|
* among them: where its value is discarded it reads its target only to write the result
|
|
@@ -77,14 +79,6 @@ function valueDiscarded(node) {
|
|
|
77
79
|
function isName(node) {
|
|
78
80
|
return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PrivateIdentifier;
|
|
79
81
|
}
|
|
80
|
-
/** The expression one node is, through however many parentheses are written around it. */
|
|
81
|
-
function unparenthesized(node) {
|
|
82
|
-
let held = node;
|
|
83
|
-
while (isParenthesizedExpression(held)) {
|
|
84
|
-
held = held.expression;
|
|
85
|
-
}
|
|
86
|
-
return held;
|
|
87
|
-
}
|
|
88
82
|
/**
|
|
89
83
|
* Records the name one store writes into.
|
|
90
84
|
*
|
|
@@ -94,7 +88,7 @@ function unparenthesized(node) {
|
|
|
94
88
|
* target writes each name it binds, through its spreads and nested patterns.
|
|
95
89
|
*/
|
|
96
90
|
function markWrite(target, writes, accesses) {
|
|
97
|
-
const held =
|
|
91
|
+
const held = parenthesesRemoved(target);
|
|
98
92
|
if (isName(held)) {
|
|
99
93
|
writes.add(held.pos);
|
|
100
94
|
return;
|
|
@@ -109,7 +103,7 @@ function markWrite(target, writes, accesses) {
|
|
|
109
103
|
accesses.add(held);
|
|
110
104
|
// A store through a member's value lands in storage every holder of the value
|
|
111
105
|
// reads, so it reads the member; a variable only ever stored into stays written.
|
|
112
|
-
if (!isPropertyAccessExpression(
|
|
106
|
+
if (!isPropertyAccessExpression(parenthesesRemoved(held.expression))) {
|
|
113
107
|
markWrite(held.expression, writes, accesses);
|
|
114
108
|
}
|
|
115
109
|
return;
|
|
@@ -181,7 +175,7 @@ function markStores(node, writes, reads, accesses) {
|
|
|
181
175
|
}
|
|
182
176
|
/** Whether one node is `import.meta.url`. */
|
|
183
177
|
function isImportMetaURL(node) {
|
|
184
|
-
const at =
|
|
178
|
+
const at = parenthesesRemoved(node);
|
|
185
179
|
return (isPropertyAccessExpression(at) &&
|
|
186
180
|
isMetaProperty(at.expression) &&
|
|
187
181
|
at.expression.keywordToken === SyntaxKind.ImportKeyword &&
|
|
@@ -190,7 +184,7 @@ function isImportMetaURL(node) {
|
|
|
190
184
|
/** The module URL one node builds, where it builds one. */
|
|
191
185
|
function moduleURLOf(node) {
|
|
192
186
|
const relative = (argument) => {
|
|
193
|
-
const at = argument === undefined ? undefined :
|
|
187
|
+
const at = argument === undefined ? undefined : parenthesesRemoved(argument);
|
|
194
188
|
return at !== undefined &&
|
|
195
189
|
(isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at)) &&
|
|
196
190
|
/^\.\.?\//u.test(at.text)
|
|
@@ -205,7 +199,7 @@ function moduleURLOf(node) {
|
|
|
205
199
|
: undefined;
|
|
206
200
|
}
|
|
207
201
|
if (isCallExpression(node)) {
|
|
208
|
-
const callee =
|
|
202
|
+
const callee = parenthesesRemoved(node.expression);
|
|
209
203
|
const specifier = relative(node.arguments[0]);
|
|
210
204
|
if (specifier !== undefined &&
|
|
211
205
|
isPropertyAccessExpression(callee) &&
|
|
@@ -490,7 +484,7 @@ export function testFileRulesOf(testFiles, paths, supportFiles = []) {
|
|
|
490
484
|
};
|
|
491
485
|
}),
|
|
492
486
|
...(support === 0 ? [] : [{ rule: TEST_SUPPORT_RULE, matched: support }]),
|
|
493
|
-
].sort((a, b) =>
|
|
487
|
+
].sort((a, b) => bytewise(a.rule, b.rule));
|
|
494
488
|
}
|
|
495
489
|
const importedCache = new WeakMap();
|
|
496
490
|
/**
|
|
@@ -609,20 +603,13 @@ function aloneIn(file, uses, writes, found) {
|
|
|
609
603
|
.filter(([, nodes]) => nodes.some((node) => inside(node, write.value)) &&
|
|
610
604
|
nodes.every((node) => sameTarget.some((one) => inside(node, one.value))))
|
|
611
605
|
.map(([name]) => name)
|
|
612
|
-
.sort(
|
|
606
|
+
.sort(bytewise);
|
|
613
607
|
if (names.length > 0) {
|
|
614
608
|
result.push({ index: write.index, names });
|
|
615
609
|
}
|
|
616
610
|
}
|
|
617
611
|
return result;
|
|
618
612
|
}
|
|
619
|
-
/** Two strings ordered bytewise, which is the order every set of a run is read in. */
|
|
620
|
-
function compare(a, b) {
|
|
621
|
-
if (a === b) {
|
|
622
|
-
return 0;
|
|
623
|
-
}
|
|
624
|
-
return a < b ? -1 : 1;
|
|
625
|
-
}
|
|
626
613
|
/**
|
|
627
614
|
* Every reference one project's own files make to the project's own declarations, in
|
|
628
615
|
* position order, and the files the test-file rules classified.
|
|
@@ -1192,14 +1179,14 @@ export function references(project, held, targetRoot, options) {
|
|
|
1192
1179
|
const scripts = new Set(walked.filter((one) => one.file.externalModuleIndicator === undefined).map((one) => one.fileId));
|
|
1193
1180
|
found.push(...augmentationReferences(held, found, scripts, libraryMerged(scripts)));
|
|
1194
1181
|
found.sort((a, b) => byPosition(a.position, b.position) ||
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1182
|
+
bytewise(a.to, b.to) ||
|
|
1183
|
+
bytewise(a.from, b.from) ||
|
|
1184
|
+
bytewise(a.use, b.use));
|
|
1198
1185
|
return {
|
|
1199
1186
|
references: consumer === undefined
|
|
1200
1187
|
? found
|
|
1201
1188
|
: found.map((reference) => ({ ...reference, consumer: consumer.id })),
|
|
1202
|
-
testFilePaths: tests.sort(
|
|
1189
|
+
testFilePaths: tests.sort(bytewise),
|
|
1203
1190
|
cost: {
|
|
1204
1191
|
batched,
|
|
1205
1192
|
fileBatches,
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holds a report to `contract/report.schema.json` and `contract/finding.schema.json`
|
|
3
|
+
* before it is written, reading the schemas the generator copies from the Contract, so
|
|
4
|
+
* this analyzer never writes a document a reader refuses. It evaluates the keywords the
|
|
5
|
+
* Contract's schemas use; the schemas' patterns are written to match alike in RE2 and in
|
|
6
|
+
* ECMAScript.
|
|
7
|
+
*/
|
|
8
|
+
import { REPORT_SCHEMAS } from "./contract/report.generated.js";
|
|
9
|
+
import { bytewise } from "./text/compare.js";
|
|
10
|
+
import { isRecord } from "./json-document.js";
|
|
11
|
+
/**
|
|
12
|
+
* Every keyword the validator evaluates or knows to carry no assertion. A schema naming
|
|
13
|
+
* another fails every check rather than passing values the keyword would refuse.
|
|
14
|
+
*/
|
|
15
|
+
const KEYWORDS = new Set([
|
|
16
|
+
...["$schema", "$id", "$comment", "$defs", "title", "description", "default", "examples"],
|
|
17
|
+
...["$ref", "type", "enum", "const", "pattern", "minLength", "minimum", "maximum"],
|
|
18
|
+
...["properties", "additionalProperties", "required", "minProperties"],
|
|
19
|
+
...["items", "minItems", "maxItems", "uniqueItems"],
|
|
20
|
+
...["allOf", "anyOf", "oneOf", "not", "if", "then", "else"],
|
|
21
|
+
]);
|
|
22
|
+
const patterns = new Map();
|
|
23
|
+
function pattern(source) {
|
|
24
|
+
let held = patterns.get(source);
|
|
25
|
+
if (held === undefined) {
|
|
26
|
+
held = new RegExp(source, "u");
|
|
27
|
+
patterns.set(source, held);
|
|
28
|
+
}
|
|
29
|
+
return held;
|
|
30
|
+
}
|
|
31
|
+
function typeOf(value) {
|
|
32
|
+
if (value === null) {
|
|
33
|
+
return "null";
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
return "array";
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === "number") {
|
|
39
|
+
return Number.isInteger(value) ? "integer" : "number";
|
|
40
|
+
}
|
|
41
|
+
return typeof value;
|
|
42
|
+
}
|
|
43
|
+
function hasType(value, name) {
|
|
44
|
+
const held = typeOf(value);
|
|
45
|
+
return held === name || (name === "number" && held === "integer");
|
|
46
|
+
}
|
|
47
|
+
/** One value's encoding with object members sorted, so two equal values encode alike. */
|
|
48
|
+
function canonical(value) {
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
return `[${value.map(canonical).join(",")}]`;
|
|
51
|
+
}
|
|
52
|
+
if (isRecord(value)) {
|
|
53
|
+
return `{${Object.keys(value)
|
|
54
|
+
.sort(bytewise)
|
|
55
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
56
|
+
.join(",")}}`;
|
|
57
|
+
}
|
|
58
|
+
return JSON.stringify(value);
|
|
59
|
+
}
|
|
60
|
+
/** A string's length in code points, which JSON Schema's minLength counts. */
|
|
61
|
+
function codePoints(value) {
|
|
62
|
+
let counted = 0;
|
|
63
|
+
for (const _point of value) {
|
|
64
|
+
counted += 1;
|
|
65
|
+
}
|
|
66
|
+
return counted;
|
|
67
|
+
}
|
|
68
|
+
const escape = (name) => name.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
69
|
+
/** A schema evaluated in the document it is read from, so a relative `$ref` resolves there. */
|
|
70
|
+
class Checker {
|
|
71
|
+
documents;
|
|
72
|
+
constructor(documents) {
|
|
73
|
+
this.documents = documents;
|
|
74
|
+
}
|
|
75
|
+
resolve(doc, ref) {
|
|
76
|
+
const [file = "", pointer = ""] = ref.split("#");
|
|
77
|
+
const target = file === "" ? doc : file;
|
|
78
|
+
let schema = this.documents[target];
|
|
79
|
+
for (const token of pointer.split("/").filter((one) => one !== "")) {
|
|
80
|
+
schema = isRecord(schema)
|
|
81
|
+
? schema[token.replaceAll("~1", "/").replaceAll("~0", "~")]
|
|
82
|
+
: undefined;
|
|
83
|
+
}
|
|
84
|
+
if (!isRecord(schema)) {
|
|
85
|
+
throw new Error(`report schema: ${ref} names no schema`);
|
|
86
|
+
}
|
|
87
|
+
return [target, schema];
|
|
88
|
+
}
|
|
89
|
+
matches(doc, schema, value, at) {
|
|
90
|
+
const found = [];
|
|
91
|
+
this.check(doc, schema, value, at, found);
|
|
92
|
+
return found.length === 0;
|
|
93
|
+
}
|
|
94
|
+
check(doc, schema, value, at, found) {
|
|
95
|
+
for (const keyword of Object.keys(schema)) {
|
|
96
|
+
if (!KEYWORDS.has(keyword) && !keyword.startsWith("x-")) {
|
|
97
|
+
throw new Error(`report schema: the keyword ${keyword} is not one this validator evaluates`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const refuse = (keyword, reason, pointer = at) => {
|
|
101
|
+
found.push({ pointer, keyword, reason });
|
|
102
|
+
};
|
|
103
|
+
if (typeof schema["$ref"] === "string") {
|
|
104
|
+
const [target, referenced] = this.resolve(doc, schema["$ref"]);
|
|
105
|
+
this.check(target, referenced, value, at, found);
|
|
106
|
+
}
|
|
107
|
+
const types = schema["type"];
|
|
108
|
+
const named = types === undefined ? [] : Array.isArray(types) ? types : [types];
|
|
109
|
+
if (named.length > 0 && !named.some((one) => hasType(value, String(one)))) {
|
|
110
|
+
refuse("type", `the value is ${typeOf(value)}, want ${named.join(", ")}`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.checkValue(schema, value, refuse);
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
this.checkArray(doc, schema, value, at, found, refuse);
|
|
116
|
+
}
|
|
117
|
+
else if (isRecord(value)) {
|
|
118
|
+
this.checkObject(doc, schema, value, at, found, refuse);
|
|
119
|
+
}
|
|
120
|
+
this.checkApplicators(doc, schema, value, at, found, refuse);
|
|
121
|
+
}
|
|
122
|
+
checkValue(schema, value, refuse) {
|
|
123
|
+
const values = schema["enum"];
|
|
124
|
+
if (Array.isArray(values) && !values.some((one) => canonical(one) === canonical(value))) {
|
|
125
|
+
refuse("enum", `${canonical(value)} is not one of the values the schema lists`);
|
|
126
|
+
}
|
|
127
|
+
if (schema["const"] !== undefined && canonical(schema["const"]) !== canonical(value)) {
|
|
128
|
+
refuse("const", `${canonical(value)} is not ${canonical(schema["const"])}`);
|
|
129
|
+
}
|
|
130
|
+
if (typeof value === "string") {
|
|
131
|
+
if (typeof schema["pattern"] === "string" && !pattern(schema["pattern"]).test(value)) {
|
|
132
|
+
refuse("pattern", `${JSON.stringify(value)} does not match ${schema["pattern"]}`);
|
|
133
|
+
}
|
|
134
|
+
if (typeof schema["minLength"] === "number" && codePoints(value) < schema["minLength"]) {
|
|
135
|
+
refuse("minLength", `${JSON.stringify(value)} is shorter than ${String(schema["minLength"])} characters`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (typeof value === "number") {
|
|
139
|
+
if (typeof schema["minimum"] === "number" && value < schema["minimum"]) {
|
|
140
|
+
refuse("minimum", `${String(value)} is below ${String(schema["minimum"])}`);
|
|
141
|
+
}
|
|
142
|
+
if (typeof schema["maximum"] === "number" && value > schema["maximum"]) {
|
|
143
|
+
refuse("maximum", `${String(value)} is above ${String(schema["maximum"])}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
checkObject(doc, schema, value, at, found, refuse) {
|
|
148
|
+
const properties = isRecord(schema["properties"]) ? schema["properties"] : {};
|
|
149
|
+
const additional = schema["additionalProperties"];
|
|
150
|
+
for (const name of Object.keys(value).sort(bytewise)) {
|
|
151
|
+
const member = `${at}/${escape(name)}`;
|
|
152
|
+
const declared = properties[name];
|
|
153
|
+
if (isRecord(declared)) {
|
|
154
|
+
this.check(doc, declared, value[name], member, found);
|
|
155
|
+
}
|
|
156
|
+
else if (additional === false) {
|
|
157
|
+
refuse("additionalProperties", `the member ${JSON.stringify(name)} is not one the schema declares`);
|
|
158
|
+
}
|
|
159
|
+
else if (isRecord(additional)) {
|
|
160
|
+
this.check(doc, additional, value[name], member, found);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const name of Array.isArray(schema["required"]) ? schema["required"] : []) {
|
|
164
|
+
if (!Object.hasOwn(value, name)) {
|
|
165
|
+
refuse("required", `the member ${JSON.stringify(name)} is absent`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const fewest = schema["minProperties"];
|
|
169
|
+
if (typeof fewest === "number" && Object.keys(value).length < fewest) {
|
|
170
|
+
refuse("minProperties", `the object holds ${String(Object.keys(value).length)} members, fewer than ${String(fewest)}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
checkArray(doc, schema, value, at, found, refuse) {
|
|
174
|
+
const items = schema["items"];
|
|
175
|
+
if (isRecord(items)) {
|
|
176
|
+
value.forEach((item, i) => {
|
|
177
|
+
this.check(doc, items, item, `${at}/${String(i)}`, found);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (typeof schema["minItems"] === "number" && value.length < schema["minItems"]) {
|
|
181
|
+
refuse("minItems", `the array holds ${String(value.length)} items, fewer than ${String(schema["minItems"])}`);
|
|
182
|
+
}
|
|
183
|
+
if (typeof schema["maxItems"] === "number" && value.length > schema["maxItems"]) {
|
|
184
|
+
refuse("maxItems", `the array holds ${String(value.length)} items, more than ${String(schema["maxItems"])}`);
|
|
185
|
+
}
|
|
186
|
+
if (schema["uniqueItems"] === true && new Set(value.map(canonical)).size !== value.length) {
|
|
187
|
+
refuse("uniqueItems", "the array lists one value twice");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
checkApplicators(doc, schema, value, at, found, refuse) {
|
|
191
|
+
const list = (keyword) => Array.isArray(schema[keyword]) ? schema[keyword] : [];
|
|
192
|
+
for (const each of list("allOf")) {
|
|
193
|
+
this.check(doc, each, value, at, found);
|
|
194
|
+
}
|
|
195
|
+
const matching = (keyword) => list(keyword).filter((each) => this.matches(doc, each, value, at)).length;
|
|
196
|
+
if (schema["anyOf"] !== undefined && matching("anyOf") === 0) {
|
|
197
|
+
refuse("anyOf", "the value matches none of the alternatives");
|
|
198
|
+
}
|
|
199
|
+
if (schema["oneOf"] !== undefined && matching("oneOf") !== 1) {
|
|
200
|
+
refuse("oneOf", `the value matches ${String(matching("oneOf"))} of the alternatives, want exactly one`);
|
|
201
|
+
}
|
|
202
|
+
if (isRecord(schema["not"]) && this.matches(doc, schema["not"], value, at)) {
|
|
203
|
+
refuse("not", "the value matches a schema it must not match");
|
|
204
|
+
}
|
|
205
|
+
if (isRecord(schema["if"])) {
|
|
206
|
+
const branch = this.matches(doc, schema["if"], value, at) ? schema["then"] : schema["else"];
|
|
207
|
+
if (isRecord(branch)) {
|
|
208
|
+
this.check(doc, branch, value, at, found);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* A validator over one set of schema documents, each named as a `$ref` names it, rooted
|
|
215
|
+
* at one of them: what it refuses in a value, none for an instance.
|
|
216
|
+
*/
|
|
217
|
+
export function schemaRefusals(documents, root) {
|
|
218
|
+
const checker = new Checker(documents);
|
|
219
|
+
const top = documents[root];
|
|
220
|
+
if (top === undefined) {
|
|
221
|
+
throw new Error(`the schema set holds no document ${root}`);
|
|
222
|
+
}
|
|
223
|
+
return (value) => {
|
|
224
|
+
const found = [];
|
|
225
|
+
checker.check(root, top, value, "", found);
|
|
226
|
+
return found;
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const reportSchema = schemaRefusals(REPORT_SCHEMAS, "report.schema.json");
|
|
230
|
+
/**
|
|
231
|
+
* What the report schema refuses in one report document, none for an instance, and, as
|
|
232
|
+
* no schema keyword can state it, a components listing that is not one entry per
|
|
233
|
+
* component a finding or an edge evaluation's finding names.
|
|
234
|
+
*/
|
|
235
|
+
export function reportRefusals(document) {
|
|
236
|
+
const found = reportSchema(document);
|
|
237
|
+
return found.length > 0 ? found : listingRefusals(document);
|
|
238
|
+
}
|
|
239
|
+
/** The components listing held to the components the findings name, once each. */
|
|
240
|
+
function listingRefusals(report) {
|
|
241
|
+
if (report.components === undefined) {
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
const refuse = (pointer, reason) => [
|
|
245
|
+
{ pointer, keyword: "components", reason },
|
|
246
|
+
];
|
|
247
|
+
const listed = new Set();
|
|
248
|
+
for (const [i, entry] of report.components.entries()) {
|
|
249
|
+
if (listed.has(entry.id)) {
|
|
250
|
+
return refuse(`/components/${String(i)}`, `the entry lists component ${entry.id} a second time`);
|
|
251
|
+
}
|
|
252
|
+
listed.add(entry.id);
|
|
253
|
+
}
|
|
254
|
+
const named = [
|
|
255
|
+
...report.findings.map((finding, i) => [`/findings/${String(i)}`, finding.component.id]),
|
|
256
|
+
...report.edge_evaluations.flatMap((one, i) => one.finding === undefined
|
|
257
|
+
? []
|
|
258
|
+
: [[`/edge_evaluations/${String(i)}/finding`, one.finding.component.id]]),
|
|
259
|
+
];
|
|
260
|
+
for (const [pointer, id] of named) {
|
|
261
|
+
if (!listed.has(id)) {
|
|
262
|
+
return refuse(pointer, `the finding names component ${id}, which the components listing lacks`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const ids = new Set(named.map(([, id]) => id));
|
|
266
|
+
for (const [i, entry] of report.components.entries()) {
|
|
267
|
+
if (!ids.has(entry.id)) {
|
|
268
|
+
return refuse(`/components/${String(i)}`, `the entry lists component ${entry.id}, which no finding names`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return [];
|
|
272
|
+
}
|
package/dist/src/report.js
CHANGED
|
@@ -3,14 +3,9 @@
|
|
|
3
3
|
* in the order its schema lists them, built from what one run answered. Every reporter
|
|
4
4
|
* renders this document, so no two renderings disagree about which findings exist.
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export const SCHEMA_VERSIONS_ACCEPTED = [SCHEMA_VERSION];
|
|
10
|
-
/** The name this analyzer writes every document under and mints every identifier with. */
|
|
11
|
-
export const ANALYZER_NAME = "deadset-ts";
|
|
12
|
-
/** The language this analyzer claims. */
|
|
13
|
-
export const LANGUAGE = "ts";
|
|
6
|
+
import { SCHEMA_VERSION, SCHEMA_VERSIONS } from "./contract/contract.generated.js";
|
|
7
|
+
import { bytewise } from "./text/compare.js";
|
|
8
|
+
import { LANGUAGE, PRODUCT } from "./product.js";
|
|
14
9
|
/**
|
|
15
10
|
* Each component the findings name, once, with the members the first finding naming it
|
|
16
11
|
* carries, in the order the findings are: `findings` paired with their wire forms and
|
|
@@ -18,21 +13,17 @@ export const LANGUAGE = "ts";
|
|
|
18
13
|
*/
|
|
19
14
|
function componentsOf(findings) {
|
|
20
15
|
const listed = new Map();
|
|
21
|
-
for (const [
|
|
16
|
+
for (const [written, finding] of findings) {
|
|
22
17
|
const { members } = finding.component;
|
|
23
|
-
if (members !== undefined && !listed.has(
|
|
24
|
-
listed.set(
|
|
18
|
+
if (members !== undefined && !listed.has(written.component.id)) {
|
|
19
|
+
listed.set(written.component.id, {
|
|
20
|
+
id: written.component.id,
|
|
21
|
+
members: members.map(wireSymbol),
|
|
22
|
+
});
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
25
|
return [...listed.values()];
|
|
28
26
|
}
|
|
29
|
-
/** Two strings ordered bytewise. */
|
|
30
|
-
function compare(a, b) {
|
|
31
|
-
if (a === b) {
|
|
32
|
-
return 0;
|
|
33
|
-
}
|
|
34
|
-
return a < b ? -1 : 1;
|
|
35
|
-
}
|
|
36
27
|
function wirePosition(position) {
|
|
37
28
|
return {
|
|
38
29
|
path: position.path,
|
|
@@ -140,19 +131,19 @@ function wireStale(stale) {
|
|
|
140
131
|
}
|
|
141
132
|
/** The canonical key over two findings: path, line, column, code, then the symbol reference. */
|
|
142
133
|
function canonical(a, b) {
|
|
143
|
-
return (
|
|
134
|
+
return (bytewise(a.position.path, b.position.path) ||
|
|
144
135
|
a.position.line - b.position.line ||
|
|
145
136
|
a.position.column - b.position.column ||
|
|
146
|
-
|
|
147
|
-
|
|
137
|
+
bytewise(a.code, b.code) ||
|
|
138
|
+
bytewise(a.symbol.ref, b.symbol.ref));
|
|
148
139
|
}
|
|
149
140
|
/** The canonical key over two stale suppressions, each component from the record's own member. */
|
|
150
141
|
function canonicalStale(a, b) {
|
|
151
|
-
return (
|
|
142
|
+
return (bytewise(a.position.path, b.position.path) ||
|
|
152
143
|
a.position.line - b.position.line ||
|
|
153
144
|
a.position.column - b.position.column ||
|
|
154
|
-
|
|
155
|
-
|
|
145
|
+
bytewise(a.code, b.code) ||
|
|
146
|
+
bytewise(a.symbol, b.symbol));
|
|
156
147
|
}
|
|
157
148
|
/** One declared gap as a report carries it, its members in the schema's order. */
|
|
158
149
|
function wireGap(gap) {
|
|
@@ -165,9 +156,9 @@ function wireGap(gap) {
|
|
|
165
156
|
}
|
|
166
157
|
/** Edge evaluations by edge, then side, then their compact encodings. */
|
|
167
158
|
function byEdge(a, b) {
|
|
168
|
-
return (
|
|
169
|
-
|
|
170
|
-
|
|
159
|
+
return (bytewise(a.edge, b.edge) ||
|
|
160
|
+
bytewise(a.side, b.side) ||
|
|
161
|
+
bytewise(JSON.stringify(a), JSON.stringify(b)));
|
|
171
162
|
}
|
|
172
163
|
/**
|
|
173
164
|
* The lines a reader deletes by acting on every finding: each distinct component a root
|
|
@@ -216,19 +207,19 @@ export function buildReport(input) {
|
|
|
216
207
|
const findings = paired.map(([wire]) => wire);
|
|
217
208
|
const evaluated = input.result.edgeEvaluations.flatMap((one) => one.finding === undefined ? [] : [[wireFinding(one.finding), one.finding]]);
|
|
218
209
|
const unavailable = [...new Map(input.unavailable.map((one) => [one.id, one])).values()]
|
|
219
|
-
.sort((a, b) =>
|
|
210
|
+
.sort((a, b) => bytewise(a.id, b.id))
|
|
220
211
|
.map((one) => ({ id: one.id, role: "consumer", reason: one.reason }));
|
|
221
212
|
const loaded = [...input.loaded]
|
|
222
|
-
.sort((a, b) =>
|
|
213
|
+
.sort((a, b) => bytewise(a.id, b.id) || bytewise(a.path, b.path))
|
|
223
214
|
.map((one) => ({ id: one.id, role: "consumer", path: one.path }));
|
|
224
215
|
return {
|
|
225
216
|
schema_version: SCHEMA_VERSION,
|
|
226
217
|
contract_version: input.contractVersion,
|
|
227
218
|
analyzer: {
|
|
228
|
-
name:
|
|
219
|
+
name: PRODUCT,
|
|
229
220
|
version: input.version,
|
|
230
221
|
languages: [LANGUAGE],
|
|
231
|
-
schema_versions_accepted:
|
|
222
|
+
schema_versions_accepted: SCHEMA_VERSIONS,
|
|
232
223
|
conformance: {
|
|
233
224
|
corpus_version: input.conformance.corpusVersion,
|
|
234
225
|
result: input.conformance.result,
|
|
@@ -237,10 +228,10 @@ export function buildReport(input) {
|
|
|
237
228
|
},
|
|
238
229
|
target: { kind: input.target.kind, root: input.target.root, identity: input.target.identity },
|
|
239
230
|
configurations: [...input.configurations]
|
|
240
|
-
.sort((a, b) =>
|
|
231
|
+
.sort((a, b) => bytewise(a.id, b.id))
|
|
241
232
|
.map((one) => ({ id: one.id, project: one.project })),
|
|
242
233
|
configurations_not_built: [...input.notBuilt]
|
|
243
|
-
.sort((a, b) =>
|
|
234
|
+
.sort((a, b) => bytewise(a.id, b.id))
|
|
244
235
|
.map((one) => ({ id: one.id, project: one.project, error: one.error })),
|
|
245
236
|
consumers: { declared: loaded.length + unavailable.length, loaded, unavailable },
|
|
246
237
|
findings,
|
|
@@ -253,10 +244,10 @@ export function buildReport(input) {
|
|
|
253
244
|
// name, so the compact encodings alone order them.
|
|
254
245
|
declared_gaps: input.declaredGaps
|
|
255
246
|
.map(wireGap)
|
|
256
|
-
.sort((a, b) =>
|
|
247
|
+
.sort((a, b) => bytewise(JSON.stringify(a), JSON.stringify(b))),
|
|
257
248
|
excluded_by_cgo: [],
|
|
258
249
|
test_file_rules: [...input.testFileRules]
|
|
259
|
-
.sort((a, b) =>
|
|
250
|
+
.sort((a, b) => bytewise(a.rule, b.rule) || a.matched - b.matched)
|
|
260
251
|
.map((one) => ({ rule: one.rule, matched: one.matched })),
|
|
261
252
|
type_error_skips: [
|
|
262
253
|
...new Map(input.typeErrorSkips.map((one) => {
|
|
@@ -264,14 +255,14 @@ export function buildReport(input) {
|
|
|
264
255
|
return [JSON.stringify(skip), skip];
|
|
265
256
|
})),
|
|
266
257
|
]
|
|
267
|
-
.sort(([a, x], [b, y]) =>
|
|
258
|
+
.sort(([a, x], [b, y]) => bytewise(x.path, y.path) || x.line - y.line || bytewise(a, b))
|
|
268
259
|
.map(([, skip]) => skip),
|
|
269
260
|
notes: [...new Map((input.notes ?? []).map((one) => [JSON.stringify(one), one])).entries()]
|
|
270
|
-
.sort(([a, x], [b, y]) =>
|
|
261
|
+
.sort(([a, x], [b, y]) => bytewise(x.kind, y.kind) || bytewise(x.path, y.path) || bytewise(a, b))
|
|
271
262
|
.map(([, note]) => note),
|
|
272
263
|
unanswered_questions: [...input.unanswered]
|
|
273
|
-
.sort((a, b) =>
|
|
274
|
-
|
|
264
|
+
.sort((a, b) => bytewise(a.configuration, b.configuration) ||
|
|
265
|
+
bytewise(JSON.stringify(a), JSON.stringify(b)))
|
|
275
266
|
.map((one) => ({
|
|
276
267
|
configuration: one.configuration,
|
|
277
268
|
questions: one.questions,
|
|
@@ -298,16 +289,22 @@ export function sortFindings(report, by) {
|
|
|
298
289
|
return { ...report, findings: [...report.findings].sort(by === "size" ? bySize : canonical) };
|
|
299
290
|
}
|
|
300
291
|
/**
|
|
301
|
-
* The report holding the first `most` findings of its order, the rest counted as omitted
|
|
302
|
-
*
|
|
292
|
+
* The report holding the first `most` findings of its order, the rest counted as omitted,
|
|
293
|
+
* and listing only the components those findings name. Every other total keeps describing
|
|
294
|
+
* the whole set. Zero keeps every finding.
|
|
303
295
|
*/
|
|
304
296
|
export function capFindings(report, most) {
|
|
305
297
|
if (most <= 0 || report.findings.length <= most) {
|
|
306
298
|
return report;
|
|
307
299
|
}
|
|
300
|
+
const findings = report.findings.slice(0, most);
|
|
301
|
+
const named = new Set([...findings, ...report.edge_evaluations.flatMap((one) => one.finding ?? [])].map((finding) => finding.component.id));
|
|
308
302
|
return {
|
|
309
303
|
...report,
|
|
310
|
-
findings
|
|
304
|
+
findings,
|
|
305
|
+
...(report.components === undefined
|
|
306
|
+
? {}
|
|
307
|
+
: { components: report.components.filter((listed) => named.has(listed.id)) }),
|
|
311
308
|
totals: { ...report.totals, omitted: report.findings.length - most },
|
|
312
309
|
};
|
|
313
310
|
}
|
|
@@ -5,6 +5,8 @@ import { lineHashes, symbolFingerprint } from "./fingerprint.js";
|
|
|
5
5
|
import { RenderError } from "./reporter.js";
|
|
6
6
|
import { utf8Bytes } from "./sha256.js";
|
|
7
7
|
import { withheldLine } from "./text.js";
|
|
8
|
+
import { bytewise } from "../text/compare.js";
|
|
9
|
+
import { message } from "../product.js";
|
|
8
10
|
const SCHEMA = "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json";
|
|
9
11
|
const VERSION = "2.1.0";
|
|
10
12
|
/** The unit a finding's column counts. */
|
|
@@ -101,7 +103,7 @@ function lineHashCache(read) {
|
|
|
101
103
|
text = read(at.path);
|
|
102
104
|
}
|
|
103
105
|
catch (error) {
|
|
104
|
-
throw new RenderError(`read ${at.path} for its line fingerprint: ${
|
|
106
|
+
throw new RenderError(`read ${at.path} for its line fingerprint: ${message(error)}`);
|
|
105
107
|
}
|
|
106
108
|
hashes = lineHashes(text);
|
|
107
109
|
held.set(at.path, hashes);
|
|
@@ -226,7 +228,7 @@ export function sarif(report, options) {
|
|
|
226
228
|
...report.findings.map((found) => findingResult(found, index, hashOf, members)),
|
|
227
229
|
...report.stale_suppressions.map((stale) => staleResult(stale, index, hashOf)),
|
|
228
230
|
];
|
|
229
|
-
const languages = [...report.analyzer.languages].sort().join("+");
|
|
231
|
+
const languages = [...report.analyzer.languages].sort(bytewise).join("+");
|
|
230
232
|
const withheld = withheldLine(report.totals);
|
|
231
233
|
const log = {
|
|
232
234
|
$schema: SCHEMA,
|