@cplieger/deadset-ts 7.2.0-dev.3 → 8.0.0-dev.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 (153) hide show
  1. package/dist/bin/mapper-connection.js +7 -4
  2. package/dist/src/analysis.js +116 -16
  3. package/dist/src/augmentations.js +21 -8
  4. package/dist/src/automatic-types.js +189 -0
  5. package/dist/src/base-url.js +18 -0
  6. package/dist/src/component-files.js +96 -31
  7. package/dist/src/components.js +7 -6
  8. package/dist/src/config.js +1 -0
  9. package/dist/src/configuration-files.js +54 -15
  10. package/dist/src/configured-declarations.js +24 -2
  11. package/dist/src/conformance-record.js +2 -2
  12. package/dist/src/contextual-uses.js +26 -26
  13. package/dist/src/convention-rows.js +75 -0
  14. package/dist/src/conventions.js +101 -5
  15. package/dist/src/decorator.js +21 -18
  16. package/dist/src/dependencies.js +185 -30
  17. package/dist/src/destructuring.js +77 -2
  18. package/dist/src/discover.js +188 -85
  19. package/dist/src/edges.js +1 -1
  20. package/dist/src/entry-point-gaps.js +7 -10
  21. package/dist/src/entry-points.js +112 -11
  22. package/dist/src/findings/completion.js +116 -21
  23. package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
  24. package/dist/src/findings/emitters.js +1 -1
  25. package/dist/src/findings/intra-function.js +44 -13
  26. package/dist/src/findings/non-code-artifacts.js +66 -33
  27. package/dist/src/findings/unused-declarations.js +21 -1
  28. package/dist/src/findings/visibility-narrowing.js +22 -3
  29. package/dist/src/generated-file.js +11 -0
  30. package/dist/src/git-ignore.js +86 -0
  31. package/dist/src/graph.js +4 -1
  32. package/dist/src/import-bindings.js +97 -0
  33. package/dist/src/indexed-access.js +130 -28
  34. package/dist/src/interface-satisfaction.js +30 -12
  35. package/dist/src/intra-function-parts.js +313 -50
  36. package/dist/src/inventory.js +63 -8
  37. package/dist/src/json-document.js +5 -5
  38. package/dist/src/kinds.js +13 -13
  39. package/dist/src/matrix.js +18 -9
  40. package/dist/src/mdx-entries.js +197 -0
  41. package/dist/src/module-declarations.js +9 -13
  42. package/dist/src/namespace-objects.js +28 -18
  43. package/dist/src/overrides.js +45 -9
  44. package/dist/src/paths.js +9 -0
  45. package/dist/src/print.js +1 -0
  46. package/dist/src/ref.js +10 -0
  47. package/dist/src/references.js +29 -13
  48. package/dist/src/reflective-lookup.js +8 -6
  49. package/dist/src/report.js +4 -2
  50. package/dist/src/reporters/template.js +22 -13
  51. package/dist/src/resolve.js +2 -0
  52. package/dist/src/roots.js +110 -38
  53. package/dist/src/schema.js +1 -0
  54. package/dist/src/serialization-contract.js +97 -20
  55. package/dist/src/session.js +26 -1
  56. package/dist/src/source-files.js +39 -14
  57. package/dist/src/spelled-names.js +58 -0
  58. package/dist/src/suppress-file.js +12 -4
  59. package/dist/src/suppress-inline.js +21 -3
  60. package/dist/src/suppress.js +7 -5
  61. package/dist/src/sweep.js +27 -10
  62. package/dist/src/symbol-keys.js +103 -2
  63. package/dist/src/template-field.js +9 -6
  64. package/dist/src/test-support.js +121 -17
  65. package/dist/src/type-errors.js +116 -35
  66. package/dist/src/type-parameter-reads.js +172 -0
  67. package/dist/src/value-reach.js +17 -5
  68. package/dist/src/verbs/analyze.js +19 -1
  69. package/dist/src/verbs/explain.js +4 -2
  70. package/dist/src/version-range.js +10 -5
  71. package/dist/src/version.js +1 -1
  72. package/dist/src/workflow-steps.js +119 -22
  73. package/dist/src/workspace-programs.js +7 -3
  74. package/dist/src/workspace-resolution.js +72 -13
  75. package/dist/src/workspace.js +1 -1
  76. package/jsr.json +1 -1
  77. package/package.json +2 -2
  78. package/src/analysis.ts +175 -16
  79. package/src/augmentations.ts +21 -10
  80. package/src/automatic-types.ts +246 -0
  81. package/src/base-url.ts +24 -0
  82. package/src/component-files.ts +116 -31
  83. package/src/components.ts +7 -6
  84. package/src/config.ts +3 -0
  85. package/src/configuration-files.ts +80 -18
  86. package/src/configured-declarations.ts +33 -4
  87. package/src/conformance-record.ts +2 -2
  88. package/src/contextual-uses.ts +27 -27
  89. package/src/convention-rows.ts +103 -0
  90. package/src/conventions.ts +121 -7
  91. package/src/decorator.ts +28 -18
  92. package/src/dependencies.ts +249 -43
  93. package/src/destructuring.ts +93 -2
  94. package/src/discover.ts +234 -86
  95. package/src/edges.ts +1 -1
  96. package/src/entry-point-gaps.ts +8 -11
  97. package/src/entry-points.ts +133 -11
  98. package/src/finding.ts +10 -0
  99. package/src/findings/completion.ts +147 -24
  100. package/src/findings/dependencies-and-module-machinery.ts +90 -28
  101. package/src/findings/emitters.ts +1 -1
  102. package/src/findings/intra-function.ts +56 -14
  103. package/src/findings/non-code-artifacts.ts +81 -41
  104. package/src/findings/unused-declarations.ts +25 -1
  105. package/src/findings/visibility-narrowing.ts +32 -3
  106. package/src/generated-file.ts +16 -0
  107. package/src/git-ignore.ts +98 -0
  108. package/src/graph.ts +6 -0
  109. package/src/host.ts +5 -9
  110. package/src/import-bindings.ts +128 -0
  111. package/src/indexed-access.ts +152 -30
  112. package/src/interface-satisfaction.ts +33 -13
  113. package/src/intra-function-parts.ts +415 -64
  114. package/src/inventory.ts +83 -7
  115. package/src/json-document.ts +7 -7
  116. package/src/kinds.ts +13 -13
  117. package/src/matrix.ts +32 -14
  118. package/src/mdx-entries.ts +241 -0
  119. package/src/module-declarations.ts +11 -13
  120. package/src/namespace-objects.ts +34 -21
  121. package/src/overrides.ts +56 -9
  122. package/src/paths.ts +10 -0
  123. package/src/print.ts +1 -0
  124. package/src/ref.ts +12 -0
  125. package/src/references.ts +38 -17
  126. package/src/reflective-lookup.ts +9 -7
  127. package/src/report.ts +16 -3
  128. package/src/reporters/template.ts +21 -13
  129. package/src/resolve.ts +3 -0
  130. package/src/roots.ts +163 -45
  131. package/src/schema.ts +1 -0
  132. package/src/serialization-contract.ts +108 -17
  133. package/src/session.ts +29 -1
  134. package/src/source-files.ts +66 -16
  135. package/src/spelled-names.ts +102 -0
  136. package/src/suppress-file.ts +14 -3
  137. package/src/suppress-inline.ts +21 -2
  138. package/src/suppress.ts +9 -5
  139. package/src/sweep.ts +32 -11
  140. package/src/symbol-keys.ts +129 -2
  141. package/src/template-field.ts +9 -6
  142. package/src/test-support.ts +149 -16
  143. package/src/type-errors.ts +132 -41
  144. package/src/type-parameter-reads.ts +227 -0
  145. package/src/value-reach.ts +19 -6
  146. package/src/verbs/analyze.ts +28 -1
  147. package/src/verbs/explain.ts +5 -2
  148. package/src/version-range.ts +10 -5
  149. package/src/version.ts +1 -1
  150. package/src/workflow-steps.ts +127 -22
  151. package/src/workspace-programs.ts +7 -3
  152. package/src/workspace-resolution.ts +86 -14
  153. package/src/workspace.ts +1 -1
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The import bindings of one file: the names its import declarations bind, which only
3
+ * the file itself can read.
4
+ */
5
+
6
+ import {
7
+ isExportSpecifier,
8
+ isExternalModuleReference,
9
+ isIdentifier,
10
+ isImportDeclaration,
11
+ isImportEqualsDeclaration,
12
+ isJsxElement,
13
+ isJsxFragment,
14
+ isJsxSelfClosingElement,
15
+ isNamespaceImport,
16
+ SyntaxKind,
17
+ type Identifier,
18
+ type Node,
19
+ type SourceFile,
20
+ } from "@typescript/native/ast";
21
+
22
+ /** The extensions of the files whose import bindings are judged: no markup can read them. */
23
+ const JUDGED = /\.(?:[mc]?ts|tsx|[mc]?js|jsx)$/u;
24
+
25
+ /** The compiler's `jsx` settings whose emit calls no factory the file must hold. */
26
+ const AUTOMATIC_RUNTIME: ReadonlySet<unknown> = new Set([4, 5, "react-jsx", "react-jsxdev"]);
27
+
28
+ /** A doc comment, where a JavaScript file names the types it imports. */
29
+ const DOC_COMMENT = /\/\*\*[\s\S]*?\*\//gu;
30
+
31
+ /** The parts of a doc comment the compiler resolves names in: a `{…}` type or link, a `@see` target. */
32
+ const DOC_REFERENCE = /\{[^{}]*\}|@see\s+[A-Za-z_$][\w$.]*/gu;
33
+
34
+ /** The factory a pragma comment names: `@jsx h` or `@jsxFrag Fragment`. */
35
+ const PRAGMA = /@jsx(?:Frag)?\s+([A-Za-z_$][\w$]*)/gu;
36
+
37
+ /** The compiler options the JSX rule reads. */
38
+ export interface JsxOptions {
39
+ readonly jsx?: unknown;
40
+ readonly jsxFactory?: unknown;
41
+ readonly jsxFragmentFactory?: unknown;
42
+ }
43
+
44
+ /**
45
+ * The names one file's import declarations bind: the default, named and namespace names of
46
+ * an `import` declaration, and the name of an `import x = require()` declaration that is
47
+ * not exported. None is judged in a declaration file or a component file.
48
+ */
49
+ export function importBindings(file: SourceFile): readonly Identifier[] {
50
+ if (file.isDeclarationFile || !JUDGED.test(file.fileName)) {
51
+ return [];
52
+ }
53
+ const found: Identifier[] = [];
54
+ for (const statement of file.statements) {
55
+ if (isImportDeclaration(statement)) {
56
+ const clause = statement.importClause;
57
+ if (clause?.name !== undefined) {
58
+ found.push(clause.name);
59
+ }
60
+ const named = clause?.namedBindings;
61
+ if (named !== undefined && isNamespaceImport(named)) {
62
+ found.push(named.name);
63
+ } else if (named !== undefined) {
64
+ for (const element of named.elements) {
65
+ found.push(element.name);
66
+ }
67
+ }
68
+ } else if (
69
+ isImportEqualsDeclaration(statement) &&
70
+ isExternalModuleReference(statement.moduleReference) &&
71
+ isIdentifier(statement.name) &&
72
+ !(statement.modifiers ?? []).some((modifier) => modifier.kind === SyntaxKind.ExportKeyword)
73
+ ) {
74
+ found.push(statement.name);
75
+ }
76
+ }
77
+ return found;
78
+ }
79
+
80
+ /** Whether one identifier names a binding in a local export, which reads it whatever it resolves to. */
81
+ export function exportsLocally(use: Identifier): boolean {
82
+ return isExportSpecifier(use.parent);
83
+ }
84
+
85
+ /**
86
+ * The names a file reads without an identifier the program resolves: each name a type, a
87
+ * link or a `@see` tag of its doc comments spells, and, where it writes JSX the compiler
88
+ * turns into factory calls, the factories the configuration or a pragma names.
89
+ */
90
+ export function implicitReads(file: SourceFile, options: JsxOptions): ReadonlySet<string> {
91
+ const names = new Set<string>();
92
+ for (const comment of file.text.match(DOC_COMMENT) ?? []) {
93
+ for (const reference of comment.match(DOC_REFERENCE) ?? []) {
94
+ for (const word of reference.match(/[A-Za-z_$][\w$]*/gu) ?? []) {
95
+ names.add(word);
96
+ }
97
+ }
98
+ }
99
+ if (AUTOMATIC_RUNTIME.has(options.jsx) || !writesJsx(file)) {
100
+ return names;
101
+ }
102
+ for (const factory of [options.jsxFactory, options.jsxFragmentFactory]) {
103
+ names.add(typeof factory === "string" ? (factory.split(".")[0] ?? factory) : "React");
104
+ }
105
+ for (const match of file.text.matchAll(PRAGMA)) {
106
+ if (match[1] !== undefined) {
107
+ names.add(match[1]);
108
+ }
109
+ }
110
+ return names;
111
+ }
112
+
113
+ /** Whether a file holds a JSX element or fragment. */
114
+ function writesJsx(file: SourceFile): boolean {
115
+ let found = false;
116
+ const visit = (node: Node): void => {
117
+ if (found) {
118
+ return;
119
+ }
120
+ if (isJsxElement(node) || isJsxSelfClosingElement(node) || isJsxFragment(node)) {
121
+ found = true;
122
+ return;
123
+ }
124
+ node.forEachChild(visit);
125
+ };
126
+ file.forEachChild(visit);
127
+ return found;
128
+ }
@@ -11,6 +11,8 @@ import {
11
11
  isCallExpression,
12
12
  isClassDeclaration,
13
13
  isClassExpression,
14
+ isConstructorDeclaration,
15
+ isConstructorTypeNode,
14
16
  isFunctionDeclaration,
15
17
  isFunctionExpression,
16
18
  isFunctionTypeNode,
@@ -27,6 +29,7 @@ import {
27
29
  isObjectBindingPattern,
28
30
  isParenthesizedTypeNode,
29
31
  isPropertyAccessExpression,
32
+ isPropertyAssignment,
30
33
  isPropertySignatureDeclaration,
31
34
  isStringLiteral,
32
35
  isTypeAliasDeclaration,
@@ -71,6 +74,7 @@ function isGeneric(node: Node): node is Generic {
71
74
  isArrowFunction(node) ||
72
75
  isFunctionExpression(node) ||
73
76
  isFunctionTypeNode(node) ||
77
+ isConstructorTypeNode(node) ||
74
78
  isTypeAliasDeclaration(node) ||
75
79
  isInterfaceDeclaration(node) ||
76
80
  isClassDeclaration(node) ||
@@ -79,6 +83,25 @@ function isGeneric(node: Node): node is Generic {
79
83
  );
80
84
  }
81
85
 
86
+ /** Whether a function declares no type parameter and a parameter whose type holds `keyof`. */
87
+ function takesKeyof(node: Node): node is Generic {
88
+ if (
89
+ !(
90
+ isFunctionDeclaration(node) ||
91
+ isMethodDeclaration(node) ||
92
+ isMethodSignatureDeclaration(node) ||
93
+ isArrowFunction(node) ||
94
+ isFunctionExpression(node)
95
+ ) ||
96
+ node.typeParameters !== undefined
97
+ ) {
98
+ return false;
99
+ }
100
+ return node.parameters.some(
101
+ (parameter) => parameter.type !== undefined && keyofOperands(parameter.type).length > 0,
102
+ );
103
+ }
104
+
82
105
  /** Whether one generic is a receiver's: a class or an interface whose methods it parameterizes. */
83
106
  function isReceiver(node: Node): boolean {
84
107
  return isInterfaceDeclaration(node) || isClassDeclaration(node) || isClassExpression(node);
@@ -121,8 +144,8 @@ function unwrappedType(node: TypeNode): TypeNode {
121
144
  function literalNames(index: TypeNode): readonly string[] | undefined {
122
145
  const node = unwrappedType(index);
123
146
  if (isUnionTypeNode(node)) {
124
- const names = node.types.map(literalNames);
125
- return names.every((one) => one !== undefined) ? names.flat() : undefined;
147
+ const names = node.types.flatMap((one) => literalNames(one) ?? []);
148
+ return names.length > 0 ? names : undefined;
126
149
  }
127
150
  if (!isLiteralTypeNode(node)) {
128
151
  return undefined;
@@ -134,19 +157,14 @@ function literalNames(index: TypeNode): readonly string[] | undefined {
134
157
  return isNumericLiteral(literal) ? [String(Number(literal.text))] : undefined;
135
158
  }
136
159
 
137
- /** The name a literal type carries, a union's each, or none where a constituent is no literal. */
138
- function literalTypeNames(
139
- type: Type,
140
- parts: (type: Type) => readonly Type[],
141
- ): string[] | undefined {
160
+ /** The names the literal constituents of a type carry: a key the type names is one the access requires. */
161
+ function literalTypeNames(type: Type, parts: (type: Type) => readonly Type[]): string[] {
142
162
  const names: string[] = [];
143
163
  for (const part of parts(type)) {
144
164
  if (part.isStringLiteralType()) {
145
165
  names.push(part.value);
146
166
  } else if (part.isNumberLiteralType()) {
147
167
  names.push(String(part.value));
148
- } else {
149
- return undefined;
150
168
  }
151
169
  }
152
170
  return names;
@@ -266,7 +284,9 @@ function calledName(generic: Generic): string | undefined {
266
284
  }
267
285
  const parent = generic.parent as Node | undefined;
268
286
  return parent !== undefined &&
269
- (isVariableDeclaration(parent) || isPropertySignatureDeclaration(parent)) &&
287
+ (isVariableDeclaration(parent) ||
288
+ isPropertySignatureDeclaration(parent) ||
289
+ isPropertyAssignment(parent)) &&
270
290
  isIdentifier(parent.name)
271
291
  ? parent.name.text
272
292
  : undefined;
@@ -274,9 +294,12 @@ function calledName(generic: Generic): string | undefined {
274
294
 
275
295
  /** The generic a declaration a symbol names is: the function a variable is initialized with included. */
276
296
  function genericOf(node: Node): Generic | undefined {
277
- if (isGeneric(node) || isReceiverMember(node)) {
297
+ if (isGeneric(node) || isReceiverMember(node) || takesKeyof(node)) {
278
298
  return node;
279
299
  }
300
+ if (isConstructorDeclaration(node) && isGeneric(node.parent)) {
301
+ return node.parent;
302
+ }
280
303
  if (isPropertySignatureDeclaration(node)) {
281
304
  // The typings mark the annotation present; a signature written without one has none.
282
305
  const declared = (node as { readonly type?: TypeNode }).type;
@@ -378,6 +401,8 @@ export function indexedAccessReferences<Brand>(
378
401
  const carried = new Map<Generic, Carried[]>();
379
402
  const names = new Set<string>();
380
403
  const sites: Site[] = [];
404
+ /** Accesses indexed by a type parameter whose constraint is no `keyof`: it requires each key it names. */
405
+ const constrained: (Omit<Access, "index"> & { readonly constraint: TypeNode })[] = [];
381
406
  /** Calls passing an argument, which a generic declared outside the own files may fix. */
382
407
  const argumentCalls: Site[] = [];
383
408
 
@@ -407,13 +432,28 @@ export function indexedAccessReferences<Brand>(
407
432
  const own = held.declarations.get(nodeKey(file, node));
408
433
  const holder = own ?? from;
409
434
  const inside = isGeneric(node) ? [...enclosing, node] : enclosing;
410
- if (isGeneric(node)) {
435
+ if (isGeneric(node) || takesKeyof(node)) {
411
436
  carriedIn(node).forEach((one) => carry(node, one));
412
437
  }
413
438
  if (isIndexedAccessTypeNode(node) && !aliasTypes.has(node)) {
414
439
  const literal = literalNames(node.indexType);
440
+ const parameter =
441
+ literal === undefined ? namedParameter(node.indexType, inside) : undefined;
442
+ const constraint =
443
+ parameter === undefined
444
+ ? undefined
445
+ : (
446
+ parameter.generic.typeParameters?.[parameter.index] as
447
+ { readonly constraint?: TypeNode } | undefined
448
+ )?.constraint;
415
449
  if (literal !== undefined) {
416
450
  accesses.push({ file, from: holder, at: node, object: node.objectType, index: literal });
451
+ } else if (
452
+ constraint !== undefined &&
453
+ keyofOperands(constraint).length === 0 &&
454
+ namedParameter(node.objectType, inside) === undefined
455
+ ) {
456
+ constrained.push({ file, from: holder, at: node, object: node.objectType, constraint });
417
457
  }
418
458
  }
419
459
  if (
@@ -433,7 +473,7 @@ export function indexedAccessReferences<Brand>(
433
473
  if (name !== undefined) {
434
474
  const site = { file, from: holder, node, name, enclosing: inside };
435
475
  sites.push(site);
436
- if (isCallExpression(node) && node.arguments.length > 0) {
476
+ if (argumentsOf(node).length > 0) {
437
477
  argumentCalls.push(site);
438
478
  }
439
479
  }
@@ -447,6 +487,36 @@ export function indexedAccessReferences<Brand>(
447
487
  });
448
488
  }
449
489
 
490
+ /** The literal keys one constraint names, through the type aliases it references. */
491
+ const requiredNames = (node: TypeNode, seen: ReadonlySet<Node>): readonly string[] => {
492
+ const type = unwrappedType(node);
493
+ if (isUnionTypeNode(type)) {
494
+ return type.types.flatMap((one) => requiredNames(one, seen));
495
+ }
496
+ if (!isTypeReferenceNode(type) || type.typeArguments !== undefined) {
497
+ return literalNames(type) ?? [];
498
+ }
499
+ const [symbol] = queries.symbolsAt([type.typeName]);
500
+ if (symbol === UNANSWERED || symbol === undefined) {
501
+ return [];
502
+ }
503
+ return symbol.declarations.flatMap((handle) => {
504
+ const declared = project.declarationAt(handle)?.node;
505
+ return declared !== undefined &&
506
+ isTypeAliasDeclaration(declared) &&
507
+ declared.typeParameters === undefined &&
508
+ !seen.has(declared)
509
+ ? requiredNames(declared.type, new Set([...seen, declared]))
510
+ : [];
511
+ });
512
+ };
513
+ for (const { constraint, ...access } of constrained) {
514
+ const index = requiredNames(constraint, new Set());
515
+ if (index.length > 0) {
516
+ accesses.push({ ...access, index });
517
+ }
518
+ }
519
+
450
520
  function partsOf(type: Type): readonly Type[] {
451
521
  const parts = queries.constituents(type);
452
522
  return parts === UNANSWERED ? [type] : parts;
@@ -471,11 +541,13 @@ export function indexedAccessReferences<Brand>(
471
541
  * variable holding the callee is initialized with.
472
542
  */
473
543
  const receiverTypeOf = (callee: Expression): Type | undefined => {
544
+ let operand: Type | undefined;
474
545
  if (isPropertyAccessExpression(callee)) {
475
546
  const type = queries.typeAt(callee.expression);
476
- const args = type === UNANSWERED || type === undefined ? [] : queries.typeArguments(type);
477
- if (type !== UNANSWERED && type !== undefined && args !== UNANSWERED && args.length > 0) {
478
- return type;
547
+ operand = type === UNANSWERED ? undefined : type;
548
+ const args = operand === undefined ? [] : queries.typeArguments(operand);
549
+ if (operand !== undefined && args !== UNANSWERED && args.length > 0) {
550
+ return operand;
479
551
  }
480
552
  }
481
553
  const name = isPropertyAccessExpression(callee) ? callee.name : callee;
@@ -485,7 +557,7 @@ export function indexedAccessReferences<Brand>(
485
557
  ? named
486
558
  : queries.aliased(named);
487
559
  if (symbol === UNANSWERED || symbol === undefined) {
488
- return undefined;
560
+ return operand;
489
561
  }
490
562
  for (const handle of symbol.declarations) {
491
563
  const node = project.declarationAt(handle)?.node;
@@ -506,7 +578,42 @@ export function indexedAccessReferences<Brand>(
506
578
  return type;
507
579
  }
508
580
  }
509
- return undefined;
581
+ return operand;
582
+ };
583
+
584
+ /**
585
+ * The instantiation of `owner` a receiver's type is, or the first one among the base
586
+ * types it extends, breadth first: a subclass fixes its base's type parameters, and
587
+ * `this` stands for its class.
588
+ */
589
+ const instantiationOf = (type: Type, owner: Node): Type | undefined => {
590
+ const seen = new Set<number>();
591
+ for (let queue = [type]; queue.length > 0;) {
592
+ const next: Type[] = [];
593
+ for (const at of queue) {
594
+ if (seen.has(at.id)) {
595
+ continue;
596
+ }
597
+ seen.add(at.id);
598
+ const target = at.isTypeReference() ? queries.targetOf(at) : at;
599
+ const symbol = target === UNANSWERED ? undefined : queries.symbolOfType(target);
600
+ const declares =
601
+ symbol !== undefined &&
602
+ symbol !== UNANSWERED &&
603
+ symbol.declarations.some((handle) => project.declarationAt(handle)?.node === owner);
604
+ if (declares) {
605
+ return at;
606
+ }
607
+ const constraint = at.isTypeParameter() ? queries.constraintOf(at) : undefined;
608
+ const bases = at.isTypeParameter() ? [] : queries.baseTypes(at);
609
+ next.push(...(bases === UNANSWERED ? [] : bases));
610
+ if (constraint !== undefined && constraint !== UNANSWERED) {
611
+ next.push(constraint);
612
+ }
613
+ }
614
+ queue = next;
615
+ }
616
+ return type;
510
617
  };
511
618
 
512
619
  /** What one site fixes for one side of a carried access. */
@@ -517,7 +624,10 @@ export function indexedAccessReferences<Brand>(
517
624
  const { node } = site;
518
625
  if (side.kind === "receiver") {
519
626
  const callee = isCallExpression(node) ? node.expression : undefined;
520
- const receiver = callee === undefined ? undefined : receiverTypeOf(callee);
627
+ const owner = receiverOf(generic);
628
+ const typed = callee === undefined ? undefined : receiverTypeOf(callee);
629
+ const receiver =
630
+ typed === undefined || owner === undefined ? typed : instantiationOf(typed, owner);
521
631
  if (receiver === undefined) {
522
632
  return undefined;
523
633
  }
@@ -541,7 +651,7 @@ export function indexedAccessReferences<Brand>(
541
651
  if (isTypeReferenceNode(node)) {
542
652
  return undefined;
543
653
  }
544
- const at = (generic.parameters ?? []).findIndex((parameter) => {
654
+ const at = parametersOf(generic).findIndex((parameter) => {
545
655
  const type = parameter.type === undefined ? undefined : unwrappedType(parameter.type);
546
656
  return (
547
657
  type !== undefined &&
@@ -645,9 +755,7 @@ export function indexedAccessReferences<Brand>(
645
755
  // by its type parameters and its receiver's. Only a call passing an argument the compiler
646
756
  // types as a literal can fix an index, so only such a call's signature is resolved.
647
757
  const outside = argumentCalls.filter((site) => !resolvedGenerics.has(site));
648
- const argumentNodes = argumentCalls.flatMap((site) => [
649
- ...(site.node as CallExpression).arguments,
650
- ]);
758
+ const argumentNodes = argumentCalls.flatMap((site) => argumentsOf(site.node));
651
759
  const argumentTypes = queries.typesAt(argumentNodes);
652
760
  const literalArgument = new Set<Node>();
653
761
  argumentTypes.forEach((type, at) => {
@@ -656,14 +764,14 @@ export function indexedAccessReferences<Brand>(
656
764
  node !== undefined &&
657
765
  type !== UNANSWERED &&
658
766
  type !== undefined &&
659
- (literalTypeNames(type, partsOf)?.length ?? 0) > 0
767
+ literalTypeNames(type, partsOf).length > 0
660
768
  ) {
661
769
  literalArgument.add(node);
662
770
  }
663
771
  });
664
772
  const own = project.ownPaths();
665
773
  for (const site of outside) {
666
- if (!(site.node as CallExpression).arguments.some((one) => literalArgument.has(one))) {
774
+ if (!argumentsOf(site.node).some((one) => literalArgument.has(one))) {
667
775
  continue;
668
776
  }
669
777
  const resolved = queries.resolvedSignature(site.node);
@@ -682,7 +790,7 @@ export function indexedAccessReferences<Brand>(
682
790
  }
683
791
  }
684
792
  for (const site of argumentCalls) {
685
- if ((site.node as CallExpression).arguments.some((one) => literalArgument.has(one))) {
793
+ if (argumentsOf(site.node).some((one) => literalArgument.has(one))) {
686
794
  accesses.push(...instantiatedAccesses(site));
687
795
  }
688
796
  }
@@ -706,7 +814,7 @@ export function indexedAccessReferences<Brand>(
706
814
  ) {
707
815
  return [];
708
816
  }
709
- const count = (site.node as CallExpression).arguments.length;
817
+ const count = argumentsOf(site.node).length;
710
818
  const parameters: Type[] = [];
711
819
  for (let at = 0; at < count; at += 1) {
712
820
  const type = queries.parameterType(generic, at);
@@ -745,13 +853,13 @@ export function indexedAccessReferences<Brand>(
745
853
  }
746
854
  const names = access.index.isTypeParameter()
747
855
  ? parameters.flatMap((parameter, at) => {
748
- if (parameter.id !== access.index.id) {
856
+ if (!partsOf(parameter).some((part) => part.id === access.index.id)) {
749
857
  return [];
750
858
  }
751
859
  const passed = queries.typeAtPosition(resolved, at);
752
- return passed === UNANSWERED ? [] : (literalTypeNames(passed, partsOf) ?? []);
860
+ return passed === UNANSWERED ? [] : literalTypeNames(passed, partsOf);
753
861
  })
754
- : (literalTypeNames(access.index, partsOf) ?? []);
862
+ : literalTypeNames(access.index, partsOf);
755
863
  if (names.length > 0) {
756
864
  found.push({
757
865
  file: site.file,
@@ -819,6 +927,20 @@ export function indexedAccessReferences<Brand>(
819
927
  return found;
820
928
  }
821
929
 
930
+ /** The arguments a call or a `new` expression passes, none for a type reference. */
931
+ function argumentsOf(node: Site["node"]): readonly Expression[] {
932
+ return isTypeReferenceNode(node) ? [] : (node.arguments ?? []);
933
+ }
934
+
935
+ /** The parameters a site's arguments bind: a class's are its constructor's. */
936
+ function parametersOf(generic: Generic): NonNullable<Generic["parameters"]> {
937
+ if (isClassDeclaration(generic) || isClassExpression(generic)) {
938
+ const constructor = generic.members.find(isConstructorDeclaration);
939
+ return constructor?.parameters ?? [];
940
+ }
941
+ return generic.parameters ?? [];
942
+ }
943
+
822
944
  /** What makes two carried accesses one: the kind and position of each side, or its written node. */
823
945
  function sideKey(one: Carried): readonly unknown[] {
824
946
  const key = (side: Side): unknown =>
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * The interface-satisfaction class: a member is held back when a value of its class
3
- * reaches a position the checker types as an interface requiring that member, through an
4
- * `implements` entry, a typed initializer, an assignment, an argument, a return, an array
5
- * element, an object property or an `as` or `satisfies` operand. `this` is a value of its
6
- * class, and a position typed by a type parameter reaches its constraint. The contextual
7
- * lookup has no batch form, so it is asked only of a value typed as a target class.
3
+ * reaches a position typed as an interface, or an object type a type alias names, that
4
+ * requires the member: an `implements` entry, a typed initializer, an assignment, an
5
+ * argument, a return, an array element, an object property or an `as` or `satisfies`
6
+ * operand. `this` is a value of its class; a type parameter reaches its constraint. The
7
+ * contextual lookup has no batch form, so it is asked only of a value of a target class.
8
8
  */
9
9
 
10
10
  import {
@@ -20,6 +20,7 @@ import {
20
20
  isNewExpression,
21
21
  isObjectLiteralExpression,
22
22
  isSetAccessorDeclaration,
23
+ isTypeLiteralNode,
23
24
  ModifierFlags,
24
25
  SyntaxKind,
25
26
  type Expression,
@@ -74,6 +75,11 @@ interface Pair {
74
75
  readonly sites: Node[];
75
76
  }
76
77
 
78
+ /** Whether a type alias names one type: the client's type object carries the alias it was written through. */
79
+ function aliased(type: Type): boolean {
80
+ return (type as { readonly aliasSymbol?: unknown }).aliasSymbol !== undefined;
81
+ }
82
+
77
83
  /** Whether one node declares a static member, whose `this` is the class itself. */
78
84
  function isStatic(node: Node): boolean {
79
85
  const flags = (node as { readonly modifierFlags?: number }).modifierFlags ?? 0;
@@ -225,24 +231,38 @@ export function interfaceSatisfaction<Brand>(input: DetectorInput<Brand>): reado
225
231
  return generic === undefined ? [] : [{ owner: generic, type: part }];
226
232
  });
227
233
 
228
- const interfaceNames = new Map<number, string | undefined>();
229
- /** The interface types one position's type is or holds, each with the interface's name. */
234
+ const interfaceNames = new Map<string, string | undefined>();
235
+ /**
236
+ * The interface types one position's type is or holds, each with the interface's name:
237
+ * an interface, or an object type literal a type alias names, directly or as a
238
+ * constituent of an intersection the alias names.
239
+ */
230
240
  const interfacesOf = (type: Type): readonly { type: Type; name: string }[] =>
231
241
  partsOf(type).flatMap((part): readonly { type: Type; name: string }[] => {
232
242
  if (part.isTypeParameter()) {
233
243
  const constraint = must(queries.constraintOf(part));
234
244
  return constraint === undefined ? [] : interfacesOf(constraint);
235
245
  }
236
- if (!interfaceNames.has(part.id)) {
246
+ const named = aliased(part) ? part : aliased(type) ? type : undefined;
247
+ const key = `${String(part.id)}:${String(named?.id ?? "")}`;
248
+ if (!interfaceNames.has(key)) {
237
249
  const symbol = part.isObjectType() ? must(queries.symbolOfType(part)) : undefined;
250
+ const flags = symbol?.flags ?? 0;
238
251
  interfaceNames.set(
239
- part.id,
240
- symbol !== undefined && (symbol.flags & SymbolFlags.Interface) !== 0
241
- ? symbol.name
242
- : undefined,
252
+ key,
253
+ (flags & SymbolFlags.Interface) !== 0
254
+ ? symbol?.name
255
+ : (flags & SymbolFlags.TypeLiteral) !== 0 &&
256
+ named !== undefined &&
257
+ symbol?.declarations.some((handle) => {
258
+ const node = project.declarationAt(handle)?.node;
259
+ return node !== undefined && isTypeLiteralNode(node);
260
+ }) === true
261
+ ? must(queries.typeToString(named))
262
+ : undefined,
243
263
  );
244
264
  }
245
- const name = interfaceNames.get(part.id);
265
+ const name = interfaceNames.get(key);
246
266
  return name === undefined ? [] : [{ type: part, name }];
247
267
  });
248
268