@cplieger/deadset-ts 8.0.0-dev.6 → 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 +25 -43
- 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 +2 -1
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/contract/contract.generated.js +13 -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 +2 -4
- package/dist/src/exempt.js +6 -22
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/reads-and-writes.js +2 -1
- package/dist/src/findings/self-check.js +3 -2
- package/dist/src/findings-pass.js +6 -11
- 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/intra-function-parts.js +10 -13
- 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/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 +15 -16
- 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 +11 -20
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +8 -7
- 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/suppress-file.js +8 -10
- 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 +15 -13
- package/dist/src/verbs/describe.js +4 -4
- package/dist/src/verbs/explain.js +5 -3
- package/dist/src/verbs/print-projects.js +2 -1
- 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 +26 -51
- 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 +4 -2
- package/src/configuration-files.ts +27 -170
- package/src/contract/contract.generated.ts +25 -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 +4 -4
- package/src/exempt.ts +5 -27
- package/src/file-facts.ts +6 -24
- package/src/findings/reads-and-writes.ts +2 -1
- package/src/findings/self-check.ts +3 -2
- package/src/findings-pass.ts +6 -12
- 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/intra-function-parts.ts +10 -14
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +5 -0
- package/src/keyof-comparisons.ts +3 -7
- 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 +15 -17
- package/src/report.ts +24 -34
- package/src/reporters/sarif.ts +4 -4
- package/src/reporters/template.ts +4 -20
- package/src/resolve.ts +20 -27
- package/src/roots.ts +7 -20
- package/src/run.ts +8 -13
- 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/suppress-file.ts +8 -11
- 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 +22 -21
- package/src/verbs/describe.ts +4 -4
- package/src/verbs/explain.ts +5 -3
- package/src/verbs/print-projects.ts +2 -1
- package/src/verbs/print-retained.ts +2 -1
- package/src/verbs/print-roots.ts +2 -1
- 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/schema.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConfigError } from "./config.js";
|
|
2
|
+
import { bytewise } from "./text/compare.js";
|
|
2
3
|
const leaf = { kind: "leaf" };
|
|
3
4
|
const integer = { kind: "leaf", integer: true };
|
|
4
5
|
/** A list of declarations, each entry in one of the three shapes the `ts` section states. */
|
|
@@ -128,7 +129,7 @@ function collect(node, at, keys, settingsOnly) {
|
|
|
128
129
|
export function declaredKeys() {
|
|
129
130
|
const keys = [];
|
|
130
131
|
collect(SCHEMA_ROOT, "", keys, false);
|
|
131
|
-
return keys.sort();
|
|
132
|
+
return keys.sort(bytewise);
|
|
132
133
|
}
|
|
133
134
|
/**
|
|
134
135
|
* The dotted path of every setting that holds one value, in ascending order. A
|
|
@@ -139,7 +140,7 @@ export function declaredKeys() {
|
|
|
139
140
|
export function settingPaths() {
|
|
140
141
|
const paths = [];
|
|
141
142
|
collect(SCHEMA_ROOT, "", paths, true);
|
|
142
|
-
return paths.sort();
|
|
143
|
+
return paths.sort(bytewise);
|
|
143
144
|
}
|
|
144
145
|
/**
|
|
145
146
|
* Whether one dotted path names a setting a source may supply: a setting of the
|
|
@@ -373,7 +374,7 @@ export function checkDocument(text, node, label) {
|
|
|
373
374
|
*/
|
|
374
375
|
export function nestFlags(flat, label) {
|
|
375
376
|
const nested = {};
|
|
376
|
-
for (const path of Object.keys(flat).sort()) {
|
|
377
|
+
for (const path of Object.keys(flat).sort(bytewise)) {
|
|
377
378
|
if (!declaresSetting(path)) {
|
|
378
379
|
throw unimplementedKey(label, path);
|
|
379
380
|
}
|
package/dist/src/scope.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ConfigError } from "./config.js";
|
|
2
2
|
import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.js";
|
|
3
3
|
import { checkDocument } from "./schema.js";
|
|
4
|
+
import { isRecord } from "./json-document.js";
|
|
5
|
+
import { message } from "./product.js";
|
|
4
6
|
/** The roles a scope document declares for a module. */
|
|
5
7
|
const ROLE_TARGET = "target";
|
|
6
8
|
const ROLE_CONSUMER = "consumer";
|
|
@@ -32,9 +34,6 @@ const SCOPE_ROOT = {
|
|
|
32
34
|
consumers: { kind: "list", members: MODULE.members ?? {} },
|
|
33
35
|
},
|
|
34
36
|
};
|
|
35
|
-
function isRecord(value) {
|
|
36
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
-
}
|
|
38
37
|
/**
|
|
39
38
|
* One string member, or undefined where the document omits it. A member the
|
|
40
39
|
* document writes is a string holding at least one character, so `null` and `""`
|
|
@@ -107,7 +106,7 @@ export function readScope(host, file) {
|
|
|
107
106
|
text = host.readFile(absolute);
|
|
108
107
|
}
|
|
109
108
|
catch (error) {
|
|
110
|
-
throw new ScopeError(`scope: ${file}: ${
|
|
109
|
+
throw new ScopeError(`scope: ${file}: ${message(error)}`);
|
|
111
110
|
}
|
|
112
111
|
if (text.length > MAX_DOCUMENT_LENGTH) {
|
|
113
112
|
throw new ScopeError(`scope: ${file} is longer than ${String(MAX_DOCUMENT_LENGTH)} characters`);
|
|
@@ -117,7 +116,7 @@ export function readScope(host, file) {
|
|
|
117
116
|
value = JSON.parse(text);
|
|
118
117
|
}
|
|
119
118
|
catch (error) {
|
|
120
|
-
throw new ScopeError(`scope: ${file}: ${
|
|
119
|
+
throw new ScopeError(`scope: ${file}: ${message(error)}`);
|
|
121
120
|
}
|
|
122
121
|
if (!isRecord(value)) {
|
|
123
122
|
throw new ScopeError(`scope: ${file} is not one JSON object`);
|
|
@@ -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";
|
|
@@ -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
|
}
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
import { CODE } from "./contract/contract.generated.js";
|
|
8
8
|
import { jsonChunks } from "./json-chunks.js";
|
|
9
9
|
import { joinPath } from "./paths.js";
|
|
10
|
-
import { positionAt, walkDocument } from "./json-document.js";
|
|
10
|
+
import { isRecord, positionAt, walkDocument } 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)) {
|
|
@@ -145,9 +143,9 @@ function readDocument(host, targetRoot, symbols, shape) {
|
|
|
145
143
|
parsed = JSON.parse(text);
|
|
146
144
|
}
|
|
147
145
|
catch (error) {
|
|
148
|
-
return malformed(`${shape.file}: ${
|
|
146
|
+
return malformed(`${shape.file}: ${message(error)}`);
|
|
149
147
|
}
|
|
150
|
-
if (!
|
|
148
|
+
if (!isRecord(parsed)) {
|
|
151
149
|
return malformed(shape.file);
|
|
152
150
|
}
|
|
153
151
|
const walk = walkDocument(text, shape.array);
|
|
@@ -228,9 +226,9 @@ export function otherSchemeRows(text, own = OWN_SCHEME) {
|
|
|
228
226
|
parsed = JSON.parse(text);
|
|
229
227
|
}
|
|
230
228
|
catch (error) {
|
|
231
|
-
return refuse(
|
|
229
|
+
return refuse(message(error));
|
|
232
230
|
}
|
|
233
|
-
const held =
|
|
231
|
+
const held = isRecord(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
|
|
234
232
|
if (!Array.isArray(held)) {
|
|
235
233
|
return refuse(BASELINE_SHAPE.documentWant);
|
|
236
234
|
}
|
|
@@ -239,7 +237,7 @@ export function otherSchemeRows(text, own = OWN_SCHEME) {
|
|
|
239
237
|
readRecord(value, documentSite(BASELINE_SHAPE), new Map(), BASELINE_SHAPE);
|
|
240
238
|
}
|
|
241
239
|
catch (error) {
|
|
242
|
-
return refuse(
|
|
240
|
+
return refuse(message(error));
|
|
243
241
|
}
|
|
244
242
|
// readRecord has checked every member is a string and the code and symbol are present.
|
|
245
243
|
const { code, symbol, path, reason } = value;
|
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
|
}
|
|
@@ -16,6 +16,8 @@ import { ConfigError } from "./config.js";
|
|
|
16
16
|
import { memberNames } from "./member-names.js";
|
|
17
17
|
import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.js";
|
|
18
18
|
import { isComponentFile } from "./position.js";
|
|
19
|
+
import { bytewise } from "./text/compare.js";
|
|
20
|
+
import { filesBelow } from "./fs/tree.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,13 +43,8 @@ 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));
|
|
@@ -75,7 +65,7 @@ export function readTemplates(host, targetRoot, analysis) {
|
|
|
75
65
|
delimiters: analysis.templateDelimiters,
|
|
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
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glob patterns over paths in two forms. The configured form (the configuration, convention
|
|
3
|
+
* rows, bundler entries, workspace member patterns) has `*` and `?` within a segment, `**`
|
|
4
|
+
* across segments and brace lists. A `.gitignore` line has git's form: bracket expressions
|
|
5
|
+
* and backslash escapes, no brace lists (https://git-scm.com/docs/gitignore#_pattern_format).
|
|
6
|
+
* A configured root is not a glob: it names a symbol reference, and is read where it is used.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* One character of a pattern as the expression syntax reads it literally. The set escaped
|
|
10
|
+
* is closed: under the unicode flag an escape of any other character is refused outright.
|
|
11
|
+
*/
|
|
12
|
+
function quoted(character) {
|
|
13
|
+
return /[.*+?^${}()|[\]\\/]/u.test(character) ? `\\${character}` : character;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The ASCII ranges each POSIX class a bracket expression may name holds, as git's own
|
|
17
|
+
* character table reads them, each range written as its first and last character.
|
|
18
|
+
*/
|
|
19
|
+
const CLASSES = new Map([
|
|
20
|
+
["alnum", "09AZaz"],
|
|
21
|
+
["alpha", "AZaz"],
|
|
22
|
+
["blank", "\t\t "],
|
|
23
|
+
["cntrl", "\0\x1f\x7f\x7f"],
|
|
24
|
+
["digit", "09"],
|
|
25
|
+
["graph", "!~"],
|
|
26
|
+
["lower", "az"],
|
|
27
|
+
["print", " ~"],
|
|
28
|
+
["punct", "!/:@[`{~"],
|
|
29
|
+
["space", "\t\n\r\r "],
|
|
30
|
+
["upper", "AZ"],
|
|
31
|
+
["xdigit", "09AFaf"],
|
|
32
|
+
]);
|
|
33
|
+
/** What a pattern git cannot read matches: nothing, as git aborts the whole match. */
|
|
34
|
+
const NOTHING = "(?!)";
|
|
35
|
+
/** One code point as a member of an expression class. */
|
|
36
|
+
function member(point) {
|
|
37
|
+
return `\\u{${point.toString(16)}}`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The class a bracket expression opening at `open` denotes, and the index past its `]`,
|
|
41
|
+
* read as git's wildmatch reads it: a leading `!` or `^` negates it, a `]` first is a
|
|
42
|
+
* member, `\` escapes one character, `x-y` is a range (one whose `y` is below `x` holds
|
|
43
|
+
* `x` alone) and `[:name:]` a POSIX class. An unclosed bracket or an unknown class name
|
|
44
|
+
* matches nothing. Neither form matches the solidus.
|
|
45
|
+
*/
|
|
46
|
+
function bracket(pattern, open) {
|
|
47
|
+
let at = open + 1;
|
|
48
|
+
const negated = pattern[at] === "!" || pattern[at] === "^";
|
|
49
|
+
if (negated) {
|
|
50
|
+
at += 1;
|
|
51
|
+
}
|
|
52
|
+
const next = () => {
|
|
53
|
+
const point = pattern.codePointAt(at);
|
|
54
|
+
at += point !== undefined && point > 0xffff ? 2 : 1;
|
|
55
|
+
return point;
|
|
56
|
+
};
|
|
57
|
+
let members = "";
|
|
58
|
+
let low;
|
|
59
|
+
for (let first = true;; first = false) {
|
|
60
|
+
let point = next();
|
|
61
|
+
if (point === undefined) {
|
|
62
|
+
return { source: NOTHING, end: pattern.length };
|
|
63
|
+
}
|
|
64
|
+
if (point === 0x5d && !first) {
|
|
65
|
+
return { source: negated ? `[^/${members}]` : `(?!/)[${members}]`, end: at };
|
|
66
|
+
}
|
|
67
|
+
if (point === 0x2d && low !== undefined && at < pattern.length && pattern[at] !== "]") {
|
|
68
|
+
let high = next();
|
|
69
|
+
if (high === 0x5c) {
|
|
70
|
+
high = next();
|
|
71
|
+
}
|
|
72
|
+
if (high === undefined) {
|
|
73
|
+
return { source: NOTHING, end: pattern.length };
|
|
74
|
+
}
|
|
75
|
+
members += high >= low ? `${member(low)}-${member(high)}` : "";
|
|
76
|
+
low = undefined;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (point === 0x5b && pattern[at] === ":") {
|
|
80
|
+
const close = pattern.indexOf("]", at + 1);
|
|
81
|
+
if (close < 0) {
|
|
82
|
+
return { source: NOTHING, end: pattern.length };
|
|
83
|
+
}
|
|
84
|
+
if (close > at + 1 && pattern[close - 1] === ":") {
|
|
85
|
+
const ranges = CLASSES.get(pattern.slice(at + 1, close - 1));
|
|
86
|
+
if (ranges === undefined) {
|
|
87
|
+
return { source: NOTHING, end: pattern.length };
|
|
88
|
+
}
|
|
89
|
+
for (let one = 0; one < ranges.length; one += 2) {
|
|
90
|
+
members += `${member(ranges.charCodeAt(one))}-${member(ranges.charCodeAt(one + 1))}`;
|
|
91
|
+
}
|
|
92
|
+
low = undefined;
|
|
93
|
+
at = close + 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (point === 0x5c) {
|
|
98
|
+
point = next();
|
|
99
|
+
if (point === undefined) {
|
|
100
|
+
return { source: NOTHING, end: pattern.length };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
members += member(point);
|
|
104
|
+
low = point;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** The expression source of one pattern with no brace list. */
|
|
108
|
+
function source(pattern, form) {
|
|
109
|
+
let text = "";
|
|
110
|
+
let at = 0;
|
|
111
|
+
while (at < pattern.length) {
|
|
112
|
+
if (pattern.startsWith("**/", at)) {
|
|
113
|
+
text += "(?:[^/]*/)*";
|
|
114
|
+
at += 3;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (pattern.startsWith("**", at)) {
|
|
118
|
+
text += ".*";
|
|
119
|
+
at += 2;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const character = pattern[at] ?? "";
|
|
123
|
+
if (form === "gitignore" && character === "[") {
|
|
124
|
+
const read = bracket(pattern, at);
|
|
125
|
+
text += read.source;
|
|
126
|
+
at = read.end;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (form === "gitignore" && character === "\\" && at + 1 < pattern.length) {
|
|
130
|
+
text += quoted(pattern[at + 1] ?? "");
|
|
131
|
+
at += 2;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
at += 1;
|
|
135
|
+
text += character === "*" ? "[^/]*" : character === "?" ? "[^/]" : quoted(character);
|
|
136
|
+
}
|
|
137
|
+
return text;
|
|
138
|
+
}
|
|
139
|
+
/** A pattern with every brace list expanded into its alternatives; a lone `{` stands for itself. */
|
|
140
|
+
export function braceExpanded(pattern) {
|
|
141
|
+
const open = pattern.indexOf("{");
|
|
142
|
+
const close = open < 0 ? -1 : pattern.indexOf("}", open);
|
|
143
|
+
if (close < 0) {
|
|
144
|
+
return [pattern];
|
|
145
|
+
}
|
|
146
|
+
const before = pattern.slice(0, open);
|
|
147
|
+
const after = pattern.slice(close + 1);
|
|
148
|
+
return pattern
|
|
149
|
+
.slice(open + 1, close)
|
|
150
|
+
.split(",")
|
|
151
|
+
.flatMap((member) => braceExpanded(before + member + after));
|
|
152
|
+
}
|
|
153
|
+
/** The expression one whole-path pattern of the configured form denotes. */
|
|
154
|
+
export function globExpression(pattern) {
|
|
155
|
+
const alternatives = braceExpanded(pattern).map((one) => source(one, "configured"));
|
|
156
|
+
return new RegExp(`^(?:${alternatives.join("|")})$`, "u");
|
|
157
|
+
}
|
|
158
|
+
/** The expression one `.gitignore` pattern denotes over a path below its file's directory. */
|
|
159
|
+
export function gitignoreExpression(pattern) {
|
|
160
|
+
return new RegExp(`^${source(pattern, "gitignore")}$`, "u");
|
|
161
|
+
}
|
|
162
|
+
const SEGMENTS = new Map();
|
|
163
|
+
/**
|
|
164
|
+
* Whether one directory name matches one segment of a workspace's member pattern, its brace
|
|
165
|
+
* lists already expanded. Neither `*` nor `?` matches a leading dot, as npm's workspaces read
|
|
166
|
+
* them: https://docs.npmjs.com/cli/using-npm/workspaces
|
|
167
|
+
*/
|
|
168
|
+
export function segmentMatches(segment, name) {
|
|
169
|
+
if (name.startsWith(".") && !segment.startsWith(".")) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
let expression = SEGMENTS.get(segment);
|
|
173
|
+
if (expression === undefined) {
|
|
174
|
+
expression = globExpression(segment);
|
|
175
|
+
SEGMENTS.set(segment, expression);
|
|
176
|
+
}
|
|
177
|
+
return expression.test(name);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Whether one path's segments match a member pattern's, `**` standing for any run of segments
|
|
181
|
+
* that starts with no dot.
|
|
182
|
+
*/
|
|
183
|
+
export function pathMatches(pattern, path) {
|
|
184
|
+
const [head, ...rest] = pattern;
|
|
185
|
+
if (head === undefined) {
|
|
186
|
+
return path.length === 0;
|
|
187
|
+
}
|
|
188
|
+
if (head === "**") {
|
|
189
|
+
return (pathMatches(rest, path) ||
|
|
190
|
+
(path.length > 0 && !(path[0] ?? "").startsWith(".") && pathMatches(pattern, path.slice(1))));
|
|
191
|
+
}
|
|
192
|
+
const [name, ...below] = path;
|
|
193
|
+
return name !== undefined && segmentMatches(head, name) && pathMatches(rest, below);
|
|
194
|
+
}
|