@cplieger/deadset-ts 8.0.0-dev.15 → 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/analysis.js +41 -37
- package/dist/src/commands/read.js +6 -7
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +3 -1
- package/dist/src/contract/config.generated.js +1 -1
- package/dist/src/contract/contract.generated.js +119 -2
- package/dist/src/dependencies.js +79 -48
- 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/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 -4
- package/dist/src/index.js +1 -1
- package/dist/src/injection-container.js +1 -1
- package/dist/src/inventory/commonjs.js +62 -17
- package/dist/src/inventory/enumerate.js +2 -1
- 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 +15 -7
- 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/references.js +202 -32
- 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/source-files.js +5 -9
- 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 +2 -2
- package/src/analysis.ts +75 -52
- 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 +4 -0
- package/src/contract/config.generated.ts +1 -1
- package/src/contract/contract.generated.ts +138 -2
- package/src/dependencies.ts +96 -49
- 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 +1 -1
- 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 -5
- package/src/index.ts +1 -1
- package/src/injection-container.ts +1 -1
- package/src/inventory/commonjs.ts +102 -17
- package/src/inventory/enumerate.ts +2 -1
- 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 +27 -11
- 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/references.ts +281 -34
- 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/source-files.ts +5 -10
- 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 -444
- package/dist/src/own-body.js +0 -28
- package/dist/src/roots.js +0 -865
- package/dist/src/unseen-loads.js +0 -189
- package/src/build-scripts.ts +0 -536
- package/src/entry-points.ts +0 -584
- package/src/own-body.ts +0 -42
- package/src/unseen-loads.ts +0 -282
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* it runs in. A workflow or hook manager file is read by `text/yaml.ts`, and one it does not
|
|
5
5
|
* read gives no command; each command is read by `commands/read.ts`.
|
|
6
6
|
*/
|
|
7
|
-
import { pathsNamed, readCommands } from "
|
|
8
|
-
import { isRecord } from "
|
|
9
|
-
import { joinPath, resolvePath } from "
|
|
10
|
-
import { filesIn } from "
|
|
11
|
-
import { yamlDocument } from "
|
|
7
|
+
import { pathsNamed, readCommands } from "../commands/read.js";
|
|
8
|
+
import { isRecord } from "../json-document.js";
|
|
9
|
+
import { joinPath, resolvePath } from "../paths.js";
|
|
10
|
+
import { filesIn } from "../fs/tree.js";
|
|
11
|
+
import { yamlDocument } from "../text/yaml.js";
|
|
12
12
|
const WORKFLOWS = ".github/workflows";
|
|
13
13
|
/** The directories whose every file is a git hook script, run as the shell runs it. */
|
|
14
14
|
const HOOK_DIRS = [".husky", ".githooks"];
|
|
@@ -144,14 +144,24 @@ export function commandFileTexts(host, targetRoot) {
|
|
|
144
144
|
...[makefileAt(host, root) ?? []].flat(),
|
|
145
145
|
].flatMap((path) => readText(host, path) ?? []);
|
|
146
146
|
}
|
|
147
|
+
/** Reads the target root's workflow steps, git hooks, hook managers and makefile. */
|
|
148
|
+
export function readWorkflows(host, targetRoot) {
|
|
149
|
+
return {
|
|
150
|
+
readings: workflowCommands(host, targetRoot).map(({ text, dir, writtenDirectory }) => {
|
|
151
|
+
const reading = readCommands(text, dir);
|
|
152
|
+
return { ...reading, held: [reading.held, writtenDirectory ?? ""].join("\n") };
|
|
153
|
+
}),
|
|
154
|
+
texts: commandFileTexts(host, targetRoot),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
147
157
|
/**
|
|
148
158
|
* The own files the target root's workflow steps, git hooks and recipes run or name, as
|
|
149
159
|
* `pathsNamed` reads each command.
|
|
150
160
|
*/
|
|
151
|
-
export function workflowEntries(
|
|
161
|
+
export function workflowEntries(workflows, files) {
|
|
152
162
|
const found = [];
|
|
153
|
-
for (const
|
|
154
|
-
for (const { path, written } of pathsNamed(
|
|
163
|
+
for (const reading of workflows.readings) {
|
|
164
|
+
for (const { path, written } of pathsNamed(reading)) {
|
|
155
165
|
for (const file of files.named(path)) {
|
|
156
166
|
found.push({ file, source: written });
|
|
157
167
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** The extension lists the analysis reads files by, each written once. */
|
|
2
|
+
/**
|
|
3
|
+
* The extensions tried, in order, after a path written without one: a relative specifier
|
|
4
|
+
* of a configuration file (`roots.5`) and a path a command runs or loads (`roots.13`).
|
|
5
|
+
*/
|
|
6
|
+
export const APPENDED_EXTENSIONS = [
|
|
7
|
+
".ts",
|
|
8
|
+
".tsx",
|
|
9
|
+
".mts",
|
|
10
|
+
".cts",
|
|
11
|
+
".js",
|
|
12
|
+
".jsx",
|
|
13
|
+
".mjs",
|
|
14
|
+
".cjs",
|
|
15
|
+
];
|
|
16
|
+
/** The extensions of a module a tool runs or loads as its configuration (`roots.2`). */
|
|
17
|
+
export const MODULE_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"];
|
|
18
|
+
/** The extensions of a file a build writes from a source file. */
|
|
19
|
+
export const OUTPUT_EXTENSIONS = [
|
|
20
|
+
".js",
|
|
21
|
+
".mjs",
|
|
22
|
+
".cjs",
|
|
23
|
+
".jsx",
|
|
24
|
+
".d.ts",
|
|
25
|
+
".d.mts",
|
|
26
|
+
".d.cts",
|
|
27
|
+
];
|
|
28
|
+
/** One list as a glob's brace list, its dots dropped: `{js,mjs}`. */
|
|
29
|
+
export function braced(extensions) {
|
|
30
|
+
return `{${extensions.map((one) => one.slice(1)).join(",")}}`;
|
|
31
|
+
}
|
|
32
|
+
/** The extension of one list a name ends with, the longest where several do. */
|
|
33
|
+
export function extensionOf(name, extensions) {
|
|
34
|
+
return extensions
|
|
35
|
+
.filter((one) => name.endsWith(one))
|
|
36
|
+
.reduce((longest, one) => (one.length > (longest?.length ?? 0) ? one : longest), undefined);
|
|
37
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
import { FACTS_VERSION } from "./contract/contract.generated.js";
|
|
7
|
+
import { LANGUAGE, PRODUCT } from "./product.js";
|
|
8
|
+
import { bytewise } from "./text/compare.js";
|
|
9
|
+
/** The facts document of one run, as the text written to the path `--facts` names. */
|
|
10
|
+
export function factsDocument(version, generated) {
|
|
11
|
+
const document = {
|
|
12
|
+
facts_version: FACTS_VERSION,
|
|
13
|
+
analyzer: { name: PRODUCT, version },
|
|
14
|
+
language: LANGUAGE,
|
|
15
|
+
evaluated: false,
|
|
16
|
+
generated: [...generated]
|
|
17
|
+
.sort(([a], [b]) => bytewise(a, b))
|
|
18
|
+
.map(([path, fact]) => ({
|
|
19
|
+
path,
|
|
20
|
+
line: fact.line,
|
|
21
|
+
evidence: fact.evidence,
|
|
22
|
+
detail: fact.detail,
|
|
23
|
+
...(fact.generator === undefined
|
|
24
|
+
? {}
|
|
25
|
+
: {
|
|
26
|
+
generator: {
|
|
27
|
+
row: fact.generator.row,
|
|
28
|
+
...(fact.generator.version === undefined
|
|
29
|
+
? {}
|
|
30
|
+
: { version: fact.generator.version }),
|
|
31
|
+
},
|
|
32
|
+
schema_files: fact.generator.schemaFiles,
|
|
33
|
+
}),
|
|
34
|
+
})),
|
|
35
|
+
bindings: [],
|
|
36
|
+
};
|
|
37
|
+
return `${JSON.stringify(document, null, 2)}\n`;
|
|
38
|
+
}
|
|
@@ -96,16 +96,13 @@ function atPossible(input) {
|
|
|
96
96
|
*/
|
|
97
97
|
function dialledOver(input, suppressed, found, unmarked) {
|
|
98
98
|
const would = [...unmarked, ...found];
|
|
99
|
-
const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked));
|
|
100
99
|
const unanswered = heldKeys(input);
|
|
100
|
+
const heldIds = new Set(input.swept.heldByUnanswered);
|
|
101
|
+
const keyOf = (finding) => declarationKey(finding.symbol.holder ?? finding.symbol.ref, finding.position.path);
|
|
102
|
+
const held = (finding) => unanswered.has(keyOf(finding)) || insideSkipped(input.swept.skipped, finding.position);
|
|
103
|
+
const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked), { withholds: held, holdsRecord: (record) => heldIds.has(record.bound) });
|
|
101
104
|
const markup = markupKeys(input);
|
|
102
|
-
const candidates = found.filter((finding) =>
|
|
103
|
-
const key = declarationKey(finding.symbol.holder ?? finding.symbol.ref, finding.position.path);
|
|
104
|
-
return (!ledger.withheld(finding) &&
|
|
105
|
-
!unanswered.has(key) &&
|
|
106
|
-
!markup.has(key) &&
|
|
107
|
-
!insideSkipped(input.swept.skipped, finding.position));
|
|
108
|
-
});
|
|
105
|
+
const candidates = found.filter((finding) => !ledger.withheld(finding) && !held(finding) && !markup.has(keyOf(finding)));
|
|
109
106
|
const kept = reportable(input, candidates);
|
|
110
107
|
const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
|
|
111
108
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { CODE } from "../contract/contract.generated.js";
|
|
1
|
+
import { CODE, REMEDIES } from "../contract/contract.generated.js";
|
|
2
2
|
import { REPOSITORY_DOCUMENT } from "../config.js";
|
|
3
3
|
import { FIXED_SEVERITY_CODES } from "../kinds.js";
|
|
4
|
-
import {
|
|
4
|
+
import { names, subjectOfCode, } from "../suppress.js";
|
|
5
5
|
import { bytewise } from "../text/compare.js";
|
|
6
6
|
/** The subject kind of a finding about a configured declaration. */
|
|
7
7
|
const DECLARATION_SUBJECT = "configured-declaration";
|
|
@@ -24,6 +24,39 @@ export function fileRefsOf(symbols) {
|
|
|
24
24
|
.map((symbol) => [symbol.position.path, symbol.ref]));
|
|
25
25
|
return (path) => byPath.get(path);
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Why a record of `mechanism` naming `codes` can match nothing, one clause per subject whose
|
|
29
|
+
* findings kinds.json's remedies table lets no record of that mechanism name, in subject order.
|
|
30
|
+
*/
|
|
31
|
+
function unnameable(codes, mechanism) {
|
|
32
|
+
return Object.keys(REMEDIES).flatMap((subject) => {
|
|
33
|
+
const named = codes.filter((code) => subjectOfCode(code) === subject);
|
|
34
|
+
if (named.length === 0 || names(subject, mechanism)) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const { names: what, suppressions } = REMEDIES[subject];
|
|
38
|
+
const reports = `${spelled(named)} ${named.length === 1 ? "reports" : "each report"} ${what}`;
|
|
39
|
+
if (suppressions.length > 0) {
|
|
40
|
+
const records = suppressions.map((one) => articled(WRITTEN[one]));
|
|
41
|
+
return [`${reports}, which only ${spelledOr(records)} can name`];
|
|
42
|
+
}
|
|
43
|
+
const settable = named.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
|
|
44
|
+
const remedy = settable.length === 0
|
|
45
|
+
? "fix what the finding names instead"
|
|
46
|
+
: `fix what the finding names or set the severity of ${spelled(settable)} instead`;
|
|
47
|
+
return [`${reports}, which no suppression can name; ${remedy}`];
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** A record's written name with its indefinite article. */
|
|
51
|
+
function articled(name) {
|
|
52
|
+
return `${/^[aeiou]/u.test(name) ? "an" : "a"} ${name}`;
|
|
53
|
+
}
|
|
54
|
+
/** A list as a sentence names alternatives. */
|
|
55
|
+
function spelledOr(items) {
|
|
56
|
+
return items.length < 2
|
|
57
|
+
? items.join("")
|
|
58
|
+
: `${items.slice(0, -1).join(", ")} or ${items.at(-1) ?? ""}`;
|
|
59
|
+
}
|
|
27
60
|
/** A list of codes as a sentence names them. */
|
|
28
61
|
function spelled(codes) {
|
|
29
62
|
if (codes.length < 2) {
|
|
@@ -85,25 +118,8 @@ export function staleSuppressions(stale, files) {
|
|
|
85
118
|
const reports = [...new Set(records.flatMap((one) => one.reports))]
|
|
86
119
|
.filter((code) => !codes.includes(code))
|
|
87
120
|
.sort(bytewise);
|
|
88
|
-
const rows = codes.filter(isUnsuppressibleCode);
|
|
89
|
-
const dependencies = first.mechanism === "inline" ? codes.filter(isDependencyCode) : [];
|
|
90
|
-
const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
|
|
91
|
-
const remedy = settable.length === 0
|
|
92
|
-
? "fix what the finding names instead"
|
|
93
|
-
: `fix what the finding names or set the severity of ${spelled(settable)} instead`;
|
|
94
121
|
const declared = reports.length === 0 ? "" : `its declaration reports ${spelled(reports)}`;
|
|
95
|
-
const because =
|
|
96
|
-
...(rows.length > 0
|
|
97
|
-
? [
|
|
98
|
-
`${spelled(rows)} ${rows.length === 1 ? "reports a row" : "report rows"} of a document, which no suppression can name; ${remedy}`,
|
|
99
|
-
]
|
|
100
|
-
: []),
|
|
101
|
-
...(dependencies.length > 0
|
|
102
|
-
? [
|
|
103
|
-
`${spelled(dependencies)} ${dependencies.length === 1 ? "reports a dependency row" : "report dependency rows"}, which only an ignore entry or a baseline row can name`,
|
|
104
|
-
]
|
|
105
|
-
: []),
|
|
106
|
-
];
|
|
122
|
+
const because = unnameable(codes, first.mechanism);
|
|
107
123
|
const instead = because.length > 0
|
|
108
124
|
? `, because ${because.join("; ")}${declared === "" ? "" : `; ${declared}`}`
|
|
109
125
|
: declared === ""
|
|
@@ -7,7 +7,7 @@ import { EXIT_CLEAN, EXIT_FINDINGS, EXIT_PENDING } from "./contract/contract.gen
|
|
|
7
7
|
import { fails } from "./config.js";
|
|
8
8
|
import { decidedFindings } from "./findings/emitters.js";
|
|
9
9
|
import { fileRefsOf, staleSuppressions, } from "./findings/self-check.js";
|
|
10
|
-
import {
|
|
10
|
+
import { namesKind, pairs, suppressionCounts } from "./suppress.js";
|
|
11
11
|
import { bytewise } from "./text/compare.js";
|
|
12
12
|
import { PRODUCT } from "./product.js";
|
|
13
13
|
/** The canonical order: path, line, column, code, then the symbol reference. */
|
|
@@ -71,15 +71,14 @@ export function verdictOf(result, config) {
|
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
73
|
* The rows one round of a baseline write records from its findings, in their order. A
|
|
74
|
-
* finding
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* once.
|
|
74
|
+
* finding no baseline row can name is not recorded: a row recording it would be stale on
|
|
75
|
+
* the first read back. Two findings that would record the same row, two parts of one
|
|
76
|
+
* declaration under one code, record it once.
|
|
78
77
|
*/
|
|
79
78
|
export function recordedFindings(findings) {
|
|
80
79
|
const rows = new Map();
|
|
81
80
|
for (const finding of findings) {
|
|
82
|
-
if (
|
|
81
|
+
if (!namesKind(finding.symbol.kind, "baseline")) {
|
|
83
82
|
continue;
|
|
84
83
|
}
|
|
85
84
|
const row = { code: finding.code, symbol: finding.symbol.ref, path: finding.position.path };
|
|
@@ -9,7 +9,7 @@ import { isClassDeclaration, isClassExpression, isHeritageClause, SyntaxKind, }
|
|
|
9
9
|
import { aliasChains } from "./alias-chain.js";
|
|
10
10
|
import { calleeName, callsOf, classesPassed, decoratedClasses, decoratorCallee } from "./calls.js";
|
|
11
11
|
import { classMembers, memberComponent, typesAt } from "./class-members.js";
|
|
12
|
-
import { entriesAt, names, namesADeclaration, resolveEntries, spelledEntry, } from "./configured-declarations.js";
|
|
12
|
+
import { entriesAt, names, namesADeclaration, resolveEntries, spelledEntry, } from "./roots/configured-declarations.js";
|
|
13
13
|
import { nodeKey } from "./inventory.js";
|
|
14
14
|
import { renderPosition } from "./position.js";
|
|
15
15
|
import { must } from "./query.js";
|
package/dist/src/fs/manifest.js
CHANGED
|
@@ -16,22 +16,27 @@ export const DEPENDENCY_SECTIONS = [
|
|
|
16
16
|
export function inInstalledPackages(path) {
|
|
17
17
|
return path.split("/").includes(INSTALLED_PACKAGES);
|
|
18
18
|
}
|
|
19
|
-
/** One JSON file
|
|
20
|
-
export function
|
|
19
|
+
/** One JSON file, read and parsed, as {@link JSONRead} tells its states apart. */
|
|
20
|
+
export function readJSONFile(host, path) {
|
|
21
21
|
let text;
|
|
22
22
|
try {
|
|
23
23
|
text = host.readFile(path);
|
|
24
24
|
}
|
|
25
|
-
catch {
|
|
26
|
-
return
|
|
25
|
+
catch (error) {
|
|
26
|
+
return { kind: "unread", reason: message(error) };
|
|
27
27
|
}
|
|
28
28
|
try {
|
|
29
|
-
return { text, value: JSON.parse(text) };
|
|
29
|
+
return { kind: "read", text, value: JSON.parse(text) };
|
|
30
30
|
}
|
|
31
31
|
catch {
|
|
32
|
-
return
|
|
32
|
+
return { kind: "malformed" };
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
/** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
|
|
36
|
+
export function readDocument(host, path) {
|
|
37
|
+
const read = readJSONFile(host, path);
|
|
38
|
+
return read.kind === "read" ? { text: read.text, value: read.value } : undefined;
|
|
39
|
+
}
|
|
35
40
|
/** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
|
|
36
41
|
export function readJSON(host, path) {
|
|
37
42
|
return readDocument(host, path)?.value;
|
|
@@ -50,21 +55,11 @@ export function readManifestAt(host, dir) {
|
|
|
50
55
|
if (!holdsManifest(host, dir)) {
|
|
51
56
|
return { kind: "absent" };
|
|
52
57
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
return { kind: "unread", reason: message(error) };
|
|
59
|
-
}
|
|
60
|
-
let value;
|
|
61
|
-
try {
|
|
62
|
-
value = JSON.parse(text);
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return { kind: "malformed" };
|
|
58
|
+
const read = readJSONFile(host, joinPath(dir, MANIFEST));
|
|
59
|
+
if (read.kind !== "read") {
|
|
60
|
+
return read;
|
|
66
61
|
}
|
|
67
|
-
return isRecord(value) ? { kind: "read", manifest: value } : { kind: "malformed" };
|
|
62
|
+
return isRecord(read.value) ? { kind: "read", manifest: read.value } : { kind: "malformed" };
|
|
68
63
|
}
|
|
69
64
|
/** Whether one directory holds a manifest file. */
|
|
70
65
|
export function holdsManifest(host, dir) {
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
* The generated-file class on TypeScript: every declaration in a file that marks itself
|
|
3
3
|
* generated, below a directory an applied convention row names as generated, or minified,
|
|
4
4
|
* is retained, because a generator owns the file and a deletion there is undone at the next
|
|
5
|
-
* generation.
|
|
5
|
+
* generation. The same decision is each `generated` entry of the facts document.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { isIdentifier, isImportDeclaration, isPropertyAssignment, isStringLiteralLikeNode, isVariableStatement, NodeFlags, } from "@typescript/native/ast";
|
|
8
|
+
import { GENERATORS } from "./contract/contract.generated.js";
|
|
9
|
+
import { minifiedBy } from "./javascript-file.js";
|
|
10
|
+
import { bytewise } from "./text/compare.js";
|
|
11
|
+
import { satisfies } from "./version-range.js";
|
|
8
12
|
/** The generated-code header, matched against each line of a leading comment. */
|
|
9
13
|
const HEADER_LINE = /^\/\/ Code generated .* DO NOT EDIT\.$/u;
|
|
10
14
|
/** The documentation tag `@generated` as a word of a comment. */
|
|
@@ -15,9 +19,17 @@ const GENERATED_BY = /^\/\/ Generated by \S/u;
|
|
|
15
19
|
const GENERATED_WORD = /\bgenerated\b/iu;
|
|
16
20
|
/** The phrases a generator's notice asks a reader not to edit the file by. */
|
|
17
21
|
const KEEP_OUT = /\b(?:do not edit|will be overwritten|should not make any changes)\b/iu;
|
|
18
|
-
/** The comments before a text's first token, a leading hashbang line skipped. */
|
|
22
|
+
/** The comments before a text's first token, a leading hashbang line skipped, each as its lines. */
|
|
19
23
|
function leadingComments(text) {
|
|
20
24
|
const comments = [];
|
|
25
|
+
const lineAt = (offset) => text.slice(0, offset).split(/\r\n|[\n\r\u2028\u2029]/u).length;
|
|
26
|
+
const lines = (from, to) => {
|
|
27
|
+
const first = lineAt(from);
|
|
28
|
+
return text
|
|
29
|
+
.slice(from, to)
|
|
30
|
+
.split(/\r\n|[\n\r\u2028\u2029]/u)
|
|
31
|
+
.map((one, index) => ({ text: one, line: first + index }));
|
|
32
|
+
};
|
|
21
33
|
let at = text.startsWith("\uFEFF") ? 1 : 0;
|
|
22
34
|
if (text.startsWith("#!", at)) {
|
|
23
35
|
const end = text.indexOf("\n", at);
|
|
@@ -30,7 +42,7 @@ function leadingComments(text) {
|
|
|
30
42
|
if (text.startsWith("//", at)) {
|
|
31
43
|
const end = text.slice(at).search(/[\r\n\u2028\u2029]/u);
|
|
32
44
|
const stop = end === -1 ? text.length : at + end;
|
|
33
|
-
comments.push(
|
|
45
|
+
comments.push(lines(at, stop));
|
|
34
46
|
at = stop;
|
|
35
47
|
}
|
|
36
48
|
else if (text.startsWith("/*", at)) {
|
|
@@ -38,7 +50,7 @@ function leadingComments(text) {
|
|
|
38
50
|
if (end === -1) {
|
|
39
51
|
return comments;
|
|
40
52
|
}
|
|
41
|
-
comments.push(
|
|
53
|
+
comments.push(lines(at, end + 2));
|
|
42
54
|
at = end + 2;
|
|
43
55
|
}
|
|
44
56
|
else {
|
|
@@ -46,43 +58,168 @@ function leadingComments(text) {
|
|
|
46
58
|
}
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
|
-
/** The
|
|
50
|
-
export function
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
/** The mark one file's leading comments make it generated by, or undefined where they mark nothing. */
|
|
62
|
+
export function generatedMark(text) {
|
|
63
|
+
const lines = leadingComments(text).flat();
|
|
64
|
+
const header = lines.find((one) => HEADER_LINE.test(one.text));
|
|
65
|
+
if (header !== undefined) {
|
|
66
|
+
return { evidence: "header", line: header.line, detail: header.text };
|
|
54
67
|
}
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
const tag = lines.find((one) => GENERATED_TAG.test(one.text));
|
|
69
|
+
if (tag !== undefined) {
|
|
70
|
+
return { evidence: "tag", line: tag.line, detail: tag.text };
|
|
57
71
|
}
|
|
58
|
-
const
|
|
59
|
-
if (GENERATED_BY.test(
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
const [first] = lines;
|
|
73
|
+
if (first !== undefined && GENERATED_BY.test(first.text)) {
|
|
74
|
+
return { evidence: "notice", line: first.line, detail: first.text };
|
|
75
|
+
}
|
|
76
|
+
const notice = lines.map((one) => one.text).join("\n");
|
|
77
|
+
if (GENERATED_WORD.test(notice) && KEEP_OUT.test(notice)) {
|
|
78
|
+
const marked = lines.find((one) => GENERATED_WORD.test(one.text) || KEEP_OUT.test(one.text));
|
|
79
|
+
return marked === undefined
|
|
80
|
+
? undefined
|
|
81
|
+
: { evidence: "notice", line: marked.line, detail: marked.text };
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
/** What one form's first group captures, for each line the form matches. */
|
|
86
|
+
function after(lines, form) {
|
|
87
|
+
return lines.flatMap((one) => form.exec(one.text)?.[1] ?? []);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Where each plugin's output states its version and its schema files, as `references-ts.36`
|
|
91
|
+
* states: a plugin missing here states no version, and one with no schema form names none.
|
|
92
|
+
*/
|
|
93
|
+
const STATEMENTS = {
|
|
94
|
+
"protoc-gen-es": {
|
|
95
|
+
version: /^\/\/ @generated by protoc-gen-es (\S+)/u,
|
|
96
|
+
schemaFiles: /^\/\/ @generated from file (\S+)/u,
|
|
97
|
+
},
|
|
98
|
+
"protoc-gen-connect-es": {
|
|
99
|
+
version: /^\/\/ @generated by protoc-gen-connect-es (\S+)/u,
|
|
100
|
+
schemaFiles: /^\/\/ @generated from file (\S+)/u,
|
|
101
|
+
},
|
|
102
|
+
"protobuf-ts": {
|
|
103
|
+
version: /^\/\/ @generated by protobuf-ts (\S+)/u,
|
|
104
|
+
schemaFiles: /^\/\/ @generated from protobuf file "([^"]+)"/u,
|
|
105
|
+
},
|
|
106
|
+
"ts-proto": {
|
|
107
|
+
version: /^\/\/\s+protoc-gen-ts_proto\s+(\S+)$/u,
|
|
108
|
+
schemaFiles: /^\/\/ source: (.+)$/u,
|
|
109
|
+
},
|
|
110
|
+
"protoc-gen-grpc-web": {
|
|
111
|
+
version: /^\/\/\s+protoc-gen-grpc-web\s+(\S+)$/u,
|
|
112
|
+
schemaFiles: /^\/\/ source: (.+)$/u,
|
|
113
|
+
},
|
|
114
|
+
"protoc-gen-js": { schemaFiles: /^\/\/ source: (.+)$/u },
|
|
115
|
+
"ts-protoc-gen": { schemaFiles: /^\/\/ file: (.+)$/u },
|
|
116
|
+
};
|
|
117
|
+
/** A ts-proto service path literal: `/<package>.<service>/<method>`. */
|
|
118
|
+
const SERVICE_PATH = /^\/(?:\w+\.)+\w+\/\w+$/u;
|
|
119
|
+
/** Whether one ts-proto file writes a service path, as `references-ts.36` reads one. */
|
|
120
|
+
function writesServicePath(file) {
|
|
121
|
+
const named = file.statements.some((statement) => isVariableStatement(statement) &&
|
|
122
|
+
(statement.declarationList.flags & NodeFlags.Const) !== 0 &&
|
|
123
|
+
statement.declarationList.declarations.some((one) => isIdentifier(one.name) &&
|
|
124
|
+
one.name.text.endsWith("ServiceName") &&
|
|
125
|
+
one.initializer !== undefined &&
|
|
126
|
+
isStringLiteralLikeNode(one.initializer)));
|
|
127
|
+
let found = named;
|
|
128
|
+
const visit = (node) => {
|
|
129
|
+
if (found) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (isPropertyAssignment(node) &&
|
|
133
|
+
isIdentifier(node.name) &&
|
|
134
|
+
node.name.text === "path" &&
|
|
135
|
+
isStringLiteralLikeNode(node.initializer) &&
|
|
136
|
+
SERVICE_PATH.test(node.initializer.text)) {
|
|
137
|
+
found = true;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
node.forEachChild(visit);
|
|
141
|
+
};
|
|
142
|
+
file.forEachChild(visit);
|
|
143
|
+
return found;
|
|
144
|
+
}
|
|
145
|
+
/** Whether one file imports a module by its specifier. */
|
|
146
|
+
function imports(file, module) {
|
|
147
|
+
return file.statements.some((statement) => isImportDeclaration(statement) &&
|
|
148
|
+
isStringLiteralLikeNode(statement.moduleSpecifier) &&
|
|
149
|
+
statement.moduleSpecifier.text === module);
|
|
150
|
+
}
|
|
151
|
+
/** The option sets the rows of `references-ts.36` name, by row; every other row's is met by any output. */
|
|
152
|
+
const OPTION_SETS = {
|
|
153
|
+
"ts-proto-2-output-schema": (file) => imports(file, "ts-proto-descriptors"),
|
|
154
|
+
"ts-proto-2": writesServicePath,
|
|
155
|
+
"ts-proto-2-types": (file) => !writesServicePath(file),
|
|
156
|
+
};
|
|
157
|
+
/** The first generator row one file matches, in `generators.json`'s order, as `bindings.3` reads it. */
|
|
158
|
+
export function generatorOf(file) {
|
|
159
|
+
const lines = leadingComments(file.text).flat();
|
|
160
|
+
for (const row of GENERATORS) {
|
|
161
|
+
const header = new RegExp(row.header, "u");
|
|
162
|
+
if (!lines.some((one) => header.test(one.text))) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const statements = STATEMENTS[row.plugin] ?? {};
|
|
166
|
+
const [version] = statements.version === undefined ? [] : after(lines, statements.version);
|
|
167
|
+
if ("versions" in row &&
|
|
168
|
+
(version === undefined ||
|
|
169
|
+
!satisfies(version.replace(/^v/u, ""), `>=${row.versions.min.replace(/^v/u, "")} <=${row.versions.max.replace(/^v/u, "")}`))) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (OPTION_SETS[row.id]?.(file) === false) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const schemaFiles = statements.schemaFiles === undefined
|
|
176
|
+
? []
|
|
177
|
+
: [
|
|
178
|
+
...new Set(after(lines, statements.schemaFiles).filter((one) => one.endsWith(".proto"))),
|
|
179
|
+
].sort(bytewise);
|
|
180
|
+
return { row: row.id, ...(version === undefined ? {} : { version }), schemaFiles };
|
|
62
181
|
}
|
|
63
182
|
return undefined;
|
|
64
183
|
}
|
|
65
184
|
/**
|
|
66
|
-
* Every own file of one project that is generated, by its path below the target root,
|
|
67
|
-
*
|
|
68
|
-
*
|
|
185
|
+
* Every own file of one project that is generated, by its path below the target root, with
|
|
186
|
+
* what decided it: its leading comments' mark, else the row naming its directory as
|
|
187
|
+
* generated, else its minified name or text; and the generator row it matches.
|
|
69
188
|
*/
|
|
70
189
|
export function generatedFilesOf(files, byRow) {
|
|
71
190
|
const found = new Map();
|
|
72
191
|
for (const [path, file] of files.byPath) {
|
|
73
192
|
const row = byRow.get(path);
|
|
74
|
-
const
|
|
193
|
+
const minified = row === undefined ? minifiedBy(file.fileName, file.text) : undefined;
|
|
194
|
+
const fact = generatedMark(file.text) ??
|
|
75
195
|
(row === undefined
|
|
76
|
-
? minified
|
|
77
|
-
?
|
|
78
|
-
:
|
|
79
|
-
:
|
|
80
|
-
if (
|
|
81
|
-
|
|
196
|
+
? minified === undefined
|
|
197
|
+
? undefined
|
|
198
|
+
: { evidence: "minified", line: 1, detail: minified }
|
|
199
|
+
: { evidence: "convention", line: 1, detail: row });
|
|
200
|
+
if (fact === undefined) {
|
|
201
|
+
continue;
|
|
82
202
|
}
|
|
203
|
+
const generator = generatorOf(file);
|
|
204
|
+
found.set(path, generator === undefined ? fact : { ...fact, generator });
|
|
83
205
|
}
|
|
84
206
|
return found;
|
|
85
207
|
}
|
|
208
|
+
/** The detail a retention in a generated file names, from what decided the file. */
|
|
209
|
+
function retentionDetail(fact) {
|
|
210
|
+
switch (fact.evidence) {
|
|
211
|
+
case "header":
|
|
212
|
+
return "generated by the header line its leading comments hold";
|
|
213
|
+
case "tag":
|
|
214
|
+
return "generated by the @generated tag its leading comments hold";
|
|
215
|
+
case "notice":
|
|
216
|
+
return "generated by the notice its leading comments hold";
|
|
217
|
+
case "convention":
|
|
218
|
+
return `generated below a directory the convention row ${fact.detail} names`;
|
|
219
|
+
case "minified":
|
|
220
|
+
return "generated as a minified JavaScript file";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
86
223
|
/** The generated-file detector, recording each file's first line. */
|
|
87
224
|
export function generatedFile(input) {
|
|
88
225
|
const generated = input.generated;
|
|
@@ -90,9 +227,15 @@ export function generatedFile(input) {
|
|
|
90
227
|
return [];
|
|
91
228
|
}
|
|
92
229
|
return input.held.symbols.flatMap((symbol) => {
|
|
93
|
-
const
|
|
94
|
-
return symbol.kind === "file" ||
|
|
230
|
+
const fact = generated.get(symbol.position.path);
|
|
231
|
+
return symbol.kind === "file" || fact === undefined
|
|
95
232
|
? []
|
|
96
|
-
: [
|
|
233
|
+
: [
|
|
234
|
+
{
|
|
235
|
+
id: symbol.id,
|
|
236
|
+
detail: retentionDetail(fact),
|
|
237
|
+
site: { path: symbol.position.path, line: 1, column: 1 },
|
|
238
|
+
},
|
|
239
|
+
];
|
|
97
240
|
});
|
|
98
241
|
}
|
package/dist/src/graph.js
CHANGED
|
@@ -32,8 +32,8 @@ const SWEPT = {
|
|
|
32
32
|
};
|
|
33
33
|
/**
|
|
34
34
|
* Whether a root of each kind names a caller, which holds it live under both relations: a
|
|
35
|
-
* tool reads what it loads and what its strings name, a runtime runs a test
|
|
36
|
-
*
|
|
35
|
+
* tool reads what it loads and what its strings name, a runtime runs a test or setup
|
|
36
|
+
* file, a page's module script, a command, a script or a workflow step, the manifest
|
|
37
37
|
* names what enters the package, an alias global stands for its export, an outside
|
|
38
38
|
* declaration names a global interface merging into it, and an entry file, a configured root
|
|
39
39
|
* and a pattern assert a caller. A published API supposes a consumer: reachability alone.
|
|
@@ -54,8 +54,6 @@ const NAMES_A_CALLER = {
|
|
|
54
54
|
"configuration-string": true,
|
|
55
55
|
"bundler-entry": true,
|
|
56
56
|
"test-runner": true,
|
|
57
|
-
"browser-tests": true,
|
|
58
|
-
worker: true,
|
|
59
57
|
"html-entry": true,
|
|
60
58
|
"workflow-step": true,
|
|
61
59
|
"document-import": true,
|
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { run, SETTING_OPTIONS } from "./run.js";
|
|
2
|
-
export { DECLINED_CONVENTIONS } from "./entry-point-gaps.js";
|
|
2
|
+
export { DECLINED_CONVENTIONS } from "./roots/entry-point-gaps.js";
|
|
3
3
|
export { byPosition, positionKey, PositionError, renderPosition, } from "./position.js";
|
|
4
4
|
export { computedComponent, isRef, nameComponent, REF_EXPRESSIONS, renderRef, } from "./ref.js";
|
|
5
5
|
export { CONTRACT_VERSION } from "./contract/contract.generated.js";
|
|
@@ -10,7 +10,7 @@ import { isClassDeclaration, isClassExpression, isConstructorDeclaration, isGetA
|
|
|
10
10
|
import { aliasChains } from "./alias-chain.js";
|
|
11
11
|
import { calleeName, callsOf, classesPassed, decoratorsOf, spelledDecorator } from "./calls.js";
|
|
12
12
|
import { classMembers } from "./class-members.js";
|
|
13
|
-
import { entriesAt, namesADeclaration, resolveEntries, spelledEntry, } from "./configured-declarations.js";
|
|
13
|
+
import { entriesAt, namesADeclaration, resolveEntries, spelledEntry, } from "./roots/configured-declarations.js";
|
|
14
14
|
import { isParameterProperty, nodeKey } from "./inventory.js";
|
|
15
15
|
import { renderPosition } from "./position.js";
|
|
16
16
|
/** Whether one member declaration is a parameter property, or a property or accessor carrying a decorator. */
|