@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10
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/README.md +1 -1
- package/dist/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +24 -5
- package/dist/src/analysis.js +245 -309
- package/dist/src/augmentations.js +124 -18
- package/dist/src/automatic-types.js +170 -0
- package/dist/src/build-scripts.js +425 -0
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +9 -9
- package/dist/src/config.js +6 -81
- package/dist/src/configuration-files.js +78 -188
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/consumers.js +3 -30
- package/dist/src/contextual-uses.js +56 -9
- package/dist/src/contract/config.generated.js +558 -0
- package/dist/src/contract/contract.generated.js +1244 -0
- package/dist/src/contract/report.generated.js +862 -0
- package/dist/src/convention-rows.js +146 -1
- package/dist/src/conventions.js +136 -70
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +280 -62
- package/dist/src/edges.js +38 -122
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +9 -18
- package/dist/src/entry-points.js +106 -21
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +1 -63
- package/dist/src/exempt.js +21 -40
- package/dist/src/failure.js +36 -0
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +225 -63
- package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
- package/dist/src/findings/emitters.js +6 -6
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +53 -19
- package/dist/src/findings/non-code-artifacts.js +71 -39
- package/dist/src/findings/reads-and-writes.js +16 -13
- package/dist/src/findings/self-check.js +10 -12
- package/dist/src/findings/unused-declarations.js +57 -28
- package/dist/src/findings/visibility-narrowing.js +40 -18
- package/dist/src/findings-pass.js +8 -15
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +52 -0
- package/dist/src/fs/manifest.js +72 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/generated-file.js +18 -4
- package/dist/src/git-ignore.js +99 -0
- package/dist/src/graph.js +9 -4
- package/dist/src/html-entries.js +6 -33
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/in-operands.js +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/injection-container.js +1 -1
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +341 -69
- package/dist/src/inventory.js +54 -29
- package/dist/src/javascript-file.js +33 -0
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +3 -366
- package/dist/src/load/classify.js +270 -0
- package/dist/src/load/diagnostics.js +80 -0
- package/dist/src/load/discover.js +277 -0
- package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
- package/dist/src/load/expected-module.js +136 -0
- package/dist/src/load/load.js +27 -0
- package/dist/src/load/module-lookup.js +37 -0
- package/dist/src/load/plan.js +313 -0
- package/dist/src/load/specifiers.js +115 -0
- package/dist/src/load/synthesize.js +140 -0
- package/dist/src/load/test-globals.js +207 -0
- package/dist/src/load/test-joins.js +82 -0
- package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
- package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
- package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
- package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
- package/dist/src/{workspace.js → load/workspace.js} +62 -124
- package/dist/src/manifest.js +49 -54
- package/dist/src/matrix.js +94 -13
- package/dist/src/mdx-entries.js +174 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/overrides.js +41 -10
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +26 -15
- package/dist/src/position.js +2 -1
- package/dist/src/print.js +11 -78
- package/dist/src/product.js +13 -0
- package/dist/src/query.js +1 -0
- package/dist/src/ref.js +11 -0
- package/dist/src/references.js +52 -31
- package/dist/src/report-schema.js +51 -0
- package/dist/src/report.js +59 -41
- package/dist/src/reporters/annotations.js +15 -8
- package/dist/src/reporters/sarif.js +16 -10
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +245 -727
- package/dist/src/roots.js +374 -116
- package/dist/src/run.js +66 -77
- package/dist/src/schema.js +320 -342
- package/dist/src/scope.js +38 -115
- package/dist/src/serialization-contract.js +649 -29
- package/dist/src/session.js +41 -29
- package/dist/src/source-files.js +40 -15
- package/dist/src/suppress-file.js +34 -29
- package/dist/src/suppress-inline.js +21 -4
- package/dist/src/suppress.js +40 -56
- package/dist/src/sweep.js +32 -17
- package/dist/src/symbol-keys.js +105 -6
- package/dist/src/template-field.js +9 -19
- package/dist/src/test-support.js +119 -18
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/named-files.js +54 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/text/yaml.js +393 -0
- package/dist/src/type-errors.js +27 -221
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/unseen-loads.js +100 -0
- package/dist/src/unwrap.js +23 -0
- package/dist/src/usage.js +10 -0
- package/dist/src/verbs/analyze.js +181 -125
- package/dist/src/verbs/describe.js +9 -16
- package/dist/src/verbs/explain.js +14 -9
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +6 -4
- package/dist/src/verbs/print-retained.js +4 -2
- package/dist/src/verbs/print-roots.js +4 -2
- package/dist/src/verbs/verb.js +1 -5
- package/dist/src/verbs/version.js +14 -3
- package/dist/src/workflow-steps.js +128 -33
- package/jsr.json +1 -1
- package/package.json +5 -4
- package/src/analysis.ts +333 -457
- package/src/augmentations.ts +152 -23
- package/src/automatic-types.ts +228 -0
- package/src/build-scripts.ts +543 -0
- package/src/component-layer.ts +4 -2
- package/src/components.ts +9 -10
- package/src/config.ts +31 -299
- package/src/configuration-files.ts +100 -191
- package/src/configured-declarations.ts +18 -23
- package/src/conformance-record.ts +2 -2
- package/src/consumers.ts +6 -57
- package/src/contextual-uses.ts +64 -12
- package/src/contract/config.generated.ts +818 -0
- package/src/contract/contract.generated.ts +1308 -0
- package/src/contract/report.generated.ts +1237 -0
- package/src/convention-rows.ts +186 -1
- package/src/conventions.ts +166 -90
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +356 -78
- package/src/edges.ts +41 -140
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +11 -20
- package/src/entry-points.ts +124 -28
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +1 -75
- package/src/exempt.ts +36 -50
- package/src/failure.ts +63 -0
- package/src/file-facts.ts +9 -24
- package/src/finding.ts +24 -9
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +297 -77
- package/src/findings/dependencies-and-module-machinery.ts +90 -31
- package/src/findings/emitter.ts +8 -9
- package/src/findings/emitters.ts +8 -8
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +64 -20
- package/src/findings/non-code-artifacts.ts +88 -50
- package/src/findings/reads-and-writes.ts +20 -16
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +65 -28
- package/src/findings/visibility-narrowing.ts +51 -19
- package/src/findings-pass.ts +10 -19
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +70 -0
- package/src/fs/manifest.ts +92 -0
- package/src/fs/tree.ts +110 -0
- package/src/generated-file.ts +23 -4
- package/src/git-ignore.ts +122 -0
- package/src/graph.ts +11 -3
- package/src/host.ts +17 -14
- package/src/html-entries.ts +6 -33
- package/src/import-bindings.ts +128 -0
- package/src/in-operands.ts +2 -1
- package/src/index.ts +1 -1
- package/src/indexed-access.ts +152 -30
- package/src/injection-container.ts +1 -1
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +449 -85
- package/src/inventory.ts +73 -27
- package/src/javascript-file.ts +37 -0
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +5 -413
- package/src/load/classify.ts +397 -0
- package/src/load/diagnostics.ts +144 -0
- package/src/load/discover.ts +455 -0
- package/src/{emit-map.ts → load/emit-map.ts} +1 -1
- package/src/load/expected-module.ts +188 -0
- package/src/load/load.ts +73 -0
- package/src/load/module-lookup.ts +50 -0
- package/src/load/plan.ts +504 -0
- package/src/load/specifiers.ts +168 -0
- package/src/load/synthesize.ts +211 -0
- package/src/load/test-globals.ts +251 -0
- package/src/load/test-joins.ts +98 -0
- package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
- package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
- package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
- package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
- package/src/{workspace.ts → load/workspace.ts} +90 -134
- package/src/manifest.ts +81 -75
- package/src/matrix.ts +154 -18
- package/src/mdx-entries.ts +218 -0
- package/src/module-declarations.ts +11 -13
- package/src/outside-tests.ts +1 -1
- package/src/overrides.ts +51 -12
- package/src/partial.ts +3 -2
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +12 -91
- package/src/product.ts +18 -0
- package/src/query.ts +11 -0
- package/src/ref.ts +13 -0
- package/src/references.ts +62 -32
- package/src/report-schema.ts +68 -0
- package/src/report.ts +105 -215
- package/src/reporters/annotations.ts +22 -11
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -4
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +29 -16
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +283 -1111
- package/src/roots.ts +490 -137
- package/src/run.ts +75 -91
- package/src/schema.ts +391 -387
- package/src/scope.ts +46 -138
- package/src/serialization-contract.ts +818 -26
- package/src/session.ts +77 -54
- package/src/source-files.ts +67 -17
- package/src/suppress-file.ts +36 -34
- package/src/suppress-inline.ts +20 -3
- package/src/suppress.ts +52 -63
- package/src/sweep.ts +53 -18
- package/src/symbol-keys.ts +131 -7
- package/src/template-field.ts +15 -25
- package/src/test-support.ts +155 -18
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/named-files.inputs.json +1522 -0
- package/src/text/named-files.ts +68 -0
- package/src/text/shell.ts +161 -0
- package/src/text/yaml.ts +423 -0
- package/src/type-errors.ts +35 -273
- package/src/type-parameter-reads.ts +227 -0
- package/src/unseen-loads.ts +134 -0
- package/src/unwrap.ts +34 -0
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +245 -161
- package/src/verbs/describe.ts +14 -15
- package/src/verbs/explain.ts +15 -13
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +6 -4
- package/src/verbs/print-retained.ts +4 -2
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +2 -8
- package/src/verbs/version.ts +15 -3
- package/src/workflow-steps.ts +149 -34
- package/dist/src/discover.js +0 -347
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/dist/src/setup-failure.js +0 -23
- package/dist/src/test-joins.js +0 -101
- package/dist/src/version.js +0 -7
- package/src/discover.ts +0 -510
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
- package/src/setup-failure.ts +0 -39
- package/src/test-joins.ts +0 -120
- package/src/version.ts +0 -7
package/dist/src/sweep.js
CHANGED
|
@@ -70,22 +70,34 @@ export function sweep(graph, input) {
|
|
|
70
70
|
});
|
|
71
71
|
return { liveUnder: live, candidates };
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* The directory a test runner reads a manual mock from, whose exports it puts in place of
|
|
75
|
+
* the module's: https://jestjs.io/docs/manual-mocks
|
|
76
|
+
*/
|
|
77
|
+
const MANUAL_MOCKS = "__mocks__";
|
|
78
|
+
/** Whether a test runner calls or reads one export of a test file: a function, or any export of a manual mock. */
|
|
79
|
+
export function runnerCalls(symbol) {
|
|
80
|
+
return symbol.kind === "function" || symbol.position.path.split("/").includes(MANUAL_MOCKS);
|
|
81
|
+
}
|
|
73
82
|
/**
|
|
74
83
|
* Per declaration of the graph, whether the run that counts test references reaches it.
|
|
75
|
-
* That run's roots are the graph's own and each test file,
|
|
76
|
-
*
|
|
77
|
-
*
|
|
84
|
+
* That run's roots are the graph's own and, of each test file, its top level and every
|
|
85
|
+
* function it exports, which a test runner may call, and every export of a manual mock,
|
|
86
|
+
* which the runner reads; any other export of a test file, and test-support code, which
|
|
87
|
+
* runs where a test imports it, are judged as any file's are. `held` seeds it as a mark
|
|
88
|
+
* seeds a sweep; with `namespaces` false a file read reaches none of the file's exports.
|
|
78
89
|
*/
|
|
79
|
-
export function reachedWithTheTests(graph, held) {
|
|
90
|
+
export function reachedWithTheTests(graph, held, namespaces = true) {
|
|
80
91
|
const tests = graph.symbols.flatMap((symbol, at) => {
|
|
81
|
-
const
|
|
82
|
-
|
|
92
|
+
const exported = symbol.exported && graph.symbols[graph.parent[at] ?? OUTSIDE]?.kind === "file";
|
|
93
|
+
const called = exported && runnerCalls(symbol);
|
|
94
|
+
return graph.test[at] === true &&
|
|
95
|
+
graph.support[at] !== true &&
|
|
96
|
+
(symbol.kind === "file" || called)
|
|
83
97
|
? [{ at, kind: "test-runner" }]
|
|
84
98
|
: [];
|
|
85
99
|
});
|
|
86
|
-
return reachable({ ...graph, rooted: [...graph.rooted, ...tests] }, flagged(graph, held), {
|
|
87
|
-
production: false,
|
|
88
|
-
});
|
|
100
|
+
return reachable({ ...graph, rooted: [...graph.rooted, ...tests] }, flagged(graph, held), { production: false }, namespaces);
|
|
89
101
|
}
|
|
90
102
|
/**
|
|
91
103
|
* The references a loaded consumer makes that the mode counts. A production mode counts
|
|
@@ -112,7 +124,7 @@ function flagged(graph, ids) {
|
|
|
112
124
|
* included, while a root reaches through the references the mode counts. The held seed
|
|
113
125
|
* runs first, so what it reaches is expanded under the wider rule.
|
|
114
126
|
*/
|
|
115
|
-
function reachable(graph, held, mode) {
|
|
127
|
+
function reachable(graph, held, mode, namespaces = true) {
|
|
116
128
|
const reached = graph.symbols.map(() => false);
|
|
117
129
|
const expanded = graph.symbols.map(() => false);
|
|
118
130
|
const queue = [];
|
|
@@ -140,7 +152,7 @@ function reachable(graph, held, mode) {
|
|
|
140
152
|
if (!holding && mode.production && edge.test) {
|
|
141
153
|
continue;
|
|
142
154
|
}
|
|
143
|
-
enter(edge.to, !edge.evaluation);
|
|
155
|
+
enter(edge.to, namespaces && !edge.evaluation);
|
|
144
156
|
}
|
|
145
157
|
}
|
|
146
158
|
};
|
|
@@ -155,17 +167,18 @@ function reachable(graph, held, mode) {
|
|
|
155
167
|
}
|
|
156
168
|
// A consumer's reference reaches what it names as one of the target's own does.
|
|
157
169
|
for (const edge of callsOf(graph, mode)) {
|
|
158
|
-
enter(edge.to, !edge.evaluation);
|
|
170
|
+
enter(edge.to, namespaces && !edge.evaluation);
|
|
159
171
|
}
|
|
160
172
|
walk(false);
|
|
161
173
|
return reached;
|
|
162
174
|
}
|
|
163
175
|
/**
|
|
164
176
|
* Admits every test declaration whose set of referenced production declarations is
|
|
165
|
-
* non-empty and wholly dead, and returns which it admitted.
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
177
|
+
* non-empty and wholly dead, and returns which it admitted. An ambient declaration describes
|
|
178
|
+
* code the target does not implement, so it is no production declaration here. The set is
|
|
179
|
+
* read from every reference the test makes, which a production sweep does not count. A
|
|
180
|
+
* test-support declaration a test referencing a live target also references is a live
|
|
181
|
+
* target of every test referencing it. A marked or exempt test is never admitted.
|
|
169
182
|
*/
|
|
170
183
|
function admitTestsOfDeadCode(graph, dead, held) {
|
|
171
184
|
const testCode = (at) => graph.test[at] === true || graph.support[at] === true;
|
|
@@ -174,7 +187,9 @@ function admitTestsOfDeadCode(graph, dead, held) {
|
|
|
174
187
|
let live = 0;
|
|
175
188
|
if (testCode(at) && graph.subject[at] === true && held[at] !== true) {
|
|
176
189
|
for (const edge of graph.out[at] ?? []) {
|
|
177
|
-
if (testCode(edge.to) ||
|
|
190
|
+
if (testCode(edge.to) ||
|
|
191
|
+
graph.subject[edge.to] !== true ||
|
|
192
|
+
graph.ambient[edge.to] === true) {
|
|
178
193
|
continue;
|
|
179
194
|
}
|
|
180
195
|
targets += 1;
|
package/dist/src/symbol-keys.js
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
* The members a unique symbol keys, used through element accesses. `value[key]`, where
|
|
3
3
|
* `key` is a constant of a `unique symbol` type, reads the member of the receiver's type
|
|
4
4
|
* whose computed name is that same constant, and a store through the access writes it.
|
|
5
|
-
* No name node resolves to such a member: the key's name resolves to the constant.
|
|
5
|
+
* No name node resolves to such a member: the key's name resolves to the constant. An
|
|
6
|
+
* iteration reads the member its well-known iteration symbol keys.
|
|
6
7
|
*/
|
|
7
|
-
import { isComputedPropertyName, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral,
|
|
8
|
+
import { isComputedPropertyName, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isPropertyAccessExpression, isSpreadElement, isStringLiteral, } from "@typescript/native/ast";
|
|
9
|
+
import { parenthesesRemoved } from "./unwrap.js";
|
|
10
|
+
import { nodeKey } from "./inventory.js";
|
|
11
|
+
import { renderPosition } from "./position.js";
|
|
8
12
|
import { isAnswered } from "./query.js";
|
|
9
13
|
/** The identifier an element access's argument names its key by, where it is a name. */
|
|
10
14
|
export function keyNameOf(argument) {
|
|
11
|
-
|
|
12
|
-
while (isParenthesizedExpression(at)) {
|
|
13
|
-
at = at.expression;
|
|
14
|
-
}
|
|
15
|
+
const at = parenthesesRemoved(argument);
|
|
15
16
|
if (isIdentifier(at)) {
|
|
16
17
|
return at;
|
|
17
18
|
}
|
|
@@ -112,3 +113,101 @@ export function keyedUses(project, chains, resolved, properties, cap) {
|
|
|
112
113
|
return ids.length === 0 ? [] : [{ access: one.access, ids: [...new Set(ids)] }];
|
|
113
114
|
});
|
|
114
115
|
}
|
|
116
|
+
/** The member name `[Symbol.<name>]` spells, where a computed name spells one. */
|
|
117
|
+
function wellKnownName(name) {
|
|
118
|
+
if (name === undefined || !isComputedPropertyName(name)) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
const key = name.expression;
|
|
122
|
+
return isPropertyAccessExpression(key) &&
|
|
123
|
+
isIdentifier(key.expression) &&
|
|
124
|
+
key.expression.text === "Symbol" &&
|
|
125
|
+
isIdentifier(key.name)
|
|
126
|
+
? key.name.text
|
|
127
|
+
: undefined;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The references the iterations of one project's own files make to the iteration members
|
|
131
|
+
* of the values they iterate: a `for…of` statement and a spread element read
|
|
132
|
+
* `[Symbol.iterator]`, and a `for await…of` statement reads `[Symbol.asyncIterator]` and,
|
|
133
|
+
* which it falls back to, `[Symbol.iterator]`. Each is made by the declaration holding it.
|
|
134
|
+
*/
|
|
135
|
+
export function iterationReferences(project, held, chains, root, testFiles) {
|
|
136
|
+
const queries = project.queries;
|
|
137
|
+
const ownPaths = project.ownPaths();
|
|
138
|
+
const iterated = [];
|
|
139
|
+
for (const file of project.ownSourceFiles()) {
|
|
140
|
+
const fileId = held.declarations.get(nodeKey(file, file));
|
|
141
|
+
if (fileId === undefined) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const visit = (node, from) => {
|
|
145
|
+
const holder = held.declarations.get(nodeKey(file, node)) ?? from;
|
|
146
|
+
if (isForOfStatement(node)) {
|
|
147
|
+
const protocols = node.awaitModifier === undefined ? ["iterator"] : ["asyncIterator", "iterator"];
|
|
148
|
+
iterated.push({ file, from: holder, operand: node.expression, protocols });
|
|
149
|
+
}
|
|
150
|
+
else if (isSpreadElement(node)) {
|
|
151
|
+
iterated.push({ file, from: holder, operand: node.expression, protocols: ["iterator"] });
|
|
152
|
+
}
|
|
153
|
+
node.forEachChild((child) => {
|
|
154
|
+
visit(child, holder);
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
file.forEachChild((child) => {
|
|
158
|
+
visit(child, fileId);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (iterated.length === 0) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
const types = queries.typesAt(iterated.map((one) => one.operand));
|
|
165
|
+
const members = new Map();
|
|
166
|
+
const membersOf = (type) => {
|
|
167
|
+
const known = members.get(type.id);
|
|
168
|
+
if (known !== undefined) {
|
|
169
|
+
return known;
|
|
170
|
+
}
|
|
171
|
+
const keyed = [];
|
|
172
|
+
const properties = queries.propertiesOf(type);
|
|
173
|
+
for (const property of isAnswered(properties) ? properties : []) {
|
|
174
|
+
for (const handle of property.declarations) {
|
|
175
|
+
const node = ownPaths.has(handle.path) ? project.declarationAt(handle)?.node : undefined;
|
|
176
|
+
const name = wellKnownName(node?.name);
|
|
177
|
+
if (name !== undefined) {
|
|
178
|
+
keyed.push({ name, ids: chains.declarationsOf(property) });
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
members.set(type.id, keyed);
|
|
184
|
+
return keyed;
|
|
185
|
+
};
|
|
186
|
+
const found = [];
|
|
187
|
+
iterated.forEach((one, index) => {
|
|
188
|
+
const type = types[index];
|
|
189
|
+
if (type === undefined || !isAnswered(type)) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const parts = type.isUnionType() || type.isIntersectionType() ? queries.constituents(type) : [type];
|
|
193
|
+
const position = renderPosition(one.file, root, one.operand.getStart());
|
|
194
|
+
for (const part of isAnswered(parts) ? parts : []) {
|
|
195
|
+
for (const member of membersOf(part)) {
|
|
196
|
+
if (!one.protocols.includes(member.name)) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
for (const to of member.ids) {
|
|
200
|
+
found.push({
|
|
201
|
+
from: one.from,
|
|
202
|
+
to,
|
|
203
|
+
position,
|
|
204
|
+
use: "read",
|
|
205
|
+
resolution: "keyed",
|
|
206
|
+
test: testFiles.has(position.path),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
return found;
|
|
213
|
+
}
|
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
* type, because nothing ties the name to a type.
|
|
13
13
|
*/
|
|
14
14
|
import { readComponent } from "./component-files.js";
|
|
15
|
-
import { ConfigError } from "./config.js";
|
|
16
15
|
import { memberNames } from "./member-names.js";
|
|
17
16
|
import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.js";
|
|
18
17
|
import { isComponentFile } from "./position.js";
|
|
18
|
+
import { bytewise } from "./text/compare.js";
|
|
19
|
+
import { filesBelow } from "./fs/tree.js";
|
|
20
|
+
import { UsageError } from "./usage.js";
|
|
19
21
|
/** The kinds of declaration an action can name. */
|
|
20
22
|
const READ_BY_A_TEMPLATE = new Set([
|
|
21
23
|
"method",
|
|
@@ -30,13 +32,6 @@ const READ_BY_A_TEMPLATE = new Set([
|
|
|
30
32
|
const READ_BY_MARKUP = new Set([...READ_BY_A_TEMPLATE, "enum-member"]);
|
|
31
33
|
/** One identifier, as the grammar of the analyzed language spells one. */
|
|
32
34
|
const IDENTIFIER = /[\p{ID_Start}$_][\p{ID_Continue}$\u200C\u200D]*/uy;
|
|
33
|
-
/** Two strings ordered bytewise. */
|
|
34
|
-
function compare(a, b) {
|
|
35
|
-
if (a === b) {
|
|
36
|
-
return 0;
|
|
37
|
-
}
|
|
38
|
-
return a < b ? -1 : 1;
|
|
39
|
-
}
|
|
40
35
|
/**
|
|
41
36
|
* Every template under the configured directories, read once for the run, in path
|
|
42
37
|
* order and each file once. A directory is named relative to the target root, and
|
|
@@ -48,34 +43,29 @@ function compare(a, b) {
|
|
|
48
43
|
export function readTemplates(host, targetRoot, analysis) {
|
|
49
44
|
const files = new Map();
|
|
50
45
|
const read = (dir) => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const path = joinPath(dir, entry.name);
|
|
54
|
-
if (entry.directory) {
|
|
55
|
-
read(path);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
46
|
+
for (const below of filesBelow(host, joinPath(targetRoot, dir), () => true)) {
|
|
47
|
+
const path = joinPath(dir, below);
|
|
58
48
|
const absolute = joinPath(targetRoot, path);
|
|
59
49
|
if (!files.has(path) && host.kindOf(absolute) === "file") {
|
|
60
50
|
files.set(path, host.readFile(absolute));
|
|
61
51
|
}
|
|
62
52
|
}
|
|
63
53
|
};
|
|
64
|
-
for (const configured of analysis.
|
|
54
|
+
for (const configured of analysis.template_dirs) {
|
|
65
55
|
const dir = normalizePath(configured);
|
|
66
56
|
if (isAbsolutePath(dir) ||
|
|
67
57
|
dir === ".." ||
|
|
68
58
|
dir.startsWith("../") ||
|
|
69
59
|
host.kindOf(joinPath(targetRoot, dir)) !== "directory") {
|
|
70
|
-
throw new
|
|
60
|
+
throw new UsageError(`analysis.template_dirs: ${JSON.stringify(configured)} is not a directory below the target root`);
|
|
71
61
|
}
|
|
72
62
|
read(dir);
|
|
73
63
|
}
|
|
74
64
|
return {
|
|
75
|
-
delimiters: analysis.
|
|
65
|
+
delimiters: analysis.template_delimiters,
|
|
76
66
|
files: [...files]
|
|
77
67
|
.map(([path, text]) => ({ path, text }))
|
|
78
|
-
.sort((a, b) =>
|
|
68
|
+
.sort((a, b) => bytewise(a.path, b.path)),
|
|
79
69
|
};
|
|
80
70
|
}
|
|
81
71
|
/** The end of the quoted string that opens at `from`, past its closing quote. */
|
package/dist/src/test-support.js
CHANGED
|
@@ -1,19 +1,113 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Test-support code: the files only tests reach
|
|
3
|
-
*
|
|
4
|
-
* included, is written in a test file or in
|
|
5
|
-
* tests: what it references is referenced by
|
|
2
|
+
* Test-support code: the files only tests reach that carry test evidence of their own. A
|
|
3
|
+
* test reaches each one through a chain of references, and every reference to a
|
|
4
|
+
* declaration of one, an import for its effects included, is written in a test file or in
|
|
5
|
+
* another such file, so it is judged with the tests: what it references is referenced by
|
|
6
|
+
* test code.
|
|
6
7
|
*/
|
|
8
|
+
import { CONVENTION_ROWS, TEST_LIBRARIES, TEST_LIBRARY_SCOPES } from "./convention-rows.js";
|
|
9
|
+
import { packageOfSpecifier } from "./dependencies.js";
|
|
7
10
|
/** The root kind that never takes a file out of test-support code: the skip only withholds. */
|
|
8
11
|
const SKIP_ROOT = "type-error";
|
|
12
|
+
/**
|
|
13
|
+
* The test runners and test libraries an import of which is test evidence: each runner a
|
|
14
|
+
* convention row reads test files for, and the convention table's test libraries.
|
|
15
|
+
*/
|
|
16
|
+
const TEST_PACKAGES = new Set([
|
|
17
|
+
...CONVENTION_ROWS.filter((row) => row.testFiles !== undefined).map((row) => row.package),
|
|
18
|
+
...TEST_LIBRARIES,
|
|
19
|
+
]);
|
|
20
|
+
/**
|
|
21
|
+
* The Node.js built-in modules a bare specifier names, which are no package. The set is
|
|
22
|
+
* closed: Node.js adds a new built-in under the `node:` scheme alone
|
|
23
|
+
* (https://nodejs.org/api/modules.html#built-in-modules-with-mandatory-node-prefix).
|
|
24
|
+
*/
|
|
25
|
+
const NODE_BUILTINS = new Set([
|
|
26
|
+
"assert",
|
|
27
|
+
"async_hooks",
|
|
28
|
+
"buffer",
|
|
29
|
+
"child_process",
|
|
30
|
+
"cluster",
|
|
31
|
+
"console",
|
|
32
|
+
"constants",
|
|
33
|
+
"crypto",
|
|
34
|
+
"dgram",
|
|
35
|
+
"diagnostics_channel",
|
|
36
|
+
"dns",
|
|
37
|
+
"domain",
|
|
38
|
+
"events",
|
|
39
|
+
"fs",
|
|
40
|
+
"http",
|
|
41
|
+
"http2",
|
|
42
|
+
"https",
|
|
43
|
+
"inspector",
|
|
44
|
+
"module",
|
|
45
|
+
"net",
|
|
46
|
+
"os",
|
|
47
|
+
"path",
|
|
48
|
+
"perf_hooks",
|
|
49
|
+
"process",
|
|
50
|
+
"punycode",
|
|
51
|
+
"querystring",
|
|
52
|
+
"readline",
|
|
53
|
+
"repl",
|
|
54
|
+
"stream",
|
|
55
|
+
"string_decoder",
|
|
56
|
+
"sys",
|
|
57
|
+
"timers",
|
|
58
|
+
"tls",
|
|
59
|
+
"trace_events",
|
|
60
|
+
"tty",
|
|
61
|
+
"url",
|
|
62
|
+
"util",
|
|
63
|
+
"v8",
|
|
64
|
+
"vm",
|
|
65
|
+
"wasi",
|
|
66
|
+
"worker_threads",
|
|
67
|
+
"zlib",
|
|
68
|
+
]);
|
|
69
|
+
/** Whether a specifier names a test runner or a test library. */
|
|
70
|
+
function namesTestPackage(specifier) {
|
|
71
|
+
const name = specifier.startsWith("node:") ? specifier : packageOfSpecifier(specifier);
|
|
72
|
+
return (name !== undefined &&
|
|
73
|
+
(TEST_PACKAGES.has(name) || TEST_LIBRARY_SCOPES.some((scope) => name.startsWith(scope))));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Whether a file carries test evidence: an import of a test runner or a test library, or
|
|
77
|
+
* package imports, Node built-ins aside, that are all devDependencies no file imports
|
|
78
|
+
* outside the test files and `onlyTests`, the files only tests reach.
|
|
79
|
+
*/
|
|
80
|
+
function evidenceReader(tests, evidence) {
|
|
81
|
+
const importers = new Map();
|
|
82
|
+
for (const [path, specifiers] of evidence.imports) {
|
|
83
|
+
for (const specifier of specifiers) {
|
|
84
|
+
const name = packageOfSpecifier(specifier);
|
|
85
|
+
if (name !== undefined) {
|
|
86
|
+
importers.set(name, [...(importers.get(name) ?? []), path]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return (onlyTests, path) => {
|
|
91
|
+
const specifiers = evidence.imports.get(path) ?? [];
|
|
92
|
+
if (specifiers.some(namesTestPackage)) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
const names = specifiers.flatMap((one) => packageOfSpecifier(one) ?? []);
|
|
96
|
+
const packages = names.filter((name) => !NODE_BUILTINS.has(name));
|
|
97
|
+
return (packages.length > 0 &&
|
|
98
|
+
packages.every((name) => evidence.devDependency(path, name) &&
|
|
99
|
+
(importers.get(name) ?? []).every((one) => tests.has(one) || onlyTests.has(one))));
|
|
100
|
+
};
|
|
101
|
+
}
|
|
9
102
|
/**
|
|
10
103
|
* The files of one project that are test-support code, below the target root: the
|
|
11
104
|
* largest set of files a test file reaches, none of them a test file or holding a root,
|
|
12
|
-
* whose every user outside the set is a test
|
|
13
|
-
*
|
|
14
|
-
*
|
|
105
|
+
* each carrying test evidence of its own, whose every user outside the set is a test
|
|
106
|
+
* file; and the files only tests reach that are not among them. A root has a caller
|
|
107
|
+
* outside the reference graph: a runtime, a tool, a configuration, or a consumer of a
|
|
108
|
+
* library's published API. `testFiles` are the files the test-file rules classified.
|
|
15
109
|
*/
|
|
16
|
-
export function testSupportFiles(symbols, references, roots, testFiles) {
|
|
110
|
+
export function testSupportFiles(symbols, references, roots, testFiles, evidence) {
|
|
17
111
|
const fileOf = new Map(symbols.map((symbol) => [symbol.id, symbol.position.path]));
|
|
18
112
|
const users = new Map();
|
|
19
113
|
const uses = new Map();
|
|
@@ -47,18 +141,25 @@ export function testSupportFiles(symbols, references, roots, testFiles) {
|
|
|
47
141
|
}
|
|
48
142
|
}
|
|
49
143
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
144
|
+
const withoutOutsideUsers = (files) => {
|
|
145
|
+
for (let changed = true; changed;) {
|
|
146
|
+
changed = false;
|
|
147
|
+
for (const path of files) {
|
|
148
|
+
const from = users.get(path) ?? new Set();
|
|
149
|
+
if (![...from].every((one) => tests.has(one) || files.has(one))) {
|
|
150
|
+
files.delete(path);
|
|
151
|
+
changed = true;
|
|
152
|
+
}
|
|
58
153
|
}
|
|
59
154
|
}
|
|
60
|
-
|
|
61
|
-
|
|
155
|
+
return files;
|
|
156
|
+
};
|
|
157
|
+
// Evidence reads the files only tests reach, evidence or none, so it is fixed before the
|
|
158
|
+
// files without evidence leave the set.
|
|
159
|
+
const onlyTests = withoutOutsideUsers(new Set([...reached].filter((path) => !rooted.has(path))));
|
|
160
|
+
const carries = evidenceReader(tests, evidence);
|
|
161
|
+
const support = withoutOutsideUsers(new Set([...onlyTests].filter((path) => carries(onlyTests, path))));
|
|
162
|
+
return { support, testUsed: new Set([...onlyTests].filter((path) => !support.has(path))) };
|
|
62
163
|
}
|
|
63
164
|
/** The references with every one a test-support file makes counted as a test reference. */
|
|
64
165
|
export function supportReferences(references, support) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two strings in the order of their UTF-8 bytes, the order every set of a run is read in.
|
|
3
|
+
* That is code point order, which UTF-16 code unit order (`<` on strings) breaks above
|
|
4
|
+
* U+FFFF: a surrogate unit is below U+E000 while the code point it encodes is above U+FFFF.
|
|
5
|
+
*/
|
|
6
|
+
export function bytewise(a, b) {
|
|
7
|
+
if (a === b) {
|
|
8
|
+
return 0;
|
|
9
|
+
}
|
|
10
|
+
const length = Math.min(a.length, b.length);
|
|
11
|
+
for (let at = 0; at < length; at += 1) {
|
|
12
|
+
const x = a.charCodeAt(at);
|
|
13
|
+
const y = b.charCodeAt(at);
|
|
14
|
+
if (x !== y) {
|
|
15
|
+
return ranked(x) < ranked(y) ? -1 : 1;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return a.length < b.length ? -1 : 1;
|
|
19
|
+
}
|
|
20
|
+
/** One UTF-16 unit moved so that a surrogate ranks above every unit of U+E000 to U+FFFF. */
|
|
21
|
+
function ranked(unit) {
|
|
22
|
+
if (unit >= 0xe000) {
|
|
23
|
+
return unit - 0x800;
|
|
24
|
+
}
|
|
25
|
+
return unit >= 0xd800 ? unit + 0x2000 : unit;
|
|
26
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glob patterns over paths in two forms. The configured form (the configuration, convention
|
|
3
|
+
* rows, bundler entries, workspace member patterns) has `*` and `?` within a segment, `**`
|
|
4
|
+
* across segments and brace lists. A `.gitignore` line has git's form: bracket expressions
|
|
5
|
+
* and backslash escapes, no brace lists (https://git-scm.com/docs/gitignore#_pattern_format).
|
|
6
|
+
* A configured root is not a glob: it names a symbol reference, and is read where it is used.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* One character of a pattern as the expression syntax reads it literally. The set escaped
|
|
10
|
+
* is closed: under the unicode flag an escape of any other character is refused outright.
|
|
11
|
+
*/
|
|
12
|
+
function quoted(character) {
|
|
13
|
+
return /[.*+?^${}()|[\]\\/]/u.test(character) ? `\\${character}` : character;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The ASCII ranges each POSIX class a bracket expression may name holds, as git's own
|
|
17
|
+
* character table reads them, each range written as its first and last character.
|
|
18
|
+
*/
|
|
19
|
+
const CLASSES = new Map([
|
|
20
|
+
["alnum", "09AZaz"],
|
|
21
|
+
["alpha", "AZaz"],
|
|
22
|
+
["blank", "\t\t "],
|
|
23
|
+
["cntrl", "\0\x1f\x7f\x7f"],
|
|
24
|
+
["digit", "09"],
|
|
25
|
+
["graph", "!~"],
|
|
26
|
+
["lower", "az"],
|
|
27
|
+
["print", " ~"],
|
|
28
|
+
["punct", "!/:@[`{~"],
|
|
29
|
+
["space", "\t\n\r\r "],
|
|
30
|
+
["upper", "AZ"],
|
|
31
|
+
["xdigit", "09AFaf"],
|
|
32
|
+
]);
|
|
33
|
+
/** What a pattern git cannot read matches: nothing, as git aborts the whole match. */
|
|
34
|
+
const NOTHING = "(?!)";
|
|
35
|
+
/** One code point as a member of an expression class. */
|
|
36
|
+
function member(point) {
|
|
37
|
+
return `\\u{${point.toString(16)}}`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The class a bracket expression opening at `open` denotes, and the index past its `]`,
|
|
41
|
+
* read as git's wildmatch reads it: a leading `!` or `^` negates it, a `]` first is a
|
|
42
|
+
* member, `\` escapes one character, `x-y` is a range (one whose `y` is below `x` holds
|
|
43
|
+
* `x` alone) and `[:name:]` a POSIX class. An unclosed bracket or an unknown class name
|
|
44
|
+
* matches nothing. Neither form matches the solidus.
|
|
45
|
+
*/
|
|
46
|
+
function bracket(pattern, open) {
|
|
47
|
+
let at = open + 1;
|
|
48
|
+
const negated = pattern[at] === "!" || pattern[at] === "^";
|
|
49
|
+
if (negated) {
|
|
50
|
+
at += 1;
|
|
51
|
+
}
|
|
52
|
+
const next = () => {
|
|
53
|
+
const point = pattern.codePointAt(at);
|
|
54
|
+
at += point !== undefined && point > 0xffff ? 2 : 1;
|
|
55
|
+
return point;
|
|
56
|
+
};
|
|
57
|
+
let members = "";
|
|
58
|
+
let low;
|
|
59
|
+
for (let first = true;; first = false) {
|
|
60
|
+
let point = next();
|
|
61
|
+
if (point === undefined) {
|
|
62
|
+
return { source: NOTHING, end: pattern.length };
|
|
63
|
+
}
|
|
64
|
+
if (point === 0x5d && !first) {
|
|
65
|
+
return { source: negated ? `[^/${members}]` : `(?!/)[${members}]`, end: at };
|
|
66
|
+
}
|
|
67
|
+
if (point === 0x2d && low !== undefined && at < pattern.length && pattern[at] !== "]") {
|
|
68
|
+
let high = next();
|
|
69
|
+
if (high === 0x5c) {
|
|
70
|
+
high = next();
|
|
71
|
+
}
|
|
72
|
+
if (high === undefined) {
|
|
73
|
+
return { source: NOTHING, end: pattern.length };
|
|
74
|
+
}
|
|
75
|
+
members += high >= low ? `${member(low)}-${member(high)}` : "";
|
|
76
|
+
low = undefined;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (point === 0x5b && pattern[at] === ":") {
|
|
80
|
+
const close = pattern.indexOf("]", at + 1);
|
|
81
|
+
if (close < 0) {
|
|
82
|
+
return { source: NOTHING, end: pattern.length };
|
|
83
|
+
}
|
|
84
|
+
if (close > at + 1 && pattern[close - 1] === ":") {
|
|
85
|
+
const ranges = CLASSES.get(pattern.slice(at + 1, close - 1));
|
|
86
|
+
if (ranges === undefined) {
|
|
87
|
+
return { source: NOTHING, end: pattern.length };
|
|
88
|
+
}
|
|
89
|
+
for (let one = 0; one < ranges.length; one += 2) {
|
|
90
|
+
members += `${member(ranges.charCodeAt(one))}-${member(ranges.charCodeAt(one + 1))}`;
|
|
91
|
+
}
|
|
92
|
+
low = undefined;
|
|
93
|
+
at = close + 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (point === 0x5c) {
|
|
98
|
+
point = next();
|
|
99
|
+
if (point === undefined) {
|
|
100
|
+
return { source: NOTHING, end: pattern.length };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
members += member(point);
|
|
104
|
+
low = point;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** The expression source of one pattern with no brace list. */
|
|
108
|
+
function source(pattern, form) {
|
|
109
|
+
let text = "";
|
|
110
|
+
let at = 0;
|
|
111
|
+
while (at < pattern.length) {
|
|
112
|
+
if (pattern.startsWith("**/", at)) {
|
|
113
|
+
text += "(?:[^/]*/)*";
|
|
114
|
+
at += 3;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (pattern.startsWith("**", at)) {
|
|
118
|
+
text += ".*";
|
|
119
|
+
at += 2;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const character = pattern[at] ?? "";
|
|
123
|
+
if (form === "gitignore" && character === "[") {
|
|
124
|
+
const read = bracket(pattern, at);
|
|
125
|
+
text += read.source;
|
|
126
|
+
at = read.end;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (form === "gitignore" && character === "\\" && at + 1 < pattern.length) {
|
|
130
|
+
text += quoted(pattern[at + 1] ?? "");
|
|
131
|
+
at += 2;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
at += 1;
|
|
135
|
+
text += character === "*" ? "[^/]*" : character === "?" ? "[^/]" : quoted(character);
|
|
136
|
+
}
|
|
137
|
+
return text;
|
|
138
|
+
}
|
|
139
|
+
/** A pattern with every brace list expanded into its alternatives; a lone `{` stands for itself. */
|
|
140
|
+
export function braceExpanded(pattern) {
|
|
141
|
+
const open = pattern.indexOf("{");
|
|
142
|
+
const close = open < 0 ? -1 : pattern.indexOf("}", open);
|
|
143
|
+
if (close < 0) {
|
|
144
|
+
return [pattern];
|
|
145
|
+
}
|
|
146
|
+
const before = pattern.slice(0, open);
|
|
147
|
+
const after = pattern.slice(close + 1);
|
|
148
|
+
return pattern
|
|
149
|
+
.slice(open + 1, close)
|
|
150
|
+
.split(",")
|
|
151
|
+
.flatMap((member) => braceExpanded(before + member + after));
|
|
152
|
+
}
|
|
153
|
+
/** The expression one whole-path pattern of the configured form denotes. */
|
|
154
|
+
export function globExpression(pattern) {
|
|
155
|
+
const alternatives = braceExpanded(pattern).map((one) => source(one, "configured"));
|
|
156
|
+
return new RegExp(`^(?:${alternatives.join("|")})$`, "u");
|
|
157
|
+
}
|
|
158
|
+
/** The expression one `.gitignore` pattern denotes over a path below its file's directory. */
|
|
159
|
+
export function gitignoreExpression(pattern) {
|
|
160
|
+
return new RegExp(`^${source(pattern, "gitignore")}$`, "u");
|
|
161
|
+
}
|
|
162
|
+
const SEGMENTS = new Map();
|
|
163
|
+
/**
|
|
164
|
+
* Whether one directory name matches one segment of a workspace's member pattern, its brace
|
|
165
|
+
* lists already expanded. Neither `*` nor `?` matches a leading dot, as npm's workspaces read
|
|
166
|
+
* them: https://docs.npmjs.com/cli/using-npm/workspaces
|
|
167
|
+
*/
|
|
168
|
+
export function segmentMatches(segment, name) {
|
|
169
|
+
if (name.startsWith(".") && !segment.startsWith(".")) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
let expression = SEGMENTS.get(segment);
|
|
173
|
+
if (expression === undefined) {
|
|
174
|
+
expression = globExpression(segment);
|
|
175
|
+
SEGMENTS.set(segment, expression);
|
|
176
|
+
}
|
|
177
|
+
return expression.test(name);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Whether one path's segments match a member pattern's, `**` standing for any run of segments
|
|
181
|
+
* that starts with no dot.
|
|
182
|
+
*/
|
|
183
|
+
export function pathMatches(pattern, path) {
|
|
184
|
+
const [head, ...rest] = pattern;
|
|
185
|
+
if (head === undefined) {
|
|
186
|
+
return path.length === 0;
|
|
187
|
+
}
|
|
188
|
+
if (head === "**") {
|
|
189
|
+
return (pathMatches(rest, path) ||
|
|
190
|
+
(path.length > 0 && !(path[0] ?? "").startsWith(".") && pathMatches(pattern, path.slice(1))));
|
|
191
|
+
}
|
|
192
|
+
const [name, ...below] = path;
|
|
193
|
+
return name !== undefined && segmentMatches(head, name) && pathMatches(rest, below);
|
|
194
|
+
}
|