@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,11 @@
1
+ [package]
2
+ name = "tishlang_parser"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "Tish recursive descent parser"
6
+
7
+ license-file = { workspace = true }
8
+ repository = { workspace = true }
9
+ [dependencies]
10
+ tishlang_lexer = { path = "../tish_lexer", version = ">=0.1" }
11
+ tishlang_ast = { path = "../tish_ast", version = ">=0.1" }
@@ -0,0 +1,386 @@
1
+ //! Tish recursive descent parser.
2
+
3
+ mod parser;
4
+
5
+ use parser::Parser;
6
+
7
+ use tishlang_ast::Program;
8
+ use tishlang_lexer::Lexer;
9
+ pub use tishlang_lexer::LexerOptions;
10
+
11
+ /// Parse `source`, reading lexer options from the environment (e.g. `TISH_IGNORE_INDENT=1`
12
+ /// to ignore indentation syntax). Every backend funnels through here, so the env toggle
13
+ /// reaches run/build/dump-ast/fmt/lint/lsp uniformly.
14
+ pub fn parse(source: &str) -> Result<Program, String> {
15
+ parse_with_options(source, LexerOptions::from_env())
16
+ }
17
+
18
+ /// Parse with explicit lexer options, bypassing the environment.
19
+ ///
20
+ /// With `LexerOptions { ignore_indent: true }`, indentation is treated as ordinary
21
+ /// whitespace and blocks must be brace-delimited — useful for debugging how nested
22
+ /// blocks transpile, since fully brace-delimited code parses identically either way.
23
+ pub fn parse_with_options(source: &str, options: LexerOptions) -> Result<Program, String> {
24
+ let lexer = Lexer::with_options(source, options);
25
+ let tokens: Result<Vec<_>, _> = lexer.collect();
26
+ let tokens = tokens?;
27
+ let mut parser = Parser::new(&tokens);
28
+ parser.parse_program()
29
+ }
30
+
31
+ #[cfg(test)]
32
+ mod tests {
33
+ use super::*;
34
+ use tishlang_ast::{CallArg, Expr, ObjectProp, Statement};
35
+
36
+ #[test]
37
+ fn test_async_fn_parse() {
38
+ let program = parse("async fn foo() { }").expect("parse async fn");
39
+ assert_eq!(program.statements.len(), 1);
40
+ if let tishlang_ast::Statement::FunDecl { async_, name, .. } = &program.statements[0] {
41
+ assert!(async_, "expected async function");
42
+ assert_eq!(name.as_ref(), "foo");
43
+ } else {
44
+ panic!("expected FunDecl");
45
+ }
46
+ }
47
+
48
+ #[test]
49
+ fn test_object_literal_shorthand_single() {
50
+ let program = parse("const o = { port }").expect("parse object shorthand");
51
+ assert_eq!(program.statements.len(), 1);
52
+ let stmt = &program.statements[0];
53
+ let init = match stmt {
54
+ Statement::VarDecl {
55
+ init: Some(ref i), ..
56
+ } => i,
57
+ _ => panic!("expected VarDecl with init"),
58
+ };
59
+ let props = match init {
60
+ Expr::Object { ref props, .. } => props,
61
+ _ => panic!("expected Object expr"),
62
+ };
63
+ assert_eq!(props.len(), 1);
64
+ match &props[0] {
65
+ ObjectProp::KeyValue(k, v) => {
66
+ assert_eq!(k.as_ref(), "port");
67
+ if let Expr::Ident { ref name, .. } = v {
68
+ assert_eq!(name.as_ref(), "port");
69
+ } else {
70
+ panic!("expected Ident value for shorthand");
71
+ }
72
+ }
73
+ _ => panic!("expected KeyValue prop"),
74
+ }
75
+ }
76
+
77
+ #[test]
78
+ fn test_object_literal_string_key() {
79
+ let program =
80
+ parse(r#"const o = { "ai-a": 0, human: 1 }"#).expect("parse object with string key");
81
+ assert_eq!(program.statements.len(), 1);
82
+ let stmt = &program.statements[0];
83
+ let init = match stmt {
84
+ Statement::VarDecl {
85
+ init: Some(ref i), ..
86
+ } => i,
87
+ _ => panic!("expected VarDecl with init"),
88
+ };
89
+ let props = match init {
90
+ Expr::Object { ref props, .. } => props,
91
+ _ => panic!("expected Object expr"),
92
+ };
93
+ assert_eq!(props.len(), 2);
94
+ match &props[0] {
95
+ ObjectProp::KeyValue(k, _) => assert_eq!(k.as_ref(), "ai-a"),
96
+ _ => panic!("expected KeyValue prop"),
97
+ }
98
+ match &props[1] {
99
+ ObjectProp::KeyValue(k, _) => assert_eq!(k.as_ref(), "human"),
100
+ _ => panic!("expected KeyValue prop"),
101
+ }
102
+ }
103
+
104
+ #[test]
105
+ fn test_object_literal_shorthand_mixed() {
106
+ let program = parse("const o = { port, x: 1 }").expect("parse mixed object");
107
+ assert_eq!(program.statements.len(), 1);
108
+ let stmt = &program.statements[0];
109
+ let init = match stmt {
110
+ Statement::VarDecl {
111
+ init: Some(ref i), ..
112
+ } => i,
113
+ _ => panic!("expected VarDecl with init"),
114
+ };
115
+ let props = match init {
116
+ Expr::Object { ref props, .. } => props,
117
+ _ => panic!("expected Object expr"),
118
+ };
119
+ assert_eq!(props.len(), 2);
120
+ match &props[0] {
121
+ ObjectProp::KeyValue(k, v) => {
122
+ assert_eq!(k.as_ref(), "port");
123
+ if let Expr::Ident { ref name, .. } = v {
124
+ assert_eq!(name.as_ref(), "port");
125
+ } else {
126
+ panic!("expected Ident value for shorthand");
127
+ }
128
+ }
129
+ _ => panic!("expected KeyValue prop"),
130
+ }
131
+ match &props[1] {
132
+ ObjectProp::KeyValue(k, v) => {
133
+ assert_eq!(k.as_ref(), "x");
134
+ if let Expr::Literal { .. } = v {
135
+ // x: 1
136
+ } else {
137
+ panic!("expected Literal for x");
138
+ }
139
+ }
140
+ _ => panic!("expected KeyValue prop"),
141
+ }
142
+ }
143
+
144
+ fn unwrap_expr_stmt(program: &tishlang_ast::Program) -> &Expr {
145
+ match program.statements.first() {
146
+ Some(Statement::ExprStmt { expr, .. }) => expr,
147
+ _ => panic!("expected expression statement"),
148
+ }
149
+ }
150
+
151
+ #[test]
152
+ fn new_expression_simple_call() {
153
+ let program = parse("new Foo()").expect("parse");
154
+ let e = unwrap_expr_stmt(&program);
155
+ match e {
156
+ Expr::New { callee, args, .. } => {
157
+ assert!(
158
+ matches!(callee.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Foo")
159
+ );
160
+ assert!(args.is_empty());
161
+ }
162
+ _ => panic!("expected New, got {:?}", e),
163
+ }
164
+ }
165
+
166
+ #[test]
167
+ fn new_expression_with_args() {
168
+ let program = parse("new Uint8Array(16)").expect("parse");
169
+ let e = unwrap_expr_stmt(&program);
170
+ match e {
171
+ Expr::New { callee, args, .. } => {
172
+ assert!(
173
+ matches!(callee.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Uint8Array")
174
+ );
175
+ assert_eq!(args.len(), 1);
176
+ assert!(matches!(&args[0], CallArg::Expr(Expr::Literal { .. })));
177
+ }
178
+ _ => panic!("expected New"),
179
+ }
180
+ }
181
+
182
+ #[test]
183
+ fn new_expression_member_callee() {
184
+ let program = parse("new ns.AudioContext()").expect("parse");
185
+ let e = unwrap_expr_stmt(&program);
186
+ match e {
187
+ Expr::New { callee, args, .. } => {
188
+ assert!(matches!(
189
+ callee.as_ref(),
190
+ Expr::Member { prop: tishlang_ast::MemberProp::Name { name, .. }, .. } if name.as_ref() == "AudioContext"
191
+ ));
192
+ assert!(args.is_empty());
193
+ }
194
+ _ => panic!("expected New"),
195
+ }
196
+ }
197
+
198
+ #[test]
199
+ fn new_expression_chained_new() {
200
+ let program = parse("new new Date()").expect("parse");
201
+ let e = unwrap_expr_stmt(&program);
202
+ match e {
203
+ Expr::New { callee, args, .. } => {
204
+ assert!(args.is_empty());
205
+ match callee.as_ref() {
206
+ Expr::New {
207
+ callee: inner,
208
+ args: inner_args,
209
+ ..
210
+ } => {
211
+ assert!(
212
+ matches!(inner.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Date")
213
+ );
214
+ assert!(inner_args.is_empty());
215
+ }
216
+ _ => panic!("expected nested New"),
217
+ }
218
+ }
219
+ _ => panic!("expected New"),
220
+ }
221
+ }
222
+
223
+ #[test]
224
+ fn new_then_member_access() {
225
+ let program = parse("new Foo().bar").expect("parse");
226
+ let e = unwrap_expr_stmt(&program);
227
+ match e {
228
+ Expr::Member {
229
+ object,
230
+ prop: tishlang_ast::MemberProp::Name { name, .. },
231
+ ..
232
+ } => {
233
+ assert_eq!(name.as_ref(), "bar");
234
+ match object.as_ref() {
235
+ Expr::New { callee, args, .. } => {
236
+ assert!(
237
+ matches!(callee.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Foo")
238
+ );
239
+ assert!(args.is_empty());
240
+ }
241
+ _ => panic!("expected New object"),
242
+ }
243
+ }
244
+ _ => panic!("expected Member"),
245
+ }
246
+ }
247
+
248
+ #[test]
249
+ fn new_with_spread_arg() {
250
+ let program = parse("new Foo(...xs)").expect("parse");
251
+ let e = unwrap_expr_stmt(&program);
252
+ match e {
253
+ Expr::New { args, .. } => {
254
+ assert!(
255
+ matches!(&args[0], CallArg::Spread(Expr::Ident { name, .. }) if name.as_ref() == "xs")
256
+ );
257
+ }
258
+ _ => panic!("expected New"),
259
+ }
260
+ }
261
+
262
+ #[test]
263
+ fn stdlib_builtins_d_tish_parses() {
264
+ const SRC: &str = include_str!("../../../stdlib/builtins.d.tish");
265
+ parse(SRC).expect("stdlib/builtins.d.tish should parse");
266
+ }
267
+
268
+ #[test]
269
+ fn for_empty_head_parses() {
270
+ let src = r#"fn f() {
271
+ for (;;)
272
+ const x = 1
273
+ }"#;
274
+ let program = parse(src).expect("for (;;)");
275
+ let body = match &program.statements[0] {
276
+ Statement::FunDecl { body, .. } => body,
277
+ _ => panic!("expected fn"),
278
+ };
279
+ let stmts = match body.as_ref() {
280
+ Statement::Block { statements, .. } => statements,
281
+ _ => panic!("expected block body"),
282
+ };
283
+ assert!(
284
+ matches!(
285
+ stmts.iter().find(|s| matches!(s, Statement::For { .. })),
286
+ Some(Statement::For {
287
+ init: None,
288
+ cond: None,
289
+ update: None,
290
+ ..
291
+ })
292
+ ),
293
+ "expected for (;;)"
294
+ );
295
+ }
296
+
297
+ #[test]
298
+ fn brace_function_body_does_not_nest_block_around_first_let() {
299
+ let src = "fn h() {\n let a = 1\n let b = 2\n}\n";
300
+ let program = parse(src).expect("parse");
301
+ let body = match &program.statements[0] {
302
+ Statement::FunDecl { body, .. } => body,
303
+ _ => panic!("expected fn"),
304
+ };
305
+ let stmts = match body.as_ref() {
306
+ Statement::Block { statements, .. } => statements,
307
+ _ => panic!("expected block body"),
308
+ };
309
+ assert_eq!(
310
+ stmts.len(),
311
+ 2,
312
+ "expected two top-level lets in fn body, not Block(let) + let — got {stmts:?}"
313
+ );
314
+ assert!(matches!(stmts[0], Statement::VarDecl { .. }));
315
+ assert!(matches!(stmts[1], Statement::VarDecl { .. }));
316
+ }
317
+
318
+ #[test]
319
+ fn member_access_allows_type_property_name() {
320
+ let src = "fn f() {\n const label = 0\n label.type = \"button\"\n}\n";
321
+ parse(src).expect("label.type should parse: `type` is a keyword but valid after `.`");
322
+ }
323
+
324
+ #[test]
325
+ fn brace_block_stmt_then_const_then_if_are_siblings() {
326
+ let src = "fn g() {\n f()\n const x = 1\n if (x) {\n f()\n }\n}\n";
327
+ let program = parse(src).expect("parse");
328
+ let body = match &program.statements[0] {
329
+ Statement::FunDecl { body, .. } => body,
330
+ _ => panic!("expected fn"),
331
+ };
332
+ let stmts = match body.as_ref() {
333
+ Statement::Block { statements, .. } => statements,
334
+ _ => panic!("expected block body"),
335
+ };
336
+ assert_eq!(
337
+ stmts.len(),
338
+ 3,
339
+ "expected expr; const; if as siblings — got {stmts:?}"
340
+ );
341
+ assert!(matches!(stmts[0], Statement::ExprStmt { .. }));
342
+ assert!(matches!(stmts[1], Statement::VarDecl { .. }));
343
+ assert!(matches!(stmts[2], Statement::If { .. }));
344
+ }
345
+
346
+ #[test]
347
+ fn ignore_indent_parses_brace_blocks_identically() {
348
+ // Fully brace-delimited code: braces are authoritative, indentation is decoration.
349
+ // Ignoring indentation must therefore produce an identical AST.
350
+ let src = "fn f() {\n let a = 1\n if (a) {\n let b = 2\n g(b)\n }\n}\n";
351
+ let normal = parse(src).expect("parse (indentation significant)");
352
+ let ignored = parse_with_options(src, LexerOptions { ignore_indent: true })
353
+ .expect("parse (indentation ignored)");
354
+ assert_eq!(
355
+ format!("{normal:#?}"),
356
+ format!("{ignored:#?}"),
357
+ "brace-delimited code must parse identically with indentation ignored"
358
+ );
359
+ }
360
+
361
+ #[test]
362
+ fn ignore_indent_drops_indentation_induced_block() {
363
+ // A leading-indented line makes the lexer open an indent level, so the parser wraps
364
+ // `a()` in a `Block` — the kind of stray, indentation-driven nesting that can give
365
+ // transpiled JS the wrong lexical scope. Ignoring indentation removes that wrapper.
366
+ let src = " a()\nb()\n";
367
+
368
+ let normal = parse(src).expect("parse normal");
369
+ assert!(
370
+ matches!(normal.statements.first(), Some(Statement::Block { .. })),
371
+ "indentation should wrap a() in a Block, got: {:?}",
372
+ normal.statements
373
+ );
374
+
375
+ let ignored = parse_with_options(src, LexerOptions { ignore_indent: true })
376
+ .expect("parse ignored");
377
+ assert!(
378
+ ignored
379
+ .statements
380
+ .iter()
381
+ .all(|s| matches!(s, Statement::ExprStmt { .. })),
382
+ "with indentation ignored, both calls are flat expression statements, got: {:?}",
383
+ ignored.statements
384
+ );
385
+ }
386
+ }