@cplieger/deadset-ts 8.0.0-dev.6 → 8.0.0-dev.8
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 +24 -5
- package/dist/src/analysis.js +37 -56
- 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 +6 -82
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/consumers.js +2 -2
- package/dist/src/contract/config.generated.js +544 -0
- package/dist/src/contract/contract.generated.js +13 -0
- package/dist/src/contract/report.generated.js +3 -2
- package/dist/src/conventions.js +20 -60
- package/dist/src/dependencies.js +25 -43
- package/dist/src/discover.js +14 -40
- package/dist/src/edges.js +38 -122
- 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 +0 -4
- package/dist/src/exempt.js +6 -22
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +5 -4
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +1 -1
- package/dist/src/findings/non-code-artifacts.js +2 -2
- package/dist/src/findings/reads-and-writes.js +2 -1
- package/dist/src/findings/self-check.js +3 -2
- package/dist/src/findings/unused-declarations.js +1 -1
- package/dist/src/findings/visibility-narrowing.js +2 -2
- package/dist/src/findings-pass.js +7 -12
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +59 -0
- 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/injection-container.js +1 -1
- package/dist/src/intra-function-parts.js +10 -13
- package/dist/src/inventory.js +2 -21
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +0 -7
- 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 +11 -79
- package/dist/src/product.js +13 -0
- package/dist/src/references.js +14 -27
- package/dist/src/report-schema.js +3 -225
- package/dist/src/report.js +22 -31
- package/dist/src/reporters/sarif.js +4 -2
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +245 -724
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +55 -61
- package/dist/src/schema.js +320 -343
- package/dist/src/scope.js +34 -103
- package/dist/src/serialization-contract.js +5 -9
- package/dist/src/session.js +2 -1
- package/dist/src/suppress-file.js +21 -25
- package/dist/src/suppress.js +3 -3
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +9 -19
- 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/usage.js +10 -0
- package/dist/src/verbs/analyze.js +25 -26
- package/dist/src/verbs/describe.js +4 -4
- package/dist/src/verbs/explain.js +8 -6
- package/dist/src/verbs/print-projects.js +4 -3
- package/dist/src/verbs/print-retained.js +2 -1
- package/dist/src/verbs/print-roots.js +2 -1
- package/dist/src/verbs/version.js +2 -1
- 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 +1 -1
- package/src/analysis.ts +55 -76
- 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 +31 -296
- package/src/configuration-files.ts +27 -170
- package/src/configured-declarations.ts +18 -23
- package/src/consumers.ts +4 -4
- package/src/contract/config.generated.ts +798 -0
- package/src/contract/contract.generated.ts +25 -0
- package/src/contract/report.generated.ts +3 -2
- package/src/conventions.ts +26 -74
- package/src/dependencies.ts +32 -50
- package/src/discover.ts +15 -40
- package/src/edges.ts +41 -140
- 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 +0 -5
- package/src/exempt.ts +8 -30
- package/src/file-facts.ts +6 -24
- package/src/findings/completion.ts +5 -6
- package/src/findings/emitter.ts +2 -2
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +1 -1
- package/src/findings/non-code-artifacts.ts +4 -4
- package/src/findings/reads-and-writes.ts +2 -1
- package/src/findings/self-check.ts +3 -2
- package/src/findings/unused-declarations.ts +1 -1
- package/src/findings/visibility-narrowing.ts +2 -2
- package/src/findings-pass.ts +9 -15
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +74 -0
- package/src/fs/manifest.ts +52 -0
- package/src/fs/tree.ts +110 -0
- package/src/git-ignore.ts +38 -25
- package/src/host.ts +12 -5
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/injection-container.ts +1 -1
- package/src/intra-function-parts.ts +10 -14
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +0 -8
- 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 +12 -92
- package/src/product.ts +18 -0
- package/src/references.ts +14 -29
- package/src/report-schema.ts +3 -301
- package/src/report.ts +24 -34
- package/src/reporters/sarif.ts +4 -4
- package/src/reporters/template.ts +4 -20
- package/src/resolve.ts +283 -1114
- package/src/roots.ts +7 -20
- package/src/run.ts +57 -75
- package/src/schema.ts +391 -388
- package/src/scope.ts +40 -126
- package/src/serialization-contract.ts +5 -10
- package/src/session.ts +2 -1
- package/src/suppress-file.ts +21 -29
- package/src/suppress.ts +3 -3
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +15 -25
- 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/usage.ts +10 -0
- package/src/verbs/analyze.ts +32 -42
- package/src/verbs/describe.ts +4 -4
- package/src/verbs/explain.ts +8 -10
- package/src/verbs/print-projects.ts +4 -3
- package/src/verbs/print-retained.ts +2 -1
- package/src/verbs/print-roots.ts +2 -1
- package/src/verbs/verb.ts +2 -2
- package/src/verbs/version.ts +2 -1
- 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/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
package/dist/src/scope.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SCOPE_SCHEMA } from "./contract/config.generated.js";
|
|
2
|
+
import { dotted, locatedFault, placeIn, readStrict, StrictJSONError } from "./json-document.js";
|
|
2
3
|
import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const ROLE_CONSUMER = "consumer";
|
|
4
|
+
import { message } from "./product.js";
|
|
5
|
+
import { firstRefusal } from "./schema.js";
|
|
6
|
+
import { UsageError } from "./usage.js";
|
|
7
7
|
/**
|
|
8
8
|
* A scope document above this length is refused rather than decoded. It names one
|
|
9
9
|
* target and its consumers, so a larger file is not one.
|
|
10
10
|
*/
|
|
11
11
|
const MAX_DOCUMENT_LENGTH = 1 << 20;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* The bytes read of a scope document. A UTF-16 unit decodes from at most three bytes,
|
|
14
|
+
* so a document past the bound is past it within these.
|
|
15
|
+
*/
|
|
16
|
+
const MAX_DOCUMENT_BYTES = 3 * (MAX_DOCUMENT_LENGTH + 1);
|
|
17
|
+
/**
|
|
18
|
+
* A scope that cannot be read as one. It ends the run with the failure code: the run
|
|
19
|
+
* could not learn what to analyze, so it produced no answer.
|
|
15
20
|
*/
|
|
16
21
|
export class ScopeError extends Error {
|
|
17
22
|
constructor(message) {
|
|
@@ -19,122 +24,48 @@ export class ScopeError extends Error {
|
|
|
19
24
|
this.name = "ScopeError";
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
members: { id: { kind: "leaf" }, role: { kind: "leaf" }, path: { kind: "leaf" } },
|
|
25
|
-
};
|
|
26
|
-
/** The closed key list of the scope document, compared as bytes. */
|
|
27
|
-
const SCOPE_ROOT = {
|
|
28
|
-
kind: "section",
|
|
29
|
-
members: {
|
|
30
|
-
target: MODULE,
|
|
31
|
-
workspace: { kind: "leaf" },
|
|
32
|
-
consumers: { kind: "list", members: MODULE.members ?? {} },
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
function isRecord(value) {
|
|
36
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* One string member, or undefined where the document omits it. A member the
|
|
40
|
-
* document writes is a string holding at least one character, so `null` and `""`
|
|
41
|
-
* are refused rather than read as absent.
|
|
42
|
-
*/
|
|
43
|
-
function stringMember(parent, name, at) {
|
|
44
|
-
if (!Object.hasOwn(parent, name)) {
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
const value = parent[name];
|
|
48
|
-
if (typeof value !== "string") {
|
|
49
|
-
throw new ScopeError(`scope: ${at === "" ? name : `${at}.${name}`} is not a string`);
|
|
50
|
-
}
|
|
51
|
-
if (value === "") {
|
|
52
|
-
throw new ScopeError(`scope: ${at === "" ? name : `${at}.${name}`} names nothing`);
|
|
53
|
-
}
|
|
54
|
-
return value;
|
|
55
|
-
}
|
|
56
|
-
function readModule(value, at, role, documentDir) {
|
|
57
|
-
if (!isRecord(value)) {
|
|
58
|
-
throw new ScopeError(`scope: ${at} is not an object`);
|
|
59
|
-
}
|
|
60
|
-
const declared = stringMember(value, "role", at);
|
|
61
|
-
if (declared !== undefined && declared !== role) {
|
|
62
|
-
throw new ScopeError(`scope: ${at}.role is ${JSON.stringify(declared)}, want ${JSON.stringify(role)}`);
|
|
63
|
-
}
|
|
64
|
-
const path = stringMember(value, "path", at);
|
|
65
|
-
if (path === undefined) {
|
|
66
|
-
throw new ScopeError(`scope: ${at}.path names nothing`);
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
id: stringMember(value, "id", at) ?? "",
|
|
70
|
-
path: isAbsolutePath(path) ? path : joinPath(documentDir, path),
|
|
71
|
-
};
|
|
27
|
+
function refusal(file, place, reason) {
|
|
28
|
+
return new ScopeError(`scope: ${file}: ${place === "" ? "" : `${place}: `}${reason}`);
|
|
72
29
|
}
|
|
73
30
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
function checkKeys(text, file) {
|
|
79
|
-
try {
|
|
80
|
-
checkDocument(text, SCOPE_ROOT, `scope: ${file}`);
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
if (error instanceof ConfigError) {
|
|
84
|
-
// The configuration's refusal names the nearest configuration key, which a
|
|
85
|
-
// scope document does not have.
|
|
86
|
-
throw new ScopeError(error.kind === "unimplemented-key"
|
|
87
|
-
? `scope: ${file}: ${error.key} is not a declared key`
|
|
88
|
-
: error.message);
|
|
89
|
-
}
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Decodes the scope document at `file`. Decoding is strict: an undeclared key, a
|
|
95
|
-
* key written twice in one object, and a value of a type the document does not
|
|
96
|
-
* admit, `null` included, are errors, and a relative path inside the document
|
|
97
|
-
* resolves against the document's own directory.
|
|
98
|
-
*
|
|
99
|
-
* The `workspace` key the format declares is checked and carries nothing here: it
|
|
100
|
-
* names the file a Go consumer resolves the target through, and a TypeScript
|
|
101
|
-
* project resolves one through its own compiler configuration.
|
|
31
|
+
* Decodes the scope document at `file`, held to the Contract's scope schema, a relative
|
|
32
|
+
* path resolved against the document's own directory. A document that cannot be read is
|
|
33
|
+
* a usage error, one that is refused a scope error. `workspace` is read by Go consumers
|
|
34
|
+
* alone: a TypeScript project resolves the target through its own compiler configuration.
|
|
102
35
|
*/
|
|
103
36
|
export function readScope(host, file) {
|
|
104
37
|
const absolute = resolvePath(host.workingDirectory(), file);
|
|
105
38
|
let text;
|
|
106
39
|
try {
|
|
107
|
-
text = host.readFile(absolute);
|
|
40
|
+
text = host.readFile(absolute, MAX_DOCUMENT_BYTES);
|
|
108
41
|
}
|
|
109
42
|
catch (error) {
|
|
110
|
-
throw new
|
|
43
|
+
throw new UsageError(`scope: ${file}: ${message(error)}`);
|
|
111
44
|
}
|
|
112
45
|
if (text.length > MAX_DOCUMENT_LENGTH) {
|
|
113
46
|
throw new ScopeError(`scope: ${file} is longer than ${String(MAX_DOCUMENT_LENGTH)} characters`);
|
|
114
47
|
}
|
|
115
48
|
let value;
|
|
116
49
|
try {
|
|
117
|
-
value =
|
|
50
|
+
value = readStrict(text).value;
|
|
118
51
|
}
|
|
119
52
|
catch (error) {
|
|
120
|
-
|
|
53
|
+
if (!(error instanceof StrictJSONError)) {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
throw refusal(file, dotted(error.place), locatedFault(text, error));
|
|
121
57
|
}
|
|
122
|
-
|
|
123
|
-
|
|
58
|
+
const refused = firstRefusal(SCOPE_SCHEMA, value);
|
|
59
|
+
if (refused !== undefined) {
|
|
60
|
+
throw refusal(file, dotted(placeIn(value, refused.pointer, refused.member)), refused.reason);
|
|
124
61
|
}
|
|
125
|
-
|
|
126
|
-
stringMember(value, "workspace", "");
|
|
62
|
+
const declared = value;
|
|
127
63
|
const documentDir = dirnamePath(absolute);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (!Array.isArray(listed)) {
|
|
134
|
-
throw new ScopeError(`scope: ${file}: consumers is not an array`);
|
|
135
|
-
}
|
|
136
|
-
const consumers = listed.map((entry, index) => readModule(entry, `consumers[${String(index)}]`, ROLE_CONSUMER, documentDir));
|
|
137
|
-
return { target, consumers };
|
|
64
|
+
const moduleOf = (module) => ({
|
|
65
|
+
id: module.id ?? "",
|
|
66
|
+
path: isAbsolutePath(module.path) ? module.path : joinPath(documentDir, module.path),
|
|
67
|
+
});
|
|
68
|
+
return { target: moduleOf(declared.target), consumers: (declared.consumers ?? []).map(moduleOf) };
|
|
138
69
|
}
|
|
139
70
|
/** The single-package scope one directory is, when no scope document is given. */
|
|
140
71
|
export function scopeForDir(host, dir) {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* function invoked through `call`, `apply` or `bind` is the callee.
|
|
8
8
|
*/
|
|
9
9
|
import { isArrayLiteralExpression, isArrowFunction, isAsExpression, isAwaitExpression, isCallExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isElementAccessExpression, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isFunctionLikeDeclaration, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isMethodDeclaration, isNonNullExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isForInStatement, isPropertyAccessExpression, isPropertyDeclaration, isReturnStatement, isSatisfiesExpression, isSetAccessorDeclaration, isSourceFile, isSpreadAssignment, isSpreadElement, isStringLiteral, isTypeAssertion, isTypeLiteralNode, isVariableDeclaration, ModifierFlags, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
|
|
10
|
+
import { parenthesesRemoved } from "./unwrap.js";
|
|
10
11
|
import { SignatureKind, SymbolFlags, TypeFlags, } from "@typescript/native/sync";
|
|
11
12
|
import { aliasChains } from "./alias-chain.js";
|
|
12
13
|
import { calleeName, resolvedTargets, valuesPassed } from "./calls.js";
|
|
@@ -18,7 +19,7 @@ import { must, Unanswerable } from "./query.js";
|
|
|
18
19
|
import { erasedStores } from "./erased-stores.js";
|
|
19
20
|
import { checkedTypes, CONVERSION_MEMBERS, valueReach } from "./value-reach.js";
|
|
20
21
|
/** The serializer every run reads, whatever the configuration names. */
|
|
21
|
-
const JSON_STRINGIFY = {
|
|
22
|
+
const JSON_STRINGIFY = { global: "JSON.stringify" };
|
|
22
23
|
/** The methods a serializer and a formatter resolve by name on a value that left the analysis. */
|
|
23
24
|
const CONVERSIONS = new Set(["toJSON", "toString"]);
|
|
24
25
|
/** The method a serializer calls on a value that carries one, wherever the value goes. */
|
|
@@ -88,11 +89,6 @@ const FUNCTION_INTERFACES = new Set([
|
|
|
88
89
|
"CallableFunction",
|
|
89
90
|
"NewableFunction",
|
|
90
91
|
]);
|
|
91
|
-
function unparenthesized(expression) {
|
|
92
|
-
return isParenthesizedExpression(expression)
|
|
93
|
-
? unparenthesized(expression.expression)
|
|
94
|
-
: expression;
|
|
95
|
-
}
|
|
96
92
|
/**
|
|
97
93
|
* The invocation a call writes through a method of `Function.prototype`, read from
|
|
98
94
|
* the syntax alone: `f.call(self, a)`, `f.apply(self, [a])`, `f.bind(self, a)`, which
|
|
@@ -101,12 +97,12 @@ function unparenthesized(expression) {
|
|
|
101
97
|
* carries. Whether the method is the prototype's is resolution's to say.
|
|
102
98
|
*/
|
|
103
99
|
function throughMethod(call) {
|
|
104
|
-
const expression =
|
|
100
|
+
const expression = parenthesesRemoved(call.expression);
|
|
105
101
|
if (isPropertyAccessExpression(expression) && FUNCTION_METHODS.has(expression.name.text)) {
|
|
106
102
|
const [, ...rest] = call.arguments;
|
|
107
103
|
let passed = rest;
|
|
108
104
|
if (expression.name.text === "apply") {
|
|
109
|
-
const list = rest[0] === undefined ? undefined :
|
|
105
|
+
const list = rest[0] === undefined ? undefined : parenthesesRemoved(rest[0]);
|
|
110
106
|
passed = list === undefined ? [] : isArrayLiteralExpression(list) ? list.elements : [list];
|
|
111
107
|
}
|
|
112
108
|
return {
|
|
@@ -115,7 +111,7 @@ function throughMethod(call) {
|
|
|
115
111
|
};
|
|
116
112
|
}
|
|
117
113
|
if (isCallExpression(expression)) {
|
|
118
|
-
const bound =
|
|
114
|
+
const bound = parenthesesRemoved(expression.expression);
|
|
119
115
|
if (isPropertyAccessExpression(bound) && bound.name.text === "bind") {
|
|
120
116
|
return {
|
|
121
117
|
method: bound.name,
|
package/dist/src/session.js
CHANGED
|
@@ -3,6 +3,7 @@ import { componentLayer, mapperManifest } from "./component-layer.js";
|
|
|
3
3
|
import { DiscoveryError } from "./discover.js";
|
|
4
4
|
import { joinPath } from "./paths.js";
|
|
5
5
|
import { queriesOf, UnansweredLog, } from "./query.js";
|
|
6
|
+
import { MANIFEST } from "./fs/manifest.js";
|
|
6
7
|
/**
|
|
7
8
|
* Opens the compiler client the analysis reads through.
|
|
8
9
|
*
|
|
@@ -16,7 +17,7 @@ export function openEngine(options) {
|
|
|
16
17
|
if (components !== undefined && components.extensions.length > 0) {
|
|
17
18
|
const made = components.host.temporaryDirectory();
|
|
18
19
|
try {
|
|
19
|
-
components.host.writeDocument(joinPath(made.path,
|
|
20
|
+
components.host.writeDocument(joinPath(made.path, MANIFEST), [
|
|
20
21
|
mapperManifest(components.host.componentMapperCommand()),
|
|
21
22
|
]);
|
|
22
23
|
}
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* ignore file by gaining a maintainer's reason.
|
|
6
6
|
*/
|
|
7
7
|
import { CODE } from "./contract/contract.generated.js";
|
|
8
|
+
import { readTargetDocument } from "./fs/document.js";
|
|
8
9
|
import { jsonChunks } from "./json-chunks.js";
|
|
9
|
-
import {
|
|
10
|
-
import { positionAt, walkDocument } from "./json-document.js";
|
|
10
|
+
import { isRecord, positionAt, readStrict, StrictJSONError } from "./json-document.js";
|
|
11
11
|
import { declarationKey } from "./ref.js";
|
|
12
12
|
import { CODE_FORM, SuppressionError, blankReason, isEntryPath, isPartKind, isSymbolRef, siteText, } from "./suppress.js";
|
|
13
|
+
import { message } from "./product.js";
|
|
13
14
|
/** The name and the location the grammar fixes for the ignore file. */
|
|
14
15
|
export const IGNORE_FILE = "deadset-ignore.json";
|
|
15
16
|
/** The name and the location the grammar fixes for the baseline. */
|
|
@@ -43,9 +44,6 @@ const PATH_WANT = "a path relative to the target root, with the solidus as separ
|
|
|
43
44
|
function documentSite(shape) {
|
|
44
45
|
return { path: shape.file, line: 0, column: 0 };
|
|
45
46
|
}
|
|
46
|
-
function isObject(value) {
|
|
47
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
48
|
-
}
|
|
49
47
|
/**
|
|
50
48
|
* Reads one entry or row: the records it binds, the refusals it carries, or the
|
|
51
49
|
* malformed value that ends the run.
|
|
@@ -54,7 +52,7 @@ function readRecord(value, site, byReference, shape) {
|
|
|
54
52
|
const refuse = (text, want) => {
|
|
55
53
|
throw new SuppressionError(shape.mechanism, siteText(site), text, want);
|
|
56
54
|
};
|
|
57
|
-
if (!
|
|
55
|
+
if (!isRecord(value)) {
|
|
58
56
|
return refuse(JSON.stringify(value), shape.recordWant);
|
|
59
57
|
}
|
|
60
58
|
for (const [key, held] of Object.entries(value)) {
|
|
@@ -132,28 +130,27 @@ function byReferenceOf(symbols) {
|
|
|
132
130
|
}
|
|
133
131
|
/** Reads one document at the target root, binding each record against the run's declarations. */
|
|
134
132
|
function readDocument(host, targetRoot, symbols, shape) {
|
|
135
|
-
const
|
|
136
|
-
if (
|
|
133
|
+
const text = readTargetDocument(host, targetRoot, shape.file);
|
|
134
|
+
if (text === undefined) {
|
|
137
135
|
return { records: [], refusals: [] };
|
|
138
136
|
}
|
|
139
|
-
const text = host.readFile(file);
|
|
140
137
|
const malformed = (detail, site = documentSite(shape)) => {
|
|
141
138
|
throw new SuppressionError(shape.mechanism, siteText(site), detail, shape.documentWant);
|
|
142
139
|
};
|
|
143
|
-
let
|
|
140
|
+
let document;
|
|
144
141
|
try {
|
|
145
|
-
|
|
142
|
+
document = readStrict(text, shape.array);
|
|
146
143
|
}
|
|
147
144
|
catch (error) {
|
|
148
|
-
|
|
145
|
+
if (!(error instanceof StrictJSONError)) {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
return malformed(error.message, positionAt(text, shape.file, error.offset));
|
|
149
149
|
}
|
|
150
|
-
|
|
150
|
+
const parsed = document.value;
|
|
151
|
+
if (!isRecord(parsed)) {
|
|
151
152
|
return malformed(shape.file);
|
|
152
153
|
}
|
|
153
|
-
const walk = walkDocument(text, shape.array);
|
|
154
|
-
if (walk.twice !== undefined) {
|
|
155
|
-
malformed(`the member ${JSON.stringify(walk.twice.name)} is written twice, so neither value is chosen`, positionAt(text, shape.file, walk.twice.offset));
|
|
156
|
-
}
|
|
157
154
|
for (const key of Object.keys(parsed)) {
|
|
158
155
|
if (key !== "description" && key !== shape.array) {
|
|
159
156
|
malformed(`the member ${JSON.stringify(key)} is not one the grammar declares`);
|
|
@@ -168,7 +165,7 @@ function readDocument(host, targetRoot, symbols, shape) {
|
|
|
168
165
|
const records = [];
|
|
169
166
|
const refusals = [];
|
|
170
167
|
held.forEach((value, index) => {
|
|
171
|
-
const offset =
|
|
168
|
+
const offset = document.starts.get(index);
|
|
172
169
|
const site = offset === undefined ? documentSite(shape) : positionAt(text, shape.file, offset);
|
|
173
170
|
const read = readRecord(value, site, byReference, shape);
|
|
174
171
|
records.push(...read.records);
|
|
@@ -228,9 +225,9 @@ export function otherSchemeRows(text, own = OWN_SCHEME) {
|
|
|
228
225
|
parsed = JSON.parse(text);
|
|
229
226
|
}
|
|
230
227
|
catch (error) {
|
|
231
|
-
return refuse(
|
|
228
|
+
return refuse(message(error));
|
|
232
229
|
}
|
|
233
|
-
const held =
|
|
230
|
+
const held = isRecord(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
|
|
234
231
|
if (!Array.isArray(held)) {
|
|
235
232
|
return refuse(BASELINE_SHAPE.documentWant);
|
|
236
233
|
}
|
|
@@ -239,7 +236,7 @@ export function otherSchemeRows(text, own = OWN_SCHEME) {
|
|
|
239
236
|
readRecord(value, documentSite(BASELINE_SHAPE), new Map(), BASELINE_SHAPE);
|
|
240
237
|
}
|
|
241
238
|
catch (error) {
|
|
242
|
-
return refuse(
|
|
239
|
+
return refuse(message(error));
|
|
243
240
|
}
|
|
244
241
|
// readRecord has checked every member is a string and the code and symbol are present.
|
|
245
242
|
const { code, symbol, path, reason } = value;
|
|
@@ -280,10 +277,9 @@ function* separatorsEscaped(pieces) {
|
|
|
280
277
|
* A baseline holding one row per finding, in the order given, which is the report's, and
|
|
281
278
|
* the other language's rows `kept`, Go's rows ahead of TypeScript's and each scheme's in
|
|
282
279
|
* the order given, so two analyzers writing one document in either order write the same
|
|
283
|
-
* bytes, in the form grammar/suppression.md fixes. Every
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* rather than written, because the grammar requires a reason on every row.
|
|
280
|
+
* bytes, in the form grammar/suppression.md fixes. Every row carries the provenance as
|
|
281
|
+
* its reason, so a finding missing a value a row needs, or an identity missing its name
|
|
282
|
+
* or version, is refused rather than written.
|
|
287
283
|
*/
|
|
288
284
|
export function writeBaseline(findings, provenance, kept = []) {
|
|
289
285
|
if (provenance.analyzer === "" || provenance.version === "") {
|
package/dist/src/suppress.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* withholds every such finding anyway, and stale otherwise.
|
|
8
8
|
*/
|
|
9
9
|
import { KIND_ROWS, SYMBOL_SUBJECTS, } from "./contract/contract.generated.js";
|
|
10
|
-
import {
|
|
10
|
+
import { UsageError } from "./usage.js";
|
|
11
11
|
import { positionKey } from "./position.js";
|
|
12
12
|
import { isRef } from "./ref.js";
|
|
13
13
|
/**
|
|
@@ -15,9 +15,9 @@ import { isRef } from "./ref.js";
|
|
|
15
15
|
* instruction the analysis cannot carry out. It is a malformed input, so the command line
|
|
16
16
|
* answers it with the usage code.
|
|
17
17
|
*/
|
|
18
|
-
export class SuppressionError extends
|
|
18
|
+
export class SuppressionError extends UsageError {
|
|
19
19
|
constructor(mechanism, at, text, want) {
|
|
20
|
-
super(
|
|
20
|
+
super(`${mechanism} ${at}: ${text}: want ${want}`);
|
|
21
21
|
this.name = "SuppressionError";
|
|
22
22
|
}
|
|
23
23
|
}
|
package/dist/src/symbol-keys.js
CHANGED
|
@@ -5,16 +5,14 @@
|
|
|
5
5
|
* No name node resolves to such a member: the key's name resolves to the constant. An
|
|
6
6
|
* iteration reads the member its well-known iteration symbol keys.
|
|
7
7
|
*/
|
|
8
|
-
import { isComputedPropertyName, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral,
|
|
8
|
+
import { isComputedPropertyName, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isPropertyAccessExpression, isSpreadElement, isStringLiteral, } from "@typescript/native/ast";
|
|
9
|
+
import { parenthesesRemoved } from "./unwrap.js";
|
|
9
10
|
import { nodeKey } from "./inventory.js";
|
|
10
11
|
import { renderPosition } from "./position.js";
|
|
11
12
|
import { isAnswered } from "./query.js";
|
|
12
13
|
/** The identifier an element access's argument names its key by, where it is a name. */
|
|
13
14
|
export function keyNameOf(argument) {
|
|
14
|
-
|
|
15
|
-
while (isParenthesizedExpression(at)) {
|
|
16
|
-
at = at.expression;
|
|
17
|
-
}
|
|
15
|
+
const at = parenthesesRemoved(argument);
|
|
18
16
|
if (isIdentifier(at)) {
|
|
19
17
|
return at;
|
|
20
18
|
}
|
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
* type, because nothing ties the name to a type.
|
|
13
13
|
*/
|
|
14
14
|
import { readComponent } from "./component-files.js";
|
|
15
|
-
import { ConfigError } from "./config.js";
|
|
16
15
|
import { memberNames } from "./member-names.js";
|
|
17
16
|
import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.js";
|
|
18
17
|
import { isComponentFile } from "./position.js";
|
|
18
|
+
import { bytewise } from "./text/compare.js";
|
|
19
|
+
import { filesBelow } from "./fs/tree.js";
|
|
20
|
+
import { UsageError } from "./usage.js";
|
|
19
21
|
/** The kinds of declaration an action can name. */
|
|
20
22
|
const READ_BY_A_TEMPLATE = new Set([
|
|
21
23
|
"method",
|
|
@@ -30,13 +32,6 @@ const READ_BY_A_TEMPLATE = new Set([
|
|
|
30
32
|
const READ_BY_MARKUP = new Set([...READ_BY_A_TEMPLATE, "enum-member"]);
|
|
31
33
|
/** One identifier, as the grammar of the analyzed language spells one. */
|
|
32
34
|
const IDENTIFIER = /[\p{ID_Start}$_][\p{ID_Continue}$\u200C\u200D]*/uy;
|
|
33
|
-
/** Two strings ordered bytewise. */
|
|
34
|
-
function compare(a, b) {
|
|
35
|
-
if (a === b) {
|
|
36
|
-
return 0;
|
|
37
|
-
}
|
|
38
|
-
return a < b ? -1 : 1;
|
|
39
|
-
}
|
|
40
35
|
/**
|
|
41
36
|
* Every template under the configured directories, read once for the run, in path
|
|
42
37
|
* order and each file once. A directory is named relative to the target root, and
|
|
@@ -48,34 +43,29 @@ function compare(a, b) {
|
|
|
48
43
|
export function readTemplates(host, targetRoot, analysis) {
|
|
49
44
|
const files = new Map();
|
|
50
45
|
const read = (dir) => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const path = joinPath(dir, entry.name);
|
|
54
|
-
if (entry.directory) {
|
|
55
|
-
read(path);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
46
|
+
for (const below of filesBelow(host, joinPath(targetRoot, dir), () => true)) {
|
|
47
|
+
const path = joinPath(dir, below);
|
|
58
48
|
const absolute = joinPath(targetRoot, path);
|
|
59
49
|
if (!files.has(path) && host.kindOf(absolute) === "file") {
|
|
60
50
|
files.set(path, host.readFile(absolute));
|
|
61
51
|
}
|
|
62
52
|
}
|
|
63
53
|
};
|
|
64
|
-
for (const configured of analysis.
|
|
54
|
+
for (const configured of analysis.template_dirs) {
|
|
65
55
|
const dir = normalizePath(configured);
|
|
66
56
|
if (isAbsolutePath(dir) ||
|
|
67
57
|
dir === ".." ||
|
|
68
58
|
dir.startsWith("../") ||
|
|
69
59
|
host.kindOf(joinPath(targetRoot, dir)) !== "directory") {
|
|
70
|
-
throw new
|
|
60
|
+
throw new UsageError(`analysis.template_dirs: ${JSON.stringify(configured)} is not a directory below the target root`);
|
|
71
61
|
}
|
|
72
62
|
read(dir);
|
|
73
63
|
}
|
|
74
64
|
return {
|
|
75
|
-
delimiters: analysis.
|
|
65
|
+
delimiters: analysis.template_delimiters,
|
|
76
66
|
files: [...files]
|
|
77
67
|
.map(([path, text]) => ({ path, text }))
|
|
78
|
-
.sort((a, b) =>
|
|
68
|
+
.sort((a, b) => bytewise(a.path, b.path)),
|
|
79
69
|
};
|
|
80
70
|
}
|
|
81
71
|
/** The end of the quoted string that opens at `from`, past its closing quote. */
|
package/dist/src/test-globals.js
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* written for the runner, as one that imports it is.
|
|
8
8
|
*/
|
|
9
9
|
import { isAsExpression, isBinaryExpression, isCallExpression, isExportAssignment, isExpressionStatement, isIdentifier, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, SyntaxKind, } from "@typescript/native/ast";
|
|
10
|
-
import { dirnamePath, joinPath } from "./paths.js";
|
|
10
|
+
import { ancestors, dirnamePath, joinPath } from "./paths.js";
|
|
11
11
|
import { UNANSWERED } from "./query.js";
|
|
12
12
|
import { writtenPath } from "./workspace-programs.js";
|
|
13
|
+
import { filesIn } from "./fs/tree.js";
|
|
14
|
+
import { holdsManifest, INSTALLED_PACKAGES } from "./fs/manifest.js";
|
|
13
15
|
/** The test runner whose configuration injects the globals, and the `types` entry declaring them. */
|
|
14
16
|
export const GLOBALS_RUNNER = "vitest";
|
|
15
17
|
const GLOBALS_TYPES = "vitest/globals";
|
|
@@ -93,17 +95,7 @@ function setsGlobals(file) {
|
|
|
93
95
|
}
|
|
94
96
|
/** Whether the runner's configuration in one manifest's directory injects the globals. */
|
|
95
97
|
function injectsGlobals(host, parse, dir) {
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
names = host
|
|
99
|
-
.readDirectory(dir)
|
|
100
|
-
.filter((entry) => !entry.directory && RUNNER_CONFIG.test(entry.name))
|
|
101
|
-
.map((entry) => entry.name);
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
return names.some((name) => {
|
|
98
|
+
return filesIn(host, dir, (name) => RUNNER_CONFIG.test(name)).some((name) => {
|
|
107
99
|
const path = joinPath(dir, name);
|
|
108
100
|
try {
|
|
109
101
|
return setsGlobals(parse(path, host.readFile(path)));
|
|
@@ -115,25 +107,11 @@ function injectsGlobals(host, parse, dir) {
|
|
|
115
107
|
}
|
|
116
108
|
/** Whether a file declaring the globals is installed where the configuration resolves packages from. */
|
|
117
109
|
function globalsInstalled(host, configFile) {
|
|
118
|
-
|
|
119
|
-
if (host.kindOf(joinPath(dir, "node_modules", GLOBALS_RUNNER, "globals.d.ts")) === "file") {
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
if (dirnamePath(dir) === dir) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
110
|
+
return ancestors(dirnamePath(configFile)).some((dir) => host.kindOf(joinPath(dir, INSTALLED_PACKAGES, GLOBALS_RUNNER, "globals.d.ts")) === "file");
|
|
126
111
|
}
|
|
127
112
|
/** The directory of the manifest nearest one configuration file, at or above its own. */
|
|
128
113
|
function manifestDir(host, configFile) {
|
|
129
|
-
|
|
130
|
-
if (host.kindOf(joinPath(dir, "package.json")) === "file") {
|
|
131
|
-
return dir;
|
|
132
|
-
}
|
|
133
|
-
if (dirnamePath(dir) === dir) {
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
114
|
+
return ancestors(dirnamePath(configFile)).find((dir) => holdsManifest(host, dir));
|
|
137
115
|
}
|
|
138
116
|
function typesOf(value) {
|
|
139
117
|
return Array.isArray(value) && value.every((one) => typeof one === "string") ? value : undefined;
|
package/dist/src/test-joins.js
CHANGED
|
@@ -6,14 +6,16 @@
|
|
|
6
6
|
* file another package's, which its own test runner compiles.
|
|
7
7
|
*/
|
|
8
8
|
import { sourcePaths } from "./file-facts.js";
|
|
9
|
-
import { dirnamePath, joinPath
|
|
9
|
+
import { ancestors, dirnamePath, joinPath } from "./paths.js";
|
|
10
10
|
import { writtenPath } from "./workspace-programs.js";
|
|
11
|
+
import { bytewise } from "./text/compare.js";
|
|
12
|
+
import { holdsManifest } from "./fs/manifest.js";
|
|
11
13
|
const PREFERRED = "/tsconfig.json";
|
|
12
14
|
/** The configurations of one directory in the order a file joins them: `tsconfig.json` first. */
|
|
13
15
|
function byPreference(a, b) {
|
|
14
16
|
const first = a.endsWith(PREFERRED) ? 0 : 1;
|
|
15
17
|
const second = b.endsWith(PREFERRED) ? 0 : 1;
|
|
16
|
-
return first - second || (a
|
|
18
|
+
return first - second || bytewise(a, b);
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
19
21
|
* Per configuration of `configFiles`, the absolute paths of the test files that join it:
|
|
@@ -43,15 +45,13 @@ export function joinedTestFiles(engine, host, root, configFiles, patterns) {
|
|
|
43
45
|
if (listed.has(file) || !patterns.some((pattern) => pattern.test(path))) {
|
|
44
46
|
continue;
|
|
45
47
|
}
|
|
46
|
-
for (
|
|
48
|
+
for (const dir of ancestors(dirnamePath(file), root)) {
|
|
47
49
|
const [nearest] = byDir.get(dir) ?? [];
|
|
48
50
|
if (nearest !== undefined) {
|
|
49
51
|
joins.set(nearest, [...(joins.get(nearest) ?? []), file]);
|
|
50
52
|
break;
|
|
51
53
|
}
|
|
52
|
-
if (host
|
|
53
|
-
dir === root ||
|
|
54
|
-
relativePath(root, dir) === undefined) {
|
|
54
|
+
if (holdsManifest(host, dir)) {
|
|
55
55
|
break;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two strings in the order of their UTF-8 bytes, the order every set of a run is read in.
|
|
3
|
+
* That is code point order, which UTF-16 code unit order (`<` on strings) breaks above
|
|
4
|
+
* U+FFFF: a surrogate unit is below U+E000 while the code point it encodes is above U+FFFF.
|
|
5
|
+
*/
|
|
6
|
+
export function bytewise(a, b) {
|
|
7
|
+
if (a === b) {
|
|
8
|
+
return 0;
|
|
9
|
+
}
|
|
10
|
+
const length = Math.min(a.length, b.length);
|
|
11
|
+
for (let at = 0; at < length; at += 1) {
|
|
12
|
+
const x = a.charCodeAt(at);
|
|
13
|
+
const y = b.charCodeAt(at);
|
|
14
|
+
if (x !== y) {
|
|
15
|
+
return ranked(x) < ranked(y) ? -1 : 1;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return a.length < b.length ? -1 : 1;
|
|
19
|
+
}
|
|
20
|
+
/** One UTF-16 unit moved so that a surrogate ranks above every unit of U+E000 to U+FFFF. */
|
|
21
|
+
function ranked(unit) {
|
|
22
|
+
if (unit >= 0xe000) {
|
|
23
|
+
return unit - 0x800;
|
|
24
|
+
}
|
|
25
|
+
return unit >= 0xd800 ? unit + 0x2000 : unit;
|
|
26
|
+
}
|