@openrewrite/rewrite 8.92.16 → 8.92.18

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 (254) hide show
  1. package/README.md +48 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/java/formatting-utils.d.ts +2 -1
  6. package/dist/java/formatting-utils.d.ts.map +1 -1
  7. package/dist/java/formatting-utils.js +4 -3
  8. package/dist/java/formatting-utils.js.map +1 -1
  9. package/dist/java/rpc.d.ts.map +1 -1
  10. package/dist/java/rpc.js +2 -0
  11. package/dist/java/rpc.js.map +1 -1
  12. package/dist/java/tree.d.ts +8 -0
  13. package/dist/java/tree.d.ts.map +1 -1
  14. package/dist/java/tree.js +11 -1
  15. package/dist/java/tree.js.map +1 -1
  16. package/dist/java/type.d.ts +7 -1
  17. package/dist/java/type.d.ts.map +1 -1
  18. package/dist/java/type.js +16 -1
  19. package/dist/java/type.js.map +1 -1
  20. package/dist/javascript/add-import.d.ts +27 -21
  21. package/dist/javascript/add-import.d.ts.map +1 -1
  22. package/dist/javascript/add-import.js +303 -143
  23. package/dist/javascript/add-import.js.map +1 -1
  24. package/dist/javascript/amd.d.ts.map +1 -1
  25. package/dist/javascript/amd.js +25 -10
  26. package/dist/javascript/amd.js.map +1 -1
  27. package/dist/javascript/autodetect.js +36 -45
  28. package/dist/javascript/autodetect.js.map +1 -1
  29. package/dist/javascript/binding.d.ts +6 -4
  30. package/dist/javascript/binding.d.ts.map +1 -1
  31. package/dist/javascript/binding.js +54 -18
  32. package/dist/javascript/binding.js.map +1 -1
  33. package/dist/javascript/comparator.d.ts +22 -5
  34. package/dist/javascript/comparator.d.ts.map +1 -1
  35. package/dist/javascript/comparator.js +120 -38
  36. package/dist/javascript/comparator.js.map +1 -1
  37. package/dist/javascript/format/format.d.ts +2 -0
  38. package/dist/javascript/format/format.d.ts.map +1 -1
  39. package/dist/javascript/format/format.js +11 -1
  40. package/dist/javascript/format/format.js.map +1 -1
  41. package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -1
  42. package/dist/javascript/format/tabs-and-indents-visitor.js +31 -4
  43. package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -1
  44. package/dist/javascript/index.d.ts +4 -2
  45. package/dist/javascript/index.d.ts.map +1 -1
  46. package/dist/javascript/index.js +10 -1
  47. package/dist/javascript/index.js.map +1 -1
  48. package/dist/javascript/markers.d.ts +12 -0
  49. package/dist/javascript/markers.d.ts.map +1 -1
  50. package/dist/javascript/markers.js +81 -2
  51. package/dist/javascript/markers.js.map +1 -1
  52. package/dist/javascript/method-matcher.d.ts +13 -2
  53. package/dist/javascript/method-matcher.d.ts.map +1 -1
  54. package/dist/javascript/method-matcher.js +32 -19
  55. package/dist/javascript/method-matcher.js.map +1 -1
  56. package/dist/javascript/node-resolution-result.js +3 -2
  57. package/dist/javascript/node-resolution-result.js.map +1 -1
  58. package/dist/javascript/package-name.d.ts +6 -0
  59. package/dist/javascript/package-name.d.ts.map +1 -0
  60. package/dist/javascript/package-name.js +47 -0
  61. package/dist/javascript/package-name.js.map +1 -0
  62. package/dist/javascript/parser.d.ts +9 -0
  63. package/dist/javascript/parser.d.ts.map +1 -1
  64. package/dist/javascript/parser.js +185 -88
  65. package/dist/javascript/parser.js.map +1 -1
  66. package/dist/javascript/preconditions.d.ts +4 -1
  67. package/dist/javascript/preconditions.d.ts.map +1 -1
  68. package/dist/javascript/preconditions.js +7 -4
  69. package/dist/javascript/preconditions.js.map +1 -1
  70. package/dist/javascript/print.d.ts +10 -1
  71. package/dist/javascript/print.d.ts.map +1 -1
  72. package/dist/javascript/print.js +120 -77
  73. package/dist/javascript/print.js.map +1 -1
  74. package/dist/javascript/recipes/change-method-target-to-import.d.ts +29 -0
  75. package/dist/javascript/recipes/change-method-target-to-import.d.ts.map +1 -0
  76. package/dist/javascript/recipes/change-method-target-to-import.js +164 -0
  77. package/dist/javascript/recipes/change-method-target-to-import.js.map +1 -0
  78. package/dist/javascript/recipes/index.d.ts +1 -0
  79. package/dist/javascript/recipes/index.d.ts.map +1 -1
  80. package/dist/javascript/recipes/index.js +1 -0
  81. package/dist/javascript/recipes/index.js.map +1 -1
  82. package/dist/javascript/scope.d.ts +60 -16
  83. package/dist/javascript/scope.d.ts.map +1 -1
  84. package/dist/javascript/scope.js +596 -131
  85. package/dist/javascript/scope.js.map +1 -1
  86. package/dist/javascript/search/uses-method.d.ts +1 -1
  87. package/dist/javascript/search/uses-method.d.ts.map +1 -1
  88. package/dist/javascript/search/uses-method.js +2 -2
  89. package/dist/javascript/search/uses-method.js.map +1 -1
  90. package/dist/javascript/search/uses-type.d.ts +14 -1
  91. package/dist/javascript/search/uses-type.d.ts.map +1 -1
  92. package/dist/javascript/search/uses-type.js +24 -9
  93. package/dist/javascript/search/uses-type.js.map +1 -1
  94. package/dist/javascript/templating/bindings.d.ts +1 -1
  95. package/dist/javascript/templating/bindings.d.ts.map +1 -1
  96. package/dist/javascript/templating/bindings.js +10 -50
  97. package/dist/javascript/templating/bindings.js.map +1 -1
  98. package/dist/javascript/templating/comparator.d.ts +8 -11
  99. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  100. package/dist/javascript/templating/comparator.js +12 -110
  101. package/dist/javascript/templating/comparator.js.map +1 -1
  102. package/dist/javascript/templating/engine.d.ts +34 -24
  103. package/dist/javascript/templating/engine.d.ts.map +1 -1
  104. package/dist/javascript/templating/engine.js +208 -115
  105. package/dist/javascript/templating/engine.js.map +1 -1
  106. package/dist/javascript/templating/pattern.d.ts +11 -5
  107. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  108. package/dist/javascript/templating/pattern.js +86 -20
  109. package/dist/javascript/templating/pattern.js.map +1 -1
  110. package/dist/javascript/templating/placeholder-replacement.d.ts +34 -3
  111. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  112. package/dist/javascript/templating/placeholder-replacement.js +126 -60
  113. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  114. package/dist/javascript/templating/precedence.d.ts +3 -1
  115. package/dist/javascript/templating/precedence.d.ts.map +1 -1
  116. package/dist/javascript/templating/precedence.js +28 -8
  117. package/dist/javascript/templating/precedence.js.map +1 -1
  118. package/dist/javascript/templating/template.d.ts +7 -6
  119. package/dist/javascript/templating/template.d.ts.map +1 -1
  120. package/dist/javascript/templating/template.js +31 -28
  121. package/dist/javascript/templating/template.js.map +1 -1
  122. package/dist/javascript/templating/types.d.ts +6 -3
  123. package/dist/javascript/templating/types.d.ts.map +1 -1
  124. package/dist/javascript/templating/utils.d.ts +10 -1
  125. package/dist/javascript/templating/utils.d.ts.map +1 -1
  126. package/dist/javascript/templating/utils.js +22 -3
  127. package/dist/javascript/templating/utils.js.map +1 -1
  128. package/dist/javascript/tree-debug.d.ts +2 -0
  129. package/dist/javascript/tree-debug.d.ts.map +1 -1
  130. package/dist/javascript/tree-debug.js +6 -0
  131. package/dist/javascript/tree-debug.js.map +1 -1
  132. package/dist/javascript/tsconfig.d.ts.map +1 -1
  133. package/dist/javascript/tsconfig.js +7 -17
  134. package/dist/javascript/tsconfig.js.map +1 -1
  135. package/dist/javascript/type-mapping.d.ts +35 -16
  136. package/dist/javascript/type-mapping.d.ts.map +1 -1
  137. package/dist/javascript/type-mapping.js +271 -252
  138. package/dist/javascript/type-mapping.js.map +1 -1
  139. package/dist/javascript/type-report-cli.d.ts +7 -0
  140. package/dist/javascript/type-report-cli.d.ts.map +1 -0
  141. package/dist/javascript/type-report-cli.js +129 -0
  142. package/dist/javascript/type-report-cli.js.map +1 -0
  143. package/dist/javascript/type-report.d.ts +61 -0
  144. package/dist/javascript/type-report.d.ts.map +1 -0
  145. package/dist/javascript/type-report.js +488 -0
  146. package/dist/javascript/type-report.js.map +1 -0
  147. package/dist/markers.d.ts +4 -0
  148. package/dist/markers.d.ts.map +1 -1
  149. package/dist/markers.js +8 -0
  150. package/dist/markers.js.map +1 -1
  151. package/dist/print.d.ts +6 -1
  152. package/dist/print.d.ts.map +1 -1
  153. package/dist/print.js +21 -7
  154. package/dist/print.js.map +1 -1
  155. package/dist/recipe.d.ts +5 -0
  156. package/dist/recipe.d.ts.map +1 -1
  157. package/dist/recipe.js +5 -0
  158. package/dist/recipe.js.map +1 -1
  159. package/dist/rewrite-javascript-version.txt +1 -1
  160. package/dist/rpc/java-rpc-client.d.ts +2 -0
  161. package/dist/rpc/java-rpc-client.d.ts.map +1 -1
  162. package/dist/rpc/java-rpc-client.js +1 -0
  163. package/dist/rpc/java-rpc-client.js.map +1 -1
  164. package/dist/rpc/queue.d.ts +24 -0
  165. package/dist/rpc/queue.d.ts.map +1 -1
  166. package/dist/rpc/queue.js +71 -11
  167. package/dist/rpc/queue.js.map +1 -1
  168. package/dist/rpc/request/get-object.d.ts +4 -0
  169. package/dist/rpc/request/get-object.d.ts.map +1 -1
  170. package/dist/rpc/request/get-object.js +23 -1
  171. package/dist/rpc/request/get-object.js.map +1 -1
  172. package/dist/rpc/request/parse.d.ts.map +1 -1
  173. package/dist/rpc/request/parse.js +3 -1
  174. package/dist/rpc/request/parse.js.map +1 -1
  175. package/dist/rpc/request/prepare-recipe.d.ts +3 -1
  176. package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
  177. package/dist/rpc/request/prepare-recipe.js +13 -6
  178. package/dist/rpc/request/prepare-recipe.js.map +1 -1
  179. package/dist/rpc/request/print.d.ts +8 -2
  180. package/dist/rpc/request/print.d.ts.map +1 -1
  181. package/dist/rpc/request/print.js +12 -9
  182. package/dist/rpc/request/print.js.map +1 -1
  183. package/dist/rpc/rewrite-rpc.d.ts +3 -2
  184. package/dist/rpc/rewrite-rpc.d.ts.map +1 -1
  185. package/dist/rpc/rewrite-rpc.js +9 -3
  186. package/dist/rpc/rewrite-rpc.js.map +1 -1
  187. package/dist/rpc/server.d.ts.map +1 -1
  188. package/dist/rpc/server.js +19 -5
  189. package/dist/rpc/server.js.map +1 -1
  190. package/dist/search/is-source-file.d.ts +1 -0
  191. package/dist/search/is-source-file.d.ts.map +1 -1
  192. package/dist/search/is-source-file.js +7 -2
  193. package/dist/search/is-source-file.js.map +1 -1
  194. package/dist/test/java-rpc.d.ts.map +1 -1
  195. package/dist/test/java-rpc.js +3 -0
  196. package/dist/test/java-rpc.js.map +1 -1
  197. package/dist/test/rewrite-test.d.ts +5 -0
  198. package/dist/test/rewrite-test.d.ts.map +1 -1
  199. package/dist/test/rewrite-test.js +14 -3
  200. package/dist/test/rewrite-test.js.map +1 -1
  201. package/package.json +4 -3
  202. package/src/index.ts +3 -1
  203. package/src/java/formatting-utils.ts +5 -4
  204. package/src/java/rpc.ts +2 -0
  205. package/src/java/tree.ts +15 -0
  206. package/src/java/type.ts +19 -2
  207. package/src/javascript/add-import.ts +338 -152
  208. package/src/javascript/amd.ts +28 -12
  209. package/src/javascript/autodetect.ts +32 -41
  210. package/src/javascript/binding.ts +64 -22
  211. package/src/javascript/comparator.ts +129 -43
  212. package/src/javascript/format/format.ts +9 -0
  213. package/src/javascript/format/tabs-and-indents-visitor.ts +30 -3
  214. package/src/javascript/index.ts +6 -3
  215. package/src/javascript/markers.ts +77 -5
  216. package/src/javascript/method-matcher.ts +34 -22
  217. package/src/javascript/node-resolution-result.ts +3 -2
  218. package/src/javascript/package-name.ts +45 -0
  219. package/src/javascript/parser.ts +200 -91
  220. package/src/javascript/preconditions.ts +7 -4
  221. package/src/javascript/print.ts +123 -76
  222. package/src/javascript/recipes/change-method-target-to-import.ts +178 -0
  223. package/src/javascript/recipes/index.ts +1 -0
  224. package/src/javascript/scope.ts +705 -169
  225. package/src/javascript/search/uses-method.ts +2 -2
  226. package/src/javascript/search/uses-type.ts +30 -9
  227. package/src/javascript/templating/bindings.ts +13 -38
  228. package/src/javascript/templating/comparator.ts +16 -127
  229. package/src/javascript/templating/engine.ts +236 -129
  230. package/src/javascript/templating/pattern.ts +107 -28
  231. package/src/javascript/templating/placeholder-replacement.ts +154 -62
  232. package/src/javascript/templating/precedence.ts +33 -11
  233. package/src/javascript/templating/template.ts +35 -30
  234. package/src/javascript/templating/types.ts +6 -3
  235. package/src/javascript/templating/utils.ts +24 -2
  236. package/src/javascript/tree-debug.ts +9 -0
  237. package/src/javascript/tsconfig.ts +7 -17
  238. package/src/javascript/type-mapping.ts +277 -255
  239. package/src/javascript/type-report-cli.ts +96 -0
  240. package/src/javascript/type-report.ts +575 -0
  241. package/src/markers.ts +8 -0
  242. package/src/print.ts +23 -8
  243. package/src/recipe.ts +6 -0
  244. package/src/rpc/java-rpc-client.ts +3 -0
  245. package/src/rpc/queue.ts +80 -7
  246. package/src/rpc/request/get-object.ts +24 -0
  247. package/src/rpc/request/parse.ts +4 -1
  248. package/src/rpc/request/prepare-recipe.ts +16 -6
  249. package/src/rpc/request/print.ts +12 -8
  250. package/src/rpc/rewrite-rpc.ts +16 -5
  251. package/src/rpc/server.ts +18 -6
  252. package/src/search/is-source-file.ts +8 -3
  253. package/src/test/java-rpc.ts +3 -0
  254. package/src/test/rewrite-test.ts +14 -3
@@ -13,14 +13,63 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import {Cursor, isTree} from "../tree";
16
+ import {Cursor, isTree, Tree} from "../tree";
17
17
  import {J} from "../java";
18
+ import {UUID} from "../uuid";
18
19
  import {JS} from "./tree";
19
20
  // scope.ts sits below the visitor, so this stays type-only.
20
21
  import type {JavaScriptVisitor} from "./visitor";
21
22
 
22
23
  const noNames: ReadonlySet<string> = new Set();
23
24
 
25
+ /**
26
+ * What a name means where it is bound, a value, a type or a namespace, which is what TypeScript
27
+ * resolves a name against. A `const` binds a value, an interface a type, a class both, and the
28
+ * qualifier before a dot in a type reads a namespace, which an interface does not declare. A read
29
+ * with one meaning is hidden only by a binding with that meaning.
30
+ */
31
+ export type Meaning = 'value' | 'type' | 'namespace';
32
+
33
+ /** One name a scope binds, with every meaning it has there and the declarations giving it each. */
34
+ interface Binding {
35
+ meanings: ReadonlySet<Meaning>;
36
+ declarations: readonly Bound[];
37
+ }
38
+
39
+ type Bindings = ReadonlyMap<string, Binding>;
40
+
41
+ const noBindings: Bindings = new Map();
42
+
43
+ /** Whether `bindings` holds `name` with `meaning`, or with any meaning where none is asked for. */
44
+ function binds(bindings: Bindings, name: string, meaning?: Meaning): boolean {
45
+ const binding = bindings.get(name);
46
+ return binding !== undefined && (meaning === undefined || binding.meanings.has(meaning));
47
+ }
48
+
49
+ /** One name a declaration binds, as the identifier spelling it, and the meanings it has there. */
50
+ interface Bound {
51
+ identifier: J.Identifier;
52
+ meanings: readonly Meaning[];
53
+ }
54
+
55
+ function bound(identifiers: J.Identifier[], ...meanings: Meaning[]): Bound[] {
56
+ return identifiers.map(identifier => ({identifier, meanings}));
57
+ }
58
+
59
+ function toBindings(bound: Bound[]): Bindings {
60
+ const bindings = new Map<string, { meanings: Set<Meaning>; declarations: Bound[] }>();
61
+ for (const declaration of bound) {
62
+ const name = declaration.identifier.simpleName;
63
+ let held = bindings.get(name);
64
+ if (!held) {
65
+ bindings.set(name, held = {meanings: new Set(), declarations: []});
66
+ }
67
+ declaration.meanings.forEach(meaning => held.meanings.add(meaning));
68
+ held.declarations.push(declaration);
69
+ }
70
+ return bindings;
71
+ }
72
+
24
73
  /** One scope: the names it binds itself, the scopes around it, and what they answer together. */
25
74
  export interface Scope {
26
75
  /** The names this scope binds itself, which is not what it reaches: for that, ask `declares`. */
@@ -33,16 +82,17 @@ export interface Scope {
33
82
  */
34
83
  walk(visit: (scope: Scope) => boolean): void;
35
84
 
36
- /** Whether this scope or one enclosing it binds `name`. */
37
- declares(name: string): boolean;
85
+ /**
86
+ * Whether this scope or one enclosing it binds `name`. Any meaning counts unless one is asked
87
+ * for, since a new binding collides with a type of its name as much as with a value.
88
+ */
89
+ declares(name: string, meaning?: Meaning): boolean;
38
90
 
39
91
  /**
40
- * The node owning the innermost scope that binds `name` — the compilation unit for a
41
- * module-scope binding, otherwise the function, block or loop holding it — or undefined where
42
- * nothing in scope does. A caller holding a declaration asks whether this is the node it came
43
- * from: anything nearer shadows it.
92
+ * The node owning the innermost scope that binds `name`, or undefined. A caller holding a
93
+ * declaration compares this to the node it came from, since anything nearer shadows it.
44
94
  */
45
- declaringScope(name: string): J | undefined;
95
+ declaringScope(name: string, meaning?: Meaning): J | undefined;
46
96
  }
47
97
 
48
98
  /**
@@ -56,7 +106,7 @@ export function scopeOf(cursor: Cursor): Scope {
56
106
 
57
107
  function scopeAt(cursor: Cursor): Scope {
58
108
  return {
59
- names: () => frameBindings(cursor.value, cursor.parent?.value),
109
+ names: () => new Set(bindingsAt(cursor).keys()),
60
110
  walk: visit => {
61
111
  for (let c: Cursor | undefined = cursor; c; c = enclosingScopeCursor(c.parent)) {
62
112
  if (!visit(scopeAt(c))) {
@@ -64,8 +114,8 @@ function scopeAt(cursor: Cursor): Scope {
64
114
  }
65
115
  }
66
116
  },
67
- declares: name => declaringScopeOf(cursor, name) !== undefined,
68
- declaringScope: name => declaringScopeOf(cursor, name)
117
+ declares: (name, meaning) => declaringScopeOf(cursor, name, meaning) !== undefined,
118
+ declaringScope: (name, meaning) => declaringScopeOf(cursor, name, meaning)
69
119
  };
70
120
  }
71
121
 
@@ -80,8 +130,10 @@ function enclosingScopeCursor(from: Cursor | undefined): Cursor | undefined {
80
130
 
81
131
  /** The nodes {@link readBindings} answers for; everything else binds nothing and is not a scope. */
82
132
  const scopeKinds = new Set<string>([
83
- JS.Kind.CompilationUnit, J.Kind.Block, J.Kind.MethodDeclaration, J.Kind.Lambda,
84
- J.Kind.ClassDeclaration, J.Kind.TryCatch, J.Kind.ForLoop, J.Kind.ForEachLoop, JS.Kind.ForInLoop
133
+ JS.Kind.CompilationUnit, J.Kind.Block, J.Kind.MethodDeclaration, JS.Kind.ComputedPropertyMethodDeclaration,
134
+ J.Kind.Lambda, JS.Kind.ArrowFunction, J.Kind.ClassDeclaration, J.Kind.TryCatch, J.Kind.ForLoop,
135
+ J.Kind.ForEachLoop, JS.Kind.ForInLoop, JS.Kind.NamespaceDeclaration, JS.Kind.TypeDeclaration,
136
+ JS.Kind.FunctionType, JS.Kind.MappedType, JS.Kind.ConditionalType
85
137
  ]);
86
138
 
87
139
  /**
@@ -132,15 +184,20 @@ export function namesDeclaredWithin(node: unknown, cacheKey: object = node as ob
132
184
  }
133
185
 
134
186
  const names = new Set<string>();
135
- const collect = (node: any): boolean => {
136
- declarationNames(node).forEach(name => names.add(name));
137
- if (node.kind !== J.Kind.ClassDeclaration) {
187
+ const collect = (node: Tree): boolean => {
188
+ declarationNames(node).forEach(({identifier}) => names.add(identifier.simpleName));
189
+ const members = membersOf(node);
190
+ if (!members) {
138
191
  return true;
139
192
  }
140
- // The walk ends at this branch, so the class's own type parameters are read here.
141
- typeParameterNames(node).forEach(name => names.add(name));
193
+ // The walk ends at this branch, so what else the node holds is read here.
194
+ if (node.kind === J.Kind.ClassDeclaration) {
195
+ typeParameterNames(node).forEach(({identifier}) => names.add(identifier.simpleName));
196
+ } else if (node.kind === J.Kind.NewClass) {
197
+ walk((node as J.NewClass).arguments, collect);
198
+ }
142
199
  // A member's name is not one the file binds, though the code inside one still declares names.
143
- for (const member of (node as J.ClassDeclaration).body?.statements ?? []) {
200
+ for (const member of members.statements) {
144
201
  const element = unwrap(member);
145
202
  walk(element, node => node === element || collect(node));
146
203
  }
@@ -155,7 +212,8 @@ export function namesDeclaredWithin(node: unknown, cacheKey: object = node as ob
155
212
  /**
156
213
  * The names `node` reads and nothing within it binds, so each one reaches a binding further out.
157
214
  * A name its parent introduces rather than reads is not one, nor is a name an inner scope rebinds:
158
- * that reference reads the rebinding.
215
+ * that reference reads the rebinding. A value position is hidden only by a value binding, a type
216
+ * position only by a type.
159
217
  */
160
218
  export function namesReferencedWithin(node: unknown, cacheKey: object = node as object): ReadonlySet<string> {
161
219
  if (cacheKey === null || typeof cacheKey !== 'object') {
@@ -166,84 +224,280 @@ export function namesReferencedWithin(node: unknown, cacheKey: object = node as
166
224
  return cached;
167
225
  }
168
226
  const names = new Set<string>();
169
- collectReferences(node, undefined, undefined, names);
227
+ treesIn(node).forEach(tree => readReferences(tree, undefined, undefined, 'value', (identifier, reading, scopes) => {
228
+ if (reading !== undefined && !bindingFrame(scopes, identifier.simpleName, reading)) {
229
+ names.add(identifier.simpleName);
230
+ }
231
+ }));
170
232
  referenced.set(cacheKey, names);
171
233
  return names;
172
234
  }
173
235
 
174
236
  /** The scopes enclosing a node, innermost first, each paired with what {@link frameBindings} reads. */
175
237
  interface Frames {
176
- node: unknown;
177
- parent: unknown;
238
+ node: Tree;
239
+ parent: Tree | undefined;
178
240
  outer?: Frames;
179
241
  }
180
242
 
181
- /** Whether a scope between the name and where the walk began binds it. */
182
- function shadowed(scopes: Frames | undefined, name: string): boolean {
243
+ /** The innermost of `scopes` binding `name` as `reading` asks, or undefined where none does. */
244
+ function bindingFrame(
245
+ scopes: Frames | undefined, name: string, reading: Exclude<Reading, undefined>
246
+ ): Frames | undefined {
183
247
  for (let scope = scopes; scope; scope = scope.outer) {
184
- if (frameBindings(scope.node, scope.parent).has(name)) {
185
- return true;
248
+ if (binds(frameBindings(scope.node, scope.parent), name, meaningRead(reading))) {
249
+ return scope;
186
250
  }
187
251
  }
188
- return false;
252
+ return undefined;
189
253
  }
190
254
 
191
- function collectReferences(node: unknown, parent: unknown, scopes: Frames | undefined, names: Set<string>): void {
192
- if (Array.isArray(node)) {
193
- node.forEach(child => collectReferences(child, parent, scopes, names));
194
- return;
255
+ /**
256
+ * What an identifier reads with. That is one meaning, or `'any'`, which is how an export clause or
257
+ * an `import a = NS.b` reads, or `'write'`, a value it assigns to. Undefined is a slot that names
258
+ * rather than reads.
259
+ */
260
+ type Reading = Meaning | 'any' | 'write' | undefined;
261
+
262
+ /** The meaning a reading resolves against, or undefined for any. */
263
+ function meaningRead(reading: Exclude<Reading, undefined>): Meaning | undefined {
264
+ return reading === 'any' ? undefined : reading === 'write' ? 'value' : reading;
265
+ }
266
+
267
+ /**
268
+ * Told of each identifier a walk finds, with what it reads with and the scopes around it. `trail`
269
+ * holds the nodes from the walk's root down to it, and changes as the walk goes on.
270
+ */
271
+ type OnIdentifier =
272
+ (identifier: J.Identifier, reading: Reading, scopes: Frames | undefined, trail: readonly Tree[]) => void;
273
+
274
+ /**
275
+ * Walks `node` top-down, telling `onIdentifier` of every identifier. `reading` is what `node`
276
+ * itself reads with, or undefined under a slot that names rather than reads.
277
+ */
278
+ function readReferences(
279
+ node: Tree, parent: Tree | undefined, scopes: Frames | undefined, reading: Reading, onIdentifier: OnIdentifier,
280
+ trail: Tree[] = []
281
+ ): void {
282
+ trail.push(node);
283
+ if (node.kind === J.Kind.Identifier) {
284
+ onIdentifier(node as J.Identifier, reading, scopes, trail);
195
285
  }
196
- const kind = (node as { kind?: string } | undefined)?.kind;
197
- if (kind === J.Kind.RightPadded || kind === J.Kind.LeftPadded) {
198
- // A naming position reads against the tree parent, so padding forwards the one it was handed.
199
- collectReferences((node as J.RightPadded<any>).element, parent, scopes, names);
200
- return;
286
+ const within = framesAt(node, parent, scopes);
287
+ for (const [key, value] of Object.entries(node)) {
288
+ if (key !== 'markers') {
289
+ const [below, reads] = descend(node, parent, key, within, reading);
290
+ // A naming position reads against the tree parent, so the padding between is stepped over.
291
+ treesIn(value).forEach(child => readReferences(child, node, below, reads, onIdentifier, trail));
292
+ }
201
293
  }
202
- if (kind === J.Kind.Container) {
203
- collectReferences((node as J.Container<any>).elements, parent, scopes, names);
204
- return;
294
+ trail.pop();
295
+ }
296
+
297
+ /** The scopes a node sits in, with its own in front where it is one. */
298
+ function framesAt(node: Tree, parent: Tree | undefined, scopes: Frames | undefined): Frames | undefined {
299
+ return scopeKinds.has(node.kind) ? {node, parent, outer: scopes} : scopes;
300
+ }
301
+
302
+ /** The scopes the slot `key` of `node` resolves against, and what a read in it reads with. */
303
+ function descend(
304
+ node: Tree, parent: Tree | undefined, key: string, within: Frames | undefined, reading: Reading
305
+ ): [Frames | undefined, Reading] {
306
+ return [reachOf(node, parent, key, within), meaningOf(node, key, reading)];
307
+ }
308
+
309
+ /**
310
+ * The scopes the slot `key` of `node` resolves against, `within` being those `node` sits in. A
311
+ * conditional type's `infer` names reach its extends clause and true branch alone, so its check
312
+ * type and false branch read past them.
313
+ */
314
+ function reachOf(node: Tree, parent: Tree | undefined, key: string, within: Frames | undefined): Frames | undefined {
315
+ const pastInferred =
316
+ node.kind === JS.Kind.ConditionalType && key === 'checkType' ||
317
+ node.kind === J.Kind.Ternary && parent?.kind === JS.Kind.ConditionalType && key === 'falsePart';
318
+ return pastInferred ? within?.outer : within;
319
+ }
320
+
321
+ /**
322
+ * What a read in slot `key` of `node` reads with, given what `node` itself reads with. Undefined
323
+ * is a slot that names rather than reads, and so is everything under it. A whole import declares
324
+ * what it spells, and a re-export's clause names another module's members.
325
+ */
326
+ function meaningOf(node: Tree, key: string, reading: Reading): Reading {
327
+ // A computed key and a binding's default value read from within a naming slot.
328
+ if (node.kind === JS.Kind.ComputedPropertyName || node.kind === JS.Kind.BindingElement && key === 'initializer') {
329
+ return 'value';
205
330
  }
206
- if (!isTree(node)) {
207
- return;
331
+ if (reading === undefined) {
332
+ return undefined;
333
+ }
334
+ if (reading === 'write') {
335
+ return carriesWrite(node, key) ? 'write' : meaningOf(node, key, 'value');
336
+ }
337
+ if (key === 'typeParameters' || key === 'typeArguments') {
338
+ return 'type';
339
+ }
340
+ if (key === 'label' || key === namingSlots[node.kind] || key === 'name' && !readsItsName(node)) {
341
+ return undefined;
342
+ }
343
+ if (writes(node, key)) {
344
+ return 'write';
345
+ }
346
+ switch (node.kind) {
347
+ case JS.Kind.Import:
348
+ // `import a = NS.b` reads `NS` as whatever it is. The rest of an import declares.
349
+ return key === 'initializer' ? 'any' : undefined;
350
+ case JS.Kind.ExportDeclaration:
351
+ if (key !== 'exportClause') {
352
+ return reading;
353
+ }
354
+ return (node as JS.ExportDeclaration).moduleSpecifier === undefined ? 'any' : undefined;
355
+ case J.Kind.FieldAccess:
356
+ // In a type, `NS.Foo` reads the namespace `NS`, as `A.B.C` reads `A`.
357
+ return key === 'target' && reading === 'type' ? 'namespace' : reading;
358
+ case JS.Kind.TypeInfo:
359
+ case JS.Kind.TypeDeclaration:
360
+ case J.Kind.TypeParameter:
361
+ return 'type';
362
+ case JS.Kind.TypeQuery:
363
+ // `typeof x` reads the value `x` from within a type.
364
+ return key === 'typeExpression' ? 'value' : reading;
365
+ case JS.Kind.TypePredicate:
366
+ // `x is Foo` names the parameter `x`, a value, from within a return type.
367
+ return key === 'parameterName' ? 'value' : reading;
368
+ case JS.Kind.IndexSignatureDeclaration:
369
+ return key === 'typeExpression' ? 'type' : reading;
370
+ case JS.Kind.As:
371
+ return key === 'right' ? 'type' : reading;
372
+ case JS.Kind.SatisfiesExpression:
373
+ return key === 'satisfiesType' ? 'type' : reading;
374
+ case J.Kind.TypeCast:
375
+ return key === 'class' ? 'type' : reading;
376
+ case J.Kind.ClassDeclaration:
377
+ if ((node as J.ClassDeclaration).classKind.type === J.ClassDeclaration.Kind.Type.Interface) {
378
+ return 'type';
379
+ }
380
+ return key === 'implements' ? 'type' : reading;
381
+ default:
382
+ return reading;
208
383
  }
209
- const name = kind === J.Kind.Identifier ? (node as J.Identifier).simpleName : undefined;
210
- if (name && reads(node as J.Identifier, parent) && !shadowed(scopes, name)) {
211
- names.add(name);
384
+ }
385
+
386
+ /** Whether slot `key` of `node` is one {@link isWrite} counts as assigned to. A declaring loop head names instead. */
387
+ function writes(node: Tree, key: string): boolean {
388
+ switch (node.kind) {
389
+ case J.Kind.Assignment:
390
+ case J.Kind.AssignmentOperation:
391
+ case JS.Kind.AssignmentOperation:
392
+ return key === 'variable';
393
+ case J.Kind.Unary:
394
+ return key === 'expression' && incrementing.has((node as J.Unary).operator.element);
395
+ case J.Kind.ForEachLoopControl:
396
+ return key === 'variable';
397
+ default:
398
+ return false;
399
+ }
400
+ }
401
+
402
+ const incrementing = new Set<J.Unary.Type>([
403
+ J.Unary.Type.PreIncrement, J.Unary.Type.PreDecrement, J.Unary.Type.PostIncrement, J.Unary.Type.PostDecrement
404
+ ]);
405
+
406
+ /**
407
+ * Whether slot `key` of `node` passes a write on to what it holds, which parentheses, a type
408
+ * assertion and the shapes of a destructuring target do. Anything else under a target reads, as
409
+ * `x` does in `x.y = 1`.
410
+ */
411
+ function carriesWrite(node: Tree, key: string): boolean {
412
+ switch (node.kind) {
413
+ case J.Kind.Parentheses:
414
+ return key === 'tree';
415
+ case JS.Kind.As:
416
+ return key === 'left';
417
+ case JS.Kind.ExpressionStatement:
418
+ case JS.Kind.Spread:
419
+ case JS.Kind.SatisfiesExpression:
420
+ case J.Kind.TypeCast:
421
+ return key === 'expression';
422
+ case J.Kind.NewArray:
423
+ return key === 'initializer';
424
+ case J.Kind.NewClass:
425
+ return key === 'body';
426
+ case J.Kind.Block:
427
+ return key === 'statements';
428
+ case JS.Kind.PropertyAssignment:
429
+ return key === (isShorthand(node) ? 'name' : 'initializer');
430
+ case JS.Kind.ArrayBindingPattern:
431
+ return key === 'elements';
432
+ case JS.Kind.ObjectBindingPattern:
433
+ return key === 'bindings';
434
+ case JS.Kind.BindingElement:
435
+ return key === 'name';
436
+ default:
437
+ return false;
212
438
  }
213
- const within = scopeKinds.has(kind!) ? {node, parent, outer: scopes} : scopes;
214
- Object.entries(node as object).forEach(([key, value]) =>
215
- key !== 'markers' && collectReferences(value, node, within, names));
216
439
  }
217
440
 
441
+ /**
442
+ * Whether a node's `name` slot reads rather than names. A call names a member of whatever it
443
+ * selects from, so with nothing selected its name is the function called, and a shorthand `{x}`
444
+ * reads the `x` it also names.
445
+ */
446
+ function readsItsName(node: Tree): boolean {
447
+ return node.kind === J.Kind.MethodInvocation && !(node as J.MethodInvocation).select || isShorthand(node);
448
+ }
449
+
450
+ /** A shorthand property `{x}` or `{x = 1}`, which reads the `x` it names. */
451
+ function isShorthand(node: Tree | undefined): boolean {
452
+ return node?.kind === JS.Kind.PropertyAssignment &&
453
+ (node as JS.PropertyAssignment).assigmentToken === JS.PropertyAssignment.Token.Equals;
454
+ }
455
+
456
+ /** The slot, beside `name` and `label`, in which a node names rather than reads. */
457
+ const namingSlots: Record<string, string> = {
458
+ [JS.Kind.Alias]: 'alias',
459
+ [JS.Kind.BindingElement]: 'propertyName',
460
+ [JS.Kind.ImportType]: 'qualifier',
461
+ [JS.Kind.JsxAttribute]: 'key'
462
+ };
463
+
218
464
  /**
219
465
  * Every name a binding pattern introduces. `member` is the property a name takes its value from,
220
466
  * which only a name an object pattern binds directly has: anything deeper reads a property of a
221
467
  * property, an array element is chosen by position, and a rest name gathers what nothing claimed.
222
468
  */
223
469
  export function bindingNames(pattern: J | undefined): { name: string; member?: string }[] {
470
+ return patternBindings(pattern).map(({identifier, member}) =>
471
+ member === undefined ? {name: identifier.simpleName} : {name: identifier.simpleName, member});
472
+ }
473
+
474
+ /** As {@link bindingNames}, with the identifier each name is read from. */
475
+ function patternBindings(pattern: J | undefined): { identifier: J.Identifier; member?: string }[] {
224
476
  switch (pattern?.kind) {
225
477
  case J.Kind.Identifier: {
226
- const simpleName = (pattern as J.Identifier).simpleName;
227
- return simpleName ? [{name: simpleName}] : [];
478
+ const identifier = pattern as J.Identifier;
479
+ return identifier.simpleName ? [{identifier}] : [];
228
480
  }
229
481
  case JS.Kind.Spread:
230
- return bindingNames((pattern as JS.Spread).expression);
482
+ return patternBindings((pattern as JS.Spread).expression);
231
483
  case JS.Kind.ArrayBindingPattern:
232
484
  return unnamedMembers((pattern as JS.ArrayBindingPattern).elements.elements
233
- .flatMap(element => bindingNames(unwrap(element))));
485
+ .flatMap(element => patternBindings(unwrap(element))));
234
486
  case JS.Kind.ObjectBindingPattern:
235
487
  return (pattern as JS.ObjectBindingPattern).bindings.elements
236
- .flatMap(element => bindingNames(unwrap(element)));
488
+ .flatMap(element => patternBindings(unwrap(element)));
237
489
  case JS.Kind.BindingElement: {
238
490
  const element = pattern as JS.BindingElement;
239
491
  if (element.name?.kind !== J.Kind.Identifier) {
240
- return unnamedMembers(bindingNames(element.name as J));
492
+ return unnamedMembers(patternBindings(element.name as J));
241
493
  }
242
- const name = (element.name as J.Identifier).simpleName;
494
+ const identifier = element.name as J.Identifier;
243
495
  const propertyName = unwrap(element.propertyName);
244
496
  return [{
245
- name,
246
- member: propertyName?.kind === J.Kind.Identifier ? (propertyName as J.Identifier).simpleName : name
497
+ identifier,
498
+ member: propertyName?.kind === J.Kind.Identifier
499
+ ? (propertyName as J.Identifier).simpleName
500
+ : identifier.simpleName
247
501
  }];
248
502
  }
249
503
  default:
@@ -251,79 +505,132 @@ export function bindingNames(pattern: J | undefined): { name: string; member?: s
251
505
  }
252
506
  }
253
507
 
254
- function unnamedMembers(bound: { name: string }[]): { name: string }[] {
255
- return bound.map(({name}) => ({name}));
508
+ function boundIdentifiers(pattern: J | undefined): J.Identifier[] {
509
+ return patternBindings(pattern).map(bound => bound.identifier);
510
+ }
511
+
512
+ function unnamedMembers(bound: { identifier: J.Identifier }[]): { identifier: J.Identifier }[] {
513
+ return bound.map(({identifier}) => ({identifier}));
256
514
  }
257
515
 
258
- function declaringScopeOf(cursor: Cursor, name: string): J | undefined {
516
+ /**
517
+ * The block of members a class, object literal or type literal holds. A member is reached through
518
+ * the value or type holding it, so its name binds nowhere.
519
+ */
520
+ function membersOf(node: Tree | undefined): J.Block | undefined {
521
+ switch (node?.kind) {
522
+ case J.Kind.ClassDeclaration:
523
+ return (node as J.ClassDeclaration).body;
524
+ case J.Kind.NewClass:
525
+ return (node as J.NewClass).body;
526
+ case JS.Kind.TypeLiteral:
527
+ return (node as JS.TypeLiteral).members;
528
+ default:
529
+ return undefined;
530
+ }
531
+ }
532
+
533
+ function declaringScopeOf(cursor: Cursor, name: string, meaning?: Meaning): J | undefined {
259
534
  for (let c: Cursor | undefined = cursor; c; c = c.parent) {
260
- if (frameBindings(c.value, c.parent?.value).has(name)) {
535
+ if (binds(bindingsAt(c), name, meaning)) {
261
536
  return c.value;
262
537
  }
263
538
  }
264
539
  return undefined;
265
540
  }
266
541
 
542
+ /** What the node a cursor stands on binds, which is nothing where it stands on padding or the root. */
543
+ function bindingsAt(cursor: Cursor): Bindings {
544
+ const parent = cursor.parent?.value;
545
+ return isTree(cursor.value) ? frameBindings(cursor.value, isTree(parent) ? parent : undefined) : noBindings;
546
+ }
547
+
267
548
  /**
268
549
  * What one node on the cursor path binds, `parent` being the node it hangs from. Only these two
269
550
  * answers turn on the parent; a node alone settles the rest, which is what makes them cacheable.
270
551
  */
271
- function frameBindings(node: any, parent: any): ReadonlySet<string> {
272
- // A class body holds members, which are reached through an instance rather than by name.
273
- if (node?.kind === J.Kind.Block && parent?.kind === J.Kind.ClassDeclaration) {
274
- return noNames;
552
+ function frameBindings(node: Tree, parent: Tree | undefined): Bindings {
553
+ if (node.kind === J.Kind.Block && membersOf(parent) === node) {
554
+ // An enum's members are in scope in its body, where an initializer may name an earlier one.
555
+ return isEnum(parent) ? toBindings(enumMemberNames(node as J.Block)) : noBindings;
556
+ }
557
+ // An arrow binds its lambda's parameters itself, since its return type reads them.
558
+ if (node.kind === J.Kind.Lambda && parent?.kind === JS.Kind.ArrowFunction) {
559
+ return noBindings;
275
560
  }
276
561
  // A function expression is the only function whose own name its body reaches: a declaration's
277
562
  // name belongs to the enclosing block, a method's to an instance.
278
- if (node?.kind === J.Kind.MethodDeclaration && parent?.kind === JS.Kind.StatementExpression) {
279
- let names = selfNamed.get(node);
280
- if (!names) {
281
- const self = bindingNames((node as J.MethodDeclaration).name).map(bound => bound.name);
282
- selfNamed.set(node, names = new Set([...self, ...ownBindings(node)]));
563
+ if (node.kind === J.Kind.MethodDeclaration && parent?.kind === JS.Kind.StatementExpression) {
564
+ let bindings = selfNamed.get(node);
565
+ if (!bindings) {
566
+ const self = bound(boundIdentifiers((node as J.MethodDeclaration).name), 'value');
567
+ selfNamed.set(node, bindings = toBindings([...self, ...readBindings(node)]));
283
568
  }
284
- return names;
569
+ return bindings;
285
570
  }
286
571
  return ownBindings(node);
287
572
  }
288
573
 
289
- function ownBindings(node: any): ReadonlySet<string> {
290
- if (typeof node !== 'object' || node === null) {
291
- return noNames;
574
+ function ownBindings(node: Tree): Bindings {
575
+ let bindings = frames.get(node);
576
+ if (!bindings) {
577
+ frames.set(node, bindings = toBindings(readBindings(node)));
292
578
  }
293
- let names = frames.get(node);
294
- if (!names) {
295
- frames.set(node, names = new Set(readBindings(node)));
296
- }
297
- return names;
579
+ return bindings;
298
580
  }
299
581
 
300
-
301
- function readBindings(node: any): string[] {
582
+ function readBindings(node: Tree): Bound[] {
302
583
  switch (node.kind) {
303
584
  case JS.Kind.CompilationUnit: {
304
585
  const statements = (node as JS.CompilationUnit).statements;
305
586
  return [...declaredNames(statements), ...hoistedNames(statements)];
306
587
  }
307
- case J.Kind.Block:
308
- return blockScopedNames((node as J.Block).statements);
309
- case J.Kind.MethodDeclaration: {
310
- const method = node as J.MethodDeclaration;
311
- return [...declaredNames(method.parameters.elements), ...hoistedNames(method.body)];
588
+ case J.Kind.Block: {
589
+ const block = node as J.Block;
590
+ // A class static block is a var scope, as a function body is.
591
+ return block.static.element
592
+ ? [...blockScopedNames(block.statements), ...hoistedNames(block.statements)]
593
+ : blockScopedNames(block.statements);
594
+ }
595
+ case J.Kind.MethodDeclaration:
596
+ case JS.Kind.ComputedPropertyMethodDeclaration: {
597
+ const method = node as J.MethodDeclaration | JS.ComputedPropertyMethodDeclaration;
598
+ return [
599
+ ...typeParameterNames(method),
600
+ ...declaredNames(method.parameters.elements),
601
+ ...hoistedNames(method.body)
602
+ ];
312
603
  }
313
604
  case J.Kind.Lambda: {
314
605
  const lambda = node as J.Lambda;
315
606
  return [...declaredNames(lambda.parameters.parameters), ...hoistedNames(lambda.body)];
316
607
  }
317
608
  case J.Kind.ClassDeclaration:
318
- return bindingNames((node as J.ClassDeclaration).name).map(bound => bound.name);
609
+ return [...declarationNames(node), ...typeParameterNames(node)];
610
+ // A namespace is where the functions and `var`s in its body hoist to, its block binding the rest.
611
+ case JS.Kind.NamespaceDeclaration:
612
+ return isGlobalAugmentation(node as JS.NamespaceDeclaration)
613
+ ? []
614
+ : hoistedNames((node as JS.NamespaceDeclaration).body);
615
+ case JS.Kind.ArrowFunction:
616
+ return [...typeParameterNames(node), ...readBindings((node as JS.ArrowFunction).lambda)];
617
+ case JS.Kind.TypeDeclaration:
618
+ return typeParameterNames(node);
619
+ case JS.Kind.FunctionType:
620
+ return [...typeParameterNames(node), ...declaredNames((node as JS.FunctionType).parameters.elements)];
621
+ case JS.Kind.MappedType:
622
+ return declarationNames(unwrap((node as JS.MappedType).keysRemapping.typeParameter));
623
+ case JS.Kind.ConditionalType:
624
+ return inferredNames((node as JS.ConditionalType).condition.element.condition);
319
625
  case J.Kind.TryCatch:
320
626
  return declaredNames([(node as J.Try.Catch).parameter.tree]);
627
+ // A loop head's `var` hoists past the loop, as one anywhere in a function body does.
321
628
  case J.Kind.ForLoop:
322
- return declaredNames((node as J.ForLoop).control.init);
629
+ return blockScopedNames((node as J.ForLoop).control.init);
323
630
  case J.Kind.ForEachLoop:
324
- return declaredNames([(node as J.ForEachLoop).control.variable]);
631
+ return blockScopedNames([(node as J.ForEachLoop).control.variable]);
325
632
  case JS.Kind.ForInLoop:
326
- return declaredNames([(node as JS.ForInLoop).control.variable]);
633
+ return blockScopedNames([(node as JS.ForInLoop).control.variable]);
327
634
  default:
328
635
  return [];
329
636
  }
@@ -333,7 +640,7 @@ function readBindings(node: any): string[] {
333
640
  * The names statements bind in the block holding them — the complement of what
334
641
  * {@link hoistedNames} claims, so `function (x) { var x = 1; }` reads as one binding, not two.
335
642
  */
336
- function blockScopedNames(statements: any[]): string[] {
643
+ function blockScopedNames(statements: readonly unknown[]): Bound[] {
337
644
  return statements.flatMap(statement => {
338
645
  const element = unwrap(statement);
339
646
  switch (element?.kind) {
@@ -350,37 +657,57 @@ function blockScopedNames(statements: any[]): string[] {
350
657
  });
351
658
  }
352
659
 
353
- function isBlockScoped(declaration: any): boolean {
354
- return ((declaration.modifiers ?? []) as J.Modifier[]).some(m => blockScoped.has(m.keyword!));
660
+ function isBlockScoped(declaration: Tree): boolean {
661
+ return ((declaration as J.VariableDeclarations).modifiers ?? []).some(m => blockScoped.has(m.keyword!));
355
662
  }
356
663
 
357
664
  /** The names statements declare directly in the scope holding them. */
358
- function declaredNames(statements: any[]): string[] {
665
+ function declaredNames(statements: readonly unknown[]): Bound[] {
359
666
  return statements.flatMap(statement => declarationNames(unwrap(statement)));
360
667
  }
361
668
 
362
- function declarationNames(statement: any): string[] {
669
+ /** The names a declaration binds, each with the meanings it has there. */
670
+ function declarationNames(statement: Tree | undefined): Bound[] {
363
671
  switch (statement?.kind) {
364
672
  case JS.Kind.Import:
365
673
  return importNames(statement as JS.Import);
366
674
  case J.Kind.VariableDeclarations:
367
- return (statement as J.VariableDeclarations).variables
368
- .flatMap(variable => bindingNames(unwrap(variable)?.name))
369
- .map(bound => bound.name);
675
+ return bound((statement as J.VariableDeclarations).variables.flatMap(variable => {
676
+ const named = unwrap(variable) as J.VariableDeclarations.NamedVariable | undefined;
677
+ return boundIdentifiers(named?.name);
678
+ }), 'value');
370
679
  case JS.Kind.ScopedVariableDeclarations:
371
680
  return declaredNames((statement as JS.ScopedVariableDeclarations).variables);
372
681
  case J.Kind.MethodDeclaration:
373
- return bindingNames((statement as J.MethodDeclaration).name).map(bound => bound.name);
374
- case J.Kind.ClassDeclaration:
375
- return bindingNames((statement as J.ClassDeclaration).name).map(bound => bound.name);
376
- case JS.Kind.NamespaceDeclaration:
377
- return bindingNames(unwrap((statement as JS.NamespaceDeclaration).name)).map(bound => bound.name);
682
+ return bound(boundIdentifiers((statement as J.MethodDeclaration).name), 'value');
683
+ case J.Kind.ClassDeclaration: {
684
+ const declaration = statement as J.ClassDeclaration;
685
+ switch (declaration.classKind.type) {
686
+ case J.ClassDeclaration.Kind.Type.Interface:
687
+ return bound(boundIdentifiers(declaration.name), 'type');
688
+ case J.ClassDeclaration.Kind.Type.Enum:
689
+ // An enum's members are read as `E.A` in a type, so it is a namespace too.
690
+ return bound(boundIdentifiers(declaration.name), 'value', 'type', 'namespace');
691
+ default:
692
+ return bound(boundIdentifiers(declaration.name), 'value', 'type');
693
+ }
694
+ }
695
+ case JS.Kind.NamespaceDeclaration: {
696
+ const declaration = statement as JS.NamespaceDeclaration;
697
+ if (isGlobalAugmentation(declaration)) {
698
+ return [];
699
+ }
700
+ const names = boundIdentifiers(rootName(unwrap(declaration.name)));
701
+ return isInstantiated(declaration) ? bound(names, 'namespace', 'value') : bound(names, 'namespace');
702
+ }
378
703
  case JS.Kind.TypeDeclaration:
379
- return bindingNames(unwrap((statement as JS.TypeDeclaration).name)).map(bound => bound.name);
704
+ return bound(boundIdentifiers(unwrap((statement as JS.TypeDeclaration).name)), 'type');
380
705
  case J.Kind.TypeParameter:
381
- // Reached by namesDeclaredWithin's walk, never through a statement list: a type
382
- // parameter binds across the declaration carrying it, not in the scope that one sits in.
383
- return bindingNames((statement as J.TypeParameter).name).map(bound => bound.name);
706
+ // A type parameter binds across the declaration carrying it, not in the scope that one
707
+ // sits in, so no statement list leads here.
708
+ return bound(boundIdentifiers((statement as J.TypeParameter).name), 'type');
709
+ case JS.Kind.MappedTypeParameter:
710
+ return bound(boundIdentifiers((statement as JS.MappedType.Parameter).name as J), 'type');
384
711
  case J.Kind.Case:
385
712
  // The cases of a switch share the block it opens, so each one's declarations bind in all.
386
713
  return declaredNames((statement as J.Case).statements.elements);
@@ -389,25 +716,93 @@ function declarationNames(statement: any): string[] {
389
716
  }
390
717
  }
391
718
 
719
+ /**
720
+ * Whether a namespace binds a value, which it does once its body holds something other than
721
+ * interfaces, type aliases, unexported imports and namespaces that do not. That is the binder's
722
+ * rule, under which a `const enum` counts, whatever the emitter later drops.
723
+ */
724
+ function isInstantiated(declaration: JS.NamespaceDeclaration): boolean {
725
+ return (declaration.body?.statements ?? []).some(statement => {
726
+ const element = unwrap(statement);
727
+ switch (element?.kind) {
728
+ case JS.Kind.TypeDeclaration:
729
+ return false;
730
+ case J.Kind.ClassDeclaration:
731
+ return (element as J.ClassDeclaration).classKind.type !== J.ClassDeclaration.Kind.Type.Interface;
732
+ case JS.Kind.Import:
733
+ return (element as JS.Import).modifiers.some(modifier => modifier.keyword === 'export');
734
+ case JS.Kind.NamespaceDeclaration:
735
+ return isInstantiated(element as JS.NamespaceDeclaration);
736
+ default:
737
+ return true;
738
+ }
739
+ });
740
+ }
741
+
742
+ /** `declare global { … }`, whose declarations the whole file sees, as if written at its top. */
743
+ function isGlobalAugmentation(declaration: JS.NamespaceDeclaration): boolean {
744
+ const name = unwrap(declaration.name);
745
+ return declaration.keywordType.element === JS.NamespaceDeclaration.KeywordType.Empty &&
746
+ name?.kind === J.Kind.Identifier && (name as J.Identifier).simpleName === 'global';
747
+ }
748
+
749
+ function isEnum(node: Tree | undefined): boolean {
750
+ return node?.kind === J.Kind.ClassDeclaration &&
751
+ (node as J.ClassDeclaration).classKind.type === J.ClassDeclaration.Kind.Type.Enum;
752
+ }
753
+
754
+ function enumMemberNames(body: J.Block): Bound[] {
755
+ return body.statements.flatMap(statement => {
756
+ const element = unwrap(statement);
757
+ return element?.kind === J.Kind.EnumValueSet
758
+ ? bound((element as J.EnumValueSet).enums.map(value => value.element.name), 'value')
759
+ : [];
760
+ });
761
+ }
762
+
763
+ /** The identifier a dotted name `A.B.C` declares, which is `A`: the rest are members of it. */
764
+ function rootName(name: J | undefined): J | undefined {
765
+ return name?.kind === J.Kind.FieldAccess ? rootName((name as J.FieldAccess).target) : name;
766
+ }
767
+
392
768
  /**
393
769
  * The names a declaration's type parameters bind. A class keeps them in a container and everything
394
770
  * else in a `J.TypeParameters`, and both hold the same right-padded list.
395
771
  */
396
- function typeParameterNames(node: any): string[] {
397
- const held = node?.typeParameters;
772
+ function typeParameterNames(node: Tree): Bound[] {
773
+ const held = (node as { typeParameters?: J.TypeParameters | J.Container<J.TypeParameter> }).typeParameters;
398
774
  const parameters = held?.kind === J.Kind.TypeParameters
399
775
  ? (held as J.TypeParameters).typeParameters
400
776
  : (held as J.Container<J.TypeParameter> | undefined)?.elements;
401
777
  return (parameters ?? []).flatMap(parameter => declarationNames(unwrap(parameter)));
402
778
  }
403
779
 
404
- /** The names an import binds, which for an aliased or namespace specifier is the alias. */
405
- function importNames(jsImport: JS.Import): string[] {
780
+ /**
781
+ * The names the `infer` declarations in a conditional type's extends clause bind. A conditional
782
+ * type nested in the clause keeps its own `infer` names to itself.
783
+ */
784
+ function inferredNames(extendsType: unknown): Bound[] {
785
+ const names: Bound[] = [];
786
+ walk(extendsType, node => {
787
+ if (node.kind === JS.Kind.InferType) {
788
+ names.push(...declarationNames(unwrap((node as JS.InferType).typeParameter)));
789
+ }
790
+ return node.kind !== JS.Kind.ConditionalType;
791
+ });
792
+ return names;
793
+ }
794
+
795
+ /**
796
+ * The names an import binds, which for an aliased or namespace specifier is the alias, each with
797
+ * every meaning. A `type` import is no exception: `typeof X` reads it as a value from within a
798
+ * type, and a value use outside one is the checker's error rather than a read of a free name.
799
+ */
800
+ function importNames(jsImport: JS.Import): Bound[] {
406
801
  const importClause = jsImport.importClause;
407
802
  if (!importClause) {
408
803
  return [];
409
804
  }
410
- const names = bindingNames(unwrap(importClause.name)).map(bound => bound.name);
805
+ const names = boundIdentifiers(unwrap(importClause.name));
411
806
  const namedBindings = importClause.namedBindings;
412
807
  if (namedBindings?.kind === JS.Kind.NamedImports) {
413
808
  for (const element of (namedBindings as JS.NamedImports).elements.elements) {
@@ -419,12 +814,12 @@ function importNames(jsImport: JS.Import): string[] {
419
814
  } else {
420
815
  names.push(...aliasedName(namedBindings));
421
816
  }
422
- return names;
817
+ return bound(names, 'value', 'type', 'namespace');
423
818
  }
424
819
 
425
- function aliasedName(specifier: J | undefined): string[] {
820
+ function aliasedName(specifier: J | undefined): J.Identifier[] {
426
821
  const name = specifier?.kind === JS.Kind.Alias ? (specifier as JS.Alias).alias : specifier;
427
- return bindingNames(name).map(bound => bound.name);
822
+ return boundIdentifiers(name);
428
823
  }
429
824
 
430
825
  const blockScoped = new Set(['let', 'const', 'using']);
@@ -433,19 +828,20 @@ const blockScoped = new Set(['let', 'const', 'using']);
433
828
  // replaced one is walked afresh: every call site in a function asks what that body hoists, every
434
829
  // import added to a file asks what that file declares, and every scope is asked what it binds by
435
830
  // each of the call sites it encloses.
436
- const hoisted = new WeakMap<object, string[]>();
831
+ const hoisted = new WeakMap<object, Bound[]>();
437
832
  const declared = new WeakMap<object, ReadonlySet<string>>();
438
833
  const used = new WeakMap<object, ReadonlySet<string>>();
439
834
  const referenced = new WeakMap<object, ReadonlySet<string>>();
440
- const frames = new WeakMap<object, ReadonlySet<string>>();
441
- const selfNamed = new WeakMap<object, ReadonlySet<string>>();
835
+ const slots = new WeakMap<Tree, Map<UUID, string>>();
836
+ const frames = new WeakMap<object, Bindings>();
837
+ const selfNamed = new WeakMap<object, Bindings>();
442
838
 
443
839
  /**
444
840
  * The names blocks under `scope` hoist out to it. A `var` or function declaration reaches the whole
445
841
  * function it sits in, so one nested in a block is in scope outside that block; only a `let`, `const`
446
842
  * or `using` keyword says otherwise.
447
843
  */
448
- function hoistedNames(scope: any): string[] {
844
+ function hoistedNames(scope: unknown): Bound[] {
449
845
  if (typeof scope !== 'object' || scope === null) {
450
846
  return [];
451
847
  }
@@ -454,12 +850,14 @@ function hoistedNames(scope: any): string[] {
454
850
  return cached;
455
851
  }
456
852
 
457
- const names: string[] = [];
458
- const collect = (node: any): boolean => {
853
+ const names: Bound[] = [];
854
+ const collect = (node: Tree): boolean => {
459
855
  switch (node.kind) {
460
856
  case J.Kind.MethodDeclaration:
461
857
  names.push(...declarationNames(node));
462
858
  return false;
859
+ case JS.Kind.ComputedPropertyMethodDeclaration:
860
+ return false;
463
861
  case J.Kind.VariableDeclarations:
464
862
  case JS.Kind.ScopedVariableDeclarations:
465
863
  if (!isBlockScoped(node)) {
@@ -473,8 +871,14 @@ function hoistedNames(scope: any): string[] {
473
871
  case JS.Kind.StatementExpression:
474
872
  // A function or class used as an expression names itself for its own body alone.
475
873
  return false;
874
+ case JS.Kind.NamespaceDeclaration:
875
+ // A namespace keeps its hoisting to itself, a global augmentation gives it to the file.
876
+ return isGlobalAugmentation(node as JS.NamespaceDeclaration);
476
877
  case J.Kind.Lambda:
477
878
  case J.Kind.ClassDeclaration:
879
+ case J.Kind.NewClass:
880
+ case JS.Kind.TypeLiteral:
881
+ // Nothing in an expression or a type hoists past it.
478
882
  return false;
479
883
  default:
480
884
  return true;
@@ -491,7 +895,7 @@ function hoistedNames(scope: any): string[] {
491
895
  * into, which a `JavaType` is not, so the walk stays in the tree the source spells rather than
492
896
  * entering the type graph — cyclic and shared, where it would not terminate.
493
897
  */
494
- export function walk(node: unknown, visit: (node: any) => boolean): void {
898
+ export function walk(node: unknown, visit: (node: Tree) => boolean): void {
495
899
  if (Array.isArray(node)) {
496
900
  node.forEach(child => walk(child, visit));
497
901
  return;
@@ -507,9 +911,13 @@ export function walk(node: unknown, visit: (node: any) => boolean): void {
507
911
  }
508
912
  }
509
913
 
510
- /** The element a padding wrapper holds, or the node itself. */
511
- function unwrap(node: any): any {
512
- return node?.kind === J.Kind.RightPadded || node?.kind === J.Kind.LeftPadded ? unwrap(node.element) : node;
914
+ /** The tree a padding wrapper holds, or the node itself where it is a tree. */
915
+ function unwrap(node: unknown): J | undefined {
916
+ const kind = (node as { kind?: string } | undefined)?.kind;
917
+ if (kind === J.Kind.RightPadded || kind === J.Kind.LeftPadded) {
918
+ return unwrap((node as { element?: unknown }).element);
919
+ }
920
+ return isTree(node) ? node as J : undefined;
513
921
  }
514
922
 
515
923
  /** `cursor` is protected on `TreeVisitor` and these APIs are free functions, so reaching it takes a cast. */
@@ -554,60 +962,188 @@ export function declarationsOf(statement: J | undefined): J.VariableDeclarations
554
962
  }
555
963
 
556
964
  /**
557
- * Whether the identifier references a binding rather than naming one, which is what tells a rename
558
- * which occurrences to follow. A name its parent introduces does not — a property, a method, a
559
- * variable, a type parameter — nor one drawn from a namespace of its own: a statement label,
560
- * declaring (`x:`) or referencing (`break x`), and a JSX attribute's prop. Position is all this
561
- * reads: an import specifier's own name answers true, and a type position reads alike to a value.
562
- */
563
- export function isValueReference(cursor: Cursor, identifier: J.Identifier): boolean {
564
- let c: Cursor | undefined = cursor.parent;
565
- while (c && isPadding(c.value)) {
566
- c = c.parent;
567
- }
568
- return references(identifier, c?.value);
569
- }
570
-
571
- /** The position half of {@link isValueReference}, against a parent already found. */
572
- function references(identifier: J.Identifier, parent: unknown): boolean {
573
- const owner = parent as {
574
- kind?: string; name?: unknown; key?: unknown; label?: unknown; select?: unknown;
575
- propertyName?: unknown;
576
- } | undefined;
577
-
578
- // A call names a member of whatever it selects from. With nothing selected there is no member,
579
- // and its `name` is a reference to the function being called.
580
- if (owner?.kind === J.Kind.MethodInvocation && !owner.select) {
581
- return true;
965
+ * Whether the identifier may be renamed in place, which is so where its slot reads rather than
966
+ * names. A shorthand property `{x}` answers false though it reads `x`, since a rename has to
967
+ * expand it to `{x: y}`. Position is all this reads, so a type position reads alike to a value,
968
+ * and what `x` reads is {@link resolve}'s question.
969
+ */
970
+ export function isReference(cursor: Cursor, identifier: J.Identifier): boolean {
971
+ const path = pathTo(cursor, identifier);
972
+ return !isShorthand(path[path.length - 1]) && position(path, identifier)[1] !== undefined;
973
+ }
974
+
975
+ /**
976
+ * The tree nodes from the nearest compilation unit on `cursor`, or its outermost tree, down to the
977
+ * one holding `identifier`, which is the cursor's own node or that node's parent.
978
+ */
979
+ function pathTo(cursor: Cursor, identifier: J.Identifier): Tree[] {
980
+ const path = treePath(cursor);
981
+ if (path[path.length - 1] === identifier) {
982
+ path.pop();
582
983
  }
984
+ return path;
985
+ }
583
986
 
584
- // A binding element names the property it destructures and binds under `name`, so both slots
585
- // name rather than reference.
586
- if (owner?.kind === JS.Kind.BindingElement && holds(owner.propertyName, identifier)) {
587
- return false;
987
+ /** The cursors on `cursor`'s chain holding tree nodes, down from the nearest compilation unit or outermost tree. */
988
+ function treeCursors(cursor: Cursor): Cursor[] {
989
+ const cursors: Cursor[] = [];
990
+ for (let c: Cursor | undefined = cursor; c; c = c.parent) {
991
+ if (isTree(c.value)) {
992
+ cursors.unshift(c);
993
+ }
994
+ if (c.value?.kind === JS.Kind.CompilationUnit) {
995
+ break;
996
+ }
997
+ }
998
+ return cursors;
999
+ }
1000
+
1001
+ function treePath(cursor: Cursor): Tree[] {
1002
+ return treeCursors(cursor).map(c => c.value as Tree);
1003
+ }
1004
+
1005
+ /**
1006
+ * The scopes around `leaf` and what it reads with, folded down `path`, which ends at the node
1007
+ * holding `leaf`, and what each node of `path` reads with. The fold runs to the end since a naming
1008
+ * slot can hold a read deeper down.
1009
+ */
1010
+ function position(path: Tree[], leaf: Tree): [Frames | undefined, Reading, Reading[]] {
1011
+ let scopes: Frames | undefined;
1012
+ let reading: Reading = 'value';
1013
+ const readings: Reading[] = [];
1014
+ for (let i = 0; i < path.length; i++) {
1015
+ readings.push(reading);
1016
+ const child = path[i + 1] ?? leaf;
1017
+ const key = slotOf(path[i], child);
1018
+ if (key === undefined) {
1019
+ const spelled = (child as Partial<J.Identifier>).simpleName ?? child.kind;
1020
+ throw new Error(`\`${spelled}\` is not under the cursor it is resolved from`);
1021
+ }
1022
+ [scopes, reading] = descend(path[i], path[i - 1], key, framesAt(path[i], path[i - 1], scopes), reading);
1023
+ }
1024
+ return [scopes, reading, readings];
1025
+ }
1026
+
1027
+ /**
1028
+ * The slot of `node` holding `child`, by id, so a copy or a rebuilt node is found as the original.
1029
+ * A node's slots are indexed once, through padding, containers and lists.
1030
+ */
1031
+ function slotOf(node: Tree, child: Tree): string | undefined {
1032
+ let index = slots.get(node);
1033
+ if (!index) {
1034
+ index = new Map();
1035
+ for (const [key, value] of Object.entries(node)) {
1036
+ if (key !== 'markers') {
1037
+ for (const held of treesIn(value)) {
1038
+ index.set(held.id, key);
1039
+ }
1040
+ }
1041
+ }
1042
+ slots.set(node, index);
588
1043
  }
1044
+ return index.get(child.id);
1045
+ }
589
1046
 
590
- // A JSX attribute keeps its prop name on `key`; the three label-bearing nodes keep theirs on
591
- // `label`. Every other kind that names rather than references keeps it on `name`.
592
- return !holds(owner?.kind === JS.Kind.JsxAttribute ? owner.key : (owner?.name ?? owner?.label), identifier);
1047
+ /** The tree nodes a slot holds directly, through padding, containers and lists. */
1048
+ function treesIn(slot: unknown): Tree[] {
1049
+ if (Array.isArray(slot)) {
1050
+ return slot.flatMap(treesIn);
1051
+ }
1052
+ const kind = (slot as { kind?: string } | undefined)?.kind;
1053
+ if (kind === J.Kind.RightPadded || kind === J.Kind.LeftPadded) {
1054
+ return treesIn((slot as J.RightPadded<any>).element);
1055
+ }
1056
+ if (kind === J.Kind.Container) {
1057
+ return treesIn((slot as J.Container<any>).elements);
1058
+ }
1059
+ return isTree(slot) ? [slot] : [];
593
1060
  }
594
1061
 
595
- /** Whether a node slot, padded or not, is the identifier itself. */
596
- function holds(slot: unknown, identifier: J.Identifier): boolean {
597
- return slot === identifier || (slot as { element?: unknown } | undefined)?.element === identifier;
1062
+ /**
1063
+ * @deprecated Use {@link isReference}. Unlike this function once did, it answers false for an
1064
+ * import's own name, an alias and a re-export's specifier, which name rather than read.
1065
+ */
1066
+ export const isValueReference = isReference;
1067
+
1068
+ /**
1069
+ * The node owning the scope that binds what `identifier` reads, or undefined where it reads a
1070
+ * global or reads nothing. Position is what resolves it, so the cursor stands on the identifier
1071
+ * or on the node holding it, and a node the cursor does not hold is refused.
1072
+ */
1073
+ export function resolve(cursor: Cursor, identifier: J.Identifier): J | undefined {
1074
+ const [scopes, reading] = position(pathTo(cursor, identifier), identifier);
1075
+ return reading && (bindingFrame(scopes, identifier.simpleName, reading)?.node as J | undefined);
598
1076
  }
599
1077
 
600
1078
  /**
601
- * As {@link references}, for a collector rather than a renamer: a shorthand property's name slot is
602
- * also the value it reads, which a rename has to expand rather than follow.
1079
+ * Whether the identifier is assigned to where it stands. An assignment's target, plain or compound,
1080
+ * is, and so are the operand of `++` or `--`, the head of a for-in or for-of declaring nothing, and
1081
+ * a name in such a target's destructuring pattern. A write resolves as a value read, since
1082
+ * `x = 2` assigns to the `x` it reads. The cursor is as {@link resolve} takes it.
603
1083
  */
604
- function reads(identifier: J.Identifier, parent: unknown): boolean {
605
- const owner = parent as { kind?: string; initializer?: unknown } | undefined;
606
- return (owner?.kind === JS.Kind.PropertyAssignment && owner.initializer === undefined) ||
607
- references(identifier, parent);
1084
+ export function isWrite(cursor: Cursor, identifier: J.Identifier): boolean {
1085
+ return position(pathTo(cursor, identifier), identifier)[1] === 'write';
608
1086
  }
609
1087
 
610
- function isPadding(value: unknown): boolean {
611
- const kind = (value as { kind?: string } | undefined)?.kind;
612
- return kind === J.Kind.RightPadded || kind === J.Kind.LeftPadded || kind === J.Kind.Container;
1088
+ /** One identifier referring to a binding, from {@link referencesOf}. */
1089
+ export interface Reference {
1090
+ /** The identifier's position, holding the tree nodes above it with no padding between them. */
1091
+ cursor: Cursor;
1092
+ identifier: J.Identifier;
1093
+ /** Whether the identifier is one declaring the binding. */
1094
+ declares: boolean;
1095
+ /** Whether the identifier is assigned to, as {@link isWrite} answers. */
1096
+ writes: boolean;
1097
+ }
1098
+
1099
+ /**
1100
+ * Every identifier referring to the binding `name` has at `cursor`, the ones declaring it and the
1101
+ * ones resolving to it, in the order the tree holds them, which a type annotation can depart from.
1102
+ * The binding is the innermost one of the name with `meaning`, or with any meaning where none is
1103
+ * asked for. A read with any meaning, as an export clause reads, refers to every meaning it finds.
1104
+ * A name bound nowhere has no references. Each reference's cursor continues the caller's chain.
1105
+ */
1106
+ export function referencesOf(cursor: Cursor, name: string, meaning?: Meaning): Reference[] {
1107
+ const cursors = treeCursors(cursor);
1108
+ const path = cursors.map(c => c.value as Tree);
1109
+ if (path.length === 0) {
1110
+ return [];
1111
+ }
1112
+ const [scopes, reading, readings] = position(path.slice(0, -1), path[path.length - 1]);
1113
+ readings.push(reading);
1114
+ const here = framesAt(path[path.length - 1], path[path.length - 2], scopes);
1115
+ const frame = bindingFrame(here, name, meaning ?? 'any');
1116
+ if (!frame) {
1117
+ return [];
1118
+ }
1119
+ const declarations = new Set(frameBindings(frame.node, frame.parent).get(name)!.declarations
1120
+ .filter(declaration => meaning === undefined || declaration.meanings.includes(meaning))
1121
+ .map(declaration => declaration.identifier.id));
1122
+ // A class binds its own name in its body too, so a frame further out declaring the same
1123
+ // identifier holds the same binding, and its references sit under that frame.
1124
+ const sharesBinding = (scope: Frames | undefined): boolean => scope !== undefined &&
1125
+ (frameBindings(scope.node, scope.parent).get(name)?.declarations ?? [])
1126
+ .some(declaration => declarations.has(declaration.identifier.id));
1127
+ let root = frame;
1128
+ for (let outer = frame.outer; outer; outer = outer.outer) {
1129
+ if (sharesBinding(outer)) {
1130
+ root = outer;
1131
+ }
1132
+ }
1133
+ const at = path.indexOf(root.node);
1134
+ const references: Reference[] = [];
1135
+ readReferences(root.node, root.parent, root.outer, readings[at], (identifier, reads, scopes, trail) => {
1136
+ if (identifier.simpleName !== name) {
1137
+ return;
1138
+ }
1139
+ const declares = declarations.has(identifier.id);
1140
+ const refers = !declares && reads !== undefined &&
1141
+ (meaning === undefined || reads === 'any' || meaningRead(reads) === meaning) &&
1142
+ sharesBinding(bindingFrame(scopes, name, reads));
1143
+ if (declares || refers) {
1144
+ const standing = trail.reduce((above, node) => new Cursor(node, above), cursors[at].parent);
1145
+ references.push({cursor: standing!, identifier, declares, writes: reads === 'write'});
1146
+ }
1147
+ });
1148
+ return references;
613
1149
  }