@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,562 @@
1
+ //! Go to definition for import specifiers: relative paths, bare `node_modules` packages, and
2
+ //! native `tish:` / `@scope/pkg` / `cargo:` → Rust `pub fn` sites (via `syn` + optional `cargo metadata`).
3
+
4
+ use std::collections::HashMap;
5
+ use std::path::{Path, PathBuf};
6
+ use std::sync::{Arc, RwLock};
7
+
8
+ use regex::Regex;
9
+ use tower_lsp::lsp_types::{Location, Position, Range, Url};
10
+
11
+ use tishlang_ast::{ImportSpecifier, Program, Statement};
12
+ use tishlang_compile::{
13
+ export_name_to_rust_ident, is_builtin_native_spec, is_cargo_native_spec, is_native_import,
14
+ normalize_builtin_spec, read_project_tish_config, resolve_bare_spec, resolve_native_modules,
15
+ };
16
+ use tishlang_resolve::member_access_chain_at_cursor;
17
+
18
+ /// Pick a workspace / project root for resolving `package.json` and `node_modules`.
19
+ fn infer_project_root(file_path: &Path, roots: &[PathBuf]) -> Option<PathBuf> {
20
+ if let Ok(can) = file_path.canonicalize() {
21
+ for r in roots {
22
+ if let Ok(rc) = r.canonicalize() {
23
+ if can.starts_with(&rc) {
24
+ return Some(rc);
25
+ }
26
+ }
27
+ }
28
+ }
29
+ let mut dir = file_path.parent()?.to_path_buf();
30
+ loop {
31
+ if dir.join("package.json").exists() || dir.join("Cargo.toml").exists() {
32
+ return dir.canonicalize().ok().or(Some(dir));
33
+ }
34
+ dir = dir.parent()?.to_path_buf();
35
+ }
36
+ }
37
+
38
+ fn location_from_rust_path(path: &Path, line: u32, col: u32) -> Option<Location> {
39
+ let uri = Url::from_file_path(path).ok()?;
40
+ let line_str = std::fs::read_to_string(path).ok()?;
41
+ let line_slice = line_str.lines().nth(line as usize).unwrap_or("");
42
+ let end_char = line_slice.len() as u32;
43
+ Some(Location {
44
+ uri,
45
+ range: Range {
46
+ start: Position {
47
+ line,
48
+ character: col,
49
+ },
50
+ end: Position {
51
+ line,
52
+ character: end_char.max(col.saturating_add(1)),
53
+ },
54
+ },
55
+ })
56
+ }
57
+
58
+ fn resolve_cargo_dep_crate_root(
59
+ project_root: &Path,
60
+ dep_key: &str,
61
+ raw: &serde_json::Value,
62
+ ) -> Result<PathBuf, String> {
63
+ match raw {
64
+ serde_json::Value::String(ver) => {
65
+ let r = tishlang_cargo_bindgen::resolve_registry_dependency(dep_key, ver)?;
66
+ Ok(r.source_root())
67
+ }
68
+ serde_json::Value::Object(map) => {
69
+ let path_str = map.get("path").and_then(|v| v.as_str()).ok_or_else(|| {
70
+ "cargo:… rustDependencies entry must be a version string or an object with \"path\""
71
+ .to_string()
72
+ })?;
73
+ let glue = Path::new(path_str);
74
+ let glue = if glue.is_absolute() {
75
+ glue.to_path_buf()
76
+ } else {
77
+ project_root.join(path_str)
78
+ };
79
+ let glue = glue.canonicalize().unwrap_or(glue);
80
+ let glue_cargo = glue.join("Cargo.toml");
81
+ if glue_cargo.is_file() {
82
+ match tishlang_cargo_bindgen::resolve_dependency_from_manifest(&glue_cargo, dep_key)
83
+ {
84
+ Ok(r) => Ok(r.source_root()),
85
+ Err(_) if glue.join("src").is_dir() => Ok(glue),
86
+ Err(e) => Err(e),
87
+ }
88
+ } else if glue.join("src").is_dir() {
89
+ Ok(glue)
90
+ } else {
91
+ Err(format!(
92
+ "path dependency for {} does not look like a Rust crate: {}",
93
+ dep_key,
94
+ glue.display()
95
+ ))
96
+ }
97
+ }
98
+ _ => Err(format!(
99
+ "tish.rustDependencies.{} must be a string (semver) or object with path",
100
+ dep_key
101
+ )),
102
+ }
103
+ }
104
+
105
+ fn cargo_crate_root_cached(
106
+ project_root: &Path,
107
+ spec: &str,
108
+ dep_key: &str,
109
+ raw: &serde_json::Value,
110
+ cache: &RwLock<HashMap<(PathBuf, String), PathBuf>>,
111
+ ) -> Result<PathBuf, String> {
112
+ let key = (project_root.to_path_buf(), spec.to_string());
113
+ if let Ok(g) = cache.read() {
114
+ if let Some(p) = g.get(&key) {
115
+ return Ok(p.clone());
116
+ }
117
+ }
118
+ let root = resolve_cargo_dep_crate_root(project_root, dep_key, raw)?;
119
+ if let Ok(mut g) = cache.write() {
120
+ g.insert(key, root.clone());
121
+ }
122
+ Ok(root)
123
+ }
124
+
125
+ fn rust_def_for_crate_root(crate_root: &Path, tish_export: &str) -> Option<Location> {
126
+ let snake = export_name_to_rust_ident(tish_export);
127
+ let try_names: [&str; 2] = [&snake, tish_export];
128
+ for name in try_names {
129
+ if name.is_empty() {
130
+ continue;
131
+ }
132
+ if let Ok((path, line, col)) =
133
+ tishlang_cargo_bindgen::rust_public_fn_location(crate_root, name)
134
+ {
135
+ return location_from_rust_path(&path, line, col);
136
+ }
137
+ }
138
+ None
139
+ }
140
+
141
+ fn rust_fn_location_exact(crate_root: &Path, rust_fn_ident: &str) -> Option<Location> {
142
+ if rust_fn_ident.is_empty() {
143
+ return None;
144
+ }
145
+ let (path, line, col) =
146
+ tishlang_cargo_bindgen::rust_public_fn_location(crate_root, rust_fn_ident).ok()?;
147
+ location_from_rust_path(&path, line, col)
148
+ }
149
+
150
+ fn is_ident_char_member(c: char) -> bool {
151
+ c.is_alphanumeric() || c == '_'
152
+ }
153
+
154
+ /// When the cursor is on `member` in `receiver.member` on a single line, returns `(receiver, member)`.
155
+ fn split_receiver_member(line: &str, col: usize) -> Option<(String, String)> {
156
+ let chars: Vec<char> = line.chars().collect();
157
+ if chars.is_empty() {
158
+ return None;
159
+ }
160
+ let col = col.min(chars.len().saturating_sub(1));
161
+ if !is_ident_char_member(chars[col]) {
162
+ return None;
163
+ }
164
+ let mut end = col;
165
+ while end + 1 < chars.len() && is_ident_char_member(chars[end + 1]) {
166
+ end += 1;
167
+ }
168
+ let mut start = col;
169
+ while start > 0 && is_ident_char_member(chars[start - 1]) {
170
+ start -= 1;
171
+ }
172
+ if start == 0 {
173
+ return None;
174
+ }
175
+ if chars[start - 1] != '.' {
176
+ return None;
177
+ }
178
+ let member: String = chars[start..=end].iter().collect();
179
+ let mut k = start - 2;
180
+ while k > 0 && is_ident_char_member(chars[k - 1]) {
181
+ k -= 1;
182
+ }
183
+ let receiver: String = chars[k..start - 1].iter().collect();
184
+ if receiver.is_empty() {
185
+ return None;
186
+ }
187
+ Some((receiver, member))
188
+ }
189
+
190
+ fn try_rust_member_on_crate(
191
+ crate_root: &Path,
192
+ members: &[Arc<str>],
193
+ imported_receiver: Option<&str>,
194
+ ) -> Option<Location> {
195
+ let last = members.last()?.as_ref();
196
+ if let Some(loc) = rust_def_for_crate_root(crate_root, last) {
197
+ return Some(loc);
198
+ }
199
+ if members.len() >= 2 {
200
+ let joined = members
201
+ .iter()
202
+ .map(|m| export_name_to_rust_ident(m.as_ref()))
203
+ .collect::<Vec<_>>()
204
+ .join("_");
205
+ if let Some(loc) = rust_fn_location_exact(crate_root, &joined) {
206
+ return Some(loc);
207
+ }
208
+ }
209
+ if let Some(im) = imported_receiver {
210
+ let mut s = export_name_to_rust_ident(im);
211
+ for m in members {
212
+ s.push('_');
213
+ s.push_str(&export_name_to_rust_ident(m.as_ref()));
214
+ }
215
+ if let Some(loc) = rust_fn_location_exact(crate_root, &s) {
216
+ return Some(loc);
217
+ }
218
+ }
219
+ None
220
+ }
221
+
222
+ /// Optional `| …` hover line after the 1-based line in a native package’s `lsp-pragmas.d.tish`.
223
+ fn parse_lsp_pragmas_native(
224
+ src: &str,
225
+ ) -> HashMap<String, (crate::builtin_goto::BuiltinDef, Option<String>)> {
226
+ let re = Regex::new(r"(?m)^\s*//\s*@tish-source\s+(\S+)\s+(\S+)\s+(\d+)(?:\s*\|\s*(.*?))?\s*$")
227
+ .expect("native lsp pragma regex");
228
+ let mut m = HashMap::new();
229
+ for cap in re.captures_iter(src) {
230
+ let sym = cap[1].to_string();
231
+ let rel = cap[2].to_string();
232
+ let line_1: u32 = cap[3].parse().unwrap_or(1);
233
+ let doc = cap
234
+ .get(4)
235
+ .map(|g| g.as_str().trim().to_string())
236
+ .filter(|s| !s.is_empty());
237
+ m.insert(
238
+ sym,
239
+ (
240
+ crate::builtin_goto::BuiltinDef {
241
+ rel_path: rel,
242
+ line: line_1.saturating_sub(1),
243
+ character: 0,
244
+ },
245
+ doc,
246
+ ),
247
+ );
248
+ }
249
+ m
250
+ }
251
+
252
+ fn pragma_key_for_native_member(
253
+ imported_for_prefix: Option<&str>,
254
+ members: &[Arc<str>],
255
+ ) -> Option<String> {
256
+ if members.is_empty() {
257
+ return None;
258
+ }
259
+ match imported_for_prefix {
260
+ Some(prefix) => {
261
+ let tail: String = members
262
+ .iter()
263
+ .map(|m| m.as_ref())
264
+ .collect::<Vec<_>>()
265
+ .join(".");
266
+ if tail.is_empty() {
267
+ None
268
+ } else {
269
+ Some(format!("{prefix}.{tail}"))
270
+ }
271
+ }
272
+ None => {
273
+ if members.len() >= 2 {
274
+ Some(
275
+ members
276
+ .iter()
277
+ .map(|m| m.as_ref())
278
+ .collect::<Vec<_>>()
279
+ .join("."),
280
+ )
281
+ } else {
282
+ None
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ fn lookup_lsp_pragma_in_crate_root(
289
+ crate_root: &Path,
290
+ key: &str,
291
+ ) -> Option<(crate::builtin_goto::BuiltinDef, Option<String>)> {
292
+ let path = crate_root.join("lsp-pragmas.d.tish");
293
+ let src = std::fs::read_to_string(&path).ok()?;
294
+ let map = parse_lsp_pragmas_native(&src);
295
+ map.get(key).cloned()
296
+ }
297
+
298
+ #[derive(Debug, Clone)]
299
+ pub struct NativeMemberDefinition {
300
+ pub location: Location,
301
+ /// From `lsp-pragmas.d.tish` when Rust `pub fn` resolution misses.
302
+ pub doc: Option<String>,
303
+ }
304
+
305
+ /// Static member chain `root.a.b` where `root` is an import: resolve the leaf to a Rust `pub fn`,
306
+ /// else to `lsp-pragmas.d.tish` in the native package (e.g. `tish-macos`).
307
+ pub fn native_member_definition(
308
+ program: &Program,
309
+ file_path: &Path,
310
+ text: &str,
311
+ roots: &[PathBuf],
312
+ cargo_src_cache: &RwLock<HashMap<(PathBuf, String), PathBuf>>,
313
+ lsp_line: u32,
314
+ lsp_character: u32,
315
+ word: &str,
316
+ ) -> Option<NativeMemberDefinition> {
317
+ let project_root = infer_project_root(file_path, roots)?;
318
+ let from_dir = file_path.parent()?;
319
+
320
+ let (receiver_local, members): (String, Vec<Arc<str>>) =
321
+ if let Some(ch) = member_access_chain_at_cursor(program, text, lsp_line, lsp_character) {
322
+ if ch.members.last()?.as_ref() != word {
323
+ return None;
324
+ }
325
+ (ch.root_local.as_ref().to_string(), ch.members)
326
+ } else {
327
+ let line_str = text.lines().nth(lsp_line as usize)?;
328
+ let col = lsp_character as usize;
329
+ let (recv, member) = split_receiver_member(line_str, col)?;
330
+ if member != word {
331
+ return None;
332
+ }
333
+ (recv, vec![Arc::from(member.as_str())])
334
+ };
335
+
336
+ for stmt in &program.statements {
337
+ let Statement::Import {
338
+ specifiers, from, ..
339
+ } = stmt
340
+ else {
341
+ continue;
342
+ };
343
+ let from_s = from.as_ref();
344
+ for sp in specifiers {
345
+ let (imported_for_prefix, local) = match sp {
346
+ ImportSpecifier::Named { name, alias, .. } => (
347
+ Some(name.as_ref()),
348
+ alias.as_ref().map(|a| a.as_ref()).unwrap_or(name.as_ref()),
349
+ ),
350
+ ImportSpecifier::Default { name, .. } => (Some(name.as_ref()), name.as_ref()),
351
+ ImportSpecifier::Namespace { name, .. } => (None, name.as_ref()),
352
+ };
353
+ if local != receiver_local.as_str() {
354
+ continue;
355
+ }
356
+
357
+ if from_s.starts_with("./") || from_s.starts_with("../") {
358
+ if members.len() == 1 {
359
+ let loc = resolve_relative_tish(from_dir, from_s, members[0].as_ref())?;
360
+ return Some(NativeMemberDefinition {
361
+ location: loc,
362
+ doc: None,
363
+ });
364
+ }
365
+ continue;
366
+ }
367
+
368
+ if !is_native_import(from_s) {
369
+ continue;
370
+ }
371
+
372
+ let spec = normalize_builtin_spec(from_s).unwrap_or_else(|| from_s.to_string());
373
+ if is_builtin_native_spec(&spec) {
374
+ continue;
375
+ }
376
+
377
+ let crate_root = if is_cargo_native_spec(&spec) {
378
+ let dep_key = spec.strip_prefix("cargo:")?;
379
+ let tish = read_project_tish_config(&project_root);
380
+ let raw = tish
381
+ .get("rustDependencies")
382
+ .and_then(|v| v.get(dep_key))
383
+ .cloned()?;
384
+ cargo_crate_root_cached(&project_root, &spec, dep_key, &raw, cargo_src_cache)
385
+ .ok()?
386
+ } else {
387
+ let mods = resolve_native_modules(program, &project_root).ok()?;
388
+ let m = mods.iter().find(|mm| mm.spec == spec)?;
389
+ m.crate_path.clone()
390
+ };
391
+
392
+ if let Some(loc) = try_rust_member_on_crate(&crate_root, &members, imported_for_prefix)
393
+ {
394
+ return Some(NativeMemberDefinition {
395
+ location: loc,
396
+ doc: None,
397
+ });
398
+ }
399
+ if let Some(key) = pragma_key_for_native_member(imported_for_prefix, &members) {
400
+ if let Some((def, doc)) = lookup_lsp_pragma_in_crate_root(&crate_root, &key) {
401
+ if let Some(loc) = crate::builtin_goto::to_file_location(&crate_root, &def) {
402
+ return Some(NativeMemberDefinition { location: loc, doc });
403
+ }
404
+ }
405
+ }
406
+ return None;
407
+ }
408
+ }
409
+ None
410
+ }
411
+
412
+ /// Static member chain `root.a.b` where `root` is an import: resolve the leaf name to a Rust `pub fn`
413
+ /// (native / `cargo:`) or a single-level export in a relative `.tish` module.
414
+ pub fn definition_for_native_receiver_member(
415
+ program: &Program,
416
+ file_path: &Path,
417
+ text: &str,
418
+ roots: &[PathBuf],
419
+ cargo_src_cache: &RwLock<HashMap<(PathBuf, String), PathBuf>>,
420
+ lsp_line: u32,
421
+ lsp_character: u32,
422
+ word: &str,
423
+ ) -> Option<Location> {
424
+ native_member_definition(
425
+ program,
426
+ file_path,
427
+ text,
428
+ roots,
429
+ cargo_src_cache,
430
+ lsp_line,
431
+ lsp_character,
432
+ word,
433
+ )
434
+ .map(|d| d.location)
435
+ }
436
+
437
+ fn resolve_relative_tish(from_dir: &Path, from_s: &str, imported: &str) -> Option<Location> {
438
+ let target = from_dir.join(from_s.trim_start_matches("./"));
439
+ let target = if target.extension().is_none() {
440
+ target.with_extension("tish")
441
+ } else {
442
+ target
443
+ };
444
+ let can = target.canonicalize().ok()?;
445
+ let u = Url::from_file_path(&can).ok()?;
446
+ let src = std::fs::read_to_string(&can).ok()?;
447
+ let prog = tishlang_parser::parse(&src).ok()?;
448
+ crate::find_export(&prog, imported, &u, &src)
449
+ }
450
+
451
+ /// After same-file [`tishlang_resolve::definition_span`] misses, resolve import sites (Tish files,
452
+ /// `node_modules` packages, and Rust `pub fn` for native / `cargo:` imports).
453
+ pub fn definition_for_import(
454
+ program: &Program,
455
+ file_path: &Path,
456
+ word: &str,
457
+ roots: &[PathBuf],
458
+ cargo_src_cache: &RwLock<HashMap<(PathBuf, String), PathBuf>>,
459
+ ) -> Option<Location> {
460
+ if word.is_empty() {
461
+ return None;
462
+ }
463
+ let project_root = infer_project_root(file_path, roots)?;
464
+ let from_dir = file_path.parent()?;
465
+
466
+ for stmt in &program.statements {
467
+ let Statement::Import {
468
+ specifiers, from, ..
469
+ } = stmt
470
+ else {
471
+ continue;
472
+ };
473
+ let from_s = from.as_ref();
474
+ for sp in specifiers {
475
+ let (imported, local) = match sp {
476
+ ImportSpecifier::Named { name, alias, .. } => (
477
+ name.as_ref(),
478
+ alias.as_ref().map(|a| a.as_ref()).unwrap_or(name.as_ref()),
479
+ ),
480
+ ImportSpecifier::Default { name, .. } => (name.as_ref(), name.as_ref()),
481
+ ImportSpecifier::Namespace { .. } => continue,
482
+ };
483
+ if local != word {
484
+ continue;
485
+ }
486
+
487
+ if from_s.starts_with("./") || from_s.starts_with("../") {
488
+ return resolve_relative_tish(from_dir, from_s, imported);
489
+ }
490
+
491
+ if is_native_import(from_s) {
492
+ let spec = normalize_builtin_spec(from_s).unwrap_or_else(|| from_s.to_string());
493
+ if is_builtin_native_spec(&spec) {
494
+ return None;
495
+ }
496
+ if is_cargo_native_spec(&spec) {
497
+ let dep_key = spec.strip_prefix("cargo:")?;
498
+ let tish = read_project_tish_config(&project_root);
499
+ let raw = tish
500
+ .get("rustDependencies")
501
+ .and_then(|v| v.get(dep_key))
502
+ .cloned()?;
503
+ let crate_root = cargo_crate_root_cached(
504
+ &project_root,
505
+ &spec,
506
+ dep_key,
507
+ &raw,
508
+ cargo_src_cache,
509
+ )
510
+ .ok()?;
511
+ return rust_def_for_crate_root(&crate_root, imported);
512
+ }
513
+
514
+ let mods = resolve_native_modules(program, &project_root).ok()?;
515
+ let m = mods.iter().find(|mm| mm.spec == spec)?;
516
+ return rust_def_for_crate_root(&m.crate_path, imported);
517
+ }
518
+
519
+ let entry = resolve_bare_spec(from_s, from_dir, &project_root)?;
520
+ let u = Url::from_file_path(&entry).ok()?;
521
+ let src = std::fs::read_to_string(&entry).ok()?;
522
+ let prog = tishlang_parser::parse(&src).ok()?;
523
+ return crate::find_export(&prog, imported, &u, &src);
524
+ }
525
+ }
526
+ None
527
+ }
528
+
529
+ #[cfg(test)]
530
+ mod receiver_member_tests {
531
+ use std::sync::Arc;
532
+
533
+ #[test]
534
+ fn splits_window_set_title() {
535
+ let line = "window.setTitle(\"Hi\")";
536
+ let col = "window.".len(); // on 's'
537
+ let (recv, mem) = super::split_receiver_member(line, col).expect("split");
538
+ assert_eq!(recv, "window");
539
+ assert_eq!(mem, "setTitle");
540
+ }
541
+
542
+ #[test]
543
+ fn native_pragma_parse_optional_doc() {
544
+ let src = r"// @tish-source window.innerHeight src/appkit/window_api.rs 289 | Height in points.
545
+ // @tish-source window.innerWidth src/appkit/window_api.rs 272
546
+ ";
547
+ let m = super::parse_lsp_pragmas_native(src);
548
+ let (def, doc) = m.get("window.innerHeight").expect("innerHeight");
549
+ assert_eq!(def.rel_path, "src/appkit/window_api.rs");
550
+ assert_eq!(def.line, 288);
551
+ assert_eq!(doc.as_deref(), Some("Height in points."));
552
+ let (def2, doc2) = m.get("window.innerWidth").expect("innerWidth");
553
+ assert_eq!(def2.line, 271);
554
+ assert!(doc2.is_none());
555
+ }
556
+
557
+ #[test]
558
+ fn pragma_key_named_import() {
559
+ let k = super::pragma_key_for_native_member(Some("window"), &[Arc::from("innerHeight")]);
560
+ assert_eq!(k.as_deref(), Some("window.innerHeight"));
561
+ }
562
+ }