@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
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* alias, a tag, a block scalar, a complex key or a second document, has no value here,
|
|
6
6
|
* as a JSON document that does not parse has none.
|
|
7
7
|
*/
|
|
8
|
+
import { isRecord } from "../json-document.js";
|
|
8
9
|
/** A failure to read the document within the subset; it ends the whole reading. */
|
|
9
10
|
class Unsupported extends Error {
|
|
10
11
|
}
|
|
@@ -353,3 +354,40 @@ export function yamlDocument(text) {
|
|
|
353
354
|
throw error;
|
|
354
355
|
}
|
|
355
356
|
}
|
|
357
|
+
/** The key one line of the document's top-level mapping opens, or undefined where it opens none. */
|
|
358
|
+
function topLevelKey(raw) {
|
|
359
|
+
const content = withoutComment(raw).trimEnd();
|
|
360
|
+
const end = /^\s/u.test(content) ? -1 : keyEnd(content);
|
|
361
|
+
if (end < 0) {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
const key = scalar(content.slice(0, end));
|
|
366
|
+
return typeof key === "object" && key !== null ? undefined : String(key);
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return undefined;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* The value of one key of a document's top-level block mapping, read from that key's lines
|
|
374
|
+
* alone, so a document whose other keys lie outside the subset still yields it. A key
|
|
375
|
+
* declared twice, or one whose own lines lie outside the subset, is refused.
|
|
376
|
+
*/
|
|
377
|
+
export function yamlKey(text, key) {
|
|
378
|
+
const lines = text.replace(/^\uFEFF/u, "").split(/\r?\n/u);
|
|
379
|
+
const starts = lines.flatMap((line, at) => (topLevelKey(line) === undefined ? [] : [at]));
|
|
380
|
+
const named = starts.filter((at) => topLevelKey(lines[at] ?? "") === key);
|
|
381
|
+
const [start] = named;
|
|
382
|
+
if (start === undefined) {
|
|
383
|
+
return { kind: "absent" };
|
|
384
|
+
}
|
|
385
|
+
if (named.length > 1) {
|
|
386
|
+
return { kind: "refused", reason: `the ${key} key is declared more than once` };
|
|
387
|
+
}
|
|
388
|
+
const end = starts.find((at) => at > start) ?? lines.length;
|
|
389
|
+
const value = yamlDocument(lines.slice(start, end).join("\n"));
|
|
390
|
+
return isRecord(value)
|
|
391
|
+
? { kind: "value", value: value[key] }
|
|
392
|
+
: { kind: "refused", reason: `the value of ${key} is outside the YAML subset read here` };
|
|
393
|
+
}
|
package/dist/src/type-errors.js
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
import { isClassDeclaration, isFunctionLikeDeclaration, isNoSubstitutionTemplateLiteral, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/ast";
|
|
9
9
|
import { packageOfSpecifier } from "./dependencies.js";
|
|
10
10
|
import { nodeKey } from "./inventory.js";
|
|
11
|
-
import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
|
|
11
|
+
import { ancestors, dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath, } from "./paths.js";
|
|
12
12
|
import { isAnswered } from "./query.js";
|
|
13
13
|
import { findWorkspace } from "./workspace.js";
|
|
14
|
+
import { entriesOf } from "./fs/tree.js";
|
|
15
|
+
import { INSTALLED_PACKAGES, MANIFEST, manifestAt } from "./fs/manifest.js";
|
|
14
16
|
/** The compiler's codes for an import whose module it cannot find. */
|
|
15
17
|
const MODULE_NOT_FOUND = new Set([2307, 2792]);
|
|
16
18
|
/**
|
|
@@ -19,7 +21,6 @@ const MODULE_NOT_FOUND = new Set([2307, 2792]);
|
|
|
19
21
|
* 5056), a file outside `rootDir` (6059), one a composite project does not list (6307).
|
|
20
22
|
*/
|
|
21
23
|
const CONFIGURATION_LEVEL = new Set([5055, 5056, 6059, 6307]);
|
|
22
|
-
const MANIFEST = "package.json";
|
|
23
24
|
const MANIFEST_SECTIONS = [
|
|
24
25
|
"dependencies",
|
|
25
26
|
"devDependencies",
|
|
@@ -85,54 +86,29 @@ function provided(host, path, fileOnly) {
|
|
|
85
86
|
}
|
|
86
87
|
const dir = dirnamePath(path);
|
|
87
88
|
const stem = `${path.slice(dir.length + 1)}.`;
|
|
88
|
-
|
|
89
|
-
return host
|
|
90
|
-
.readDirectory(dir)
|
|
91
|
-
.some((entry) => entry.name.startsWith(stem) && counts(joinPath(dir, entry.name)));
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
89
|
+
return entriesOf(host, dir).some((entry) => entry.name.startsWith(stem) && counts(joinPath(dir, entry.name)));
|
|
96
90
|
}
|
|
97
91
|
/** The manifest at or above `dir`, no higher than the target root, that declares `name`. */
|
|
98
92
|
function declaringManifest(host, dir, targetRoot, name) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
if (typeof manifest === "object" && manifest !== null) {
|
|
109
|
-
const sections = manifest;
|
|
110
|
-
for (const section of MANIFEST_SECTIONS) {
|
|
111
|
-
const declared = sections[section];
|
|
112
|
-
if (typeof declared === "object" && declared !== null && Object.hasOwn(declared, name)) {
|
|
113
|
-
return path;
|
|
114
|
-
}
|
|
93
|
+
const chain = ancestors(dir, targetRoot);
|
|
94
|
+
// A directory outside the target root reads its own manifest alone.
|
|
95
|
+
for (const at of chain.length > 0 ? chain : [dir]) {
|
|
96
|
+
const manifest = manifestAt(host, at);
|
|
97
|
+
for (const section of MANIFEST_SECTIONS) {
|
|
98
|
+
const declared = manifest?.[section];
|
|
99
|
+
if (typeof declared === "object" && declared !== null && Object.hasOwn(declared, name)) {
|
|
100
|
+
return joinPath(at, MANIFEST);
|
|
115
101
|
}
|
|
116
102
|
}
|
|
117
|
-
// The target root is not below itself, so the walk stops after reading its manifest.
|
|
118
|
-
if (relativePath(targetRoot, at) === undefined) {
|
|
119
|
-
return undefined;
|
|
120
|
-
}
|
|
121
103
|
}
|
|
104
|
+
return undefined;
|
|
122
105
|
}
|
|
123
106
|
/**
|
|
124
107
|
* Whether a package is installed for an importer in `dir`: in the `node_modules` of
|
|
125
108
|
* `dir` or of any ancestor, above the target root too, as Node and the compiler look.
|
|
126
109
|
*/
|
|
127
110
|
function installed(host, dir, name) {
|
|
128
|
-
|
|
129
|
-
if (host.kindOf(joinPath(at, "node_modules", name)) === "directory") {
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
if (dirnamePath(at) === at) {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
111
|
+
return ancestors(dir).some((at) => host.kindOf(joinPath(at, INSTALLED_PACKAGES, name)) === "directory");
|
|
136
112
|
}
|
|
137
113
|
/**
|
|
138
114
|
* Why one import that the compiler cannot resolve names a module the project expects,
|
package/dist/src/unseen-loads.js
CHANGED
|
@@ -4,19 +4,12 @@
|
|
|
4
4
|
* naming a directory below the calling file's. The files such a load runs are roots the
|
|
5
5
|
* analysis does not see, so the configuration's roots are never complete.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { isBinaryExpression, isCallExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isStringLiteral, isTemplateExpression, SyntaxKind, } from "@typescript/native/ast";
|
|
8
8
|
import { dirnamePath, relativePath, resolvePath } from "./paths.js";
|
|
9
9
|
import { renderPosition } from "./position.js";
|
|
10
10
|
import { UNANSWERED } from "./query.js";
|
|
11
11
|
import { sourceFilesOf } from "./source-files.js";
|
|
12
|
-
|
|
13
|
-
function unwrapped(node) {
|
|
14
|
-
let at = node;
|
|
15
|
-
while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
|
|
16
|
-
at = at.expression;
|
|
17
|
-
}
|
|
18
|
-
return at;
|
|
19
|
-
}
|
|
12
|
+
import { unwrapped } from "./unwrap.js";
|
|
20
13
|
/** The literal text a computed path starts with: a template's head, or a concatenation's first literal. */
|
|
21
14
|
export function leadingLiteral(node) {
|
|
22
15
|
const at = unwrapped(node);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isAsExpression, isNonNullExpression, isParenthesizedExpression, isSatisfiesExpression, } from "@typescript/native/ast";
|
|
2
|
+
/** The expression inside every pair of parentheses around `node`. */
|
|
3
|
+
export function parenthesesRemoved(node) {
|
|
4
|
+
let at = node;
|
|
5
|
+
while (isParenthesizedExpression(at)) {
|
|
6
|
+
at = at.expression;
|
|
7
|
+
}
|
|
8
|
+
return at;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The expression inside every wrapper around `node` that changes no value it evaluates to:
|
|
12
|
+
* parentheses, `as` and `satisfies` assertions and the non-null assertion.
|
|
13
|
+
*/
|
|
14
|
+
export function unwrapped(node) {
|
|
15
|
+
let at = node;
|
|
16
|
+
while (isParenthesizedExpression(at) ||
|
|
17
|
+
isAsExpression(at) ||
|
|
18
|
+
isSatisfiesExpression(at) ||
|
|
19
|
+
isNonNullExpression(at)) {
|
|
20
|
+
at = at.expression;
|
|
21
|
+
}
|
|
22
|
+
return at;
|
|
23
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CONTRACT_VERSION, EXIT_CLEAN, EXIT_FAILURE, } from "../contract/contract.generated.js";
|
|
1
2
|
import { runAnalysis, runBaselineWrite, unansweredCounts, } from "../analysis.js";
|
|
2
3
|
import { ConfigError, kindOf } from "../config.js";
|
|
3
4
|
import { CONFORMANCE, DECLARED_GAPS } from "../conformance.js";
|
|
@@ -5,14 +6,16 @@ import { moduleIdentity } from "../consumers.js";
|
|
|
5
6
|
import { analyzerProvenance, verdictOf } from "../findings-pass.js";
|
|
6
7
|
import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "../paths.js";
|
|
7
8
|
import { partialNotes } from "../partial.js";
|
|
8
|
-
import { buildReport,
|
|
9
|
+
import { buildReport, capFindings, sortFindings, } from "../report.js";
|
|
10
|
+
import { reportRefusals } from "../report-schema.js";
|
|
9
11
|
import { json } from "../reporters/json.js";
|
|
10
12
|
import { RENDERINGS } from "../reporters/reporters.js";
|
|
11
13
|
import { parseTemplate, TemplateError } from "../reporters/template.js";
|
|
12
14
|
import { resolve } from "../resolve.js";
|
|
13
15
|
import { otherSchemeRows, rowReason, writeBaseline } from "../suppress-file.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
+
import {} from "./verb.js";
|
|
17
|
+
import { bytewise } from "../text/compare.js";
|
|
18
|
+
import { diagnose, LANGUAGE, message } from "../product.js";
|
|
16
19
|
/** The options `analyze` takes beside the ones every verb takes. */
|
|
17
20
|
export const ANALYZE_OPTIONS = {
|
|
18
21
|
plain: ["report", "baseline-write", "exit-code", "template"],
|
|
@@ -30,7 +33,7 @@ class ReportPathError extends Error {
|
|
|
30
33
|
}
|
|
31
34
|
/** The formats this analyzer renders, for a message that names them. */
|
|
32
35
|
function formatNames() {
|
|
33
|
-
return [...RENDERINGS.keys()].sort().join(", ");
|
|
36
|
+
return [...RENDERINGS.keys()].sort(bytewise).join(", ");
|
|
34
37
|
}
|
|
35
38
|
/** The renderings one invocation writes: the ones it named, else the configuration's. */
|
|
36
39
|
function formatsOf(named, configured) {
|
|
@@ -136,7 +139,7 @@ function templateOf(host, invoked, named, formats) {
|
|
|
136
139
|
source = host.readFile(resolvePath(invoked, named));
|
|
137
140
|
}
|
|
138
141
|
catch (error) {
|
|
139
|
-
throw new ConfigError("malformed", "", `--template=${named}: ${
|
|
142
|
+
throw new ConfigError("malformed", "", `--template=${named}: ${message(error)}`);
|
|
140
143
|
}
|
|
141
144
|
try {
|
|
142
145
|
return parseTemplate(source);
|
|
@@ -185,7 +188,7 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
|
|
|
185
188
|
version = host.analyzerVersion();
|
|
186
189
|
}
|
|
187
190
|
catch (error) {
|
|
188
|
-
err
|
|
191
|
+
diagnose(err, message(error));
|
|
189
192
|
return EXIT_FAILURE;
|
|
190
193
|
}
|
|
191
194
|
let report;
|
|
@@ -253,10 +256,14 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
|
|
|
253
256
|
if (!(error instanceof ReportPathError)) {
|
|
254
257
|
throw error;
|
|
255
258
|
}
|
|
256
|
-
err
|
|
259
|
+
diagnose(err, error.message);
|
|
257
260
|
return EXIT_FAILURE;
|
|
258
261
|
}
|
|
259
262
|
try {
|
|
263
|
+
const [refused] = reportRefusals(report);
|
|
264
|
+
if (refused !== undefined) {
|
|
265
|
+
throw new Error(`the report breaks the report schema at ${refused.pointer || "the document"}: ${refused.keyword}: ${refused.reason}`);
|
|
266
|
+
}
|
|
260
267
|
host.writeDocument(documents.report, json(report));
|
|
261
268
|
const options = {
|
|
262
269
|
readSource: (path) => host.readFile(joinPath(targetRoot, path)),
|
|
@@ -276,30 +283,30 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
|
|
|
276
283
|
}
|
|
277
284
|
}
|
|
278
285
|
catch (error) {
|
|
279
|
-
err
|
|
286
|
+
diagnose(err, message(error));
|
|
280
287
|
return EXIT_FAILURE;
|
|
281
288
|
}
|
|
282
289
|
for (const note of partial) {
|
|
283
|
-
err
|
|
290
|
+
diagnose(err, note);
|
|
284
291
|
}
|
|
285
292
|
for (const line of [...unread, ...unfound]) {
|
|
286
|
-
err
|
|
293
|
+
diagnose(err, line);
|
|
287
294
|
}
|
|
288
295
|
for (const warning of warnings) {
|
|
289
|
-
err
|
|
296
|
+
diagnose(err, `${warning.path}:${String(warning.line)}: a <script> start tag that begins a line is not read as a block: ${warning.reason}`);
|
|
290
297
|
}
|
|
291
298
|
for (const skip of report.type_error_skips) {
|
|
292
|
-
err
|
|
299
|
+
diagnose(err, `${skip.path}:${String(skip.line)}: ${skip.message}: the function or statement holding this type error is not evaluated`);
|
|
293
300
|
}
|
|
294
301
|
if (report.totals.pending > 0) {
|
|
295
|
-
err
|
|
302
|
+
diagnose(err, `${counted(report.totals.pending, "pending finding")}: the report names a declared cross-language edge, and no merge has resolved it`);
|
|
296
303
|
}
|
|
297
304
|
if (report.totals.stale_suppressions > 0) {
|
|
298
|
-
err
|
|
305
|
+
diagnose(err, counted(report.totals.stale_suppressions, "stale suppression"));
|
|
299
306
|
}
|
|
300
307
|
if (exitCode === EXIT_CODE_OFF) {
|
|
301
308
|
if (verdict !== EXIT_CLEAN) {
|
|
302
|
-
err
|
|
309
|
+
diagnose(err, `the exit code is configured off: the verdict of this run is ${String(verdict)}`);
|
|
303
310
|
}
|
|
304
311
|
return EXIT_CLEAN;
|
|
305
312
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { CONTRACT_VERSION, EXIT_CLEAN, EXIT_FAILURE, EXIT_USAGE, SCHEMA_VERSIONS, } from "../contract/contract.generated.js";
|
|
1
2
|
import { CONFORMANCE } from "../conformance.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { EXIT_CLEAN, EXIT_FAILURE, EXIT_USAGE } from "./verb.js";
|
|
3
|
+
import {} from "./verb.js";
|
|
4
|
+
import { diagnose, LANGUAGE, message, PRODUCT } from "../product.js";
|
|
5
5
|
/**
|
|
6
6
|
* The document `describe` writes: the analyzer, the Contract version it implements, the
|
|
7
7
|
* report schema versions it reads, the languages it claims, and its recorded result over
|
|
@@ -9,10 +9,10 @@ import { EXIT_CLEAN, EXIT_FAILURE, EXIT_USAGE } from "./verb.js";
|
|
|
9
9
|
*/
|
|
10
10
|
export function describeDocument(version, recorded) {
|
|
11
11
|
const document = {
|
|
12
|
-
name:
|
|
12
|
+
name: PRODUCT,
|
|
13
13
|
version,
|
|
14
14
|
contract_version: CONTRACT_VERSION,
|
|
15
|
-
schema_versions_accepted:
|
|
15
|
+
schema_versions_accepted: SCHEMA_VERSIONS,
|
|
16
16
|
languages: [LANGUAGE],
|
|
17
17
|
...(recorded === undefined
|
|
18
18
|
? {}
|
|
@@ -29,7 +29,7 @@ export function describeDocument(version, recorded) {
|
|
|
29
29
|
/** Writes this analyzer's description to the output stream, and nothing to the error stream. */
|
|
30
30
|
export const describeVerb = ({ out, err, host, args }) => {
|
|
31
31
|
if (args.length > 0) {
|
|
32
|
-
err
|
|
32
|
+
diagnose(err, `describe takes no argument, got ${JSON.stringify(args[0])}`);
|
|
33
33
|
return EXIT_USAGE;
|
|
34
34
|
}
|
|
35
35
|
let version;
|
|
@@ -37,7 +37,7 @@ export const describeVerb = ({ out, err, host, args }) => {
|
|
|
37
37
|
version = host.analyzerVersion();
|
|
38
38
|
}
|
|
39
39
|
catch (error) {
|
|
40
|
-
err
|
|
40
|
+
diagnose(err, `describe: ${message(error)}`);
|
|
41
41
|
return EXIT_FAILURE;
|
|
42
42
|
}
|
|
43
43
|
out.write(describeDocument(version, CONFORMANCE));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EXIT_CLEAN, EXIT_USAGE } from "../contract/contract.generated.js";
|
|
1
2
|
import { runAnalysis } from "../analysis.js";
|
|
2
3
|
import { listing } from "../components.js";
|
|
3
4
|
import { ConfigError } from "../config.js";
|
|
@@ -7,7 +8,9 @@ import { partialNotes } from "../partial.js";
|
|
|
7
8
|
import { resolvePath } from "../paths.js";
|
|
8
9
|
import { positionKey } from "../position.js";
|
|
9
10
|
import { resolve } from "../resolve.js";
|
|
10
|
-
import {
|
|
11
|
+
import {} from "./verb.js";
|
|
12
|
+
import { bytewise } from "../text/compare.js";
|
|
13
|
+
import { diagnose } from "../product.js";
|
|
11
14
|
/**
|
|
12
15
|
* The three spellings of an explanation request, each naming the symbol to explain. They are
|
|
13
16
|
* one request, because exactly one state holds for a symbol and the answer is that state.
|
|
@@ -73,7 +76,7 @@ function subjectOf(symbols, named) {
|
|
|
73
76
|
const partial = symbols
|
|
74
77
|
.filter((symbol) => symbol.ref.toLowerCase().includes(folded) || symbol.name.toLowerCase().includes(folded))
|
|
75
78
|
.map(described)
|
|
76
|
-
.sort(
|
|
79
|
+
.sort(bytewise);
|
|
77
80
|
return { candidates: partial.slice(0, MAX_PARTIAL_MATCHES) };
|
|
78
81
|
}
|
|
79
82
|
/** Every reference of the run once, a reference several configurations make counted once. */
|
|
@@ -317,7 +320,7 @@ export const explainVerb = ({ out, err, host, inputs, scope, openClient, option
|
|
|
317
320
|
targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
|
|
318
321
|
});
|
|
319
322
|
if (subject === undefined) {
|
|
320
|
-
err
|
|
323
|
+
diagnose(err, `${JSON.stringify(named)} names no one symbol of the target`);
|
|
321
324
|
for (const candidate of candidates) {
|
|
322
325
|
err.write(` ${candidate}\n`);
|
|
323
326
|
}
|
|
@@ -329,7 +332,7 @@ export const explainVerb = ({ out, err, host, inputs, scope, openClient, option
|
|
|
329
332
|
out.write(explanation(analysis, subject, config.reporters.cascade));
|
|
330
333
|
}
|
|
331
334
|
for (const note of notes) {
|
|
332
|
-
err
|
|
335
|
+
diagnose(err, note);
|
|
333
336
|
}
|
|
334
337
|
return subject === undefined ? EXIT_USAGE : EXIT_CLEAN;
|
|
335
338
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { EXIT_CLEAN } from "../contract/contract.generated.js";
|
|
1
2
|
import { printConfig } from "../print.js";
|
|
2
3
|
import { resolve } from "../resolve.js";
|
|
3
|
-
import {
|
|
4
|
+
import {} from "./verb.js";
|
|
4
5
|
/** Writes the resolved configuration with the source of every setting. */
|
|
5
6
|
export const printConfigVerb = ({ out, inputs }) => {
|
|
6
7
|
const { config, provenance } = resolve(inputs());
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { EXIT_CLEAN } from "../contract/contract.generated.js";
|
|
1
2
|
import { runProjects } from "../analysis.js";
|
|
2
3
|
import { partialNotes } from "../partial.js";
|
|
3
4
|
import { resolvePath } from "../paths.js";
|
|
4
5
|
import { resolveUnkinded } from "../resolve.js";
|
|
5
|
-
import {
|
|
6
|
+
import {} from "./verb.js";
|
|
7
|
+
import { diagnose } from "../product.js";
|
|
6
8
|
/**
|
|
7
9
|
* Writes the name of every project one run analyzes, one per line: the identifier the
|
|
8
10
|
* build matrix declares for it, or, where discovery derived the project, its
|
|
@@ -22,7 +24,7 @@ export const printProjectsVerb = ({ out, err, host, inputs, scope, openClient })
|
|
|
22
24
|
unanswered: answer.unanswered,
|
|
23
25
|
targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
|
|
24
26
|
})) {
|
|
25
|
-
err
|
|
27
|
+
diagnose(err, note);
|
|
26
28
|
}
|
|
27
29
|
return EXIT_CLEAN;
|
|
28
30
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { EXIT_CLEAN } from "../contract/contract.generated.js";
|
|
1
2
|
import { runSweep } from "../analysis.js";
|
|
2
3
|
import { retainedLines } from "../exempt.js";
|
|
3
4
|
import { partialNotes } from "../partial.js";
|
|
4
5
|
import { resolvePath } from "../paths.js";
|
|
5
6
|
import { resolve } from "../resolve.js";
|
|
6
|
-
import {
|
|
7
|
+
import {} from "./verb.js";
|
|
8
|
+
import { diagnose } from "../product.js";
|
|
7
9
|
/**
|
|
8
10
|
* Writes every declaration an exemption held back in the production sweep a report is
|
|
9
11
|
* built from, one per line, and nothing where no exemption held one back. One fact
|
|
@@ -26,7 +28,7 @@ export const printRetainedVerb = ({ out, err, host, inputs, scope, openClient })
|
|
|
26
28
|
unanswered: swept.unanswered,
|
|
27
29
|
targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
|
|
28
30
|
})) {
|
|
29
|
-
err
|
|
31
|
+
diagnose(err, note);
|
|
30
32
|
}
|
|
31
33
|
return EXIT_CLEAN;
|
|
32
34
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { EXIT_CLEAN, EXIT_FINDINGS } from "../contract/contract.generated.js";
|
|
1
2
|
import { runRoots } from "../analysis.js";
|
|
2
3
|
import { partialNotes } from "../partial.js";
|
|
3
4
|
import { resolvePath } from "../paths.js";
|
|
4
5
|
import { resolve } from "../resolve.js";
|
|
5
|
-
import {
|
|
6
|
+
import {} from "./verb.js";
|
|
7
|
+
import { diagnose } from "../product.js";
|
|
6
8
|
/**
|
|
7
9
|
* One root as a line: the declaration's reference, why it is a root, and the string
|
|
8
10
|
* that named it, separated by tabs. A run of several configurations adds the ones
|
|
@@ -39,7 +41,7 @@ export const printRootsVerb = ({ out, err, host, inputs, scope, openClient }) =>
|
|
|
39
41
|
unanswered: answer.unanswered,
|
|
40
42
|
targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
|
|
41
43
|
})) {
|
|
42
|
-
err
|
|
44
|
+
diagnose(err, note);
|
|
43
45
|
}
|
|
44
46
|
return answer.findings.length > 0 ? EXIT_FINDINGS : EXIT_CLEAN;
|
|
45
47
|
};
|
package/dist/src/verbs/verb.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { CONTRACT_VERSION } from "../
|
|
2
|
-
import {
|
|
1
|
+
import { CONTRACT_VERSION, EXIT_CLEAN } from "../contract/contract.generated.js";
|
|
2
|
+
import {} from "./verb.js";
|
|
3
|
+
import { PRODUCT } from "../product.js";
|
|
3
4
|
/** Writes the version the host reports and the Contract version this analyzer implements. */
|
|
4
5
|
export const versionVerb = ({ out, host }) => {
|
|
5
|
-
out.write(
|
|
6
|
+
out.write(`${PRODUCT} ${host.analyzerVersion()}\ncontract ${CONTRACT_VERSION}\n`);
|
|
6
7
|
return EXIT_CLEAN;
|
|
7
8
|
};
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* path from the target root. A workflow or hook file is read as text, line by line, with no
|
|
4
4
|
* YAML parsed.
|
|
5
5
|
*/
|
|
6
|
-
import { scriptTokens } from "./manifest.js";
|
|
7
6
|
import { joinPath, relativePath, resolvePath } from "./paths.js";
|
|
7
|
+
import { filesIn } from "./fs/tree.js";
|
|
8
|
+
import { commandsOf, scriptTokens, unquoted, withoutComment } from "./text/shell.js";
|
|
8
9
|
const WORKFLOWS = ".github/workflows";
|
|
9
10
|
/** A line holding a run command: indentation and one sequence marker before `run:`. */
|
|
10
11
|
const RUN_LINE = /^(\s*(?:- )?)run:(.*)$/u;
|
|
@@ -19,26 +20,13 @@ const HOOK_MANAGERS = [
|
|
|
19
20
|
];
|
|
20
21
|
/** The names GNU make reads a makefile under, in the order it tries them. */
|
|
21
22
|
const MAKEFILES = ["GNUmakefile", "makefile", "Makefile"];
|
|
22
|
-
/** The operators that end one shell command of a recipe line. */
|
|
23
|
-
const COMMAND_SEPARATORS = /&&|\|\||;|\|/u;
|
|
24
23
|
const BLOCK_SCALAR = /^[|>][+-]?$/u;
|
|
25
|
-
const QUOTED = /^(["'])(.*)\1$/su;
|
|
26
24
|
/** A token holding one of these is computed or a pattern, so it names no file. */
|
|
27
25
|
const COMPUTED = /[${*?[]/u;
|
|
28
26
|
const INDENT = /^\s*/u;
|
|
29
|
-
/** The files directly in one directory whose name `accepts`, in name order. */
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
entries = host.readDirectory(dir);
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
return entries
|
|
39
|
-
.filter((entry) => !entry.directory && accepts(entry.name))
|
|
40
|
-
.map((entry) => joinPath(dir, entry.name))
|
|
41
|
-
.sort();
|
|
27
|
+
/** The files directly in one directory whose name `accepts`, by path in name order. */
|
|
28
|
+
function pathsIn(host, dir, accepts) {
|
|
29
|
+
return filesIn(host, dir, accepts).map((name) => joinPath(dir, name));
|
|
42
30
|
}
|
|
43
31
|
function readText(host, path) {
|
|
44
32
|
try {
|
|
@@ -58,7 +46,7 @@ export function runCommands(text, key = RUN_LINE) {
|
|
|
58
46
|
continue;
|
|
59
47
|
}
|
|
60
48
|
const column = (match[1] ?? "").length;
|
|
61
|
-
const value = (match[2] ?? "").trim()
|
|
49
|
+
const value = unquoted((match[2] ?? "").trim());
|
|
62
50
|
if (!BLOCK_SCALAR.test(value)) {
|
|
63
51
|
found.push(value);
|
|
64
52
|
continue;
|
|
@@ -88,7 +76,7 @@ function recipeLines(host, root) {
|
|
|
88
76
|
.replace(/\\\r?\n/gu, " ")
|
|
89
77
|
.split(/\r?\n/u)
|
|
90
78
|
.filter((line) => line.startsWith("\t"))
|
|
91
|
-
.map((line) => withoutComment(line.slice(1).replace(/^[@+-]+/u, ""))
|
|
79
|
+
.map((line) => commandsOf(withoutComment(line.slice(1).replace(/^[@+-]+/u, ""))));
|
|
92
80
|
}
|
|
93
81
|
/**
|
|
94
82
|
* Every token the recipe lines of the target root's makefile hold, each with the directory
|
|
@@ -113,31 +101,6 @@ function recipeTokens(host, root) {
|
|
|
113
101
|
}
|
|
114
102
|
return found;
|
|
115
103
|
}
|
|
116
|
-
/** One shell line up to its comment: a `#` that starts a word outside quotes. */
|
|
117
|
-
function withoutComment(line) {
|
|
118
|
-
let quote = "";
|
|
119
|
-
for (let at = 0; at < line.length; at += 1) {
|
|
120
|
-
const char = line[at];
|
|
121
|
-
if (quote !== "") {
|
|
122
|
-
if (char === quote) {
|
|
123
|
-
quote = "";
|
|
124
|
-
}
|
|
125
|
-
else if (char === "\\" && quote === '"') {
|
|
126
|
-
at += 1;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
else if (char === "'" || char === '"') {
|
|
130
|
-
quote = char;
|
|
131
|
-
}
|
|
132
|
-
else if (char === "\\") {
|
|
133
|
-
at += 1;
|
|
134
|
-
}
|
|
135
|
-
else if (char === "#" && (at === 0 || /\s/u.test(line[at - 1] ?? ""))) {
|
|
136
|
-
return line.slice(0, at);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return line;
|
|
140
|
-
}
|
|
141
104
|
/**
|
|
142
105
|
* Every command the target root's workflow steps, git hooks and makefile run: the `run:`
|
|
143
106
|
* steps of the workflow files in `.github/workflows`, every line of each file in `.husky`
|
|
@@ -159,11 +122,11 @@ export function workflowTokens(host, targetRoot) {
|
|
|
159
122
|
/** The commands of the workflow steps, git hooks and hook managers at one root. */
|
|
160
123
|
function stepCommands(host, root) {
|
|
161
124
|
const commands = [];
|
|
162
|
-
for (const path of
|
|
125
|
+
for (const path of pathsIn(host, joinPath(root, WORKFLOWS), (name) => /\.ya?ml$/u.test(name))) {
|
|
163
126
|
commands.push(...runCommands(readText(host, path) ?? ""));
|
|
164
127
|
}
|
|
165
128
|
for (const dir of HOOK_DIRS) {
|
|
166
|
-
for (const path of
|
|
129
|
+
for (const path of pathsIn(host, joinPath(root, dir), () => true)) {
|
|
167
130
|
commands.push(...(readText(host, path) ?? "").split(/\r?\n/u).map(withoutComment));
|
|
168
131
|
}
|
|
169
132
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { emittedFrom } from "./emit-map.js";
|
|
9
9
|
import { readManifest } from "./manifest.js";
|
|
10
10
|
import { joinPath, relativePath } from "./paths.js";
|
|
11
|
+
import { MANIFEST } from "./fs/manifest.js";
|
|
11
12
|
/** The one character a subpath pattern of a manifest's `exports` holds. */
|
|
12
13
|
const SUBPATH_WILDCARD = "*";
|
|
13
14
|
/** The paths one written target stands for in its member's sources, itself included. */
|
|
@@ -53,8 +54,7 @@ export function memberEntries(host, resolver, targetRoot) {
|
|
|
53
54
|
return [];
|
|
54
55
|
}
|
|
55
56
|
const manifest = readManifest(host, member.dir);
|
|
56
|
-
const where = relativePath(targetRoot, joinPath(member.dir,
|
|
57
|
-
joinPath(member.dir, "package.json");
|
|
57
|
+
const where = relativePath(targetRoot, joinPath(member.dir, MANIFEST)) ?? joinPath(member.dir, MANIFEST);
|
|
58
58
|
const kept = manifest.entries.flatMap((entry) => {
|
|
59
59
|
const exported = entry.member.startsWith("exports");
|
|
60
60
|
if (member.private && exported) {
|
|
@@ -9,6 +9,8 @@ import { ModuleKind, ModuleResolutionKind } from "@typescript/native/sync";
|
|
|
9
9
|
import { isStringLiteralLikeNode } from "@typescript/native/ast";
|
|
10
10
|
import { baseUrlPaths } from "./base-url.js";
|
|
11
11
|
import { dirnamePath, resolvePath } from "./paths.js";
|
|
12
|
+
import { bytewise } from "./text/compare.js";
|
|
13
|
+
import { PRODUCT } from "./product.js";
|
|
12
14
|
/** The most questions one round trip carries. */
|
|
13
15
|
const BATCH = 2048;
|
|
14
16
|
/**
|
|
@@ -55,7 +57,7 @@ function unstatable(reading) {
|
|
|
55
57
|
export function writtenPath(host, configFile) {
|
|
56
58
|
const stem = configFile.replace(/\.json$/u, "");
|
|
57
59
|
for (let counter = 0;; counter += 1) {
|
|
58
|
-
const path = `${stem}
|
|
60
|
+
const path = `${stem}.${PRODUCT}${counter === 0 ? "" : `-${String(counter)}`}.json`;
|
|
59
61
|
if (host.kindOf(path) === "absent") {
|
|
60
62
|
return path;
|
|
61
63
|
}
|
|
@@ -102,10 +104,10 @@ function configurationOf(engine, host, reading) {
|
|
|
102
104
|
...(exclude === undefined ? {} : { exclude }),
|
|
103
105
|
});
|
|
104
106
|
const file = writtenPath(host, reading.configFile);
|
|
105
|
-
const expected = [...reading.parsed.fileNames].sort().join("\u0000");
|
|
107
|
+
const expected = [...reading.parsed.fileNames].sort(bytewise).join("\u0000");
|
|
106
108
|
const listed = (value) => {
|
|
107
109
|
try {
|
|
108
|
-
return [...engine.parseConfigJson(value, file).fileNames].sort().join("\u0000");
|
|
110
|
+
return [...engine.parseConfigJson(value, file).fileNames].sort(bytewise).join("\u0000");
|
|
109
111
|
}
|
|
110
112
|
catch {
|
|
111
113
|
return undefined;
|