@cplieger/deadset-ts 8.0.0-dev.14 → 8.0.0-dev.16
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/alias-chain.js +2 -2
- package/dist/src/analysis.js +43 -38
- package/dist/src/commands/read.js +6 -7
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +4 -2
- package/dist/src/contract/config.generated.js +1 -1
- package/dist/src/contract/contract.generated.js +120 -2
- package/dist/src/dependencies.js +81 -49
- package/dist/src/entries/build-scripts.js +222 -0
- package/dist/src/{configuration-files.js → entries/configuration-files.js} +20 -58
- package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
- package/dist/src/entries/conventions.js +739 -0
- package/dist/src/{html-entries.js → entries/html-entries.js} +3 -3
- package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +2 -2
- package/dist/src/entries/run-directories.js +77 -0
- package/dist/src/entries/sources.js +144 -0
- package/dist/src/entries/static-module.js +669 -0
- package/dist/src/entries/test-runners.js +684 -0
- package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
- package/dist/src/extensions.js +37 -0
- package/dist/src/facts.js +38 -0
- package/dist/src/file-facts.js +1 -1
- package/dist/src/findings/emitters.js +5 -8
- package/dist/src/findings/self-check.js +36 -20
- package/dist/src/findings-pass.js +5 -6
- package/dist/src/framework-lifecycle.js +1 -1
- package/dist/src/fs/manifest.js +15 -20
- package/dist/src/generated-file.js +172 -29
- package/dist/src/graph.js +2 -5
- package/dist/src/index.js +1 -1
- package/dist/src/injection-container.js +1 -1
- package/dist/src/inventory/commonjs.js +507 -0
- package/dist/src/inventory/enumerate.js +167 -0
- package/dist/src/inventory/syntax.js +236 -0
- package/dist/src/inventory.js +305 -705
- package/dist/src/javascript-file.js +26 -11
- package/dist/src/load/classify.js +28 -9
- package/dist/src/load/diagnostics.js +8 -1
- package/dist/src/load/discover.js +16 -8
- package/dist/src/load/package-scope.js +56 -0
- package/dist/src/load/plan.js +2 -2
- package/dist/src/load/synthesize.js +5 -5
- package/dist/src/load/workspace-manifests.js +1 -1
- package/dist/src/matrix.js +6 -13
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/paths.js +16 -0
- package/dist/src/platform-calls.js +78 -0
- package/dist/src/platform-symbols.js +140 -0
- package/dist/src/position.js +41 -0
- package/dist/src/ref.js +11 -20
- package/dist/src/references.js +281 -50
- package/dist/src/resolve.js +1 -1
- package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +4 -4
- package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
- package/dist/src/{manifest.js → roots/manifest.js} +4 -4
- package/dist/src/roots/published-reach.js +412 -0
- package/dist/src/roots/root-set.js +489 -0
- package/dist/src/roots/unseen-loads.js +208 -0
- package/dist/src/serialization-contract.js +1 -1
- package/dist/src/session.js +3 -0
- package/dist/src/source-files.js +5 -9
- package/dist/src/suppress-inline.js +33 -81
- package/dist/src/suppress.js +30 -32
- package/dist/src/test-support.js +4 -2
- package/dist/src/verbs/analyze.js +13 -1
- package/dist/src/verbs/describe.js +5 -3
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +2 -2
- package/src/analysis.ts +77 -53
- package/src/automatic-types.ts +1 -1
- package/src/commands/read.ts +6 -8
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +5 -1
- package/src/contract/config.generated.ts +1 -1
- package/src/contract/contract.generated.ts +139 -2
- package/src/dependencies.ts +98 -50
- package/src/entries/build-scripts.ts +292 -0
- package/src/{configuration-files.ts → entries/configuration-files.ts} +23 -75
- package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
- package/src/{conventions.ts → entries/conventions.ts} +347 -522
- package/src/{html-entries.ts → entries/html-entries.ts} +5 -5
- package/src/{mdx-entries.ts → entries/mdx-entries.ts} +4 -4
- package/src/entries/run-directories.ts +108 -0
- package/src/entries/sources.ts +266 -0
- package/src/entries/static-module.ts +857 -0
- package/src/entries/test-runners.ts +868 -0
- package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
- package/src/exempt.ts +3 -2
- package/src/extensions.ts +45 -0
- package/src/facts.ts +44 -0
- package/src/file-facts.ts +2 -2
- package/src/findings/emitters.ts +14 -10
- package/src/findings/self-check.ts +41 -22
- package/src/findings/visibility-narrowing.ts +1 -1
- package/src/findings-pass.ts +5 -12
- package/src/framework-lifecycle.ts +1 -1
- package/src/fs/manifest.ts +25 -21
- package/src/generated-file.ts +240 -36
- package/src/graph.ts +3 -6
- package/src/index.ts +1 -1
- package/src/injection-container.ts +1 -1
- package/src/inventory/commonjs.ts +696 -0
- package/src/inventory/enumerate.ts +203 -0
- package/src/inventory/syntax.ts +311 -0
- package/src/inventory.ts +424 -878
- package/src/javascript-file.ts +29 -12
- package/src/load/classify.ts +49 -13
- package/src/load/diagnostics.ts +11 -1
- package/src/load/discover.ts +28 -12
- package/src/load/package-scope.ts +74 -0
- package/src/load/plan.ts +11 -2
- package/src/load/synthesize.ts +6 -6
- package/src/load/test-globals.ts +1 -1
- package/src/load/workspace-manifests.ts +1 -1
- package/src/load/workspace-run.ts +1 -1
- package/src/matrix.ts +7 -14
- package/src/outside-tests.ts +1 -1
- package/src/paths.ts +18 -0
- package/src/platform-calls.ts +119 -0
- package/src/platform-symbols.ts +205 -0
- package/src/position.ts +42 -0
- package/src/ref.ts +11 -20
- package/src/references.ts +383 -73
- package/src/report.ts +1 -1
- package/src/resolve.ts +1 -1
- package/src/{configured-declarations.ts → roots/configured-declarations.ts} +7 -7
- package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
- package/src/{manifest.ts → roots/manifest.ts} +5 -5
- package/src/roots/published-reach.ts +481 -0
- package/src/{roots.ts → roots/root-set.ts} +203 -630
- package/src/roots/unseen-loads.ts +307 -0
- package/src/serialization-contract.ts +1 -1
- package/src/session.ts +17 -1
- package/src/source-files.ts +5 -10
- package/src/suppress-inline.ts +53 -89
- package/src/suppress.ts +48 -33
- package/src/test-support.ts +8 -3
- package/src/verbs/analyze.ts +17 -4
- package/src/verbs/describe.ts +5 -2
- package/src/verbs/explain.ts +1 -1
- package/dist/src/build-scripts.js +0 -418
- package/dist/src/conventions.js +0 -907
- package/dist/src/entry-points.js +0 -513
- package/dist/src/roots.js +0 -865
- package/dist/src/unseen-loads.js +0 -100
- package/src/build-scripts.ts +0 -536
- package/src/entry-points.ts +0 -661
- package/src/unseen-loads.ts +0 -134
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { SourceFile } from "@typescript/native/ast";
|
|
9
|
-
import { pathsNamed, readCommands } from "
|
|
10
|
-
import type { Host } from "
|
|
11
|
-
import { isRecord } from "
|
|
12
|
-
import { joinPath, resolvePath } from "
|
|
13
|
-
import type { SourceFiles } from "
|
|
14
|
-
import { filesIn } from "
|
|
15
|
-
import { yamlDocument } from "
|
|
9
|
+
import { pathsNamed, readCommands, type CommandReading } from "../commands/read.ts";
|
|
10
|
+
import type { Host } from "../host.ts";
|
|
11
|
+
import { isRecord } from "../json-document.ts";
|
|
12
|
+
import { joinPath, resolvePath } from "../paths.ts";
|
|
13
|
+
import type { SourceFiles } from "../source-files.ts";
|
|
14
|
+
import { filesIn } from "../fs/tree.ts";
|
|
15
|
+
import { yamlDocument } from "../text/yaml.ts";
|
|
16
16
|
|
|
17
17
|
/** One file a workflow step, git hook or recipe runs or names. */
|
|
18
18
|
interface WorkflowEntry {
|
|
@@ -204,18 +204,36 @@ export function commandFileTexts(host: Host, targetRoot: string): readonly strin
|
|
|
204
204
|
].flatMap((path) => readText(host, path) ?? []);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/** What the target root's workflow steps, git hooks, hook managers and makefile run, read once per run. */
|
|
208
|
+
export interface Workflows {
|
|
209
|
+
/** Each command read, its held text followed by the directory the file writes for it. */
|
|
210
|
+
readonly readings: readonly CommandReading[];
|
|
211
|
+
/** The whole text of each file a command is read from. */
|
|
212
|
+
readonly texts: readonly string[];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Reads the target root's workflow steps, git hooks, hook managers and makefile. */
|
|
216
|
+
export function readWorkflows(host: Host, targetRoot: string): Workflows {
|
|
217
|
+
return {
|
|
218
|
+
readings: workflowCommands(host, targetRoot).map(({ text, dir, writtenDirectory }) => {
|
|
219
|
+
const reading = readCommands(text, dir);
|
|
220
|
+
return { ...reading, held: [reading.held, writtenDirectory ?? ""].join("\n") };
|
|
221
|
+
}),
|
|
222
|
+
texts: commandFileTexts(host, targetRoot),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
207
226
|
/**
|
|
208
227
|
* The own files the target root's workflow steps, git hooks and recipes run or name, as
|
|
209
228
|
* `pathsNamed` reads each command.
|
|
210
229
|
*/
|
|
211
230
|
export function workflowEntries(
|
|
212
|
-
|
|
213
|
-
targetRoot: string,
|
|
231
|
+
workflows: Workflows,
|
|
214
232
|
files: SourceFiles,
|
|
215
233
|
): readonly WorkflowEntry[] {
|
|
216
234
|
const found: WorkflowEntry[] = [];
|
|
217
|
-
for (const
|
|
218
|
-
for (const { path, written } of pathsNamed(
|
|
235
|
+
for (const reading of workflows.readings) {
|
|
236
|
+
for (const { path, written } of pathsNamed(reading)) {
|
|
219
237
|
for (const file of files.named(path)) {
|
|
220
238
|
found.push({ file, source: written });
|
|
221
239
|
}
|
package/src/exempt.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { EXEMPTION_CLASSES } from "./contract/contract.generated.ts";
|
|
11
|
+
import type { GeneratedFact } from "./generated-file.ts";
|
|
11
12
|
import type { ResolvedConfig } from "./config.ts";
|
|
12
13
|
import {
|
|
13
14
|
TS_EXEMPTION_CLASSES,
|
|
@@ -51,8 +52,8 @@ export interface DetectorInput<Brand> {
|
|
|
51
52
|
readonly ts: ResolvedConfig["ts"];
|
|
52
53
|
/** The absolute path of each declared consumer, whose modules are part of the analysis. */
|
|
53
54
|
readonly consumers: readonly string[];
|
|
54
|
-
/** The project's generated files, by path below the target root, each with
|
|
55
|
-
readonly generated?: ReadonlyMap<string,
|
|
55
|
+
/** The project's generated files, by path below the target root, each with what decided it. */
|
|
56
|
+
readonly generated?: ReadonlyMap<string, GeneratedFact>;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
/**
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** The extension lists the analysis reads files by, each written once. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The extensions tried, in order, after a path written without one: a relative specifier
|
|
5
|
+
* of a configuration file (`roots.5`) and a path a command runs or loads (`roots.13`).
|
|
6
|
+
*/
|
|
7
|
+
export const APPENDED_EXTENSIONS = [
|
|
8
|
+
".ts",
|
|
9
|
+
".tsx",
|
|
10
|
+
".mts",
|
|
11
|
+
".cts",
|
|
12
|
+
".js",
|
|
13
|
+
".jsx",
|
|
14
|
+
".mjs",
|
|
15
|
+
".cjs",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
/** The extensions of a module a tool runs or loads as its configuration (`roots.2`). */
|
|
19
|
+
export const MODULE_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"] as const;
|
|
20
|
+
|
|
21
|
+
/** The extensions of a file a build writes from a source file. */
|
|
22
|
+
export const OUTPUT_EXTENSIONS = [
|
|
23
|
+
".js",
|
|
24
|
+
".mjs",
|
|
25
|
+
".cjs",
|
|
26
|
+
".jsx",
|
|
27
|
+
".d.ts",
|
|
28
|
+
".d.mts",
|
|
29
|
+
".d.cts",
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
/** One list as a glob's brace list, its dots dropped: `{js,mjs}`. */
|
|
33
|
+
export function braced(extensions: readonly string[]): string {
|
|
34
|
+
return `{${extensions.map((one) => one.slice(1)).join(",")}}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The extension of one list a name ends with, the longest where several do. */
|
|
38
|
+
export function extensionOf(name: string, extensions: readonly string[]): string | undefined {
|
|
39
|
+
return extensions
|
|
40
|
+
.filter((one) => name.endsWith(one))
|
|
41
|
+
.reduce<string | undefined>(
|
|
42
|
+
(longest, one) => (one.length > (longest?.length ?? 0) ? one : longest),
|
|
43
|
+
undefined,
|
|
44
|
+
);
|
|
45
|
+
}
|
package/src/facts.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The facts document `--facts` writes, `bindings.1` and `bindings.2`: the run's generated
|
|
3
|
+
* files under the one decision the generated-file class reads. It carries no binding
|
|
4
|
+
* record, so it states `evaluated: false`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FACTS_VERSION } from "./contract/contract.generated.ts";
|
|
8
|
+
import type { GeneratedFact } from "./generated-file.ts";
|
|
9
|
+
import { LANGUAGE, PRODUCT } from "./product.ts";
|
|
10
|
+
import { bytewise } from "./text/compare.ts";
|
|
11
|
+
|
|
12
|
+
/** The facts document of one run, as the text written to the path `--facts` names. */
|
|
13
|
+
export function factsDocument(
|
|
14
|
+
version: string,
|
|
15
|
+
generated: ReadonlyMap<string, GeneratedFact>,
|
|
16
|
+
): string {
|
|
17
|
+
const document = {
|
|
18
|
+
facts_version: FACTS_VERSION,
|
|
19
|
+
analyzer: { name: PRODUCT, version },
|
|
20
|
+
language: LANGUAGE,
|
|
21
|
+
evaluated: false,
|
|
22
|
+
generated: [...generated]
|
|
23
|
+
.sort(([a], [b]) => bytewise(a, b))
|
|
24
|
+
.map(([path, fact]) => ({
|
|
25
|
+
path,
|
|
26
|
+
line: fact.line,
|
|
27
|
+
evidence: fact.evidence,
|
|
28
|
+
detail: fact.detail,
|
|
29
|
+
...(fact.generator === undefined
|
|
30
|
+
? {}
|
|
31
|
+
: {
|
|
32
|
+
generator: {
|
|
33
|
+
row: fact.generator.row,
|
|
34
|
+
...(fact.generator.version === undefined
|
|
35
|
+
? {}
|
|
36
|
+
: { version: fact.generator.version }),
|
|
37
|
+
},
|
|
38
|
+
schema_files: fact.generator.schemaFiles,
|
|
39
|
+
}),
|
|
40
|
+
})),
|
|
41
|
+
bindings: [],
|
|
42
|
+
};
|
|
43
|
+
return `${JSON.stringify(document, null, 2)}\n`;
|
|
44
|
+
}
|
package/src/file-facts.ts
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
import type { SourceFile } from "@typescript/native/ast";
|
|
8
8
|
import type { Host } from "./host.ts";
|
|
9
|
-
import { packageScope } from "./
|
|
9
|
+
import { packageScope } from "./load/package-scope.ts";
|
|
10
10
|
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
11
11
|
import { renderRef } from "./ref.ts";
|
|
12
12
|
import type { ProjectView } from "./session.ts";
|
|
13
|
-
import type { UnseenLoad } from "./unseen-loads.ts";
|
|
13
|
+
import type { UnseenLoad } from "./roots/unseen-loads.ts";
|
|
14
14
|
import { filesBelow } from "./fs/tree.ts";
|
|
15
15
|
|
|
16
16
|
/** A file name the compiler reads as a source file. */
|
package/src/findings/emitters.ts
CHANGED
|
@@ -161,18 +161,22 @@ function dialledOver(
|
|
|
161
161
|
unmarked: readonly CompletedFinding[],
|
|
162
162
|
): Omit<DecidedFindings, "would"> {
|
|
163
163
|
const would = [...unmarked, ...found];
|
|
164
|
-
const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked));
|
|
165
164
|
const unanswered = heldKeys(input);
|
|
165
|
+
const heldIds = new Set(input.swept.heldByUnanswered);
|
|
166
|
+
const keyOf = (finding: CompletedFinding): string =>
|
|
167
|
+
declarationKey(finding.symbol.holder ?? finding.symbol.ref, finding.position.path);
|
|
168
|
+
const held = (finding: CompletedFinding): boolean =>
|
|
169
|
+
unanswered.has(keyOf(finding)) || insideSkipped(input.swept.skipped, finding.position);
|
|
170
|
+
const ledger = ledgerOf(
|
|
171
|
+
suppressed.records,
|
|
172
|
+
would,
|
|
173
|
+
dialsOver(input, found, suppressed, unmarked),
|
|
174
|
+
{ withholds: held, holdsRecord: (record) => heldIds.has(record.bound) },
|
|
175
|
+
);
|
|
166
176
|
const markup = markupKeys(input);
|
|
167
|
-
const candidates = found.filter(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
!ledger.withheld(finding) &&
|
|
171
|
-
!unanswered.has(key) &&
|
|
172
|
-
!markup.has(key) &&
|
|
173
|
-
!insideSkipped(input.swept.skipped, finding.position)
|
|
174
|
-
);
|
|
175
|
-
});
|
|
177
|
+
const candidates = found.filter(
|
|
178
|
+
(finding) => !ledger.withheld(finding) && !held(finding) && !markup.has(keyOf(finding)),
|
|
179
|
+
);
|
|
176
180
|
const kept = reportable(input, candidates);
|
|
177
181
|
const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
|
|
178
182
|
return {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { CODE } from "../contract/contract.generated.ts";
|
|
1
|
+
import { CODE, REMEDIES, type Subject } from "../contract/contract.generated.ts";
|
|
2
2
|
import { REPOSITORY_DOCUMENT } from "../config.ts";
|
|
3
3
|
import type { Finding, FindingDetails, SuppressionEntry } from "../finding.ts";
|
|
4
4
|
import type { InventorySymbol } from "../inventory.ts";
|
|
5
5
|
import { FIXED_SEVERITY_CODES } from "../kinds.ts";
|
|
6
6
|
import type { Position } from "../position.ts";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
names,
|
|
9
|
+
subjectOfCode,
|
|
10
10
|
type Mechanism,
|
|
11
11
|
type Refusal,
|
|
12
12
|
type SuppressionRecord,
|
|
@@ -72,6 +72,43 @@ export function fileRefsOf(symbols: readonly InventorySymbol[]): FileRefs {
|
|
|
72
72
|
return (path) => byPath.get(path);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Why a record of `mechanism` naming `codes` can match nothing, one clause per subject whose
|
|
77
|
+
* findings kinds.json's remedies table lets no record of that mechanism name, in subject order.
|
|
78
|
+
*/
|
|
79
|
+
function unnameable(codes: readonly string[], mechanism: Mechanism): readonly string[] {
|
|
80
|
+
return (Object.keys(REMEDIES) as Subject[]).flatMap((subject) => {
|
|
81
|
+
const named = codes.filter((code) => subjectOfCode(code) === subject);
|
|
82
|
+
if (named.length === 0 || names(subject, mechanism)) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
const { names: what, suppressions } = REMEDIES[subject];
|
|
86
|
+
const reports = `${spelled(named)} ${named.length === 1 ? "reports" : "each report"} ${what}`;
|
|
87
|
+
if (suppressions.length > 0) {
|
|
88
|
+
const records = suppressions.map((one) => articled(WRITTEN[one]));
|
|
89
|
+
return [`${reports}, which only ${spelledOr(records)} can name`];
|
|
90
|
+
}
|
|
91
|
+
const settable = named.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
|
|
92
|
+
const remedy =
|
|
93
|
+
settable.length === 0
|
|
94
|
+
? "fix what the finding names instead"
|
|
95
|
+
: `fix what the finding names or set the severity of ${spelled(settable)} instead`;
|
|
96
|
+
return [`${reports}, which no suppression can name; ${remedy}`];
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A record's written name with its indefinite article. */
|
|
101
|
+
function articled(name: string): string {
|
|
102
|
+
return `${/^[aeiou]/u.test(name) ? "an" : "a"} ${name}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** A list as a sentence names alternatives. */
|
|
106
|
+
function spelledOr(items: readonly string[]): string {
|
|
107
|
+
return items.length < 2
|
|
108
|
+
? items.join("")
|
|
109
|
+
: `${items.slice(0, -1).join(", ")} or ${items.at(-1) ?? ""}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
75
112
|
/** A list of codes as a sentence names them. */
|
|
76
113
|
function spelled(codes: readonly string[]): string {
|
|
77
114
|
if (codes.length < 2) {
|
|
@@ -176,26 +213,8 @@ export function staleSuppressions(
|
|
|
176
213
|
const reports = [...new Set(records.flatMap((one) => one.reports))]
|
|
177
214
|
.filter((code) => !codes.includes(code))
|
|
178
215
|
.sort(bytewise);
|
|
179
|
-
const rows = codes.filter(isUnsuppressibleCode);
|
|
180
|
-
const dependencies = first.mechanism === "inline" ? codes.filter(isDependencyCode) : [];
|
|
181
|
-
const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
|
|
182
|
-
const remedy =
|
|
183
|
-
settable.length === 0
|
|
184
|
-
? "fix what the finding names instead"
|
|
185
|
-
: `fix what the finding names or set the severity of ${spelled(settable)} instead`;
|
|
186
216
|
const declared = reports.length === 0 ? "" : `its declaration reports ${spelled(reports)}`;
|
|
187
|
-
const because =
|
|
188
|
-
...(rows.length > 0
|
|
189
|
-
? [
|
|
190
|
-
`${spelled(rows)} ${rows.length === 1 ? "reports a row" : "report rows"} of a document, which no suppression can name; ${remedy}`,
|
|
191
|
-
]
|
|
192
|
-
: []),
|
|
193
|
-
...(dependencies.length > 0
|
|
194
|
-
? [
|
|
195
|
-
`${spelled(dependencies)} ${dependencies.length === 1 ? "reports a dependency row" : "report dependency rows"}, which only an ignore entry or a baseline row can name`,
|
|
196
|
-
]
|
|
197
|
-
: []),
|
|
198
|
-
];
|
|
217
|
+
const because = unnameable(codes, first.mechanism);
|
|
199
218
|
const instead =
|
|
200
219
|
because.length > 0
|
|
201
220
|
? `, because ${because.join("; ")}${declared === "" ? "" : `; ${declared}`}`
|
|
@@ -4,7 +4,7 @@ import { OUTSIDE, type Graph } from "../graph.ts";
|
|
|
4
4
|
import type { InventorySymbol, SymbolKind } from "../inventory.ts";
|
|
5
5
|
import type { MatrixCandidate } from "../matrix.ts";
|
|
6
6
|
import { declarationKey } from "../ref.ts";
|
|
7
|
-
import type { RootKind } from "../roots.ts";
|
|
7
|
+
import type { RootKind } from "../roots/root-set.ts";
|
|
8
8
|
import { runnerCalls } from "../sweep.ts";
|
|
9
9
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
10
10
|
import { writeOnlyDeclarations } from "./reads-and-writes.ts";
|
package/src/findings-pass.ts
CHANGED
|
@@ -16,13 +16,7 @@ import {
|
|
|
16
16
|
type StaleRecord,
|
|
17
17
|
type StaleSuppression,
|
|
18
18
|
} from "./findings/self-check.ts";
|
|
19
|
-
import {
|
|
20
|
-
isDependencySubject,
|
|
21
|
-
isRowSubject,
|
|
22
|
-
pairs,
|
|
23
|
-
suppressionCounts,
|
|
24
|
-
type Ledger,
|
|
25
|
-
} from "./suppress.ts";
|
|
19
|
+
import { namesKind, pairs, suppressionCounts, type Ledger } from "./suppress.ts";
|
|
26
20
|
import type { Provenance, Recorded } from "./suppress-file.ts";
|
|
27
21
|
import { bytewise } from "./text/compare.ts";
|
|
28
22
|
import { PRODUCT } from "./product.ts";
|
|
@@ -131,15 +125,14 @@ export interface RecordableFinding {
|
|
|
131
125
|
|
|
132
126
|
/**
|
|
133
127
|
* The rows one round of a baseline write records from its findings, in their order. A
|
|
134
|
-
* finding
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* once.
|
|
128
|
+
* finding no baseline row can name is not recorded: a row recording it would be stale on
|
|
129
|
+
* the first read back. Two findings that would record the same row, two parts of one
|
|
130
|
+
* declaration under one code, record it once.
|
|
138
131
|
*/
|
|
139
132
|
export function recordedFindings(findings: readonly RecordableFinding[]): readonly Recorded[] {
|
|
140
133
|
const rows = new Map<string, Recorded>();
|
|
141
134
|
for (const finding of findings) {
|
|
142
|
-
if (
|
|
135
|
+
if (!namesKind(finding.symbol.kind, "baseline")) {
|
|
143
136
|
continue;
|
|
144
137
|
}
|
|
145
138
|
const row = { code: finding.code, symbol: finding.symbol.ref, path: finding.position.path };
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
resolveEntries,
|
|
27
27
|
spelledEntry,
|
|
28
28
|
type ResolvedEntry,
|
|
29
|
-
} from "./configured-declarations.ts";
|
|
29
|
+
} from "./roots/configured-declarations.ts";
|
|
30
30
|
import type { DetectorInput, Evidence } from "./exempt.ts";
|
|
31
31
|
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
32
32
|
import { renderPosition } from "./position.ts";
|
package/src/fs/manifest.ts
CHANGED
|
@@ -22,24 +22,36 @@ export function inInstalledPackages(path: string): boolean {
|
|
|
22
22
|
return path.split("/").includes(INSTALLED_PACKAGES);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
/** What one JSON file reads as: its text and decoded value, text that does not parse, or a file that cannot be read, with the read's error. */
|
|
26
|
+
type JSONRead =
|
|
27
|
+
| { readonly kind: "read"; readonly text: string; readonly value: unknown }
|
|
28
|
+
| { readonly kind: "malformed" }
|
|
29
|
+
| { readonly kind: "unread"; readonly reason: string };
|
|
30
|
+
|
|
31
|
+
/** One JSON file, read and parsed, as {@link JSONRead} tells its states apart. */
|
|
32
|
+
export function readJSONFile(host: Host, path: string): JSONRead {
|
|
30
33
|
let text: string;
|
|
31
34
|
try {
|
|
32
35
|
text = host.readFile(path);
|
|
33
|
-
} catch {
|
|
34
|
-
return
|
|
36
|
+
} catch (error: unknown) {
|
|
37
|
+
return { kind: "unread", reason: message(error) };
|
|
35
38
|
}
|
|
36
39
|
try {
|
|
37
|
-
return { text, value: JSON.parse(text) as unknown };
|
|
40
|
+
return { kind: "read", text, value: JSON.parse(text) as unknown };
|
|
38
41
|
} catch {
|
|
39
|
-
return
|
|
42
|
+
return { kind: "malformed" };
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
/** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
|
|
47
|
+
export function readDocument(
|
|
48
|
+
host: Host,
|
|
49
|
+
path: string,
|
|
50
|
+
): { text: string; value: unknown } | undefined {
|
|
51
|
+
const read = readJSONFile(host, path);
|
|
52
|
+
return read.kind === "read" ? { text: read.text, value: read.value } : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
/** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
|
|
44
56
|
export function readJSON(host: Host, path: string): unknown {
|
|
45
57
|
return readDocument(host, path)?.value;
|
|
@@ -71,19 +83,11 @@ export function readManifestAt(host: Host, dir: string): ManifestRead {
|
|
|
71
83
|
if (!holdsManifest(host, dir)) {
|
|
72
84
|
return { kind: "absent" };
|
|
73
85
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
} catch (error: unknown) {
|
|
78
|
-
return { kind: "unread", reason: message(error) };
|
|
79
|
-
}
|
|
80
|
-
let value: unknown;
|
|
81
|
-
try {
|
|
82
|
-
value = JSON.parse(text);
|
|
83
|
-
} catch {
|
|
84
|
-
return { kind: "malformed" };
|
|
86
|
+
const read = readJSONFile(host, joinPath(dir, MANIFEST));
|
|
87
|
+
if (read.kind !== "read") {
|
|
88
|
+
return read;
|
|
85
89
|
}
|
|
86
|
-
return isRecord(value) ? { kind: "read", manifest: value } : { kind: "malformed" };
|
|
90
|
+
return isRecord(read.value) ? { kind: "read", manifest: read.value } : { kind: "malformed" };
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
/** Whether one directory holds a manifest file. */
|