@cplieger/deadset-ts 5.2.3 → 6.0.0

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 (73) hide show
  1. package/dist/src/analysis.js +28 -5
  2. package/dist/src/augmentations.js +50 -0
  3. package/dist/src/components.js +15 -15
  4. package/dist/src/conformance-record.js +2 -2
  5. package/dist/src/convention-rows.js +17 -2
  6. package/dist/src/conventions.js +82 -1
  7. package/dist/src/dependencies.js +42 -26
  8. package/dist/src/entry-points.js +28 -14
  9. package/dist/src/exempt.js +1 -0
  10. package/dist/src/findings/dependencies-and-module-machinery.js +2 -2
  11. package/dist/src/findings/emitters.js +47 -12
  12. package/dist/src/findings/interfaces.js +4 -3
  13. package/dist/src/findings/intra-function.js +15 -2
  14. package/dist/src/findings/unused-declarations.js +13 -1
  15. package/dist/src/findings-pass.js +1 -0
  16. package/dist/src/graph.js +6 -6
  17. package/dist/src/implementations.js +1 -1
  18. package/dist/src/indexed-access.js +215 -0
  19. package/dist/src/interface-satisfaction.js +85 -23
  20. package/dist/src/intra-function-parts.js +139 -30
  21. package/dist/src/inventory.js +6 -1
  22. package/dist/src/kinds.js +13 -12
  23. package/dist/src/matrix.js +34 -9
  24. package/dist/src/module-declarations.js +180 -0
  25. package/dist/src/outside-tests.js +17 -15
  26. package/dist/src/overrides.js +70 -9
  27. package/dist/src/query.js +7 -0
  28. package/dist/src/ref.js +1 -0
  29. package/dist/src/references.js +6 -0
  30. package/dist/src/report.js +6 -1
  31. package/dist/src/reporters/sarif.js +3 -1
  32. package/dist/src/reporters/text.js +23 -3
  33. package/dist/src/roots.js +50 -10
  34. package/dist/src/version.js +1 -1
  35. package/dist/src/workflow-steps.js +82 -0
  36. package/jsr.json +1 -1
  37. package/package.json +7 -1
  38. package/src/analysis.ts +40 -4
  39. package/src/augmentations.ts +72 -0
  40. package/src/components.ts +15 -13
  41. package/src/conformance-record.ts +2 -2
  42. package/src/convention-rows.ts +25 -3
  43. package/src/conventions.ts +106 -2
  44. package/src/dependencies.ts +51 -31
  45. package/src/entry-points.ts +32 -19
  46. package/src/exempt.ts +4 -1
  47. package/src/findings/dependencies-and-module-machinery.ts +2 -2
  48. package/src/findings/emitters.ts +71 -12
  49. package/src/findings/interfaces.ts +7 -6
  50. package/src/findings/intra-function.ts +20 -2
  51. package/src/findings/unused-declarations.ts +16 -1
  52. package/src/findings-pass.ts +4 -1
  53. package/src/graph.ts +6 -6
  54. package/src/implementations.ts +1 -1
  55. package/src/indexed-access.ts +295 -0
  56. package/src/interface-satisfaction.ts +118 -27
  57. package/src/intra-function-parts.ts +196 -35
  58. package/src/inventory.ts +8 -0
  59. package/src/kinds.ts +14 -12
  60. package/src/matrix.ts +51 -8
  61. package/src/module-declarations.ts +241 -0
  62. package/src/outside-tests.ts +18 -16
  63. package/src/overrides.ts +80 -14
  64. package/src/query.ts +28 -0
  65. package/src/ref.ts +1 -0
  66. package/src/references.ts +6 -0
  67. package/src/report.ts +11 -1
  68. package/src/reporters/sarif.ts +4 -1
  69. package/src/reporters/text.ts +25 -3
  70. package/src/roots.ts +56 -7
  71. package/src/sweep.ts +12 -0
  72. package/src/version.ts +1 -1
  73. package/src/workflow-steps.ts +104 -0
@@ -27,7 +27,7 @@ import { NO_SELF_CHECK, unmatchedRootFindings, } from "./findings/self-check.js"
27
27
  import { frameworkLifecycle } from "./framework-lifecycle.js";
28
28
  import { implementations, mergeImplementations } from "./implementations.js";
29
29
  import { injectionContainer } from "./injection-container.js";
30
- import { interfaceSatisfaction } from "./interface-satisfaction.js";
30
+ import { interfaceSatisfaction, settledSatisfaction } from "./interface-satisfaction.js";
31
31
  import { intraFunctionFacts, projectParts } from "./intra-function-parts.js";
32
32
  import { inventory } from "./inventory.js";
33
33
  import { packageEntries, readManifest } from "./manifest.js";
@@ -278,6 +278,7 @@ function readProjects(engine, host, scope, config, stage) {
278
278
  componentsUnread: workspace?.componentsUnread ?? [],
279
279
  conventionsApplied: conventions.applied,
280
280
  conventionUses: conventions.uses,
281
+ memberDirs: workspace?.memberDirs ?? [],
281
282
  outsideTests,
282
283
  };
283
284
  }
@@ -406,15 +407,17 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
406
407
  // Test-support code is code only test code imports in any configuration of the run: a
407
408
  // configuration that holds no test file says nothing about what the tests import.
408
409
  const support = testSupportFiles(withOutside.flatMap((one) => one.projectRead.held.symbols), withOutside.flatMap((one) => one.references), withOutside.flatMap((one) => one.projectRead.rooted.liveUnderReachability), withOutside.flatMap((one) => one.testFiles));
409
- const answers = withOutside.map((one) => {
410
+ const settled = settledSatisfaction(withOutside.map((one) => ({ evidence: one.evidence, held: one.projectRead.held })), [...withOutside.flatMap((one) => one.references), ...read.consumers.references]);
411
+ const answers = withOutside.map((one, index) => {
410
412
  const { projectRead } = one;
411
- const detected = heldRecords(one.evidence, {
413
+ const satisfied = settled[index];
414
+ const detected = heldRecords(satisfied?.evidence ?? one.evidence, {
412
415
  disabled,
413
416
  mode,
414
417
  testFiles: new Set([...one.testFiles, ...support]),
415
418
  });
416
419
  const exempt = detected.records;
417
- const made = supportReferences(one.references, support);
420
+ const made = supportReferences([...one.references, ...(satisfied?.references ?? [])], support);
418
421
  const held = new Set(projectRead.held.symbols.map((symbol) => symbol.position.path));
419
422
  const supportFiles = [...support].filter((path) => held.has(path)).sort();
420
423
  const configured = {
@@ -473,6 +476,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
473
476
  componentsUnread: read.componentsUnread,
474
477
  conventionsApplied: read.conventionsApplied,
475
478
  conventionUses: read.conventionUses,
479
+ memberDirs: read.memberDirs,
476
480
  };
477
481
  }
478
482
  /**
@@ -633,6 +637,25 @@ function readEmitterRun(engine, host, scope, config, mode, detectors) {
633
637
  ? run
634
638
  : { ...run, unansweredHeld: [...new Set([...run.unansweredHeld, ...unread])] };
635
639
  }
640
+ /**
641
+ * The workspace members whose manifest the run reads whole enough to judge its
642
+ * dependencies: a built project holds a file below the member's directory, and no derived
643
+ * configuration at or below it was dropped. A file no built project holds may import any
644
+ * dependency the member declares.
645
+ */
646
+ function membersWhollyRead(host, targetRoot, read) {
647
+ const root = resolvePath(host.workingDirectory(), targetRoot);
648
+ const held = new Set(read.matrix.union.symbols.map((symbol) => symbol.position.path));
649
+ const dropped = read.notBuilt.map((one) => relativePath(root, one.configFile) ?? "");
650
+ return read.memberDirs.filter((dir) => {
651
+ const below = relativePath(root, dir);
652
+ if (below === undefined) {
653
+ return false;
654
+ }
655
+ const inside = (path) => path.startsWith(`${below}/`);
656
+ return [...held].some(inside) && !dropped.some(inside);
657
+ });
658
+ }
636
659
  /**
637
660
  * The input every emitter reads over one sweep of a read run: the facts beside the sweep
638
661
  * each kind family reads, every per-project one read in the same session as the sweep.
@@ -649,7 +672,7 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
649
672
  references: one.configured.references,
650
673
  accessors: one.extra.accessors,
651
674
  })), swept.exempt, input.mode),
652
- dependencies: dependenciesOf(host, targetRoot, [...projects.map((one) => one.extra.needs), configurationNeeds(read.conventionUses)], swept.sweep.candidates.map((candidate) => candidate.id)),
675
+ dependencies: dependenciesOf(host, targetRoot, [...projects.map((one) => one.extra.needs), configurationNeeds(read.conventionUses)], swept.sweep.candidates.map((candidate) => candidate.id), membersWhollyRead(host, targetRoot, read)),
653
676
  implementations: mergeImplementations(projects.map((one) => one.extra.implementations)),
654
677
  files: {
655
678
  tree: sourceTree(host, targetRoot),
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The references a use of an augmentation's declaration makes to what holds it. No name
3
+ * spells a `declare global` block, so a use of a declaration it holds is a use of the
4
+ * block. An interface or namespace an augmentation block holds merges with the one of its
5
+ * name, which the language or a library may type a value by where the target never names
6
+ * it, as it types `import.meta` or `process.env`, so a use of its member is a use of it.
7
+ */
8
+ /** Whether one declaration is a `declare global` block: a module-level namespace named `global`. */
9
+ function isGlobalBlock(symbol, held, files) {
10
+ return (symbol.kind === "namespace" &&
11
+ files.has(symbol.parent) &&
12
+ symbol.ref.endsWith("#global") &&
13
+ !held.modules.has(symbol.id));
14
+ }
15
+ /**
16
+ * For each reference to a declaration an augmentation block holds, a read at the same place
17
+ * of every interface and namespace between it and the block, and of a `declare global` block
18
+ * itself.
19
+ */
20
+ export function augmentationReferences(held, found) {
21
+ const files = new Set(held.symbols.filter((one) => one.kind === "file").map((one) => one.id));
22
+ const globals = new Set(held.symbols.filter((one) => isGlobalBlock(one, held, files)).map((one) => one.id));
23
+ if (globals.size === 0 && held.modules.size === 0) {
24
+ return [];
25
+ }
26
+ const byId = new Map(held.symbols.map((one) => [one.id, one]));
27
+ const holders = (id) => {
28
+ const ids = [];
29
+ for (let at = byId.get(byId.get(id)?.parent ?? ""); at !== undefined; at = byId.get(at.parent)) {
30
+ if (globals.has(at.id)) {
31
+ return [...ids, at.id];
32
+ }
33
+ if (held.modules.has(at.id)) {
34
+ return ids;
35
+ }
36
+ if (at.kind === "interface" || at.kind === "namespace") {
37
+ ids.push(at.id);
38
+ }
39
+ }
40
+ return [];
41
+ };
42
+ return found.flatMap((reference) => holders(reference.to).map((to) => ({
43
+ from: reference.from,
44
+ to,
45
+ position: reference.position,
46
+ use: "read",
47
+ resolution: reference.resolution,
48
+ test: reference.test,
49
+ })));
50
+ }
@@ -25,13 +25,13 @@ export function listing(component, cascade) {
25
25
  }
26
26
  /**
27
27
  * Groups the declarations `dead` marks into components, ordered by their first member's
28
- * site; `dead` and `testOfDeadCode` hold one flag per declaration of the graph. Root
29
- * members sit on the cycles no other cycle references. A cycle two roots both reach is
30
- * dead only through both, so a component is a weakly connected part of the dead
31
- * subgraph: no dead declaration of one references a declaration of another.
28
+ * site; `dead`, `testOfDeadCode` and `unreferencedTest` hold one flag per declaration of
29
+ * the graph. Root members sit on the cycles no other cycle references. A cycle two roots
30
+ * both reach is dead only through both, so a component is a weakly connected part of the
31
+ * dead subgraph: no dead declaration of one references a declaration of another.
32
32
  */
33
- export function componentsOf(graph, dead, testOfDeadCode) {
34
- const { at, adjacent } = deadSubgraph(graph, dead, testOfDeadCode);
33
+ export function componentsOf(graph, dead, testOfDeadCode, unreferencedTest = []) {
34
+ const { at, adjacent } = deadSubgraph(graph, dead, testOfDeadCode, unreferencedTest);
35
35
  if (at.length === 0) {
36
36
  return [];
37
37
  }
@@ -90,17 +90,17 @@ function linesSpanned(symbols) {
90
90
  }
91
91
  /**
92
92
  * The dead declarations by their place in the subgraph, a test file's declaration only
93
- * where it is a test of dead code, and the edges between them: each
94
- * reference one makes to another, an edge each way between a dead member and its dead
95
- * container (one direction alone leaves the member a component the container reaches),
96
- * and an edge back from each production target of an admitted test, which puts the test in
97
- * the component of the code it exercises because a subject never references its test.
93
+ * where it is a test of dead code or nothing the tests reach, and the edges between them:
94
+ * each reference one makes to another, except an unreferenced test-file declaration's
95
+ * reference to production code, which decides nothing of its target, an edge each way
96
+ * between a dead member and its dead container, and an edge back from each production
97
+ * target of an admitted test, which puts the test in its subject's component.
98
98
  */
99
- function deadSubgraph(graph, dead, testOfDeadCode) {
99
+ function deadSubgraph(graph, dead, testOfDeadCode, unreferencedTest) {
100
100
  const at = [];
101
101
  const position = graph.symbols.map((_symbol, index) => {
102
- // A test file's declaration belongs to a component only as a test of dead code.
103
- if (dead[index] !== true || (graph.test[index] === true && testOfDeadCode[index] !== true)) {
102
+ const judged = testOfDeadCode[index] === true || unreferencedTest[index] === true;
103
+ if (dead[index] !== true || (graph.test[index] === true && !judged)) {
104
104
  return UNVISITED;
105
105
  }
106
106
  at.push(index);
@@ -110,7 +110,7 @@ function deadSubgraph(graph, dead, testOfDeadCode) {
110
110
  at.forEach((index, from) => {
111
111
  for (const edge of graph.out[index] ?? []) {
112
112
  const to = position[edge.to] ?? UNVISITED;
113
- if (to === UNVISITED) {
113
+ if (to === UNVISITED || (unreferencedTest[index] === true && graph.test[edge.to] !== true)) {
114
114
  continue;
115
115
  }
116
116
  adjacent[from]?.push(to);
@@ -2,9 +2,9 @@
2
2
  // Regenerate it with `UPDATE_GOLDEN=1 npx vitest --run src/conformance.corpus.test.ts`.
3
3
  export const RECORD = {
4
4
  conformance: {
5
- corpusVersion: "3.0.0",
5
+ corpusVersion: "4.0.0",
6
6
  result: "pass",
7
- digest: "sha256:db52e3164347cab70f45f438b4ac9ce89573be9303503daeb237a3c40361b001",
7
+ digest: "sha256:8250b61deb71467139bf4cbcaf10802cdabb1a2c2261a11a571fc1ff55bfc3dc",
8
8
  },
9
9
  gaps: [],
10
10
  };
@@ -32,6 +32,8 @@ const APP_DIRECTORY_ENTRIES = [
32
32
  const SOLIDSTART_PLUGIN = { module: "@solidjs/start/config", export: "solidStart" };
33
33
  const NUXT_CONFIG = [`nuxt.config.${ANY_MODULE}`];
34
34
  const STRYKER_CONFIG = "{stryker,.stryker}.{conf,config}.{json,js,mjs,cjs}";
35
+ const STRYKER_MODULE = "{stryker,.stryker}.{conf,config}.{js,mjs,cjs}";
36
+ const VITEST_CONFIG = `{vitest,vite}.config.${ANY_MODULE}`;
35
37
  /** Every row, ordered by name and then by range. */
36
38
  export const CONVENTION_ROWS = [
37
39
  {
@@ -146,6 +148,14 @@ export const CONVENTION_ROWS = [
146
148
  },
147
149
  ],
148
150
  },
151
+ {
152
+ name: "prettier",
153
+ package: "prettier",
154
+ range: ">=3.0.0 <4.0.0",
155
+ entries: [`{prettier.config,.prettierrc}.${ANY_MODULE}`],
156
+ moves: [],
157
+ manifestKeys: ["prettier"],
158
+ },
149
159
  {
150
160
  name: "qwik-city",
151
161
  package: "@builder.io/qwik-city",
@@ -263,8 +273,9 @@ export const CONVENTION_ROWS = [
263
273
  name: "stryker",
264
274
  package: "@stryker-mutator/core",
265
275
  range: ">=7.0.0 <11.0.0",
266
- entries: [],
276
+ entries: [STRYKER_MODULE],
267
277
  moves: [],
278
+ selections: [{ property: "mutate", files: [STRYKER_CONFIG] }],
268
279
  shortNames: [
269
280
  {
270
281
  property: "testRunner",
@@ -408,9 +419,13 @@ export const CONVENTION_ROWS = [
408
419
  shortNames: [
409
420
  {
410
421
  property: "test.coverage.provider",
411
- files: [`{vitest,vite}.config.${ANY_MODULE}`],
422
+ files: [VITEST_CONFIG],
412
423
  package: "@vitest/coverage-{}",
413
424
  },
414
425
  ],
426
+ selections: [
427
+ { property: "test.coverage.include", files: [VITEST_CONFIG] },
428
+ { property: "test.coverage.exclude", files: [VITEST_CONFIG] },
429
+ ],
415
430
  },
416
431
  ];
@@ -8,6 +8,7 @@
8
8
  import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isClassDeclaration, isComputedPropertyName, isExportAssignment, isExportDeclaration, isExpressionStatement, isFunctionDeclaration, isIdentifier, isImportDeclaration, isNamedExports, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNonNullExpression, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadAssignment, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/unstable/ast";
9
9
  import { lineOf } from "./component-files.js";
10
10
  import { CONVENTION_ROWS, } from "./convention-rows.js";
11
+ import { documentStrings } from "./configuration-files.js";
11
12
  import { globExpression } from "./glob.js";
12
13
  import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.js";
13
14
  import { satisfies } from "./version-range.js";
@@ -349,6 +350,74 @@ function readPath(object, keys, values, elements) {
349
350
  }
350
351
  return undefined;
351
352
  }
353
+ /**
354
+ * Every string literal one file writes at one property's path inside the objects it is
355
+ * read from, as {@link readProperty} reads them: the value itself, or each element of an
356
+ * array value, whatever else the path holds.
357
+ */
358
+ function stringsAt(file, property, call) {
359
+ const declared = declaredValues(file);
360
+ const objects = call === undefined
361
+ ? defaultExports(file).map((one) => objectOf(one, declared, true))
362
+ : optionsArguments(file, call).map((one) => one === undefined ? undefined : objectOf(one, declared, false));
363
+ const found = [];
364
+ const visit = (object, keys) => {
365
+ if (!isObjectLiteralExpression(object)) {
366
+ return;
367
+ }
368
+ const [key, ...rest] = keys;
369
+ for (const member of object.properties) {
370
+ if (!isPropertyAssignment(member) || keyOf(member) !== key) {
371
+ continue;
372
+ }
373
+ const held = unwrapped(member.initializer);
374
+ if (rest.length > 0) {
375
+ visit(held, rest);
376
+ }
377
+ else if (literalText(held) !== undefined) {
378
+ found.push(held);
379
+ }
380
+ else if (isArrayLiteralExpression(held)) {
381
+ found.push(...held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined));
382
+ }
383
+ }
384
+ };
385
+ for (const object of objects) {
386
+ if (object !== undefined) {
387
+ visit(object, property.split("."));
388
+ }
389
+ }
390
+ return found;
391
+ }
392
+ /** The strings an applied row's selection keys hold, added per configuration file to `into`. */
393
+ function selectedStrings(host, parse, dir, readings, into) {
394
+ for (const reading of readings) {
395
+ for (const { name, file } of readingFiles(host, parse, dir, reading.files)) {
396
+ const path = joinPath(dir, name);
397
+ const held = into.get(path) ?? { starts: new Set(), texts: [] };
398
+ for (const node of stringsAt(file, reading.property, reading.call)) {
399
+ held.starts.add(node.getStart());
400
+ held.texts.push(literalText(node) ?? "");
401
+ }
402
+ into.set(path, held);
403
+ }
404
+ }
405
+ }
406
+ /**
407
+ * The dependencies one manifest declares that a string at any depth of one of its members
408
+ * names, alone or followed by `/` and a subpath, a member's name aside.
409
+ */
410
+ function manifestKeyUses(manifest, keys) {
411
+ if (!isRecord(manifest)) {
412
+ return [];
413
+ }
414
+ const declared = DEPENDENCY_SECTIONS.flatMap((section) => {
415
+ const held = manifest[section];
416
+ return isRecord(held) ? Object.keys(held) : [];
417
+ });
418
+ const strings = keys.flatMap((key) => documentStrings(manifest[key]));
419
+ return declared.filter((name) => strings.some((text) => text === name || text.startsWith(`${name}/`)));
420
+ }
352
421
  /**
353
422
  * Every value one file writes at one property's path inside the object it is read from:
354
423
  * the options argument of every call `call` names, or the file's default export. A string
@@ -511,6 +580,7 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
511
580
  const generated = [];
512
581
  const failures = [];
513
582
  const uses = new Set();
583
+ const selected = new Map();
514
584
  for (const manifest of manifests) {
515
585
  const declared = readJSON(host, joinPath(manifest.dir, "package.json"));
516
586
  const versions = new Map();
@@ -571,6 +641,10 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
571
641
  for (const name of shortNameUses(host, parse, manifest.dir, declared, row.shortNames ?? [])) {
572
642
  uses.add(name);
573
643
  }
644
+ for (const name of manifestKeyUses(declared, row.manifestKeys ?? [])) {
645
+ uses.add(name);
646
+ }
647
+ selectedStrings(host, parse, manifest.dir, row.selections ?? [], selected);
574
648
  const excludes = [
575
649
  ...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
576
650
  ].map(globExpression);
@@ -591,7 +665,14 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
591
665
  unique.sort((a, b) => compare(a.name, b.name) ||
592
666
  compare(a.manifest, b.manifest) ||
593
667
  compare(JSON.stringify(a), JSON.stringify(b)));
594
- return { applied: unique, globs, generated, failures, uses: [...uses].sort(compare) };
668
+ return {
669
+ applied: unique,
670
+ globs,
671
+ generated,
672
+ failures,
673
+ uses: [...uses].sort(compare),
674
+ selected,
675
+ };
595
676
  }
596
677
  /**
597
678
  * Every own file of one project below a directory an applied row names as generated, by
@@ -1,15 +1,15 @@
1
1
  /**
2
- * The dependencies the target's manifest declares, and what the projects' own files
3
- * need from outside the target. A file needs a package it imports, re-exports from,
4
- * augments or references the types of, by name or through the file the specifier
5
- * resolves to; a compiler configuration needs one it names, and a configuration file a
6
- * tool loads needs each dependency its strings spell. A package one dependency pulls in
7
- * for itself is that dependency's need, not the target's.
2
+ * The dependencies the target's manifest and its workspace members' manifests declare, and
3
+ * what the projects' own files need from outside the target. A file needs a package it
4
+ * imports, re-exports from, augments or references the types of, by name or through the
5
+ * file the specifier resolves to; a compiler configuration needs one it names, and a
6
+ * configuration file a tool loads needs each dependency its strings spell. A package one
7
+ * dependency pulls in for itself is that dependency's need, not the target's.
8
8
  */
9
9
  import { isExportDeclaration, isExternalModuleReference, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isShorthandPropertyAssignment, isStringLiteral, isNoSubstitutionTemplateLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
10
10
  import { configurationFiles, declaredIn as declaredBeside, dependenciesNamed, moduleStrings, } from "./configuration-files.js";
11
11
  import { nodeKey, packageScope } from "./inventory.js";
12
- import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.js";
12
+ import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
13
13
  import { UNANSWERED } from "./query.js";
14
14
  import { sourceFilesOf } from "./source-files.js";
15
15
  const MANIFEST = "package.json";
@@ -476,8 +476,8 @@ function installedOf(host, root, name) {
476
476
  }
477
477
  }
478
478
  }
479
- /** Every dependency the manifest text declares, in the order the text writes them. */
480
- function declaredIn(text, manifest) {
479
+ /** Every dependency the manifest text at `path` declares, in the order the text writes them. */
480
+ function declaredIn(text, manifest, path, scope) {
481
481
  const sections = membersAt(text, skipWhitespace(text, 0));
482
482
  const declared = [];
483
483
  for (const [member, section] of SECTIONS) {
@@ -489,7 +489,12 @@ function declaredIn(text, manifest) {
489
489
  for (const [name, { key }] of membersAt(text, held.value)) {
490
490
  const { line, column } = lineAndColumn(text, key);
491
491
  declared.push({
492
- dependency: { name, section, position: { path: MANIFEST, line, column, endLine: line } },
492
+ dependency: {
493
+ name,
494
+ section,
495
+ position: { path, line, column, endLine: line },
496
+ manifest: scope,
497
+ },
493
498
  at: key,
494
499
  });
495
500
  }
@@ -497,25 +502,36 @@ function declaredIn(text, manifest) {
497
502
  return declared.sort((a, b) => a.at - b.at).map(({ dependency }) => dependency);
498
503
  }
499
504
  /**
500
- * The run's dependency evidence: the target's manifest read with the position of every
501
- * dependency it declares, the installed copy of each, what the projects need, and the
502
- * last uses each deletion candidate holds.
503
- *
504
- * Only the manifest at the target root is read, as for entry points. A manifest the
505
+ * The run's dependency evidence: the target's manifest and each workspace member's in
506
+ * `members` below the target root, read with the position of every dependency each
507
+ * declares, the installed copy of each found from its manifest's directory, what the
508
+ * projects need, and the last uses each deletion candidate holds. A manifest the
505
509
  * program cannot read declares nothing.
506
510
  */
507
- export function dependenciesOf(host, targetRoot, perProject, candidates) {
508
- const read = readJSON(host, joinPath(targetRoot, MANIFEST));
509
- const manifest = isRecord(read?.value) ? read.value : undefined;
510
- const scope = packageScope(host, targetRoot)(MANIFEST);
511
- const declared = read !== undefined && manifest !== undefined ? declaredIn(read.text, manifest) : [];
511
+ export function dependenciesOf(host, targetRoot, perProject, candidates, members = []) {
512
512
  const root = resolvePath(host.workingDirectory(), targetRoot);
513
+ const scopeOf = packageScope(host, targetRoot);
514
+ const manifests = new Map([[root, MANIFEST]]);
515
+ for (const dir of members) {
516
+ const below = relativePath(root, dir);
517
+ if (below !== undefined && !manifests.has(dir)) {
518
+ manifests.set(dir, `${below}/${MANIFEST}`);
519
+ }
520
+ }
521
+ const declared = [];
513
522
  const installed = new Map();
514
- for (const dependency of declared) {
515
- if (!installed.has(dependency.name)) {
516
- const found = installedOf(host, root, dependency.name);
517
- if (found !== undefined) {
518
- installed.set(dependency.name, found);
523
+ for (const [dir, path] of manifests) {
524
+ const read = readJSON(host, joinPath(dir, MANIFEST));
525
+ if (read === undefined || !isRecord(read.value)) {
526
+ continue;
527
+ }
528
+ for (const dependency of declaredIn(read.text, read.value, path, scopeOf(path))) {
529
+ declared.push(dependency);
530
+ if (!installed.has(dependency.name)) {
531
+ const found = installedOf(host, dir, dependency.name);
532
+ if (found !== undefined) {
533
+ installed.set(dependency.name, found);
534
+ }
519
535
  }
520
536
  }
521
537
  }
@@ -547,5 +563,5 @@ export function dependenciesOf(host, targetRoot, perProject, candidates) {
547
563
  lastUses.set(id, sole.sort((a, b) => (a < b ? -1 : 1)));
548
564
  }
549
565
  }
550
- return { manifest: scope, declared, installed, needed, lastUses };
566
+ return { declared, installed, needed, lastUses };
551
567
  }
@@ -17,10 +17,9 @@ import { globExpression } from "./glob.js";
17
17
  import { htmlEntries } from "./html-entries.js";
18
18
  import { dirnamePath, relativePath, resolvePath } from "./paths.js";
19
19
  import { UNANSWERED } from "./query.js";
20
+ import { workflowEntries } from "./workflow-steps.js";
20
21
  /** The extensions the test runner and the browser-test runner load a configuration from. */
21
22
  const MODULE_EXTENSIONS = ["js", "mjs", "cjs", "ts", "mts", "cts"];
22
- /** The extensions the mutation tester's configuration is loaded from. */
23
- const SCRIPT_EXTENSIONS = ["js", "mjs", "cjs"];
24
23
  /** The tool file names the configuration form does not cover. */
25
24
  const CONVENTIONS = [
26
25
  {
@@ -29,12 +28,6 @@ const CONVENTIONS = [
29
28
  extensions: MODULE_EXTENSIONS,
30
29
  testRunner: true,
31
30
  },
32
- {
33
- rule: "mutation-testing",
34
- stems: ["stryker.conf", ".stryker.config", ".stryker.conf"],
35
- extensions: SCRIPT_EXTENSIONS,
36
- testRunner: false,
37
- },
38
31
  ];
39
32
  /** The stem of the browser-test runner's configuration, whose test directory is read. */
40
33
  const BROWSER_TEST_CONFIG = "playwright.config";
@@ -168,11 +161,29 @@ function isBrowserTestConfig(name) {
168
161
  /**
169
162
  * The files the configuration files of one project enter: each configuration module,
170
163
  * with what it exports, and each own file a relative string of a configuration module
171
- * or a JSON configuration file names, read against that file's directory.
164
+ * or a JSON configuration file names, read against that file's directory. A string at a
165
+ * selection key of an applied convention row names no file.
172
166
  */
173
- function configurationEntries(host, targetRoot, files, parse) {
167
+ function configurationEntries(host, targetRoot, files, parse, selected) {
174
168
  const { modules, documents, outside } = configurationFiles(host, targetRoot, files, parse);
175
169
  const found = [];
170
+ const unselected = (file) => {
171
+ const starts = selected.get(file.fileName)?.starts;
172
+ return moduleStrings(file)
173
+ .filter((one) => starts?.has(one.node.getStart()) !== true)
174
+ .map((one) => one.text);
175
+ };
176
+ const unselectedIn = (path, texts) => {
177
+ const left = [...(selected.get(path)?.texts ?? [])];
178
+ return texts.filter((text) => {
179
+ const at = left.indexOf(text);
180
+ if (at < 0) {
181
+ return true;
182
+ }
183
+ left.splice(at, 1);
184
+ return false;
185
+ });
186
+ };
176
187
  const strings = (dir, texts) => {
177
188
  for (const text of texts) {
178
189
  for (const file of filesNamedBy(files, dir, text)) {
@@ -182,10 +193,10 @@ function configurationEntries(host, targetRoot, files, parse) {
182
193
  };
183
194
  for (const { file, form } of modules) {
184
195
  found.push({ file, rule: "configuration-file", source: form, exports: true });
185
- strings(dirnamePath(file.fileName), moduleStrings(file).map((one) => one.text));
196
+ strings(dirnamePath(file.fileName), unselected(file));
186
197
  }
187
198
  for (const document of documents) {
188
- strings(dirnamePath(document.path), document.strings);
199
+ strings(dirnamePath(document.path), unselectedIn(document.path, document.strings));
189
200
  }
190
201
  for (const { file } of outside) {
191
202
  strings(dirnamePath(file.fileName), moduleStrings(file).map((one) => one.text));
@@ -385,8 +396,8 @@ function workersOf(project, file, files) {
385
396
  * file's path below the target root; a file one of them matches is a test file the
386
397
  * runner executes.
387
398
  */
388
- export function entryPoints(project, files, testFiles, host, targetRoot, parse) {
389
- const found = [...configurationEntries(host, targetRoot, files, parse)];
399
+ export function entryPoints(project, files, testFiles, host, targetRoot, parse, selected = new Map()) {
400
+ const found = [...configurationEntries(host, targetRoot, files, parse, selected)];
390
401
  const tests = testFiles.map((pattern) => ({ pattern, expression: globExpression(pattern) }));
391
402
  for (const [path, file] of files.byPath) {
392
403
  const name = basename(path);
@@ -410,5 +421,8 @@ export function entryPoints(project, files, testFiles, host, targetRoot, parse)
410
421
  for (const { file, source } of htmlEntries(host, targetRoot, files, parse)) {
411
422
  found.push({ file, rule: "html-entry", source, exports: true });
412
423
  }
424
+ for (const { file, source } of workflowEntries(host, targetRoot, files)) {
425
+ found.push({ file, rule: "workflow-step", source, exports: true });
426
+ }
413
427
  return found;
414
428
  }
@@ -117,6 +117,7 @@ export function evidenceRecords(input, detectors, disabled) {
117
117
  detail: evidence.detail,
118
118
  site: evidence.site,
119
119
  ...(evidence.whileLive === undefined ? {} : { whileLive: evidence.whileLive }),
120
+ ...(evidence.implementing === undefined ? {} : { implementing: evidence.implementing }),
120
121
  });
121
122
  }
122
123
  }
@@ -30,7 +30,7 @@ function neededBy(dependencies) {
30
30
  return needed;
31
31
  }
32
32
  /**
33
- * A dependency, development dependency or peer dependency of the target's manifest
33
+ * A dependency, development dependency or peer dependency of a manifest the run reads
34
34
  * that the target does not need, at the position of its key.
35
35
  */
36
36
  function unusedDependencies(dependencies) {
@@ -41,7 +41,7 @@ function unusedDependencies(dependencies) {
41
41
  code: UNUSED_DEPENDENCY,
42
42
  position: dependency.position,
43
43
  symbol: {
44
- ref: renderRef(dependencies.manifest, {
44
+ ref: renderRef(dependency.manifest, {
45
45
  of: "dependency",
46
46
  name: dependency.name,
47
47
  section: dependency.section,