@tishlang/tish-format 1.0.12 → 1.0.13

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