@tishlang/tish-format 1.0.12 → 2.0.1

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