@cplieger/deadset-ts 8.0.0-dev.14 → 8.0.0-dev.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/alias-chain.js +2 -2
- package/dist/src/analysis.js +43 -38
- package/dist/src/commands/read.js +6 -7
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +4 -2
- package/dist/src/contract/config.generated.js +1 -1
- package/dist/src/contract/contract.generated.js +120 -2
- package/dist/src/dependencies.js +81 -49
- package/dist/src/entries/build-scripts.js +222 -0
- package/dist/src/{configuration-files.js → entries/configuration-files.js} +20 -58
- package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
- package/dist/src/entries/conventions.js +739 -0
- package/dist/src/{html-entries.js → entries/html-entries.js} +3 -3
- package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +2 -2
- package/dist/src/entries/run-directories.js +77 -0
- package/dist/src/entries/sources.js +144 -0
- package/dist/src/entries/static-module.js +669 -0
- package/dist/src/entries/test-runners.js +684 -0
- package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
- package/dist/src/extensions.js +37 -0
- package/dist/src/facts.js +38 -0
- package/dist/src/file-facts.js +1 -1
- package/dist/src/findings/emitters.js +5 -8
- package/dist/src/findings/self-check.js +36 -20
- package/dist/src/findings-pass.js +5 -6
- package/dist/src/framework-lifecycle.js +1 -1
- package/dist/src/fs/manifest.js +15 -20
- package/dist/src/generated-file.js +172 -29
- package/dist/src/graph.js +2 -5
- package/dist/src/index.js +1 -1
- package/dist/src/injection-container.js +1 -1
- package/dist/src/inventory/commonjs.js +507 -0
- package/dist/src/inventory/enumerate.js +167 -0
- package/dist/src/inventory/syntax.js +236 -0
- package/dist/src/inventory.js +305 -705
- package/dist/src/javascript-file.js +26 -11
- package/dist/src/load/classify.js +28 -9
- package/dist/src/load/diagnostics.js +8 -1
- package/dist/src/load/discover.js +16 -8
- package/dist/src/load/package-scope.js +56 -0
- package/dist/src/load/plan.js +2 -2
- package/dist/src/load/synthesize.js +5 -5
- package/dist/src/load/workspace-manifests.js +1 -1
- package/dist/src/matrix.js +6 -13
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/paths.js +16 -0
- package/dist/src/platform-calls.js +78 -0
- package/dist/src/platform-symbols.js +140 -0
- package/dist/src/position.js +41 -0
- package/dist/src/ref.js +11 -20
- package/dist/src/references.js +281 -50
- package/dist/src/resolve.js +1 -1
- package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +4 -4
- package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
- package/dist/src/{manifest.js → roots/manifest.js} +4 -4
- package/dist/src/roots/published-reach.js +412 -0
- package/dist/src/roots/root-set.js +489 -0
- package/dist/src/roots/unseen-loads.js +208 -0
- package/dist/src/serialization-contract.js +1 -1
- package/dist/src/session.js +3 -0
- package/dist/src/source-files.js +5 -9
- package/dist/src/suppress-inline.js +33 -81
- package/dist/src/suppress.js +30 -32
- package/dist/src/test-support.js +4 -2
- package/dist/src/verbs/analyze.js +13 -1
- package/dist/src/verbs/describe.js +5 -3
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +2 -2
- package/src/analysis.ts +77 -53
- package/src/automatic-types.ts +1 -1
- package/src/commands/read.ts +6 -8
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +5 -1
- package/src/contract/config.generated.ts +1 -1
- package/src/contract/contract.generated.ts +139 -2
- package/src/dependencies.ts +98 -50
- package/src/entries/build-scripts.ts +292 -0
- package/src/{configuration-files.ts → entries/configuration-files.ts} +23 -75
- package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
- package/src/{conventions.ts → entries/conventions.ts} +347 -522
- package/src/{html-entries.ts → entries/html-entries.ts} +5 -5
- package/src/{mdx-entries.ts → entries/mdx-entries.ts} +4 -4
- package/src/entries/run-directories.ts +108 -0
- package/src/entries/sources.ts +266 -0
- package/src/entries/static-module.ts +857 -0
- package/src/entries/test-runners.ts +868 -0
- package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
- package/src/exempt.ts +3 -2
- package/src/extensions.ts +45 -0
- package/src/facts.ts +44 -0
- package/src/file-facts.ts +2 -2
- package/src/findings/emitters.ts +14 -10
- package/src/findings/self-check.ts +41 -22
- package/src/findings/visibility-narrowing.ts +1 -1
- package/src/findings-pass.ts +5 -12
- package/src/framework-lifecycle.ts +1 -1
- package/src/fs/manifest.ts +25 -21
- package/src/generated-file.ts +240 -36
- package/src/graph.ts +3 -6
- package/src/index.ts +1 -1
- package/src/injection-container.ts +1 -1
- package/src/inventory/commonjs.ts +696 -0
- package/src/inventory/enumerate.ts +203 -0
- package/src/inventory/syntax.ts +311 -0
- package/src/inventory.ts +424 -878
- package/src/javascript-file.ts +29 -12
- package/src/load/classify.ts +49 -13
- package/src/load/diagnostics.ts +11 -1
- package/src/load/discover.ts +28 -12
- package/src/load/package-scope.ts +74 -0
- package/src/load/plan.ts +11 -2
- package/src/load/synthesize.ts +6 -6
- package/src/load/test-globals.ts +1 -1
- package/src/load/workspace-manifests.ts +1 -1
- package/src/load/workspace-run.ts +1 -1
- package/src/matrix.ts +7 -14
- package/src/outside-tests.ts +1 -1
- package/src/paths.ts +18 -0
- package/src/platform-calls.ts +119 -0
- package/src/platform-symbols.ts +205 -0
- package/src/position.ts +42 -0
- package/src/ref.ts +11 -20
- package/src/references.ts +383 -73
- package/src/report.ts +1 -1
- package/src/resolve.ts +1 -1
- package/src/{configured-declarations.ts → roots/configured-declarations.ts} +7 -7
- package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
- package/src/{manifest.ts → roots/manifest.ts} +5 -5
- package/src/roots/published-reach.ts +481 -0
- package/src/{roots.ts → roots/root-set.ts} +203 -630
- package/src/roots/unseen-loads.ts +307 -0
- package/src/serialization-contract.ts +1 -1
- package/src/session.ts +17 -1
- package/src/source-files.ts +5 -10
- package/src/suppress-inline.ts +53 -89
- package/src/suppress.ts +48 -33
- package/src/test-support.ts +8 -3
- package/src/verbs/analyze.ts +17 -4
- package/src/verbs/describe.ts +5 -2
- package/src/verbs/explain.ts +1 -1
- package/dist/src/build-scripts.js +0 -418
- package/dist/src/conventions.js +0 -907
- package/dist/src/entry-points.js +0 -513
- package/dist/src/roots.js +0 -865
- package/dist/src/unseen-loads.js +0 -100
- package/src/build-scripts.ts +0 -536
- package/src/entry-points.ts +0 -661
- package/src/unseen-loads.ts +0 -134
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The convention rows a run applies, and the files they root. A row applies to a
|
|
3
|
+
* manifest that declares its enabling package when the installed package's version is
|
|
4
|
+
* in the row's range; its globs are read against that manifest's directory, with every
|
|
5
|
+
* directory a configuration property moves read from the property's literal value.
|
|
6
|
+
* No configuration file is run.
|
|
7
|
+
*/
|
|
8
|
+
import { lineOf } from "../component-files.js";
|
|
9
|
+
import { CONVENTION_ROWS, } from "./convention-rows.js";
|
|
10
|
+
import { documentStrings } from "./configuration-files.js";
|
|
11
|
+
import { readCommands } from "../commands/read.js";
|
|
12
|
+
import { nodeTestGlobs, } from "./test-runners.js";
|
|
13
|
+
import { documentModule, membersNamed, staticModule, textsOf, valuesAt, } from "./static-module.js";
|
|
14
|
+
import { ancestors, dirnamePath, joinPath, normalizePath, relativePath, resolvePath, } from "../paths.js";
|
|
15
|
+
import { satisfies } from "../version-range.js";
|
|
16
|
+
import { bytewise } from "../text/compare.js";
|
|
17
|
+
import { isRecord } from "../json-document.js";
|
|
18
|
+
import { filesIn } from "../fs/tree.js";
|
|
19
|
+
import { DEPENDENCY_SECTIONS, INSTALLED_PACKAGES, MANIFEST, readJSON } from "../fs/manifest.js";
|
|
20
|
+
import { globExpression } from "../text/glob.js";
|
|
21
|
+
import { yamlDocument } from "../text/yaml.js";
|
|
22
|
+
import { APPENDED_EXTENSIONS, braced } from "../extensions.js";
|
|
23
|
+
/** A configuration file its tool reads as a YAML document. */
|
|
24
|
+
const YAML_NAME = /\.ya?ml$/u;
|
|
25
|
+
/** A configuration file its tool reads as a JSON document whose name does not end with `.json`. */
|
|
26
|
+
const JSON_NAME = /^\.[\w-]+rc$|\.jsonc$/u;
|
|
27
|
+
/** The sections an installed package is required by, so its absence fails the run. */
|
|
28
|
+
const REQUIRED_SECTIONS = ["dependencies", "devDependencies"];
|
|
29
|
+
function declares(manifest, name, sections = DEPENDENCY_SECTIONS) {
|
|
30
|
+
if (!isRecord(manifest)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return sections.some((section) => {
|
|
34
|
+
const held = manifest[section];
|
|
35
|
+
return isRecord(held) && Object.hasOwn(held, name);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The version the installed package's manifest names, read from the nearest
|
|
40
|
+
* `node_modules` at or above `dir` that holds it, `""` where that manifest names
|
|
41
|
+
* none, or `undefined` where no `node_modules` holds it.
|
|
42
|
+
*/
|
|
43
|
+
function installedVersion(host, dir, name) {
|
|
44
|
+
for (const at of ancestors(dir)) {
|
|
45
|
+
const path = joinPath(at, INSTALLED_PACKAGES, name, MANIFEST);
|
|
46
|
+
if (host.kindOf(path) === "file") {
|
|
47
|
+
const manifest = readJSON(host, path);
|
|
48
|
+
const version = isRecord(manifest) ? manifest["version"] : undefined;
|
|
49
|
+
return typeof version === "string" ? version : "";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
/** The objects a reading reads in one configuration file: each named call's options, or the default export. */
|
|
55
|
+
function readObjects(module, call) {
|
|
56
|
+
return call === undefined ? module.defaultExport : module.options(call.module, call.export);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Reads the values at one path of keys below one object into `values`, and answers the
|
|
60
|
+
* first place that does not write its value out: an object that is no object literal, a
|
|
61
|
+
* spread or a computed key, a value at the path's end that is no literal, or one before
|
|
62
|
+
* it that is no object literal.
|
|
63
|
+
*/
|
|
64
|
+
function readPath(object, keys, values, elements) {
|
|
65
|
+
if (object.kind !== "object") {
|
|
66
|
+
return object;
|
|
67
|
+
}
|
|
68
|
+
const [key, ...rest] = keys;
|
|
69
|
+
for (const member of object.members) {
|
|
70
|
+
if (member.key === undefined) {
|
|
71
|
+
return member;
|
|
72
|
+
}
|
|
73
|
+
if (member.key !== key) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (rest.length === 0) {
|
|
77
|
+
const texts = textsOf(member.value, elements);
|
|
78
|
+
if (texts === undefined) {
|
|
79
|
+
return member;
|
|
80
|
+
}
|
|
81
|
+
values.push(...texts);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const failed = member.value.kind === "object" ? readPath(member.value, rest, values, elements) : member;
|
|
85
|
+
if (failed !== undefined) {
|
|
86
|
+
return failed;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
/** The text values among the values at one path: each value itself, or each element of an array value. */
|
|
93
|
+
function stringsAt(objects, property) {
|
|
94
|
+
return valuesAt(objects, property.split(".")).flatMap((value) => (value.kind === "array" ? value.elements : [value]).flatMap((one) => one.kind === "text" ? [one] : []));
|
|
95
|
+
}
|
|
96
|
+
/** Whether one value holds a helper setting's value, as {@link HelperReading} states it. */
|
|
97
|
+
function holdsValue(held, value) {
|
|
98
|
+
if (value === true) {
|
|
99
|
+
return held.kind === "boolean" && held.value;
|
|
100
|
+
}
|
|
101
|
+
if (held.kind === "text") {
|
|
102
|
+
return held.text === value;
|
|
103
|
+
}
|
|
104
|
+
return (held.kind === "array" &&
|
|
105
|
+
held.elements.some((element) => {
|
|
106
|
+
const first = element.kind === "array" ? element.elements[0] : element;
|
|
107
|
+
return first?.kind === "text" && first.text === value;
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
/** The directory one group of objects' test-file strings are read against, as {@link TestFileReading} states it. */
|
|
111
|
+
function testFileRoot(objects, dir, roots) {
|
|
112
|
+
for (const property of roots) {
|
|
113
|
+
const values = valuesAt(objects, property.split("."));
|
|
114
|
+
if (values.length > 0) {
|
|
115
|
+
const texts = new Set(values.map((one) => (one.kind === "text" ? one.text : undefined)));
|
|
116
|
+
const [text] = texts;
|
|
117
|
+
return texts.size === 1 && text !== undefined ? resolvePath(dir, text) : undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return dir;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The files an applied row's test-file properties name, each string also added to
|
|
124
|
+
* `selected`, since a test file is rooted by no configuration string (conventions.11). A
|
|
125
|
+
* property's path up to its last `*` selects objects that each resolve their strings
|
|
126
|
+
* against their own roots.
|
|
127
|
+
*/
|
|
128
|
+
function testFilesNamed(read, dir, readings, into, selected) {
|
|
129
|
+
for (const reading of readings) {
|
|
130
|
+
const star = reading.property.lastIndexOf("*.");
|
|
131
|
+
const each = star < 0 ? undefined : reading.property.slice(0, star + 1).split(".");
|
|
132
|
+
const property = star < 0 ? reading.property : reading.property.slice(star + 2);
|
|
133
|
+
for (const { name, module } of read(dir, reading.files)) {
|
|
134
|
+
const path = joinPath(dir, name);
|
|
135
|
+
const held = selected.get(path) ?? { starts: new Set(), texts: [] };
|
|
136
|
+
selected.set(path, held);
|
|
137
|
+
const objects = readObjects(module, reading.call);
|
|
138
|
+
const groups = each === undefined ? [objects] : valuesAt(objects, each).map((one) => [one]);
|
|
139
|
+
for (const group of groups) {
|
|
140
|
+
const at = testFileRoot(group, dir, reading.roots);
|
|
141
|
+
if (at === undefined) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
for (const { text, at: start } of stringsAt(group, property)) {
|
|
145
|
+
if (start !== undefined) {
|
|
146
|
+
held.starts.add(start);
|
|
147
|
+
}
|
|
148
|
+
held.texts.push(text);
|
|
149
|
+
if (text !== "") {
|
|
150
|
+
into.set(resolvePath(at, text), text);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** The one value an object's last write of a key gives, `null` where it writes none, undefined where it may hide one. */
|
|
158
|
+
function lastMember(object, key) {
|
|
159
|
+
const values = membersNamed(object, key);
|
|
160
|
+
return values === undefined ? undefined : (values.at(-1) ?? null);
|
|
161
|
+
}
|
|
162
|
+
/** The test directory one object states, each member it does not write taken from `inherited`. */
|
|
163
|
+
function testDirectoryOf(object, reading, inherited) {
|
|
164
|
+
if (object.kind !== "object") {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
const directory = lastMember(object, reading.directory);
|
|
168
|
+
const match = lastMember(object, reading.match);
|
|
169
|
+
if (directory === undefined || match === undefined) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
const dirs = directory === null ? [inherited.directory] : textsOf(directory, false);
|
|
173
|
+
const patterns = match === null ? inherited.patterns : textsOf(match, true);
|
|
174
|
+
const [one] = dirs ?? [];
|
|
175
|
+
return one === undefined || patterns === undefined ? undefined : { directory: one, patterns };
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The test-file patterns an applied row's test directory readings give: each configuration
|
|
179
|
+
* object's own, or, where it lists projects, each project's, a member a project does not
|
|
180
|
+
* write taken from the configuration. An object whose text does not state them gives none.
|
|
181
|
+
*/
|
|
182
|
+
function testDirectoryGlobs(read, dir, readings, into) {
|
|
183
|
+
for (const reading of readings) {
|
|
184
|
+
const base = { directory: ".", patterns: [reading.defaultMatch] };
|
|
185
|
+
for (const { module } of read(dir, reading.files)) {
|
|
186
|
+
for (const config of module.defaultExport) {
|
|
187
|
+
const top = testDirectoryOf(config, reading, base);
|
|
188
|
+
const projects = lastMember(config, reading.projects);
|
|
189
|
+
if (top === undefined || projects === undefined) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const listed = projects === null ? [] : projects.kind === "array" ? projects.elements : [projects];
|
|
193
|
+
const each = listed.length === 0 ? [top] : listed.map((one) => testDirectoryOf(one, reading, top));
|
|
194
|
+
for (const found of each) {
|
|
195
|
+
for (const pattern of found?.patterns ?? []) {
|
|
196
|
+
const expression = anywhereBelow(pattern);
|
|
197
|
+
into.push({
|
|
198
|
+
dir: resolvePath(dir, found?.directory ?? "."),
|
|
199
|
+
pattern,
|
|
200
|
+
matches: (name) => expression.test(name.replace(/^\/+/u, "")),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The expression a test-file pattern denotes over a file's whole path, as Playwright's
|
|
210
|
+
* matcher reads it: a pattern gains a leading `**` segment unless it starts with one, and
|
|
211
|
+
* case is not compared (https://github.com/microsoft/playwright/blob/main/packages/playwright/src/util.ts,
|
|
212
|
+
* `createFileMatcher`).
|
|
213
|
+
*/
|
|
214
|
+
function anywhereBelow(pattern) {
|
|
215
|
+
const expression = globExpression(pattern.startsWith("**/") ? pattern : `**/${pattern}`);
|
|
216
|
+
return new RegExp(expression.source, "iu");
|
|
217
|
+
}
|
|
218
|
+
/** The strings an applied row's selection keys hold, added per configuration file to `into`. */
|
|
219
|
+
function selectedStrings(read, dir, readings, into) {
|
|
220
|
+
for (const reading of readings) {
|
|
221
|
+
for (const { name, module } of read(dir, reading.files)) {
|
|
222
|
+
const path = joinPath(dir, name);
|
|
223
|
+
const held = into.get(path) ?? { starts: new Set(), texts: [] };
|
|
224
|
+
for (const { text, at } of stringsAt(readObjects(module, reading.call), reading.property)) {
|
|
225
|
+
if (at !== undefined) {
|
|
226
|
+
held.starts.add(at);
|
|
227
|
+
}
|
|
228
|
+
held.texts.push(text);
|
|
229
|
+
}
|
|
230
|
+
into.set(path, held);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The dependencies one manifest declares that a string at any depth of one of its members
|
|
236
|
+
* names, alone or followed by `/` and a subpath, a member's name aside.
|
|
237
|
+
*/
|
|
238
|
+
function manifestKeyUses(manifest, keys) {
|
|
239
|
+
if (!isRecord(manifest)) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
const declared = DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
243
|
+
const held = manifest[section];
|
|
244
|
+
return isRecord(held) ? Object.keys(held) : [];
|
|
245
|
+
});
|
|
246
|
+
const strings = keys.flatMap((key) => documentStrings(manifest[key]));
|
|
247
|
+
return declared.filter((name) => strings.some((text) => text === name || text.startsWith(`${name}/`)));
|
|
248
|
+
}
|
|
249
|
+
/** The extensions an entry a manifest entry key names without one may carry. */
|
|
250
|
+
const ENTRY_EXTENSIONS = braced(APPENDED_EXTENSIONS);
|
|
251
|
+
/** The characters that make a manifest string a pattern rather than a directory's name. */
|
|
252
|
+
const PATTERN = /[*?{}[\]]/u;
|
|
253
|
+
/**
|
|
254
|
+
* The entry globs one applied row's manifest entry keys write, below the manifest's
|
|
255
|
+
* directory: a directory's every module, or a file with or without an extension. A string
|
|
256
|
+
* that leaves the directory names nothing.
|
|
257
|
+
*/
|
|
258
|
+
function manifestEntryGlobs(manifest, keys) {
|
|
259
|
+
const at = (value, path) => {
|
|
260
|
+
const [key, ...rest] = path;
|
|
261
|
+
if (key === undefined) {
|
|
262
|
+
return [value];
|
|
263
|
+
}
|
|
264
|
+
if (!isRecord(value)) {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
const next = key === "*" ? Object.values(value) : Object.hasOwn(value, key) ? [value[key]] : [];
|
|
268
|
+
return next.flatMap((one) => at(one, rest));
|
|
269
|
+
};
|
|
270
|
+
return keys.flatMap(({ key, directory }) => at(manifest, key.split(".")).flatMap((value) => documentStrings(value).flatMap((text) => {
|
|
271
|
+
const path = normalizePath(text)
|
|
272
|
+
.replace(/^(?:\.\/)+/u, "")
|
|
273
|
+
.replace(/\/+$/u, "");
|
|
274
|
+
if (path === "" || path === ".." || path.startsWith("../") || path.startsWith("/")) {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
if (!directory) {
|
|
278
|
+
return [path, `${path}.${ENTRY_EXTENSIONS}`];
|
|
279
|
+
}
|
|
280
|
+
return PATTERN.test(path) ? [] : [`${path}/**/*.${ENTRY_EXTENSIONS}`];
|
|
281
|
+
})));
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Every value one file writes at one property's path inside the objects it is read from:
|
|
285
|
+
* the options argument of every call `call` names, or the file's default export. A string
|
|
286
|
+
* literal or a template literal with no substitution is the value, and so is each element
|
|
287
|
+
* of an array literal of them where `elements`. A read object, or an object along the
|
|
288
|
+
* path, that is not written out as an object literal, or that spreads another object into
|
|
289
|
+
* itself, is not a literal, and neither is any other value at the path.
|
|
290
|
+
*/
|
|
291
|
+
export function readProperty(module, property, call, elements = false) {
|
|
292
|
+
const values = [];
|
|
293
|
+
for (const object of readObjects(module, call)) {
|
|
294
|
+
const notLiteral = readPath(object, property.split("."), values, elements);
|
|
295
|
+
if (notLiteral !== undefined) {
|
|
296
|
+
return { values, notLiteral };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return { values };
|
|
300
|
+
}
|
|
301
|
+
/** A directory below a manifest's, `.` for the directory itself, `undefined` outside it. */
|
|
302
|
+
function below(dir, path) {
|
|
303
|
+
return normalizePath(path) === normalizePath(dir) ? "." : relativePath(dir, path);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Every text a template stands for, each placeholder replaced by every directory its move
|
|
307
|
+
* resolved to. A placeholder whose move resolved outside the manifest's directory leaves
|
|
308
|
+
* the text standing for nothing.
|
|
309
|
+
*/
|
|
310
|
+
function expand(template, resolved) {
|
|
311
|
+
const match = /<([A-Za-z]+)>/u.exec(template);
|
|
312
|
+
if (match === null) {
|
|
313
|
+
return [template];
|
|
314
|
+
}
|
|
315
|
+
const [placeholder, id = ""] = match;
|
|
316
|
+
const out = [];
|
|
317
|
+
for (const value of resolved.get(id) ?? []) {
|
|
318
|
+
if (value === undefined) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const replaced = value === "." && template.startsWith(`${placeholder}/`, match.index)
|
|
322
|
+
? template.replace(`${placeholder}/`, "")
|
|
323
|
+
: template.replace(placeholder, value);
|
|
324
|
+
out.push(...expand(replaced, resolved));
|
|
325
|
+
}
|
|
326
|
+
return out;
|
|
327
|
+
}
|
|
328
|
+
/** The configuration files in one directory that one glob names, by name. */
|
|
329
|
+
function configurationFiles(host, dir, glob) {
|
|
330
|
+
const expression = globExpression(glob);
|
|
331
|
+
return filesIn(host, dir, (name) => expression.test(name));
|
|
332
|
+
}
|
|
333
|
+
function resolveMove(read, dir, row, move, resolved) {
|
|
334
|
+
const set = [];
|
|
335
|
+
for (const reading of move.readings) {
|
|
336
|
+
for (const { name, text, module } of read(dir, reading.files)) {
|
|
337
|
+
const read = readProperty(module, reading.property, reading.call);
|
|
338
|
+
if (read.notLiteral !== undefined) {
|
|
339
|
+
const { at } = read.notLiteral;
|
|
340
|
+
const where = at === undefined ? name : `${name}:${String(lineOf(text, at))}`;
|
|
341
|
+
return {
|
|
342
|
+
failure: `${where} sets ${reading.property}, which moves a directory ` +
|
|
343
|
+
`the convention row ${row.name} reads, to a value that is not a string literal: ` +
|
|
344
|
+
`write it as a literal, or name ${row.name} in ts.disabled_conventions and its ` +
|
|
345
|
+
`files in ts.entry_files`,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
set.push(...read.values);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const dirs = [];
|
|
352
|
+
for (const base of expand(move.base ?? ".", resolved)) {
|
|
353
|
+
const against = joinPath(dir, base);
|
|
354
|
+
const texts = set.length > 0 ? set : move.defaults.flatMap((one) => expand(one, resolved));
|
|
355
|
+
for (const text of texts) {
|
|
356
|
+
dirs.push(below(dir, resolvePath(against, text)));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return { dirs: [...new Set(dirs)] };
|
|
360
|
+
}
|
|
361
|
+
/** A reader that reads and parses each configuration file once for every row and reading of a run. */
|
|
362
|
+
function readFilesOnce(host, parse) {
|
|
363
|
+
const read = new Map();
|
|
364
|
+
const readOne = (dir, name) => {
|
|
365
|
+
const path = joinPath(dir, name);
|
|
366
|
+
if (read.has(path)) {
|
|
367
|
+
return read.get(path);
|
|
368
|
+
}
|
|
369
|
+
let found;
|
|
370
|
+
try {
|
|
371
|
+
const text = host.readFile(path);
|
|
372
|
+
if (YAML_NAME.test(name)) {
|
|
373
|
+
const value = yamlDocument(text);
|
|
374
|
+
found = value === undefined ? undefined : { name, text, module: documentModule(value) };
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
const file = parse(JSON_NAME.test(name) ? `${path}.json` : path, text);
|
|
378
|
+
found = { name, text, module: staticModule(file) };
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
found = undefined;
|
|
383
|
+
}
|
|
384
|
+
read.set(path, found);
|
|
385
|
+
return found;
|
|
386
|
+
};
|
|
387
|
+
return (dir, files) => files.flatMap((glob) => configurationFiles(host, dir, glob).flatMap((name) => readOne(dir, name) ?? []));
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* The dependencies one manifest declares that an applied row's short names use: the
|
|
391
|
+
* package each literal value stands for, and, for a value that is not a literal, every
|
|
392
|
+
* declared dependency the row's package name stands for with some value.
|
|
393
|
+
*/
|
|
394
|
+
function shortNameUses(read, dir, manifest, readings) {
|
|
395
|
+
const declared = new Set(DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
396
|
+
const held = isRecord(manifest) ? manifest[section] : undefined;
|
|
397
|
+
return isRecord(held) ? Object.keys(held) : [];
|
|
398
|
+
}));
|
|
399
|
+
const used = new Set();
|
|
400
|
+
for (const reading of readings) {
|
|
401
|
+
const [before = "", after = ""] = reading.package.split("{}");
|
|
402
|
+
for (const { module } of read(dir, reading.files)) {
|
|
403
|
+
const read = readProperty(module, reading.property, reading.call, true);
|
|
404
|
+
for (const value of read.values) {
|
|
405
|
+
used.add(`${before}${value}${after}`);
|
|
406
|
+
}
|
|
407
|
+
if (read.notLiteral !== undefined) {
|
|
408
|
+
for (const name of declared) {
|
|
409
|
+
if (name.length >= before.length + after.length &&
|
|
410
|
+
name.startsWith(before) &&
|
|
411
|
+
name.endsWith(after)) {
|
|
412
|
+
used.add(name);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return [...used].filter((name) => declared.has(name)).sort(bytewise);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* The package one value of a prefixed-name reading names, or none: ESLint's own naming,
|
|
422
|
+
* https://github.com/eslint/eslintrc/blob/main/lib/shared/naming.js
|
|
423
|
+
*/
|
|
424
|
+
function prefixedPackage(value, reading) {
|
|
425
|
+
const { prefix, marker } = reading;
|
|
426
|
+
let name;
|
|
427
|
+
if (marker === undefined) {
|
|
428
|
+
if (value.includes(":") || value.startsWith(".") || value.startsWith("/")) {
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
431
|
+
const parts = value.split("/");
|
|
432
|
+
name = (value.startsWith("@") ? parts.slice(0, 2) : parts.slice(0, 1)).join("/");
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
if (!value.startsWith(marker) || !value.includes("/", marker.length)) {
|
|
436
|
+
return undefined;
|
|
437
|
+
}
|
|
438
|
+
name = value.slice(marker.length, value.lastIndexOf("/"));
|
|
439
|
+
}
|
|
440
|
+
if (name === "") {
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
if (!name.startsWith("@")) {
|
|
444
|
+
return name.startsWith(`${prefix}-`) ? name : `${prefix}-${name}`;
|
|
445
|
+
}
|
|
446
|
+
const [scope, rest] = name.split("/");
|
|
447
|
+
if (rest === undefined || rest === "" || rest === prefix) {
|
|
448
|
+
return `${scope ?? name}/${prefix}`;
|
|
449
|
+
}
|
|
450
|
+
return rest.startsWith(`${prefix}-`) ? name : `${scope ?? name}/${prefix}-${rest}`;
|
|
451
|
+
}
|
|
452
|
+
/** The declared dependencies an applied row's prefixed names use. */
|
|
453
|
+
function prefixedNameUses(read, dir, manifest, readings) {
|
|
454
|
+
const declared = new Set(DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
455
|
+
const held = isRecord(manifest) ? manifest[section] : undefined;
|
|
456
|
+
return isRecord(held) ? Object.keys(held) : [];
|
|
457
|
+
}));
|
|
458
|
+
const used = new Set();
|
|
459
|
+
for (const reading of readings) {
|
|
460
|
+
for (const { module } of read(dir, reading.files)) {
|
|
461
|
+
for (const value of readProperty(module, reading.property, reading.call, true).values) {
|
|
462
|
+
const named = prefixedPackage(value, reading);
|
|
463
|
+
if (named !== undefined && declared.has(named)) {
|
|
464
|
+
used.add(named);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return [...used].sort(bytewise);
|
|
470
|
+
}
|
|
471
|
+
/** The declared dependencies an applied row's helper settings use. */
|
|
472
|
+
function helperUses(read, dir, manifest, readings) {
|
|
473
|
+
const declared = new Set(DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
474
|
+
const held = isRecord(manifest) ? manifest[section] : undefined;
|
|
475
|
+
return isRecord(held) ? Object.keys(held) : [];
|
|
476
|
+
}));
|
|
477
|
+
return readings
|
|
478
|
+
.filter((reading) => declared.has(reading.package) &&
|
|
479
|
+
read(dir, reading.files).some(({ module }) => valuesAt(readObjects(module, reading.call), reading.property.split(".")).some((held) => holdsValue(held, reading.value))))
|
|
480
|
+
.map((reading) => reading.package);
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* The dependencies one manifest declares that an applied row's package keys use: each
|
|
484
|
+
* whose name a string under a member of a key's name equals, or begins followed by `/` or
|
|
485
|
+
* by `:` and a name. A member's own name is no such string.
|
|
486
|
+
*/
|
|
487
|
+
function packageKeyUses(read, dir, manifest, keys) {
|
|
488
|
+
const declared = DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
489
|
+
const held = isRecord(manifest) ? manifest[section] : undefined;
|
|
490
|
+
return isRecord(held) ? Object.keys(held) : [];
|
|
491
|
+
});
|
|
492
|
+
const strings = [];
|
|
493
|
+
const children = (value) => value.kind === "object"
|
|
494
|
+
? value.members.map((member) => member.value)
|
|
495
|
+
: value.kind === "array"
|
|
496
|
+
? value.elements
|
|
497
|
+
: [];
|
|
498
|
+
const collect = (value) => {
|
|
499
|
+
if (value.kind === "text") {
|
|
500
|
+
strings.push(value.text);
|
|
501
|
+
}
|
|
502
|
+
children(value).forEach(collect);
|
|
503
|
+
};
|
|
504
|
+
for (const { key, files } of keys) {
|
|
505
|
+
const visit = (value) => {
|
|
506
|
+
if (value.kind === "object") {
|
|
507
|
+
value.members
|
|
508
|
+
.filter((member) => member.key === key)
|
|
509
|
+
.forEach((member) => {
|
|
510
|
+
collect(member.value);
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
children(value).forEach(visit);
|
|
514
|
+
};
|
|
515
|
+
for (const { module } of read(dir, files)) {
|
|
516
|
+
module.defaultExport.forEach(visit);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return declared.filter((name) => strings.some((text) => text === name ||
|
|
520
|
+
text.startsWith(`${name}/`) ||
|
|
521
|
+
(text.startsWith(`${name}:`) && text.length > name.length + 1)));
|
|
522
|
+
}
|
|
523
|
+
/** Every simple command of a manifest's `scripts`, as the command reader reads it in the manifest's directory. */
|
|
524
|
+
function scriptCommands(manifest, dir) {
|
|
525
|
+
const scripts = isRecord(manifest) ? manifest["scripts"] : undefined;
|
|
526
|
+
return isRecord(scripts)
|
|
527
|
+
? Object.values(scripts).flatMap((text) => typeof text === "string" ? readCommands(text, dir).commands : [])
|
|
528
|
+
: [];
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* What the rows decide over the manifests the analysis reads. A row `disabled` names
|
|
532
|
+
* is skipped before anything is read for it.
|
|
533
|
+
*/
|
|
534
|
+
export function readConventions(host, parse, manifests, disabled, rows = CONVENTION_ROWS) {
|
|
535
|
+
const applied = [];
|
|
536
|
+
const globs = [];
|
|
537
|
+
const generated = [];
|
|
538
|
+
const failures = [];
|
|
539
|
+
const uses = new Set();
|
|
540
|
+
const selected = new Map();
|
|
541
|
+
const testFiles = new Map();
|
|
542
|
+
const testGlobs = [];
|
|
543
|
+
const read = readFilesOnce(host, parse);
|
|
544
|
+
for (const manifest of manifests) {
|
|
545
|
+
const declared = readJSON(host, joinPath(manifest.dir, MANIFEST));
|
|
546
|
+
let input;
|
|
547
|
+
const runnerInput = () => {
|
|
548
|
+
input ??= {
|
|
549
|
+
host,
|
|
550
|
+
dir: manifest.dir,
|
|
551
|
+
manifest: declared,
|
|
552
|
+
configuration: (dir, names) => {
|
|
553
|
+
for (const name of names) {
|
|
554
|
+
const [found] = read(dir, [name]);
|
|
555
|
+
if (found !== undefined) {
|
|
556
|
+
return { dir, module: found.module };
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return undefined;
|
|
560
|
+
},
|
|
561
|
+
scripts: scriptCommands(declared, manifest.dir),
|
|
562
|
+
};
|
|
563
|
+
return input;
|
|
564
|
+
};
|
|
565
|
+
const versions = new Map();
|
|
566
|
+
const missing = new Set();
|
|
567
|
+
for (const row of rows) {
|
|
568
|
+
if (disabled.includes(row.name) || !declares(declared, row.package)) {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
if (!versions.has(row.package)) {
|
|
572
|
+
versions.set(row.package, installedVersion(host, manifest.dir, row.package));
|
|
573
|
+
}
|
|
574
|
+
const version = versions.get(row.package);
|
|
575
|
+
if (version === undefined) {
|
|
576
|
+
if (missing.has(row.package) ||
|
|
577
|
+
manifest.nested === true ||
|
|
578
|
+
!declares(declared, row.package, REQUIRED_SECTIONS)) {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
missing.add(row.package);
|
|
582
|
+
failures.push({
|
|
583
|
+
dir: manifest.dir,
|
|
584
|
+
failure: {
|
|
585
|
+
failureClass: "missing-module",
|
|
586
|
+
detail: `${manifest.path} declares ${row.package}, which no node_modules at or ` +
|
|
587
|
+
`above its directory holds: run the package manager's install`,
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (!satisfies(version, row.range)) {
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
const resolved = new Map();
|
|
596
|
+
let failed;
|
|
597
|
+
for (const move of row.moves) {
|
|
598
|
+
const result = resolveMove(read, manifest.dir, row, move, resolved);
|
|
599
|
+
if ("failure" in result) {
|
|
600
|
+
failed = result.failure;
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
resolved.set(move.id, result.dirs);
|
|
604
|
+
}
|
|
605
|
+
if (failed !== undefined) {
|
|
606
|
+
const where = dirnamePath(manifest.path);
|
|
607
|
+
failures.push({
|
|
608
|
+
dir: manifest.dir,
|
|
609
|
+
failure: {
|
|
610
|
+
failureClass: "convention-not-literal",
|
|
611
|
+
detail: where === "." ? failed : `${where}/${failed}`,
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
applied.push({
|
|
617
|
+
name: row.name,
|
|
618
|
+
package: row.package,
|
|
619
|
+
version,
|
|
620
|
+
manifest: manifest.path,
|
|
621
|
+
});
|
|
622
|
+
for (const name of shortNameUses(read, manifest.dir, declared, row.shortNames ?? [])) {
|
|
623
|
+
uses.add(name);
|
|
624
|
+
}
|
|
625
|
+
const prefixed = row.prefixedNames ?? [];
|
|
626
|
+
for (const name of prefixedNameUses(read, manifest.dir, declared, prefixed)) {
|
|
627
|
+
uses.add(name);
|
|
628
|
+
}
|
|
629
|
+
const keys = row.packageKeys ?? [];
|
|
630
|
+
for (const name of packageKeyUses(read, manifest.dir, declared, keys)) {
|
|
631
|
+
uses.add(name);
|
|
632
|
+
}
|
|
633
|
+
for (const name of manifestKeyUses(declared, row.manifestKeys ?? [])) {
|
|
634
|
+
uses.add(name);
|
|
635
|
+
}
|
|
636
|
+
for (const name of helperUses(read, manifest.dir, declared, row.helpers ?? [])) {
|
|
637
|
+
uses.add(name);
|
|
638
|
+
}
|
|
639
|
+
selectedStrings(read, manifest.dir, row.selections ?? [], selected);
|
|
640
|
+
testFilesNamed(read, manifest.dir, row.testFiles ?? [], testFiles, selected);
|
|
641
|
+
testDirectoryGlobs(read, manifest.dir, row.testDirectories ?? [], testGlobs);
|
|
642
|
+
testGlobs.push(...(row.testRunner?.(runnerInput()) ?? []));
|
|
643
|
+
const excludes = [
|
|
644
|
+
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|
|
645
|
+
].map(globExpression);
|
|
646
|
+
for (const below of new Set((row.generated ?? []).flatMap((one) => expand(one, resolved)))) {
|
|
647
|
+
generated.push({ dir: manifest.dir, row: row.name, below });
|
|
648
|
+
}
|
|
649
|
+
const written = [
|
|
650
|
+
...row.entries.flatMap((entry) => expand(entry, resolved)),
|
|
651
|
+
...manifestEntryGlobs(declared, row.manifestEntries ?? []),
|
|
652
|
+
];
|
|
653
|
+
for (const glob of new Set(written)) {
|
|
654
|
+
globs.push({
|
|
655
|
+
dir: manifest.dir,
|
|
656
|
+
row: row.name,
|
|
657
|
+
expression: globExpression(glob),
|
|
658
|
+
excludes,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
testGlobs.push(...nodeTestGlobs(runnerInput().scripts));
|
|
663
|
+
}
|
|
664
|
+
const unique = [...new Map(applied.map((one) => [JSON.stringify(one), one])).values()];
|
|
665
|
+
unique.sort((a, b) => bytewise(a.name, b.name) ||
|
|
666
|
+
bytewise(a.manifest, b.manifest) ||
|
|
667
|
+
bytewise(JSON.stringify(a), JSON.stringify(b)));
|
|
668
|
+
return {
|
|
669
|
+
applied: unique,
|
|
670
|
+
globs,
|
|
671
|
+
generated,
|
|
672
|
+
failures,
|
|
673
|
+
uses: [...uses].sort(bytewise),
|
|
674
|
+
selected,
|
|
675
|
+
testFiles,
|
|
676
|
+
testGlobs,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* The files, absolute, of one project an applied row names as test files, each with the
|
|
681
|
+
* string or the pattern that names it.
|
|
682
|
+
*/
|
|
683
|
+
export function conventionTestFiles(conventions, files) {
|
|
684
|
+
const found = new Map(conventions.testFiles);
|
|
685
|
+
for (const { dir, pattern, matches } of conventions.testGlobs) {
|
|
686
|
+
for (const file of files.byName.values()) {
|
|
687
|
+
if (relativePath(dir, file.fileName) !== undefined &&
|
|
688
|
+
matches(file.fileName) &&
|
|
689
|
+
!found.has(file.fileName)) {
|
|
690
|
+
found.set(file.fileName, pattern);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return found;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Every own file of one project below a directory an applied row names as generated, by
|
|
698
|
+
* its path below the target root, with the name of the first such row.
|
|
699
|
+
*/
|
|
700
|
+
export function generatedFiles(conventions, files) {
|
|
701
|
+
const found = new Map();
|
|
702
|
+
for (const { dir, row, below } of conventions.generated) {
|
|
703
|
+
const root = joinPath(dir, below);
|
|
704
|
+
for (const [path, file] of files.byPath) {
|
|
705
|
+
if (!found.has(path) && relativePath(root, file.fileName) !== undefined) {
|
|
706
|
+
found.set(path, row);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return found;
|
|
711
|
+
}
|
|
712
|
+
/** Every own file of one project an applied row's globs match, once per row. */
|
|
713
|
+
export function conventionEntries(conventions, files) {
|
|
714
|
+
const found = new Map();
|
|
715
|
+
for (const glob of conventions.globs) {
|
|
716
|
+
for (const file of files.byName.values()) {
|
|
717
|
+
const matched = [file.fileName, ...files.writtenAs(file)].some((name) => {
|
|
718
|
+
const path = relativePath(glob.dir, name);
|
|
719
|
+
return (path !== undefined &&
|
|
720
|
+
glob.expression.test(path) &&
|
|
721
|
+
!glob.excludes.some((one) => one.test(path)));
|
|
722
|
+
});
|
|
723
|
+
if (matched) {
|
|
724
|
+
found.set(`${file.fileName}\u0000${glob.row}`, { file, row: glob.row });
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return [...found.values()];
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* The failures the rows met that hold for one configuration: those of a manifest whose
|
|
732
|
+
* directory holds the configuration file or one of the files the project owns.
|
|
733
|
+
*/
|
|
734
|
+
export function conventionFailuresFor(conventions, configFile, ownFiles) {
|
|
735
|
+
return conventions.failures
|
|
736
|
+
.filter(({ dir }) => below(dir, dirnamePath(configFile)) !== undefined ||
|
|
737
|
+
ownFiles.some((file) => relativePath(dir, file) !== undefined))
|
|
738
|
+
.map(({ failure }) => failure);
|
|
739
|
+
}
|