@cplieger/deadset-ts 8.0.0-dev.15 → 8.0.0-dev.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/analysis.js +41 -37
- package/dist/src/commands/read.js +6 -7
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +3 -1
- package/dist/src/contract/config.generated.js +1 -1
- package/dist/src/contract/contract.generated.js +119 -2
- package/dist/src/dependencies.js +79 -48
- package/dist/src/entries/build-scripts.js +222 -0
- package/dist/src/{configuration-files.js → entries/configuration-files.js} +20 -58
- package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
- package/dist/src/entries/conventions.js +739 -0
- package/dist/src/{html-entries.js → entries/html-entries.js} +3 -3
- package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +2 -2
- package/dist/src/entries/run-directories.js +77 -0
- package/dist/src/entries/sources.js +144 -0
- package/dist/src/entries/static-module.js +669 -0
- package/dist/src/entries/test-runners.js +684 -0
- package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
- package/dist/src/extensions.js +37 -0
- package/dist/src/facts.js +38 -0
- package/dist/src/findings/emitters.js +5 -8
- package/dist/src/findings/self-check.js +36 -20
- package/dist/src/findings-pass.js +5 -6
- package/dist/src/framework-lifecycle.js +1 -1
- package/dist/src/fs/manifest.js +15 -20
- package/dist/src/generated-file.js +172 -29
- package/dist/src/graph.js +2 -4
- package/dist/src/index.js +1 -1
- package/dist/src/injection-container.js +1 -1
- package/dist/src/inventory/commonjs.js +62 -17
- package/dist/src/inventory/enumerate.js +2 -1
- package/dist/src/javascript-file.js +26 -11
- package/dist/src/load/classify.js +28 -9
- package/dist/src/load/diagnostics.js +8 -1
- package/dist/src/load/discover.js +15 -7
- package/dist/src/load/plan.js +2 -2
- package/dist/src/load/synthesize.js +5 -5
- package/dist/src/load/workspace-manifests.js +1 -1
- package/dist/src/matrix.js +6 -13
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/paths.js +16 -0
- package/dist/src/platform-calls.js +78 -0
- package/dist/src/platform-symbols.js +140 -0
- package/dist/src/references.js +202 -32
- package/dist/src/resolve.js +1 -1
- package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +4 -4
- package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
- package/dist/src/{manifest.js → roots/manifest.js} +4 -4
- package/dist/src/roots/published-reach.js +412 -0
- package/dist/src/roots/root-set.js +489 -0
- package/dist/src/roots/unseen-loads.js +208 -0
- package/dist/src/serialization-contract.js +1 -1
- package/dist/src/source-files.js +5 -9
- package/dist/src/suppress.js +30 -32
- package/dist/src/test-support.js +4 -2
- package/dist/src/verbs/analyze.js +13 -1
- package/dist/src/verbs/describe.js +5 -3
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +75 -52
- package/src/automatic-types.ts +1 -1
- package/src/commands/read.ts +6 -8
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +4 -0
- package/src/contract/config.generated.ts +1 -1
- package/src/contract/contract.generated.ts +138 -2
- package/src/dependencies.ts +96 -49
- package/src/entries/build-scripts.ts +292 -0
- package/src/{configuration-files.ts → entries/configuration-files.ts} +23 -75
- package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
- package/src/{conventions.ts → entries/conventions.ts} +347 -522
- package/src/{html-entries.ts → entries/html-entries.ts} +5 -5
- package/src/{mdx-entries.ts → entries/mdx-entries.ts} +4 -4
- package/src/entries/run-directories.ts +108 -0
- package/src/entries/sources.ts +266 -0
- package/src/entries/static-module.ts +857 -0
- package/src/entries/test-runners.ts +868 -0
- package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
- package/src/exempt.ts +3 -2
- package/src/extensions.ts +45 -0
- package/src/facts.ts +44 -0
- package/src/file-facts.ts +1 -1
- package/src/findings/emitters.ts +14 -10
- package/src/findings/self-check.ts +41 -22
- package/src/findings/visibility-narrowing.ts +1 -1
- package/src/findings-pass.ts +5 -12
- package/src/framework-lifecycle.ts +1 -1
- package/src/fs/manifest.ts +25 -21
- package/src/generated-file.ts +240 -36
- package/src/graph.ts +3 -5
- package/src/index.ts +1 -1
- package/src/injection-container.ts +1 -1
- package/src/inventory/commonjs.ts +102 -17
- package/src/inventory/enumerate.ts +2 -1
- package/src/javascript-file.ts +29 -12
- package/src/load/classify.ts +49 -13
- package/src/load/diagnostics.ts +11 -1
- package/src/load/discover.ts +27 -11
- package/src/load/plan.ts +11 -2
- package/src/load/synthesize.ts +6 -6
- package/src/load/test-globals.ts +1 -1
- package/src/load/workspace-manifests.ts +1 -1
- package/src/load/workspace-run.ts +1 -1
- package/src/matrix.ts +7 -14
- package/src/outside-tests.ts +1 -1
- package/src/paths.ts +18 -0
- package/src/platform-calls.ts +119 -0
- package/src/platform-symbols.ts +205 -0
- package/src/references.ts +281 -34
- package/src/report.ts +1 -1
- package/src/resolve.ts +1 -1
- package/src/{configured-declarations.ts → roots/configured-declarations.ts} +7 -7
- package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
- package/src/{manifest.ts → roots/manifest.ts} +5 -5
- package/src/roots/published-reach.ts +481 -0
- package/src/{roots.ts → roots/root-set.ts} +203 -630
- package/src/roots/unseen-loads.ts +307 -0
- package/src/serialization-contract.ts +1 -1
- package/src/source-files.ts +5 -10
- package/src/suppress.ts +48 -33
- package/src/test-support.ts +8 -3
- package/src/verbs/analyze.ts +17 -4
- package/src/verbs/describe.ts +5 -2
- package/src/verbs/explain.ts +1 -1
- package/dist/src/build-scripts.js +0 -418
- package/dist/src/conventions.js +0 -907
- package/dist/src/entry-points.js +0 -444
- package/dist/src/own-body.js +0 -28
- package/dist/src/roots.js +0 -865
- package/dist/src/unseen-loads.js +0 -189
- package/src/build-scripts.ts +0 -536
- package/src/entry-points.ts +0 -584
- package/src/own-body.ts +0 -42
- package/src/unseen-loads.ts +0 -282
|
@@ -6,38 +6,8 @@
|
|
|
6
6
|
* No configuration file is run.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
isBinaryExpression,
|
|
12
|
-
isCallExpression,
|
|
13
|
-
isClassDeclaration,
|
|
14
|
-
isComputedPropertyName,
|
|
15
|
-
isExportAssignment,
|
|
16
|
-
isExportDeclaration,
|
|
17
|
-
isExpressionStatement,
|
|
18
|
-
isFunctionDeclaration,
|
|
19
|
-
isIdentifier,
|
|
20
|
-
isImportDeclaration,
|
|
21
|
-
isNamedExports,
|
|
22
|
-
isNamespaceImport,
|
|
23
|
-
isNoSubstitutionTemplateLiteral,
|
|
24
|
-
isNumericLiteral,
|
|
25
|
-
isObjectBindingPattern,
|
|
26
|
-
isObjectLiteralExpression,
|
|
27
|
-
isPropertyAccessExpression,
|
|
28
|
-
isPropertyAssignment,
|
|
29
|
-
isShorthandPropertyAssignment,
|
|
30
|
-
isSpreadAssignment,
|
|
31
|
-
isStringLiteral,
|
|
32
|
-
isVariableStatement,
|
|
33
|
-
SyntaxKind,
|
|
34
|
-
type Identifier,
|
|
35
|
-
type Node,
|
|
36
|
-
type ObjectBindingPattern,
|
|
37
|
-
type ObjectLiteralExpression,
|
|
38
|
-
type SourceFile,
|
|
39
|
-
} from "@typescript/native/ast";
|
|
40
|
-
import { lineOf } from "./component-files.ts";
|
|
9
|
+
import type { SourceFile } from "@typescript/native/ast";
|
|
10
|
+
import { lineOf } from "../component-files.ts";
|
|
41
11
|
import {
|
|
42
12
|
CONVENTION_ROWS,
|
|
43
13
|
type ConventionRow,
|
|
@@ -49,10 +19,27 @@ import {
|
|
|
49
19
|
type PackageKey,
|
|
50
20
|
type PrefixedNameReading,
|
|
51
21
|
type ShortNameReading,
|
|
22
|
+
type TestDirectoryReading,
|
|
52
23
|
type TestFileReading,
|
|
53
24
|
} from "./convention-rows.ts";
|
|
54
25
|
import { documentStrings } from "./configuration-files.ts";
|
|
55
|
-
import
|
|
26
|
+
import { readCommands } from "../commands/read.ts";
|
|
27
|
+
import {
|
|
28
|
+
nodeTestGlobs,
|
|
29
|
+
type RunnerInput,
|
|
30
|
+
type ScriptCommand,
|
|
31
|
+
type TestGlob,
|
|
32
|
+
} from "./test-runners.ts";
|
|
33
|
+
import {
|
|
34
|
+
documentModule,
|
|
35
|
+
membersNamed,
|
|
36
|
+
staticModule,
|
|
37
|
+
textsOf,
|
|
38
|
+
valuesAt,
|
|
39
|
+
type StaticModule,
|
|
40
|
+
type Value,
|
|
41
|
+
} from "./static-module.ts";
|
|
42
|
+
import type { Host } from "../host.ts";
|
|
56
43
|
import {
|
|
57
44
|
ancestors,
|
|
58
45
|
dirnamePath,
|
|
@@ -60,17 +47,17 @@ import {
|
|
|
60
47
|
normalizePath,
|
|
61
48
|
relativePath,
|
|
62
49
|
resolvePath,
|
|
63
|
-
} from "
|
|
64
|
-
import type { Failure } from "
|
|
65
|
-
import type { SourceFiles } from "
|
|
66
|
-
import { satisfies } from "
|
|
67
|
-
import { bytewise } from "
|
|
68
|
-
import { isRecord } from "
|
|
69
|
-
import {
|
|
70
|
-
import {
|
|
71
|
-
import {
|
|
72
|
-
import {
|
|
73
|
-
import {
|
|
50
|
+
} from "../paths.ts";
|
|
51
|
+
import type { Failure } from "../failure.ts";
|
|
52
|
+
import type { SourceFiles } from "../source-files.ts";
|
|
53
|
+
import { satisfies } from "../version-range.ts";
|
|
54
|
+
import { bytewise } from "../text/compare.ts";
|
|
55
|
+
import { isRecord } from "../json-document.ts";
|
|
56
|
+
import { filesIn } from "../fs/tree.ts";
|
|
57
|
+
import { DEPENDENCY_SECTIONS, INSTALLED_PACKAGES, MANIFEST, readJSON } from "../fs/manifest.ts";
|
|
58
|
+
import { globExpression } from "../text/glob.ts";
|
|
59
|
+
import { yamlDocument } from "../text/yaml.ts";
|
|
60
|
+
import { APPENDED_EXTENSIONS, braced } from "../extensions.ts";
|
|
74
61
|
|
|
75
62
|
/** One manifest the analysis reads. */
|
|
76
63
|
export interface ConventionManifest {
|
|
@@ -132,8 +119,10 @@ export interface Conventions {
|
|
|
132
119
|
readonly uses: readonly string[];
|
|
133
120
|
/** Per configuration file, absolute, the strings its applied rows' selection keys hold. */
|
|
134
121
|
readonly selected: ReadonlyMap<string, Selected>;
|
|
135
|
-
/** The files, absolute, an applied row's test-file properties name. */
|
|
136
|
-
readonly testFiles:
|
|
122
|
+
/** The files, absolute, an applied row's test-file properties name, each with the string naming it. */
|
|
123
|
+
readonly testFiles: ReadonlyMap<string, string>;
|
|
124
|
+
/** The test-file patterns of the applied rows' test directory readings. */
|
|
125
|
+
readonly testGlobs: readonly TestGlob[];
|
|
137
126
|
}
|
|
138
127
|
|
|
139
128
|
/** The strings one configuration file holds at selection keys, which root no file. */
|
|
@@ -154,17 +143,11 @@ interface ConventionEntry {
|
|
|
154
143
|
/** Parses one file's text with no program. */
|
|
155
144
|
type ParseSource = (fileName: string, text: string) => SourceFile;
|
|
156
145
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* A configuration file its tool reads as a YAML document, read as the JSON document it
|
|
161
|
-
* denotes. Its nodes' offsets are that JSON text's, so no move may read one: a failure's
|
|
162
|
-
* line would name the wrong line.
|
|
163
|
-
*/
|
|
146
|
+
/** A configuration file its tool reads as a YAML document. */
|
|
164
147
|
const YAML_NAME = /\.ya?ml$/u;
|
|
165
148
|
|
|
166
|
-
/** A configuration file
|
|
167
|
-
const
|
|
149
|
+
/** A configuration file its tool reads as a JSON document whose name does not end with `.json`. */
|
|
150
|
+
const JSON_NAME = /^\.[\w-]+rc$|\.jsonc$/u;
|
|
168
151
|
|
|
169
152
|
/** The sections an installed package is required by, so its absence fails the run. */
|
|
170
153
|
const REQUIRED_SECTIONS = ["dependencies", "devDependencies"] as const;
|
|
@@ -200,319 +183,48 @@ function installedVersion(host: Host, dir: string, name: string): string | undef
|
|
|
200
183
|
return undefined;
|
|
201
184
|
}
|
|
202
185
|
|
|
203
|
-
/** The
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
return undefined;
|
|
207
|
-
}
|
|
208
|
-
const { name } = property;
|
|
209
|
-
const key = isComputedPropertyName(name) ? name.expression : name;
|
|
210
|
-
if (
|
|
211
|
-
(isIdentifier(key) && key === name) ||
|
|
212
|
-
isStringLiteral(key) ||
|
|
213
|
-
isNoSubstitutionTemplateLiteral(key)
|
|
214
|
-
) {
|
|
215
|
-
return key.text;
|
|
216
|
-
}
|
|
217
|
-
return undefined;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/** Whether one member's computed name is no literal, so it may set any key. */
|
|
221
|
-
function computesKey(member: Node): boolean {
|
|
222
|
-
const name = (member as { readonly name?: Node }).name;
|
|
223
|
-
if (name === undefined || !isComputedPropertyName(name)) {
|
|
224
|
-
return false;
|
|
225
|
-
}
|
|
226
|
-
const key = name.expression;
|
|
227
|
-
return !(isStringLiteral(key) || isNumericLiteral(key) || isNoSubstitutionTemplateLiteral(key));
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** The values one file's top-level variable declarations bind, by name. */
|
|
231
|
-
function declaredValues(file: SourceFile): ReadonlyMap<string, Node> {
|
|
232
|
-
const values = new Map<string, Node>();
|
|
233
|
-
for (const statement of file.statements) {
|
|
234
|
-
if (isVariableStatement(statement)) {
|
|
235
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
236
|
-
if (isIdentifier(declaration.name) && declaration.initializer !== undefined) {
|
|
237
|
-
values.set(declaration.name.text, declaration.initializer);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return values;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function isModuleExports(node: Node): boolean {
|
|
246
|
-
return (
|
|
247
|
-
isPropertyAccessExpression(node) &&
|
|
248
|
-
isIdentifier(node.expression) &&
|
|
249
|
-
node.expression.text === "module" &&
|
|
250
|
-
node.name.text === "exports"
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Every expression or declaration one file exports as its default, in any module syntax.
|
|
256
|
-
* A JSON module's default export is its document's value.
|
|
257
|
-
*/
|
|
258
|
-
function defaultExports(file: SourceFile): readonly Node[] {
|
|
259
|
-
if (file.fileName.endsWith(JSON_EXTENSION)) {
|
|
260
|
-
return file.statements.flatMap((statement) =>
|
|
261
|
-
isExpressionStatement(statement) ? [statement.expression] : [],
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
const found: Node[] = [];
|
|
265
|
-
for (const statement of file.statements) {
|
|
266
|
-
if (isExportAssignment(statement)) {
|
|
267
|
-
found.push(statement.expression);
|
|
268
|
-
} else if (
|
|
269
|
-
(isFunctionDeclaration(statement) || isClassDeclaration(statement)) &&
|
|
270
|
-
statement.modifiers?.some((one) => one.kind === SyntaxKind.DefaultKeyword) === true
|
|
271
|
-
) {
|
|
272
|
-
found.push(statement);
|
|
273
|
-
} else if (
|
|
274
|
-
isExportDeclaration(statement) &&
|
|
275
|
-
statement.moduleSpecifier === undefined &&
|
|
276
|
-
statement.exportClause !== undefined &&
|
|
277
|
-
isNamedExports(statement.exportClause)
|
|
278
|
-
) {
|
|
279
|
-
for (const element of statement.exportClause.elements) {
|
|
280
|
-
if (element.name.text === "default") {
|
|
281
|
-
found.push(element.propertyName ?? element.name);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
} else if (
|
|
285
|
-
isExpressionStatement(statement) &&
|
|
286
|
-
isBinaryExpression(statement.expression) &&
|
|
287
|
-
statement.expression.operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
288
|
-
isModuleExports(statement.expression.left)
|
|
289
|
-
) {
|
|
290
|
-
found.push(statement.expression.right);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
return found;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/** The declarations whose name is a binding of the scope they are written in. */
|
|
297
|
-
const BINDING_KINDS: ReadonlySet<SyntaxKind> = new Set([
|
|
298
|
-
SyntaxKind.VariableDeclaration,
|
|
299
|
-
SyntaxKind.Parameter,
|
|
300
|
-
SyntaxKind.BindingElement,
|
|
301
|
-
SyntaxKind.FunctionDeclaration,
|
|
302
|
-
SyntaxKind.ClassDeclaration,
|
|
303
|
-
SyntaxKind.ImportClause,
|
|
304
|
-
SyntaxKind.ImportSpecifier,
|
|
305
|
-
SyntaxKind.NamespaceImport,
|
|
306
|
-
SyntaxKind.ImportEqualsDeclaration,
|
|
307
|
-
]);
|
|
308
|
-
|
|
309
|
-
/** Whether one file declares a binding named `require` at any depth, which shadows the global. */
|
|
310
|
-
function declaresRequire(file: SourceFile): boolean {
|
|
311
|
-
let found = false;
|
|
312
|
-
const visit = (node: Node): void => {
|
|
313
|
-
const name = (node as { readonly name?: Node }).name;
|
|
314
|
-
if (
|
|
315
|
-
name !== undefined &&
|
|
316
|
-
isIdentifier(name) &&
|
|
317
|
-
name.text === "require" &&
|
|
318
|
-
BINDING_KINDS.has(node.kind)
|
|
319
|
-
) {
|
|
320
|
-
found = true;
|
|
321
|
-
}
|
|
322
|
-
if (!found) {
|
|
323
|
-
node.forEachChild(visit);
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
file.forEachChild(visit);
|
|
327
|
-
return found;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* The top-level `const`, `let` and `var` declarations that bind a `require` of one module,
|
|
332
|
-
* written with the module's name as its one string-literal argument, in a file that
|
|
333
|
-
* declares no binding named `require`: each binds as an import of the module does.
|
|
334
|
-
*/
|
|
335
|
-
function requireBindings(
|
|
336
|
-
file: SourceFile,
|
|
337
|
-
module: string,
|
|
338
|
-
): readonly (Identifier | ObjectBindingPattern)[] {
|
|
339
|
-
const found: (Identifier | ObjectBindingPattern)[] = [];
|
|
340
|
-
for (const statement of file.statements) {
|
|
341
|
-
if (!isVariableStatement(statement)) {
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
345
|
-
const value =
|
|
346
|
-
declaration.initializer === undefined ? undefined : unwrapped(declaration.initializer);
|
|
347
|
-
const [argument, ...rest] =
|
|
348
|
-
value !== undefined && isCallExpression(value) ? value.arguments : [];
|
|
349
|
-
if (
|
|
350
|
-
value === undefined ||
|
|
351
|
-
!isCallExpression(value) ||
|
|
352
|
-
!isIdentifier(value.expression) ||
|
|
353
|
-
value.expression.text !== "require" ||
|
|
354
|
-
argument === undefined ||
|
|
355
|
-
rest.length > 0 ||
|
|
356
|
-
!isStringLiteral(argument) ||
|
|
357
|
-
argument.text !== module
|
|
358
|
-
) {
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
if (isIdentifier(declaration.name) || isObjectBindingPattern(declaration.name)) {
|
|
362
|
-
found.push(declaration.name);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return found.length > 0 && declaresRequire(file) ? [] : found;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* The first argument of every call one file makes to one module's export, through a
|
|
371
|
-
* named or a namespace import; `undefined` for a call with no argument.
|
|
372
|
-
*/
|
|
373
|
-
function optionsArguments(file: SourceFile, call: OptionsCall): readonly (Node | undefined)[] {
|
|
374
|
-
const names = new Set<string>();
|
|
375
|
-
const namespaces = new Set<string>();
|
|
376
|
-
for (const name of requireBindings(file, call.module)) {
|
|
377
|
-
if (isIdentifier(name)) {
|
|
378
|
-
namespaces.add(name.text);
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
for (const element of name.elements) {
|
|
382
|
-
const local = element.name;
|
|
383
|
-
const exported = element.propertyName ?? local;
|
|
384
|
-
if (
|
|
385
|
-
element.dotDotDotToken === undefined &&
|
|
386
|
-
local !== undefined &&
|
|
387
|
-
isIdentifier(local) &&
|
|
388
|
-
exported !== undefined &&
|
|
389
|
-
(isIdentifier(exported) || isStringLiteral(exported)) &&
|
|
390
|
-
exported.text === call.export
|
|
391
|
-
) {
|
|
392
|
-
names.add(local.text);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
for (const statement of file.statements) {
|
|
397
|
-
const bindings = isImportDeclaration(statement)
|
|
398
|
-
? statement.importClause?.namedBindings
|
|
399
|
-
: undefined;
|
|
400
|
-
if (
|
|
401
|
-
bindings === undefined ||
|
|
402
|
-
!isImportDeclaration(statement) ||
|
|
403
|
-
!isStringLiteral(statement.moduleSpecifier) ||
|
|
404
|
-
statement.moduleSpecifier.text !== call.module
|
|
405
|
-
) {
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
if (isNamespaceImport(bindings)) {
|
|
409
|
-
namespaces.add(bindings.name.text);
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
for (const element of bindings.elements) {
|
|
413
|
-
if ((element.propertyName ?? element.name).text === call.export) {
|
|
414
|
-
names.add(element.name.text);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
const found: (Node | undefined)[] = [];
|
|
419
|
-
const visit = (node: Node): void => {
|
|
420
|
-
if (isCallExpression(node)) {
|
|
421
|
-
const callee = unwrapped(node.expression);
|
|
422
|
-
const named = isIdentifier(callee) && names.has(callee.text);
|
|
423
|
-
const throughNamespace =
|
|
424
|
-
isPropertyAccessExpression(callee) &&
|
|
425
|
-
isIdentifier(callee.expression) &&
|
|
426
|
-
namespaces.has(callee.expression.text) &&
|
|
427
|
-
callee.name.text === call.export;
|
|
428
|
-
if (named || throughNamespace) {
|
|
429
|
-
found.push(node.arguments[0]);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
node.forEachChild(visit);
|
|
433
|
-
};
|
|
434
|
-
file.forEachChild(visit);
|
|
435
|
-
return found;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* The object literal one expression stands for, read through the file's top-level
|
|
440
|
-
* declarations and, where `throughCalls`, through a call's first argument: `undefined`
|
|
441
|
-
* for a call with no argument, and any other node where the expression is not written
|
|
442
|
-
* out as an object literal.
|
|
443
|
-
*/
|
|
444
|
-
function objectOf(
|
|
445
|
-
node: Node,
|
|
446
|
-
declared: ReadonlyMap<string, Node>,
|
|
447
|
-
throughCalls: boolean,
|
|
448
|
-
seen: ReadonlySet<string> = new Set(),
|
|
449
|
-
): Node | undefined {
|
|
450
|
-
const at = unwrapped(node);
|
|
451
|
-
if (isIdentifier(at)) {
|
|
452
|
-
const value = declared.get(at.text);
|
|
453
|
-
if (value !== undefined && !seen.has(at.text)) {
|
|
454
|
-
return objectOf(value, declared, throughCalls, new Set([...seen, at.text]));
|
|
455
|
-
}
|
|
456
|
-
} else if (throughCalls && isCallExpression(at)) {
|
|
457
|
-
const [first] = at.arguments;
|
|
458
|
-
return first === undefined ? undefined : objectOf(first, declared, throughCalls, seen);
|
|
459
|
-
}
|
|
460
|
-
return at;
|
|
186
|
+
/** The objects a reading reads in one configuration file: each named call's options, or the default export. */
|
|
187
|
+
function readObjects(module: StaticModule, call: OptionsCall | undefined): readonly Value[] {
|
|
188
|
+
return call === undefined ? module.defaultExport : module.options(call.module, call.export);
|
|
461
189
|
}
|
|
462
190
|
|
|
463
|
-
/**
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
return isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at) ? at.text : undefined;
|
|
191
|
+
/** Where a value that a reading cannot take as a literal is written, undefined in a document with no positions. */
|
|
192
|
+
interface NotLiteral {
|
|
193
|
+
readonly at: number | undefined;
|
|
467
194
|
}
|
|
468
195
|
|
|
469
196
|
/**
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
const one = literalText(held);
|
|
475
|
-
if (one !== undefined) {
|
|
476
|
-
return [one];
|
|
477
|
-
}
|
|
478
|
-
if (!elements || !isArrayLiteralExpression(held)) {
|
|
479
|
-
return undefined;
|
|
480
|
-
}
|
|
481
|
-
const texts = held.elements.map(literalText);
|
|
482
|
-
return texts.every((text) => text !== undefined) ? texts : undefined;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Reads the values at one path of keys below one object literal into `values`, and
|
|
487
|
-
* answers the first member that does not write its value out: a spread, a value that is
|
|
488
|
-
* not a literal at the path's end, or one that is not an object literal before it.
|
|
197
|
+
* Reads the values at one path of keys below one object into `values`, and answers the
|
|
198
|
+
* first place that does not write its value out: an object that is no object literal, a
|
|
199
|
+
* spread or a computed key, a value at the path's end that is no literal, or one before
|
|
200
|
+
* it that is no object literal.
|
|
489
201
|
*/
|
|
490
202
|
function readPath(
|
|
491
|
-
object:
|
|
203
|
+
object: Value,
|
|
492
204
|
keys: readonly string[],
|
|
493
205
|
values: string[],
|
|
494
206
|
elements: boolean,
|
|
495
|
-
):
|
|
207
|
+
): NotLiteral | undefined {
|
|
208
|
+
if (object.kind !== "object") {
|
|
209
|
+
return object;
|
|
210
|
+
}
|
|
496
211
|
const [key, ...rest] = keys;
|
|
497
|
-
for (const member of object.
|
|
498
|
-
if (
|
|
212
|
+
for (const member of object.members) {
|
|
213
|
+
if (member.key === undefined) {
|
|
499
214
|
return member;
|
|
500
215
|
}
|
|
501
|
-
if (
|
|
216
|
+
if (member.key !== key) {
|
|
502
217
|
continue;
|
|
503
218
|
}
|
|
504
|
-
const held = isPropertyAssignment(member) ? unwrapped(member.initializer) : undefined;
|
|
505
219
|
if (rest.length === 0) {
|
|
506
|
-
const texts =
|
|
220
|
+
const texts = textsOf(member.value, elements);
|
|
507
221
|
if (texts === undefined) {
|
|
508
222
|
return member;
|
|
509
223
|
}
|
|
510
224
|
values.push(...texts);
|
|
511
225
|
} else {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
const failed = readPath(held, rest, values, elements);
|
|
226
|
+
const failed =
|
|
227
|
+
member.value.kind === "object" ? readPath(member.value, rest, values, elements) : member;
|
|
516
228
|
if (failed !== undefined) {
|
|
517
229
|
return failed;
|
|
518
230
|
}
|
|
@@ -521,80 +233,45 @@ function readPath(
|
|
|
521
233
|
return undefined;
|
|
522
234
|
}
|
|
523
235
|
|
|
524
|
-
/**
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
one === undefined ? undefined : objectOf(one, declared, false),
|
|
535
|
-
);
|
|
536
|
-
const found: Node[] = [];
|
|
537
|
-
const visit = (object: Node, keys: readonly string[]): void => {
|
|
538
|
-
if (!isObjectLiteralExpression(object)) {
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
const [key, ...rest] = keys;
|
|
542
|
-
for (const member of object.properties) {
|
|
543
|
-
if (!isPropertyAssignment(member) || keyOf(member) !== key) {
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
const held = unwrapped(member.initializer);
|
|
547
|
-
if (rest.length > 0) {
|
|
548
|
-
visit(held, rest);
|
|
549
|
-
} else {
|
|
550
|
-
found.push(held);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
};
|
|
554
|
-
for (const object of objects) {
|
|
555
|
-
if (object !== undefined) {
|
|
556
|
-
visit(object, property.split("."));
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
return found;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/** The string literals among values: each value itself, or each element of an array value. */
|
|
563
|
-
function stringsAt(file: SourceFile, property: string, call?: OptionsCall): readonly Node[] {
|
|
564
|
-
return valuesAt(file, property, call).flatMap((held) => {
|
|
565
|
-
if (literalText(held) !== undefined) {
|
|
566
|
-
return [held];
|
|
567
|
-
}
|
|
568
|
-
return isArrayLiteralExpression(held)
|
|
569
|
-
? held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined)
|
|
570
|
-
: [];
|
|
571
|
-
});
|
|
236
|
+
/** The text values among the values at one path: each value itself, or each element of an array value. */
|
|
237
|
+
function stringsAt(
|
|
238
|
+
objects: readonly Value[],
|
|
239
|
+
property: string,
|
|
240
|
+
): readonly { readonly text: string; readonly at: number | undefined }[] {
|
|
241
|
+
return valuesAt(objects, property.split(".")).flatMap((value) =>
|
|
242
|
+
(value.kind === "array" ? value.elements : [value]).flatMap((one) =>
|
|
243
|
+
one.kind === "text" ? [one] : [],
|
|
244
|
+
),
|
|
245
|
+
);
|
|
572
246
|
}
|
|
573
247
|
|
|
574
248
|
/** Whether one value holds a helper setting's value, as {@link HelperReading} states it. */
|
|
575
|
-
function holdsValue(held:
|
|
249
|
+
function holdsValue(held: Value, value: true | string): boolean {
|
|
576
250
|
if (value === true) {
|
|
577
|
-
return held.kind ===
|
|
251
|
+
return held.kind === "boolean" && held.value;
|
|
578
252
|
}
|
|
579
|
-
if (
|
|
580
|
-
return
|
|
253
|
+
if (held.kind === "text") {
|
|
254
|
+
return held.text === value;
|
|
581
255
|
}
|
|
582
256
|
return (
|
|
583
|
-
|
|
257
|
+
held.kind === "array" &&
|
|
584
258
|
held.elements.some((element) => {
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
return first !== undefined && literalText(first) === value;
|
|
259
|
+
const first = element.kind === "array" ? element.elements[0] : element;
|
|
260
|
+
return first?.kind === "text" && first.text === value;
|
|
588
261
|
})
|
|
589
262
|
);
|
|
590
263
|
}
|
|
591
264
|
|
|
592
|
-
/** The directory one
|
|
593
|
-
function testFileRoot(
|
|
594
|
-
|
|
595
|
-
|
|
265
|
+
/** The directory one group of objects' test-file strings are read against, as {@link TestFileReading} states it. */
|
|
266
|
+
function testFileRoot(
|
|
267
|
+
objects: readonly Value[],
|
|
268
|
+
dir: string,
|
|
269
|
+
roots: readonly string[],
|
|
270
|
+
): string | undefined {
|
|
271
|
+
for (const property of roots) {
|
|
272
|
+
const values = valuesAt(objects, property.split("."));
|
|
596
273
|
if (values.length > 0) {
|
|
597
|
-
const texts = new Set(values.map(
|
|
274
|
+
const texts = new Set(values.map((one) => (one.kind === "text" ? one.text : undefined)));
|
|
598
275
|
const [text] = texts;
|
|
599
276
|
return texts.size === 1 && text !== undefined ? resolvePath(dir, text) : undefined;
|
|
600
277
|
}
|
|
@@ -602,45 +279,146 @@ function testFileRoot(file: SourceFile, dir: string, reading: TestFileReading):
|
|
|
602
279
|
return dir;
|
|
603
280
|
}
|
|
604
281
|
|
|
605
|
-
/**
|
|
282
|
+
/**
|
|
283
|
+
* The files an applied row's test-file properties name, each string also added to
|
|
284
|
+
* `selected`, since a test file is rooted by no configuration string (conventions.11). A
|
|
285
|
+
* property's path up to its last `*` selects objects that each resolve their strings
|
|
286
|
+
* against their own roots.
|
|
287
|
+
*/
|
|
606
288
|
function testFilesNamed(
|
|
607
|
-
|
|
608
|
-
parse: ParseSource,
|
|
289
|
+
read: ReadFiles,
|
|
609
290
|
dir: string,
|
|
610
291
|
readings: readonly TestFileReading[],
|
|
611
|
-
into:
|
|
292
|
+
into: Map<string, string>,
|
|
293
|
+
selected: Map<string, { starts: Set<number>; texts: string[] }>,
|
|
612
294
|
): void {
|
|
613
295
|
for (const reading of readings) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
296
|
+
const star = reading.property.lastIndexOf("*.");
|
|
297
|
+
const each = star < 0 ? undefined : reading.property.slice(0, star + 1).split(".");
|
|
298
|
+
const property = star < 0 ? reading.property : reading.property.slice(star + 2);
|
|
299
|
+
for (const { name, module } of read(dir, reading.files)) {
|
|
300
|
+
const path = joinPath(dir, name);
|
|
301
|
+
const held = selected.get(path) ?? { starts: new Set<number>(), texts: [] };
|
|
302
|
+
selected.set(path, held);
|
|
303
|
+
const objects = readObjects(module, reading.call);
|
|
304
|
+
const groups = each === undefined ? [objects] : valuesAt(objects, each).map((one) => [one]);
|
|
305
|
+
for (const group of groups) {
|
|
306
|
+
const at = testFileRoot(group, dir, reading.roots);
|
|
307
|
+
if (at === undefined) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
for (const { text, at: start } of stringsAt(group, property)) {
|
|
311
|
+
if (start !== undefined) {
|
|
312
|
+
held.starts.add(start);
|
|
313
|
+
}
|
|
314
|
+
held.texts.push(text);
|
|
315
|
+
if (text !== "") {
|
|
316
|
+
into.set(resolvePath(at, text), text);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
618
319
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** The directory and patterns one configuration object's tests are read from, undefined where its text does not state them. */
|
|
325
|
+
interface TestDirectory {
|
|
326
|
+
readonly directory: string;
|
|
327
|
+
readonly patterns: readonly string[];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** The one value an object's last write of a key gives, `null` where it writes none, undefined where it may hide one. */
|
|
331
|
+
function lastMember(object: Value, key: string): Value | null | undefined {
|
|
332
|
+
const values = membersNamed(object, key);
|
|
333
|
+
return values === undefined ? undefined : (values.at(-1) ?? null);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** The test directory one object states, each member it does not write taken from `inherited`. */
|
|
337
|
+
function testDirectoryOf(
|
|
338
|
+
object: Value,
|
|
339
|
+
reading: TestDirectoryReading,
|
|
340
|
+
inherited: TestDirectory,
|
|
341
|
+
): TestDirectory | undefined {
|
|
342
|
+
if (object.kind !== "object") {
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
const directory = lastMember(object, reading.directory);
|
|
346
|
+
const match = lastMember(object, reading.match);
|
|
347
|
+
if (directory === undefined || match === undefined) {
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
const dirs = directory === null ? [inherited.directory] : textsOf(directory, false);
|
|
351
|
+
const patterns = match === null ? inherited.patterns : textsOf(match, true);
|
|
352
|
+
const [one] = dirs ?? [];
|
|
353
|
+
return one === undefined || patterns === undefined ? undefined : { directory: one, patterns };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The test-file patterns an applied row's test directory readings give: each configuration
|
|
358
|
+
* object's own, or, where it lists projects, each project's, a member a project does not
|
|
359
|
+
* write taken from the configuration. An object whose text does not state them gives none.
|
|
360
|
+
*/
|
|
361
|
+
function testDirectoryGlobs(
|
|
362
|
+
read: ReadFiles,
|
|
363
|
+
dir: string,
|
|
364
|
+
readings: readonly TestDirectoryReading[],
|
|
365
|
+
into: TestGlob[],
|
|
366
|
+
): void {
|
|
367
|
+
for (const reading of readings) {
|
|
368
|
+
const base = { directory: ".", patterns: [reading.defaultMatch] };
|
|
369
|
+
for (const { module } of read(dir, reading.files)) {
|
|
370
|
+
for (const config of module.defaultExport) {
|
|
371
|
+
const top = testDirectoryOf(config, reading, base);
|
|
372
|
+
const projects = lastMember(config, reading.projects);
|
|
373
|
+
if (top === undefined || projects === undefined) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const listed =
|
|
377
|
+
projects === null ? [] : projects.kind === "array" ? projects.elements : [projects];
|
|
378
|
+
const each =
|
|
379
|
+
listed.length === 0 ? [top] : listed.map((one) => testDirectoryOf(one, reading, top));
|
|
380
|
+
for (const found of each) {
|
|
381
|
+
for (const pattern of found?.patterns ?? []) {
|
|
382
|
+
const expression = anywhereBelow(pattern);
|
|
383
|
+
into.push({
|
|
384
|
+
dir: resolvePath(dir, found?.directory ?? "."),
|
|
385
|
+
pattern,
|
|
386
|
+
matches: (name) => expression.test(name.replace(/^\/+/u, "")),
|
|
387
|
+
});
|
|
388
|
+
}
|
|
623
389
|
}
|
|
624
390
|
}
|
|
625
391
|
}
|
|
626
392
|
}
|
|
627
393
|
}
|
|
628
394
|
|
|
395
|
+
/**
|
|
396
|
+
* The expression a test-file pattern denotes over a file's whole path, as Playwright's
|
|
397
|
+
* matcher reads it: a pattern gains a leading `**` segment unless it starts with one, and
|
|
398
|
+
* case is not compared (https://github.com/microsoft/playwright/blob/main/packages/playwright/src/util.ts,
|
|
399
|
+
* `createFileMatcher`).
|
|
400
|
+
*/
|
|
401
|
+
function anywhereBelow(pattern: string): RegExp {
|
|
402
|
+
const expression = globExpression(pattern.startsWith("**/") ? pattern : `**/${pattern}`);
|
|
403
|
+
return new RegExp(expression.source, "iu");
|
|
404
|
+
}
|
|
405
|
+
|
|
629
406
|
/** The strings an applied row's selection keys hold, added per configuration file to `into`. */
|
|
630
407
|
function selectedStrings(
|
|
631
|
-
|
|
632
|
-
parse: ParseSource,
|
|
408
|
+
read: ReadFiles,
|
|
633
409
|
dir: string,
|
|
634
410
|
readings: readonly MoveReading[],
|
|
635
411
|
into: Map<string, { starts: Set<number>; texts: string[] }>,
|
|
636
412
|
): void {
|
|
637
413
|
for (const reading of readings) {
|
|
638
|
-
for (const { name,
|
|
414
|
+
for (const { name, module } of read(dir, reading.files)) {
|
|
639
415
|
const path = joinPath(dir, name);
|
|
640
416
|
const held = into.get(path) ?? { starts: new Set<number>(), texts: [] };
|
|
641
|
-
for (const
|
|
642
|
-
|
|
643
|
-
|
|
417
|
+
for (const { text, at } of stringsAt(readObjects(module, reading.call), reading.property)) {
|
|
418
|
+
if (at !== undefined) {
|
|
419
|
+
held.starts.add(at);
|
|
420
|
+
}
|
|
421
|
+
held.texts.push(text);
|
|
644
422
|
}
|
|
645
423
|
into.set(path, held);
|
|
646
424
|
}
|
|
@@ -666,7 +444,7 @@ function manifestKeyUses(manifest: unknown, keys: readonly string[]): readonly s
|
|
|
666
444
|
}
|
|
667
445
|
|
|
668
446
|
/** The extensions an entry a manifest entry key names without one may carry. */
|
|
669
|
-
const ENTRY_EXTENSIONS =
|
|
447
|
+
const ENTRY_EXTENSIONS = braced(APPENDED_EXTENSIONS);
|
|
670
448
|
|
|
671
449
|
/** The characters that make a manifest string a pattern rather than a directory's name. */
|
|
672
450
|
const PATTERN = /[*?{}[\]]/u;
|
|
@@ -710,12 +488,12 @@ function manifestEntryGlobs(manifest: unknown, keys: readonly ManifestEntryKey[]
|
|
|
710
488
|
interface PropertyReading {
|
|
711
489
|
/** The literal values the file sets the property to. */
|
|
712
490
|
readonly values: readonly string[];
|
|
713
|
-
/**
|
|
714
|
-
readonly notLiteral?:
|
|
491
|
+
/** Where the first value that is not a literal is written, where there is one. */
|
|
492
|
+
readonly notLiteral?: NotLiteral;
|
|
715
493
|
}
|
|
716
494
|
|
|
717
495
|
/**
|
|
718
|
-
* Every value one file writes at one property's path inside the
|
|
496
|
+
* Every value one file writes at one property's path inside the objects it is read from:
|
|
719
497
|
* the options argument of every call `call` names, or the file's default export. A string
|
|
720
498
|
* literal or a template literal with no substitution is the value, and so is each element
|
|
721
499
|
* of an array literal of them where `elements`. A read object, or an object along the
|
|
@@ -723,26 +501,14 @@ interface PropertyReading {
|
|
|
723
501
|
* itself, is not a literal, and neither is any other value at the path.
|
|
724
502
|
*/
|
|
725
503
|
export function readProperty(
|
|
726
|
-
|
|
504
|
+
module: StaticModule,
|
|
727
505
|
property: string,
|
|
728
506
|
call?: OptionsCall,
|
|
729
507
|
elements = false,
|
|
730
508
|
): PropertyReading {
|
|
731
|
-
const declared = declaredValues(file);
|
|
732
|
-
const objects =
|
|
733
|
-
call === undefined
|
|
734
|
-
? defaultExports(file).map((one) => objectOf(one, declared, true))
|
|
735
|
-
: optionsArguments(file, call).map((one) =>
|
|
736
|
-
one === undefined ? undefined : objectOf(one, declared, false),
|
|
737
|
-
);
|
|
738
509
|
const values: string[] = [];
|
|
739
|
-
for (const object of
|
|
740
|
-
|
|
741
|
-
continue;
|
|
742
|
-
}
|
|
743
|
-
const notLiteral = isObjectLiteralExpression(object)
|
|
744
|
-
? readPath(object, property.split("."), values, elements)
|
|
745
|
-
: object;
|
|
510
|
+
for (const object of readObjects(module, call)) {
|
|
511
|
+
const notLiteral = readPath(object, property.split("."), values, elements);
|
|
746
512
|
if (notLiteral !== undefined) {
|
|
747
513
|
return { values, notLiteral };
|
|
748
514
|
}
|
|
@@ -793,8 +559,7 @@ function configurationFiles(host: Host, dir: string, glob: string): readonly str
|
|
|
793
559
|
type MoveResult = { readonly dirs: readonly (string | undefined)[] } | { readonly failure: string };
|
|
794
560
|
|
|
795
561
|
function resolveMove(
|
|
796
|
-
|
|
797
|
-
parse: ParseSource,
|
|
562
|
+
read: ReadFiles,
|
|
798
563
|
dir: string,
|
|
799
564
|
row: ConventionRow,
|
|
800
565
|
move: DirectoryMove,
|
|
@@ -802,13 +567,14 @@ function resolveMove(
|
|
|
802
567
|
): MoveResult {
|
|
803
568
|
const set: string[] = [];
|
|
804
569
|
for (const reading of move.readings) {
|
|
805
|
-
for (const { name, text,
|
|
806
|
-
const read = readProperty(
|
|
570
|
+
for (const { name, text, module } of read(dir, reading.files)) {
|
|
571
|
+
const read = readProperty(module, reading.property, reading.call);
|
|
807
572
|
if (read.notLiteral !== undefined) {
|
|
808
|
-
const
|
|
573
|
+
const { at } = read.notLiteral;
|
|
574
|
+
const where = at === undefined ? name : `${name}:${String(lineOf(text, at))}`;
|
|
809
575
|
return {
|
|
810
576
|
failure:
|
|
811
|
-
`${
|
|
577
|
+
`${where} sets ${reading.property}, which moves a directory ` +
|
|
812
578
|
`the convention row ${row.name} reads, to a value that is not a string literal: ` +
|
|
813
579
|
`write it as a literal, or name ${row.name} in ts.disabled_conventions and its ` +
|
|
814
580
|
`files in ts.entry_files`,
|
|
@@ -828,39 +594,44 @@ function resolveMove(
|
|
|
828
594
|
return { dirs: [...new Set(dirs)] };
|
|
829
595
|
}
|
|
830
596
|
|
|
831
|
-
/**
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
597
|
+
/** One configuration file a reading names, read as a static module. */
|
|
598
|
+
interface ReadFile {
|
|
599
|
+
readonly name: string;
|
|
600
|
+
readonly text: string;
|
|
601
|
+
readonly module: StaticModule;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/** The configuration files in one directory a reading's globs name, in name order. */
|
|
605
|
+
type ReadFiles = (dir: string, files: readonly string[]) => readonly ReadFile[];
|
|
606
|
+
|
|
607
|
+
/** A reader that reads and parses each configuration file once for every row and reading of a run. */
|
|
608
|
+
function readFilesOnce(host: Host, parse: ParseSource): ReadFiles {
|
|
609
|
+
const read = new Map<string, ReadFile | undefined>();
|
|
610
|
+
const readOne = (dir: string, name: string): ReadFile | undefined => {
|
|
611
|
+
const path = joinPath(dir, name);
|
|
612
|
+
if (read.has(path)) {
|
|
613
|
+
return read.get(path);
|
|
614
|
+
}
|
|
615
|
+
let found: ReadFile | undefined;
|
|
616
|
+
try {
|
|
617
|
+
const text = host.readFile(path);
|
|
848
618
|
if (YAML_NAME.test(name)) {
|
|
849
619
|
const value = yamlDocument(text);
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}
|
|
854
|
-
continue;
|
|
620
|
+
found = value === undefined ? undefined : { name, text, module: documentModule(value) };
|
|
621
|
+
} else {
|
|
622
|
+
const file = parse(JSON_NAME.test(name) ? `${path}.json` : path, text);
|
|
623
|
+
found = { name, text, module: staticModule(file) };
|
|
855
624
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
text,
|
|
859
|
-
file: parse(JSON_RC.test(name) ? `${path}${JSON_EXTENSION}` : path, text),
|
|
860
|
-
});
|
|
625
|
+
} catch {
|
|
626
|
+
found = undefined;
|
|
861
627
|
}
|
|
862
|
-
|
|
863
|
-
|
|
628
|
+
read.set(path, found);
|
|
629
|
+
return found;
|
|
630
|
+
};
|
|
631
|
+
return (dir, files) =>
|
|
632
|
+
files.flatMap((glob) =>
|
|
633
|
+
configurationFiles(host, dir, glob).flatMap((name) => readOne(dir, name) ?? []),
|
|
634
|
+
);
|
|
864
635
|
}
|
|
865
636
|
|
|
866
637
|
/**
|
|
@@ -869,8 +640,7 @@ function readingFiles(
|
|
|
869
640
|
* declared dependency the row's package name stands for with some value.
|
|
870
641
|
*/
|
|
871
642
|
function shortNameUses(
|
|
872
|
-
|
|
873
|
-
parse: ParseSource,
|
|
643
|
+
read: ReadFiles,
|
|
874
644
|
dir: string,
|
|
875
645
|
manifest: unknown,
|
|
876
646
|
readings: readonly ShortNameReading[],
|
|
@@ -884,8 +654,8 @@ function shortNameUses(
|
|
|
884
654
|
const used = new Set<string>();
|
|
885
655
|
for (const reading of readings) {
|
|
886
656
|
const [before = "", after = ""] = reading.package.split("{}");
|
|
887
|
-
for (const {
|
|
888
|
-
const read = readProperty(
|
|
657
|
+
for (const { module } of read(dir, reading.files)) {
|
|
658
|
+
const read = readProperty(module, reading.property, reading.call, true);
|
|
889
659
|
for (const value of read.values) {
|
|
890
660
|
used.add(`${before}${value}${after}`);
|
|
891
661
|
}
|
|
@@ -939,8 +709,7 @@ function prefixedPackage(value: string, reading: PrefixedNameReading): string |
|
|
|
939
709
|
|
|
940
710
|
/** The declared dependencies an applied row's prefixed names use. */
|
|
941
711
|
function prefixedNameUses(
|
|
942
|
-
|
|
943
|
-
parse: ParseSource,
|
|
712
|
+
read: ReadFiles,
|
|
944
713
|
dir: string,
|
|
945
714
|
manifest: unknown,
|
|
946
715
|
readings: readonly PrefixedNameReading[],
|
|
@@ -953,8 +722,8 @@ function prefixedNameUses(
|
|
|
953
722
|
);
|
|
954
723
|
const used = new Set<string>();
|
|
955
724
|
for (const reading of readings) {
|
|
956
|
-
for (const {
|
|
957
|
-
for (const value of readProperty(
|
|
725
|
+
for (const { module } of read(dir, reading.files)) {
|
|
726
|
+
for (const value of readProperty(module, reading.property, reading.call, true).values) {
|
|
958
727
|
const named = prefixedPackage(value, reading);
|
|
959
728
|
if (named !== undefined && declared.has(named)) {
|
|
960
729
|
used.add(named);
|
|
@@ -967,8 +736,7 @@ function prefixedNameUses(
|
|
|
967
736
|
|
|
968
737
|
/** The declared dependencies an applied row's helper settings use. */
|
|
969
738
|
function helperUses(
|
|
970
|
-
|
|
971
|
-
parse: ParseSource,
|
|
739
|
+
read: ReadFiles,
|
|
972
740
|
dir: string,
|
|
973
741
|
manifest: unknown,
|
|
974
742
|
readings: readonly HelperReading[],
|
|
@@ -983,8 +751,8 @@ function helperUses(
|
|
|
983
751
|
.filter(
|
|
984
752
|
(reading) =>
|
|
985
753
|
declared.has(reading.package) &&
|
|
986
|
-
|
|
987
|
-
valuesAt(
|
|
754
|
+
read(dir, reading.files).some(({ module }) =>
|
|
755
|
+
valuesAt(readObjects(module, reading.call), reading.property.split(".")).some((held) =>
|
|
988
756
|
holdsValue(held, reading.value),
|
|
989
757
|
),
|
|
990
758
|
),
|
|
@@ -998,8 +766,7 @@ function helperUses(
|
|
|
998
766
|
* by `:` and a name. A member's own name is no such string.
|
|
999
767
|
*/
|
|
1000
768
|
function packageKeyUses(
|
|
1001
|
-
|
|
1002
|
-
parse: ParseSource,
|
|
769
|
+
read: ReadFiles,
|
|
1003
770
|
dir: string,
|
|
1004
771
|
manifest: unknown,
|
|
1005
772
|
keys: readonly PackageKey[],
|
|
@@ -1009,31 +776,31 @@ function packageKeyUses(
|
|
|
1009
776
|
return isRecord(held) ? Object.keys(held) : [];
|
|
1010
777
|
});
|
|
1011
778
|
const strings: string[] = [];
|
|
1012
|
-
const
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
779
|
+
const children = (value: Value): readonly Value[] =>
|
|
780
|
+
value.kind === "object"
|
|
781
|
+
? value.members.map((member) => member.value)
|
|
782
|
+
: value.kind === "array"
|
|
783
|
+
? value.elements
|
|
784
|
+
: [];
|
|
785
|
+
const collect = (value: Value): void => {
|
|
786
|
+
if (value.kind === "text") {
|
|
787
|
+
strings.push(value.text);
|
|
1020
788
|
}
|
|
789
|
+
children(value).forEach(collect);
|
|
1021
790
|
};
|
|
1022
791
|
for (const { key, files } of keys) {
|
|
1023
|
-
const visit = (
|
|
1024
|
-
if (
|
|
1025
|
-
|
|
792
|
+
const visit = (value: Value): void => {
|
|
793
|
+
if (value.kind === "object") {
|
|
794
|
+
value.members
|
|
795
|
+
.filter((member) => member.key === key)
|
|
796
|
+
.forEach((member) => {
|
|
797
|
+
collect(member.value);
|
|
798
|
+
});
|
|
1026
799
|
}
|
|
1027
|
-
|
|
800
|
+
children(value).forEach(visit);
|
|
1028
801
|
};
|
|
1029
|
-
for (const {
|
|
1030
|
-
|
|
1031
|
-
for (const one of defaultExports(file)) {
|
|
1032
|
-
const object = objectOf(one, values, true);
|
|
1033
|
-
if (object !== undefined) {
|
|
1034
|
-
visit(object);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
802
|
+
for (const { module } of read(dir, files)) {
|
|
803
|
+
module.defaultExport.forEach(visit);
|
|
1037
804
|
}
|
|
1038
805
|
}
|
|
1039
806
|
return declared.filter((name) =>
|
|
@@ -1046,6 +813,16 @@ function packageKeyUses(
|
|
|
1046
813
|
);
|
|
1047
814
|
}
|
|
1048
815
|
|
|
816
|
+
/** Every simple command of a manifest's `scripts`, as the command reader reads it in the manifest's directory. */
|
|
817
|
+
function scriptCommands(manifest: unknown, dir: string): readonly ScriptCommand[] {
|
|
818
|
+
const scripts = isRecord(manifest) ? manifest["scripts"] : undefined;
|
|
819
|
+
return isRecord(scripts)
|
|
820
|
+
? Object.values(scripts).flatMap((text) =>
|
|
821
|
+
typeof text === "string" ? readCommands(text, dir).commands : [],
|
|
822
|
+
)
|
|
823
|
+
: [];
|
|
824
|
+
}
|
|
825
|
+
|
|
1049
826
|
/**
|
|
1050
827
|
* What the rows decide over the manifests the analysis reads. A row `disabled` names
|
|
1051
828
|
* is skipped before anything is read for it.
|
|
@@ -1063,9 +840,30 @@ export function readConventions(
|
|
|
1063
840
|
const failures: ConventionFailure[] = [];
|
|
1064
841
|
const uses = new Set<string>();
|
|
1065
842
|
const selected = new Map<string, { starts: Set<number>; texts: string[] }>();
|
|
1066
|
-
const testFiles = new
|
|
843
|
+
const testFiles = new Map<string, string>();
|
|
844
|
+
const testGlobs: TestGlob[] = [];
|
|
845
|
+
const read = readFilesOnce(host, parse);
|
|
1067
846
|
for (const manifest of manifests) {
|
|
1068
847
|
const declared = readJSON(host, joinPath(manifest.dir, MANIFEST));
|
|
848
|
+
let input: RunnerInput | undefined;
|
|
849
|
+
const runnerInput = (): RunnerInput => {
|
|
850
|
+
input ??= {
|
|
851
|
+
host,
|
|
852
|
+
dir: manifest.dir,
|
|
853
|
+
manifest: declared,
|
|
854
|
+
configuration: (dir, names) => {
|
|
855
|
+
for (const name of names) {
|
|
856
|
+
const [found] = read(dir, [name]);
|
|
857
|
+
if (found !== undefined) {
|
|
858
|
+
return { dir, module: found.module };
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return undefined;
|
|
862
|
+
},
|
|
863
|
+
scripts: scriptCommands(declared, manifest.dir),
|
|
864
|
+
};
|
|
865
|
+
return input;
|
|
866
|
+
};
|
|
1069
867
|
const versions = new Map<string, string | undefined>();
|
|
1070
868
|
const missing = new Set<string>();
|
|
1071
869
|
for (const row of rows) {
|
|
@@ -1102,7 +900,7 @@ export function readConventions(
|
|
|
1102
900
|
const resolved = new Map<string, readonly (string | undefined)[]>();
|
|
1103
901
|
let failed: string | undefined;
|
|
1104
902
|
for (const move of row.moves) {
|
|
1105
|
-
const result = resolveMove(
|
|
903
|
+
const result = resolveMove(read, manifest.dir, row, move, resolved);
|
|
1106
904
|
if ("failure" in result) {
|
|
1107
905
|
failed = result.failure;
|
|
1108
906
|
break;
|
|
@@ -1126,25 +924,27 @@ export function readConventions(
|
|
|
1126
924
|
version,
|
|
1127
925
|
manifest: manifest.path,
|
|
1128
926
|
});
|
|
1129
|
-
for (const name of shortNameUses(
|
|
927
|
+
for (const name of shortNameUses(read, manifest.dir, declared, row.shortNames ?? [])) {
|
|
1130
928
|
uses.add(name);
|
|
1131
929
|
}
|
|
1132
930
|
const prefixed = row.prefixedNames ?? [];
|
|
1133
|
-
for (const name of prefixedNameUses(
|
|
931
|
+
for (const name of prefixedNameUses(read, manifest.dir, declared, prefixed)) {
|
|
1134
932
|
uses.add(name);
|
|
1135
933
|
}
|
|
1136
934
|
const keys = row.packageKeys ?? [];
|
|
1137
|
-
for (const name of packageKeyUses(
|
|
935
|
+
for (const name of packageKeyUses(read, manifest.dir, declared, keys)) {
|
|
1138
936
|
uses.add(name);
|
|
1139
937
|
}
|
|
1140
938
|
for (const name of manifestKeyUses(declared, row.manifestKeys ?? [])) {
|
|
1141
939
|
uses.add(name);
|
|
1142
940
|
}
|
|
1143
|
-
for (const name of helperUses(
|
|
941
|
+
for (const name of helperUses(read, manifest.dir, declared, row.helpers ?? [])) {
|
|
1144
942
|
uses.add(name);
|
|
1145
943
|
}
|
|
1146
|
-
selectedStrings(
|
|
1147
|
-
testFilesNamed(
|
|
944
|
+
selectedStrings(read, manifest.dir, row.selections ?? [], selected);
|
|
945
|
+
testFilesNamed(read, manifest.dir, row.testFiles ?? [], testFiles, selected);
|
|
946
|
+
testDirectoryGlobs(read, manifest.dir, row.testDirectories ?? [], testGlobs);
|
|
947
|
+
testGlobs.push(...(row.testRunner?.(runnerInput()) ?? []));
|
|
1148
948
|
const excludes = [
|
|
1149
949
|
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|
|
1150
950
|
].map(globExpression);
|
|
@@ -1164,6 +964,7 @@ export function readConventions(
|
|
|
1164
964
|
});
|
|
1165
965
|
}
|
|
1166
966
|
}
|
|
967
|
+
testGlobs.push(...nodeTestGlobs(runnerInput().scripts));
|
|
1167
968
|
}
|
|
1168
969
|
const unique = [...new Map(applied.map((one) => [JSON.stringify(one), one])).values()];
|
|
1169
970
|
unique.sort(
|
|
@@ -1180,9 +981,33 @@ export function readConventions(
|
|
|
1180
981
|
uses: [...uses].sort(bytewise),
|
|
1181
982
|
selected,
|
|
1182
983
|
testFiles,
|
|
984
|
+
testGlobs,
|
|
1183
985
|
};
|
|
1184
986
|
}
|
|
1185
987
|
|
|
988
|
+
/**
|
|
989
|
+
* The files, absolute, of one project an applied row names as test files, each with the
|
|
990
|
+
* string or the pattern that names it.
|
|
991
|
+
*/
|
|
992
|
+
export function conventionTestFiles(
|
|
993
|
+
conventions: Conventions,
|
|
994
|
+
files: SourceFiles,
|
|
995
|
+
): ReadonlyMap<string, string> {
|
|
996
|
+
const found = new Map(conventions.testFiles);
|
|
997
|
+
for (const { dir, pattern, matches } of conventions.testGlobs) {
|
|
998
|
+
for (const file of files.byName.values()) {
|
|
999
|
+
if (
|
|
1000
|
+
relativePath(dir, file.fileName) !== undefined &&
|
|
1001
|
+
matches(file.fileName) &&
|
|
1002
|
+
!found.has(file.fileName)
|
|
1003
|
+
) {
|
|
1004
|
+
found.set(file.fileName, pattern);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return found;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1186
1011
|
/**
|
|
1187
1012
|
* Every own file of one project below a directory an applied row names as generated, by
|
|
1188
1013
|
* its path below the target root, with the name of the first such row.
|