@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.
Files changed (113) hide show
  1. package/dist/src/analysis.js +95 -60
  2. package/dist/src/augmentations.js +14 -12
  3. package/dist/src/component-layer.js +1 -1
  4. package/dist/src/components.js +62 -20
  5. package/dist/src/configuration-files.js +230 -30
  6. package/dist/src/conformance-record.js +1 -1
  7. package/dist/src/contextual-uses.js +104 -13
  8. package/dist/src/convention-rows.js +10 -0
  9. package/dist/src/conventions.js +35 -1
  10. package/dist/src/dependencies.js +90 -32
  11. package/dist/src/discover.js +1 -1
  12. package/dist/src/edges.js +1 -1
  13. package/dist/src/element-access.js +341 -0
  14. package/dist/src/erased-stores.js +317 -0
  15. package/dist/src/exempt-classes.js +9 -22
  16. package/dist/src/exempt.js +3 -15
  17. package/dist/src/findings/completion.js +78 -16
  18. package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
  19. package/dist/src/findings/emitters.js +1 -5
  20. package/dist/src/findings/self-check.js +27 -10
  21. package/dist/src/findings-pass.js +6 -5
  22. package/dist/src/in-operands.js +30 -0
  23. package/dist/src/indexed-access.js +264 -119
  24. package/dist/src/intra-function-parts.js +25 -11
  25. package/dist/src/inventory.js +28 -12
  26. package/dist/src/keyof-comparisons.js +170 -0
  27. package/dist/src/kinds.js +17 -7
  28. package/dist/src/manifest.js +6 -16
  29. package/dist/src/matrix.js +20 -2
  30. package/dist/src/query.js +3 -0
  31. package/dist/src/references.js +14 -3
  32. package/dist/src/report.js +4 -1
  33. package/dist/src/reporters/sarif.js +4 -0
  34. package/dist/src/roots.js +0 -1
  35. package/dist/src/run.js +0 -2
  36. package/dist/src/schema.js +1 -1
  37. package/dist/src/scope.js +2 -2
  38. package/dist/src/serialization-contract.js +43 -14
  39. package/dist/src/session.js +12 -8
  40. package/dist/src/suppress-file.js +58 -6
  41. package/dist/src/suppress-inline.js +14 -5
  42. package/dist/src/suppress.js +64 -35
  43. package/dist/src/unimplemented-callees.js +312 -0
  44. package/dist/src/value-reach.js +7 -7
  45. package/dist/src/verbs/analyze.js +11 -9
  46. package/dist/src/workspace-programs.js +0 -3
  47. package/dist/src/workspace-resolution.js +0 -2
  48. package/dist/src/workspace.js +0 -3
  49. package/jsr.json +1 -1
  50. package/package.json +3 -2
  51. package/src/analysis.ts +145 -82
  52. package/src/augmentations.ts +14 -11
  53. package/src/calls.ts +3 -3
  54. package/src/component-files.ts +7 -7
  55. package/src/component-layer.ts +2 -2
  56. package/src/components.ts +104 -22
  57. package/src/config.ts +4 -4
  58. package/src/configuration-files.ts +250 -31
  59. package/src/conformance-record.ts +1 -1
  60. package/src/contextual-uses.ts +135 -17
  61. package/src/convention-rows.ts +23 -0
  62. package/src/conventions.ts +53 -1
  63. package/src/dependencies.ts +119 -38
  64. package/src/discover.ts +3 -3
  65. package/src/edges.ts +3 -3
  66. package/src/element-access.ts +446 -0
  67. package/src/entry-points.ts +1 -1
  68. package/src/erased-stores.ts +413 -0
  69. package/src/exempt-classes.ts +10 -24
  70. package/src/exempt.ts +5 -29
  71. package/src/file-facts.ts +3 -1
  72. package/src/finding.ts +3 -1
  73. package/src/findings/completion.ts +97 -21
  74. package/src/findings/dependencies-and-module-machinery.ts +1 -1
  75. package/src/findings/emitters.ts +4 -9
  76. package/src/findings/self-check.ts +31 -14
  77. package/src/findings-pass.ts +14 -7
  78. package/src/graph.ts +2 -2
  79. package/src/html-entries.ts +1 -1
  80. package/src/in-operands.ts +45 -0
  81. package/src/indexed-access.ts +303 -137
  82. package/src/intra-function-parts.ts +41 -16
  83. package/src/inventory.ts +37 -33
  84. package/src/json-document.ts +1 -1
  85. package/src/keyof-comparisons.ts +211 -0
  86. package/src/kinds.ts +20 -7
  87. package/src/manifest.ts +7 -22
  88. package/src/matrix.ts +34 -2
  89. package/src/namespace-objects.ts +1 -1
  90. package/src/pnpm-workspace.ts +1 -1
  91. package/src/query.ts +10 -0
  92. package/src/references.ts +30 -7
  93. package/src/report.ts +11 -5
  94. package/src/reporters/sarif.ts +4 -0
  95. package/src/roots.ts +0 -3
  96. package/src/run.ts +0 -2
  97. package/src/schema.ts +2 -2
  98. package/src/scope.ts +2 -2
  99. package/src/serialization-contract.ts +53 -14
  100. package/src/session.ts +34 -31
  101. package/src/stores.ts +1 -1
  102. package/src/suppress-file.ts +68 -5
  103. package/src/suppress-inline.ts +17 -7
  104. package/src/suppress.ts +79 -47
  105. package/src/template-field.ts +1 -1
  106. package/src/unimplemented-callees.ts +411 -0
  107. package/src/value-reach.ts +12 -8
  108. package/src/verbs/analyze.ts +15 -9
  109. package/src/verbs/verb.ts +1 -4
  110. package/src/workspace-programs.ts +2 -9
  111. package/src/workspace-resolution.ts +0 -3
  112. package/src/workspace-run.ts +1 -1
  113. package/src/workspace.ts +1 -9
@@ -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
- * function parameters and results too; a class instance there is the
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
- export interface ContextualUse {
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
- export interface ContextualUses {
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
- /** The parameter and return types of a type's one call signature, read once per type. */
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
- const signatures = queries.signaturesOf(type, SignatureKind.Call);
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 and through a function's
299
- * parameters, checked the other way, and its result. A source that is one of the
300
- * target's own object types matches it as it stands. Each pair is compared once, which
301
- * ends the walk over a recursive type.
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 === undefined) {
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
  }
@@ -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
  ];
@@ -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 (missing.has(row.package) || !declares(declared, row.package, REQUIRED_SECTIONS)) {
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