@cplieger/deadset-ts 7.0.0-dev.2 → 7.0.0-dev.4

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 (137) hide show
  1. package/README.md +1 -1
  2. package/dist/src/alias-chain.js +2 -2
  3. package/dist/src/analysis.js +89 -44
  4. package/dist/src/augmentations.js +14 -12
  5. package/dist/src/calls.js +2 -2
  6. package/dist/src/class-members.js +1 -1
  7. package/dist/src/components.js +62 -20
  8. package/dist/src/configuration-files.js +6 -6
  9. package/dist/src/configured-declarations.js +2 -2
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/contextual-uses.js +71 -14
  12. package/dist/src/convention-rows.js +6 -2
  13. package/dist/src/conventions.js +4 -2
  14. package/dist/src/dependencies.js +81 -27
  15. package/dist/src/deprecation.js +1 -1
  16. package/dist/src/destructuring.js +2 -2
  17. package/dist/src/discover.js +1 -1
  18. package/dist/src/element-access.js +341 -0
  19. package/dist/src/entry-points.js +2 -2
  20. package/dist/src/enum-group.js +2 -2
  21. package/dist/src/erased-stores.js +317 -0
  22. package/dist/src/findings/completion.js +46 -15
  23. package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
  24. package/dist/src/findings/emitters.js +1 -1
  25. package/dist/src/findings/self-check.js +17 -4
  26. package/dist/src/findings-pass.js +6 -5
  27. package/dist/src/framework-lifecycle.js +1 -1
  28. package/dist/src/html-entries.js +1 -1
  29. package/dist/src/implementations.js +2 -2
  30. package/dist/src/in-operands.js +30 -0
  31. package/dist/src/indexed-access.js +264 -125
  32. package/dist/src/injection-container.js +1 -1
  33. package/dist/src/interface-satisfaction.js +2 -2
  34. package/dist/src/intra-function-parts.js +26 -12
  35. package/dist/src/inventory.js +26 -1
  36. package/dist/src/keyof-comparisons.js +170 -0
  37. package/dist/src/kinds.js +17 -7
  38. package/dist/src/manifest.js +6 -16
  39. package/dist/src/matrix.js +20 -2
  40. package/dist/src/member-names.js +1 -1
  41. package/dist/src/module-declarations.js +1 -1
  42. package/dist/src/namespace-objects.js +1 -1
  43. package/dist/src/outside-tests.js +1 -1
  44. package/dist/src/overrides.js +2 -2
  45. package/dist/src/query.js +4 -1
  46. package/dist/src/references.js +13 -2
  47. package/dist/src/reflective-lookup.js +2 -2
  48. package/dist/src/report.js +1 -1
  49. package/dist/src/reporters/sarif.js +4 -0
  50. package/dist/src/roots.js +1 -1
  51. package/dist/src/serialization-contract.js +45 -16
  52. package/dist/src/session.js +15 -8
  53. package/dist/src/signatures.js +1 -1
  54. package/dist/src/stores.js +1 -1
  55. package/dist/src/suppress-file.js +58 -6
  56. package/dist/src/suppress-inline.js +14 -5
  57. package/dist/src/suppress.js +54 -24
  58. package/dist/src/symbol-keys.js +1 -1
  59. package/dist/src/type-errors.js +1 -1
  60. package/dist/src/type-query-alias.js +1 -1
  61. package/dist/src/unimplemented-callees.js +312 -0
  62. package/dist/src/value-flow.js +1 -1
  63. package/dist/src/value-reach.js +8 -8
  64. package/dist/src/verbs/analyze.js +11 -9
  65. package/dist/src/workspace-programs.js +2 -2
  66. package/jsr.json +1 -1
  67. package/package.json +3 -3
  68. package/src/alias-chain.ts +2 -2
  69. package/src/analysis.ts +135 -52
  70. package/src/augmentations.ts +14 -11
  71. package/src/calls.ts +2 -2
  72. package/src/class-members.ts +2 -2
  73. package/src/component-layer.ts +1 -1
  74. package/src/components.ts +103 -21
  75. package/src/configuration-files.ts +6 -5
  76. package/src/configured-declarations.ts +2 -6
  77. package/src/conformance-record.ts +1 -1
  78. package/src/contextual-uses.ts +82 -18
  79. package/src/convention-rows.ts +6 -2
  80. package/src/conventions.ts +11 -2
  81. package/src/decorator.ts +1 -1
  82. package/src/dependencies.ts +108 -31
  83. package/src/deprecation.ts +1 -1
  84. package/src/destructuring.ts +2 -2
  85. package/src/discover.ts +2 -2
  86. package/src/element-access.ts +446 -0
  87. package/src/entry-points.ts +2 -2
  88. package/src/enum-group.ts +2 -2
  89. package/src/erased-stores.ts +413 -0
  90. package/src/file-facts.ts +3 -1
  91. package/src/findings/completion.ts +54 -20
  92. package/src/findings/dependencies-and-module-machinery.ts +1 -1
  93. package/src/findings/emitters.ts +1 -1
  94. package/src/findings/self-check.ts +18 -4
  95. package/src/findings-pass.ts +12 -5
  96. package/src/framework-lifecycle.ts +2 -2
  97. package/src/html-entries.ts +1 -1
  98. package/src/implementations.ts +2 -2
  99. package/src/in-operands.ts +45 -0
  100. package/src/indexed-access.ts +304 -146
  101. package/src/injection-container.ts +1 -1
  102. package/src/interface-satisfaction.ts +2 -2
  103. package/src/intra-function-parts.ts +42 -17
  104. package/src/inventory.ts +34 -2
  105. package/src/keyof-comparisons.ts +211 -0
  106. package/src/kinds.ts +20 -7
  107. package/src/manifest.ts +6 -21
  108. package/src/matrix.ts +34 -2
  109. package/src/member-names.ts +1 -1
  110. package/src/module-declarations.ts +1 -1
  111. package/src/namespace-objects.ts +1 -1
  112. package/src/outside-tests.ts +1 -1
  113. package/src/overrides.ts +2 -2
  114. package/src/position.ts +1 -1
  115. package/src/query.ts +12 -2
  116. package/src/references.ts +25 -3
  117. package/src/reflective-lookup.ts +2 -2
  118. package/src/report.ts +3 -1
  119. package/src/reporters/sarif.ts +4 -0
  120. package/src/roots.ts +2 -2
  121. package/src/serialization-contract.ts +55 -16
  122. package/src/session.ts +39 -32
  123. package/src/signatures.ts +1 -1
  124. package/src/source-files.ts +1 -1
  125. package/src/stores.ts +1 -1
  126. package/src/suppress-file.ts +68 -5
  127. package/src/suppress-inline.ts +15 -5
  128. package/src/suppress.ts +63 -28
  129. package/src/symbol-keys.ts +2 -2
  130. package/src/type-errors.ts +2 -2
  131. package/src/type-query-alias.ts +1 -1
  132. package/src/unimplemented-callees.ts +411 -0
  133. package/src/value-flow.ts +1 -1
  134. package/src/value-reach.ts +12 -8
  135. package/src/verbs/analyze.ts +15 -9
  136. package/src/workflow-steps.ts +1 -1
  137. package/src/workspace-programs.ts +4 -4
@@ -6,8 +6,8 @@
6
6
  * the parameters and results of functions and constructors too; a class instance there is
7
7
  * the interface-satisfaction class's evidence instead.
8
8
  */
9
- import { isComputedPropertyName, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isPropertyAssignment, isSetAccessorDeclaration, isShorthandPropertyAssignment, isSpreadAssignment, isStringLiteral, } from "@typescript/native/unstable/ast";
10
- import { SignatureKind, SymbolFlags } from "@typescript/native/unstable/sync";
9
+ import { isClassDeclaration, isClassExpression, isComputedPropertyName, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isMethodSignatureDeclaration, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isPropertyAssignment, isSetAccessorDeclaration, isShorthandPropertyAssignment, isSpreadAssignment, isStringLiteral, } from "@typescript/native/ast";
10
+ import { SignatureKind } from "@typescript/native/sync";
11
11
  import { isAnswered } from "./query.js";
12
12
  /** The member kinds a contextual type writes and reads: data members and the members of object types. */
13
13
  const MEMBER_KINDS = new Set(["type-member", "class-member"]);
@@ -36,6 +36,20 @@ function propertyName(property) {
36
36
  }
37
37
  return undefined;
38
38
  }
39
+ /**
40
+ * Whether an object spread copies a member a declaration declares: a method, a method
41
+ * signature and a class's accessor are no own data property of the value, while an
42
+ * accessor of an interface or a type literal describes one. It is decided on the
43
+ * declaration, because a mapped type over a class keeps the class's declarations on
44
+ * plain property symbols.
45
+ */
46
+ function copiedBySpread(node) {
47
+ if (isMethodDeclaration(node) || isMethodSignatureDeclaration(node)) {
48
+ return false;
49
+ }
50
+ return !((isGetAccessorDeclaration(node) || isSetAccessorDeclaration(node)) &&
51
+ (isClassDeclaration(node.parent) || isClassExpression(node.parent)));
52
+ }
39
53
  /**
40
54
  * The contextual uses of one project's literals and values, asked in batches: one
41
55
  * contextual type per literal and per value whose type carries a member of the target,
@@ -85,9 +99,12 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
85
99
  ids: chains
86
100
  .declarationsOf(property)
87
101
  .filter((id) => MEMBER_KINDS.has(kinds.get(id) ?? "file")),
102
+ methods: chains
103
+ .declarationsOf(property)
104
+ .filter((id) => kinds.get(id) === "interface-method"),
88
105
  handles: new Set(property.declarations.map((handle) => `${handle.path}#${String(handle.index)}`)),
89
106
  own: property.declarations.some((handle) => ownPaths.has(handle.path)),
90
- accessor: (property.flags & SymbolFlags.Accessor) !== 0,
107
+ declarations: property.declarations.filter((handle) => ownPaths.has(handle.path)),
91
108
  });
92
109
  }
93
110
  tables.set(type.id, table);
@@ -137,7 +154,10 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
137
154
  batches += batchesOf(carried.length);
138
155
  const types = queries.typesAt(carried.map((flow) => flow.value), cap);
139
156
  /** The members a position may read structurally: those of object types, not of classes. */
140
- const readable = (member) => member.ids.filter((id) => kinds.get(id) === "type-member");
157
+ const readable = (member) => [
158
+ ...member.ids.filter((id) => kinds.get(id) === "type-member"),
159
+ ...member.methods,
160
+ ];
141
161
  const carries = (part) => [...membersOf(part).values()].some((member) => readable(member).length > 0);
142
162
  /** Whether an object type has members and declares none of them in the target. */
143
163
  const outside = (part) => {
@@ -161,6 +181,19 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
161
181
  elements.set(type.id, element);
162
182
  return element;
163
183
  };
184
+ const argumentsOf = new Map();
185
+ /** The type arguments of an instantiation of a generic type, read once per type. */
186
+ const typeArgumentsOf = (type) => {
187
+ const known = argumentsOf.get(type.id);
188
+ if (known !== undefined) {
189
+ return known;
190
+ }
191
+ lookups += type.isTypeReference() ? 1 : 0;
192
+ const held = queries.typeArguments(type);
193
+ const found = isAnswered(held) ? held : [];
194
+ argumentsOf.set(type.id, found);
195
+ return found;
196
+ };
164
197
  const calls = new Map();
165
198
  /**
166
199
  * The parameter and return types of a type's one call signature, or of its one construct
@@ -195,10 +228,11 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
195
228
  };
196
229
  /**
197
230
  * Each object type a value of `source` is checked as against the object types of
198
- * `target`: the value's own, and through array elements and through a function's
199
- * parameters, checked the other way, and its result. A source that is one of the
200
- * target's own object types matches it as it stands. Each pair is compared once, which
201
- * ends the walk over a recursive type.
231
+ * `target`: the value's own, and through array elements, a function's parameters,
232
+ * checked the other way, and result, and the type arguments two instantiations share
233
+ * by place, an array among them, as an array reaches `Iterable<T>`.
234
+ * A source that is one of the target's own object types matches it as it stands. Each
235
+ * pair is compared once, which ends the walk over a recursive type.
202
236
  */
203
237
  const compared = (source, target, into, seen) => {
204
238
  const key = `${String(source?.id)}>${String(target?.id)}`;
@@ -220,9 +254,20 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
220
254
  }
221
255
  const element = elementOf(one);
222
256
  const called = element === undefined ? callOf(one) : undefined;
257
+ const sourceArguments = typeArgumentsOf(one);
223
258
  for (const part of targets) {
259
+ const partElement = elementOf(part);
260
+ if (element !== undefined && partElement !== undefined) {
261
+ compared(element, partElement, into, seen);
262
+ continue;
263
+ }
264
+ const targetArguments = typeArgumentsOf(part);
265
+ // A defaulted trailing parameter, as `Iterable<T, TReturn, TNext>` has, leaves
266
+ // the places two instantiations share comparable.
267
+ sourceArguments.slice(0, targetArguments.length).forEach((argument, index) => {
268
+ compared(argument, targetArguments[index], into, seen);
269
+ });
224
270
  if (element !== undefined) {
225
- compared(element, elementOf(part), into, seen);
226
271
  continue;
227
272
  }
228
273
  const other = called === undefined ? undefined : callOf(part);
@@ -253,7 +298,7 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
253
298
  if (element !== undefined) {
254
299
  return mayCarry(element, seen);
255
300
  }
256
- return callOf(part) !== undefined;
301
+ return callOf(part) !== undefined || typeArgumentsOf(part).some((one) => mayCarry(one, seen));
257
302
  });
258
303
  };
259
304
  const sourced = carried.flatMap((flow, index) => {
@@ -302,6 +347,19 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
302
347
  reads.push({ at: flow.value, from: flow.from, ids: [...ids] });
303
348
  }
304
349
  });
350
+ const copies = new Map();
351
+ /** Whether a spread copies the member: where any declaration of it declares data. */
352
+ const copied = (member) => {
353
+ let known = copies.get(member);
354
+ if (known === undefined) {
355
+ known = member.declarations.some((handle) => {
356
+ const node = project.declarationAt(handle)?.node;
357
+ return node === undefined || copiedBySpread(node);
358
+ });
359
+ copies.set(member, known);
360
+ }
361
+ return known;
362
+ };
305
363
  const spreads = literals.flatMap(({ literal, from }) => literal.properties.flatMap((property) => isSpreadAssignment(property) ? [{ value: property.expression, from }] : []));
306
364
  batches += batchesOf(spreads.length);
307
365
  const spreadTypes = queries.typesAt(spreads.map((one) => one.value), cap);
@@ -310,11 +368,10 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
310
368
  if (type === undefined || !isAnswered(type)) {
311
369
  return;
312
370
  }
313
- // A spread gets every own enumerable property, an overwritten key's getter included.
314
- // So it reads each data member but a class's accessor, which sits on the prototype with
315
- // the class's methods (never data members), and a private name, which is no property
371
+ // A spread gets every own enumerable property, an overwritten key's getter included,
372
+ // and no private name, which is no property
316
373
  // (https://tc39.es/ecma262/#sec-copydataproperties).
317
- const own = (member, id) => !privateNames.has(id) && !(member.accessor && kinds.get(id) === "class-member");
374
+ const own = (member, id) => !privateNames.has(id) && copied(member);
318
375
  const ids = [
319
376
  ...new Set(partsOf(type).flatMap((part) => [...membersOf(part).values()].flatMap((member) => member.ids.filter((id) => own(member, id))))),
320
377
  ];
@@ -41,7 +41,8 @@ const STRYKER_MODULE = "{stryker,.stryker}.{conf,config}.{js,mjs,cjs}";
41
41
  const VITEST_CONFIG = `{vitest,vite}.config.${ANY_MODULE}`;
42
42
  /**
43
43
  * The properties Vitest takes its project root from, `test.root` before Vite's `root`; it
44
- * resolves `setupFiles` against that root (https://vitest.dev/config/setupfiles).
44
+ * resolves `setupFiles` and `globalSetup` against that root
45
+ * (https://vitest.dev/config/setupfiles, https://vitest.dev/config/globalsetup).
45
46
  */
46
47
  const VITEST_ROOTS = ["test.root", "root"];
47
48
  /** The entry files of an Angular CLI project, at the workspace root or below `projects`. */
@@ -540,6 +541,9 @@ export const CONVENTION_ROWS = [
540
541
  { property: "test.coverage.include", files: [VITEST_CONFIG] },
541
542
  { property: "test.coverage.exclude", files: [VITEST_CONFIG] },
542
543
  ],
543
- testFiles: [{ property: "test.setupFiles", files: [VITEST_CONFIG], roots: VITEST_ROOTS }],
544
+ testFiles: [
545
+ { property: "test.setupFiles", files: [VITEST_CONFIG], roots: VITEST_ROOTS },
546
+ { property: "test.globalSetup", files: [VITEST_CONFIG], roots: VITEST_ROOTS },
547
+ ],
544
548
  },
545
549
  ];
@@ -5,7 +5,7 @@
5
5
  * directory a configuration property moves read from the property's literal value.
6
6
  * No configuration file is run.
7
7
  */
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";
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/ast";
9
9
  import { lineOf } from "./component-files.js";
10
10
  import { CONVENTION_ROWS, } from "./convention-rows.js";
11
11
  import { documentStrings } from "./configuration-files.js";
@@ -707,7 +707,9 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
707
707
  }
708
708
  const version = versions.get(row.package);
709
709
  if (version === undefined) {
710
- if (missing.has(row.package) || !declares(declared, row.package, REQUIRED_SECTIONS)) {
710
+ if (missing.has(row.package) ||
711
+ manifest.nested === true ||
712
+ !declares(declared, row.package, REQUIRED_SECTIONS)) {
711
713
  continue;
712
714
  }
713
715
  missing.add(row.package);
@@ -6,7 +6,7 @@
6
6
  * file a tool loads needs each dependency its strings spell. A package one dependency pulls in for
7
7
  * itself is that dependency's need, not the target's.
8
8
  */
9
- import { isCallExpression, isExportDeclaration, isExternalModuleReference, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isMetaProperty, isNamespaceExport, isPropertyAccessExpression, isShorthandPropertyAssignment, isStringLiteral, isNoSubstitutionTemplateLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
9
+ import { isCallExpression, isExportDeclaration, isExternalModuleReference, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isMetaProperty, isNamespaceExport, isPropertyAccessExpression, isShorthandPropertyAssignment, isStringLiteral, isNoSubstitutionTemplateLiteral, SyntaxKind, } from "@typescript/native/ast";
10
10
  import { configurationFiles, declaredIn as declaredBeside, dependenciesNamed, moduleStrings, } from "./configuration-files.js";
11
11
  import { nodeKey, packageScope } from "./inventory.js";
12
12
  import { scriptTokens } from "./manifest.js";
@@ -29,11 +29,13 @@ const PROJECT_OWNER = "";
29
29
  /** What a configuration's own reading needs: packages no deletion of a declaration removes. */
30
30
  export function configurationNeeds(packages) {
31
31
  return {
32
- packages: new Set(packages),
33
32
  uses: new Map([[PROJECT_OWNER, new Set(packages)]]),
33
+ at: new Map([[ANYWHERE, new Set(packages)]]),
34
34
  unanswered: [],
35
35
  };
36
36
  }
37
+ /** The key of {@link ProjectNeeds.at} for a use no file of the target governs. */
38
+ const ANYWHERE = "";
37
39
  function isRecord(value) {
38
40
  return typeof value === "object" && value !== null && !Array.isArray(value);
39
41
  }
@@ -207,9 +209,10 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
207
209
  }
208
210
  const packages = configurationPackages(host, project.configFile, project.program.getCompilerOptions(), programPackages);
209
211
  const uses = new Map();
210
- const use = (owners, named) => {
212
+ const at = new Map();
213
+ const use = (owners, named, path) => {
211
214
  for (const one of named) {
212
- packages.add(one);
215
+ at.set(path, (at.get(path) ?? new Set()).add(one));
213
216
  for (const owner of owners) {
214
217
  let set = uses.get(owner);
215
218
  if (set === undefined) {
@@ -220,10 +223,13 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
220
223
  }
221
224
  }
222
225
  };
223
- use([PROJECT_OWNER], packages);
226
+ use([PROJECT_OWNER], packages, project.configFile);
224
227
  const unanswered = [];
225
228
  for (const file of project.ownSourceFiles()) {
226
- if (fileNeeds(project, held, file, programPackages, use)) {
229
+ const inFile = (owners, named) => {
230
+ use(owners, named, file.fileName);
231
+ };
232
+ if (fileNeeds(project, held, file, programPackages, inFile)) {
227
233
  unanswered.push(file.fileName);
228
234
  }
229
235
  }
@@ -241,14 +247,14 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
241
247
  for (const { file } of configuration.modules) {
242
248
  const names = declaredAt(file.fileName);
243
249
  for (const { node, text } of moduleStrings(file)) {
244
- use(ownersOf(file, held, node), dependenciesNamed(text, names));
250
+ use(ownersOf(file, held, node), dependenciesNamed(text, names), file.fileName);
245
251
  }
246
252
  }
247
253
  for (const { strings, manifestDirs } of configuration.documents) {
248
254
  for (const manifestDir of manifestDirs) {
249
255
  const names = declaredAt(joinPath(manifestDir, "package.json"));
250
256
  for (const text of strings) {
251
- use([PROJECT_OWNER], dependenciesNamed(text, names));
257
+ use([PROJECT_OWNER], dependenciesNamed(text, names), joinPath(manifestDir, MANIFEST));
252
258
  }
253
259
  }
254
260
  }
@@ -256,11 +262,11 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
256
262
  for (const manifestDir of manifestDirs) {
257
263
  const names = declaredAt(joinPath(manifestDir, "package.json"));
258
264
  for (const { text } of moduleStrings(file)) {
259
- use([PROJECT_OWNER], dependenciesNamed(text, names));
265
+ use([PROJECT_OWNER], dependenciesNamed(text, names), joinPath(manifestDir, MANIFEST));
260
266
  }
261
267
  }
262
268
  }
263
- return { packages, uses, unanswered };
269
+ return { uses, at, unanswered };
264
270
  }
265
271
  /** The declarations of the inventory a use written at one node belongs to. */
266
272
  function ownersOf(file, held, node) {
@@ -496,8 +502,10 @@ function installedOf(host, root, name) {
496
502
  const read = readJSON(host, joinPath(dir, MODULES_DIR, name, MANIFEST));
497
503
  if (read !== undefined && isRecord(read.value)) {
498
504
  const bin = read.value["bin"];
505
+ const peers = read.value["peerDependencies"];
499
506
  // A string `bin` names one command, the package's name without its scope.
500
507
  return {
508
+ peers: isRecord(peers) ? Object.keys(peers) : [],
501
509
  commands: typeof bin === "string"
502
510
  ? bin === ""
503
511
  ? []
@@ -558,20 +566,49 @@ function scriptTokensOf(manifest) {
558
566
  function isInside(path, dir) {
559
567
  return dir === "" || path.startsWith(`${dir}/`);
560
568
  }
569
+ /** The directory below the target root a declared dependency's manifest is in, `""` being the root. */
570
+ function manifestDir(dependency) {
571
+ const dir = dirnamePath(dependency.position.path);
572
+ return dir === "." ? "" : dir;
573
+ }
561
574
  /**
562
- * The run's dependency evidence: the target's manifest and each workspace member's in
563
- * `members` below the target root, read with the position of every dependency each
564
- * declares, what the projects and the commands its scripts and workflow steps run need,
565
- * and the last uses each deletion candidate holds. A member in `unbuilt` holds source only
566
- * in configurations the run could not build. A manifest the program cannot read declares
567
- * nothing.
575
+ * Adds to `needed` each declared dependency that the installed manifest of a needed or
576
+ * run declared dependency names as a peer, until no further one joins: the declaration of
577
+ * that name in the needing dependency's manifest or in a manifest above it, where the
578
+ * installed peer is resolved from. `peers` holds each declaration's installed peers by
579
+ * {@link declarationKey}.
568
580
  */
569
- export function dependenciesOf(host, targetRoot, perProject, candidates, members = [], unbuiltMembers = []) {
581
+ function addNeededPeers(declared, peers, needed, ran) {
582
+ const pending = declared.filter((dependency) => needed.has(declarationKey(dependency)) || ran.has(declarationKey(dependency)));
583
+ for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
584
+ const below = manifestDir(next);
585
+ for (const peer of peers.get(declarationKey(next)) ?? []) {
586
+ for (const dependency of declared) {
587
+ const key = declarationKey(dependency);
588
+ if (dependency.name === peer &&
589
+ !needed.has(key) &&
590
+ (below === manifestDir(dependency) || isInside(below, manifestDir(dependency)))) {
591
+ needed.add(key);
592
+ pending.push(dependency);
593
+ }
594
+ }
595
+ }
596
+ }
597
+ }
598
+ /**
599
+ * The run's dependency evidence over the target's manifest and those of `members` and
600
+ * `nested` packages below the root: each declared dependency's position, what the projects,
601
+ * scripts and workflow steps need, and each deletion candidate's last uses. A member in
602
+ * `unbuilt` holds source only in configurations the run could not build. An unreadable
603
+ * manifest declares nothing, nor does a nested package's uninstalled dependency, because
604
+ * no run requires a nested package to be installed.
605
+ */
606
+ export function dependenciesOf(host, targetRoot, perProject, candidates, members = [], unbuiltMembers = [], nested = []) {
570
607
  const root = resolvePath(host.workingDirectory(), targetRoot);
571
608
  const scopeOf = packageScope(host, targetRoot);
572
609
  const manifests = new Map([[root, MANIFEST]]);
573
610
  const unbuilt = new Set();
574
- for (const dir of [...members, ...unbuiltMembers]) {
611
+ for (const dir of [...members, ...unbuiltMembers, ...nested]) {
575
612
  const below = relativePath(root, dir);
576
613
  if (below !== undefined && !manifests.has(dir)) {
577
614
  manifests.set(dir, `${below}/${MANIFEST}`);
@@ -586,6 +623,8 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
586
623
  const workflow = new Set(workflowTokens(host, targetRoot));
587
624
  const declared = [];
588
625
  const ran = new Set();
626
+ const peers = new Map();
627
+ const uninstalledHere = new Set(nested);
589
628
  for (const [dir, path] of manifests) {
590
629
  const read = readJSON(host, joinPath(dir, MANIFEST));
591
630
  if (read === undefined || !isRecord(read.value)) {
@@ -593,12 +632,16 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
593
632
  }
594
633
  const tokens = scriptTokensOf(read.value);
595
634
  for (const dependency of declaredIn(read.text, read.value, path, scopeOf(path))) {
596
- declared.push(dependency);
597
635
  const found = installedOf(host, dir, dependency.name);
636
+ if (found === undefined && uninstalledHere.has(dir)) {
637
+ continue;
638
+ }
639
+ declared.push(dependency);
598
640
  if (found === undefined) {
599
641
  continue;
600
642
  }
601
- const runs = (command) => tokens.has(command) || (dir === root && workflow.has(command));
643
+ peers.set(declarationKey(dependency), found.peers);
644
+ const runs = (command) => tokens.has(command) || workflow.has(command);
602
645
  if (found.commands.some(runs)) {
603
646
  ran.add(declarationKey(dependency));
604
647
  }
@@ -607,25 +650,36 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
607
650
  // A declaration several projects compile is one user, and a use any project makes is
608
651
  // a use, so the uses are merged by declaration before anything is counted.
609
652
  const needed = new Set();
653
+ const byName = new Map();
654
+ for (const dependency of declared) {
655
+ byName.set(dependency.name, [...(byName.get(dependency.name) ?? []), dependency]);
656
+ }
610
657
  const uses = new Map();
611
658
  for (const project of perProject) {
612
659
  // A specifier the checker left unanswered may resolve to any dependency a manifest at
613
660
  // or above its file declares, and what the configuration needs is never deleted.
614
661
  const unanswered = project.unanswered.flatMap((file) => relativePath(root, file) ?? []);
615
- const unknown = declared
616
- .filter((dependency) => {
617
- const dir = dirnamePath(dependency.position.path);
618
- return unanswered.some((file) => isInside(file, dir === "." ? "" : dir));
619
- })
620
- .map((dependency) => dependency.name);
662
+ const unknownDeclared = declared.filter((dependency) => unanswered.some((file) => isInside(file, manifestDir(dependency))));
663
+ unknownDeclared.forEach((dependency) => needed.add(declarationKey(dependency)));
664
+ const unknown = unknownDeclared.map((dependency) => dependency.name);
621
665
  const held = unknown.length === 0 ? [] : [[PROJECT_OWNER, new Set(unknown)]];
622
666
  for (const [id, used] of [...project.uses, ...held]) {
623
667
  const merged = uses.get(id) ?? new Set();
624
668
  used.forEach((one) => merged.add(one));
625
669
  uses.set(id, merged);
626
670
  }
627
- [...project.packages, ...unknown].forEach((one) => needed.add(one));
671
+ for (const [path, named] of project.at) {
672
+ const below = path === ANYWHERE ? undefined : relativePath(root, path);
673
+ for (const one of named) {
674
+ for (const dependency of byName.get(one) ?? []) {
675
+ if (below === undefined || isInside(below, manifestDir(dependency))) {
676
+ needed.add(declarationKey(dependency));
677
+ }
678
+ }
679
+ }
680
+ }
628
681
  }
682
+ addNeededPeers(declared, peers, needed, ran);
629
683
  const users = new Map();
630
684
  for (const used of uses.values()) {
631
685
  used.forEach((one) => users.set(one, (users.get(one) ?? 0) + 1));
@@ -6,7 +6,7 @@
6
6
  * declares, and the checker attaches it to the first name alone, so the later names read
7
7
  * the list's own tags from the tree.
8
8
  */
9
- import { isVariableStatement, SyntaxKind } from "@typescript/native/unstable/ast";
9
+ import { isVariableStatement, SyntaxKind } from "@typescript/native/ast";
10
10
  import { nodeKey } from "./inventory.js";
11
11
  import { isAnswered, UNANSWERED } from "./query.js";
12
12
  /** The tag the TypeScript tools recognize as marking a declaration deprecated. */
@@ -5,8 +5,8 @@
5
5
  * to a local of the same name, renames it, gives it a default or nests a pattern under
6
6
  * it. A rest element names no property: what it copies is read where the copy is.
7
7
  */
8
- import { isArrayLiteralExpression, isBinaryExpression, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAssignment, isShorthandPropertyAssignment, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
9
- import { TypeFlags } from "@typescript/native/unstable/sync";
8
+ import { isArrayLiteralExpression, isBinaryExpression, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAssignment, isShorthandPropertyAssignment, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
9
+ import { TypeFlags } from "@typescript/native/sync";
10
10
  import { isAnswered, propertyTables, UNANSWERED, } from "./query.js";
11
11
  /** The text a property name spells, or `undefined` for a computed name. */
12
12
  function nameText(node) {
@@ -1,4 +1,4 @@
1
- import { DiagnosticCategory } from "@typescript/native/unstable/sync";
1
+ import { DiagnosticCategory } from "@typescript/native/sync";
2
2
  import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
3
3
  import { SetupError } from "./setup-failure.js";
4
4
  /** Directory names discovery does not descend into. */