@cplieger/deadset-ts 7.0.0-dev.1 → 7.0.0-dev.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/analysis.js +95 -60
- package/dist/src/augmentations.js +14 -12
- package/dist/src/component-layer.js +1 -1
- package/dist/src/components.js +62 -20
- package/dist/src/configuration-files.js +230 -30
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contextual-uses.js +104 -13
- package/dist/src/convention-rows.js +10 -0
- package/dist/src/conventions.js +35 -1
- package/dist/src/dependencies.js +90 -32
- package/dist/src/discover.js +1 -1
- package/dist/src/edges.js +1 -1
- package/dist/src/element-access.js +341 -0
- package/dist/src/erased-stores.js +317 -0
- package/dist/src/exempt-classes.js +9 -22
- package/dist/src/exempt.js +3 -15
- package/dist/src/findings/completion.js +78 -16
- package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
- package/dist/src/findings/emitters.js +1 -5
- package/dist/src/findings/self-check.js +27 -10
- package/dist/src/findings-pass.js +6 -5
- package/dist/src/in-operands.js +30 -0
- package/dist/src/indexed-access.js +264 -119
- package/dist/src/intra-function-parts.js +25 -11
- package/dist/src/inventory.js +28 -12
- package/dist/src/keyof-comparisons.js +170 -0
- package/dist/src/kinds.js +17 -7
- package/dist/src/manifest.js +6 -16
- package/dist/src/matrix.js +20 -2
- package/dist/src/query.js +3 -0
- package/dist/src/references.js +14 -3
- package/dist/src/report.js +4 -1
- package/dist/src/reporters/sarif.js +4 -0
- package/dist/src/roots.js +0 -1
- package/dist/src/run.js +0 -2
- package/dist/src/schema.js +1 -1
- package/dist/src/scope.js +2 -2
- package/dist/src/serialization-contract.js +43 -14
- package/dist/src/session.js +12 -8
- package/dist/src/suppress-file.js +58 -6
- package/dist/src/suppress-inline.js +14 -5
- package/dist/src/suppress.js +64 -35
- package/dist/src/unimplemented-callees.js +312 -0
- package/dist/src/value-reach.js +7 -7
- package/dist/src/verbs/analyze.js +11 -9
- package/dist/src/workspace-programs.js +0 -3
- package/dist/src/workspace-resolution.js +0 -2
- package/dist/src/workspace.js +0 -3
- package/jsr.json +1 -1
- package/package.json +3 -2
- package/src/analysis.ts +145 -82
- package/src/augmentations.ts +14 -11
- package/src/calls.ts +3 -3
- package/src/component-files.ts +7 -7
- package/src/component-layer.ts +2 -2
- package/src/components.ts +104 -22
- package/src/config.ts +4 -4
- package/src/configuration-files.ts +250 -31
- package/src/conformance-record.ts +1 -1
- package/src/contextual-uses.ts +135 -17
- package/src/convention-rows.ts +23 -0
- package/src/conventions.ts +53 -1
- package/src/dependencies.ts +119 -38
- package/src/discover.ts +3 -3
- package/src/edges.ts +3 -3
- package/src/element-access.ts +446 -0
- package/src/entry-points.ts +1 -1
- package/src/erased-stores.ts +413 -0
- package/src/exempt-classes.ts +10 -24
- package/src/exempt.ts +5 -29
- package/src/file-facts.ts +3 -1
- package/src/finding.ts +3 -1
- package/src/findings/completion.ts +97 -21
- package/src/findings/dependencies-and-module-machinery.ts +1 -1
- package/src/findings/emitters.ts +4 -9
- package/src/findings/self-check.ts +31 -14
- package/src/findings-pass.ts +14 -7
- package/src/graph.ts +2 -2
- package/src/html-entries.ts +1 -1
- package/src/in-operands.ts +45 -0
- package/src/indexed-access.ts +303 -137
- package/src/intra-function-parts.ts +41 -16
- package/src/inventory.ts +37 -33
- package/src/json-document.ts +1 -1
- package/src/keyof-comparisons.ts +211 -0
- package/src/kinds.ts +20 -7
- package/src/manifest.ts +7 -22
- package/src/matrix.ts +34 -2
- package/src/namespace-objects.ts +1 -1
- package/src/pnpm-workspace.ts +1 -1
- package/src/query.ts +10 -0
- package/src/references.ts +30 -7
- package/src/report.ts +11 -5
- package/src/reporters/sarif.ts +4 -0
- package/src/roots.ts +0 -3
- package/src/run.ts +0 -2
- package/src/schema.ts +2 -2
- package/src/scope.ts +2 -2
- package/src/serialization-contract.ts +53 -14
- package/src/session.ts +34 -31
- package/src/stores.ts +1 -1
- package/src/suppress-file.ts +68 -5
- package/src/suppress-inline.ts +17 -7
- package/src/suppress.ts +79 -47
- package/src/template-field.ts +1 -1
- package/src/unimplemented-callees.ts +411 -0
- package/src/value-reach.ts +12 -8
- package/src/verbs/analyze.ts +15 -9
- package/src/verbs/verb.ts +1 -4
- package/src/workspace-programs.ts +2 -9
- package/src/workspace-resolution.ts +0 -3
- package/src/workspace-run.ts +1 -1
- package/src/workspace.ts +1 -9
package/src/contextual-uses.ts
CHANGED
|
@@ -3,27 +3,31 @@
|
|
|
3
3
|
* An object-literal property writes the member of its name on each constituent of the
|
|
4
4
|
* literal's contextual type that declares one. A value reaching a position typed by another
|
|
5
5
|
* object type reads each of its members that type declares, through array elements and
|
|
6
|
-
*
|
|
7
|
-
* interface-satisfaction class's evidence instead.
|
|
6
|
+
* the parameters and results of functions and constructors too; a class instance there is
|
|
7
|
+
* the interface-satisfaction class's evidence instead.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
+
isClassDeclaration,
|
|
12
|
+
isClassExpression,
|
|
11
13
|
isComputedPropertyName,
|
|
12
14
|
isGetAccessorDeclaration,
|
|
13
15
|
isIdentifier,
|
|
14
16
|
isMethodDeclaration,
|
|
17
|
+
isMethodSignatureDeclaration,
|
|
15
18
|
isNoSubstitutionTemplateLiteral,
|
|
16
19
|
isNumericLiteral,
|
|
17
20
|
isObjectLiteralExpression,
|
|
18
21
|
isPropertyAssignment,
|
|
19
22
|
isSetAccessorDeclaration,
|
|
20
23
|
isShorthandPropertyAssignment,
|
|
24
|
+
isSpreadAssignment,
|
|
21
25
|
isStringLiteral,
|
|
22
26
|
type Expression,
|
|
23
27
|
type Node,
|
|
24
28
|
type ObjectLiteralExpression,
|
|
25
29
|
} from "@typescript/native/unstable/ast";
|
|
26
|
-
import { SignatureKind, type Type } from "@typescript/native/unstable/sync";
|
|
30
|
+
import { SignatureKind, type NodeHandle, type Type } from "@typescript/native/unstable/sync";
|
|
27
31
|
import type { AliasChains } from "./alias-chain.ts";
|
|
28
32
|
import type { Inventory, SymbolKind } from "./inventory.ts";
|
|
29
33
|
import { isAnswered, type PropertyTables } from "./query.ts";
|
|
@@ -45,14 +49,14 @@ export interface FlowAt {
|
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
/** One use the contextual types make: the node it is written at and the members it names. */
|
|
48
|
-
|
|
52
|
+
interface ContextualUse {
|
|
49
53
|
readonly at: Node;
|
|
50
54
|
readonly from: string;
|
|
51
55
|
readonly ids: readonly string[];
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
/** What the contextual types of one project's literals and values use, and what asking cost. */
|
|
55
|
-
|
|
59
|
+
interface ContextualUses {
|
|
56
60
|
/** Each literal property that writes a member, by the property's node. */
|
|
57
61
|
readonly writes: readonly ContextualUse[];
|
|
58
62
|
/** Each value whose members a position typed by another object type reads. */
|
|
@@ -93,8 +97,9 @@ function propertyName(
|
|
|
93
97
|
return undefined;
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
/** The parameter and result types of one call signature, each where the checker answered. */
|
|
100
|
+
/** The parameter and result types of one call or construct signature, each where the checker answered. */
|
|
97
101
|
interface Called {
|
|
102
|
+
readonly kind: SignatureKind;
|
|
98
103
|
readonly parameters: readonly (Type | undefined)[];
|
|
99
104
|
readonly result: Type | undefined;
|
|
100
105
|
}
|
|
@@ -113,9 +118,30 @@ interface Checked {
|
|
|
113
118
|
/** One member of an object type, by the inventory's declarations and the checker's declaration handles. */
|
|
114
119
|
interface Member {
|
|
115
120
|
readonly ids: readonly string[];
|
|
121
|
+
/** The inventory's interface methods it is, which a position reads but no literal writes. */
|
|
122
|
+
readonly methods: readonly string[];
|
|
116
123
|
readonly handles: ReadonlySet<string>;
|
|
117
124
|
/** Whether a declaration of it is in one of the target's own files. */
|
|
118
125
|
readonly own: boolean;
|
|
126
|
+
/** The checker's declarations of it in the target's own files. */
|
|
127
|
+
readonly declarations: readonly NodeHandle[];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Whether an object spread copies a member a declaration declares: a method, a method
|
|
132
|
+
* signature and a class's accessor are no own data property of the value, while an
|
|
133
|
+
* accessor of an interface or a type literal describes one. It is decided on the
|
|
134
|
+
* declaration, because a mapped type over a class keeps the class's declarations on
|
|
135
|
+
* plain property symbols.
|
|
136
|
+
*/
|
|
137
|
+
function copiedBySpread(node: Node): boolean {
|
|
138
|
+
if (isMethodDeclaration(node) || isMethodSignatureDeclaration(node)) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
return !(
|
|
142
|
+
(isGetAccessorDeclaration(node) || isSetAccessorDeclaration(node)) &&
|
|
143
|
+
(isClassDeclaration(node.parent) || isClassExpression(node.parent))
|
|
144
|
+
);
|
|
119
145
|
}
|
|
120
146
|
|
|
121
147
|
/**
|
|
@@ -135,6 +161,11 @@ export function contextualUses<Brand>(
|
|
|
135
161
|
const queries = project.queries;
|
|
136
162
|
const ownPaths = project.ownPaths();
|
|
137
163
|
const kinds = new Map(held.symbols.map((symbol) => [symbol.id, symbol.kind]));
|
|
164
|
+
const privateNames = new Set(
|
|
165
|
+
held.symbols
|
|
166
|
+
.filter((symbol) => symbol.visibility === "private-name")
|
|
167
|
+
.map((symbol) => symbol.id),
|
|
168
|
+
);
|
|
138
169
|
let batches = 0;
|
|
139
170
|
let lookups = 0;
|
|
140
171
|
const batchesOf = (count: number): number => Math.ceil(count / cap);
|
|
@@ -173,10 +204,14 @@ export function contextualUses<Brand>(
|
|
|
173
204
|
ids: chains
|
|
174
205
|
.declarationsOf(property)
|
|
175
206
|
.filter((id) => MEMBER_KINDS.has(kinds.get(id) ?? "file")),
|
|
207
|
+
methods: chains
|
|
208
|
+
.declarationsOf(property)
|
|
209
|
+
.filter((id) => kinds.get(id) === "interface-method"),
|
|
176
210
|
handles: new Set(
|
|
177
211
|
property.declarations.map((handle) => `${handle.path}#${String(handle.index)}`),
|
|
178
212
|
),
|
|
179
213
|
own: property.declarations.some((handle) => ownPaths.has(handle.path)),
|
|
214
|
+
declarations: property.declarations.filter((handle) => ownPaths.has(handle.path)),
|
|
180
215
|
});
|
|
181
216
|
}
|
|
182
217
|
tables.set(type.id, table);
|
|
@@ -240,8 +275,10 @@ export function contextualUses<Brand>(
|
|
|
240
275
|
cap,
|
|
241
276
|
);
|
|
242
277
|
/** The members a position may read structurally: those of object types, not of classes. */
|
|
243
|
-
const readable = (member: Member): readonly string[] =>
|
|
244
|
-
member.ids.filter((id) => kinds.get(id) === "type-member")
|
|
278
|
+
const readable = (member: Member): readonly string[] => [
|
|
279
|
+
...member.ids.filter((id) => kinds.get(id) === "type-member"),
|
|
280
|
+
...member.methods,
|
|
281
|
+
];
|
|
245
282
|
const carries = (part: Type): boolean =>
|
|
246
283
|
[...membersOf(part).values()].some((member) => readable(member).length > 0);
|
|
247
284
|
/** Whether an object type has members and declares none of them in the target. */
|
|
@@ -268,14 +305,37 @@ export function contextualUses<Brand>(
|
|
|
268
305
|
return element;
|
|
269
306
|
};
|
|
270
307
|
|
|
308
|
+
const argumentsOf = new Map<number, readonly Type[]>();
|
|
309
|
+
/** The type arguments of an instantiation of a generic type, read once per type. */
|
|
310
|
+
const typeArgumentsOf = (type: Type): readonly Type[] => {
|
|
311
|
+
const known = argumentsOf.get(type.id);
|
|
312
|
+
if (known !== undefined) {
|
|
313
|
+
return known;
|
|
314
|
+
}
|
|
315
|
+
lookups += type.isTypeReference() ? 1 : 0;
|
|
316
|
+
const held = queries.typeArguments(type);
|
|
317
|
+
const found = isAnswered(held) ? held : [];
|
|
318
|
+
argumentsOf.set(type.id, found);
|
|
319
|
+
return found;
|
|
320
|
+
};
|
|
321
|
+
|
|
271
322
|
const calls = new Map<number, Called | undefined>();
|
|
272
|
-
/**
|
|
323
|
+
/**
|
|
324
|
+
* The parameter and return types of a type's one call signature, or of its one construct
|
|
325
|
+
* signature where it has no call signature, read once per type.
|
|
326
|
+
*/
|
|
273
327
|
const callOf = (type: Type): Called | undefined => {
|
|
274
328
|
if (calls.has(type.id)) {
|
|
275
329
|
return calls.get(type.id);
|
|
276
330
|
}
|
|
277
331
|
lookups += 1;
|
|
278
|
-
|
|
332
|
+
let kind = SignatureKind.Call;
|
|
333
|
+
let signatures = queries.signaturesOf(type, kind);
|
|
334
|
+
if (isAnswered(signatures) && signatures.length === 0) {
|
|
335
|
+
lookups += 1;
|
|
336
|
+
kind = SignatureKind.Construct;
|
|
337
|
+
signatures = queries.signaturesOf(type, kind);
|
|
338
|
+
}
|
|
279
339
|
let called: Called | undefined;
|
|
280
340
|
const [signature, ...more] = isAnswered(signatures) ? signatures : [];
|
|
281
341
|
if (signature !== undefined && more.length === 0) {
|
|
@@ -285,6 +345,7 @@ export function contextualUses<Brand>(
|
|
|
285
345
|
const result = queries.returnTypeOf(signature);
|
|
286
346
|
lookups += parameters.length + 1;
|
|
287
347
|
called = {
|
|
348
|
+
kind,
|
|
288
349
|
parameters: parameters.map((one) => (isAnswered(one) ? one : undefined)),
|
|
289
350
|
result: isAnswered(result) ? result : undefined,
|
|
290
351
|
};
|
|
@@ -295,10 +356,11 @@ export function contextualUses<Brand>(
|
|
|
295
356
|
|
|
296
357
|
/**
|
|
297
358
|
* Each object type a value of `source` is checked as against the object types of
|
|
298
|
-
* `target`: the value's own, and through array elements
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
359
|
+
* `target`: the value's own, and through array elements, a function's parameters,
|
|
360
|
+
* checked the other way, and result, and the type arguments two instantiations share
|
|
361
|
+
* by place, an array among them, as an array reaches `Iterable<T>`.
|
|
362
|
+
* A source that is one of the target's own object types matches it as it stands. Each
|
|
363
|
+
* pair is compared once, which ends the walk over a recursive type.
|
|
302
364
|
*/
|
|
303
365
|
const compared = (
|
|
304
366
|
source: Type | undefined,
|
|
@@ -324,13 +386,24 @@ export function contextualUses<Brand>(
|
|
|
324
386
|
}
|
|
325
387
|
const element = elementOf(one);
|
|
326
388
|
const called = element === undefined ? callOf(one) : undefined;
|
|
389
|
+
const sourceArguments = typeArgumentsOf(one);
|
|
327
390
|
for (const part of targets) {
|
|
391
|
+
const partElement = elementOf(part);
|
|
392
|
+
if (element !== undefined && partElement !== undefined) {
|
|
393
|
+
compared(element, partElement, into, seen);
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
const targetArguments = typeArgumentsOf(part);
|
|
397
|
+
// A defaulted trailing parameter, as `Iterable<T, TReturn, TNext>` has, leaves
|
|
398
|
+
// the places two instantiations share comparable.
|
|
399
|
+
sourceArguments.slice(0, targetArguments.length).forEach((argument, index) => {
|
|
400
|
+
compared(argument, targetArguments[index], into, seen);
|
|
401
|
+
});
|
|
328
402
|
if (element !== undefined) {
|
|
329
|
-
compared(element, elementOf(part), into, seen);
|
|
330
403
|
continue;
|
|
331
404
|
}
|
|
332
405
|
const other = called === undefined ? undefined : callOf(part);
|
|
333
|
-
if (called === undefined || other
|
|
406
|
+
if (called === undefined || other?.kind !== called.kind) {
|
|
334
407
|
continue;
|
|
335
408
|
}
|
|
336
409
|
other.parameters.forEach((parameter, index) => {
|
|
@@ -358,7 +431,7 @@ export function contextualUses<Brand>(
|
|
|
358
431
|
if (element !== undefined) {
|
|
359
432
|
return mayCarry(element, seen);
|
|
360
433
|
}
|
|
361
|
-
return callOf(part) !== undefined;
|
|
434
|
+
return callOf(part) !== undefined || typeArgumentsOf(part).some((one) => mayCarry(one, seen));
|
|
362
435
|
});
|
|
363
436
|
};
|
|
364
437
|
|
|
@@ -413,5 +486,50 @@ export function contextualUses<Brand>(
|
|
|
413
486
|
reads.push({ at: flow.value, from: flow.from, ids: [...ids] });
|
|
414
487
|
}
|
|
415
488
|
});
|
|
489
|
+
const copies = new Map<Member, boolean>();
|
|
490
|
+
/** Whether a spread copies the member: where any declaration of it declares data. */
|
|
491
|
+
const copied = (member: Member): boolean => {
|
|
492
|
+
let known = copies.get(member);
|
|
493
|
+
if (known === undefined) {
|
|
494
|
+
known = member.declarations.some((handle) => {
|
|
495
|
+
const node = project.declarationAt(handle)?.node;
|
|
496
|
+
return node === undefined || copiedBySpread(node);
|
|
497
|
+
});
|
|
498
|
+
copies.set(member, known);
|
|
499
|
+
}
|
|
500
|
+
return known;
|
|
501
|
+
};
|
|
502
|
+
const spreads = literals.flatMap(({ literal, from }) =>
|
|
503
|
+
literal.properties.flatMap((property) =>
|
|
504
|
+
isSpreadAssignment(property) ? [{ value: property.expression, from }] : [],
|
|
505
|
+
),
|
|
506
|
+
);
|
|
507
|
+
batches += batchesOf(spreads.length);
|
|
508
|
+
const spreadTypes = queries.typesAt(
|
|
509
|
+
spreads.map((one) => one.value),
|
|
510
|
+
cap,
|
|
511
|
+
);
|
|
512
|
+
spreads.forEach(({ value, from }, index) => {
|
|
513
|
+
const type = spreadTypes[index];
|
|
514
|
+
if (type === undefined || !isAnswered(type)) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
// A spread gets every own enumerable property, an overwritten key's getter included,
|
|
518
|
+
// and no private name, which is no property
|
|
519
|
+
// (https://tc39.es/ecma262/#sec-copydataproperties).
|
|
520
|
+
const own = (member: Member, id: string): boolean => !privateNames.has(id) && copied(member);
|
|
521
|
+
const ids = [
|
|
522
|
+
...new Set(
|
|
523
|
+
partsOf(type).flatMap((part) =>
|
|
524
|
+
[...membersOf(part).values()].flatMap((member) =>
|
|
525
|
+
member.ids.filter((id) => own(member, id)),
|
|
526
|
+
),
|
|
527
|
+
),
|
|
528
|
+
),
|
|
529
|
+
];
|
|
530
|
+
if (ids.length > 0) {
|
|
531
|
+
reads.push({ at: value, from, ids });
|
|
532
|
+
}
|
|
533
|
+
});
|
|
416
534
|
return { writes, reads, batches, lookups };
|
|
417
535
|
}
|
package/src/convention-rows.ts
CHANGED
|
@@ -53,6 +53,16 @@ export interface PackageKey {
|
|
|
53
53
|
readonly files: readonly string[];
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/** One configuration property whose strings name files the test runner loads beside the tests. */
|
|
57
|
+
export interface TestFileReading extends MoveReading {
|
|
58
|
+
/**
|
|
59
|
+
* The properties that move the directory the strings are read against, the first one the
|
|
60
|
+
* file writes deciding, read against the manifest's directory, which stands where none is
|
|
61
|
+
* written. A value that is not one string literal leaves the file's strings unread.
|
|
62
|
+
*/
|
|
63
|
+
readonly roots: readonly string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
56
66
|
/** One directory a row's globs name that a configuration property moves. */
|
|
57
67
|
export interface DirectoryMove {
|
|
58
68
|
/** The placeholder the row's globs and later moves write as `<id>`. */
|
|
@@ -91,6 +101,8 @@ export interface ConventionRow {
|
|
|
91
101
|
* string there names no file the tool runs or loads.
|
|
92
102
|
*/
|
|
93
103
|
readonly selections?: readonly MoveReading[];
|
|
104
|
+
/** The properties whose strings name a file the test runner loads beside the tests, a test file. */
|
|
105
|
+
readonly testFiles?: readonly TestFileReading[];
|
|
94
106
|
/**
|
|
95
107
|
* The members whose strings name a package the tool loads: the package itself, a
|
|
96
108
|
* subpath of it, or the package followed by `:` and a name.
|
|
@@ -144,6 +156,13 @@ const STRYKER_CONFIG = "{stryker,.stryker}.{conf,config}.{json,js,mjs,cjs}";
|
|
|
144
156
|
const STRYKER_MODULE = "{stryker,.stryker}.{conf,config}.{js,mjs,cjs}";
|
|
145
157
|
const VITEST_CONFIG = `{vitest,vite}.config.${ANY_MODULE}`;
|
|
146
158
|
|
|
159
|
+
/**
|
|
160
|
+
* The properties Vitest takes its project root from, `test.root` before Vite's `root`; it
|
|
161
|
+
* resolves `setupFiles` and `globalSetup` against that root
|
|
162
|
+
* (https://vitest.dev/config/setupfiles, https://vitest.dev/config/globalsetup).
|
|
163
|
+
*/
|
|
164
|
+
const VITEST_ROOTS = ["test.root", "root"] as const;
|
|
165
|
+
|
|
147
166
|
/** The entry files of an Angular CLI project, at the workspace root or below `projects`. */
|
|
148
167
|
const ANGULAR_ENTRIES = [
|
|
149
168
|
"src/{main,main.server,server,polyfills,test}.ts",
|
|
@@ -642,5 +661,9 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
642
661
|
{ property: "test.coverage.include", files: [VITEST_CONFIG] },
|
|
643
662
|
{ property: "test.coverage.exclude", files: [VITEST_CONFIG] },
|
|
644
663
|
],
|
|
664
|
+
testFiles: [
|
|
665
|
+
{ property: "test.setupFiles", files: [VITEST_CONFIG], roots: VITEST_ROOTS },
|
|
666
|
+
{ property: "test.globalSetup", files: [VITEST_CONFIG], roots: VITEST_ROOTS },
|
|
667
|
+
],
|
|
645
668
|
},
|
|
646
669
|
];
|
package/src/conventions.ts
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
type OptionsCall,
|
|
56
56
|
type PackageKey,
|
|
57
57
|
type ShortNameReading,
|
|
58
|
+
type TestFileReading,
|
|
58
59
|
} from "./convention-rows.ts";
|
|
59
60
|
import { documentStrings } from "./configuration-files.ts";
|
|
60
61
|
import { globExpression } from "./glob.ts";
|
|
@@ -70,6 +71,11 @@ export interface ConventionManifest {
|
|
|
70
71
|
readonly dir: string;
|
|
71
72
|
/** The manifest's path below the target root, `/`-separated. */
|
|
72
73
|
readonly path: string;
|
|
74
|
+
/**
|
|
75
|
+
* Whether the manifest is a nested package's, whose install no run requires: an
|
|
76
|
+
* enabling package it declares and nothing installs applies no row and fails nothing.
|
|
77
|
+
*/
|
|
78
|
+
readonly nested?: boolean;
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
/** One row a run applied, as the report lists it. */
|
|
@@ -119,6 +125,8 @@ export interface Conventions {
|
|
|
119
125
|
readonly uses: readonly string[];
|
|
120
126
|
/** Per configuration file, absolute, the strings its applied rows' selection keys hold. */
|
|
121
127
|
readonly selected: ReadonlyMap<string, Selected>;
|
|
128
|
+
/** The files, absolute, an applied row's test-file properties name. */
|
|
129
|
+
readonly testFiles: ReadonlySet<string>;
|
|
122
130
|
}
|
|
123
131
|
|
|
124
132
|
/** The strings one configuration file holds at selection keys, which root no file. */
|
|
@@ -608,6 +616,43 @@ function holdsValue(held: Node, value: true | string): boolean {
|
|
|
608
616
|
);
|
|
609
617
|
}
|
|
610
618
|
|
|
619
|
+
/** The directory one file's test-file strings are read against, as {@link TestFileReading} states it. */
|
|
620
|
+
function testFileRoot(file: SourceFile, dir: string, reading: TestFileReading): string | undefined {
|
|
621
|
+
for (const property of reading.roots) {
|
|
622
|
+
const values = valuesAt(file, property, reading.call);
|
|
623
|
+
if (values.length > 0) {
|
|
624
|
+
const texts = new Set(values.map(literalText));
|
|
625
|
+
const [text] = texts;
|
|
626
|
+
return texts.size === 1 && text !== undefined ? resolvePath(dir, text) : undefined;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return dir;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** The files an applied row's test-file properties name. */
|
|
633
|
+
function testFilesNamed(
|
|
634
|
+
host: Host,
|
|
635
|
+
parse: ParseSource,
|
|
636
|
+
dir: string,
|
|
637
|
+
readings: readonly TestFileReading[],
|
|
638
|
+
into: Set<string>,
|
|
639
|
+
): void {
|
|
640
|
+
for (const reading of readings) {
|
|
641
|
+
for (const { file } of readingFiles(host, parse, dir, reading.files)) {
|
|
642
|
+
const at = testFileRoot(file, dir, reading);
|
|
643
|
+
if (at === undefined) {
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
for (const node of stringsAt(file, reading.property, reading.call)) {
|
|
647
|
+
const text = literalText(node);
|
|
648
|
+
if (text !== undefined && text !== "") {
|
|
649
|
+
into.add(resolvePath(at, text));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
611
656
|
/** The strings an applied row's selection keys hold, added per configuration file to `into`. */
|
|
612
657
|
function selectedStrings(
|
|
613
658
|
host: Host,
|
|
@@ -953,6 +998,7 @@ export function readConventions(
|
|
|
953
998
|
const failures: ConventionFailure[] = [];
|
|
954
999
|
const uses = new Set<string>();
|
|
955
1000
|
const selected = new Map<string, { starts: Set<number>; texts: string[] }>();
|
|
1001
|
+
const testFiles = new Set<string>();
|
|
956
1002
|
for (const manifest of manifests) {
|
|
957
1003
|
const declared = readJSON(host, joinPath(manifest.dir, "package.json"));
|
|
958
1004
|
const versions = new Map<string, string | undefined>();
|
|
@@ -966,7 +1012,11 @@ export function readConventions(
|
|
|
966
1012
|
}
|
|
967
1013
|
const version = versions.get(row.package);
|
|
968
1014
|
if (version === undefined) {
|
|
969
|
-
if (
|
|
1015
|
+
if (
|
|
1016
|
+
missing.has(row.package) ||
|
|
1017
|
+
manifest.nested === true ||
|
|
1018
|
+
!declares(declared, row.package, REQUIRED_SECTIONS)
|
|
1019
|
+
) {
|
|
970
1020
|
continue;
|
|
971
1021
|
}
|
|
972
1022
|
missing.add(row.package);
|
|
@@ -1025,6 +1075,7 @@ export function readConventions(
|
|
|
1025
1075
|
uses.add(name);
|
|
1026
1076
|
}
|
|
1027
1077
|
selectedStrings(host, parse, manifest.dir, row.selections ?? [], selected);
|
|
1078
|
+
testFilesNamed(host, parse, manifest.dir, row.testFiles ?? [], testFiles);
|
|
1028
1079
|
const excludes = [
|
|
1029
1080
|
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|
|
1030
1081
|
].map(globExpression);
|
|
@@ -1055,6 +1106,7 @@ export function readConventions(
|
|
|
1055
1106
|
failures,
|
|
1056
1107
|
uses: [...uses].sort(compare),
|
|
1057
1108
|
selected,
|
|
1109
|
+
testFiles,
|
|
1058
1110
|
};
|
|
1059
1111
|
}
|
|
1060
1112
|
|