@tishlang/tish-lsp 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/Cargo.toml +51 -0
  2. package/LICENSE +13 -0
  3. package/README.md +18 -0
  4. package/bin/tish-lsp +0 -0
  5. package/crates/js_to_tish/Cargo.toml +11 -0
  6. package/crates/js_to_tish/README.md +18 -0
  7. package/crates/js_to_tish/src/error.rs +55 -0
  8. package/crates/js_to_tish/src/lib.rs +11 -0
  9. package/crates/js_to_tish/src/span_util.rs +35 -0
  10. package/crates/js_to_tish/src/transform/expr.rs +611 -0
  11. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  12. package/crates/js_to_tish/src/transform.rs +60 -0
  13. package/crates/tish/Cargo.toml +62 -0
  14. package/crates/tish/build.rs +21 -0
  15. package/crates/tish/src/cargo_native_registry.rs +32 -0
  16. package/crates/tish/src/cli_help.rs +576 -0
  17. package/crates/tish/src/main.rs +853 -0
  18. package/crates/tish/src/repl_completion.rs +199 -0
  19. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  20. package/crates/tish/tests/error_source_location.rs +36 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  24. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  25. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  26. package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
  27. package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
  28. package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
  29. package/crates/tish/tests/integration_test.rs +1406 -0
  30. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  31. package/crates/tish/tests/shortcircuit.rs +65 -0
  32. package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
  33. package/crates/tish/tests/tty_capability.rs +43 -0
  34. package/crates/tish_ast/Cargo.toml +9 -0
  35. package/crates/tish_ast/src/ast.rs +649 -0
  36. package/crates/tish_ast/src/lib.rs +5 -0
  37. package/crates/tish_build_utils/Cargo.toml +11 -0
  38. package/crates/tish_build_utils/src/lib.rs +577 -0
  39. package/crates/tish_builtins/Cargo.toml +22 -0
  40. package/crates/tish_builtins/src/array.rs +803 -0
  41. package/crates/tish_builtins/src/collections.rs +481 -0
  42. package/crates/tish_builtins/src/construct.rs +199 -0
  43. package/crates/tish_builtins/src/date.rs +538 -0
  44. package/crates/tish_builtins/src/globals.rs +293 -0
  45. package/crates/tish_builtins/src/helpers.rs +35 -0
  46. package/crates/tish_builtins/src/iterator.rs +129 -0
  47. package/crates/tish_builtins/src/lib.rs +21 -0
  48. package/crates/tish_builtins/src/math.rs +89 -0
  49. package/crates/tish_builtins/src/number.rs +96 -0
  50. package/crates/tish_builtins/src/object.rs +36 -0
  51. package/crates/tish_builtins/src/string.rs +646 -0
  52. package/crates/tish_builtins/src/symbol.rs +83 -0
  53. package/crates/tish_builtins/src/typedarrays.rs +298 -0
  54. package/crates/tish_bytecode/Cargo.toml +17 -0
  55. package/crates/tish_bytecode/src/chunk.rs +164 -0
  56. package/crates/tish_bytecode/src/compiler.rs +2604 -0
  57. package/crates/tish_bytecode/src/encoding.rs +102 -0
  58. package/crates/tish_bytecode/src/lib.rs +20 -0
  59. package/crates/tish_bytecode/src/opcode.rs +185 -0
  60. package/crates/tish_bytecode/src/peephole.rs +189 -0
  61. package/crates/tish_bytecode/src/serialize.rs +193 -0
  62. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  63. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  64. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  65. package/crates/tish_compile/Cargo.toml +27 -0
  66. package/crates/tish_compile/src/check.rs +774 -0
  67. package/crates/tish_compile/src/codegen.rs +7317 -0
  68. package/crates/tish_compile/src/infer.rs +1681 -0
  69. package/crates/tish_compile/src/lib.rs +206 -0
  70. package/crates/tish_compile/src/resolve.rs +1951 -0
  71. package/crates/tish_compile/src/types.rs +605 -0
  72. package/crates/tish_compile_js/Cargo.toml +18 -0
  73. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  74. package/crates/tish_compile_js/src/codegen.rs +938 -0
  75. package/crates/tish_compile_js/src/error.rs +20 -0
  76. package/crates/tish_compile_js/src/lib.rs +26 -0
  77. package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
  78. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  79. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  80. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  81. package/crates/tish_core/Cargo.toml +32 -0
  82. package/crates/tish_core/src/console_style.rs +170 -0
  83. package/crates/tish_core/src/json.rs +430 -0
  84. package/crates/tish_core/src/lib.rs +20 -0
  85. package/crates/tish_core/src/macros.rs +36 -0
  86. package/crates/tish_core/src/shape.rs +85 -0
  87. package/crates/tish_core/src/uri.rs +118 -0
  88. package/crates/tish_core/src/value.rs +1350 -0
  89. package/crates/tish_core/src/vmref.rs +183 -0
  90. package/crates/tish_cranelift/Cargo.toml +19 -0
  91. package/crates/tish_cranelift/src/lib.rs +43 -0
  92. package/crates/tish_cranelift/src/link.rs +130 -0
  93. package/crates/tish_cranelift/src/lower.rs +85 -0
  94. package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
  95. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  96. package/crates/tish_eval/Cargo.toml +51 -0
  97. package/crates/tish_eval/src/eval.rs +4265 -0
  98. package/crates/tish_eval/src/http.rs +191 -0
  99. package/crates/tish_eval/src/lib.rs +99 -0
  100. package/crates/tish_eval/src/natives.rs +551 -0
  101. package/crates/tish_eval/src/promise.rs +179 -0
  102. package/crates/tish_eval/src/regex.rs +299 -0
  103. package/crates/tish_eval/src/timers.rs +120 -0
  104. package/crates/tish_eval/src/value.rs +336 -0
  105. package/crates/tish_eval/src/value_convert.rs +117 -0
  106. package/crates/tish_ffi/Cargo.toml +26 -0
  107. package/crates/tish_ffi/src/lib.rs +518 -0
  108. package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
  109. package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
  110. package/crates/tish_ffi/tests/loader.rs +65 -0
  111. package/crates/tish_fmt/Cargo.toml +16 -0
  112. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  113. package/crates/tish_fmt/src/lib.rs +2157 -0
  114. package/crates/tish_jsx_web/Cargo.toml +9 -0
  115. package/crates/tish_jsx_web/README.md +5 -0
  116. package/crates/tish_jsx_web/src/lib.rs +2 -0
  117. package/crates/tish_lexer/Cargo.toml +9 -0
  118. package/crates/tish_lexer/src/lib.rs +1104 -0
  119. package/crates/tish_lexer/src/token.rs +170 -0
  120. package/crates/tish_lint/Cargo.toml +18 -0
  121. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  122. package/crates/tish_lint/src/lib.rs +281 -0
  123. package/crates/tish_llvm/Cargo.toml +13 -0
  124. package/crates/tish_llvm/src/lib.rs +115 -0
  125. package/crates/tish_lsp/Cargo.toml +25 -0
  126. package/crates/tish_lsp/README.md +26 -0
  127. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  128. package/crates/tish_lsp/src/import_goto.rs +564 -0
  129. package/crates/tish_lsp/src/main.rs +1459 -0
  130. package/crates/tish_native/Cargo.toml +16 -0
  131. package/crates/tish_native/src/build.rs +481 -0
  132. package/crates/tish_native/src/config.rs +48 -0
  133. package/crates/tish_native/src/lib.rs +416 -0
  134. package/crates/tish_opt/Cargo.toml +13 -0
  135. package/crates/tish_opt/src/lib.rs +1046 -0
  136. package/crates/tish_parser/Cargo.toml +11 -0
  137. package/crates/tish_parser/src/lib.rs +386 -0
  138. package/crates/tish_parser/src/parser.rs +2726 -0
  139. package/crates/tish_pg/Cargo.toml +34 -0
  140. package/crates/tish_pg/README.md +38 -0
  141. package/crates/tish_pg/src/error.rs +52 -0
  142. package/crates/tish_pg/src/lib.rs +955 -0
  143. package/crates/tish_resolve/Cargo.toml +13 -0
  144. package/crates/tish_resolve/src/lib.rs +3612 -0
  145. package/crates/tish_resolve/src/pos.rs +141 -0
  146. package/crates/tish_runtime/Cargo.toml +100 -0
  147. package/crates/tish_runtime/src/http.rs +1347 -0
  148. package/crates/tish_runtime/src/http_fetch.rs +492 -0
  149. package/crates/tish_runtime/src/http_hyper.rs +441 -0
  150. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  151. package/crates/tish_runtime/src/lib.rs +1447 -0
  152. package/crates/tish_runtime/src/native_promise.rs +15 -0
  153. package/crates/tish_runtime/src/promise.rs +558 -0
  154. package/crates/tish_runtime/src/promise_io.rs +38 -0
  155. package/crates/tish_runtime/src/timers.rs +172 -0
  156. package/crates/tish_runtime/src/tty.rs +226 -0
  157. package/crates/tish_runtime/src/ws.rs +778 -0
  158. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  159. package/crates/tish_ui/Cargo.toml +17 -0
  160. package/crates/tish_ui/src/jsx.rs +692 -0
  161. package/crates/tish_ui/src/lib.rs +20 -0
  162. package/crates/tish_ui/src/runtime/hooks.rs +573 -0
  163. package/crates/tish_ui/src/runtime/mod.rs +183 -0
  164. package/crates/tish_vm/Cargo.toml +60 -0
  165. package/crates/tish_vm/src/jit.rs +1050 -0
  166. package/crates/tish_vm/src/lib.rs +41 -0
  167. package/crates/tish_vm/src/vm.rs +3536 -0
  168. package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
  169. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  170. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  171. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  172. package/crates/tish_wasm/Cargo.toml +15 -0
  173. package/crates/tish_wasm/src/lib.rs +428 -0
  174. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  175. package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
  176. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  177. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  178. package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
  179. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  180. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  181. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  182. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  183. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  184. package/justfile +276 -0
  185. package/package.json +37 -0
  186. package/platform/darwin-arm64/tish-lsp +0 -0
  187. package/platform/darwin-x64/tish-lsp +0 -0
  188. package/platform/linux-arm64/tish-lsp +0 -0
  189. package/platform/linux-x64/tish-lsp +0 -0
  190. package/platform/win32-x64/tish-lsp.exe +0 -0
  191. package/scripts/install-bin.js +54 -0
@@ -0,0 +1,3612 @@
1
+ //! Lexical name resolution for Tish (go-to-definition, hover, references).
2
+ //!
3
+ //! Coordinates: LSP uses 0-based lines and UTF-16 columns; [`tishlang_ast::Span`] uses 1-based
4
+ //! lines and 1-based **Unicode scalar** columns from the lexer. Conversion goes through byte
5
+ //! offsets in the original source string.
6
+
7
+ mod pos;
8
+
9
+ pub use pos::{
10
+ lsp_position_for_span_start, span_contains_lsp_position, span_to_lsp_range_exclusive,
11
+ };
12
+
13
+ use std::collections::HashMap;
14
+ use std::sync::Arc;
15
+
16
+ use tishlang_ast::{
17
+ ArrowBody, CallArg, DestructElement, DestructPattern, ExportDeclaration, Expr, FunParam,
18
+ ImportSpecifier, MemberProp, Program, Statement, TypedParam,
19
+ };
20
+
21
+ /// Smallest source span covering the LSP cursor (definition site or reference).
22
+ #[derive(Debug, Clone)]
23
+ pub struct NameUse {
24
+ pub name: Arc<str>,
25
+ pub span: tishlang_ast::Span,
26
+ }
27
+
28
+ /// Find the tightest name under the cursor (identifier reference or binding).
29
+ pub fn name_at_cursor(
30
+ program: &Program,
31
+ source: &str,
32
+ lsp_line: u32,
33
+ lsp_character: u32,
34
+ ) -> Option<NameUse> {
35
+ let mut best: Option<(u64, NameUse)> = None;
36
+ for s in &program.statements {
37
+ collect_stmt(s, source, lsp_line, lsp_character, &mut best);
38
+ }
39
+ best.map(|(_, u)| u)
40
+ }
41
+
42
+ fn span_size(source: &str, span: &tishlang_ast::Span) -> u64 {
43
+ pos::lex_span_byte_range(source, span)
44
+ .map(|(a, b)| b.saturating_sub(a) as u64)
45
+ .unwrap_or(u64::MAX)
46
+ }
47
+
48
+ fn consider(
49
+ source: &str,
50
+ lsp_line: u32,
51
+ lsp_char: u32,
52
+ span: &tishlang_ast::Span,
53
+ name: Arc<str>,
54
+ best: &mut Option<(u64, NameUse)>,
55
+ ) {
56
+ if !pos::span_contains_lsp_position(source, span, lsp_line, lsp_char) {
57
+ return;
58
+ }
59
+ let sz = span_size(source, span);
60
+ let nu = NameUse { name, span: *span };
61
+ match best {
62
+ None => *best = Some((sz, nu)),
63
+ Some((osz, _)) if sz < *osz => *best = Some((sz, nu)),
64
+ _ => {}
65
+ }
66
+ }
67
+
68
+ fn synthetic_name_span(start: (usize, usize), name: &str) -> tishlang_ast::Span {
69
+ tishlang_ast::Span {
70
+ start,
71
+ end: (start.0, start.1.saturating_add(name.chars().count())),
72
+ }
73
+ }
74
+
75
+ fn collect_stmt(
76
+ stmt: &Statement,
77
+ source: &str,
78
+ lsp_line: u32,
79
+ lsp_char: u32,
80
+ best: &mut Option<(u64, NameUse)>,
81
+ ) {
82
+ match stmt {
83
+ Statement::VarDecl {
84
+ name,
85
+ name_span,
86
+ init,
87
+ ..
88
+ } => {
89
+ consider(source, lsp_line, lsp_char, name_span, name.clone(), best);
90
+ if let Some(e) = init {
91
+ collect_expr(e, source, lsp_line, lsp_char, best);
92
+ }
93
+ }
94
+ Statement::VarDeclDestructure { pattern, init, .. } => {
95
+ collect_destruct_pattern(pattern, source, lsp_line, lsp_char, best);
96
+ collect_expr(init, source, lsp_line, lsp_char, best);
97
+ }
98
+ Statement::ExprStmt { expr, .. } => collect_expr(expr, source, lsp_line, lsp_char, best),
99
+ Statement::If {
100
+ cond,
101
+ then_branch,
102
+ else_branch,
103
+ ..
104
+ } => {
105
+ collect_expr(cond, source, lsp_line, lsp_char, best);
106
+ collect_stmt(then_branch, source, lsp_line, lsp_char, best);
107
+ if let Some(e) = else_branch {
108
+ collect_stmt(e, source, lsp_line, lsp_char, best);
109
+ }
110
+ }
111
+ Statement::While { cond, body, .. } => {
112
+ collect_expr(cond, source, lsp_line, lsp_char, best);
113
+ collect_stmt(body, source, lsp_line, lsp_char, best);
114
+ }
115
+ Statement::For {
116
+ init,
117
+ cond,
118
+ update,
119
+ body,
120
+ ..
121
+ } => {
122
+ if let Some(i) = init {
123
+ collect_stmt(i, source, lsp_line, lsp_char, best);
124
+ }
125
+ if let Some(e) = cond {
126
+ collect_expr(e, source, lsp_line, lsp_char, best);
127
+ }
128
+ if let Some(e) = update {
129
+ collect_expr(e, source, lsp_line, lsp_char, best);
130
+ }
131
+ collect_stmt(body, source, lsp_line, lsp_char, best);
132
+ }
133
+ Statement::ForOf {
134
+ name,
135
+ name_span,
136
+ iterable,
137
+ body,
138
+ ..
139
+ } => {
140
+ consider(source, lsp_line, lsp_char, name_span, name.clone(), best);
141
+ collect_expr(iterable, source, lsp_line, lsp_char, best);
142
+ collect_stmt(body, source, lsp_line, lsp_char, best);
143
+ }
144
+ Statement::Return { value, .. } => {
145
+ if let Some(e) = value {
146
+ collect_expr(e, source, lsp_line, lsp_char, best);
147
+ }
148
+ }
149
+ Statement::Block { statements, .. } => {
150
+ for s in statements {
151
+ collect_stmt(s, source, lsp_line, lsp_char, best);
152
+ }
153
+ }
154
+ Statement::Multi { statements, .. } => {
155
+ for s in statements {
156
+ collect_stmt(s, source, lsp_line, lsp_char, best);
157
+ }
158
+ }
159
+ Statement::FunDecl {
160
+ name,
161
+ name_span,
162
+ params,
163
+ body,
164
+ ..
165
+ } => {
166
+ consider(source, lsp_line, lsp_char, name_span, name.clone(), best);
167
+ for p in params {
168
+ collect_fun_param(p, source, lsp_line, lsp_char, best);
169
+ }
170
+ collect_stmt(body, source, lsp_line, lsp_char, best);
171
+ }
172
+ Statement::Switch {
173
+ expr,
174
+ cases,
175
+ default_body,
176
+ ..
177
+ } => {
178
+ collect_expr(expr, source, lsp_line, lsp_char, best);
179
+ for (_ce, stmts) in cases {
180
+ for s in stmts {
181
+ collect_stmt(s, source, lsp_line, lsp_char, best);
182
+ }
183
+ }
184
+ if let Some(stmts) = default_body {
185
+ for s in stmts {
186
+ collect_stmt(s, source, lsp_line, lsp_char, best);
187
+ }
188
+ }
189
+ }
190
+ Statement::DoWhile { body, cond, .. } => {
191
+ collect_stmt(body, source, lsp_line, lsp_char, best);
192
+ collect_expr(cond, source, lsp_line, lsp_char, best);
193
+ }
194
+ Statement::Throw { value, .. } => collect_expr(value, source, lsp_line, lsp_char, best),
195
+ Statement::Try {
196
+ body,
197
+ catch_param,
198
+ catch_param_span,
199
+ catch_body,
200
+ finally_body,
201
+ ..
202
+ } => {
203
+ collect_stmt(body, source, lsp_line, lsp_char, best);
204
+ if let (Some(n), Some(sp)) = (catch_param, catch_param_span) {
205
+ consider(source, lsp_line, lsp_char, sp, n.clone(), best);
206
+ }
207
+ if let Some(cb) = catch_body {
208
+ collect_stmt(cb, source, lsp_line, lsp_char, best);
209
+ }
210
+ if let Some(fb) = finally_body {
211
+ collect_stmt(fb, source, lsp_line, lsp_char, best);
212
+ }
213
+ }
214
+ Statement::Import { specifiers, .. } => {
215
+ for sp in specifiers {
216
+ match sp {
217
+ ImportSpecifier::Named {
218
+ name,
219
+ name_span,
220
+ alias,
221
+ alias_span,
222
+ } => {
223
+ let local = alias
224
+ .as_ref()
225
+ .map(|a| a.clone())
226
+ .unwrap_or_else(|| name.clone());
227
+ let spn = alias_span.as_ref().unwrap_or(name_span);
228
+ consider(source, lsp_line, lsp_char, spn, local, best);
229
+ }
230
+ ImportSpecifier::Namespace { name, name_span } => {
231
+ consider(source, lsp_line, lsp_char, name_span, name.clone(), best);
232
+ }
233
+ ImportSpecifier::Default { name, name_span } => {
234
+ consider(source, lsp_line, lsp_char, name_span, name.clone(), best);
235
+ }
236
+ }
237
+ }
238
+ }
239
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
240
+ ExportDeclaration::Named(inner) => {
241
+ collect_stmt(inner, source, lsp_line, lsp_char, best)
242
+ }
243
+ ExportDeclaration::Default(e) => collect_expr(e, source, lsp_line, lsp_char, best),
244
+ },
245
+ Statement::TypeAlias {
246
+ name, name_span, ..
247
+ } => {
248
+ consider(source, lsp_line, lsp_char, name_span, name.clone(), best);
249
+ }
250
+ Statement::DeclareVar {
251
+ name, name_span, ..
252
+ } => {
253
+ consider(source, lsp_line, lsp_char, name_span, name.clone(), best);
254
+ }
255
+ Statement::DeclareFun {
256
+ name,
257
+ name_span,
258
+ params,
259
+ ..
260
+ } => {
261
+ consider(source, lsp_line, lsp_char, name_span, name.clone(), best);
262
+ for p in params {
263
+ collect_fun_param(p, source, lsp_line, lsp_char, best);
264
+ }
265
+ }
266
+ Statement::Break { .. } | Statement::Continue { .. } => {}
267
+ }
268
+ }
269
+
270
+ fn collect_fun_param(
271
+ p: &FunParam,
272
+ source: &str,
273
+ lsp_line: u32,
274
+ lsp_char: u32,
275
+ best: &mut Option<(u64, NameUse)>,
276
+ ) {
277
+ match p {
278
+ FunParam::Simple(tp) => collect_typed_param(tp, source, lsp_line, lsp_char, best),
279
+ FunParam::Destructure { pattern, .. } => {
280
+ collect_destruct_pattern(pattern, source, lsp_line, lsp_char, best);
281
+ }
282
+ }
283
+ }
284
+
285
+ fn collect_typed_param(
286
+ tp: &TypedParam,
287
+ source: &str,
288
+ lsp_line: u32,
289
+ lsp_char: u32,
290
+ best: &mut Option<(u64, NameUse)>,
291
+ ) {
292
+ consider(
293
+ source,
294
+ lsp_line,
295
+ lsp_char,
296
+ &tp.name_span,
297
+ tp.name.clone(),
298
+ best,
299
+ );
300
+ if let Some(e) = &tp.default {
301
+ collect_expr(e, source, lsp_line, lsp_char, best);
302
+ }
303
+ }
304
+
305
+ fn collect_destruct_pattern(
306
+ p: &DestructPattern,
307
+ source: &str,
308
+ lsp_line: u32,
309
+ lsp_char: u32,
310
+ best: &mut Option<(u64, NameUse)>,
311
+ ) {
312
+ match p {
313
+ DestructPattern::Array(elements) => {
314
+ for el in elements.iter().flatten() {
315
+ match el {
316
+ DestructElement::Ident(n, sp) => {
317
+ consider(source, lsp_line, lsp_char, sp, n.clone(), best);
318
+ }
319
+ DestructElement::Pattern(inner) => {
320
+ collect_destruct_pattern(inner, source, lsp_line, lsp_char, best);
321
+ }
322
+ DestructElement::Rest(n, sp) => {
323
+ consider(source, lsp_line, lsp_char, sp, n.clone(), best);
324
+ }
325
+ }
326
+ }
327
+ }
328
+ DestructPattern::Object(props) => {
329
+ for pr in props {
330
+ match &pr.value {
331
+ DestructElement::Ident(n, sp) => {
332
+ consider(source, lsp_line, lsp_char, sp, n.clone(), best);
333
+ }
334
+ DestructElement::Pattern(inner) => {
335
+ collect_destruct_pattern(inner, source, lsp_line, lsp_char, best);
336
+ }
337
+ DestructElement::Rest(n, sp) => {
338
+ consider(source, lsp_line, lsp_char, sp, n.clone(), best);
339
+ }
340
+ }
341
+ }
342
+ }
343
+ }
344
+ }
345
+
346
+ fn collect_expr(
347
+ expr: &Expr,
348
+ source: &str,
349
+ lsp_line: u32,
350
+ lsp_char: u32,
351
+ best: &mut Option<(u64, NameUse)>,
352
+ ) {
353
+ match expr {
354
+ Expr::Ident { name, span } => {
355
+ consider(source, lsp_line, lsp_char, span, name.clone(), best)
356
+ }
357
+ Expr::Literal { .. } => {}
358
+ Expr::Binary { left, right, .. } => {
359
+ collect_expr(left, source, lsp_line, lsp_char, best);
360
+ collect_expr(right, source, lsp_line, lsp_char, best);
361
+ }
362
+ Expr::Unary { operand, .. } => collect_expr(operand, source, lsp_line, lsp_char, best),
363
+ Expr::Call { callee, args, .. } => {
364
+ collect_expr(callee, source, lsp_line, lsp_char, best);
365
+ for a in args {
366
+ match a {
367
+ CallArg::Expr(e) => collect_expr(e, source, lsp_line, lsp_char, best),
368
+ CallArg::Spread(e) => collect_expr(e, source, lsp_line, lsp_char, best),
369
+ }
370
+ }
371
+ }
372
+ Expr::New { callee, args, .. } => {
373
+ collect_expr(callee, source, lsp_line, lsp_char, best);
374
+ for a in args {
375
+ match a {
376
+ CallArg::Expr(e) => collect_expr(e, source, lsp_line, lsp_char, best),
377
+ CallArg::Spread(e) => collect_expr(e, source, lsp_line, lsp_char, best),
378
+ }
379
+ }
380
+ }
381
+ Expr::Member { object, prop, .. } => {
382
+ collect_expr(object, source, lsp_line, lsp_char, best);
383
+ match prop {
384
+ MemberProp::Name { name, span } => {
385
+ consider(source, lsp_line, lsp_char, span, name.clone(), best);
386
+ }
387
+ MemberProp::Expr(ix) => collect_expr(ix, source, lsp_line, lsp_char, best),
388
+ }
389
+ }
390
+ Expr::Index { object, index, .. } => {
391
+ collect_expr(object, source, lsp_line, lsp_char, best);
392
+ collect_expr(index, source, lsp_line, lsp_char, best);
393
+ }
394
+ Expr::Conditional {
395
+ cond,
396
+ then_branch,
397
+ else_branch,
398
+ ..
399
+ } => {
400
+ collect_expr(cond, source, lsp_line, lsp_char, best);
401
+ collect_expr(then_branch, source, lsp_line, lsp_char, best);
402
+ collect_expr(else_branch, source, lsp_line, lsp_char, best);
403
+ }
404
+ Expr::NullishCoalesce { left, right, .. } => {
405
+ collect_expr(left, source, lsp_line, lsp_char, best);
406
+ collect_expr(right, source, lsp_line, lsp_char, best);
407
+ }
408
+ Expr::Array { elements, .. } => {
409
+ for el in elements {
410
+ match el {
411
+ tishlang_ast::ArrayElement::Expr(e) => {
412
+ collect_expr(e, source, lsp_line, lsp_char, best)
413
+ }
414
+ tishlang_ast::ArrayElement::Spread(e) => {
415
+ collect_expr(e, source, lsp_line, lsp_char, best)
416
+ }
417
+ }
418
+ }
419
+ }
420
+ Expr::Object { props, .. } => {
421
+ for p in props {
422
+ match p {
423
+ tishlang_ast::ObjectProp::KeyValue(_, e) => {
424
+ collect_expr(e, source, lsp_line, lsp_char, best)
425
+ }
426
+ tishlang_ast::ObjectProp::Spread(e) => {
427
+ collect_expr(e, source, lsp_line, lsp_char, best)
428
+ }
429
+ }
430
+ }
431
+ }
432
+ Expr::Assign { name, span, value } => {
433
+ let sp = synthetic_name_span(span.start, name.as_ref());
434
+ consider(source, lsp_line, lsp_char, &sp, name.clone(), best);
435
+ collect_expr(value, source, lsp_line, lsp_char, best);
436
+ }
437
+ Expr::TypeOf { operand, .. } => collect_expr(operand, source, lsp_line, lsp_char, best),
438
+ Expr::Delete { target: del, .. } => collect_expr(del, source, lsp_line, lsp_char, best),
439
+ Expr::PostfixInc { name, span } | Expr::PostfixDec { name, span } => {
440
+ let sp = synthetic_name_span(span.start, name.as_ref());
441
+ consider(source, lsp_line, lsp_char, &sp, name.clone(), best);
442
+ }
443
+ Expr::PrefixInc { name, span } | Expr::PrefixDec { name, span } => {
444
+ let sp = synthetic_name_span(span.start, name.as_ref());
445
+ consider(source, lsp_line, lsp_char, &sp, name.clone(), best);
446
+ }
447
+ Expr::CompoundAssign {
448
+ name, span, value, ..
449
+ }
450
+ | Expr::LogicalAssign {
451
+ name, span, value, ..
452
+ } => {
453
+ let sp = synthetic_name_span(span.start, name.as_ref());
454
+ consider(source, lsp_line, lsp_char, &sp, name.clone(), best);
455
+ collect_expr(value, source, lsp_line, lsp_char, best);
456
+ }
457
+ Expr::MemberAssign { object, value, .. } => {
458
+ collect_expr(object, source, lsp_line, lsp_char, best);
459
+ collect_expr(value, source, lsp_line, lsp_char, best);
460
+ }
461
+ Expr::IndexAssign {
462
+ object,
463
+ index,
464
+ value,
465
+ ..
466
+ } => {
467
+ collect_expr(object, source, lsp_line, lsp_char, best);
468
+ collect_expr(index, source, lsp_line, lsp_char, best);
469
+ collect_expr(value, source, lsp_line, lsp_char, best);
470
+ }
471
+ Expr::ArrowFunction { params, body, .. } => {
472
+ for p in params {
473
+ collect_fun_param(p, source, lsp_line, lsp_char, best);
474
+ }
475
+ match body {
476
+ ArrowBody::Expr(e) => collect_expr(e, source, lsp_line, lsp_char, best),
477
+ ArrowBody::Block(b) => collect_stmt(b, source, lsp_line, lsp_char, best),
478
+ }
479
+ }
480
+ Expr::TemplateLiteral { exprs, .. } => {
481
+ for e in exprs {
482
+ collect_expr(e, source, lsp_line, lsp_char, best);
483
+ }
484
+ }
485
+ Expr::Await { operand, .. } => collect_expr(operand, source, lsp_line, lsp_char, best),
486
+ Expr::JsxElement {
487
+ props, children, ..
488
+ } => {
489
+ for p in props {
490
+ match p {
491
+ tishlang_ast::JsxProp::Attr { value, .. } => if let tishlang_ast::JsxAttrValue::Expr(e) = value {
492
+ collect_expr(e, source, lsp_line, lsp_char, best)
493
+ },
494
+ tishlang_ast::JsxProp::Spread(e) => {
495
+ collect_expr(e, source, lsp_line, lsp_char, best)
496
+ }
497
+ }
498
+ }
499
+ for ch in children {
500
+ match ch {
501
+ tishlang_ast::JsxChild::Expr(e) => {
502
+ collect_expr(e, source, lsp_line, lsp_char, best)
503
+ }
504
+ tishlang_ast::JsxChild::Text(_) => {}
505
+ }
506
+ }
507
+ }
508
+ Expr::JsxFragment { children, .. } => {
509
+ for ch in children {
510
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
511
+ collect_expr(e, source, lsp_line, lsp_char, best);
512
+ }
513
+ }
514
+ }
515
+ Expr::NativeModuleLoad { .. } => {}
516
+ }
517
+ }
518
+
519
+ /// For `a.b.c` with the cursor on property `c`, the root binding local name and member path `[b, c]`.
520
+ #[derive(Debug, Clone)]
521
+ pub struct MemberAccessChain {
522
+ pub root_local: Arc<str>,
523
+ pub members: Vec<Arc<str>>,
524
+ }
525
+
526
+ /// When the cursor sits on a static member name, resolve `root.local` plus `members` left-to-right after the root.
527
+ pub fn member_access_chain_at_cursor(
528
+ program: &Program,
529
+ source: &str,
530
+ lsp_line: u32,
531
+ lsp_character: u32,
532
+ ) -> Option<MemberAccessChain> {
533
+ let mut best: Option<(u64, MemberAccessChain)> = None;
534
+ for s in &program.statements {
535
+ member_chain_collect_stmt(s, source, lsp_line, lsp_character, &mut best);
536
+ }
537
+ best.map(|(_, c)| c)
538
+ }
539
+
540
+ fn chain_from_member_object(object: &Expr, rightmost: Arc<str>) -> Option<MemberAccessChain> {
541
+ let mut members = vec![rightmost];
542
+ let mut cur = object;
543
+ loop {
544
+ match cur {
545
+ Expr::Member {
546
+ object: o,
547
+ prop: MemberProp::Name { name, .. },
548
+ ..
549
+ } => {
550
+ members.push(name.clone());
551
+ cur = o.as_ref();
552
+ }
553
+ Expr::Ident { name, .. } => {
554
+ members.reverse();
555
+ return Some(MemberAccessChain {
556
+ root_local: name.clone(),
557
+ members,
558
+ });
559
+ }
560
+ _ => return None,
561
+ }
562
+ }
563
+ }
564
+
565
+ fn member_chain_try_update(
566
+ source: &str,
567
+ lsp_line: u32,
568
+ lsp_char: u32,
569
+ prop_span: &tishlang_ast::Span,
570
+ object: &Expr,
571
+ name: Arc<str>,
572
+ best: &mut Option<(u64, MemberAccessChain)>,
573
+ ) {
574
+ if !pos::span_contains_lsp_position(source, prop_span, lsp_line, lsp_char) {
575
+ return;
576
+ }
577
+ let Some(chain) = chain_from_member_object(object, name) else {
578
+ return;
579
+ };
580
+ let sz = span_size(source, prop_span);
581
+ match best {
582
+ None => *best = Some((sz, chain)),
583
+ Some((osz, _)) if sz < *osz => *best = Some((sz, chain)),
584
+ _ => {}
585
+ }
586
+ }
587
+
588
+ fn member_chain_collect_expr(
589
+ expr: &Expr,
590
+ source: &str,
591
+ lsp_line: u32,
592
+ lsp_char: u32,
593
+ best: &mut Option<(u64, MemberAccessChain)>,
594
+ ) {
595
+ match expr {
596
+ Expr::Ident { .. } | Expr::Literal { .. } | Expr::NativeModuleLoad { .. } => {}
597
+ Expr::Binary { left, right, .. } => {
598
+ member_chain_collect_expr(left, source, lsp_line, lsp_char, best);
599
+ member_chain_collect_expr(right, source, lsp_line, lsp_char, best);
600
+ }
601
+ Expr::Unary { operand, .. } => {
602
+ member_chain_collect_expr(operand, source, lsp_line, lsp_char, best)
603
+ }
604
+ Expr::Call { callee, args, .. } => {
605
+ member_chain_collect_expr(callee, source, lsp_line, lsp_char, best);
606
+ for a in args {
607
+ match a {
608
+ CallArg::Expr(e) => {
609
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best)
610
+ }
611
+ CallArg::Spread(e) => {
612
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best)
613
+ }
614
+ }
615
+ }
616
+ }
617
+ Expr::New { callee, args, .. } => {
618
+ member_chain_collect_expr(callee, source, lsp_line, lsp_char, best);
619
+ for a in args {
620
+ match a {
621
+ CallArg::Expr(e) => {
622
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best)
623
+ }
624
+ CallArg::Spread(e) => {
625
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best)
626
+ }
627
+ }
628
+ }
629
+ }
630
+ Expr::Member { object, prop, .. } => {
631
+ member_chain_collect_expr(object.as_ref(), source, lsp_line, lsp_char, best);
632
+ match prop {
633
+ MemberProp::Name { name, span } => {
634
+ member_chain_try_update(
635
+ source,
636
+ lsp_line,
637
+ lsp_char,
638
+ span,
639
+ object.as_ref(),
640
+ name.clone(),
641
+ best,
642
+ );
643
+ }
644
+ MemberProp::Expr(ix) => {
645
+ member_chain_collect_expr(ix, source, lsp_line, lsp_char, best);
646
+ }
647
+ }
648
+ }
649
+ Expr::Index { object, index, .. } => {
650
+ member_chain_collect_expr(object, source, lsp_line, lsp_char, best);
651
+ member_chain_collect_expr(index, source, lsp_line, lsp_char, best);
652
+ }
653
+ Expr::Conditional {
654
+ cond,
655
+ then_branch,
656
+ else_branch,
657
+ ..
658
+ } => {
659
+ member_chain_collect_expr(cond, source, lsp_line, lsp_char, best);
660
+ member_chain_collect_expr(then_branch, source, lsp_line, lsp_char, best);
661
+ member_chain_collect_expr(else_branch, source, lsp_line, lsp_char, best);
662
+ }
663
+ Expr::NullishCoalesce { left, right, .. } => {
664
+ member_chain_collect_expr(left, source, lsp_line, lsp_char, best);
665
+ member_chain_collect_expr(right, source, lsp_line, lsp_char, best);
666
+ }
667
+ Expr::Array { elements, .. } => {
668
+ for el in elements {
669
+ match el {
670
+ tishlang_ast::ArrayElement::Expr(e) => {
671
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best)
672
+ }
673
+ tishlang_ast::ArrayElement::Spread(e) => {
674
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best)
675
+ }
676
+ }
677
+ }
678
+ }
679
+ Expr::Object { props, .. } => {
680
+ for p in props {
681
+ match p {
682
+ tishlang_ast::ObjectProp::KeyValue(_, e) => {
683
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best)
684
+ }
685
+ tishlang_ast::ObjectProp::Spread(e) => {
686
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best)
687
+ }
688
+ }
689
+ }
690
+ }
691
+ Expr::Assign { value, .. }
692
+ | Expr::CompoundAssign { value, .. }
693
+ | Expr::LogicalAssign { value, .. } => {
694
+ member_chain_collect_expr(value, source, lsp_line, lsp_char, best);
695
+ }
696
+ Expr::TypeOf { operand, .. } => {
697
+ member_chain_collect_expr(operand, source, lsp_line, lsp_char, best)
698
+ }
699
+ Expr::Delete { target: del, .. } => {
700
+ member_chain_collect_expr(del, source, lsp_line, lsp_char, best)
701
+ }
702
+ Expr::PostfixInc { .. }
703
+ | Expr::PostfixDec { .. }
704
+ | Expr::PrefixInc { .. }
705
+ | Expr::PrefixDec { .. } => {}
706
+ Expr::MemberAssign { object, value, .. } => {
707
+ member_chain_collect_expr(object, source, lsp_line, lsp_char, best);
708
+ member_chain_collect_expr(value, source, lsp_line, lsp_char, best);
709
+ }
710
+ Expr::IndexAssign {
711
+ object,
712
+ index,
713
+ value,
714
+ ..
715
+ } => {
716
+ member_chain_collect_expr(object, source, lsp_line, lsp_char, best);
717
+ member_chain_collect_expr(index, source, lsp_line, lsp_char, best);
718
+ member_chain_collect_expr(value, source, lsp_line, lsp_char, best);
719
+ }
720
+ Expr::ArrowFunction { params, body, .. } => {
721
+ for p in params {
722
+ member_chain_collect_fun_param(p, source, lsp_line, lsp_char, best);
723
+ }
724
+ match body {
725
+ ArrowBody::Expr(e) => {
726
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best)
727
+ }
728
+ ArrowBody::Block(b) => {
729
+ member_chain_collect_stmt(b, source, lsp_line, lsp_char, best)
730
+ }
731
+ }
732
+ }
733
+ Expr::TemplateLiteral { exprs, .. } => {
734
+ for e in exprs {
735
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best);
736
+ }
737
+ }
738
+ Expr::Await { operand, .. } => {
739
+ member_chain_collect_expr(operand, source, lsp_line, lsp_char, best)
740
+ }
741
+ Expr::JsxElement {
742
+ props, children, ..
743
+ } => {
744
+ for p in props {
745
+ match p {
746
+ tishlang_ast::JsxProp::Attr { value, .. } => {
747
+ if let tishlang_ast::JsxAttrValue::Expr(e) = value {
748
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best);
749
+ }
750
+ }
751
+ tishlang_ast::JsxProp::Spread(e) => {
752
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best)
753
+ }
754
+ }
755
+ }
756
+ for ch in children {
757
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
758
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best);
759
+ }
760
+ }
761
+ }
762
+ Expr::JsxFragment { children, .. } => {
763
+ for ch in children {
764
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
765
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best);
766
+ }
767
+ }
768
+ }
769
+ }
770
+ }
771
+
772
+ fn member_chain_collect_fun_param(
773
+ p: &FunParam,
774
+ source: &str,
775
+ lsp_line: u32,
776
+ lsp_char: u32,
777
+ best: &mut Option<(u64, MemberAccessChain)>,
778
+ ) {
779
+ match p {
780
+ FunParam::Simple(tp) => {
781
+ if let Some(e) = &tp.default {
782
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best);
783
+ }
784
+ }
785
+ FunParam::Destructure { pattern, .. } => {
786
+ member_chain_collect_destruct_pattern(pattern, source, lsp_line, lsp_char, best);
787
+ }
788
+ }
789
+ }
790
+
791
+ #[allow(clippy::only_used_in_recursion)] // params threaded to recurse the pattern tree (mirrors sibling collectors)
792
+ fn member_chain_collect_destruct_pattern(
793
+ pattern: &DestructPattern,
794
+ source: &str,
795
+ lsp_line: u32,
796
+ lsp_char: u32,
797
+ best: &mut Option<(u64, MemberAccessChain)>,
798
+ ) {
799
+ match pattern {
800
+ DestructPattern::Array(elements) => {
801
+ for el in elements.iter().flatten() {
802
+ match el {
803
+ DestructElement::Ident(_, _) => {}
804
+ DestructElement::Pattern(inner) => member_chain_collect_destruct_pattern(
805
+ inner, source, lsp_line, lsp_char, best,
806
+ ),
807
+ DestructElement::Rest(_, _) => {}
808
+ }
809
+ }
810
+ }
811
+ DestructPattern::Object(props) => {
812
+ for pr in props {
813
+ match &pr.value {
814
+ DestructElement::Ident(_, _) => {}
815
+ DestructElement::Pattern(inner) => member_chain_collect_destruct_pattern(
816
+ inner, source, lsp_line, lsp_char, best,
817
+ ),
818
+ DestructElement::Rest(_, _) => {}
819
+ }
820
+ }
821
+ }
822
+ }
823
+ }
824
+
825
+ fn member_chain_collect_stmt(
826
+ stmt: &Statement,
827
+ source: &str,
828
+ lsp_line: u32,
829
+ lsp_char: u32,
830
+ best: &mut Option<(u64, MemberAccessChain)>,
831
+ ) {
832
+ match stmt {
833
+ Statement::VarDecl { init, .. } => {
834
+ if let Some(e) = init {
835
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best);
836
+ }
837
+ }
838
+ Statement::VarDeclDestructure { pattern, init, .. } => {
839
+ member_chain_collect_destruct_pattern(pattern, source, lsp_line, lsp_char, best);
840
+ member_chain_collect_expr(init, source, lsp_line, lsp_char, best);
841
+ }
842
+ Statement::ExprStmt { expr, .. } => {
843
+ member_chain_collect_expr(expr, source, lsp_line, lsp_char, best)
844
+ }
845
+ Statement::If {
846
+ cond,
847
+ then_branch,
848
+ else_branch,
849
+ ..
850
+ } => {
851
+ member_chain_collect_expr(cond, source, lsp_line, lsp_char, best);
852
+ member_chain_collect_stmt(then_branch, source, lsp_line, lsp_char, best);
853
+ if let Some(e) = else_branch {
854
+ member_chain_collect_stmt(e, source, lsp_line, lsp_char, best);
855
+ }
856
+ }
857
+ Statement::While { cond, body, .. } => {
858
+ member_chain_collect_expr(cond, source, lsp_line, lsp_char, best);
859
+ member_chain_collect_stmt(body, source, lsp_line, lsp_char, best);
860
+ }
861
+ Statement::For {
862
+ init,
863
+ cond,
864
+ update,
865
+ body,
866
+ ..
867
+ } => {
868
+ if let Some(i) = init {
869
+ member_chain_collect_stmt(i, source, lsp_line, lsp_char, best);
870
+ }
871
+ if let Some(e) = cond {
872
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best);
873
+ }
874
+ if let Some(e) = update {
875
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best);
876
+ }
877
+ member_chain_collect_stmt(body, source, lsp_line, lsp_char, best);
878
+ }
879
+ Statement::ForOf { iterable, body, .. } => {
880
+ member_chain_collect_expr(iterable, source, lsp_line, lsp_char, best);
881
+ member_chain_collect_stmt(body, source, lsp_line, lsp_char, best);
882
+ }
883
+ Statement::Return { value, .. } => {
884
+ if let Some(e) = value {
885
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best);
886
+ }
887
+ }
888
+ Statement::Block { statements, .. } => {
889
+ for s in statements {
890
+ member_chain_collect_stmt(s, source, lsp_line, lsp_char, best);
891
+ }
892
+ }
893
+ Statement::Multi { statements, .. } => {
894
+ for s in statements {
895
+ member_chain_collect_stmt(s, source, lsp_line, lsp_char, best);
896
+ }
897
+ }
898
+ Statement::FunDecl { params, body, .. } => {
899
+ for p in params {
900
+ member_chain_collect_fun_param(p, source, lsp_line, lsp_char, best);
901
+ }
902
+ member_chain_collect_stmt(body, source, lsp_line, lsp_char, best);
903
+ }
904
+ Statement::Switch {
905
+ expr,
906
+ cases,
907
+ default_body,
908
+ ..
909
+ } => {
910
+ member_chain_collect_expr(expr, source, lsp_line, lsp_char, best);
911
+ for (_ce, stmts) in cases {
912
+ for s in stmts {
913
+ member_chain_collect_stmt(s, source, lsp_line, lsp_char, best);
914
+ }
915
+ }
916
+ if let Some(stmts) = default_body {
917
+ for s in stmts {
918
+ member_chain_collect_stmt(s, source, lsp_line, lsp_char, best);
919
+ }
920
+ }
921
+ }
922
+ Statement::DoWhile { body, cond, .. } => {
923
+ member_chain_collect_stmt(body, source, lsp_line, lsp_char, best);
924
+ member_chain_collect_expr(cond, source, lsp_line, lsp_char, best);
925
+ }
926
+ Statement::Throw { value, .. } => {
927
+ member_chain_collect_expr(value, source, lsp_line, lsp_char, best)
928
+ }
929
+ Statement::Try {
930
+ body,
931
+ catch_body,
932
+ finally_body,
933
+ ..
934
+ } => {
935
+ member_chain_collect_stmt(body, source, lsp_line, lsp_char, best);
936
+ if let Some(cb) = catch_body {
937
+ member_chain_collect_stmt(cb, source, lsp_line, lsp_char, best);
938
+ }
939
+ if let Some(fb) = finally_body {
940
+ member_chain_collect_stmt(fb, source, lsp_line, lsp_char, best);
941
+ }
942
+ }
943
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
944
+ ExportDeclaration::Named(inner) => {
945
+ member_chain_collect_stmt(inner, source, lsp_line, lsp_char, best)
946
+ }
947
+ ExportDeclaration::Default(e) => {
948
+ member_chain_collect_expr(e, source, lsp_line, lsp_char, best)
949
+ }
950
+ },
951
+ Statement::Import { .. }
952
+ | Statement::Break { .. }
953
+ | Statement::Continue { .. }
954
+ | Statement::TypeAlias { .. }
955
+ | Statement::DeclareVar { .. }
956
+ | Statement::DeclareFun { .. } => {}
957
+ }
958
+ }
959
+
960
+ // --- resolve pass ---
961
+
962
+ struct ScopeStack(Vec<HashMap<String, tishlang_ast::Span>>);
963
+
964
+ impl ScopeStack {
965
+ fn new() -> Self {
966
+ Self(vec![HashMap::new()])
967
+ }
968
+ fn fork(&self) -> Self {
969
+ Self(self.0.clone())
970
+ }
971
+ fn push(&mut self) {
972
+ self.0.push(HashMap::new());
973
+ }
974
+ fn pop(&mut self) {
975
+ let _ = self.0.pop();
976
+ }
977
+ fn define(&mut self, name: &str, span: tishlang_ast::Span) {
978
+ if let Some(m) = self.0.last_mut() {
979
+ m.insert(name.to_string(), span);
980
+ }
981
+ }
982
+ fn resolve(&self, name: &str) -> Option<tishlang_ast::Span> {
983
+ for m in self.0.iter().rev() {
984
+ if let Some(s) = m.get(name) {
985
+ return Some(*s);
986
+ }
987
+ }
988
+ None
989
+ }
990
+ }
991
+
992
+ fn define_fun_param_stack(p: &FunParam, scopes: &mut ScopeStack) {
993
+ match p {
994
+ FunParam::Simple(tp) => {
995
+ scopes.define(tp.name.as_ref(), tp.name_span);
996
+ }
997
+ FunParam::Destructure { pattern, .. } => define_pattern_stack(pattern, scopes),
998
+ }
999
+ }
1000
+
1001
+ fn define_pattern_stack(pattern: &DestructPattern, scopes: &mut ScopeStack) {
1002
+ match pattern {
1003
+ DestructPattern::Array(elements) => {
1004
+ for el in elements.iter().flatten() {
1005
+ match el {
1006
+ DestructElement::Ident(n, sp) => scopes.define(n.as_ref(), *sp),
1007
+ DestructElement::Pattern(inner) => define_pattern_stack(inner, scopes),
1008
+ DestructElement::Rest(n, sp) => scopes.define(n.as_ref(), *sp),
1009
+ }
1010
+ }
1011
+ }
1012
+ DestructPattern::Object(props) => {
1013
+ for pr in props {
1014
+ match &pr.value {
1015
+ DestructElement::Ident(n, sp) => scopes.define(n.as_ref(), *sp),
1016
+ DestructElement::Pattern(inner) => define_pattern_stack(inner, scopes),
1017
+ DestructElement::Rest(n, sp) => scopes.define(n.as_ref(), *sp),
1018
+ }
1019
+ }
1020
+ }
1021
+ }
1022
+ }
1023
+
1024
+ fn walk_expr_resolve(
1025
+ expr: &Expr,
1026
+ scopes: &ScopeStack,
1027
+ target: &NameUse,
1028
+ ) -> Option<tishlang_ast::Span> {
1029
+ let tgt = target.span;
1030
+ match expr {
1031
+ Expr::Ident { name, span } if *span == tgt && name.as_ref() == target.name.as_ref() => {
1032
+ scopes.resolve(name.as_ref())
1033
+ }
1034
+ Expr::Assign { name, span, value } => {
1035
+ let sp = synthetic_name_span(span.start, name.as_ref());
1036
+ if sp == tgt && name.as_ref() == target.name.as_ref() {
1037
+ return scopes.resolve(name.as_ref());
1038
+ }
1039
+ walk_expr_resolve(value, scopes, target)
1040
+ }
1041
+ Expr::CompoundAssign {
1042
+ name, span, value, ..
1043
+ }
1044
+ | Expr::LogicalAssign {
1045
+ name, span, value, ..
1046
+ } => {
1047
+ let sp = synthetic_name_span(span.start, name.as_ref());
1048
+ if sp == tgt && name.as_ref() == target.name.as_ref() {
1049
+ return scopes.resolve(name.as_ref());
1050
+ }
1051
+ walk_expr_resolve(value, scopes, target)
1052
+ }
1053
+ Expr::PostfixInc { name, span } | Expr::PostfixDec { name, span } => {
1054
+ let sp = synthetic_name_span(span.start, name.as_ref());
1055
+ if sp == tgt && name.as_ref() == target.name.as_ref() {
1056
+ return scopes.resolve(name.as_ref());
1057
+ }
1058
+ None
1059
+ }
1060
+ Expr::PrefixInc { name, span } | Expr::PrefixDec { name, span } => {
1061
+ let sp = synthetic_name_span(span.start, name.as_ref());
1062
+ if sp == tgt && name.as_ref() == target.name.as_ref() {
1063
+ return scopes.resolve(name.as_ref());
1064
+ }
1065
+ None
1066
+ }
1067
+ Expr::Binary { left, right, .. } => walk_expr_resolve(left, scopes, target)
1068
+ .or_else(|| walk_expr_resolve(right, scopes, target)),
1069
+ Expr::Unary { operand, .. } => walk_expr_resolve(operand, scopes, target),
1070
+ Expr::Call { callee, args, .. } => {
1071
+ walk_expr_resolve(callee, scopes, target).or_else(|| {
1072
+ for a in args {
1073
+ let e = match a {
1074
+ CallArg::Expr(e) => e,
1075
+ CallArg::Spread(e) => e,
1076
+ };
1077
+ if let Some(s) = walk_expr_resolve(e, scopes, target) {
1078
+ return Some(s);
1079
+ }
1080
+ }
1081
+ None
1082
+ })
1083
+ }
1084
+ Expr::New { callee, args, .. } => walk_expr_resolve(callee, scopes, target).or_else(|| {
1085
+ for a in args {
1086
+ let e = match a {
1087
+ CallArg::Expr(e) => e,
1088
+ CallArg::Spread(e) => e,
1089
+ };
1090
+ if let Some(s) = walk_expr_resolve(e, scopes, target) {
1091
+ return Some(s);
1092
+ }
1093
+ }
1094
+ None
1095
+ }),
1096
+ Expr::Member { object, .. } => walk_expr_resolve(object, scopes, target),
1097
+ Expr::Index { object, index, .. } => walk_expr_resolve(object, scopes, target)
1098
+ .or_else(|| walk_expr_resolve(index, scopes, target)),
1099
+ Expr::Conditional {
1100
+ cond,
1101
+ then_branch,
1102
+ else_branch,
1103
+ ..
1104
+ } => walk_expr_resolve(cond, scopes, target)
1105
+ .or_else(|| walk_expr_resolve(then_branch, scopes, target))
1106
+ .or_else(|| walk_expr_resolve(else_branch, scopes, target)),
1107
+ Expr::NullishCoalesce { left, right, .. } => walk_expr_resolve(left, scopes, target)
1108
+ .or_else(|| walk_expr_resolve(right, scopes, target)),
1109
+ Expr::Array { elements, .. } => {
1110
+ for el in elements {
1111
+ let e = match el {
1112
+ tishlang_ast::ArrayElement::Expr(e) => e,
1113
+ tishlang_ast::ArrayElement::Spread(e) => e,
1114
+ };
1115
+ if let Some(s) = walk_expr_resolve(e, scopes, target) {
1116
+ return Some(s);
1117
+ }
1118
+ }
1119
+ None
1120
+ }
1121
+ Expr::Object { props, .. } => {
1122
+ for p in props {
1123
+ let e = match p {
1124
+ tishlang_ast::ObjectProp::KeyValue(_, e) => e,
1125
+ tishlang_ast::ObjectProp::Spread(e) => e,
1126
+ };
1127
+ if let Some(s) = walk_expr_resolve(e, scopes, target) {
1128
+ return Some(s);
1129
+ }
1130
+ }
1131
+ None
1132
+ }
1133
+ Expr::TypeOf { operand, .. } => walk_expr_resolve(operand, scopes, target),
1134
+ Expr::Delete { target: del, .. } => walk_expr_resolve(del, scopes, target),
1135
+ Expr::MemberAssign { object, value, .. } => walk_expr_resolve(object, scopes, target)
1136
+ .or_else(|| walk_expr_resolve(value, scopes, target)),
1137
+ Expr::IndexAssign {
1138
+ object,
1139
+ index,
1140
+ value,
1141
+ ..
1142
+ } => walk_expr_resolve(object, scopes, target)
1143
+ .or_else(|| walk_expr_resolve(index, scopes, target))
1144
+ .or_else(|| walk_expr_resolve(value, scopes, target)),
1145
+ Expr::ArrowFunction { params, body, .. } => {
1146
+ let mut inner = scopes.fork();
1147
+ inner.push();
1148
+ for p in params {
1149
+ define_fun_param_stack(p, &mut inner);
1150
+ }
1151
+ match body {
1152
+ ArrowBody::Expr(e) => walk_expr_resolve(e, &inner, target),
1153
+ ArrowBody::Block(b) => walk_stmt_resolve(b, &mut inner, target),
1154
+ }
1155
+ }
1156
+ Expr::TemplateLiteral { exprs, .. } => {
1157
+ for e in exprs {
1158
+ if let Some(s) = walk_expr_resolve(e, scopes, target) {
1159
+ return Some(s);
1160
+ }
1161
+ }
1162
+ None
1163
+ }
1164
+ Expr::Await { operand, .. } => walk_expr_resolve(operand, scopes, target),
1165
+ Expr::JsxElement {
1166
+ props, children, ..
1167
+ } => {
1168
+ for p in props {
1169
+ match p {
1170
+ tishlang_ast::JsxProp::Attr { value, .. } => {
1171
+ if let tishlang_ast::JsxAttrValue::Expr(e) = value {
1172
+ if let Some(s) = walk_expr_resolve(e, scopes, target) {
1173
+ return Some(s);
1174
+ }
1175
+ }
1176
+ }
1177
+ tishlang_ast::JsxProp::Spread(e) => {
1178
+ if let Some(s) = walk_expr_resolve(e, scopes, target) {
1179
+ return Some(s);
1180
+ }
1181
+ }
1182
+ }
1183
+ }
1184
+ for ch in children {
1185
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
1186
+ if let Some(s) = walk_expr_resolve(e, scopes, target) {
1187
+ return Some(s);
1188
+ }
1189
+ }
1190
+ }
1191
+ None
1192
+ }
1193
+ Expr::JsxFragment { children, .. } => {
1194
+ for ch in children {
1195
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
1196
+ if let Some(s) = walk_expr_resolve(e, scopes, target) {
1197
+ return Some(s);
1198
+ }
1199
+ }
1200
+ }
1201
+ None
1202
+ }
1203
+ Expr::Ident { .. } => None,
1204
+ Expr::Literal { .. } | Expr::NativeModuleLoad { .. } => None,
1205
+ }
1206
+ }
1207
+
1208
+ fn walk_stmt_resolve(
1209
+ stmt: &Statement,
1210
+ scopes: &mut ScopeStack,
1211
+ target: &NameUse,
1212
+ ) -> Option<tishlang_ast::Span> {
1213
+ let tgt_span = target.span;
1214
+ match stmt {
1215
+ Statement::VarDecl {
1216
+ name,
1217
+ name_span,
1218
+ mutable: _,
1219
+ type_ann: _,
1220
+ init,
1221
+ ..
1222
+ } => {
1223
+ if *name_span == tgt_span && name.as_ref() == target.name.as_ref() {
1224
+ return Some(*name_span);
1225
+ }
1226
+ if let Some(e) = init {
1227
+ if let Some(s) = walk_expr_resolve(e, scopes, target) {
1228
+ return Some(s);
1229
+ }
1230
+ }
1231
+ scopes.define(name.as_ref(), *name_span);
1232
+ None
1233
+ }
1234
+ Statement::VarDeclDestructure { pattern, init, .. } => {
1235
+ if let Some(s) = walk_expr_resolve(init, scopes, target) {
1236
+ return Some(s);
1237
+ }
1238
+ define_pattern_stack(pattern, scopes);
1239
+ None
1240
+ }
1241
+ Statement::ExprStmt { expr, .. } => walk_expr_resolve(expr, scopes, target),
1242
+ Statement::If {
1243
+ cond,
1244
+ then_branch,
1245
+ else_branch,
1246
+ ..
1247
+ } => walk_expr_resolve(cond, scopes, target)
1248
+ .or_else(|| walk_stmt_implicit(then_branch, scopes, target))
1249
+ .or_else(|| {
1250
+ else_branch
1251
+ .as_ref()
1252
+ .and_then(|b| walk_stmt_implicit(b, scopes, target))
1253
+ }),
1254
+ Statement::While { cond, body, .. } => walk_expr_resolve(cond, scopes, target)
1255
+ .or_else(|| walk_stmt_implicit(body, scopes, target)),
1256
+ Statement::For {
1257
+ init,
1258
+ cond,
1259
+ update,
1260
+ body,
1261
+ ..
1262
+ } => {
1263
+ scopes.push();
1264
+ if let Some(i) = init {
1265
+ if let Some(s) = walk_stmt_resolve(i, scopes, target) {
1266
+ scopes.pop();
1267
+ return Some(s);
1268
+ }
1269
+ }
1270
+ let r = (|| {
1271
+ if let Some(e) = cond {
1272
+ if let Some(s) = walk_expr_resolve(e, scopes, target) {
1273
+ return Some(s);
1274
+ }
1275
+ }
1276
+ if let Some(e) = update {
1277
+ if let Some(s) = walk_expr_resolve(e, scopes, target) {
1278
+ return Some(s);
1279
+ }
1280
+ }
1281
+ walk_stmt_implicit(body, scopes, target)
1282
+ })();
1283
+ scopes.pop();
1284
+ r
1285
+ }
1286
+ Statement::ForOf {
1287
+ name,
1288
+ name_span,
1289
+ iterable,
1290
+ body,
1291
+ ..
1292
+ } => {
1293
+ if *name_span == tgt_span && name.as_ref() == target.name.as_ref() {
1294
+ return Some(*name_span);
1295
+ }
1296
+ if let Some(s) = walk_expr_resolve(iterable, scopes, target) {
1297
+ return Some(s);
1298
+ }
1299
+ scopes.push();
1300
+ scopes.define(name.as_ref(), *name_span);
1301
+ let r = walk_stmt_implicit(body, scopes, target);
1302
+ scopes.pop();
1303
+ r
1304
+ }
1305
+ Statement::Return { value, .. } => value
1306
+ .as_ref()
1307
+ .and_then(|e| walk_expr_resolve(e, scopes, target)),
1308
+ Statement::Block { statements, .. } => {
1309
+ scopes.push();
1310
+ let mut out = None;
1311
+ for s in statements {
1312
+ if let Some(x) = walk_stmt_resolve(s, scopes, target) {
1313
+ out = Some(x);
1314
+ break;
1315
+ }
1316
+ }
1317
+ scopes.pop();
1318
+ out
1319
+ }
1320
+ // Transparent group (comma-declarators): same scope, no push/pop.
1321
+ Statement::Multi { statements, .. } => {
1322
+ let mut out = None;
1323
+ for s in statements {
1324
+ if let Some(x) = walk_stmt_resolve(s, scopes, target) {
1325
+ out = Some(x);
1326
+ break;
1327
+ }
1328
+ }
1329
+ out
1330
+ }
1331
+ Statement::FunDecl {
1332
+ name,
1333
+ name_span,
1334
+ params,
1335
+ body,
1336
+ ..
1337
+ } => {
1338
+ if *name_span == tgt_span && name.as_ref() == target.name.as_ref() {
1339
+ return Some(*name_span);
1340
+ }
1341
+ scopes.push();
1342
+ scopes.define(name.as_ref(), *name_span);
1343
+ for p in params {
1344
+ define_fun_param_stack(p, scopes);
1345
+ }
1346
+ let r = walk_stmt_resolve(body, scopes, target);
1347
+ scopes.pop();
1348
+ if r.is_some() {
1349
+ return r;
1350
+ }
1351
+ scopes.define(name.as_ref(), *name_span);
1352
+ None
1353
+ }
1354
+ Statement::Switch {
1355
+ expr,
1356
+ cases,
1357
+ default_body,
1358
+ ..
1359
+ } => {
1360
+ if let Some(s) = walk_expr_resolve(expr, scopes, target) {
1361
+ return Some(s);
1362
+ }
1363
+ scopes.push();
1364
+ let mut out = None;
1365
+ for (_ce, stmts) in cases {
1366
+ for st in stmts {
1367
+ if let Some(x) = walk_stmt_resolve(st, scopes, target) {
1368
+ out = Some(x);
1369
+ break;
1370
+ }
1371
+ }
1372
+ if out.is_some() {
1373
+ break;
1374
+ }
1375
+ }
1376
+ if out.is_none() {
1377
+ if let Some(stmts) = default_body {
1378
+ for st in stmts {
1379
+ if let Some(x) = walk_stmt_resolve(st, scopes, target) {
1380
+ out = Some(x);
1381
+ break;
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+ scopes.pop();
1387
+ out
1388
+ }
1389
+ Statement::DoWhile { body, cond, .. } => walk_stmt_implicit(body, scopes, target)
1390
+ .or_else(|| walk_expr_resolve(cond, scopes, target)),
1391
+ Statement::Throw { value, .. } => walk_expr_resolve(value, scopes, target),
1392
+ Statement::Try {
1393
+ body,
1394
+ catch_param,
1395
+ catch_param_span,
1396
+ catch_body,
1397
+ finally_body,
1398
+ ..
1399
+ } => {
1400
+ if let Some(s) = walk_stmt_resolve(body, scopes, target) {
1401
+ return Some(s);
1402
+ }
1403
+ if let (Some(n), Some(sp)) = (catch_param, catch_param_span) {
1404
+ if *sp == tgt_span && n.as_ref() == target.name.as_ref() {
1405
+ return Some(*sp);
1406
+ }
1407
+ }
1408
+ if let Some(cb) = catch_body {
1409
+ scopes.push();
1410
+ if let (Some(n), Some(sp)) = (catch_param, catch_param_span) {
1411
+ scopes.define(n.as_ref(), *sp);
1412
+ }
1413
+ let r = walk_stmt_resolve(cb, scopes, target);
1414
+ scopes.pop();
1415
+ if r.is_some() {
1416
+ return r;
1417
+ }
1418
+ }
1419
+ if let Some(fb) = finally_body {
1420
+ return walk_stmt_resolve(fb, scopes, target);
1421
+ }
1422
+ None
1423
+ }
1424
+ Statement::Import { specifiers, .. } => {
1425
+ for sp in specifiers {
1426
+ match sp {
1427
+ ImportSpecifier::Named {
1428
+ name,
1429
+ name_span,
1430
+ alias,
1431
+ alias_span,
1432
+ } => {
1433
+ let local = alias
1434
+ .as_ref()
1435
+ .map(|a| a.clone())
1436
+ .unwrap_or_else(|| name.clone());
1437
+ let spn = alias_span.as_ref().unwrap_or(name_span);
1438
+ if *spn == tgt_span && local.as_ref() == target.name.as_ref() {
1439
+ return Some(*spn);
1440
+ }
1441
+ scopes.define(local.as_ref(), *spn);
1442
+ }
1443
+ ImportSpecifier::Namespace { name, name_span } => {
1444
+ if *name_span == tgt_span && name.as_ref() == target.name.as_ref() {
1445
+ return Some(*name_span);
1446
+ }
1447
+ scopes.define(name.as_ref(), *name_span);
1448
+ }
1449
+ ImportSpecifier::Default { name, name_span } => {
1450
+ if *name_span == tgt_span && name.as_ref() == target.name.as_ref() {
1451
+ return Some(*name_span);
1452
+ }
1453
+ scopes.define(name.as_ref(), *name_span);
1454
+ }
1455
+ }
1456
+ }
1457
+ None
1458
+ }
1459
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
1460
+ ExportDeclaration::Named(inner) => walk_stmt_resolve(inner, scopes, target),
1461
+ ExportDeclaration::Default(e) => walk_expr_resolve(e, scopes, target),
1462
+ },
1463
+ Statement::TypeAlias {
1464
+ name, name_span, ..
1465
+ } => {
1466
+ if *name_span == tgt_span && name.as_ref() == target.name.as_ref() {
1467
+ return Some(*name_span);
1468
+ }
1469
+ scopes.define(name.as_ref(), *name_span);
1470
+ None
1471
+ }
1472
+ Statement::DeclareVar {
1473
+ name, name_span, ..
1474
+ } => {
1475
+ if *name_span == tgt_span && name.as_ref() == target.name.as_ref() {
1476
+ return Some(*name_span);
1477
+ }
1478
+ scopes.define(name.as_ref(), *name_span);
1479
+ None
1480
+ }
1481
+ Statement::DeclareFun {
1482
+ name,
1483
+ name_span,
1484
+ params,
1485
+ ..
1486
+ } => {
1487
+ if *name_span == tgt_span && name.as_ref() == target.name.as_ref() {
1488
+ return Some(*name_span);
1489
+ }
1490
+ scopes.push();
1491
+ scopes.define(name.as_ref(), *name_span);
1492
+ for p in params {
1493
+ define_fun_param_stack(p, scopes);
1494
+ }
1495
+ scopes.pop();
1496
+ None
1497
+ }
1498
+ Statement::Break { .. } | Statement::Continue { .. } => None,
1499
+ }
1500
+ }
1501
+
1502
+ fn walk_stmt_implicit(
1503
+ stmt: &Statement,
1504
+ scopes: &mut ScopeStack,
1505
+ target: &NameUse,
1506
+ ) -> Option<tishlang_ast::Span> {
1507
+ if matches!(stmt, Statement::Block { .. }) {
1508
+ walk_stmt_resolve(stmt, scopes, target)
1509
+ } else {
1510
+ scopes.push();
1511
+ let r = walk_stmt_resolve(stmt, scopes, target);
1512
+ scopes.pop();
1513
+ r
1514
+ }
1515
+ }
1516
+
1517
+ /// Identifier reference with no binding in lexical scope (same rules as [`definition_span`]).
1518
+ #[derive(Debug, Clone, PartialEq)]
1519
+ pub struct UnresolvedIdentifier {
1520
+ pub name: Arc<str>,
1521
+ pub span: tishlang_ast::Span,
1522
+ }
1523
+
1524
+ /// Names always present on the interpreter root scope (see `tishlang_eval`) and listed in
1525
+ /// `stdlib/builtins.d.tish`. They must not produce "unresolved identifier" diagnostics when
1526
+ /// used without a `let`/`import`.
1527
+ pub fn is_runtime_global_ident(name: &str) -> bool {
1528
+ matches!(
1529
+ name,
1530
+ "console"
1531
+ | "parseInt"
1532
+ | "parseFloat"
1533
+ | "decodeURI"
1534
+ | "encodeURI"
1535
+ | "Boolean"
1536
+ | "isFinite"
1537
+ | "isNaN"
1538
+ | "Infinity"
1539
+ | "NaN"
1540
+ | "Math"
1541
+ | "JSON"
1542
+ | "Object"
1543
+ | "Array"
1544
+ | "String"
1545
+ | "Date"
1546
+ | "Set"
1547
+ | "Map"
1548
+ | "Float64Array"
1549
+ | "Float32Array"
1550
+ | "Int8Array"
1551
+ | "Uint8Array"
1552
+ | "Uint8ClampedArray"
1553
+ | "Int16Array"
1554
+ | "Uint16Array"
1555
+ | "Int32Array"
1556
+ | "Uint32Array"
1557
+ | "AudioContext"
1558
+ | "RegExp"
1559
+ | "setTimeout"
1560
+ | "setInterval"
1561
+ | "clearTimeout"
1562
+ | "clearInterval"
1563
+ )
1564
+ }
1565
+
1566
+ fn record_unresolved(
1567
+ scopes: &ScopeStack,
1568
+ name: &Arc<str>,
1569
+ span: tishlang_ast::Span,
1570
+ out: &mut Vec<UnresolvedIdentifier>,
1571
+ ) {
1572
+ if is_runtime_global_ident(name.as_ref()) {
1573
+ return;
1574
+ }
1575
+ if scopes.resolve(name.as_ref()).is_none() {
1576
+ out.push(UnresolvedIdentifier {
1577
+ name: name.clone(),
1578
+ span,
1579
+ });
1580
+ }
1581
+ }
1582
+
1583
+ fn check_unresolved_expr(expr: &Expr, scopes: &ScopeStack, out: &mut Vec<UnresolvedIdentifier>) {
1584
+ match expr {
1585
+ Expr::Ident { name, span } => record_unresolved(scopes, name, *span, out),
1586
+ Expr::Assign { name, span, value } => {
1587
+ let sp = synthetic_name_span(span.start, name.as_ref());
1588
+ record_unresolved(scopes, name, sp, out);
1589
+ check_unresolved_expr(value, scopes, out);
1590
+ }
1591
+ Expr::CompoundAssign {
1592
+ name, span, value, ..
1593
+ }
1594
+ | Expr::LogicalAssign {
1595
+ name, span, value, ..
1596
+ } => {
1597
+ let sp = synthetic_name_span(span.start, name.as_ref());
1598
+ record_unresolved(scopes, name, sp, out);
1599
+ check_unresolved_expr(value, scopes, out);
1600
+ }
1601
+ Expr::PostfixInc { name, span } | Expr::PostfixDec { name, span } => {
1602
+ let sp = synthetic_name_span(span.start, name.as_ref());
1603
+ record_unresolved(scopes, name, sp, out);
1604
+ }
1605
+ Expr::PrefixInc { name, span } | Expr::PrefixDec { name, span } => {
1606
+ let sp = synthetic_name_span(span.start, name.as_ref());
1607
+ record_unresolved(scopes, name, sp, out);
1608
+ }
1609
+ Expr::Binary { left, right, .. } => {
1610
+ check_unresolved_expr(left, scopes, out);
1611
+ check_unresolved_expr(right, scopes, out);
1612
+ }
1613
+ Expr::Unary { operand, .. } => check_unresolved_expr(operand, scopes, out),
1614
+ Expr::Call { callee, args, .. } => {
1615
+ check_unresolved_expr(callee, scopes, out);
1616
+ for a in args {
1617
+ let e = match a {
1618
+ CallArg::Expr(e) => e,
1619
+ CallArg::Spread(e) => e,
1620
+ };
1621
+ check_unresolved_expr(e, scopes, out);
1622
+ }
1623
+ }
1624
+ Expr::New { callee, args, .. } => {
1625
+ check_unresolved_expr(callee, scopes, out);
1626
+ for a in args {
1627
+ let e = match a {
1628
+ CallArg::Expr(e) => e,
1629
+ CallArg::Spread(e) => e,
1630
+ };
1631
+ check_unresolved_expr(e, scopes, out);
1632
+ }
1633
+ }
1634
+ Expr::Member { object, prop, .. } => {
1635
+ check_unresolved_expr(object, scopes, out);
1636
+ if let tishlang_ast::MemberProp::Expr(ix) = prop {
1637
+ check_unresolved_expr(ix, scopes, out);
1638
+ }
1639
+ }
1640
+ Expr::Index { object, index, .. } => {
1641
+ check_unresolved_expr(object, scopes, out);
1642
+ check_unresolved_expr(index, scopes, out);
1643
+ }
1644
+ Expr::Conditional {
1645
+ cond,
1646
+ then_branch,
1647
+ else_branch,
1648
+ ..
1649
+ } => {
1650
+ check_unresolved_expr(cond, scopes, out);
1651
+ check_unresolved_expr(then_branch, scopes, out);
1652
+ check_unresolved_expr(else_branch, scopes, out);
1653
+ }
1654
+ Expr::NullishCoalesce { left, right, .. } => {
1655
+ check_unresolved_expr(left, scopes, out);
1656
+ check_unresolved_expr(right, scopes, out);
1657
+ }
1658
+ Expr::Array { elements, .. } => {
1659
+ for el in elements {
1660
+ let e = match el {
1661
+ tishlang_ast::ArrayElement::Expr(e) => e,
1662
+ tishlang_ast::ArrayElement::Spread(e) => e,
1663
+ };
1664
+ check_unresolved_expr(e, scopes, out);
1665
+ }
1666
+ }
1667
+ Expr::Object { props, .. } => {
1668
+ for p in props {
1669
+ let e = match p {
1670
+ tishlang_ast::ObjectProp::KeyValue(_, e) => e,
1671
+ tishlang_ast::ObjectProp::Spread(e) => e,
1672
+ };
1673
+ check_unresolved_expr(e, scopes, out);
1674
+ }
1675
+ }
1676
+ Expr::TypeOf { operand, .. } => check_unresolved_expr(operand, scopes, out),
1677
+ Expr::Delete { target: del, .. } => check_unresolved_expr(del, scopes, out),
1678
+ Expr::MemberAssign { object, value, .. } => {
1679
+ check_unresolved_expr(object, scopes, out);
1680
+ check_unresolved_expr(value, scopes, out);
1681
+ }
1682
+ Expr::IndexAssign {
1683
+ object,
1684
+ index,
1685
+ value,
1686
+ ..
1687
+ } => {
1688
+ check_unresolved_expr(object, scopes, out);
1689
+ check_unresolved_expr(index, scopes, out);
1690
+ check_unresolved_expr(value, scopes, out);
1691
+ }
1692
+ Expr::ArrowFunction { params, body, .. } => {
1693
+ let mut inner = scopes.fork();
1694
+ inner.push();
1695
+ for p in params {
1696
+ define_fun_param_stack(p, &mut inner);
1697
+ }
1698
+ match body {
1699
+ ArrowBody::Expr(e) => check_unresolved_expr(e, &inner, out),
1700
+ ArrowBody::Block(b) => check_unresolved_stmt(b, &mut inner, out),
1701
+ }
1702
+ }
1703
+ Expr::TemplateLiteral { exprs, .. } => {
1704
+ for e in exprs {
1705
+ check_unresolved_expr(e, scopes, out);
1706
+ }
1707
+ }
1708
+ Expr::Await { operand, .. } => check_unresolved_expr(operand, scopes, out),
1709
+ Expr::JsxElement {
1710
+ props, children, ..
1711
+ } => {
1712
+ for p in props {
1713
+ match p {
1714
+ tishlang_ast::JsxProp::Attr { value, .. } => {
1715
+ if let tishlang_ast::JsxAttrValue::Expr(e) = value {
1716
+ check_unresolved_expr(e, scopes, out);
1717
+ }
1718
+ }
1719
+ tishlang_ast::JsxProp::Spread(e) => check_unresolved_expr(e, scopes, out),
1720
+ }
1721
+ }
1722
+ for ch in children {
1723
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
1724
+ check_unresolved_expr(e, scopes, out);
1725
+ }
1726
+ }
1727
+ }
1728
+ Expr::JsxFragment { children, .. } => {
1729
+ for ch in children {
1730
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
1731
+ check_unresolved_expr(e, scopes, out);
1732
+ }
1733
+ }
1734
+ }
1735
+ Expr::Literal { .. } | Expr::NativeModuleLoad { .. } => {}
1736
+ }
1737
+ }
1738
+
1739
+ fn check_stmt_implicit_unresolved(
1740
+ stmt: &Statement,
1741
+ scopes: &mut ScopeStack,
1742
+ out: &mut Vec<UnresolvedIdentifier>,
1743
+ ) {
1744
+ if matches!(stmt, Statement::Block { .. }) {
1745
+ check_unresolved_stmt(stmt, scopes, out);
1746
+ } else {
1747
+ scopes.push();
1748
+ check_unresolved_stmt(stmt, scopes, out);
1749
+ scopes.pop();
1750
+ }
1751
+ }
1752
+
1753
+ fn check_unresolved_stmt(
1754
+ stmt: &Statement,
1755
+ scopes: &mut ScopeStack,
1756
+ out: &mut Vec<UnresolvedIdentifier>,
1757
+ ) {
1758
+ match stmt {
1759
+ Statement::VarDecl {
1760
+ name,
1761
+ name_span,
1762
+ init,
1763
+ ..
1764
+ } => {
1765
+ if let Some(e) = init {
1766
+ check_unresolved_expr(e, scopes, out);
1767
+ }
1768
+ scopes.define(name.as_ref(), *name_span);
1769
+ }
1770
+ Statement::VarDeclDestructure { pattern, init, .. } => {
1771
+ check_unresolved_expr(init, scopes, out);
1772
+ define_pattern_stack(pattern, scopes);
1773
+ }
1774
+ Statement::ExprStmt { expr, .. } => check_unresolved_expr(expr, scopes, out),
1775
+ Statement::If {
1776
+ cond,
1777
+ then_branch,
1778
+ else_branch,
1779
+ ..
1780
+ } => {
1781
+ check_unresolved_expr(cond, scopes, out);
1782
+ check_stmt_implicit_unresolved(then_branch, scopes, out);
1783
+ if let Some(b) = else_branch {
1784
+ check_stmt_implicit_unresolved(b, scopes, out);
1785
+ }
1786
+ }
1787
+ Statement::While { cond, body, .. } => {
1788
+ check_unresolved_expr(cond, scopes, out);
1789
+ check_stmt_implicit_unresolved(body, scopes, out);
1790
+ }
1791
+ Statement::For {
1792
+ init,
1793
+ cond,
1794
+ update,
1795
+ body,
1796
+ ..
1797
+ } => {
1798
+ scopes.push();
1799
+ if let Some(i) = init {
1800
+ check_unresolved_stmt(i, scopes, out);
1801
+ }
1802
+ if let Some(e) = cond {
1803
+ check_unresolved_expr(e, scopes, out);
1804
+ }
1805
+ if let Some(e) = update {
1806
+ check_unresolved_expr(e, scopes, out);
1807
+ }
1808
+ check_stmt_implicit_unresolved(body, scopes, out);
1809
+ scopes.pop();
1810
+ }
1811
+ Statement::ForOf {
1812
+ name,
1813
+ name_span,
1814
+ iterable,
1815
+ body,
1816
+ ..
1817
+ } => {
1818
+ check_unresolved_expr(iterable, scopes, out);
1819
+ scopes.push();
1820
+ scopes.define(name.as_ref(), *name_span);
1821
+ check_stmt_implicit_unresolved(body, scopes, out);
1822
+ scopes.pop();
1823
+ }
1824
+ Statement::Return { value, .. } => {
1825
+ if let Some(e) = value {
1826
+ check_unresolved_expr(e, scopes, out);
1827
+ }
1828
+ }
1829
+ Statement::Block { statements, .. } => {
1830
+ scopes.push();
1831
+ for s in statements {
1832
+ check_unresolved_stmt(s, scopes, out);
1833
+ }
1834
+ scopes.pop();
1835
+ }
1836
+ // Transparent group (comma-declarators): same scope, no push/pop.
1837
+ Statement::Multi { statements, .. } => {
1838
+ for s in statements {
1839
+ check_unresolved_stmt(s, scopes, out);
1840
+ }
1841
+ }
1842
+ Statement::FunDecl {
1843
+ name,
1844
+ name_span,
1845
+ params,
1846
+ body,
1847
+ ..
1848
+ } => {
1849
+ scopes.push();
1850
+ scopes.define(name.as_ref(), *name_span);
1851
+ for p in params {
1852
+ define_fun_param_stack(p, scopes);
1853
+ }
1854
+ check_unresolved_stmt(body, scopes, out);
1855
+ scopes.pop();
1856
+ scopes.define(name.as_ref(), *name_span);
1857
+ }
1858
+ Statement::Switch {
1859
+ expr,
1860
+ cases,
1861
+ default_body,
1862
+ ..
1863
+ } => {
1864
+ check_unresolved_expr(expr, scopes, out);
1865
+ scopes.push();
1866
+ for (_ce, stmts) in cases {
1867
+ for st in stmts {
1868
+ check_unresolved_stmt(st, scopes, out);
1869
+ }
1870
+ }
1871
+ if let Some(stmts) = default_body {
1872
+ for st in stmts {
1873
+ check_unresolved_stmt(st, scopes, out);
1874
+ }
1875
+ }
1876
+ scopes.pop();
1877
+ }
1878
+ Statement::DoWhile { body, cond, .. } => {
1879
+ check_stmt_implicit_unresolved(body, scopes, out);
1880
+ check_unresolved_expr(cond, scopes, out);
1881
+ }
1882
+ Statement::Throw { value, .. } => check_unresolved_expr(value, scopes, out),
1883
+ Statement::Try {
1884
+ body,
1885
+ catch_param,
1886
+ catch_param_span,
1887
+ catch_body,
1888
+ finally_body,
1889
+ ..
1890
+ } => {
1891
+ check_unresolved_stmt(body, scopes, out);
1892
+ if let Some(cb) = catch_body {
1893
+ scopes.push();
1894
+ if let (Some(n), Some(sp)) = (catch_param, catch_param_span) {
1895
+ scopes.define(n.as_ref(), *sp);
1896
+ }
1897
+ check_unresolved_stmt(cb, scopes, out);
1898
+ scopes.pop();
1899
+ }
1900
+ if let Some(fb) = finally_body {
1901
+ check_unresolved_stmt(fb, scopes, out);
1902
+ }
1903
+ }
1904
+ Statement::Import { specifiers, .. } => {
1905
+ for sp in specifiers {
1906
+ match sp {
1907
+ ImportSpecifier::Named {
1908
+ name,
1909
+ name_span,
1910
+ alias,
1911
+ alias_span,
1912
+ } => {
1913
+ let local = alias
1914
+ .as_ref()
1915
+ .map(|a| a.clone())
1916
+ .unwrap_or_else(|| name.clone());
1917
+ let spn = alias_span.as_ref().unwrap_or(name_span);
1918
+ scopes.define(local.as_ref(), *spn);
1919
+ }
1920
+ ImportSpecifier::Namespace { name, name_span } => {
1921
+ scopes.define(name.as_ref(), *name_span);
1922
+ }
1923
+ ImportSpecifier::Default { name, name_span } => {
1924
+ scopes.define(name.as_ref(), *name_span);
1925
+ }
1926
+ }
1927
+ }
1928
+ }
1929
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
1930
+ ExportDeclaration::Named(inner) => check_unresolved_stmt(inner, scopes, out),
1931
+ ExportDeclaration::Default(e) => check_unresolved_expr(e, scopes, out),
1932
+ },
1933
+ Statement::TypeAlias {
1934
+ name, name_span, ..
1935
+ } => {
1936
+ scopes.define(name.as_ref(), *name_span);
1937
+ }
1938
+ Statement::DeclareVar {
1939
+ name, name_span, ..
1940
+ } => {
1941
+ scopes.define(name.as_ref(), *name_span);
1942
+ }
1943
+ Statement::DeclareFun {
1944
+ name,
1945
+ name_span,
1946
+ params,
1947
+ ..
1948
+ } => {
1949
+ scopes.push();
1950
+ scopes.define(name.as_ref(), *name_span);
1951
+ for p in params {
1952
+ define_fun_param_stack(p, scopes);
1953
+ }
1954
+ scopes.pop();
1955
+ scopes.define(name.as_ref(), *name_span);
1956
+ }
1957
+ Statement::Break { .. } | Statement::Continue { .. } => {}
1958
+ }
1959
+ }
1960
+
1961
+ /// Collect unresolved simple-name references across `program` (top-level scope accumulates like `definition_span`).
1962
+ pub fn collect_unresolved_identifiers(program: &Program) -> Vec<UnresolvedIdentifier> {
1963
+ let mut out = Vec::new();
1964
+ let mut scopes = ScopeStack::new();
1965
+ for stmt in &program.statements {
1966
+ check_unresolved_stmt(stmt, &mut scopes, &mut out);
1967
+ }
1968
+ out
1969
+ }
1970
+
1971
+ /// Classify an unused binding for editor messaging (mirrors common TS/ESLint groupings).
1972
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1973
+ pub enum UnusedBindingKind {
1974
+ Import,
1975
+ Variable,
1976
+ Parameter,
1977
+ }
1978
+
1979
+ /// A declared name that is never read (same resolution rules as [`reference_spans_for_def`]).
1980
+ #[derive(Debug, Clone, PartialEq)]
1981
+ pub struct UnusedBinding {
1982
+ pub name: Arc<str>,
1983
+ pub span: tishlang_ast::Span,
1984
+ pub kind: UnusedBindingKind,
1985
+ }
1986
+
1987
+ #[derive(Debug, Clone)]
1988
+ struct BindingSite {
1989
+ name: Arc<str>,
1990
+ span: tishlang_ast::Span,
1991
+ kind: UnusedBindingKind,
1992
+ exported: bool,
1993
+ }
1994
+
1995
+ fn enumerate_pattern_bindings(
1996
+ pattern: &DestructPattern,
1997
+ kind: UnusedBindingKind,
1998
+ exported: bool,
1999
+ out: &mut Vec<BindingSite>,
2000
+ ) {
2001
+ match pattern {
2002
+ DestructPattern::Array(elements) => {
2003
+ for el in elements.iter().flatten() {
2004
+ match el {
2005
+ DestructElement::Ident(n, sp) => out.push(BindingSite {
2006
+ name: n.clone(),
2007
+ span: *sp,
2008
+ kind,
2009
+ exported,
2010
+ }),
2011
+ DestructElement::Pattern(inner) => {
2012
+ enumerate_pattern_bindings(inner, kind, exported, out);
2013
+ }
2014
+ DestructElement::Rest(n, sp) => out.push(BindingSite {
2015
+ name: n.clone(),
2016
+ span: *sp,
2017
+ kind,
2018
+ exported,
2019
+ }),
2020
+ }
2021
+ }
2022
+ }
2023
+ DestructPattern::Object(props) => {
2024
+ for pr in props {
2025
+ match &pr.value {
2026
+ DestructElement::Ident(n, sp) => out.push(BindingSite {
2027
+ name: n.clone(),
2028
+ span: *sp,
2029
+ kind,
2030
+ exported,
2031
+ }),
2032
+ DestructElement::Pattern(inner) => {
2033
+ enumerate_pattern_bindings(inner, kind, exported, out);
2034
+ }
2035
+ DestructElement::Rest(n, sp) => out.push(BindingSite {
2036
+ name: n.clone(),
2037
+ span: *sp,
2038
+ kind,
2039
+ exported,
2040
+ }),
2041
+ }
2042
+ }
2043
+ }
2044
+ }
2045
+ }
2046
+
2047
+ fn enumerate_fun_param(p: &FunParam, exported: bool, out: &mut Vec<BindingSite>) {
2048
+ match p {
2049
+ FunParam::Simple(tp) => {
2050
+ out.push(BindingSite {
2051
+ name: tp.name.clone(),
2052
+ span: tp.name_span,
2053
+ kind: UnusedBindingKind::Parameter,
2054
+ exported,
2055
+ });
2056
+ if let Some(e) = &tp.default {
2057
+ enumerate_expr(e, exported, out);
2058
+ }
2059
+ }
2060
+ FunParam::Destructure {
2061
+ pattern, default, ..
2062
+ } => {
2063
+ enumerate_pattern_bindings(pattern, UnusedBindingKind::Parameter, exported, out);
2064
+ if let Some(e) = default {
2065
+ enumerate_expr(e, exported, out);
2066
+ }
2067
+ }
2068
+ }
2069
+ }
2070
+
2071
+ fn enumerate_expr(expr: &Expr, exported: bool, out: &mut Vec<BindingSite>) {
2072
+ match expr {
2073
+ Expr::Literal { .. } | Expr::Ident { .. } | Expr::NativeModuleLoad { .. } => {}
2074
+ Expr::Binary { left, right, .. } => {
2075
+ enumerate_expr(left, exported, out);
2076
+ enumerate_expr(right, exported, out);
2077
+ }
2078
+ Expr::Unary { operand, .. } => enumerate_expr(operand, exported, out),
2079
+ Expr::Call { callee, args, .. } => {
2080
+ enumerate_expr(callee, exported, out);
2081
+ for a in args {
2082
+ let e = match a {
2083
+ CallArg::Expr(e) => e,
2084
+ CallArg::Spread(e) => e,
2085
+ };
2086
+ enumerate_expr(e, exported, out);
2087
+ }
2088
+ }
2089
+ Expr::New { callee, args, .. } => {
2090
+ enumerate_expr(callee, exported, out);
2091
+ for a in args {
2092
+ let e = match a {
2093
+ CallArg::Expr(e) => e,
2094
+ CallArg::Spread(e) => e,
2095
+ };
2096
+ enumerate_expr(e, exported, out);
2097
+ }
2098
+ }
2099
+ Expr::Member { object, prop, .. } => {
2100
+ enumerate_expr(object, exported, out);
2101
+ if let MemberProp::Expr(ix) = prop {
2102
+ enumerate_expr(ix, exported, out);
2103
+ }
2104
+ }
2105
+ Expr::Index { object, index, .. } => {
2106
+ enumerate_expr(object, exported, out);
2107
+ enumerate_expr(index, exported, out);
2108
+ }
2109
+ Expr::Conditional {
2110
+ cond,
2111
+ then_branch,
2112
+ else_branch,
2113
+ ..
2114
+ } => {
2115
+ enumerate_expr(cond, exported, out);
2116
+ enumerate_expr(then_branch, exported, out);
2117
+ enumerate_expr(else_branch, exported, out);
2118
+ }
2119
+ Expr::NullishCoalesce { left, right, .. } => {
2120
+ enumerate_expr(left, exported, out);
2121
+ enumerate_expr(right, exported, out);
2122
+ }
2123
+ Expr::Array { elements, .. } => {
2124
+ for el in elements {
2125
+ let e = match el {
2126
+ tishlang_ast::ArrayElement::Expr(e) => e,
2127
+ tishlang_ast::ArrayElement::Spread(e) => e,
2128
+ };
2129
+ enumerate_expr(e, exported, out);
2130
+ }
2131
+ }
2132
+ Expr::Object { props, .. } => {
2133
+ for p in props {
2134
+ let e = match p {
2135
+ tishlang_ast::ObjectProp::KeyValue(_, e) => e,
2136
+ tishlang_ast::ObjectProp::Spread(e) => e,
2137
+ };
2138
+ enumerate_expr(e, exported, out);
2139
+ }
2140
+ }
2141
+ Expr::Assign { value, .. }
2142
+ | Expr::CompoundAssign { value, .. }
2143
+ | Expr::LogicalAssign { value, .. } => {
2144
+ enumerate_expr(value, exported, out);
2145
+ }
2146
+ Expr::PostfixInc { .. }
2147
+ | Expr::PostfixDec { .. }
2148
+ | Expr::PrefixInc { .. }
2149
+ | Expr::PrefixDec { .. } => {}
2150
+ Expr::TypeOf { operand, .. } => enumerate_expr(operand, exported, out),
2151
+ Expr::Delete { target: del, .. } => enumerate_expr(del, exported, out),
2152
+ Expr::MemberAssign { object, value, .. } => {
2153
+ enumerate_expr(object, exported, out);
2154
+ enumerate_expr(value, exported, out);
2155
+ }
2156
+ Expr::IndexAssign {
2157
+ object,
2158
+ index,
2159
+ value,
2160
+ ..
2161
+ } => {
2162
+ enumerate_expr(object, exported, out);
2163
+ enumerate_expr(index, exported, out);
2164
+ enumerate_expr(value, exported, out);
2165
+ }
2166
+ Expr::ArrowFunction { params, body, .. } => {
2167
+ for p in params {
2168
+ enumerate_fun_param(p, exported, out);
2169
+ }
2170
+ match body {
2171
+ ArrowBody::Expr(e) => enumerate_expr(e, exported, out),
2172
+ ArrowBody::Block(b) => enumerate_stmt(b, exported, out),
2173
+ }
2174
+ }
2175
+ Expr::TemplateLiteral { exprs, .. } => {
2176
+ for e in exprs {
2177
+ enumerate_expr(e, exported, out);
2178
+ }
2179
+ }
2180
+ Expr::Await { operand, .. } => enumerate_expr(operand, exported, out),
2181
+ Expr::JsxElement {
2182
+ props, children, ..
2183
+ } => {
2184
+ for p in props {
2185
+ match p {
2186
+ tishlang_ast::JsxProp::Attr { value, .. } => {
2187
+ if let tishlang_ast::JsxAttrValue::Expr(e) = value {
2188
+ enumerate_expr(e, exported, out);
2189
+ }
2190
+ }
2191
+ tishlang_ast::JsxProp::Spread(e) => enumerate_expr(e, exported, out),
2192
+ }
2193
+ }
2194
+ for ch in children {
2195
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
2196
+ enumerate_expr(e, exported, out);
2197
+ }
2198
+ }
2199
+ }
2200
+ Expr::JsxFragment { children, .. } => {
2201
+ for ch in children {
2202
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
2203
+ enumerate_expr(e, exported, out);
2204
+ }
2205
+ }
2206
+ }
2207
+ }
2208
+ }
2209
+
2210
+ fn enumerate_stmt(stmt: &Statement, exported: bool, out: &mut Vec<BindingSite>) {
2211
+ match stmt {
2212
+ Statement::Import { specifiers, .. } => {
2213
+ for sp in specifiers {
2214
+ match sp {
2215
+ ImportSpecifier::Named {
2216
+ name,
2217
+ name_span,
2218
+ alias,
2219
+ alias_span,
2220
+ } => {
2221
+ let local = alias
2222
+ .as_ref()
2223
+ .map(|a| a.clone())
2224
+ .unwrap_or_else(|| name.clone());
2225
+ let spn = alias_span.as_ref().unwrap_or(name_span);
2226
+ out.push(BindingSite {
2227
+ name: local,
2228
+ span: *spn,
2229
+ kind: UnusedBindingKind::Import,
2230
+ exported: false,
2231
+ });
2232
+ }
2233
+ ImportSpecifier::Namespace { name, name_span } => {
2234
+ out.push(BindingSite {
2235
+ name: name.clone(),
2236
+ span: *name_span,
2237
+ kind: UnusedBindingKind::Import,
2238
+ exported: false,
2239
+ });
2240
+ }
2241
+ ImportSpecifier::Default { name, name_span } => {
2242
+ out.push(BindingSite {
2243
+ name: name.clone(),
2244
+ span: *name_span,
2245
+ kind: UnusedBindingKind::Import,
2246
+ exported: false,
2247
+ });
2248
+ }
2249
+ }
2250
+ }
2251
+ }
2252
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
2253
+ ExportDeclaration::Named(inner) => enumerate_stmt(inner, true, out),
2254
+ ExportDeclaration::Default(e) => enumerate_expr(e, exported, out),
2255
+ },
2256
+ Statement::VarDecl {
2257
+ name,
2258
+ name_span,
2259
+ init,
2260
+ ..
2261
+ } => {
2262
+ out.push(BindingSite {
2263
+ name: name.clone(),
2264
+ span: *name_span,
2265
+ kind: UnusedBindingKind::Variable,
2266
+ exported,
2267
+ });
2268
+ if let Some(e) = init {
2269
+ enumerate_expr(e, exported, out);
2270
+ }
2271
+ }
2272
+ Statement::VarDeclDestructure { pattern, init, .. } => {
2273
+ enumerate_pattern_bindings(pattern, UnusedBindingKind::Variable, exported, out);
2274
+ enumerate_expr(init, exported, out);
2275
+ }
2276
+ Statement::ExprStmt { expr, .. } => enumerate_expr(expr, exported, out),
2277
+ Statement::If {
2278
+ cond,
2279
+ then_branch,
2280
+ else_branch,
2281
+ ..
2282
+ } => {
2283
+ enumerate_expr(cond, exported, out);
2284
+ enumerate_stmt(then_branch, exported, out);
2285
+ if let Some(b) = else_branch {
2286
+ enumerate_stmt(b, exported, out);
2287
+ }
2288
+ }
2289
+ Statement::While { cond, body, .. } => {
2290
+ enumerate_expr(cond, exported, out);
2291
+ enumerate_stmt(body, exported, out);
2292
+ }
2293
+ Statement::For {
2294
+ init,
2295
+ cond,
2296
+ update,
2297
+ body,
2298
+ ..
2299
+ } => {
2300
+ if let Some(i) = init {
2301
+ enumerate_stmt(i, exported, out);
2302
+ }
2303
+ if let Some(e) = cond {
2304
+ enumerate_expr(e, exported, out);
2305
+ }
2306
+ if let Some(e) = update {
2307
+ enumerate_expr(e, exported, out);
2308
+ }
2309
+ enumerate_stmt(body, exported, out);
2310
+ }
2311
+ Statement::ForOf {
2312
+ name,
2313
+ name_span,
2314
+ iterable,
2315
+ body,
2316
+ ..
2317
+ } => {
2318
+ out.push(BindingSite {
2319
+ name: name.clone(),
2320
+ span: *name_span,
2321
+ kind: UnusedBindingKind::Variable,
2322
+ exported,
2323
+ });
2324
+ enumerate_expr(iterable, exported, out);
2325
+ enumerate_stmt(body, exported, out);
2326
+ }
2327
+ Statement::Return { value, .. } => {
2328
+ if let Some(e) = value {
2329
+ enumerate_expr(e, exported, out);
2330
+ }
2331
+ }
2332
+ Statement::Block { statements, .. } => {
2333
+ for s in statements {
2334
+ enumerate_stmt(s, exported, out);
2335
+ }
2336
+ }
2337
+ Statement::Multi { statements, .. } => {
2338
+ for s in statements {
2339
+ enumerate_stmt(s, exported, out);
2340
+ }
2341
+ }
2342
+ Statement::FunDecl {
2343
+ name,
2344
+ name_span,
2345
+ params,
2346
+ rest_param,
2347
+ body,
2348
+ ..
2349
+ } => {
2350
+ out.push(BindingSite {
2351
+ name: name.clone(),
2352
+ span: *name_span,
2353
+ kind: UnusedBindingKind::Variable,
2354
+ exported,
2355
+ });
2356
+ for p in params {
2357
+ enumerate_fun_param(p, exported, out);
2358
+ }
2359
+ if let Some(r) = rest_param {
2360
+ out.push(BindingSite {
2361
+ name: r.name.clone(),
2362
+ span: r.name_span,
2363
+ kind: UnusedBindingKind::Parameter,
2364
+ exported,
2365
+ });
2366
+ }
2367
+ enumerate_stmt(body, exported, out);
2368
+ }
2369
+ Statement::Switch {
2370
+ expr,
2371
+ cases,
2372
+ default_body,
2373
+ ..
2374
+ } => {
2375
+ enumerate_expr(expr, exported, out);
2376
+ for (ce, stmts) in cases {
2377
+ if let Some(e) = ce {
2378
+ enumerate_expr(e, exported, out);
2379
+ }
2380
+ for st in stmts {
2381
+ enumerate_stmt(st, exported, out);
2382
+ }
2383
+ }
2384
+ if let Some(stmts) = default_body {
2385
+ for st in stmts {
2386
+ enumerate_stmt(st, exported, out);
2387
+ }
2388
+ }
2389
+ }
2390
+ Statement::DoWhile { body, cond, .. } => {
2391
+ enumerate_stmt(body, exported, out);
2392
+ enumerate_expr(cond, exported, out);
2393
+ }
2394
+ Statement::Throw { value, .. } => enumerate_expr(value, exported, out),
2395
+ Statement::Try {
2396
+ body,
2397
+ catch_param,
2398
+ catch_param_span,
2399
+ catch_body,
2400
+ finally_body,
2401
+ ..
2402
+ } => {
2403
+ enumerate_stmt(body, exported, out);
2404
+ if let (Some(n), Some(sp), Some(cb)) = (catch_param, catch_param_span, catch_body) {
2405
+ out.push(BindingSite {
2406
+ name: n.clone(),
2407
+ span: *sp,
2408
+ kind: UnusedBindingKind::Variable,
2409
+ exported,
2410
+ });
2411
+ enumerate_stmt(cb, exported, out);
2412
+ }
2413
+ if let Some(fb) = finally_body {
2414
+ enumerate_stmt(fb, exported, out);
2415
+ }
2416
+ }
2417
+ Statement::TypeAlias {
2418
+ name, name_span, ..
2419
+ } => {
2420
+ out.push(BindingSite {
2421
+ name: name.clone(),
2422
+ span: *name_span,
2423
+ kind: UnusedBindingKind::Variable,
2424
+ exported,
2425
+ });
2426
+ }
2427
+ Statement::DeclareVar {
2428
+ name, name_span, ..
2429
+ } => {
2430
+ out.push(BindingSite {
2431
+ name: name.clone(),
2432
+ span: *name_span,
2433
+ kind: UnusedBindingKind::Variable,
2434
+ exported,
2435
+ });
2436
+ }
2437
+ Statement::DeclareFun {
2438
+ name,
2439
+ name_span,
2440
+ params,
2441
+ rest_param,
2442
+ ..
2443
+ } => {
2444
+ out.push(BindingSite {
2445
+ name: name.clone(),
2446
+ span: *name_span,
2447
+ kind: UnusedBindingKind::Variable,
2448
+ exported,
2449
+ });
2450
+ for p in params {
2451
+ enumerate_fun_param(p, exported, out);
2452
+ }
2453
+ if let Some(r) = rest_param {
2454
+ out.push(BindingSite {
2455
+ name: r.name.clone(),
2456
+ span: r.name_span,
2457
+ kind: UnusedBindingKind::Parameter,
2458
+ exported,
2459
+ });
2460
+ }
2461
+ }
2462
+ Statement::Break { .. } | Statement::Continue { .. } => {}
2463
+ }
2464
+ }
2465
+
2466
+ /// Declarations whose values are never read (imports, locals, parameters). Skips `exported` module
2467
+ /// bindings and names starting with `_` (common intentional-unused convention).
2468
+ pub fn collect_unused_bindings(program: &Program, source: &str) -> Vec<UnusedBinding> {
2469
+ let mut sites = Vec::new();
2470
+ for s in &program.statements {
2471
+ enumerate_stmt(s, false, &mut sites);
2472
+ }
2473
+ let mut out = Vec::new();
2474
+ for site in sites {
2475
+ if site.exported || site.name.as_ref().starts_with('_') {
2476
+ continue;
2477
+ }
2478
+ let refs = reference_spans_for_def(program, source, site.name.as_ref(), site.span);
2479
+ if refs.len() == 1 {
2480
+ out.push(UnusedBinding {
2481
+ name: site.name,
2482
+ span: site.span,
2483
+ kind: site.kind,
2484
+ });
2485
+ }
2486
+ }
2487
+ out
2488
+ }
2489
+
2490
+ /// Resolve go-to-definition for cursor position. Returns the defining [`tishlang_ast::Span`].
2491
+ pub fn definition_span(
2492
+ program: &Program,
2493
+ source: &str,
2494
+ lsp_line: u32,
2495
+ lsp_character: u32,
2496
+ ) -> Option<tishlang_ast::Span> {
2497
+ let use_site = name_at_cursor(program, source, lsp_line, lsp_character)?;
2498
+ let mut scopes = ScopeStack::new();
2499
+ for stmt in &program.statements {
2500
+ if let Some(s) = walk_stmt_resolve(stmt, &mut scopes, &use_site) {
2501
+ return Some(s);
2502
+ }
2503
+ }
2504
+ None
2505
+ }
2506
+
2507
+ /// Locals declared one level inside the innermost `Block` that contains the cursor (best-effort completion).
2508
+ pub fn block_locals_containing_cursor(
2509
+ program: &Program,
2510
+ source: &str,
2511
+ lsp_line: u32,
2512
+ lsp_character: u32,
2513
+ ) -> Vec<(Arc<str>, tishlang_ast::Span)> {
2514
+ let mut out = Vec::new();
2515
+ for s in &program.statements {
2516
+ collect_block_locals(s, source, lsp_line, lsp_character, &mut out);
2517
+ }
2518
+ out
2519
+ }
2520
+
2521
+ fn collect_block_locals(
2522
+ stmt: &Statement,
2523
+ source: &str,
2524
+ lsp_line: u32,
2525
+ lsp_char: u32,
2526
+ out: &mut Vec<(Arc<str>, tishlang_ast::Span)>,
2527
+ ) {
2528
+ match stmt {
2529
+ Statement::Block { statements, span } => {
2530
+ if pos::span_contains_lsp_position(source, span, lsp_line, lsp_char) {
2531
+ for st in statements {
2532
+ match st {
2533
+ Statement::VarDecl {
2534
+ name, name_span, ..
2535
+ } => out.push((name.clone(), *name_span)),
2536
+ Statement::FunDecl {
2537
+ name, name_span, ..
2538
+ } => out.push((name.clone(), *name_span)),
2539
+ Statement::TypeAlias {
2540
+ name, name_span, ..
2541
+ } => out.push((name.clone(), *name_span)),
2542
+ Statement::DeclareVar {
2543
+ name, name_span, ..
2544
+ } => out.push((name.clone(), *name_span)),
2545
+ Statement::DeclareFun {
2546
+ name, name_span, ..
2547
+ } => out.push((name.clone(), *name_span)),
2548
+ _ => {}
2549
+ }
2550
+ }
2551
+ }
2552
+ for st in statements {
2553
+ collect_block_locals(st, source, lsp_line, lsp_char, out);
2554
+ }
2555
+ }
2556
+ Statement::FunDecl { body, .. } | Statement::While { body, .. } => {
2557
+ collect_block_locals(body, source, lsp_line, lsp_char, out);
2558
+ }
2559
+ Statement::ForOf {
2560
+ name,
2561
+ name_span,
2562
+ body,
2563
+ span,
2564
+ ..
2565
+ } => {
2566
+ if pos::span_contains_lsp_position(source, span, lsp_line, lsp_char)
2567
+ && pos::span_contains_lsp_position(
2568
+ source,
2569
+ &body.as_ref().span(),
2570
+ lsp_line,
2571
+ lsp_char,
2572
+ )
2573
+ {
2574
+ out.push((name.clone(), *name_span));
2575
+ }
2576
+ collect_block_locals(body, source, lsp_line, lsp_char, out);
2577
+ }
2578
+ Statement::If {
2579
+ then_branch,
2580
+ else_branch,
2581
+ ..
2582
+ } => {
2583
+ collect_block_locals(then_branch, source, lsp_line, lsp_char, out);
2584
+ if let Some(b) = else_branch {
2585
+ collect_block_locals(b, source, lsp_line, lsp_char, out);
2586
+ }
2587
+ }
2588
+ Statement::For { init, body, .. } => {
2589
+ if let Some(i) = init {
2590
+ collect_block_locals(i, source, lsp_line, lsp_char, out);
2591
+ }
2592
+ collect_block_locals(body, source, lsp_line, lsp_char, out);
2593
+ }
2594
+ Statement::DoWhile { body, .. } => {
2595
+ collect_block_locals(body, source, lsp_line, lsp_char, out)
2596
+ }
2597
+ Statement::Try {
2598
+ body,
2599
+ catch_param,
2600
+ catch_param_span,
2601
+ catch_body,
2602
+ finally_body,
2603
+ ..
2604
+ } => {
2605
+ collect_block_locals(body, source, lsp_line, lsp_char, out);
2606
+ if let (Some(n), Some(ps), Some(cb)) = (catch_param, catch_param_span, catch_body) {
2607
+ if pos::span_contains_lsp_position(source, &cb.as_ref().span(), lsp_line, lsp_char)
2608
+ {
2609
+ out.push((n.clone(), *ps));
2610
+ }
2611
+ collect_block_locals(cb, source, lsp_line, lsp_char, out);
2612
+ }
2613
+ if let Some(fb) = finally_body {
2614
+ collect_block_locals(fb, source, lsp_line, lsp_char, out);
2615
+ }
2616
+ }
2617
+ Statement::Switch {
2618
+ cases,
2619
+ default_body,
2620
+ ..
2621
+ } => {
2622
+ for (_ce, stmts) in cases {
2623
+ for st in stmts {
2624
+ collect_block_locals(st, source, lsp_line, lsp_char, out);
2625
+ }
2626
+ }
2627
+ if let Some(stmts) = default_body {
2628
+ for st in stmts {
2629
+ collect_block_locals(st, source, lsp_line, lsp_char, out);
2630
+ }
2631
+ }
2632
+ }
2633
+ Statement::Export { declaration, .. } => {
2634
+ if let ExportDeclaration::Named(inner) = declaration.as_ref() {
2635
+ collect_block_locals(inner, source, lsp_line, lsp_char, out);
2636
+ }
2637
+ }
2638
+ _ => {}
2639
+ }
2640
+ }
2641
+
2642
+ fn param_layer_names(params: &[FunParam], rest_param: &Option<TypedParam>) -> Vec<Arc<str>> {
2643
+ let mut v = Vec::new();
2644
+ for p in params {
2645
+ match p {
2646
+ FunParam::Simple(tp) => v.push(tp.name.clone()),
2647
+ FunParam::Destructure { pattern, .. } => collect_pattern_binding_names(pattern, &mut v),
2648
+ }
2649
+ }
2650
+ if let Some(r) = rest_param {
2651
+ v.push(r.name.clone());
2652
+ }
2653
+ v
2654
+ }
2655
+
2656
+ fn collect_pattern_binding_names(pattern: &DestructPattern, out: &mut Vec<Arc<str>>) {
2657
+ match pattern {
2658
+ DestructPattern::Array(elements) => {
2659
+ for el in elements.iter().flatten() {
2660
+ match el {
2661
+ DestructElement::Ident(n, _) => out.push(n.clone()),
2662
+ DestructElement::Pattern(inner) => {
2663
+ collect_pattern_binding_names(inner, out)
2664
+ }
2665
+ DestructElement::Rest(n, _) => out.push(n.clone()),
2666
+ }
2667
+ }
2668
+ }
2669
+ DestructPattern::Object(props) => {
2670
+ for pr in props {
2671
+ match &pr.value {
2672
+ DestructElement::Ident(n, _) => out.push(n.clone()),
2673
+ DestructElement::Pattern(inner) => collect_pattern_binding_names(inner, out),
2674
+ DestructElement::Rest(n, _) => out.push(n.clone()),
2675
+ }
2676
+ }
2677
+ }
2678
+ }
2679
+ }
2680
+
2681
+ fn record_callable_stack(stack: &[Vec<Arc<str>>], best: &mut Option<(usize, Vec<Arc<str>>)>) {
2682
+ let depth = stack.len();
2683
+ let flat: Vec<Arc<str>> = stack
2684
+ .iter()
2685
+ .rev()
2686
+ .flat_map(|layer| layer.iter().cloned())
2687
+ .collect();
2688
+ match best {
2689
+ None => *best = Some((depth, flat)),
2690
+ Some((bd, _)) if depth > *bd => *best = Some((depth, flat)),
2691
+ _ => {}
2692
+ }
2693
+ }
2694
+
2695
+ fn walk_expr_completion(
2696
+ expr: &Expr,
2697
+ source: &str,
2698
+ lsp_line: u32,
2699
+ lsp_char: u32,
2700
+ stack: &mut Vec<Vec<Arc<str>>>,
2701
+ best: &mut Option<(usize, Vec<Arc<str>>)>,
2702
+ ) {
2703
+ let sp = expr.span();
2704
+ if !pos::span_contains_lsp_position(source, &sp, lsp_line, lsp_char) {
2705
+ return;
2706
+ }
2707
+ match expr {
2708
+ Expr::ArrowFunction { params, body, .. } => {
2709
+ let layer = param_layer_names(params, &None);
2710
+ stack.push(layer);
2711
+ record_callable_stack(stack, best);
2712
+ for p in params {
2713
+ match p {
2714
+ FunParam::Simple(tp) => {
2715
+ if let Some(e) = &tp.default {
2716
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2717
+ }
2718
+ }
2719
+ FunParam::Destructure {
2720
+ default: Some(e), ..
2721
+ } => {
2722
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2723
+ }
2724
+ _ => {}
2725
+ }
2726
+ }
2727
+ match body {
2728
+ ArrowBody::Expr(e) => {
2729
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best)
2730
+ }
2731
+ ArrowBody::Block(b) => {
2732
+ walk_stmt_completion(b, source, lsp_line, lsp_char, stack, best);
2733
+ }
2734
+ }
2735
+ stack.pop();
2736
+ }
2737
+ Expr::Binary { left, right, .. } => {
2738
+ walk_expr_completion(left, source, lsp_line, lsp_char, stack, best);
2739
+ walk_expr_completion(right, source, lsp_line, lsp_char, stack, best);
2740
+ }
2741
+ Expr::Unary { operand, .. } => {
2742
+ walk_expr_completion(operand, source, lsp_line, lsp_char, stack, best)
2743
+ }
2744
+ Expr::Call { callee, args, .. } => {
2745
+ walk_expr_completion(callee, source, lsp_line, lsp_char, stack, best);
2746
+ for a in args {
2747
+ let e = match a {
2748
+ CallArg::Expr(e) => e,
2749
+ CallArg::Spread(e) => e,
2750
+ };
2751
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2752
+ }
2753
+ }
2754
+ Expr::New { callee, args, .. } => {
2755
+ walk_expr_completion(callee, source, lsp_line, lsp_char, stack, best);
2756
+ for a in args {
2757
+ let e = match a {
2758
+ CallArg::Expr(e) => e,
2759
+ CallArg::Spread(e) => e,
2760
+ };
2761
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2762
+ }
2763
+ }
2764
+ Expr::Member { object, .. } => {
2765
+ walk_expr_completion(object, source, lsp_line, lsp_char, stack, best)
2766
+ }
2767
+ Expr::Index { object, index, .. } => {
2768
+ walk_expr_completion(object, source, lsp_line, lsp_char, stack, best);
2769
+ walk_expr_completion(index, source, lsp_line, lsp_char, stack, best);
2770
+ }
2771
+ Expr::Conditional {
2772
+ cond,
2773
+ then_branch,
2774
+ else_branch,
2775
+ ..
2776
+ } => {
2777
+ walk_expr_completion(cond, source, lsp_line, lsp_char, stack, best);
2778
+ walk_expr_completion(then_branch, source, lsp_line, lsp_char, stack, best);
2779
+ walk_expr_completion(else_branch, source, lsp_line, lsp_char, stack, best);
2780
+ }
2781
+ Expr::NullishCoalesce { left, right, .. } => {
2782
+ walk_expr_completion(left, source, lsp_line, lsp_char, stack, best);
2783
+ walk_expr_completion(right, source, lsp_line, lsp_char, stack, best);
2784
+ }
2785
+ Expr::Array { elements, .. } => {
2786
+ for el in elements {
2787
+ let e = match el {
2788
+ tishlang_ast::ArrayElement::Expr(e) => e,
2789
+ tishlang_ast::ArrayElement::Spread(e) => e,
2790
+ };
2791
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2792
+ }
2793
+ }
2794
+ Expr::Object { props, .. } => {
2795
+ for p in props {
2796
+ let e = match p {
2797
+ tishlang_ast::ObjectProp::KeyValue(_, e) => e,
2798
+ tishlang_ast::ObjectProp::Spread(e) => e,
2799
+ };
2800
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2801
+ }
2802
+ }
2803
+ Expr::Assign { value, .. }
2804
+ | Expr::CompoundAssign { value, .. }
2805
+ | Expr::LogicalAssign { value, .. } => {
2806
+ walk_expr_completion(value, source, lsp_line, lsp_char, stack, best);
2807
+ }
2808
+ Expr::TypeOf { operand, .. } => {
2809
+ walk_expr_completion(operand, source, lsp_line, lsp_char, stack, best)
2810
+ }
2811
+ Expr::Delete { target: del, .. } => {
2812
+ walk_expr_completion(del, source, lsp_line, lsp_char, stack, best)
2813
+ }
2814
+ Expr::MemberAssign { object, value, .. } => {
2815
+ walk_expr_completion(object, source, lsp_line, lsp_char, stack, best);
2816
+ walk_expr_completion(value, source, lsp_line, lsp_char, stack, best);
2817
+ }
2818
+ Expr::IndexAssign {
2819
+ object,
2820
+ index,
2821
+ value,
2822
+ ..
2823
+ } => {
2824
+ walk_expr_completion(object, source, lsp_line, lsp_char, stack, best);
2825
+ walk_expr_completion(index, source, lsp_line, lsp_char, stack, best);
2826
+ walk_expr_completion(value, source, lsp_line, lsp_char, stack, best);
2827
+ }
2828
+ Expr::TemplateLiteral { exprs, .. } => {
2829
+ for e in exprs {
2830
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2831
+ }
2832
+ }
2833
+ Expr::Await { operand, .. } => {
2834
+ walk_expr_completion(operand, source, lsp_line, lsp_char, stack, best)
2835
+ }
2836
+ Expr::JsxElement {
2837
+ props, children, ..
2838
+ } => {
2839
+ for p in props {
2840
+ match p {
2841
+ tishlang_ast::JsxProp::Attr { value, .. } => {
2842
+ if let tishlang_ast::JsxAttrValue::Expr(e) = value {
2843
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2844
+ }
2845
+ }
2846
+ tishlang_ast::JsxProp::Spread(e) => {
2847
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2848
+ }
2849
+ }
2850
+ }
2851
+ for ch in children {
2852
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
2853
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2854
+ }
2855
+ }
2856
+ }
2857
+ Expr::JsxFragment { children, .. } => {
2858
+ for ch in children {
2859
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
2860
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2861
+ }
2862
+ }
2863
+ }
2864
+ Expr::Ident { .. }
2865
+ | Expr::Literal { .. }
2866
+ | Expr::PostfixInc { .. }
2867
+ | Expr::PostfixDec { .. }
2868
+ | Expr::PrefixInc { .. }
2869
+ | Expr::PrefixDec { .. }
2870
+ | Expr::NativeModuleLoad { .. } => {}
2871
+ }
2872
+ }
2873
+
2874
+ fn walk_stmt_completion(
2875
+ stmt: &Statement,
2876
+ source: &str,
2877
+ lsp_line: u32,
2878
+ lsp_char: u32,
2879
+ stack: &mut Vec<Vec<Arc<str>>>,
2880
+ best: &mut Option<(usize, Vec<Arc<str>>)>,
2881
+ ) {
2882
+ let st_span = stmt.span();
2883
+ if !pos::span_contains_lsp_position(source, &st_span, lsp_line, lsp_char) {
2884
+ return;
2885
+ }
2886
+ match stmt {
2887
+ Statement::VarDecl { init: Some(e), .. } => {
2888
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2889
+ }
2890
+ Statement::VarDecl { .. } => {}
2891
+ Statement::VarDeclDestructure { init, .. } => {
2892
+ walk_expr_completion(init, source, lsp_line, lsp_char, stack, best);
2893
+ }
2894
+ Statement::ExprStmt { expr, .. } => {
2895
+ walk_expr_completion(expr, source, lsp_line, lsp_char, stack, best);
2896
+ }
2897
+ Statement::If {
2898
+ cond,
2899
+ then_branch,
2900
+ else_branch,
2901
+ ..
2902
+ } => {
2903
+ walk_expr_completion(cond, source, lsp_line, lsp_char, stack, best);
2904
+ walk_stmt_completion(then_branch, source, lsp_line, lsp_char, stack, best);
2905
+ if let Some(b) = else_branch {
2906
+ walk_stmt_completion(b, source, lsp_line, lsp_char, stack, best);
2907
+ }
2908
+ }
2909
+ Statement::While { cond, body, .. } => {
2910
+ walk_expr_completion(cond, source, lsp_line, lsp_char, stack, best);
2911
+ walk_stmt_completion(body, source, lsp_line, lsp_char, stack, best);
2912
+ }
2913
+ Statement::For {
2914
+ init,
2915
+ cond,
2916
+ update,
2917
+ body,
2918
+ ..
2919
+ } => {
2920
+ if let Some(i) = init {
2921
+ walk_stmt_completion(i, source, lsp_line, lsp_char, stack, best);
2922
+ }
2923
+ if let Some(e) = cond {
2924
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2925
+ }
2926
+ if let Some(e) = update {
2927
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2928
+ }
2929
+ walk_stmt_completion(body, source, lsp_line, lsp_char, stack, best);
2930
+ }
2931
+ Statement::ForOf {
2932
+ name,
2933
+ iterable,
2934
+ body,
2935
+ ..
2936
+ } => {
2937
+ walk_expr_completion(iterable, source, lsp_line, lsp_char, stack, best);
2938
+ let body_sp = body.as_ref().span();
2939
+ if pos::span_contains_lsp_position(source, &body_sp, lsp_line, lsp_char) {
2940
+ stack.push(vec![name.clone()]);
2941
+ record_callable_stack(stack, best);
2942
+ walk_stmt_completion(body, source, lsp_line, lsp_char, stack, best);
2943
+ stack.pop();
2944
+ }
2945
+ }
2946
+ Statement::Return { value: Some(e), .. } => {
2947
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2948
+ }
2949
+ Statement::Return { .. } => {}
2950
+ Statement::Block { statements, .. } => {
2951
+ for s in statements {
2952
+ walk_stmt_completion(s, source, lsp_line, lsp_char, stack, best);
2953
+ }
2954
+ }
2955
+ Statement::Multi { statements, .. } => {
2956
+ for s in statements {
2957
+ walk_stmt_completion(s, source, lsp_line, lsp_char, stack, best);
2958
+ }
2959
+ }
2960
+ Statement::FunDecl {
2961
+ params,
2962
+ rest_param,
2963
+ body,
2964
+ ..
2965
+ } => {
2966
+ let layer = param_layer_names(params, rest_param);
2967
+ stack.push(layer);
2968
+ record_callable_stack(stack, best);
2969
+ for p in params {
2970
+ match p {
2971
+ FunParam::Simple(tp) => {
2972
+ if let Some(e) = &tp.default {
2973
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2974
+ }
2975
+ }
2976
+ FunParam::Destructure {
2977
+ default: Some(e), ..
2978
+ } => {
2979
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
2980
+ }
2981
+ _ => {}
2982
+ }
2983
+ }
2984
+ walk_stmt_completion(body, source, lsp_line, lsp_char, stack, best);
2985
+ stack.pop();
2986
+ }
2987
+ Statement::Switch {
2988
+ expr,
2989
+ cases,
2990
+ default_body,
2991
+ ..
2992
+ } => {
2993
+ walk_expr_completion(expr, source, lsp_line, lsp_char, stack, best);
2994
+ for (_ce, stmts) in cases {
2995
+ for s in stmts {
2996
+ walk_stmt_completion(s, source, lsp_line, lsp_char, stack, best);
2997
+ }
2998
+ }
2999
+ if let Some(stmts) = default_body {
3000
+ for s in stmts {
3001
+ walk_stmt_completion(s, source, lsp_line, lsp_char, stack, best);
3002
+ }
3003
+ }
3004
+ }
3005
+ Statement::DoWhile { body, cond, .. } => {
3006
+ walk_stmt_completion(body, source, lsp_line, lsp_char, stack, best);
3007
+ walk_expr_completion(cond, source, lsp_line, lsp_char, stack, best);
3008
+ }
3009
+ Statement::Throw { value, .. } => {
3010
+ walk_expr_completion(value, source, lsp_line, lsp_char, stack, best);
3011
+ }
3012
+ Statement::Try {
3013
+ body,
3014
+ catch_param,
3015
+ catch_body,
3016
+ finally_body,
3017
+ ..
3018
+ } => {
3019
+ walk_stmt_completion(body, source, lsp_line, lsp_char, stack, best);
3020
+ if let (Some(n), Some(cb)) = (catch_param, catch_body) {
3021
+ let csp = cb.as_ref().span();
3022
+ if pos::span_contains_lsp_position(source, &csp, lsp_line, lsp_char) {
3023
+ stack.push(vec![n.clone()]);
3024
+ record_callable_stack(stack, best);
3025
+ walk_stmt_completion(cb, source, lsp_line, lsp_char, stack, best);
3026
+ stack.pop();
3027
+ }
3028
+ }
3029
+ if let Some(fb) = finally_body {
3030
+ walk_stmt_completion(fb, source, lsp_line, lsp_char, stack, best);
3031
+ }
3032
+ }
3033
+ Statement::Import { .. }
3034
+ | Statement::Break { .. }
3035
+ | Statement::Continue { .. }
3036
+ | Statement::TypeAlias { .. }
3037
+ | Statement::DeclareVar { .. }
3038
+ | Statement::DeclareFun { .. } => {}
3039
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
3040
+ ExportDeclaration::Named(inner) => {
3041
+ walk_stmt_completion(inner, source, lsp_line, lsp_char, stack, best);
3042
+ }
3043
+ ExportDeclaration::Default(e) => {
3044
+ walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
3045
+ }
3046
+ },
3047
+ }
3048
+ }
3049
+
3050
+ /// Callable parameter names visible at the cursor (innermost layer first in the returned `Vec`).
3051
+ pub fn callable_param_names_at_cursor(
3052
+ program: &Program,
3053
+ source: &str,
3054
+ lsp_line: u32,
3055
+ lsp_char: u32,
3056
+ ) -> Vec<Arc<str>> {
3057
+ let mut best: Option<(usize, Vec<Arc<str>>)> = None;
3058
+ let mut stack: Vec<Vec<Arc<str>>> = Vec::new();
3059
+ for s in &program.statements {
3060
+ walk_stmt_completion(s, source, lsp_line, lsp_char, &mut stack, &mut best);
3061
+ }
3062
+ best.map(|(_, n)| n).unwrap_or_default()
3063
+ }
3064
+
3065
+ /// Value names for completion: inner callable parameters, then block-local `let`/`fn`, then module bindings.
3066
+ pub fn completion_value_names_at_cursor(
3067
+ program: &Program,
3068
+ source: &str,
3069
+ lsp_line: u32,
3070
+ lsp_char: u32,
3071
+ ) -> Vec<Arc<str>> {
3072
+ use std::collections::HashSet;
3073
+ let mut seen = HashSet::<String>::new();
3074
+ let mut out = Vec::new();
3075
+ let push = |n: Arc<str>, out: &mut Vec<Arc<str>>, seen: &mut HashSet<String>| {
3076
+ if seen.insert(n.to_string()) {
3077
+ out.push(n);
3078
+ }
3079
+ };
3080
+ for n in callable_param_names_at_cursor(program, source, lsp_line, lsp_char) {
3081
+ push(n, &mut out, &mut seen);
3082
+ }
3083
+ for (n, _) in block_locals_containing_cursor(program, source, lsp_line, lsp_char) {
3084
+ push(n, &mut out, &mut seen);
3085
+ }
3086
+ for (n, _) in shallow_module_bindings(program) {
3087
+ push(n, &mut out, &mut seen);
3088
+ }
3089
+ out
3090
+ }
3091
+
3092
+ /// Top-level value bindings (completion baseline). Inner scopes handled in LSP via [`definition_span`].
3093
+ pub fn shallow_module_bindings(program: &Program) -> Vec<(Arc<str>, tishlang_ast::Span)> {
3094
+ let mut out = Vec::new();
3095
+ for s in &program.statements {
3096
+ match s {
3097
+ Statement::VarDecl {
3098
+ name, name_span, ..
3099
+ } => out.push((name.clone(), *name_span)),
3100
+ Statement::FunDecl {
3101
+ name, name_span, ..
3102
+ } => out.push((name.clone(), *name_span)),
3103
+ Statement::Import { specifiers, .. } => {
3104
+ for sp in specifiers {
3105
+ match sp {
3106
+ ImportSpecifier::Named {
3107
+ name,
3108
+ name_span,
3109
+ alias,
3110
+ alias_span,
3111
+ } => {
3112
+ let local = alias
3113
+ .as_ref()
3114
+ .map(|a| a.clone())
3115
+ .unwrap_or_else(|| name.clone());
3116
+ let spn = alias_span.as_ref().unwrap_or(name_span);
3117
+ out.push((local, *spn));
3118
+ }
3119
+ ImportSpecifier::Namespace { name, name_span } => {
3120
+ out.push((name.clone(), *name_span));
3121
+ }
3122
+ ImportSpecifier::Default { name, name_span } => {
3123
+ out.push((name.clone(), *name_span));
3124
+ }
3125
+ }
3126
+ }
3127
+ }
3128
+ Statement::Export { declaration, .. } => {
3129
+ if let ExportDeclaration::Named(inner) = declaration.as_ref() {
3130
+ match inner.as_ref() {
3131
+ Statement::VarDecl {
3132
+ name, name_span, ..
3133
+ } => out.push((name.clone(), *name_span)),
3134
+ Statement::FunDecl {
3135
+ name, name_span, ..
3136
+ } => out.push((name.clone(), *name_span)),
3137
+ _ => {}
3138
+ }
3139
+ }
3140
+ }
3141
+ _ => {}
3142
+ }
3143
+ }
3144
+ out
3145
+ }
3146
+
3147
+ /// All spans (definition + uses) that resolve to `def_span` for `name`.
3148
+ pub fn reference_spans_for_def(
3149
+ program: &Program,
3150
+ source: &str,
3151
+ name: &str,
3152
+ def_span: tishlang_ast::Span,
3153
+ ) -> Vec<tishlang_ast::Span> {
3154
+ let mut out = vec![def_span];
3155
+ for s in &program.statements {
3156
+ refs_stmt(s, program, source, name, def_span, &mut out);
3157
+ }
3158
+ out.sort_by_key(|s| (s.start.0, s.start.1, s.end.0, s.end.1));
3159
+ out.dedup_by(|a, b| a == b);
3160
+ out
3161
+ }
3162
+
3163
+ fn refs_stmt(
3164
+ stmt: &Statement,
3165
+ program: &Program,
3166
+ source: &str,
3167
+ name: &str,
3168
+ def_span: tishlang_ast::Span,
3169
+ out: &mut Vec<tishlang_ast::Span>,
3170
+ ) {
3171
+ match stmt {
3172
+ Statement::VarDecl { init, .. } => {
3173
+ if let Some(e) = init {
3174
+ refs_expr(e, program, source, name, def_span, out);
3175
+ }
3176
+ }
3177
+ Statement::VarDeclDestructure { init, .. } => {
3178
+ refs_expr(init, program, source, name, def_span, out)
3179
+ }
3180
+ Statement::ExprStmt { expr, .. } => refs_expr(expr, program, source, name, def_span, out),
3181
+ Statement::If {
3182
+ cond,
3183
+ then_branch,
3184
+ else_branch,
3185
+ ..
3186
+ } => {
3187
+ refs_expr(cond, program, source, name, def_span, out);
3188
+ refs_stmt(then_branch, program, source, name, def_span, out);
3189
+ if let Some(b) = else_branch {
3190
+ refs_stmt(b, program, source, name, def_span, out);
3191
+ }
3192
+ }
3193
+ Statement::While { cond, body, .. } => {
3194
+ refs_expr(cond, program, source, name, def_span, out);
3195
+ refs_stmt(body, program, source, name, def_span, out);
3196
+ }
3197
+ Statement::For {
3198
+ init,
3199
+ cond,
3200
+ update,
3201
+ body,
3202
+ ..
3203
+ } => {
3204
+ if let Some(i) = init {
3205
+ refs_stmt(i, program, source, name, def_span, out);
3206
+ }
3207
+ if let Some(e) = cond {
3208
+ refs_expr(e, program, source, name, def_span, out);
3209
+ }
3210
+ if let Some(e) = update {
3211
+ refs_expr(e, program, source, name, def_span, out);
3212
+ }
3213
+ refs_stmt(body, program, source, name, def_span, out);
3214
+ }
3215
+ Statement::ForOf { iterable, body, .. } => {
3216
+ refs_expr(iterable, program, source, name, def_span, out);
3217
+ refs_stmt(body, program, source, name, def_span, out);
3218
+ }
3219
+ Statement::Return { value, .. } => {
3220
+ if let Some(e) = value {
3221
+ refs_expr(e, program, source, name, def_span, out);
3222
+ }
3223
+ }
3224
+ Statement::Block { statements, .. } => {
3225
+ for s in statements {
3226
+ refs_stmt(s, program, source, name, def_span, out);
3227
+ }
3228
+ }
3229
+ Statement::Multi { statements, .. } => {
3230
+ for s in statements {
3231
+ refs_stmt(s, program, source, name, def_span, out);
3232
+ }
3233
+ }
3234
+ Statement::FunDecl { params, body, .. } => {
3235
+ for p in params {
3236
+ if let FunParam::Simple(tp) = p {
3237
+ if let Some(e) = &tp.default {
3238
+ refs_expr(e, program, source, name, def_span, out);
3239
+ }
3240
+ } else if let FunParam::Destructure {
3241
+ default: Some(e), ..
3242
+ } = p
3243
+ {
3244
+ refs_expr(e, program, source, name, def_span, out);
3245
+ }
3246
+ }
3247
+ refs_stmt(body, program, source, name, def_span, out);
3248
+ }
3249
+ Statement::Switch {
3250
+ expr,
3251
+ cases,
3252
+ default_body,
3253
+ ..
3254
+ } => {
3255
+ refs_expr(expr, program, source, name, def_span, out);
3256
+ for (_ce, stmts) in cases {
3257
+ for s in stmts {
3258
+ refs_stmt(s, program, source, name, def_span, out);
3259
+ }
3260
+ }
3261
+ if let Some(stmts) = default_body {
3262
+ for s in stmts {
3263
+ refs_stmt(s, program, source, name, def_span, out);
3264
+ }
3265
+ }
3266
+ }
3267
+ Statement::DoWhile { body, cond, .. } => {
3268
+ refs_stmt(body, program, source, name, def_span, out);
3269
+ refs_expr(cond, program, source, name, def_span, out);
3270
+ }
3271
+ Statement::Throw { value, .. } => refs_expr(value, program, source, name, def_span, out),
3272
+ Statement::Try {
3273
+ body,
3274
+ catch_body,
3275
+ finally_body,
3276
+ ..
3277
+ } => {
3278
+ refs_stmt(body, program, source, name, def_span, out);
3279
+ if let Some(cb) = catch_body {
3280
+ refs_stmt(cb, program, source, name, def_span, out);
3281
+ }
3282
+ if let Some(fb) = finally_body {
3283
+ refs_stmt(fb, program, source, name, def_span, out);
3284
+ }
3285
+ }
3286
+ Statement::Import { .. }
3287
+ | Statement::Export { .. }
3288
+ | Statement::Break { .. }
3289
+ | Statement::Continue { .. }
3290
+ | Statement::TypeAlias { .. }
3291
+ | Statement::DeclareVar { .. }
3292
+ | Statement::DeclareFun { .. } => {}
3293
+ }
3294
+ }
3295
+
3296
+ fn refs_expr(
3297
+ expr: &Expr,
3298
+ program: &Program,
3299
+ source: &str,
3300
+ name: &str,
3301
+ def_span: tishlang_ast::Span,
3302
+ out: &mut Vec<tishlang_ast::Span>,
3303
+ ) {
3304
+ let maybe_push =
3305
+ |span: &tishlang_ast::Span, n: &Arc<str>, out: &mut Vec<tishlang_ast::Span>| {
3306
+ if n.as_ref() != name {
3307
+ return;
3308
+ }
3309
+ let Some((l, c)) = pos::lsp_position_for_span_start(source, span) else {
3310
+ return;
3311
+ };
3312
+ if definition_span(program, source, l, c) == Some(def_span) {
3313
+ out.push(*span);
3314
+ }
3315
+ };
3316
+
3317
+ match expr {
3318
+ Expr::Ident { name: n, span } => maybe_push(span, n, out),
3319
+ Expr::Binary { left, right, .. } => {
3320
+ refs_expr(left, program, source, name, def_span, out);
3321
+ refs_expr(right, program, source, name, def_span, out);
3322
+ }
3323
+ Expr::Unary { operand, .. } => refs_expr(operand, program, source, name, def_span, out),
3324
+ Expr::Call { callee, args, .. } => {
3325
+ refs_expr(callee, program, source, name, def_span, out);
3326
+ for a in args {
3327
+ let e = match a {
3328
+ CallArg::Expr(e) => e,
3329
+ CallArg::Spread(e) => e,
3330
+ };
3331
+ refs_expr(e, program, source, name, def_span, out);
3332
+ }
3333
+ }
3334
+ Expr::New { callee, args, .. } => {
3335
+ refs_expr(callee, program, source, name, def_span, out);
3336
+ for a in args {
3337
+ let e = match a {
3338
+ CallArg::Expr(e) => e,
3339
+ CallArg::Spread(e) => e,
3340
+ };
3341
+ refs_expr(e, program, source, name, def_span, out);
3342
+ }
3343
+ }
3344
+ Expr::Member { object, .. } => refs_expr(object, program, source, name, def_span, out),
3345
+ Expr::Index { object, index, .. } => {
3346
+ refs_expr(object, program, source, name, def_span, out);
3347
+ refs_expr(index, program, source, name, def_span, out);
3348
+ }
3349
+ Expr::Conditional {
3350
+ cond,
3351
+ then_branch,
3352
+ else_branch,
3353
+ ..
3354
+ } => {
3355
+ refs_expr(cond, program, source, name, def_span, out);
3356
+ refs_expr(then_branch, program, source, name, def_span, out);
3357
+ refs_expr(else_branch, program, source, name, def_span, out);
3358
+ }
3359
+ Expr::NullishCoalesce { left, right, .. } => {
3360
+ refs_expr(left, program, source, name, def_span, out);
3361
+ refs_expr(right, program, source, name, def_span, out);
3362
+ }
3363
+ Expr::Array { elements, .. } => {
3364
+ for el in elements {
3365
+ match el {
3366
+ tishlang_ast::ArrayElement::Expr(e) => {
3367
+ refs_expr(e, program, source, name, def_span, out)
3368
+ }
3369
+ tishlang_ast::ArrayElement::Spread(e) => {
3370
+ refs_expr(e, program, source, name, def_span, out)
3371
+ }
3372
+ }
3373
+ }
3374
+ }
3375
+ Expr::Object { props, .. } => {
3376
+ for p in props {
3377
+ match p {
3378
+ tishlang_ast::ObjectProp::KeyValue(_, e) => {
3379
+ refs_expr(e, program, source, name, def_span, out)
3380
+ }
3381
+ tishlang_ast::ObjectProp::Spread(e) => {
3382
+ refs_expr(e, program, source, name, def_span, out)
3383
+ }
3384
+ }
3385
+ }
3386
+ }
3387
+ Expr::Assign {
3388
+ name: n,
3389
+ span,
3390
+ value,
3391
+ } => {
3392
+ let sp = synthetic_name_span(span.start, n.as_ref());
3393
+ maybe_push(&sp, n, out);
3394
+ refs_expr(value, program, source, name, def_span, out);
3395
+ }
3396
+ Expr::TypeOf { operand, .. } => refs_expr(operand, program, source, name, def_span, out),
3397
+ Expr::Delete { target: del, .. } => refs_expr(del, program, source, name, def_span, out),
3398
+ Expr::PostfixInc { name: n, span } | Expr::PostfixDec { name: n, span } => {
3399
+ let sp = synthetic_name_span(span.start, n.as_ref());
3400
+ maybe_push(&sp, n, out);
3401
+ }
3402
+ Expr::PrefixInc { name: n, span } | Expr::PrefixDec { name: n, span } => {
3403
+ let sp = synthetic_name_span(span.start, n.as_ref());
3404
+ maybe_push(&sp, n, out);
3405
+ }
3406
+ Expr::CompoundAssign {
3407
+ name: n,
3408
+ span,
3409
+ value,
3410
+ ..
3411
+ }
3412
+ | Expr::LogicalAssign {
3413
+ name: n,
3414
+ span,
3415
+ value,
3416
+ ..
3417
+ } => {
3418
+ let sp = synthetic_name_span(span.start, n.as_ref());
3419
+ maybe_push(&sp, n, out);
3420
+ refs_expr(value, program, source, name, def_span, out);
3421
+ }
3422
+ Expr::MemberAssign { object, value, .. } => {
3423
+ refs_expr(object, program, source, name, def_span, out);
3424
+ refs_expr(value, program, source, name, def_span, out);
3425
+ }
3426
+ Expr::IndexAssign {
3427
+ object,
3428
+ index,
3429
+ value,
3430
+ ..
3431
+ } => {
3432
+ refs_expr(object, program, source, name, def_span, out);
3433
+ refs_expr(index, program, source, name, def_span, out);
3434
+ refs_expr(value, program, source, name, def_span, out);
3435
+ }
3436
+ Expr::ArrowFunction { params, body, .. } => {
3437
+ for p in params {
3438
+ if let FunParam::Simple(tp) = p {
3439
+ if let Some(e) = &tp.default {
3440
+ refs_expr(e, program, source, name, def_span, out);
3441
+ }
3442
+ } else if let FunParam::Destructure {
3443
+ default: Some(e), ..
3444
+ } = p
3445
+ {
3446
+ refs_expr(e, program, source, name, def_span, out);
3447
+ }
3448
+ }
3449
+ match body {
3450
+ ArrowBody::Expr(e) => refs_expr(e, program, source, name, def_span, out),
3451
+ ArrowBody::Block(b) => refs_stmt(b, program, source, name, def_span, out),
3452
+ }
3453
+ }
3454
+ Expr::TemplateLiteral { exprs, .. } => {
3455
+ for e in exprs {
3456
+ refs_expr(e, program, source, name, def_span, out);
3457
+ }
3458
+ }
3459
+ Expr::Await { operand, .. } => refs_expr(operand, program, source, name, def_span, out),
3460
+ Expr::JsxElement {
3461
+ props, children, ..
3462
+ } => {
3463
+ for p in props {
3464
+ match p {
3465
+ tishlang_ast::JsxProp::Attr { value, .. } => {
3466
+ if let tishlang_ast::JsxAttrValue::Expr(e) = value {
3467
+ refs_expr(e, program, source, name, def_span, out);
3468
+ }
3469
+ }
3470
+ tishlang_ast::JsxProp::Spread(e) => {
3471
+ refs_expr(e, program, source, name, def_span, out)
3472
+ }
3473
+ }
3474
+ }
3475
+ for ch in children {
3476
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
3477
+ refs_expr(e, program, source, name, def_span, out);
3478
+ }
3479
+ }
3480
+ }
3481
+ Expr::JsxFragment { children, .. } => {
3482
+ for ch in children {
3483
+ if let tishlang_ast::JsxChild::Expr(e) = ch {
3484
+ refs_expr(e, program, source, name, def_span, out);
3485
+ }
3486
+ }
3487
+ }
3488
+ Expr::Literal { .. } | Expr::NativeModuleLoad { .. } => {}
3489
+ }
3490
+ }
3491
+
3492
+ #[cfg(test)]
3493
+ mod tests {
3494
+ use super::*;
3495
+ use tishlang_parser::parse;
3496
+
3497
+ #[test]
3498
+ fn resolves_second_line_reference() {
3499
+ let src = "let a = 1\nlet b = a\n";
3500
+ let program = parse(src).expect("parse");
3501
+ let ds = definition_span(&program, src, 1, 8).expect("def");
3502
+ assert_eq!(ds.start.0, 1);
3503
+ }
3504
+
3505
+ #[test]
3506
+ fn inner_scope_shadow() {
3507
+ let src = "let x = 1\n{\n let x = 2\n x\n}\n";
3508
+ let program = parse(src).expect("parse");
3509
+ let inner = definition_span(&program, src, 3, 2).expect("inner x def");
3510
+ assert_eq!(inner.start.0, 3);
3511
+ }
3512
+
3513
+ #[test]
3514
+ fn completion_includes_fn_params() {
3515
+ let src = "fn f(foo, bar) {\n bar\n}\n";
3516
+ let program = parse(src).expect("parse");
3517
+ let names = completion_value_names_at_cursor(&program, src, 1, 2);
3518
+ assert!(names.iter().any(|n| n.as_ref() == "foo"), "names={names:?}");
3519
+ assert!(names.iter().any(|n| n.as_ref() == "bar"));
3520
+ }
3521
+
3522
+ #[test]
3523
+ fn unresolved_unknown_ident() {
3524
+ let src = "let x = nope\n";
3525
+ let program = parse(src).expect("parse");
3526
+ let u = collect_unresolved_identifiers(&program);
3527
+ assert_eq!(u.len(), 1);
3528
+ assert_eq!(u[0].name.as_ref(), "nope");
3529
+ }
3530
+
3531
+ #[test]
3532
+ fn console_global_not_unresolved() {
3533
+ let src = "console.log(1)\n";
3534
+ let program = parse(src).expect("parse");
3535
+ let u = collect_unresolved_identifiers(&program);
3536
+ assert!(u.is_empty(), "u={u:?}");
3537
+ }
3538
+
3539
+ #[test]
3540
+ fn resolved_ident_not_unresolved() {
3541
+ let src = "let x = 1\nlet y = x\n";
3542
+ let program = parse(src).expect("parse");
3543
+ let u = collect_unresolved_identifiers(&program);
3544
+ assert!(u.is_empty(), "u={u:?}");
3545
+ }
3546
+
3547
+ #[test]
3548
+ fn unused_import_named() {
3549
+ let src = "import { a, b } from \"./m\"\nlet x = a\nx\n";
3550
+ let program = parse(src).expect("parse");
3551
+ let u = collect_unused_bindings(&program, src);
3552
+ assert_eq!(u.len(), 1);
3553
+ assert_eq!(u[0].name.as_ref(), "b");
3554
+ assert_eq!(u[0].kind, UnusedBindingKind::Import);
3555
+ }
3556
+
3557
+ #[test]
3558
+ fn unused_let_underscore_ignored() {
3559
+ let src = "let _x = 1\n";
3560
+ let program = parse(src).expect("parse");
3561
+ let u = collect_unused_bindings(&program, src);
3562
+ assert!(u.is_empty(), "u={u:?}");
3563
+ }
3564
+
3565
+ #[test]
3566
+ fn unused_local_let() {
3567
+ let src = "let dead = 1\n";
3568
+ let program = parse(src).expect("parse");
3569
+ let u = collect_unused_bindings(&program, src);
3570
+ assert_eq!(u.len(), 1);
3571
+ assert_eq!(u[0].name.as_ref(), "dead");
3572
+ assert_eq!(u[0].kind, UnusedBindingKind::Variable);
3573
+ }
3574
+
3575
+ #[test]
3576
+ fn export_not_reported_unused() {
3577
+ let src = "export let x = 1\n";
3578
+ let program = parse(src).expect("parse");
3579
+ let u = collect_unused_bindings(&program, src);
3580
+ assert!(u.is_empty(), "u={u:?}");
3581
+ }
3582
+
3583
+ #[test]
3584
+ fn member_access_chain_three_deep() {
3585
+ let src = "let x = a.b.c\n";
3586
+ let program = parse(src).expect("parse");
3587
+ let ch = member_access_chain_at_cursor(&program, src, 0, 12).expect("chain");
3588
+ assert_eq!(ch.root_local.as_ref(), "a");
3589
+ assert_eq!(ch.members.len(), 2);
3590
+ assert_eq!(ch.members[0].as_ref(), "b");
3591
+ assert_eq!(ch.members[1].as_ref(), "c");
3592
+ }
3593
+
3594
+ #[test]
3595
+ fn name_at_cursor_prefers_member_prop() {
3596
+ let src = "let x = a.b.c\n";
3597
+ let program = parse(src).expect("parse");
3598
+ let nu = name_at_cursor(&program, src, 0, 12).expect("name use");
3599
+ assert_eq!(nu.name.as_ref(), "c");
3600
+ }
3601
+
3602
+ #[test]
3603
+ fn member_access_chain_across_lines() {
3604
+ let src = "fn main()\n let x = a.b\n .c\n";
3605
+ let program = parse(src).expect("parse");
3606
+ let ch = member_access_chain_at_cursor(&program, src, 2, 5).expect("chain");
3607
+ assert_eq!(ch.root_local.as_ref(), "a");
3608
+ assert_eq!(ch.members.len(), 2);
3609
+ assert_eq!(ch.members[0].as_ref(), "b");
3610
+ assert_eq!(ch.members[1].as_ref(), "c");
3611
+ }
3612
+ }