@cplieger/deadset-ts 8.0.0-dev.15 → 8.0.0-dev.17
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/alias-chain.js +0 -5
- package/dist/src/amd.js +81 -0
- package/dist/src/analysis.js +94 -54
- package/dist/src/augmentations.js +23 -24
- package/dist/src/bindings.js +382 -0
- package/dist/src/calls.js +25 -8
- package/dist/src/commands/read.js +6 -7
- package/dist/src/commands/tools.js +5 -9
- package/dist/src/component-files.js +3 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +9 -10
- package/dist/src/contextual-uses.js +1 -17
- package/dist/src/contract/config.generated.js +5 -2
- package/dist/src/contract/contract.generated.js +119 -2
- package/dist/src/contract/report.generated.js +6 -2
- package/dist/src/dependencies.js +98 -71
- package/dist/src/destructuring.js +1 -19
- package/dist/src/element-access.js +179 -164
- package/dist/src/entries/build-scripts.js +222 -0
- package/dist/src/{configuration-files.js → entries/configuration-files.js} +22 -64
- 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} +9 -12
- package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +3 -3
- package/dist/src/entries/run-directories.js +77 -0
- package/dist/src/entries/sources.js +144 -0
- package/dist/src/entries/static-module.js +670 -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/completion.js +40 -4
- package/dist/src/findings/emitters.js +5 -8
- package/dist/src/{implementations.js → findings/implementations.js} +3 -3
- package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
- package/dist/src/findings/self-check.js +36 -20
- package/dist/src/findings-pass.js +5 -6
- package/dist/src/framework-lifecycle.js +42 -15
- package/dist/src/fs/manifest.js +15 -20
- package/dist/src/generated-file.js +172 -29
- package/dist/src/graph.js +14 -8
- package/dist/src/import-bindings.js +73 -28
- package/dist/src/index/sites.js +557 -0
- package/dist/src/index.js +1 -1
- package/dist/src/indexed-access.js +523 -478
- package/dist/src/injection-container.js +1 -1
- package/dist/src/interface-satisfaction.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/keyof-comparisons.js +124 -103
- 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/expected-module.js +2 -2
- package/dist/src/load/plan.js +2 -2
- package/dist/src/load/specifiers.js +2 -4
- package/dist/src/load/synthesize.js +10 -5
- package/dist/src/load/workspace-manifests.js +1 -1
- package/dist/src/load/workspace-resolution.js +2 -2
- package/dist/src/matrix.js +12 -17
- package/dist/src/module-declarations.js +70 -85
- package/dist/src/overrides.js +134 -112
- package/dist/src/paths.js +56 -1
- package/dist/src/platform-calls.js +78 -0
- package/dist/src/platform-symbols.js +134 -0
- package/dist/src/references/read-rule.js +1 -0
- package/dist/src/references/read-rules.js +48 -0
- package/dist/src/references/sink.js +151 -0
- package/dist/src/references/sources.js +450 -0
- package/dist/src/references.js +111 -1116
- package/dist/src/resolve.js +1 -1
- package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +7 -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 +236 -0
- package/dist/src/serialization-contract.js +9 -4
- package/dist/src/source-files.js +5 -9
- package/dist/src/stores.js +4 -2
- package/dist/src/suppress.js +30 -32
- package/dist/src/sweep.js +25 -13
- package/dist/src/symbol-keys.js +89 -83
- package/dist/src/tests/file-class.js +76 -0
- package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
- package/dist/src/{test-support.js → tests/support.js} +5 -12
- package/dist/src/type-parameter-reads.js +178 -139
- package/dist/src/unimplemented-callees.js +262 -242
- package/dist/src/verbs/analyze.js +15 -3
- package/dist/src/verbs/describe.js +5 -3
- package/dist/src/verbs/explain.js +50 -28
- package/dist/src/verbs/print-retained.js +2 -2
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +0 -7
- package/src/amd.ts +130 -0
- package/src/analysis.ts +156 -70
- package/src/augmentations.ts +41 -27
- package/src/automatic-types.ts +1 -1
- package/src/bindings.ts +460 -0
- package/src/calls.ts +25 -7
- package/src/commands/read.ts +6 -8
- package/src/commands/tools.ts +5 -11
- package/src/component-files.ts +4 -2
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +12 -11
- package/src/contextual-uses.ts +2 -22
- package/src/contract/config.generated.ts +5 -2
- package/src/contract/contract.generated.ts +138 -2
- package/src/contract/report.generated.ts +6 -2
- package/src/dependencies.ts +117 -71
- package/src/destructuring.ts +1 -31
- package/src/element-access.ts +188 -182
- package/src/entries/build-scripts.ts +292 -0
- package/src/{configuration-files.ts → entries/configuration-files.ts} +33 -82
- package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
- package/src/{conventions.ts → entries/conventions.ts} +349 -523
- package/src/{html-entries.ts → entries/html-entries.ts} +19 -18
- package/src/{mdx-entries.ts → entries/mdx-entries.ts} +5 -5
- package/src/entries/run-directories.ts +108 -0
- package/src/entries/sources.ts +266 -0
- package/src/entries/static-module.ts +858 -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 +46 -0
- package/src/file-facts.ts +1 -1
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +43 -4
- package/src/findings/emitter.ts +1 -1
- package/src/findings/emitters.ts +14 -10
- package/src/{implementations.ts → findings/implementations.ts} +4 -4
- package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
- 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 +43 -15
- package/src/fs/manifest.ts +25 -21
- package/src/generated-file.ts +240 -36
- package/src/graph.ts +27 -11
- package/src/import-bindings.ts +98 -29
- package/src/index/sites.ts +781 -0
- package/src/index.ts +1 -1
- package/src/indexed-access.ts +570 -527
- package/src/injection-container.ts +1 -1
- package/src/interface-satisfaction.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/keyof-comparisons.ts +131 -116
- package/src/load/classify.ts +49 -13
- package/src/load/diagnostics.ts +11 -1
- package/src/load/discover.ts +27 -11
- package/src/load/expected-module.ts +8 -2
- package/src/load/plan.ts +11 -2
- package/src/load/specifiers.ts +8 -5
- package/src/load/synthesize.ts +12 -6
- package/src/load/test-globals.ts +1 -1
- package/src/load/workspace-manifests.ts +1 -1
- package/src/load/workspace-resolution.ts +9 -2
- package/src/load/workspace-run.ts +1 -1
- package/src/matrix.ts +13 -17
- package/src/module-declarations.ts +74 -98
- package/src/overrides.ts +162 -134
- package/src/paths.ts +65 -1
- package/src/platform-calls.ts +119 -0
- package/src/platform-symbols.ts +189 -0
- package/src/references/read-rule.ts +60 -0
- package/src/references/read-rules.ts +55 -0
- package/src/references/sink.ts +213 -0
- package/src/references/sources.ts +632 -0
- package/src/references.ts +174 -1492
- package/src/report.ts +2 -2
- package/src/resolve.ts +1 -1
- package/src/{configured-declarations.ts → roots/configured-declarations.ts} +10 -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 +345 -0
- package/src/serialization-contract.ts +9 -4
- package/src/source-files.ts +5 -10
- package/src/stores.ts +13 -2
- package/src/suppress.ts +48 -33
- package/src/sweep.ts +37 -14
- package/src/symbol-keys.ts +102 -102
- package/src/tests/file-class.ts +118 -0
- package/src/{outside-tests.ts → tests/outside.ts} +16 -10
- package/src/{test-support.ts → tests/support.ts} +15 -21
- package/src/type-parameter-reads.ts +187 -158
- package/src/unimplemented-callees.ts +297 -280
- package/src/verbs/analyze.ts +19 -5
- package/src/verbs/describe.ts +5 -2
- package/src/verbs/explain.ts +75 -32
- package/src/verbs/print-retained.ts +2 -2
- 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,28 +19,46 @@ 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,
|
|
45
|
+
cannotStartPackageName,
|
|
58
46
|
dirnamePath,
|
|
59
47
|
joinPath,
|
|
60
48
|
normalizePath,
|
|
61
49
|
relativePath,
|
|
62
50
|
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 {
|
|
51
|
+
} from "../paths.ts";
|
|
52
|
+
import type { Failure } from "../failure.ts";
|
|
53
|
+
import type { SourceFiles } from "../source-files.ts";
|
|
54
|
+
import { satisfies } from "../version-range.ts";
|
|
55
|
+
import { bytewise } from "../text/compare.ts";
|
|
56
|
+
import { isRecord } from "../json-document.ts";
|
|
57
|
+
import { filesIn } from "../fs/tree.ts";
|
|
58
|
+
import { DEPENDENCY_SECTIONS, INSTALLED_PACKAGES, MANIFEST, readJSON } from "../fs/manifest.ts";
|
|
59
|
+
import { globExpression } from "../text/glob.ts";
|
|
60
|
+
import { yamlDocument } from "../text/yaml.ts";
|
|
61
|
+
import { APPENDED_EXTENSIONS, braced } from "../extensions.ts";
|
|
74
62
|
|
|
75
63
|
/** One manifest the analysis reads. */
|
|
76
64
|
export interface ConventionManifest {
|
|
@@ -132,8 +120,10 @@ export interface Conventions {
|
|
|
132
120
|
readonly uses: readonly string[];
|
|
133
121
|
/** Per configuration file, absolute, the strings its applied rows' selection keys hold. */
|
|
134
122
|
readonly selected: ReadonlyMap<string, Selected>;
|
|
135
|
-
/** The files, absolute, an applied row's test-file properties name. */
|
|
136
|
-
readonly testFiles:
|
|
123
|
+
/** The files, absolute, an applied row's test-file properties name, each with the string naming it. */
|
|
124
|
+
readonly testFiles: ReadonlyMap<string, string>;
|
|
125
|
+
/** The test-file patterns of the applied rows' test directory readings. */
|
|
126
|
+
readonly testGlobs: readonly TestGlob[];
|
|
137
127
|
}
|
|
138
128
|
|
|
139
129
|
/** The strings one configuration file holds at selection keys, which root no file. */
|
|
@@ -154,17 +144,11 @@ interface ConventionEntry {
|
|
|
154
144
|
/** Parses one file's text with no program. */
|
|
155
145
|
type ParseSource = (fileName: string, text: string) => SourceFile;
|
|
156
146
|
|
|
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
|
-
*/
|
|
147
|
+
/** A configuration file its tool reads as a YAML document. */
|
|
164
148
|
const YAML_NAME = /\.ya?ml$/u;
|
|
165
149
|
|
|
166
|
-
/** A configuration file
|
|
167
|
-
const
|
|
150
|
+
/** A configuration file its tool reads as a JSON document whose name does not end with `.json`. */
|
|
151
|
+
const JSON_NAME = /^\.[\w-]+rc$|\.jsonc$/u;
|
|
168
152
|
|
|
169
153
|
/** The sections an installed package is required by, so its absence fails the run. */
|
|
170
154
|
const REQUIRED_SECTIONS = ["dependencies", "devDependencies"] as const;
|
|
@@ -200,319 +184,48 @@ function installedVersion(host: Host, dir: string, name: string): string | undef
|
|
|
200
184
|
return undefined;
|
|
201
185
|
}
|
|
202
186
|
|
|
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;
|
|
187
|
+
/** The objects a reading reads in one configuration file: each named call's options, or the default export. */
|
|
188
|
+
function readObjects(module: StaticModule, call: OptionsCall | undefined): readonly Value[] {
|
|
189
|
+
return call === undefined ? module.defaultExport : module.options(call.module, call.export);
|
|
461
190
|
}
|
|
462
191
|
|
|
463
|
-
/**
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
return isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at) ? at.text : undefined;
|
|
192
|
+
/** Where a value that a reading cannot take as a literal is written, undefined in a document with no positions. */
|
|
193
|
+
interface NotLiteral {
|
|
194
|
+
readonly at: number | undefined;
|
|
467
195
|
}
|
|
468
196
|
|
|
469
197
|
/**
|
|
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.
|
|
198
|
+
* Reads the values at one path of keys below one object into `values`, and answers the
|
|
199
|
+
* first place that does not write its value out: an object that is no object literal, a
|
|
200
|
+
* spread or a computed key, a value at the path's end that is no literal, or one before
|
|
201
|
+
* it that is no object literal.
|
|
489
202
|
*/
|
|
490
203
|
function readPath(
|
|
491
|
-
object:
|
|
204
|
+
object: Value,
|
|
492
205
|
keys: readonly string[],
|
|
493
206
|
values: string[],
|
|
494
207
|
elements: boolean,
|
|
495
|
-
):
|
|
208
|
+
): NotLiteral | undefined {
|
|
209
|
+
if (object.kind !== "object") {
|
|
210
|
+
return object;
|
|
211
|
+
}
|
|
496
212
|
const [key, ...rest] = keys;
|
|
497
|
-
for (const member of object.
|
|
498
|
-
if (
|
|
213
|
+
for (const member of object.members) {
|
|
214
|
+
if (member.key === undefined) {
|
|
499
215
|
return member;
|
|
500
216
|
}
|
|
501
|
-
if (
|
|
217
|
+
if (member.key !== key) {
|
|
502
218
|
continue;
|
|
503
219
|
}
|
|
504
|
-
const held = isPropertyAssignment(member) ? unwrapped(member.initializer) : undefined;
|
|
505
220
|
if (rest.length === 0) {
|
|
506
|
-
const texts =
|
|
221
|
+
const texts = textsOf(member.value, elements);
|
|
507
222
|
if (texts === undefined) {
|
|
508
223
|
return member;
|
|
509
224
|
}
|
|
510
225
|
values.push(...texts);
|
|
511
226
|
} else {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
const failed = readPath(held, rest, values, elements);
|
|
227
|
+
const failed =
|
|
228
|
+
member.value.kind === "object" ? readPath(member.value, rest, values, elements) : member;
|
|
516
229
|
if (failed !== undefined) {
|
|
517
230
|
return failed;
|
|
518
231
|
}
|
|
@@ -521,80 +234,45 @@ function readPath(
|
|
|
521
234
|
return undefined;
|
|
522
235
|
}
|
|
523
236
|
|
|
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
|
-
});
|
|
237
|
+
/** The text values among the values at one path: each value itself, or each element of an array value. */
|
|
238
|
+
function stringsAt(
|
|
239
|
+
objects: readonly Value[],
|
|
240
|
+
property: string,
|
|
241
|
+
): readonly { readonly text: string; readonly at: number | undefined }[] {
|
|
242
|
+
return valuesAt(objects, property.split(".")).flatMap((value) =>
|
|
243
|
+
(value.kind === "array" ? value.elements : [value]).flatMap((one) =>
|
|
244
|
+
one.kind === "text" ? [one] : [],
|
|
245
|
+
),
|
|
246
|
+
);
|
|
572
247
|
}
|
|
573
248
|
|
|
574
249
|
/** Whether one value holds a helper setting's value, as {@link HelperReading} states it. */
|
|
575
|
-
function holdsValue(held:
|
|
250
|
+
function holdsValue(held: Value, value: true | string): boolean {
|
|
576
251
|
if (value === true) {
|
|
577
|
-
return held.kind ===
|
|
252
|
+
return held.kind === "boolean" && held.value;
|
|
578
253
|
}
|
|
579
|
-
if (
|
|
580
|
-
return
|
|
254
|
+
if (held.kind === "text") {
|
|
255
|
+
return held.text === value;
|
|
581
256
|
}
|
|
582
257
|
return (
|
|
583
|
-
|
|
258
|
+
held.kind === "array" &&
|
|
584
259
|
held.elements.some((element) => {
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
return first !== undefined && literalText(first) === value;
|
|
260
|
+
const first = element.kind === "array" ? element.elements[0] : element;
|
|
261
|
+
return first?.kind === "text" && first.text === value;
|
|
588
262
|
})
|
|
589
263
|
);
|
|
590
264
|
}
|
|
591
265
|
|
|
592
|
-
/** The directory one
|
|
593
|
-
function testFileRoot(
|
|
594
|
-
|
|
595
|
-
|
|
266
|
+
/** The directory one group of objects' test-file strings are read against, as {@link TestFileReading} states it. */
|
|
267
|
+
function testFileRoot(
|
|
268
|
+
objects: readonly Value[],
|
|
269
|
+
dir: string,
|
|
270
|
+
roots: readonly string[],
|
|
271
|
+
): string | undefined {
|
|
272
|
+
for (const property of roots) {
|
|
273
|
+
const values = valuesAt(objects, property.split("."));
|
|
596
274
|
if (values.length > 0) {
|
|
597
|
-
const texts = new Set(values.map(
|
|
275
|
+
const texts = new Set(values.map((one) => (one.kind === "text" ? one.text : undefined)));
|
|
598
276
|
const [text] = texts;
|
|
599
277
|
return texts.size === 1 && text !== undefined ? resolvePath(dir, text) : undefined;
|
|
600
278
|
}
|
|
@@ -602,45 +280,146 @@ function testFileRoot(file: SourceFile, dir: string, reading: TestFileReading):
|
|
|
602
280
|
return dir;
|
|
603
281
|
}
|
|
604
282
|
|
|
605
|
-
/**
|
|
283
|
+
/**
|
|
284
|
+
* The files an applied row's test-file properties name, each string also added to
|
|
285
|
+
* `selected`, since a test file is rooted by no configuration string (conventions.11). A
|
|
286
|
+
* property's path up to its last `*` selects objects that each resolve their strings
|
|
287
|
+
* against their own roots.
|
|
288
|
+
*/
|
|
606
289
|
function testFilesNamed(
|
|
607
|
-
|
|
608
|
-
parse: ParseSource,
|
|
290
|
+
read: ReadFiles,
|
|
609
291
|
dir: string,
|
|
610
292
|
readings: readonly TestFileReading[],
|
|
611
|
-
into:
|
|
293
|
+
into: Map<string, string>,
|
|
294
|
+
selected: Map<string, { starts: Set<number>; texts: string[] }>,
|
|
612
295
|
): void {
|
|
613
296
|
for (const reading of readings) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
297
|
+
const star = reading.property.lastIndexOf("*.");
|
|
298
|
+
const each = star < 0 ? undefined : reading.property.slice(0, star + 1).split(".");
|
|
299
|
+
const property = star < 0 ? reading.property : reading.property.slice(star + 2);
|
|
300
|
+
for (const { name, module } of read(dir, reading.files)) {
|
|
301
|
+
const path = joinPath(dir, name);
|
|
302
|
+
const held = selected.get(path) ?? { starts: new Set<number>(), texts: [] };
|
|
303
|
+
selected.set(path, held);
|
|
304
|
+
const objects = readObjects(module, reading.call);
|
|
305
|
+
const groups = each === undefined ? [objects] : valuesAt(objects, each).map((one) => [one]);
|
|
306
|
+
for (const group of groups) {
|
|
307
|
+
const at = testFileRoot(group, dir, reading.roots);
|
|
308
|
+
if (at === undefined) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
for (const { text, at: start } of stringsAt(group, property)) {
|
|
312
|
+
if (start !== undefined) {
|
|
313
|
+
held.starts.add(start);
|
|
314
|
+
}
|
|
315
|
+
held.texts.push(text);
|
|
316
|
+
if (text !== "") {
|
|
317
|
+
into.set(resolvePath(at, text), text);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
618
320
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** The directory and patterns one configuration object's tests are read from, undefined where its text does not state them. */
|
|
326
|
+
interface TestDirectory {
|
|
327
|
+
readonly directory: string;
|
|
328
|
+
readonly patterns: readonly string[];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** The one value an object's last write of a key gives, `null` where it writes none, undefined where it may hide one. */
|
|
332
|
+
function lastMember(object: Value, key: string): Value | null | undefined {
|
|
333
|
+
const values = membersNamed(object, key);
|
|
334
|
+
return values === undefined ? undefined : (values.at(-1) ?? null);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** The test directory one object states, each member it does not write taken from `inherited`. */
|
|
338
|
+
function testDirectoryOf(
|
|
339
|
+
object: Value,
|
|
340
|
+
reading: TestDirectoryReading,
|
|
341
|
+
inherited: TestDirectory,
|
|
342
|
+
): TestDirectory | undefined {
|
|
343
|
+
if (object.kind !== "object") {
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
const directory = lastMember(object, reading.directory);
|
|
347
|
+
const match = lastMember(object, reading.match);
|
|
348
|
+
if (directory === undefined || match === undefined) {
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
351
|
+
const dirs = directory === null ? [inherited.directory] : textsOf(directory, false);
|
|
352
|
+
const patterns = match === null ? inherited.patterns : textsOf(match, true);
|
|
353
|
+
const [one] = dirs ?? [];
|
|
354
|
+
return one === undefined || patterns === undefined ? undefined : { directory: one, patterns };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* The test-file patterns an applied row's test directory readings give: each configuration
|
|
359
|
+
* object's own, or, where it lists projects, each project's, a member a project does not
|
|
360
|
+
* write taken from the configuration. An object whose text does not state them gives none.
|
|
361
|
+
*/
|
|
362
|
+
function testDirectoryGlobs(
|
|
363
|
+
read: ReadFiles,
|
|
364
|
+
dir: string,
|
|
365
|
+
readings: readonly TestDirectoryReading[],
|
|
366
|
+
into: TestGlob[],
|
|
367
|
+
): void {
|
|
368
|
+
for (const reading of readings) {
|
|
369
|
+
const base = { directory: ".", patterns: [reading.defaultMatch] };
|
|
370
|
+
for (const { module } of read(dir, reading.files)) {
|
|
371
|
+
for (const config of module.defaultExport) {
|
|
372
|
+
const top = testDirectoryOf(config, reading, base);
|
|
373
|
+
const projects = lastMember(config, reading.projects);
|
|
374
|
+
if (top === undefined || projects === undefined) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const listed =
|
|
378
|
+
projects === null ? [] : projects.kind === "array" ? projects.elements : [projects];
|
|
379
|
+
const each =
|
|
380
|
+
listed.length === 0 ? [top] : listed.map((one) => testDirectoryOf(one, reading, top));
|
|
381
|
+
for (const found of each) {
|
|
382
|
+
for (const pattern of found?.patterns ?? []) {
|
|
383
|
+
const expression = anywhereBelow(pattern);
|
|
384
|
+
into.push({
|
|
385
|
+
dir: resolvePath(dir, found?.directory ?? "."),
|
|
386
|
+
pattern,
|
|
387
|
+
matches: (name) => expression.test(name.replace(/^\/+/u, "")),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
623
390
|
}
|
|
624
391
|
}
|
|
625
392
|
}
|
|
626
393
|
}
|
|
627
394
|
}
|
|
628
395
|
|
|
396
|
+
/**
|
|
397
|
+
* The expression a test-file pattern denotes over a file's whole path, as Playwright's
|
|
398
|
+
* matcher reads it: a pattern gains a leading `**` segment unless it starts with one, and
|
|
399
|
+
* case is not compared (https://github.com/microsoft/playwright/blob/main/packages/playwright/src/util.ts,
|
|
400
|
+
* `createFileMatcher`).
|
|
401
|
+
*/
|
|
402
|
+
function anywhereBelow(pattern: string): RegExp {
|
|
403
|
+
const expression = globExpression(pattern.startsWith("**/") ? pattern : `**/${pattern}`);
|
|
404
|
+
return new RegExp(expression.source, "iu");
|
|
405
|
+
}
|
|
406
|
+
|
|
629
407
|
/** The strings an applied row's selection keys hold, added per configuration file to `into`. */
|
|
630
408
|
function selectedStrings(
|
|
631
|
-
|
|
632
|
-
parse: ParseSource,
|
|
409
|
+
read: ReadFiles,
|
|
633
410
|
dir: string,
|
|
634
411
|
readings: readonly MoveReading[],
|
|
635
412
|
into: Map<string, { starts: Set<number>; texts: string[] }>,
|
|
636
413
|
): void {
|
|
637
414
|
for (const reading of readings) {
|
|
638
|
-
for (const { name,
|
|
415
|
+
for (const { name, module } of read(dir, reading.files)) {
|
|
639
416
|
const path = joinPath(dir, name);
|
|
640
417
|
const held = into.get(path) ?? { starts: new Set<number>(), texts: [] };
|
|
641
|
-
for (const
|
|
642
|
-
|
|
643
|
-
|
|
418
|
+
for (const { text, at } of stringsAt(readObjects(module, reading.call), reading.property)) {
|
|
419
|
+
if (at !== undefined) {
|
|
420
|
+
held.starts.add(at);
|
|
421
|
+
}
|
|
422
|
+
held.texts.push(text);
|
|
644
423
|
}
|
|
645
424
|
into.set(path, held);
|
|
646
425
|
}
|
|
@@ -666,7 +445,7 @@ function manifestKeyUses(manifest: unknown, keys: readonly string[]): readonly s
|
|
|
666
445
|
}
|
|
667
446
|
|
|
668
447
|
/** The extensions an entry a manifest entry key names without one may carry. */
|
|
669
|
-
const ENTRY_EXTENSIONS =
|
|
448
|
+
const ENTRY_EXTENSIONS = braced(APPENDED_EXTENSIONS);
|
|
670
449
|
|
|
671
450
|
/** The characters that make a manifest string a pattern rather than a directory's name. */
|
|
672
451
|
const PATTERN = /[*?{}[\]]/u;
|
|
@@ -710,12 +489,12 @@ function manifestEntryGlobs(manifest: unknown, keys: readonly ManifestEntryKey[]
|
|
|
710
489
|
interface PropertyReading {
|
|
711
490
|
/** The literal values the file sets the property to. */
|
|
712
491
|
readonly values: readonly string[];
|
|
713
|
-
/**
|
|
714
|
-
readonly notLiteral?:
|
|
492
|
+
/** Where the first value that is not a literal is written, where there is one. */
|
|
493
|
+
readonly notLiteral?: NotLiteral;
|
|
715
494
|
}
|
|
716
495
|
|
|
717
496
|
/**
|
|
718
|
-
* Every value one file writes at one property's path inside the
|
|
497
|
+
* Every value one file writes at one property's path inside the objects it is read from:
|
|
719
498
|
* the options argument of every call `call` names, or the file's default export. A string
|
|
720
499
|
* literal or a template literal with no substitution is the value, and so is each element
|
|
721
500
|
* of an array literal of them where `elements`. A read object, or an object along the
|
|
@@ -723,26 +502,14 @@ interface PropertyReading {
|
|
|
723
502
|
* itself, is not a literal, and neither is any other value at the path.
|
|
724
503
|
*/
|
|
725
504
|
export function readProperty(
|
|
726
|
-
|
|
505
|
+
module: StaticModule,
|
|
727
506
|
property: string,
|
|
728
507
|
call?: OptionsCall,
|
|
729
508
|
elements = false,
|
|
730
509
|
): 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
510
|
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;
|
|
511
|
+
for (const object of readObjects(module, call)) {
|
|
512
|
+
const notLiteral = readPath(object, property.split("."), values, elements);
|
|
746
513
|
if (notLiteral !== undefined) {
|
|
747
514
|
return { values, notLiteral };
|
|
748
515
|
}
|
|
@@ -793,8 +560,7 @@ function configurationFiles(host: Host, dir: string, glob: string): readonly str
|
|
|
793
560
|
type MoveResult = { readonly dirs: readonly (string | undefined)[] } | { readonly failure: string };
|
|
794
561
|
|
|
795
562
|
function resolveMove(
|
|
796
|
-
|
|
797
|
-
parse: ParseSource,
|
|
563
|
+
read: ReadFiles,
|
|
798
564
|
dir: string,
|
|
799
565
|
row: ConventionRow,
|
|
800
566
|
move: DirectoryMove,
|
|
@@ -802,13 +568,14 @@ function resolveMove(
|
|
|
802
568
|
): MoveResult {
|
|
803
569
|
const set: string[] = [];
|
|
804
570
|
for (const reading of move.readings) {
|
|
805
|
-
for (const { name, text,
|
|
806
|
-
const read = readProperty(
|
|
571
|
+
for (const { name, text, module } of read(dir, reading.files)) {
|
|
572
|
+
const read = readProperty(module, reading.property, reading.call);
|
|
807
573
|
if (read.notLiteral !== undefined) {
|
|
808
|
-
const
|
|
574
|
+
const { at } = read.notLiteral;
|
|
575
|
+
const where = at === undefined ? name : `${name}:${String(lineOf(text, at))}`;
|
|
809
576
|
return {
|
|
810
577
|
failure:
|
|
811
|
-
`${
|
|
578
|
+
`${where} sets ${reading.property}, which moves a directory ` +
|
|
812
579
|
`the convention row ${row.name} reads, to a value that is not a string literal: ` +
|
|
813
580
|
`write it as a literal, or name ${row.name} in ts.disabled_conventions and its ` +
|
|
814
581
|
`files in ts.entry_files`,
|
|
@@ -828,39 +595,44 @@ function resolveMove(
|
|
|
828
595
|
return { dirs: [...new Set(dirs)] };
|
|
829
596
|
}
|
|
830
597
|
|
|
831
|
-
/**
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
598
|
+
/** One configuration file a reading names, read as a static module. */
|
|
599
|
+
interface ReadFile {
|
|
600
|
+
readonly name: string;
|
|
601
|
+
readonly text: string;
|
|
602
|
+
readonly module: StaticModule;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** The configuration files in one directory a reading's globs name, in name order. */
|
|
606
|
+
type ReadFiles = (dir: string, files: readonly string[]) => readonly ReadFile[];
|
|
607
|
+
|
|
608
|
+
/** A reader that reads and parses each configuration file once for every row and reading of a run. */
|
|
609
|
+
function readFilesOnce(host: Host, parse: ParseSource): ReadFiles {
|
|
610
|
+
const read = new Map<string, ReadFile | undefined>();
|
|
611
|
+
const readOne = (dir: string, name: string): ReadFile | undefined => {
|
|
612
|
+
const path = joinPath(dir, name);
|
|
613
|
+
if (read.has(path)) {
|
|
614
|
+
return read.get(path);
|
|
615
|
+
}
|
|
616
|
+
let found: ReadFile | undefined;
|
|
617
|
+
try {
|
|
618
|
+
const text = host.readFile(path);
|
|
848
619
|
if (YAML_NAME.test(name)) {
|
|
849
620
|
const value = yamlDocument(text);
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}
|
|
854
|
-
continue;
|
|
621
|
+
found = value === undefined ? undefined : { name, text, module: documentModule(value) };
|
|
622
|
+
} else {
|
|
623
|
+
const file = parse(JSON_NAME.test(name) ? `${path}.json` : path, text);
|
|
624
|
+
found = { name, text, module: staticModule(file) };
|
|
855
625
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
text,
|
|
859
|
-
file: parse(JSON_RC.test(name) ? `${path}${JSON_EXTENSION}` : path, text),
|
|
860
|
-
});
|
|
626
|
+
} catch {
|
|
627
|
+
found = undefined;
|
|
861
628
|
}
|
|
862
|
-
|
|
863
|
-
|
|
629
|
+
read.set(path, found);
|
|
630
|
+
return found;
|
|
631
|
+
};
|
|
632
|
+
return (dir, files) =>
|
|
633
|
+
files.flatMap((glob) =>
|
|
634
|
+
configurationFiles(host, dir, glob).flatMap((name) => readOne(dir, name) ?? []),
|
|
635
|
+
);
|
|
864
636
|
}
|
|
865
637
|
|
|
866
638
|
/**
|
|
@@ -869,8 +641,7 @@ function readingFiles(
|
|
|
869
641
|
* declared dependency the row's package name stands for with some value.
|
|
870
642
|
*/
|
|
871
643
|
function shortNameUses(
|
|
872
|
-
|
|
873
|
-
parse: ParseSource,
|
|
644
|
+
read: ReadFiles,
|
|
874
645
|
dir: string,
|
|
875
646
|
manifest: unknown,
|
|
876
647
|
readings: readonly ShortNameReading[],
|
|
@@ -884,8 +655,8 @@ function shortNameUses(
|
|
|
884
655
|
const used = new Set<string>();
|
|
885
656
|
for (const reading of readings) {
|
|
886
657
|
const [before = "", after = ""] = reading.package.split("{}");
|
|
887
|
-
for (const {
|
|
888
|
-
const read = readProperty(
|
|
658
|
+
for (const { module } of read(dir, reading.files)) {
|
|
659
|
+
const read = readProperty(module, reading.property, reading.call, true);
|
|
889
660
|
for (const value of read.values) {
|
|
890
661
|
used.add(`${before}${value}${after}`);
|
|
891
662
|
}
|
|
@@ -913,7 +684,7 @@ function prefixedPackage(value: string, reading: PrefixedNameReading): string |
|
|
|
913
684
|
const { prefix, marker } = reading;
|
|
914
685
|
let name: string;
|
|
915
686
|
if (marker === undefined) {
|
|
916
|
-
if (value.includes(":") ||
|
|
687
|
+
if (value.includes(":") || cannotStartPackageName(value)) {
|
|
917
688
|
return undefined;
|
|
918
689
|
}
|
|
919
690
|
const parts = value.split("/");
|
|
@@ -939,8 +710,7 @@ function prefixedPackage(value: string, reading: PrefixedNameReading): string |
|
|
|
939
710
|
|
|
940
711
|
/** The declared dependencies an applied row's prefixed names use. */
|
|
941
712
|
function prefixedNameUses(
|
|
942
|
-
|
|
943
|
-
parse: ParseSource,
|
|
713
|
+
read: ReadFiles,
|
|
944
714
|
dir: string,
|
|
945
715
|
manifest: unknown,
|
|
946
716
|
readings: readonly PrefixedNameReading[],
|
|
@@ -953,8 +723,8 @@ function prefixedNameUses(
|
|
|
953
723
|
);
|
|
954
724
|
const used = new Set<string>();
|
|
955
725
|
for (const reading of readings) {
|
|
956
|
-
for (const {
|
|
957
|
-
for (const value of readProperty(
|
|
726
|
+
for (const { module } of read(dir, reading.files)) {
|
|
727
|
+
for (const value of readProperty(module, reading.property, reading.call, true).values) {
|
|
958
728
|
const named = prefixedPackage(value, reading);
|
|
959
729
|
if (named !== undefined && declared.has(named)) {
|
|
960
730
|
used.add(named);
|
|
@@ -967,8 +737,7 @@ function prefixedNameUses(
|
|
|
967
737
|
|
|
968
738
|
/** The declared dependencies an applied row's helper settings use. */
|
|
969
739
|
function helperUses(
|
|
970
|
-
|
|
971
|
-
parse: ParseSource,
|
|
740
|
+
read: ReadFiles,
|
|
972
741
|
dir: string,
|
|
973
742
|
manifest: unknown,
|
|
974
743
|
readings: readonly HelperReading[],
|
|
@@ -983,8 +752,8 @@ function helperUses(
|
|
|
983
752
|
.filter(
|
|
984
753
|
(reading) =>
|
|
985
754
|
declared.has(reading.package) &&
|
|
986
|
-
|
|
987
|
-
valuesAt(
|
|
755
|
+
read(dir, reading.files).some(({ module }) =>
|
|
756
|
+
valuesAt(readObjects(module, reading.call), reading.property.split(".")).some((held) =>
|
|
988
757
|
holdsValue(held, reading.value),
|
|
989
758
|
),
|
|
990
759
|
),
|
|
@@ -998,8 +767,7 @@ function helperUses(
|
|
|
998
767
|
* by `:` and a name. A member's own name is no such string.
|
|
999
768
|
*/
|
|
1000
769
|
function packageKeyUses(
|
|
1001
|
-
|
|
1002
|
-
parse: ParseSource,
|
|
770
|
+
read: ReadFiles,
|
|
1003
771
|
dir: string,
|
|
1004
772
|
manifest: unknown,
|
|
1005
773
|
keys: readonly PackageKey[],
|
|
@@ -1009,31 +777,31 @@ function packageKeyUses(
|
|
|
1009
777
|
return isRecord(held) ? Object.keys(held) : [];
|
|
1010
778
|
});
|
|
1011
779
|
const strings: string[] = [];
|
|
1012
|
-
const
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
780
|
+
const children = (value: Value): readonly Value[] =>
|
|
781
|
+
value.kind === "object"
|
|
782
|
+
? value.members.map((member) => member.value)
|
|
783
|
+
: value.kind === "array"
|
|
784
|
+
? value.elements
|
|
785
|
+
: [];
|
|
786
|
+
const collect = (value: Value): void => {
|
|
787
|
+
if (value.kind === "text") {
|
|
788
|
+
strings.push(value.text);
|
|
1020
789
|
}
|
|
790
|
+
children(value).forEach(collect);
|
|
1021
791
|
};
|
|
1022
792
|
for (const { key, files } of keys) {
|
|
1023
|
-
const visit = (
|
|
1024
|
-
if (
|
|
1025
|
-
|
|
793
|
+
const visit = (value: Value): void => {
|
|
794
|
+
if (value.kind === "object") {
|
|
795
|
+
value.members
|
|
796
|
+
.filter((member) => member.key === key)
|
|
797
|
+
.forEach((member) => {
|
|
798
|
+
collect(member.value);
|
|
799
|
+
});
|
|
1026
800
|
}
|
|
1027
|
-
|
|
801
|
+
children(value).forEach(visit);
|
|
1028
802
|
};
|
|
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
|
-
}
|
|
803
|
+
for (const { module } of read(dir, files)) {
|
|
804
|
+
module.defaultExport.forEach(visit);
|
|
1037
805
|
}
|
|
1038
806
|
}
|
|
1039
807
|
return declared.filter((name) =>
|
|
@@ -1046,6 +814,16 @@ function packageKeyUses(
|
|
|
1046
814
|
);
|
|
1047
815
|
}
|
|
1048
816
|
|
|
817
|
+
/** Every simple command of a manifest's `scripts`, as the command reader reads it in the manifest's directory. */
|
|
818
|
+
function scriptCommands(manifest: unknown, dir: string): readonly ScriptCommand[] {
|
|
819
|
+
const scripts = isRecord(manifest) ? manifest["scripts"] : undefined;
|
|
820
|
+
return isRecord(scripts)
|
|
821
|
+
? Object.values(scripts).flatMap((text) =>
|
|
822
|
+
typeof text === "string" ? readCommands(text, dir).commands : [],
|
|
823
|
+
)
|
|
824
|
+
: [];
|
|
825
|
+
}
|
|
826
|
+
|
|
1049
827
|
/**
|
|
1050
828
|
* What the rows decide over the manifests the analysis reads. A row `disabled` names
|
|
1051
829
|
* is skipped before anything is read for it.
|
|
@@ -1063,9 +841,30 @@ export function readConventions(
|
|
|
1063
841
|
const failures: ConventionFailure[] = [];
|
|
1064
842
|
const uses = new Set<string>();
|
|
1065
843
|
const selected = new Map<string, { starts: Set<number>; texts: string[] }>();
|
|
1066
|
-
const testFiles = new
|
|
844
|
+
const testFiles = new Map<string, string>();
|
|
845
|
+
const testGlobs: TestGlob[] = [];
|
|
846
|
+
const read = readFilesOnce(host, parse);
|
|
1067
847
|
for (const manifest of manifests) {
|
|
1068
848
|
const declared = readJSON(host, joinPath(manifest.dir, MANIFEST));
|
|
849
|
+
let input: RunnerInput | undefined;
|
|
850
|
+
const runnerInput = (): RunnerInput => {
|
|
851
|
+
input ??= {
|
|
852
|
+
host,
|
|
853
|
+
dir: manifest.dir,
|
|
854
|
+
manifest: declared,
|
|
855
|
+
configuration: (dir, names) => {
|
|
856
|
+
for (const name of names) {
|
|
857
|
+
const [found] = read(dir, [name]);
|
|
858
|
+
if (found !== undefined) {
|
|
859
|
+
return { dir, module: found.module };
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return undefined;
|
|
863
|
+
},
|
|
864
|
+
scripts: scriptCommands(declared, manifest.dir),
|
|
865
|
+
};
|
|
866
|
+
return input;
|
|
867
|
+
};
|
|
1069
868
|
const versions = new Map<string, string | undefined>();
|
|
1070
869
|
const missing = new Set<string>();
|
|
1071
870
|
for (const row of rows) {
|
|
@@ -1102,7 +901,7 @@ export function readConventions(
|
|
|
1102
901
|
const resolved = new Map<string, readonly (string | undefined)[]>();
|
|
1103
902
|
let failed: string | undefined;
|
|
1104
903
|
for (const move of row.moves) {
|
|
1105
|
-
const result = resolveMove(
|
|
904
|
+
const result = resolveMove(read, manifest.dir, row, move, resolved);
|
|
1106
905
|
if ("failure" in result) {
|
|
1107
906
|
failed = result.failure;
|
|
1108
907
|
break;
|
|
@@ -1126,25 +925,27 @@ export function readConventions(
|
|
|
1126
925
|
version,
|
|
1127
926
|
manifest: manifest.path,
|
|
1128
927
|
});
|
|
1129
|
-
for (const name of shortNameUses(
|
|
928
|
+
for (const name of shortNameUses(read, manifest.dir, declared, row.shortNames ?? [])) {
|
|
1130
929
|
uses.add(name);
|
|
1131
930
|
}
|
|
1132
931
|
const prefixed = row.prefixedNames ?? [];
|
|
1133
|
-
for (const name of prefixedNameUses(
|
|
932
|
+
for (const name of prefixedNameUses(read, manifest.dir, declared, prefixed)) {
|
|
1134
933
|
uses.add(name);
|
|
1135
934
|
}
|
|
1136
935
|
const keys = row.packageKeys ?? [];
|
|
1137
|
-
for (const name of packageKeyUses(
|
|
936
|
+
for (const name of packageKeyUses(read, manifest.dir, declared, keys)) {
|
|
1138
937
|
uses.add(name);
|
|
1139
938
|
}
|
|
1140
939
|
for (const name of manifestKeyUses(declared, row.manifestKeys ?? [])) {
|
|
1141
940
|
uses.add(name);
|
|
1142
941
|
}
|
|
1143
|
-
for (const name of helperUses(
|
|
942
|
+
for (const name of helperUses(read, manifest.dir, declared, row.helpers ?? [])) {
|
|
1144
943
|
uses.add(name);
|
|
1145
944
|
}
|
|
1146
|
-
selectedStrings(
|
|
1147
|
-
testFilesNamed(
|
|
945
|
+
selectedStrings(read, manifest.dir, row.selections ?? [], selected);
|
|
946
|
+
testFilesNamed(read, manifest.dir, row.testFiles ?? [], testFiles, selected);
|
|
947
|
+
testDirectoryGlobs(read, manifest.dir, row.testDirectories ?? [], testGlobs);
|
|
948
|
+
testGlobs.push(...(row.testRunner?.(runnerInput()) ?? []));
|
|
1148
949
|
const excludes = [
|
|
1149
950
|
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|
|
1150
951
|
].map(globExpression);
|
|
@@ -1164,6 +965,7 @@ export function readConventions(
|
|
|
1164
965
|
});
|
|
1165
966
|
}
|
|
1166
967
|
}
|
|
968
|
+
testGlobs.push(...nodeTestGlobs(runnerInput().scripts));
|
|
1167
969
|
}
|
|
1168
970
|
const unique = [...new Map(applied.map((one) => [JSON.stringify(one), one])).values()];
|
|
1169
971
|
unique.sort(
|
|
@@ -1180,9 +982,33 @@ export function readConventions(
|
|
|
1180
982
|
uses: [...uses].sort(bytewise),
|
|
1181
983
|
selected,
|
|
1182
984
|
testFiles,
|
|
985
|
+
testGlobs,
|
|
1183
986
|
};
|
|
1184
987
|
}
|
|
1185
988
|
|
|
989
|
+
/**
|
|
990
|
+
* The files, absolute, of one project an applied row names as test files, each with the
|
|
991
|
+
* string or the pattern that names it.
|
|
992
|
+
*/
|
|
993
|
+
export function conventionTestFiles(
|
|
994
|
+
conventions: Conventions,
|
|
995
|
+
files: SourceFiles,
|
|
996
|
+
): ReadonlyMap<string, string> {
|
|
997
|
+
const found = new Map(conventions.testFiles);
|
|
998
|
+
for (const { dir, pattern, matches } of conventions.testGlobs) {
|
|
999
|
+
for (const file of files.byName.values()) {
|
|
1000
|
+
if (
|
|
1001
|
+
relativePath(dir, file.fileName) !== undefined &&
|
|
1002
|
+
matches(file.fileName) &&
|
|
1003
|
+
!found.has(file.fileName)
|
|
1004
|
+
) {
|
|
1005
|
+
found.set(file.fileName, pattern);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
return found;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1186
1012
|
/**
|
|
1187
1013
|
* Every own file of one project below a directory an applied row names as generated, by
|
|
1188
1014
|
* its path below the target root, with the name of the first such row.
|