@tishlang/tish-format 1.0.12 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/Cargo.toml +51 -0
  2. package/LICENSE +13 -0
  3. package/bin/tish-format +0 -0
  4. package/crates/js_to_tish/Cargo.toml +11 -0
  5. package/crates/js_to_tish/README.md +18 -0
  6. package/crates/js_to_tish/src/error.rs +55 -0
  7. package/crates/js_to_tish/src/lib.rs +11 -0
  8. package/crates/js_to_tish/src/span_util.rs +35 -0
  9. package/crates/js_to_tish/src/transform/expr.rs +611 -0
  10. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  11. package/crates/js_to_tish/src/transform.rs +60 -0
  12. package/crates/tish/Cargo.toml +62 -0
  13. package/crates/tish/build.rs +21 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +576 -0
  16. package/crates/tish/src/main.rs +853 -0
  17. package/crates/tish/src/repl_completion.rs +199 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/error_source_location.rs +36 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  24. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  25. package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
  26. package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
  27. package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
  28. package/crates/tish/tests/integration_test.rs +1406 -0
  29. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  30. package/crates/tish/tests/shortcircuit.rs +65 -0
  31. package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
  32. package/crates/tish/tests/tty_capability.rs +43 -0
  33. package/crates/tish_ast/Cargo.toml +9 -0
  34. package/crates/tish_ast/src/ast.rs +649 -0
  35. package/crates/tish_ast/src/lib.rs +5 -0
  36. package/crates/tish_build_utils/Cargo.toml +11 -0
  37. package/crates/tish_build_utils/src/lib.rs +577 -0
  38. package/crates/tish_builtins/Cargo.toml +22 -0
  39. package/crates/tish_builtins/src/array.rs +803 -0
  40. package/crates/tish_builtins/src/collections.rs +481 -0
  41. package/crates/tish_builtins/src/construct.rs +199 -0
  42. package/crates/tish_builtins/src/date.rs +538 -0
  43. package/crates/tish_builtins/src/globals.rs +293 -0
  44. package/crates/tish_builtins/src/helpers.rs +35 -0
  45. package/crates/tish_builtins/src/iterator.rs +129 -0
  46. package/crates/tish_builtins/src/lib.rs +21 -0
  47. package/crates/tish_builtins/src/math.rs +89 -0
  48. package/crates/tish_builtins/src/number.rs +96 -0
  49. package/crates/tish_builtins/src/object.rs +36 -0
  50. package/crates/tish_builtins/src/string.rs +646 -0
  51. package/crates/tish_builtins/src/symbol.rs +83 -0
  52. package/crates/tish_builtins/src/typedarrays.rs +298 -0
  53. package/crates/tish_bytecode/Cargo.toml +17 -0
  54. package/crates/tish_bytecode/src/chunk.rs +164 -0
  55. package/crates/tish_bytecode/src/compiler.rs +2604 -0
  56. package/crates/tish_bytecode/src/encoding.rs +102 -0
  57. package/crates/tish_bytecode/src/lib.rs +20 -0
  58. package/crates/tish_bytecode/src/opcode.rs +185 -0
  59. package/crates/tish_bytecode/src/peephole.rs +189 -0
  60. package/crates/tish_bytecode/src/serialize.rs +193 -0
  61. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  62. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  63. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  64. package/crates/tish_compile/Cargo.toml +27 -0
  65. package/crates/tish_compile/src/check.rs +774 -0
  66. package/crates/tish_compile/src/codegen.rs +7317 -0
  67. package/crates/tish_compile/src/infer.rs +1681 -0
  68. package/crates/tish_compile/src/lib.rs +206 -0
  69. package/crates/tish_compile/src/resolve.rs +1951 -0
  70. package/crates/tish_compile/src/types.rs +605 -0
  71. package/crates/tish_compile_js/Cargo.toml +18 -0
  72. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  73. package/crates/tish_compile_js/src/codegen.rs +938 -0
  74. package/crates/tish_compile_js/src/error.rs +20 -0
  75. package/crates/tish_compile_js/src/lib.rs +26 -0
  76. package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
  77. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  78. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  79. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  80. package/crates/tish_core/Cargo.toml +32 -0
  81. package/crates/tish_core/src/console_style.rs +170 -0
  82. package/crates/tish_core/src/json.rs +430 -0
  83. package/crates/tish_core/src/lib.rs +20 -0
  84. package/crates/tish_core/src/macros.rs +36 -0
  85. package/crates/tish_core/src/shape.rs +85 -0
  86. package/crates/tish_core/src/uri.rs +118 -0
  87. package/crates/tish_core/src/value.rs +1350 -0
  88. package/crates/tish_core/src/vmref.rs +183 -0
  89. package/crates/tish_cranelift/Cargo.toml +19 -0
  90. package/crates/tish_cranelift/src/lib.rs +43 -0
  91. package/crates/tish_cranelift/src/link.rs +130 -0
  92. package/crates/tish_cranelift/src/lower.rs +85 -0
  93. package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
  94. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  95. package/crates/tish_eval/Cargo.toml +51 -0
  96. package/crates/tish_eval/src/eval.rs +4265 -0
  97. package/crates/tish_eval/src/http.rs +191 -0
  98. package/crates/tish_eval/src/lib.rs +99 -0
  99. package/crates/tish_eval/src/natives.rs +551 -0
  100. package/crates/tish_eval/src/promise.rs +179 -0
  101. package/crates/tish_eval/src/regex.rs +299 -0
  102. package/crates/tish_eval/src/timers.rs +120 -0
  103. package/crates/tish_eval/src/value.rs +336 -0
  104. package/crates/tish_eval/src/value_convert.rs +117 -0
  105. package/crates/tish_ffi/Cargo.toml +26 -0
  106. package/crates/tish_ffi/src/lib.rs +518 -0
  107. package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
  108. package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
  109. package/crates/tish_ffi/tests/loader.rs +65 -0
  110. package/crates/tish_fmt/Cargo.toml +16 -0
  111. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  112. package/crates/tish_fmt/src/lib.rs +2157 -0
  113. package/crates/tish_jsx_web/Cargo.toml +9 -0
  114. package/crates/tish_jsx_web/README.md +5 -0
  115. package/crates/tish_jsx_web/src/lib.rs +2 -0
  116. package/crates/tish_lexer/Cargo.toml +9 -0
  117. package/crates/tish_lexer/src/lib.rs +1104 -0
  118. package/crates/tish_lexer/src/token.rs +170 -0
  119. package/crates/tish_lint/Cargo.toml +18 -0
  120. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  121. package/crates/tish_lint/src/lib.rs +281 -0
  122. package/crates/tish_llvm/Cargo.toml +13 -0
  123. package/crates/tish_llvm/src/lib.rs +115 -0
  124. package/crates/tish_lsp/Cargo.toml +25 -0
  125. package/crates/tish_lsp/README.md +26 -0
  126. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  127. package/crates/tish_lsp/src/import_goto.rs +564 -0
  128. package/crates/tish_lsp/src/main.rs +1459 -0
  129. package/crates/tish_native/Cargo.toml +16 -0
  130. package/crates/tish_native/src/build.rs +481 -0
  131. package/crates/tish_native/src/config.rs +48 -0
  132. package/crates/tish_native/src/lib.rs +416 -0
  133. package/crates/tish_opt/Cargo.toml +13 -0
  134. package/crates/tish_opt/src/lib.rs +1046 -0
  135. package/crates/tish_parser/Cargo.toml +11 -0
  136. package/crates/tish_parser/src/lib.rs +386 -0
  137. package/crates/tish_parser/src/parser.rs +2726 -0
  138. package/crates/tish_pg/Cargo.toml +34 -0
  139. package/crates/tish_pg/README.md +38 -0
  140. package/crates/tish_pg/src/error.rs +52 -0
  141. package/crates/tish_pg/src/lib.rs +955 -0
  142. package/crates/tish_resolve/Cargo.toml +13 -0
  143. package/crates/tish_resolve/src/lib.rs +3601 -0
  144. package/crates/tish_resolve/src/pos.rs +141 -0
  145. package/crates/tish_runtime/Cargo.toml +100 -0
  146. package/crates/tish_runtime/src/http.rs +1347 -0
  147. package/crates/tish_runtime/src/http_fetch.rs +492 -0
  148. package/crates/tish_runtime/src/http_hyper.rs +441 -0
  149. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  150. package/crates/tish_runtime/src/lib.rs +1447 -0
  151. package/crates/tish_runtime/src/native_promise.rs +15 -0
  152. package/crates/tish_runtime/src/promise.rs +558 -0
  153. package/crates/tish_runtime/src/promise_io.rs +38 -0
  154. package/crates/tish_runtime/src/timers.rs +172 -0
  155. package/crates/tish_runtime/src/tty.rs +226 -0
  156. package/crates/tish_runtime/src/ws.rs +778 -0
  157. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  158. package/crates/tish_ui/Cargo.toml +17 -0
  159. package/crates/tish_ui/src/jsx.rs +692 -0
  160. package/crates/tish_ui/src/lib.rs +20 -0
  161. package/crates/tish_ui/src/runtime/hooks.rs +573 -0
  162. package/crates/tish_ui/src/runtime/mod.rs +183 -0
  163. package/crates/tish_vm/Cargo.toml +60 -0
  164. package/crates/tish_vm/src/jit.rs +1050 -0
  165. package/crates/tish_vm/src/lib.rs +41 -0
  166. package/crates/tish_vm/src/vm.rs +3536 -0
  167. package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
  168. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  169. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  170. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  171. package/crates/tish_wasm/Cargo.toml +15 -0
  172. package/crates/tish_wasm/src/lib.rs +428 -0
  173. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  174. package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
  175. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  176. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  177. package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
  178. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  179. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  180. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  181. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  182. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  183. package/justfile +276 -0
  184. package/package.json +2 -2
  185. package/platform/darwin-arm64/tish-fmt +0 -0
  186. package/platform/darwin-x64/tish-fmt +0 -0
  187. package/platform/linux-arm64/tish-fmt +0 -0
  188. package/platform/linux-x64/tish-fmt +0 -0
  189. package/platform/win32-x64/tish-fmt.exe +0 -0
@@ -0,0 +1,261 @@
1
+ //! Classify a `pub fn` from syn for glue emission (driven by signature shape, not crate name).
2
+
3
+ use syn::{FnArg, GenericArgument, ItemFn, PathArguments, ReturnType, Type, TypeReference};
4
+
5
+ #[derive(Debug, Clone, PartialEq, Eq)]
6
+ pub enum SignatureClass {
7
+ /// `fn foo(args: &[Value]) -> Value` (or `tishlang_runtime::Value`).
8
+ TishValueAbi,
9
+ /// First parameter `&T` (or `&mut T`), `T: Serialize` (or `?Sized + Serialize`), returns `Result<String, _>`-like.
10
+ SerializeRefToResultString,
11
+ /// First parameter `&str` (or `& 'a str`), returns `Result<_, _>`, and has `Deserialize` bound on a type param.
12
+ DeserializeStrToResult,
13
+ }
14
+
15
+ pub fn classify_public_fn(item: &ItemFn) -> Option<SignatureClass> {
16
+ if matches!(classify_tish_abi(item), Some(SignatureClass::TishValueAbi)) {
17
+ return Some(SignatureClass::TishValueAbi);
18
+ }
19
+ if is_deserialize_str_result(item) {
20
+ return Some(SignatureClass::DeserializeStrToResult);
21
+ }
22
+ if is_serialize_ref_to_result_string(item) {
23
+ return Some(SignatureClass::SerializeRefToResultString);
24
+ }
25
+ None
26
+ }
27
+
28
+ fn classify_tish_abi(item: &ItemFn) -> Option<SignatureClass> {
29
+ let sig = &item.sig;
30
+ let mut value_args = 0;
31
+ for arg in &sig.inputs {
32
+ let FnArg::Typed(t) = arg else {
33
+ continue;
34
+ };
35
+ if is_slice_value(&t.ty) {
36
+ value_args += 1;
37
+ }
38
+ }
39
+ if value_args != 1 || sig.inputs.len() != 1 {
40
+ return None;
41
+ }
42
+ let ret_ty = return_type_inner(&sig.output)?;
43
+ if !is_value_type(ret_ty) {
44
+ return None;
45
+ }
46
+ Some(SignatureClass::TishValueAbi)
47
+ }
48
+
49
+ fn return_type_inner(ret: &ReturnType) -> Option<&Type> {
50
+ match ret {
51
+ ReturnType::Default => None,
52
+ ReturnType::Type(_, ty) => Some(ty),
53
+ }
54
+ }
55
+
56
+ fn is_slice_value(ty: &Type) -> bool {
57
+ let Some(inner) = strip_reference(ty) else {
58
+ return false;
59
+ };
60
+ let Type::Slice(s) = inner else {
61
+ return false;
62
+ };
63
+ is_value_type(&s.elem)
64
+ }
65
+
66
+ fn strip_reference(ty: &Type) -> Option<&Type> {
67
+ match ty {
68
+ Type::Reference(TypeReference { elem, .. }) => Some(elem.as_ref()),
69
+ _ => None,
70
+ }
71
+ }
72
+
73
+ fn is_value_type(ty: &Type) -> bool {
74
+ let Type::Path(p) = ty else {
75
+ return false;
76
+ };
77
+ let seg = p.path.segments.last();
78
+ let Some(seg) = seg else {
79
+ return false;
80
+ };
81
+ if seg.ident != "Value" {
82
+ return false;
83
+ }
84
+ // Accept `Value`, `tishlang_runtime::Value`, `tishlang_core::Value`
85
+ if p.path.segments.len() == 1 {
86
+ return true;
87
+ }
88
+ let prev = &p.path.segments[p.path.segments.len() - 2];
89
+ prev.ident == "tishlang_runtime" || prev.ident == "tishlang_core"
90
+ }
91
+
92
+ fn is_str_ref(ty: &Type) -> bool {
93
+ match ty {
94
+ Type::Reference(TypeReference { elem, .. }) => matches!(
95
+ elem.as_ref(),
96
+ Type::Path(p) if p.path.is_ident("str")
97
+ ),
98
+ _ => false,
99
+ }
100
+ }
101
+
102
+ fn is_deserialize_str_result(item: &ItemFn) -> bool {
103
+ let sig = &item.sig;
104
+ if sig.inputs.len() != 1 {
105
+ return false;
106
+ }
107
+ let FnArg::Typed(arg) = sig.inputs.first().unwrap() else {
108
+ return false;
109
+ };
110
+ if !is_str_ref(&arg.ty) {
111
+ return false;
112
+ }
113
+ let Some(ret) = return_type_inner(&sig.output) else {
114
+ return false;
115
+ };
116
+ if result_ok_type(ret).is_none() {
117
+ return false;
118
+ }
119
+ has_deserialize_bound(item)
120
+ }
121
+
122
+ fn has_deserialize_bound(item: &ItemFn) -> bool {
123
+ for p in &item.sig.generics.params {
124
+ if let syn::GenericParam::Type(t) = p {
125
+ for b in &t.bounds {
126
+ if bound_name_is(b, "Deserialize") {
127
+ return true;
128
+ }
129
+ }
130
+ }
131
+ }
132
+ if let Some(wc) = &item.sig.generics.where_clause {
133
+ for pred in &wc.predicates {
134
+ if let syn::WherePredicate::Type(t) = pred {
135
+ for b in &t.bounds {
136
+ if bound_name_is(b, "Deserialize") {
137
+ return true;
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+ false
144
+ }
145
+
146
+ fn bound_name_is(b: &syn::TypeParamBound, want: &str) -> bool {
147
+ let syn::TypeParamBound::Trait(t) = b else {
148
+ return false;
149
+ };
150
+ let path = &t.path;
151
+ path.segments.last().is_some_and(|s| s.ident == want)
152
+ }
153
+
154
+ fn is_serialize_ref_to_result_string(item: &ItemFn) -> bool {
155
+ let sig = &item.sig;
156
+ if sig.inputs.len() != 1 {
157
+ return false;
158
+ }
159
+ let FnArg::Typed(arg) = sig.inputs.first().unwrap() else {
160
+ return false;
161
+ };
162
+ if strip_reference(&arg.ty).is_none() {
163
+ return false;
164
+ }
165
+ let Some(ret) = return_type_inner(&sig.output) else {
166
+ return false;
167
+ };
168
+ let Some(ok_ty) = result_ok_type(ret) else {
169
+ return false;
170
+ };
171
+ if !type_is_string_or_str(ok_ty) {
172
+ return false;
173
+ }
174
+ has_serialize_bound(item)
175
+ }
176
+
177
+ fn has_serialize_bound(item: &ItemFn) -> bool {
178
+ for p in &item.sig.generics.params {
179
+ if let syn::GenericParam::Type(t) = p {
180
+ for b in &t.bounds {
181
+ if bound_name_is(b, "Serialize") {
182
+ return true;
183
+ }
184
+ }
185
+ }
186
+ }
187
+ if let Some(wc) = &item.sig.generics.where_clause {
188
+ for pred in &wc.predicates {
189
+ if let syn::WherePredicate::Type(t) = pred {
190
+ for b in &t.bounds {
191
+ if bound_name_is(b, "Serialize") {
192
+ return true;
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ false
199
+ }
200
+
201
+ fn result_ok_type(ty: &Type) -> Option<&Type> {
202
+ let Type::Path(p) = ty else {
203
+ return None;
204
+ };
205
+ let seg = p.path.segments.last()?;
206
+ if seg.ident != "Result" {
207
+ return None;
208
+ }
209
+ let PathArguments::AngleBracketed(ab) = &seg.arguments else {
210
+ return None;
211
+ };
212
+ let first = ab.args.first()?;
213
+ let GenericArgument::Type(t) = first else {
214
+ return None;
215
+ };
216
+ Some(t)
217
+ }
218
+
219
+ fn type_is_string_or_str(ty: &Type) -> bool {
220
+ match ty {
221
+ Type::Path(p) => {
222
+ if p.path.is_ident("String") {
223
+ return true;
224
+ }
225
+ p.path.segments.len() == 1 && p.path.segments[0].ident == "str"
226
+ }
227
+ _ => false,
228
+ }
229
+ }
230
+
231
+ #[cfg(test)]
232
+ mod tests {
233
+ use super::*;
234
+ use syn::parse_quote;
235
+
236
+ #[test]
237
+ fn classify_serde_to_string_shape() {
238
+ let item: ItemFn = parse_quote! {
239
+ pub fn to_string<T: ?Sized + Serialize>(value: &T) -> Result<String, ()> {
240
+ unimplemented!()
241
+ }
242
+ };
243
+ assert_eq!(
244
+ classify_public_fn(&item),
245
+ Some(SignatureClass::SerializeRefToResultString)
246
+ );
247
+ }
248
+
249
+ #[test]
250
+ fn classify_from_str_shape() {
251
+ let item: ItemFn = parse_quote! {
252
+ pub fn from_str<'a, T: Deserialize<'a>>(s: &'a str) -> Result<T, ()> {
253
+ unimplemented!()
254
+ }
255
+ };
256
+ assert_eq!(
257
+ classify_public_fn(&item),
258
+ Some(SignatureClass::DeserializeStrToResult)
259
+ );
260
+ }
261
+ }
@@ -0,0 +1,125 @@
1
+ //! Walk dependency `src/**/*.rs` and collect `pub fn` items by name.
2
+
3
+ use std::collections::HashMap;
4
+ use std::fs;
5
+ use std::path::{Path, PathBuf};
6
+
7
+ use syn::{Item, ItemFn, Visibility};
8
+ use walkdir::WalkDir;
9
+
10
+ fn is_pub(vis: &Visibility) -> bool {
11
+ matches!(vis, Visibility::Public(_))
12
+ }
13
+
14
+ /// Map export name (Rust ident) to the function AST (must be unique).
15
+ pub fn discover_public_functions(crate_root: &Path) -> Result<HashMap<String, ItemFn>, String> {
16
+ let src = crate_root.join("src");
17
+ if !src.is_dir() {
18
+ return Err(format!("no src/ under {}", crate_root.display()));
19
+ }
20
+
21
+ let mut map: HashMap<String, (PathBuf, ItemFn)> = HashMap::new();
22
+
23
+ for entry in WalkDir::new(&src)
24
+ .into_iter()
25
+ .filter_map(|e| e.ok())
26
+ .filter(|e| e.path().extension().map(|x| x == "rs").unwrap_or(false))
27
+ {
28
+ let path = entry.path();
29
+ let text =
30
+ fs::read_to_string(path).map_err(|e| format!("read {}: {}", path.display(), e))?;
31
+ let file =
32
+ syn::parse_file(&text).map_err(|e| format!("parse {}: {}", path.display(), e))?;
33
+
34
+ for item in file.items {
35
+ if let Item::Fn(f) = item {
36
+ if !is_pub(&f.vis) {
37
+ continue;
38
+ }
39
+ let name = f.sig.ident.to_string();
40
+ if let Some((prev_path, _)) = map.get(&name) {
41
+ return Err(format!(
42
+ "ambiguous public fn `{}`: found in {} and {}",
43
+ name,
44
+ prev_path.display(),
45
+ path.display()
46
+ ));
47
+ }
48
+ map.insert(name, (path.to_path_buf(), f));
49
+ }
50
+ }
51
+ }
52
+
53
+ Ok(map.into_iter().map(|(k, (_, v))| (k, v)).collect())
54
+ }
55
+
56
+ /// On-disk location of a top-level `pub fn {fn_name}` under `crate_root/src` (LSP line/column, 0-based).
57
+ ///
58
+ /// Requires `proc-macro2` built with `span-locations` (this crate enables it) so spans from
59
+ /// `syn::parse_file` carry line/column.
60
+ pub fn rust_public_fn_location(
61
+ crate_root: &Path,
62
+ fn_name: &str,
63
+ ) -> Result<(PathBuf, u32, u32), String> {
64
+ let src = crate_root.join("src");
65
+ if !src.is_dir() {
66
+ return Err(format!("no src/ under {}", crate_root.display()));
67
+ }
68
+
69
+ for entry in WalkDir::new(&src)
70
+ .into_iter()
71
+ .filter_map(|e| e.ok())
72
+ .filter(|e| e.path().extension().map(|x| x == "rs").unwrap_or(false))
73
+ {
74
+ let path = entry.path();
75
+ let text =
76
+ fs::read_to_string(path).map_err(|e| format!("read {}: {}", path.display(), e))?;
77
+ let file =
78
+ syn::parse_file(&text).map_err(|e| format!("parse {}: {}", path.display(), e))?;
79
+
80
+ for item in file.items {
81
+ if let Item::Fn(f) = item {
82
+ if !is_pub(&f.vis) {
83
+ continue;
84
+ }
85
+ if f.sig.ident != fn_name {
86
+ continue;
87
+ }
88
+ let lc = f.sig.ident.span().start();
89
+ let line = u32::try_from(lc.line)
90
+ .map_err(|_| "span line out of range".to_string())?
91
+ .saturating_sub(1);
92
+ let col =
93
+ u32::try_from(lc.column).map_err(|_| "span column out of range".to_string())?;
94
+ return Ok((path.to_path_buf(), line, col));
95
+ }
96
+ }
97
+ }
98
+
99
+ Err(format!(
100
+ "no public fn `{}` found under {}/src",
101
+ fn_name,
102
+ crate_root.display()
103
+ ))
104
+ }
105
+
106
+ #[cfg(test)]
107
+ mod tests {
108
+ use super::*;
109
+ use tempfile::tempdir;
110
+
111
+ #[test]
112
+ fn rust_public_fn_location_finds_fn() {
113
+ let tmp = tempdir().unwrap();
114
+ let src = tmp.path().join("src");
115
+ fs::create_dir_all(&src).unwrap();
116
+ fs::write(
117
+ src.join("lib.rs"),
118
+ "// comment\npub fn hello_tish_export() -> i32 { 0 }\n",
119
+ )
120
+ .unwrap();
121
+ let (path, line, _col) = rust_public_fn_location(tmp.path(), "hello_tish_export").unwrap();
122
+ assert!(path.ends_with("lib.rs"));
123
+ assert_eq!(line, 1);
124
+ }
125
+ }