@cplieger/deadset-ts 3.13.0 → 4.0.1

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 (92) hide show
  1. package/README.md +67 -49
  2. package/dist/bin/node-host.js +10 -7
  3. package/dist/src/alias-chain.js +59 -7
  4. package/dist/src/analysis.js +120 -24
  5. package/dist/src/calls.js +23 -22
  6. package/dist/src/class-members.js +10 -11
  7. package/dist/src/configured-declarations.js +22 -9
  8. package/dist/src/dependencies.js +34 -17
  9. package/dist/src/deprecation.js +35 -24
  10. package/dist/src/destructuring.js +37 -29
  11. package/dist/src/discover.js +106 -29
  12. package/dist/src/entry-point-gaps.js +2 -2
  13. package/dist/src/entry-points.js +7 -0
  14. package/dist/src/enum-group.js +46 -40
  15. package/dist/src/exempt.js +42 -1
  16. package/dist/src/findings/emitters.js +15 -1
  17. package/dist/src/findings/interfaces.js +7 -1
  18. package/dist/src/framework-lifecycle.js +8 -6
  19. package/dist/src/implementations.js +45 -23
  20. package/dist/src/interface-satisfaction.js +30 -27
  21. package/dist/src/intra-function-parts.js +37 -20
  22. package/dist/src/inventory.js +85 -4
  23. package/dist/src/json-chunks.js +76 -0
  24. package/dist/src/partial.js +23 -0
  25. package/dist/src/query.js +207 -0
  26. package/dist/src/references.js +92 -20
  27. package/dist/src/reflective-lookup.js +18 -9
  28. package/dist/src/report.js +3 -1
  29. package/dist/src/reporters/annotations.js +9 -5
  30. package/dist/src/reporters/json.js +3 -2
  31. package/dist/src/reporters/sarif.js +2 -1
  32. package/dist/src/reporters/template.js +51 -10
  33. package/dist/src/reporters/text.js +8 -6
  34. package/dist/src/roots.js +27 -11
  35. package/dist/src/schema.js +2 -2
  36. package/dist/src/serialization-contract.js +12 -10
  37. package/dist/src/session.js +79 -39
  38. package/dist/src/suppress-file.js +2 -1
  39. package/dist/src/sweep.js +4 -1
  40. package/dist/src/value-reach.js +14 -19
  41. package/dist/src/verbs/analyze.js +20 -3
  42. package/dist/src/verbs/explain.js +22 -9
  43. package/dist/src/verbs/print-projects.js +41 -8
  44. package/dist/src/verbs/print-retained.js +6 -2
  45. package/dist/src/verbs/print-roots.js +5 -1
  46. package/jsr.json +1 -1
  47. package/package.json +3 -3
  48. package/src/alias-chain.ts +75 -11
  49. package/src/analysis.ts +224 -62
  50. package/src/calls.ts +10 -9
  51. package/src/class-members.ts +10 -11
  52. package/src/configured-declarations.ts +29 -13
  53. package/src/dependencies.ts +42 -20
  54. package/src/deprecation.ts +53 -27
  55. package/src/destructuring.ts +50 -39
  56. package/src/discover.ts +178 -34
  57. package/src/entry-point-gaps.ts +2 -2
  58. package/src/entry-points.ts +7 -0
  59. package/src/enum-group.ts +56 -42
  60. package/src/exempt.ts +50 -2
  61. package/src/findings/emitters.ts +18 -1
  62. package/src/findings/interfaces.ts +7 -1
  63. package/src/framework-lifecycle.ts +10 -6
  64. package/src/host.ts +5 -3
  65. package/src/implementations.ts +59 -23
  66. package/src/interface-satisfaction.ts +30 -27
  67. package/src/intra-function-parts.ts +43 -25
  68. package/src/inventory.ts +97 -4
  69. package/src/json-chunks.ts +87 -0
  70. package/src/partial.ts +40 -0
  71. package/src/query.ts +522 -0
  72. package/src/references.ts +119 -38
  73. package/src/reflective-lookup.ts +21 -11
  74. package/src/report.ts +14 -2
  75. package/src/reporters/annotations.ts +23 -21
  76. package/src/reporters/json.ts +3 -2
  77. package/src/reporters/reporter.ts +2 -2
  78. package/src/reporters/sarif.ts +3 -2
  79. package/src/reporters/template.ts +53 -11
  80. package/src/reporters/text.ts +12 -12
  81. package/src/roots.ts +34 -12
  82. package/src/schema.ts +2 -2
  83. package/src/serialization-contract.ts +12 -10
  84. package/src/session.ts +146 -58
  85. package/src/suppress-file.ts +6 -2
  86. package/src/sweep.ts +9 -4
  87. package/src/value-reach.ts +20 -23
  88. package/src/verbs/analyze.ts +25 -3
  89. package/src/verbs/explain.ts +20 -9
  90. package/src/verbs/print-projects.ts +54 -8
  91. package/src/verbs/print-retained.ts +6 -2
  92. package/src/verbs/print-roots.ts +5 -1
package/dist/src/calls.js CHANGED
@@ -8,7 +8,7 @@
8
8
  import { isArrayLiteralExpression, isCallExpression, isClassDeclaration, isDecorator, isIdentifier, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadElement, } from "@typescript/native/unstable/ast";
9
9
  import { SymbolFlags } from "@typescript/native/unstable/sync";
10
10
  import { nodeKey } from "./inventory.js";
11
- import { DEFAULT_BATCH_CAP } from "./references.js";
11
+ import { must } from "./query.js";
12
12
  /** The name node a callee or a decorator expression resolves through, where it has one. */
13
13
  export function calleeName(expression) {
14
14
  if (isParenthesizedExpression(expression)) {
@@ -37,31 +37,32 @@ export function decoratorsOf(node) {
37
37
  /**
38
38
  * What each of a batch of nodes resolves to, every alias followed to the declaration
39
39
  * it stands for. A node the checker resolves to nothing, and an alias that resolves
40
- * to nothing, are absent from the answer. Each alias is followed once.
40
+ * to nothing, are absent from the answer. Each alias is followed once. A question the
41
+ * checker does not answer stops the pass, as {@link must} stops.
41
42
  */
42
43
  export function resolvedTargets(project, nodes) {
43
44
  const followed = new Map();
44
45
  const targets = new Map();
45
- for (let from = 0; from < nodes.length; from += DEFAULT_BATCH_CAP) {
46
- const run = nodes.slice(from, from + DEFAULT_BATCH_CAP);
47
- project.symbolsAt(run.map((node) => project.handle(node))).forEach((symbol, index) => {
48
- const node = run[index];
49
- if (symbol === undefined || node === undefined) {
50
- return;
51
- }
52
- let target = symbol;
53
- if ((symbol.flags & SymbolFlags.Alias) !== 0) {
54
- if (!followed.has(symbol.id)) {
55
- const aliased = project.checker.getAliasedSymbol(symbol);
56
- followed.set(symbol.id, aliased.declarations.length === 0 ? undefined : aliased);
57
- }
58
- target = followed.get(symbol.id);
59
- }
60
- if (target !== undefined) {
61
- targets.set(node, target);
46
+ project
47
+ .symbolsAt(nodes.map((node) => project.handle(node)))
48
+ .map(must)
49
+ .forEach((symbol, index) => {
50
+ const node = nodes[index];
51
+ if (symbol === undefined || node === undefined) {
52
+ return;
53
+ }
54
+ let target = symbol;
55
+ if ((symbol.flags & SymbolFlags.Alias) !== 0) {
56
+ if (!followed.has(symbol.id)) {
57
+ const aliased = must(project.queries.aliased(symbol));
58
+ followed.set(symbol.id, aliased.declarations.length === 0 ? undefined : aliased);
62
59
  }
63
- });
64
- }
60
+ target = followed.get(symbol.id);
61
+ }
62
+ if (target !== undefined) {
63
+ targets.set(node, target);
64
+ }
65
+ });
65
66
  return targets;
66
67
  }
67
68
  /** The values one expression passes: itself, or what an array or object literal holds. */
@@ -109,7 +110,7 @@ export function classesPassed(project, held, chains, calls) {
109
110
  const classes = new Set(held.symbols.filter((symbol) => symbol.kind === "class").map((symbol) => symbol.id));
110
111
  const classesOf = (value) => {
111
112
  const symbol = value.shorthand
112
- ? project.shorthandValueAt(project.handle(value.name))
113
+ ? must(project.shorthandValueAt(project.handle(value.name)))
113
114
  : targets.get(value.name);
114
115
  return symbol === undefined
115
116
  ? []
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { isClassDeclaration, isClassExpression } from "@typescript/native/unstable/ast";
7
7
  import { nodeKey } from "./inventory.js";
8
- import { DEFAULT_BATCH_CAP } from "./references.js";
8
+ import { must } from "./query.js";
9
9
  /** The name node of each class declaration of the target, by the class's identifier. */
10
10
  export function classNames(project, held) {
11
11
  const names = new Map();
@@ -23,13 +23,12 @@ export function classNames(project, held) {
23
23
  }
24
24
  return names;
25
25
  }
26
- /** The types of a batch of nodes, asked in capped batches. */
26
+ /**
27
+ * The types of a batch of nodes, for a pass that cannot go on past a type the checker
28
+ * did not answer: it stops there, as {@link must} stops.
29
+ */
27
30
  export function typesAt(project, nodes) {
28
- const types = [];
29
- for (let from = 0; from < nodes.length; from += DEFAULT_BATCH_CAP) {
30
- types.push(...project.checker.getTypeAtLocation(nodes.slice(from, from + DEFAULT_BATCH_CAP)));
31
- }
32
- return types;
31
+ return project.queries.typesAt(nodes).map(must);
33
32
  }
34
33
  /**
35
34
  * The type one side of each class is: the instance type at the class's name, or the
@@ -39,9 +38,9 @@ function sideTypes(project, nodes, side) {
39
38
  if (side === "instance") {
40
39
  return typesAt(project, nodes);
41
40
  }
42
- const symbols = project.symbolsAt(nodes.map((node) => project.handle(node)));
41
+ const symbols = project.symbolsAt(nodes.map((node) => project.handle(node))).map(must);
43
42
  const present = symbols.filter((symbol) => symbol !== undefined);
44
- const types = present.length === 0 ? [] : project.checker.getTypeOfSymbol(present);
43
+ const types = project.queries.typesOfSymbols(present).map(must);
45
44
  let next = 0;
46
45
  return symbols.map((symbol) => (symbol === undefined ? undefined : types[next++]));
47
46
  }
@@ -52,7 +51,7 @@ function sideTypes(project, nodes, side) {
52
51
  */
53
52
  export function classMembers(project, held, classIds, side) {
54
53
  const named = [...classNames(project, held)].filter(([id]) => classIds.has(id));
55
- const own = new Set(project.ownSourceFiles().map((file) => file.fileName));
54
+ const own = project.ownPaths();
56
55
  const types = sideTypes(project, named.map(([, node]) => node), side);
57
56
  const members = new Map();
58
57
  named.forEach(([id], index) => {
@@ -61,7 +60,7 @@ export function classMembers(project, held, classIds, side) {
61
60
  return;
62
61
  }
63
62
  const found = [];
64
- for (const property of project.checker.getPropertiesOfType(type)) {
63
+ for (const property of must(project.queries.propertiesOf(type))) {
65
64
  for (const handle of property.declarations) {
66
65
  if (!own.has(handle.path)) {
67
66
  continue;
@@ -7,6 +7,7 @@
7
7
  import { isCallExpression, isExportDeclaration, isExternalModuleReference, isImportDeclaration, isImportEqualsDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
8
8
  import { SymbolFlags, } from "@typescript/native/unstable/sync";
9
9
  import { resolvedTargets } from "./calls.js";
10
+ import { must, Unanswerable } from "./query.js";
10
11
  import { nameComponent } from "./ref.js";
11
12
  /** The meanings a name of the global scope is looked up under: any of them. */
12
13
  const ANY_MEANING = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace;
@@ -55,7 +56,7 @@ function unaliased(project, symbol) {
55
56
  if ((symbol.flags & SymbolFlags.Alias) === 0) {
56
57
  return symbol;
57
58
  }
58
- const target = project.checker.getAliasedSymbol(symbol);
59
+ const target = must(project.queries.aliased(symbol));
59
60
  return target.declarations.length === 0 ? undefined : target;
60
61
  }
61
62
  /** Whether one symbol is a member a class declares: a property, a method or an accessor. */
@@ -68,20 +69,20 @@ function isClassMember(symbol) {
68
69
  * with the static suffix names one of the first and a component without it names an
69
70
  * instance member or one of the second.
70
71
  */
71
- function candidatesOf(container, isStatic) {
72
- const exported = [...container.getExports().values()];
72
+ function candidatesOf(project, container, isStatic) {
73
+ const exported = [...must(project.queries.exportsOf(container)).values()];
73
74
  if (isStatic) {
74
75
  return exported.filter(isClassMember);
75
76
  }
76
77
  const isClass = (container.flags & SymbolFlags.Class) !== 0;
77
78
  return [
78
- ...container.getMembers().values(),
79
+ ...must(project.queries.membersOf(container)).values(),
79
80
  ...exported.filter((member) => !isClass || !isClassMember(member)),
80
81
  ];
81
82
  }
82
83
  /** The member of one container a path component spells, followed through any alias. */
83
84
  function memberOf(project, container, component, isStatic) {
84
- const member = candidatesOf(container, isStatic).find((candidate) => nameComponent(candidate.name) === component);
85
+ const member = candidatesOf(project, container, isStatic).find((candidate) => nameComponent(candidate.name) === component);
85
86
  return member === undefined ? undefined : unaliased(project, member);
86
87
  }
87
88
  /** The declaration a path names from one starting symbol, component by component. */
@@ -131,7 +132,7 @@ function modulesNamed(project, specifiers) {
131
132
  file.forEachChild(visit);
132
133
  }
133
134
  const specified = [...written];
134
- const symbols = project.symbolsAt(specified.map(([, node]) => project.handle(node)));
135
+ const symbols = project.symbolsAt(specified.map(([, node]) => project.handle(node))).map(must);
135
136
  const modules = new Map();
136
137
  specified.forEach(([name], index) => {
137
138
  const symbol = symbols[index];
@@ -171,7 +172,7 @@ export function resolveEntries(project, held, entries) {
171
172
  start = module === undefined ? undefined : memberOf(project, module, first, false);
172
173
  }
173
174
  else if (nameComponent(first) === first) {
174
- const global = project.checker.resolveName(first, ANY_MEANING, undefined, false);
175
+ const global = must(project.queries.resolveName(first, ANY_MEANING, undefined, false));
175
176
  start = global === undefined ? undefined : unaliased(project, global);
176
177
  }
177
178
  const declared = walkPath(project, start, rest, isStatic);
@@ -247,9 +248,21 @@ export function configuredDeclarations(config) {
247
248
  ...config.ts.serializers.map((entry) => ({ key: "ts.serializers", entry })),
248
249
  ];
249
250
  }
250
- /** Whether each configured entry names a declaration in one project, in the entries' order. */
251
+ /**
252
+ * Whether each configured entry names a declaration in one project, in the entries'
253
+ * order. Where the checker leaves a question of the resolution unanswered, every entry
254
+ * counts as naming one, so no entry is reported as naming nothing on a gap.
255
+ */
251
256
  export function declarationsNamed(project, held, configured) {
252
- return resolveEntries(project, held, configured.map((one) => one.entry)).map(namesADeclaration);
257
+ try {
258
+ return resolveEntries(project, held, configured.map((one) => one.entry)).map(namesADeclaration);
259
+ }
260
+ catch (error) {
261
+ if (!(error instanceof Unanswerable)) {
262
+ throw error;
263
+ }
264
+ return configured.map(() => true);
265
+ }
253
266
  }
254
267
  /**
255
268
  * The reference a finding about a configured entry names it by, the entry as the
@@ -8,7 +8,7 @@
8
8
  import { isExportDeclaration, isExternalModuleReference, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isShorthandPropertyAssignment, isStringLiteral, isNoSubstitutionTemplateLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
9
9
  import { nodeKey, packageScope } from "./inventory.js";
10
10
  import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.js";
11
- import { DEFAULT_BATCH_CAP } from "./references.js";
11
+ import { UNANSWERED } from "./query.js";
12
12
  const MANIFEST = "package.json";
13
13
  const MODULES_DIR = "node_modules";
14
14
  const MODULES_SEGMENT = `/${MODULES_DIR}/`;
@@ -207,10 +207,11 @@ export function projectNeeds(project, held, host) {
207
207
  }
208
208
  };
209
209
  use([PROJECT_OWNER], packages);
210
+ let unanswered = false;
210
211
  for (const file of project.ownSourceFiles()) {
211
- fileNeeds(project, held, file, programPackages, use);
212
+ unanswered = fileNeeds(project, held, file, programPackages, use) || unanswered;
212
213
  }
213
- return { packages, uses };
214
+ return { packages, uses, unanswered };
214
215
  }
215
216
  /** The declarations of the inventory a use written at one node belongs to. */
216
217
  function ownersOf(file, held, node) {
@@ -235,6 +236,7 @@ function ownersOf(file, held, node) {
235
236
  }
236
237
  return [PROJECT_OWNER];
237
238
  }
239
+ /** Records the needs of one file, and answers whether a specifier of it went unanswered. */
238
240
  function fileNeeds(project, held, file, programPackages, use) {
239
241
  const specifiers = [...file.imports, ...file.moduleAugmentations].filter(isLiteralText);
240
242
  const bindings = [];
@@ -262,13 +264,8 @@ function fileNeeds(project, held, file, programPackages, use) {
262
264
  visit(file);
263
265
  }
264
266
  const asked = [...specifiers, ...bindings.map(({ node }) => node), ...candidates];
265
- const resolved = [];
266
- for (let at = 0; at < asked.length; at += DEFAULT_BATCH_CAP) {
267
- const handles = asked
268
- .slice(at, at + DEFAULT_BATCH_CAP)
269
- .map((node) => project.handle(node));
270
- resolved.push(...project.symbolsAt(handles));
271
- }
267
+ const handles = asked.map((node) => project.handle(node));
268
+ const resolved = project.symbolsAt(handles);
272
269
  const packagesOf = new Map();
273
270
  specifiers.forEach((specifier, index) => {
274
271
  const named = new Set();
@@ -276,7 +273,8 @@ function fileNeeds(project, held, file, programPackages, use) {
276
273
  if (byName !== undefined) {
277
274
  named.add(byName);
278
275
  }
279
- for (const declaration of resolved[index]?.declarations ?? []) {
276
+ const symbol = resolved[index];
277
+ for (const declaration of typeof symbol === "object" ? symbol.declarations : []) {
280
278
  const byPath = packageOfPath(declaration.path);
281
279
  if (byPath !== undefined) {
282
280
  named.add(byPath);
@@ -284,11 +282,20 @@ function fileNeeds(project, held, file, programPackages, use) {
284
282
  }
285
283
  packagesOf.set(specifier, [...named]);
286
284
  });
285
+ // A name the checker could not resolve counts as a use of every package a binding of
286
+ // that name brings in, so a gap never makes a dependency read as unused.
287
287
  const bySymbol = new Map();
288
- bindings.forEach(({ specifier }, index) => {
288
+ const byName = new Map();
289
+ const unresolvedNames = new Set();
290
+ bindings.forEach(({ node, specifier }, index) => {
291
+ const packages = packagesOf.get(specifier) ?? [];
292
+ byName.set(node.text, [...(byName.get(node.text) ?? []), ...packages]);
289
293
  const symbol = resolved[specifiers.length + index];
290
- if (symbol !== undefined) {
291
- bySymbol.set(symbol.id, packagesOf.get(specifier) ?? []);
294
+ if (symbol === UNANSWERED) {
295
+ unresolvedNames.add(node.text);
296
+ }
297
+ else if (symbol !== undefined) {
298
+ bySymbol.set(symbol.id, packages);
292
299
  }
293
300
  });
294
301
  for (const specifier of specifiers) {
@@ -304,7 +311,12 @@ function fileNeeds(project, held, file, programPackages, use) {
304
311
  if (parent !== undefined && isShorthandPropertyAssignment(parent) && parent.name === node) {
305
312
  symbol = project.shorthandValueAt(project.handle(node));
306
313
  }
307
- const named = symbol === undefined ? undefined : bySymbol.get(symbol.id);
314
+ const named = symbol === UNANSWERED || (unresolvedNames.has(node.text) && symbol === undefined)
315
+ ? byName.get(node.text)
316
+ : symbol === undefined
317
+ ? undefined
318
+ : (bySymbol.get(symbol.id) ??
319
+ (unresolvedNames.has(node.text) ? byName.get(node.text) : undefined));
308
320
  if (named !== undefined) {
309
321
  use(ownersOf(file, held, node), named);
310
322
  }
@@ -312,6 +324,7 @@ function fileNeeds(project, held, file, programPackages, use) {
312
324
  for (const reference of file.typeReferenceDirectives) {
313
325
  use(ownersOf(file, held, file), typePackages(reference.fileName, programPackages));
314
326
  }
327
+ return resolved.slice(0, specifiers.length).includes(UNANSWERED);
315
328
  }
316
329
  /** The offset just past the JSON value that starts at `at` in text the parser has accepted. */
317
330
  function valueEnd(text, at) {
@@ -470,12 +483,16 @@ export function dependenciesOf(host, targetRoot, perProject, candidates) {
470
483
  const needed = new Set();
471
484
  const uses = new Map();
472
485
  for (const project of perProject) {
473
- project.packages.forEach((one) => needed.add(one));
474
- for (const [id, used] of project.uses) {
486
+ // A specifier the checker left unanswered may resolve to any declared dependency,
487
+ // and what the configuration needs is never deleted, so it is needed there.
488
+ const unknown = project.unanswered ? declared.map((dependency) => dependency.name) : [];
489
+ const held = unknown.length === 0 ? [] : [[PROJECT_OWNER, new Set(unknown)]];
490
+ for (const [id, used] of [...project.uses, ...held]) {
475
491
  const merged = uses.get(id) ?? new Set();
476
492
  used.forEach((one) => merged.add(one));
477
493
  uses.set(id, merged);
478
494
  }
495
+ [...project.packages, ...unknown].forEach((one) => needed.add(one));
479
496
  }
480
497
  const users = new Map();
481
498
  for (const used of uses.values()) {
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { isVariableStatement, SyntaxKind } from "@typescript/native/unstable/ast";
10
10
  import { nodeKey } from "./inventory.js";
11
- import { DEFAULT_BATCH_CAP } from "./references.js";
11
+ import { isAnswered, UNANSWERED } from "./query.js";
12
12
  /** The tag the TypeScript tools recognize as marking a declaration deprecated. */
13
13
  const DEPRECATED_TAG = "deprecated";
14
14
  /** Whether one node's own documentation carries the deprecation tag. */
@@ -21,15 +21,15 @@ function nameNode(node) {
21
21
  return node.name ?? node;
22
22
  }
23
23
  /**
24
- * The identifiers of every declaration of one project that carries the deprecation
25
- * marker, in the inventory's order.
24
+ * The declarations of one project that carry the deprecation marker.
26
25
  *
27
26
  * The checker is asked only for a symbol with a declaration in a file whose tree holds
28
27
  * the tag, so a project whose files hold none costs no round trip, and one that does
29
- * costs one batch per file and one request per symbol that could carry the tag.
28
+ * costs one batch per file and one for the tags of the symbols that could carry it.
30
29
  */
31
30
  export function deprecatedDeclarations(project, held) {
32
31
  const marked = new Set();
32
+ const unknown = new Set();
33
33
  const markedPaths = new Set();
34
34
  const declared = [];
35
35
  for (const file of project.ownSourceFiles()) {
@@ -56,28 +56,39 @@ export function deprecatedDeclarations(project, held) {
56
56
  declared.push(named);
57
57
  }
58
58
  if (markedPaths.size > 0) {
59
- const asked = new Map();
60
- const carriesTag = (symbol) => {
61
- const known = asked.get(symbol.id);
62
- if (known !== undefined) {
63
- return known;
59
+ const pairs = declared.flatMap((named) => {
60
+ const symbols = project.symbolsAt(named.map((one) => one.handle));
61
+ return named.flatMap((one, at) => {
62
+ const symbol = symbols[at];
63
+ if (symbol === UNANSWERED) {
64
+ unknown.add(one.id);
65
+ }
66
+ return typeof symbol === "object" &&
67
+ symbol.declarations.some((declaration) => markedPaths.has(declaration.path))
68
+ ? [{ id: one.id, symbol }]
69
+ : [];
70
+ });
71
+ });
72
+ const distinct = [...new Map(pairs.map((one) => [one.symbol.id, one.symbol])).values()];
73
+ const tags = project.queries.jsDocTags(distinct);
74
+ const unread = new Set(distinct.filter((_, at) => tags[at] === UNANSWERED).map((one) => one.id));
75
+ const carries = new Set(distinct.flatMap((symbol, at) => {
76
+ const read = tags[at];
77
+ return read !== undefined &&
78
+ isAnswered(read) &&
79
+ read.some((tag) => tag.name === DEPRECATED_TAG)
80
+ ? [symbol.id]
81
+ : [];
82
+ }));
83
+ for (const one of pairs) {
84
+ if (carries.has(one.symbol.id)) {
85
+ marked.add(one.id);
64
86
  }
65
- const carries = symbol.declarations.some((declaration) => markedPaths.has(declaration.path)) &&
66
- symbol.getJsDocTags(project.checker).some((tag) => tag.name === DEPRECATED_TAG);
67
- asked.set(symbol.id, carries);
68
- return carries;
69
- };
70
- for (const named of declared) {
71
- for (let start = 0; start < named.length; start += DEFAULT_BATCH_CAP) {
72
- const batch = named.slice(start, start + DEFAULT_BATCH_CAP);
73
- project.symbolsAt(batch.map((one) => one.handle)).forEach((symbol, at) => {
74
- const one = batch[at];
75
- if (symbol !== undefined && one !== undefined && carriesTag(symbol)) {
76
- marked.add(one.id);
77
- }
78
- });
87
+ if (unread.has(one.symbol.id)) {
88
+ unknown.add(one.id);
79
89
  }
80
90
  }
81
91
  }
82
- return held.symbols.filter((symbol) => marked.has(symbol.id)).map((symbol) => symbol.id);
92
+ const inOrder = (ids) => held.symbols.filter((symbol) => ids.has(symbol.id)).map((symbol) => symbol.id);
93
+ return { deprecated: inOrder(marked), unanswered: inOrder(unknown) };
83
94
  }
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { isArrayLiteralExpression, isBinaryExpression, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAssignment, isShorthandPropertyAssignment, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
9
9
  import { TypeFlags } from "@typescript/native/unstable/sync";
10
+ import { isAnswered, UNANSWERED } from "./query.js";
10
11
  /** The text a property name spells, or `undefined` for a computed name. */
11
12
  function nameText(node) {
12
13
  if (node === undefined) {
@@ -151,29 +152,22 @@ function sourceOf(target) {
151
152
  * places and a table answers every name of it.
152
153
  */
153
154
  export function destructuring(project, cap) {
154
- const checker = project.checker;
155
+ const queries = project.queries;
155
156
  const tables = new Map();
156
157
  let patternBatches = 0;
157
158
  let patternLookups = 0;
158
- const typesAt = (nodes) => {
159
- const types = [];
160
- for (let from = 0; from < nodes.length; from += cap) {
161
- types.push(...checker.getTypeAtLocation(nodes.slice(from, from + cap)));
162
- patternBatches += 1;
163
- }
164
- return types;
165
- };
166
159
  const tableOf = (type) => {
167
160
  const known = tables.get(type.id);
168
161
  if (known !== undefined) {
169
162
  return known;
170
163
  }
171
- const table = new Map();
164
+ let table = new Map();
172
165
  if ((type.flags & TypeFlags.AnyOrUnknown) === 0) {
173
166
  patternLookups += 1;
174
- for (const property of checker.getPropertiesOfType(type)) {
175
- table.set(property.name, property);
176
- }
167
+ const properties = queries.propertiesOf(type);
168
+ table = isAnswered(properties)
169
+ ? new Map(properties.map((property) => [property.name, property]))
170
+ : UNANSWERED;
177
171
  }
178
172
  tables.set(type.id, table);
179
173
  return table;
@@ -183,7 +177,7 @@ export function destructuring(project, cap) {
183
177
  return type;
184
178
  }
185
179
  patternLookups += 1;
186
- return checker.getNonNullableType(type);
180
+ return queries.nonNullable(type);
187
181
  };
188
182
  /**
189
183
  * The type one element of an array or tuple value has: a tuple's element at `index`,
@@ -193,33 +187,42 @@ export function destructuring(project, cap) {
193
187
  if (!type.isTypeReference()) {
194
188
  return undefined;
195
189
  }
196
- patternLookups += 1;
197
- if (checker.isTupleType(type)) {
190
+ if (type.isTupleType()) {
198
191
  if (index === undefined) {
199
192
  return undefined;
200
193
  }
201
194
  patternLookups += 1;
202
- return checker.getTypeArguments(type)[index];
195
+ const elements = queries.typeArguments(type);
196
+ return isAnswered(elements) ? elements[index] : UNANSWERED;
203
197
  }
204
198
  patternLookups += 1;
205
- if (!checker.isArrayType(type)) {
199
+ const array = queries.isArray(type);
200
+ if (!isAnswered(array)) {
201
+ return UNANSWERED;
202
+ }
203
+ if (!array) {
206
204
  return undefined;
207
205
  }
208
206
  patternLookups += 1;
209
- return checker.getTypeArguments(type)[0];
207
+ const elements = queries.typeArguments(type);
208
+ return isAnswered(elements) ? elements[0] : UNANSWERED;
210
209
  };
211
210
  const step = (type, next) => {
212
211
  const defined = definedOf(type);
213
- if (defined === undefined) {
214
- return undefined;
212
+ if (defined === undefined || defined === UNANSWERED) {
213
+ return defined;
215
214
  }
216
215
  if (next.kind === "property") {
217
- const property = tableOf(defined).get(next.name);
216
+ const table = tableOf(defined);
217
+ if (!isAnswered(table)) {
218
+ return UNANSWERED;
219
+ }
220
+ const property = table.get(next.name);
218
221
  if (property === undefined) {
219
222
  return undefined;
220
223
  }
221
224
  patternLookups += 1;
222
- return checker.getTypeOfSymbol(property);
225
+ return queries.typeOfSymbol(property);
223
226
  }
224
227
  return elementOf(defined, next.kind === "element" ? next.index : undefined);
225
228
  };
@@ -238,11 +241,12 @@ export function destructuring(project, cap) {
238
241
  asking.add(source.expression);
239
242
  }
240
243
  }
241
- const answered = new Map();
242
244
  const asked = [...asking];
243
- typesAt(asked).forEach((type, index) => {
245
+ patternBatches += Math.ceil(asked.length / cap);
246
+ const answered = new Map();
247
+ queries.typesAt(asked, cap).forEach((type, index) => {
244
248
  const node = asked[index];
245
- if (type !== undefined && node !== undefined) {
249
+ if (node !== undefined) {
246
250
  answered.set(node, type);
247
251
  }
248
252
  });
@@ -255,13 +259,17 @@ export function destructuring(project, cap) {
255
259
  }
256
260
  let type = answered.get(source.expression);
257
261
  for (const next of source.steps) {
258
- type = type === undefined ? undefined : step(type, next);
262
+ type = type === undefined || type === UNANSWERED ? type : step(type, next);
259
263
  }
260
- patternTypes.set(pattern, type === undefined ? undefined : definedOf(type));
264
+ patternTypes.set(pattern, type === undefined || type === UNANSWERED ? type : definedOf(type));
261
265
  }
262
266
  return reads.map((read) => {
263
267
  const type = patternTypes.get(read.pattern);
264
- return type === undefined ? undefined : tableOf(type).get(read.name);
268
+ if (type === undefined || type === UNANSWERED) {
269
+ return type;
270
+ }
271
+ const table = tableOf(type);
272
+ return isAnswered(table) ? table.get(read.name) : UNANSWERED;
265
273
  });
266
274
  };
267
275
  return {