@cplieger/deadset-ts 8.0.0-dev.8 → 8.0.0-dev.9
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 +84 -288
- package/dist/src/configuration-files.js +2 -9
- package/dist/src/consumers.js +1 -28
- package/dist/src/contract/contract.generated.js +20 -10
- package/dist/src/conventions.js +3 -8
- package/dist/src/failure.js +36 -0
- package/dist/src/fs/document.js +21 -28
- package/dist/src/fs/manifest.js +7 -0
- package/dist/src/load/classify.js +236 -0
- package/dist/src/load/diagnostics.js +79 -0
- package/dist/src/load/discover.js +230 -0
- package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
- package/dist/src/load/expected-module.js +136 -0
- package/dist/src/load/load.js +22 -0
- package/dist/src/load/module-lookup.js +37 -0
- package/dist/src/load/plan.js +155 -0
- package/dist/src/{test-globals.js → load/test-globals.js} +51 -56
- package/dist/src/load/test-joins.js +82 -0
- package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +3 -3
- package/dist/src/{workspace-programs.js → load/workspace-programs.js} +37 -40
- package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +21 -59
- package/dist/src/{workspace-run.js → load/workspace-run.js} +4 -3
- package/dist/src/{workspace.js → load/workspace.js} +21 -17
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/run.js +20 -26
- package/dist/src/scope.js +11 -19
- package/dist/src/session.js +22 -28
- package/dist/src/source-files.js +1 -1
- package/dist/src/type-errors.js +11 -216
- package/dist/src/verbs/analyze.js +90 -109
- package/dist/src/verbs/describe.js +5 -12
- package/dist/src/verbs/version.js +12 -2
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +68 -437
- package/src/configuration-files.ts +8 -10
- package/src/consumers.ts +2 -53
- package/src/contract/contract.generated.ts +20 -10
- package/src/conventions.ts +6 -12
- package/src/failure.ts +63 -0
- package/src/fs/document.ts +23 -27
- package/src/fs/manifest.ts +8 -0
- package/src/load/classify.ts +352 -0
- package/src/load/diagnostics.ts +141 -0
- package/src/load/discover.ts +378 -0
- package/src/{emit-map.ts → load/emit-map.ts} +1 -1
- package/src/load/expected-module.ts +188 -0
- package/src/load/load.ts +61 -0
- package/src/load/module-lookup.ts +50 -0
- package/src/load/plan.ts +258 -0
- package/src/{test-globals.ts → load/test-globals.ts} +59 -65
- package/src/load/test-joins.ts +98 -0
- package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +4 -4
- package/src/{workspace-programs.ts → load/workspace-programs.ts} +59 -53
- package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +31 -78
- package/src/{workspace-run.ts → load/workspace-run.ts} +14 -11
- package/src/{workspace.ts → load/workspace.ts} +28 -21
- package/src/outside-tests.ts +1 -1
- package/src/partial.ts +1 -1
- package/src/run.ts +26 -25
- package/src/scope.ts +15 -21
- package/src/session.ts +52 -53
- package/src/source-files.ts +1 -1
- package/src/type-errors.ts +18 -288
- package/src/verbs/analyze.ts +129 -136
- package/src/verbs/describe.ts +4 -11
- package/src/verbs/version.ts +13 -2
- package/dist/src/base-url.js +0 -18
- package/dist/src/discover.js +0 -374
- package/dist/src/setup-failure.js +0 -19
- package/dist/src/test-joins.js +0 -101
- package/src/base-url.ts +0 -24
- package/src/discover.ts +0 -548
- package/src/setup-failure.ts +0 -37
- package/src/test-joins.ts +0 -118
package/dist/src/type-errors.js
CHANGED
|
@@ -1,159 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What
|
|
3
|
-
*
|
|
4
|
-
* is a type error: the function, method or file-level statement holding it is skipped,
|
|
5
|
-
* which only ever withholds a finding. `projectErrors` decides, before any of these, which
|
|
6
|
-
* errors the compiler places in no file leave the project unloaded.
|
|
2
|
+
* What a type error in a project's own file does: the function, method or file-level statement
|
|
3
|
+
* holding it is skipped, which only ever withholds a finding.
|
|
7
4
|
*/
|
|
8
|
-
import { isClassDeclaration, isFunctionLikeDeclaration,
|
|
9
|
-
import { packageOfSpecifier } from "./dependencies.js";
|
|
5
|
+
import { isClassDeclaration, isFunctionLikeDeclaration, isPropertyAccessExpression, } from "@typescript/native/ast";
|
|
10
6
|
import { nodeKey } from "./inventory.js";
|
|
11
|
-
import {
|
|
7
|
+
import { relativePath } from "./paths.js";
|
|
12
8
|
import { isAnswered } from "./query.js";
|
|
13
|
-
import { findWorkspace } from "./workspace.js";
|
|
14
|
-
import { entriesOf } from "./fs/tree.js";
|
|
15
|
-
import { INSTALLED_PACKAGES, MANIFEST, manifestAt } from "./fs/manifest.js";
|
|
16
|
-
/** The compiler's codes for an import whose module it cannot find. */
|
|
17
|
-
const MODULE_NOT_FOUND = new Set([2307, 2792]);
|
|
18
|
-
/**
|
|
19
|
-
* The compiler's codes for where the configuration places a file, which leave every type
|
|
20
|
-
* the file's statements carry as it is: an output that would overwrite an input (5055,
|
|
21
|
-
* 5056), a file outside `rootDir` (6059), one a composite project does not list (6307).
|
|
22
|
-
*/
|
|
23
|
-
const CONFIGURATION_LEVEL = new Set([5055, 5056, 6059, 6307]);
|
|
24
|
-
const MANIFEST_SECTIONS = [
|
|
25
|
-
"dependencies",
|
|
26
|
-
"devDependencies",
|
|
27
|
-
"peerDependencies",
|
|
28
|
-
"optionalDependencies",
|
|
29
|
-
];
|
|
30
9
|
/** Whether a finding's position falls inside a skipped unit. */
|
|
31
10
|
export function insideSkipped(skipped, position) {
|
|
32
11
|
return skipped.some((unit) => unit.path === position.path && unit.fromLine <= position.line && position.line <= unit.toLine);
|
|
33
12
|
}
|
|
34
|
-
/** The string literal holding one offset, innermost first, a doc comment's included. */
|
|
35
|
-
function literalAt(file, offset) {
|
|
36
|
-
let found;
|
|
37
|
-
const visit = (node) => {
|
|
38
|
-
if (offset < node.pos || offset >= node.end) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
|
|
42
|
-
found = node.text;
|
|
43
|
-
}
|
|
44
|
-
// `forEachChild` does not enter a doc comment, where a JavaScript file imports types.
|
|
45
|
-
for (const doc of node.jsDoc ?? []) {
|
|
46
|
-
visit(doc);
|
|
47
|
-
}
|
|
48
|
-
node.forEachChild(visit);
|
|
49
|
-
};
|
|
50
|
-
file.forEachChild(visit);
|
|
51
|
-
return found;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* What one `paths` key's `*` matches in one specifier, `""` for a key with no `*` that
|
|
55
|
-
* is the specifier, or `undefined` where the key does not match it.
|
|
56
|
-
*/
|
|
57
|
-
function mappedBy(key, specifier) {
|
|
58
|
-
const star = key.indexOf("*");
|
|
59
|
-
if (star < 0) {
|
|
60
|
-
return key === specifier ? "" : undefined;
|
|
61
|
-
}
|
|
62
|
-
const prefix = key.slice(0, star);
|
|
63
|
-
const suffix = key.slice(star + 1);
|
|
64
|
-
return specifier.length >= prefix.length + suffix.length &&
|
|
65
|
-
specifier.startsWith(prefix) &&
|
|
66
|
-
specifier.endsWith(suffix)
|
|
67
|
-
? specifier.slice(prefix.length, specifier.length - suffix.length)
|
|
68
|
-
: undefined;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* One `paths` target, its first `*` replaced by `star` taken literally, as the compiler does:
|
|
72
|
-
* https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/module/resolver.go#L1282
|
|
73
|
-
*/
|
|
74
|
-
function substituted(target, star) {
|
|
75
|
-
const at = target.indexOf("*");
|
|
76
|
-
return at < 0 ? target : target.slice(0, at) + star + target.slice(at + 1);
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Whether a path names something on disk, as written or with any extension; under
|
|
80
|
-
* `fileOnly`, only a file counts.
|
|
81
|
-
*/
|
|
82
|
-
function provided(host, path, fileOnly) {
|
|
83
|
-
const counts = (at) => fileOnly ? host.kindOf(at) === "file" : host.kindOf(at) !== "absent";
|
|
84
|
-
if (counts(path)) {
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
const dir = dirnamePath(path);
|
|
88
|
-
const stem = `${path.slice(dir.length + 1)}.`;
|
|
89
|
-
return entriesOf(host, dir).some((entry) => entry.name.startsWith(stem) && counts(joinPath(dir, entry.name)));
|
|
90
|
-
}
|
|
91
|
-
/** The manifest at or above `dir`, no higher than the target root, that declares `name`. */
|
|
92
|
-
function declaringManifest(host, dir, targetRoot, name) {
|
|
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);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return undefined;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Whether a package is installed for an importer in `dir`: in the `node_modules` of
|
|
108
|
-
* `dir` or of any ancestor, above the target root too, as Node and the compiler look.
|
|
109
|
-
*/
|
|
110
|
-
function installed(host, dir, name) {
|
|
111
|
-
return ancestors(dir).some((at) => host.kindOf(joinPath(at, INSTALLED_PACKAGES, name)) === "directory");
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Why one import that the compiler cannot resolve names a module the project expects,
|
|
115
|
-
* with the fix, or undefined where the module is one the project does not expect.
|
|
116
|
-
*/
|
|
117
|
-
function expectedModule(context, importer, specifier) {
|
|
118
|
-
const { host, targetRoot } = context;
|
|
119
|
-
const fix = "run the generator or the build that writes it";
|
|
120
|
-
if (specifier.startsWith(".") || isAbsolutePath(specifier)) {
|
|
121
|
-
return provided(host, resolvePath(dirnamePath(importer), specifier), false)
|
|
122
|
-
? undefined
|
|
123
|
-
: `which names no file of the target: ${fix}`;
|
|
124
|
-
}
|
|
125
|
-
if (specifier.startsWith("#")) {
|
|
126
|
-
return `which the manifest's imports map to no file: ${fix}`;
|
|
127
|
-
}
|
|
128
|
-
const mapped = context.paths.entries.flatMap(({ key, targets }) => {
|
|
129
|
-
const star = mappedBy(key, specifier);
|
|
130
|
-
return star === undefined
|
|
131
|
-
? []
|
|
132
|
-
: targets.map((target) => resolvePath(context.paths.base, substituted(target, star)));
|
|
133
|
-
});
|
|
134
|
-
const name = packageOfSpecifier(specifier);
|
|
135
|
-
// The compiler looks up in `node_modules` what `paths` does not resolve, so the error
|
|
136
|
-
// of an installed package's import is the import's own, a subpath its exports omit:
|
|
137
|
-
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/module/resolver.go#L548-L575
|
|
138
|
-
if (name !== undefined && installed(host, dirnamePath(importer), name)) {
|
|
139
|
-
return undefined;
|
|
140
|
-
}
|
|
141
|
-
if (mapped.length > 0) {
|
|
142
|
-
return mapped.some((path) => provided(host, path, true))
|
|
143
|
-
? undefined
|
|
144
|
-
: `which the compiler configuration's paths map to no file: ${fix}`;
|
|
145
|
-
}
|
|
146
|
-
const manifest = name === undefined
|
|
147
|
-
? undefined
|
|
148
|
-
: declaringManifest(host, dirnamePath(importer), targetRoot, name);
|
|
149
|
-
// No run requires a nested package that is no workspace member to be installed.
|
|
150
|
-
if (manifest === undefined ||
|
|
151
|
-
(dirnamePath(manifest) !== targetRoot && !context.member(manifest))) {
|
|
152
|
-
return undefined;
|
|
153
|
-
}
|
|
154
|
-
const where = relativePath(targetRoot, manifest) ?? manifest;
|
|
155
|
-
return `a dependency ${where} declares that is not installed: run the package manager's install`;
|
|
156
|
-
}
|
|
157
13
|
/**
|
|
158
14
|
* The unit a type error at one offset skips: the innermost function holding it, else the
|
|
159
15
|
* class member holding it where a top-level class does, else the top-level statement.
|
|
@@ -184,55 +40,13 @@ function messageOf(text, targetRoot) {
|
|
|
184
40
|
const line = text.split(`${targetRoot}/`).join("").split(/\s+/u).join(" ").trim();
|
|
185
41
|
return line === "" ? "a type error" : line;
|
|
186
42
|
}
|
|
187
|
-
/**
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const context = {
|
|
195
|
-
...where,
|
|
196
|
-
member: (manifest) => {
|
|
197
|
-
if (workspace === null) {
|
|
198
|
-
workspace = findWorkspace(where.host, where.targetRoot, () => undefined);
|
|
199
|
-
}
|
|
200
|
-
return workspace?.memberOf(manifest) !== undefined;
|
|
201
|
-
},
|
|
202
|
-
};
|
|
203
|
-
const files = new Map(project.ownSourceFiles().map((file) => [file.fileName, file]));
|
|
204
|
-
const missing = [];
|
|
205
|
-
const skips = [];
|
|
206
|
-
for (const { diagnostic, at } of errors) {
|
|
207
|
-
const file = diagnostic.fileName === undefined ? undefined : files.get(diagnostic.fileName);
|
|
208
|
-
if (file === undefined ||
|
|
209
|
-
diagnostic.reportsUnnecessary === true ||
|
|
210
|
-
CONFIGURATION_LEVEL.has(diagnostic.code)) {
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
const path = relativePath(context.targetRoot, file.fileName) ?? file.fileName;
|
|
214
|
-
const line = at?.line ?? 1;
|
|
215
|
-
const specifier = MODULE_NOT_FOUND.has(diagnostic.code)
|
|
216
|
-
? literalAt(file, diagnostic.pos)
|
|
217
|
-
: undefined;
|
|
218
|
-
const expected = specifier === undefined ? undefined : expectedModule(context, file.fileName, specifier);
|
|
219
|
-
if (specifier !== undefined && expected !== undefined) {
|
|
220
|
-
missing.push({
|
|
221
|
-
setupClass: "missing-module",
|
|
222
|
-
detail: `${path}:${String(line)}: ${path} imports ${JSON.stringify(specifier)}, ${expected}`,
|
|
223
|
-
});
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
const unit = unitAt(file, diagnostic.pos);
|
|
227
|
-
if (unit !== undefined) {
|
|
228
|
-
skips.push({
|
|
229
|
-
record: { path, line, message: messageOf(diagnostic.text, context.targetRoot) },
|
|
230
|
-
file,
|
|
231
|
-
unit,
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return { missing, skips };
|
|
43
|
+
/** The skip one type error at `offset` of an own file makes, the root spelled relative to itself. */
|
|
44
|
+
export function skipAt(file, offset, line, text, targetRoot) {
|
|
45
|
+
const unit = unitAt(file, offset);
|
|
46
|
+
const path = relativePath(targetRoot, file.fileName) ?? file.fileName;
|
|
47
|
+
return unit === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: { record: { path, line, message: messageOf(text, targetRoot) }, file, unit };
|
|
236
50
|
}
|
|
237
51
|
/** The lines each skip withholds, below the target root. */
|
|
238
52
|
export function skippedUnits(skips, targetRoot) {
|
|
@@ -297,22 +111,3 @@ export function skippedRoots(project, held, skips) {
|
|
|
297
111
|
}
|
|
298
112
|
return [...kept];
|
|
299
113
|
}
|
|
300
|
-
/** One compiler configuration's `paths` mapping, empty where it cannot be read. */
|
|
301
|
-
export function pathsOf(engine, configFile) {
|
|
302
|
-
const base = dirnamePath(configFile);
|
|
303
|
-
try {
|
|
304
|
-
const { options } = engine.parseConfigFile(configFile);
|
|
305
|
-
const { paths } = options;
|
|
306
|
-
// The parse answers `pathsBasePath`, the directory of the configuration that writes
|
|
307
|
-
// `paths` (an extended one included), though the client's option type omits it:
|
|
308
|
-
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/tsoptions/tsconfigparsing.go#L1120
|
|
309
|
-
const declaredAt = options.pathsBasePath;
|
|
310
|
-
return {
|
|
311
|
-
base: typeof declaredAt === "string" ? declaredAt : base,
|
|
312
|
-
entries: Object.entries(paths ?? {}).map(([key, targets]) => ({ key, targets })),
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
catch {
|
|
316
|
-
return { base, entries: [] };
|
|
317
|
-
}
|
|
318
|
-
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { CONTRACT_VERSION, EXIT_CLEAN
|
|
1
|
+
import { CONTRACT_VERSION, EXIT_CLEAN } from "../contract/contract.generated.js";
|
|
2
2
|
import { runAnalysis, runBaselineWrite, unansweredCounts, } from "../analysis.js";
|
|
3
3
|
import { kindOf } from "../config.js";
|
|
4
4
|
import { CONFORMANCE, DECLARED_GAPS } from "../conformance.js";
|
|
5
|
-
import { moduleIdentity } from "../consumers.js";
|
|
6
5
|
import { analyzerProvenance, verdictOf } from "../findings-pass.js";
|
|
7
6
|
import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "../paths.js";
|
|
7
|
+
import { moduleIdentity } from "../load/discover.js";
|
|
8
8
|
import { partialNotes } from "../partial.js";
|
|
9
9
|
import { buildReport, capFindings, sortFindings, } from "../report.js";
|
|
10
10
|
import { reportRefusals } from "../report-schema.js";
|
|
@@ -16,7 +16,9 @@ import { otherSchemeRows, rowReason, writeBaseline } from "../suppress-file.js";
|
|
|
16
16
|
import {} from "./verb.js";
|
|
17
17
|
import { UsageError } from "../usage.js";
|
|
18
18
|
import { bytewise } from "../text/compare.js";
|
|
19
|
+
import { productFault, failureExit } from "../failure.js";
|
|
19
20
|
import { diagnose, message } from "../product.js";
|
|
21
|
+
import { ownVersion } from "./version.js";
|
|
20
22
|
/** The options `analyze` takes beside the ones every verb takes. */
|
|
21
23
|
export const ANALYZE_OPTIONS = {
|
|
22
24
|
plain: ["report", "baseline-write", "exit-code", "template"],
|
|
@@ -25,13 +27,6 @@ export const ANALYZE_OPTIONS = {
|
|
|
25
27
|
/** The two values `--exit-code` takes; on, the default, puts the run's verdict in the exit code. */
|
|
26
28
|
const EXIT_CODE_ON = "on";
|
|
27
29
|
const EXIT_CODE_OFF = "off";
|
|
28
|
-
/** A path of the run no report can name, which ends the run with the failure code. */
|
|
29
|
-
class ReportPathError extends Error {
|
|
30
|
-
constructor(message) {
|
|
31
|
-
super(message);
|
|
32
|
-
this.name = "ReportPathError";
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
30
|
/** The formats this analyzer renders, for a message that names them. */
|
|
36
31
|
function formatNames() {
|
|
37
32
|
return [...RENDERINGS.keys()].sort(bytewise).join(", ");
|
|
@@ -62,7 +57,7 @@ function runRelative(invoked, path) {
|
|
|
62
57
|
}
|
|
63
58
|
const below = relativePath(invoked, path);
|
|
64
59
|
if (below === undefined) {
|
|
65
|
-
throw
|
|
60
|
+
throw failureExit("unreportable-path", `a report names its target relative to the directory the run was invoked from, and ${invoked} does not contain ${path}: run from a directory that does`);
|
|
66
61
|
}
|
|
67
62
|
return below;
|
|
68
63
|
}
|
|
@@ -104,7 +99,7 @@ function notBuiltOf(dropped, targetRoot) {
|
|
|
104
99
|
return dropped.map((one) => {
|
|
105
100
|
const project = relativePath(targetRoot, one.configFile);
|
|
106
101
|
if (project === undefined) {
|
|
107
|
-
throw
|
|
102
|
+
throw failureExit("unreportable-path", `the project ${one.id} would be opened from ${one.configFile}, which is not below the target ${targetRoot}, so no report can name it: analyze a target that holds it`);
|
|
108
103
|
}
|
|
109
104
|
return { id: one.id, project, error: one.error };
|
|
110
105
|
});
|
|
@@ -114,11 +109,24 @@ function configurationsOf(projects, targetRoot) {
|
|
|
114
109
|
return projects.map((one) => {
|
|
115
110
|
const project = relativePath(targetRoot, one.configFile);
|
|
116
111
|
if (project === undefined) {
|
|
117
|
-
throw
|
|
112
|
+
throw failureExit("unreportable-path", `the project ${one.id} is opened from ${one.configFile}, which is not below the target ${targetRoot}, so no report can name it: analyze a target that holds it`);
|
|
118
113
|
}
|
|
119
114
|
return { id: one.id, project };
|
|
120
115
|
});
|
|
121
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Writes one document the run was asked for. A document that cannot be produced or written
|
|
119
|
+
* ends the run with the run failure that names it and `fix`, the documents written before it
|
|
120
|
+
* kept.
|
|
121
|
+
*/
|
|
122
|
+
function writeDocument(host, path, fix, produce) {
|
|
123
|
+
try {
|
|
124
|
+
host.writeDocument(path, produce());
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
throw failureExit("unwritten-output", `${path}: ${message(error)}: ${fix}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
122
130
|
/** A count with its noun, so a message reads for one record as well as for several. */
|
|
123
131
|
function counted(n, noun) {
|
|
124
132
|
return `${String(n)} ${noun}${n === 1 ? "" : "s"}`;
|
|
@@ -180,108 +188,81 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
|
|
|
180
188
|
const template = templateOf(host, invoked, option("template"), documents.formats);
|
|
181
189
|
const scoped = scope();
|
|
182
190
|
const targetRoot = scoped.target.path;
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
version = host.analyzerVersion();
|
|
186
|
-
}
|
|
187
|
-
catch (error) {
|
|
188
|
-
diagnose(err, message(error));
|
|
189
|
-
return EXIT_FAILURE;
|
|
190
|
-
}
|
|
191
|
-
let report;
|
|
191
|
+
const version = ownVersion(host);
|
|
192
192
|
let recorded;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
let
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const root = runRelative(invoked, targetRoot);
|
|
200
|
-
const mode = { production: true };
|
|
201
|
-
let analysis;
|
|
202
|
-
if (documents.baseline === undefined) {
|
|
203
|
-
analysis = runAnalysis(openClient(config), host, scoped, config, provenance, mode);
|
|
204
|
-
recorded = [];
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
const reason = rowReason(analyzerProvenance(version));
|
|
208
|
-
const write = runBaselineWrite(openClient(config), host, scoped, config, provenance, mode, reason);
|
|
209
|
-
analysis = write.first;
|
|
210
|
-
recorded = write.rows;
|
|
211
|
-
}
|
|
212
|
-
const { result, run } = analysis;
|
|
213
|
-
warnings = run.componentWarnings;
|
|
214
|
-
unread = run.componentsUnread.map(({ configFile, reason }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: its component files are not read: ${reason}`);
|
|
215
|
-
unfound = run.librariesUnfound.map(({ configFile, error }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: ${error}: the configuration is analyzed without that library`);
|
|
216
|
-
partial = partialNotes({
|
|
217
|
-
notBuilt: run.notBuilt,
|
|
218
|
-
unanswered: run.unanswered,
|
|
219
|
-
targetRoot: resolvePath(invoked, targetRoot),
|
|
220
|
-
});
|
|
221
|
-
const built = buildReport({
|
|
222
|
-
contractVersion: CONTRACT_VERSION,
|
|
223
|
-
version,
|
|
224
|
-
conformance: CONFORMANCE,
|
|
225
|
-
declaredGaps: DECLARED_GAPS,
|
|
226
|
-
target: {
|
|
227
|
-
kind: kindOf(config.target.kind),
|
|
228
|
-
root,
|
|
229
|
-
identity: moduleIdentity(host, scoped.target),
|
|
230
|
-
},
|
|
231
|
-
configurations: configurationsOf(run.projects, targetRoot),
|
|
232
|
-
notBuilt: notBuiltOf(run.notBuilt, targetRoot),
|
|
233
|
-
loaded: run.consumers.map((consumer) => ({
|
|
234
|
-
id: consumer.id,
|
|
235
|
-
path: runRelative(invoked, consumer.path),
|
|
236
|
-
})),
|
|
237
|
-
unavailable: [],
|
|
238
|
-
result,
|
|
239
|
-
testFileRules: run.testFileRules,
|
|
240
|
-
typeErrorSkips: run.typeErrorSkips,
|
|
241
|
-
unanswered: unansweredCounts(analysis),
|
|
242
|
-
conventionsApplied: run.conventionsApplied,
|
|
243
|
-
listsComponents: config.reporters.cascade === "full",
|
|
244
|
-
notes: [
|
|
245
|
-
...unmappedNotes(run.unmappedEntries, resolvePath(invoked, targetRoot)),
|
|
246
|
-
...unseenLoadNotes(run.unseenLoads),
|
|
247
|
-
],
|
|
248
|
-
});
|
|
249
|
-
report = capFindings(sortFindings(built, config.reporters.sort), config.reporters.max_findings);
|
|
250
|
-
verdict = verdictOf(result, config);
|
|
193
|
+
const root = runRelative(invoked, targetRoot);
|
|
194
|
+
const mode = { production: true };
|
|
195
|
+
let analysis;
|
|
196
|
+
if (documents.baseline === undefined) {
|
|
197
|
+
analysis = runAnalysis(openClient(config), host, scoped, config, provenance, mode);
|
|
198
|
+
recorded = [];
|
|
251
199
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
return EXIT_FAILURE;
|
|
200
|
+
else {
|
|
201
|
+
const reason = rowReason(analyzerProvenance(version));
|
|
202
|
+
const write = runBaselineWrite(openClient(config), host, scoped, config, provenance, mode, reason);
|
|
203
|
+
analysis = write.first;
|
|
204
|
+
recorded = write.rows;
|
|
258
205
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
206
|
+
const { result, run } = analysis;
|
|
207
|
+
const warnings = run.componentWarnings;
|
|
208
|
+
const unread = run.componentsUnread.map(({ configFile, reason }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: its component files are not read: ${reason}`);
|
|
209
|
+
const unfound = run.librariesUnfound.map(({ configFile, error }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: ${error}: the configuration is analyzed without that library`);
|
|
210
|
+
const partial = partialNotes({
|
|
211
|
+
notBuilt: run.notBuilt,
|
|
212
|
+
unanswered: run.unanswered,
|
|
213
|
+
targetRoot: resolvePath(invoked, targetRoot),
|
|
214
|
+
});
|
|
215
|
+
const built = buildReport({
|
|
216
|
+
contractVersion: CONTRACT_VERSION,
|
|
217
|
+
version,
|
|
218
|
+
conformance: CONFORMANCE,
|
|
219
|
+
declaredGaps: DECLARED_GAPS,
|
|
220
|
+
target: {
|
|
221
|
+
kind: kindOf(config.target.kind),
|
|
222
|
+
root,
|
|
223
|
+
identity: moduleIdentity(host, scoped.target),
|
|
224
|
+
},
|
|
225
|
+
configurations: configurationsOf(run.projects, targetRoot),
|
|
226
|
+
notBuilt: notBuiltOf(run.notBuilt, targetRoot),
|
|
227
|
+
loaded: run.consumers.map((consumer) => ({
|
|
228
|
+
id: consumer.id,
|
|
229
|
+
path: runRelative(invoked, consumer.path),
|
|
230
|
+
})),
|
|
231
|
+
unavailable: [],
|
|
232
|
+
result,
|
|
233
|
+
testFileRules: run.testFileRules,
|
|
234
|
+
typeErrorSkips: run.typeErrorSkips,
|
|
235
|
+
unanswered: unansweredCounts(analysis),
|
|
236
|
+
conventionsApplied: run.conventionsApplied,
|
|
237
|
+
listsComponents: config.reporters.cascade === "full",
|
|
238
|
+
notes: [
|
|
239
|
+
...unmappedNotes(run.unmappedEntries, resolvePath(invoked, targetRoot)),
|
|
240
|
+
...unseenLoadNotes(run.unseenLoads),
|
|
241
|
+
],
|
|
242
|
+
});
|
|
243
|
+
const report = capFindings(sortFindings(built, config.reporters.sort), config.reporters.max_findings);
|
|
244
|
+
const verdict = verdictOf(result, config);
|
|
245
|
+
const [refused] = reportRefusals(report);
|
|
246
|
+
if (refused !== undefined) {
|
|
247
|
+
throw productFault(`built a report that breaks the report schema at ${refused.pointer || "the document"}: ${refused.keyword}: ${refused.reason}`);
|
|
248
|
+
}
|
|
249
|
+
writeDocument(host, documents.report, "name a path the run can write", () => json(report));
|
|
250
|
+
const options = {
|
|
251
|
+
readSource: (path) => host.readFile(joinPath(targetRoot, path)),
|
|
252
|
+
template,
|
|
253
|
+
};
|
|
254
|
+
for (const format of documents.formats) {
|
|
255
|
+
const rendering = RENDERINGS.get(format);
|
|
256
|
+
if (rendering !== undefined) {
|
|
257
|
+
writeDocument(host, documents.report + rendering.suffix, "name a path the run can write, or correct the template that renders it", () => rendering.render(report, options));
|
|
280
258
|
}
|
|
281
259
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
260
|
+
const baselinePath = documents.baseline;
|
|
261
|
+
if (baselinePath !== undefined) {
|
|
262
|
+
writeDocument(host, baselinePath, "name a path the run can write, or repair the rows of the baseline the run would keep", () => {
|
|
263
|
+
const kept = host.kindOf(baselinePath) === "file" ? otherSchemeRows(host.readFile(baselinePath)) : [];
|
|
264
|
+
return writeBaseline(recorded, analyzerProvenance(version), kept);
|
|
265
|
+
});
|
|
285
266
|
}
|
|
286
267
|
for (const note of partial) {
|
|
287
268
|
diagnose(err, note);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { CONTRACT_VERSION, EXIT_CLEAN,
|
|
1
|
+
import { CONTRACT_VERSION, EXIT_CLEAN, EXIT_USAGE, SCHEMA_VERSIONS, } from "../contract/contract.generated.js";
|
|
2
2
|
import { CONFORMANCE } from "../conformance.js";
|
|
3
3
|
import {} from "./verb.js";
|
|
4
|
-
import { diagnose, LANGUAGE,
|
|
4
|
+
import { diagnose, LANGUAGE, PRODUCT } from "../product.js";
|
|
5
|
+
import { ownVersion } from "./version.js";
|
|
5
6
|
/**
|
|
6
7
|
* The document `describe` writes: the analyzer, the Contract version it implements, the
|
|
7
8
|
* report schema versions it reads, the languages it claims, and its recorded result over
|
|
@@ -26,20 +27,12 @@ export function describeDocument(version, recorded) {
|
|
|
26
27
|
};
|
|
27
28
|
return `${JSON.stringify(document, null, 2)}\n`;
|
|
28
29
|
}
|
|
29
|
-
/** Writes this analyzer's description to the output stream
|
|
30
|
+
/** Writes this analyzer's description to the output stream. */
|
|
30
31
|
export const describeVerb = ({ out, err, host, args }) => {
|
|
31
32
|
if (args.length > 0) {
|
|
32
33
|
diagnose(err, `describe takes no argument, got ${JSON.stringify(args[0])}`);
|
|
33
34
|
return EXIT_USAGE;
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
version = host.analyzerVersion();
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
diagnose(err, `describe: ${message(error)}`);
|
|
41
|
-
return EXIT_FAILURE;
|
|
42
|
-
}
|
|
43
|
-
out.write(describeDocument(version, CONFORMANCE));
|
|
36
|
+
out.write(describeDocument(ownVersion(host), CONFORMANCE));
|
|
44
37
|
return EXIT_CLEAN;
|
|
45
38
|
};
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { CONTRACT_VERSION, EXIT_CLEAN } from "../contract/contract.generated.js";
|
|
2
|
+
import { productFault } from "../failure.js";
|
|
2
3
|
import {} from "./verb.js";
|
|
3
|
-
import { PRODUCT } from "../product.js";
|
|
4
|
+
import { message, PRODUCT } from "../product.js";
|
|
5
|
+
/** The version the host reports for this analyzer; one it cannot read is a fault of the analyzer. */
|
|
6
|
+
export function ownVersion(host) {
|
|
7
|
+
try {
|
|
8
|
+
return host.analyzerVersion();
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
throw productFault(`cannot read its own version: ${message(error)}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
4
14
|
/** Writes the version the host reports and the Contract version this analyzer implements. */
|
|
5
15
|
export const versionVerb = ({ out, host }) => {
|
|
6
|
-
out.write(`${PRODUCT} ${host
|
|
16
|
+
out.write(`${PRODUCT} ${ownVersion(host)}\ncontract ${CONTRACT_VERSION}\n`);
|
|
7
17
|
return EXIT_CLEAN;
|
|
8
18
|
};
|
package/jsr.json
CHANGED