diff --git a/compiler/Cargo.lock b/compiler/Cargo.lock index 3b40829..f66bf9c 100644 --- a/compiler/Cargo.lock +++ b/compiler/Cargo.lock @@ -996,6 +996,7 @@ dependencies = [ "napi-derive", "oxc_allocator", "oxc_ast", + "oxc_codegen", "oxc_sourcemap", "oxc_span", "oxc_syntax", diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 8ac3e98..93e4881 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -29,6 +29,7 @@ serde_json = { version = "1", features = ["preserve_order"] } solidjs-compiler = { git = "https://github.com/solidjs/solid", rev = "2105ffb8ca2fced55f5fac0763177fc93f97c2c2", default-features = false } oxc_allocator = "0.144" oxc_ast = "0.144" +oxc_codegen = "0.144" oxc_span = "0.144" oxc_sourcemap = "8.1" oxc_syntax = "0.144" diff --git a/compiler/src/app.rs b/compiler/src/app.rs index ca1f7cd..31799e9 100644 --- a/compiler/src/app.rs +++ b/compiler/src/app.rs @@ -2,7 +2,7 @@ //! compiler for `target`, each importing its dependencies by `.rx` path, with the program's stylesheet. What `rxc app` //! prints and the Node-API build (`src/node.rs`) returns to `@reactivescript/compiler/vite`. use crate::check::check_modules; -use crate::emit::{compile_linked, lower_view, Target}; +use crate::emit::{compile_linked, lower_view, view_jsx, Target}; use crate::modules::load_modules; use serde_json::{json, Map, Value}; use std::path::Path; @@ -26,16 +26,22 @@ pub fn target(name: &str) -> Target { /// `{entry, css, modules: {path: {code, map}}}`, or `{diagnostics: [...]}` / `{error}`. `entry` is an absolute path. /// `spa`: a client-only build, checked under 08 CN-3d. pub fn app(entry: &str, target: Target, module_name: &str, spa: bool) -> Value { + app_with(entry, target, module_name, spa, false, &read_disk) +} + +/// `app` over `read` in place of the disk: the WebAssembly build's host, or an editor's open files. `jsx`: each module +/// also carries the JSX Solid's compiler was handed (`view_jsx`). +pub fn app_with(entry: &str, target: Target, module_name: &str, spa: bool, jsx: bool, read: &dyn Fn(&str) -> Option) -> Value { // module keys are POSIX paths (`modules::join_path`); Windows takes `D:/a/b.rx` as it takes `D:\a\b.rx` let entry = &entry.replace('\\', "/"); (|| { - if read_disk(entry).is_none() { + if read(entry).is_none() { return Err(json!({ "error": format!("{entry}: no such file") })); } - let set = load_modules(entry, &read_disk).map_err(|e| { + let set = load_modules(entry, read).map_err(|e| { // a parse error is a diagnostic like the checker's, with its code (PARSE, or the habit it names) let file = e.2.clone().unwrap_or_else(|| entry.to_string()); - let mut d = crate::serve::parse_error_in(&file, &e, read_disk(&file).as_deref()); + let mut d = crate::serve::parse_error_in(&file, &e, read(&file).as_deref()); d["msg"] = json!(crate::serve::parse_message(&e)); json!({ "diagnostics": [d] }) })?; @@ -47,9 +53,13 @@ pub fn app(entry: &str, target: Target, module_name: &str, spa: bool) -> Value { let mut modules = Map::new(); for (name, module) in &out.files { let path = &out.paths[name]; - let text = read_disk(path).unwrap_or_default(); + let text = read(path).unwrap_or_default(); let l = lower_view(module, path, target, module_name, Some((path, &text))).map_err(|e| json!({ "error": e }))?; - modules.insert(path.clone(), json!({ "code": l.code, "map": l.map })); + let mut m = json!({ "code": l.code, "map": l.map }); + if jsx { + m["jsx"] = json!(view_jsx(module)); + } + modules.insert(path.clone(), m); } Ok(json!({ "entry": set.entry, "css": out.css, "modules": modules })) })() diff --git a/compiler/src/emit.rs b/compiler/src/emit.rs index ffc44a3..5170edc 100644 --- a/compiler/src/emit.rs +++ b/compiler/src/emit.rs @@ -1829,6 +1829,13 @@ pub struct Lowered { pub map: Option, } +/// A module as the JSX Solid's compiler is handed, printed: what `lower_view` compiles, before templates. +pub fn view_jsx(module: &[S]) -> String { + let alloc = oxc_allocator::Allocator::default(); + let program = crate::oxc::Build::new(&alloc).program(module); + oxc_codegen::Codegen::new().build(&program).code +} + /// Each module's JSX → JavaScript for `target` (`ref/compile/jsx.ts`): dom and ssr with hydration; universal over the /// renderer `module_name`. With `rx` (the module's `.rx` path and text), the output carries a source map onto it. CP-1c: /// Solid's positions are the program's layout; each maps to the `.rx` line it was lowered from (`oxc::Build::lines`), at diff --git a/compiler/src/parse.rs b/compiler/src/parse.rs index 45660c1..84dd0cb 100644 --- a/compiler/src/parse.rs +++ b/compiler/src/parse.rs @@ -1216,11 +1216,16 @@ pub fn parse_block(c: &mut Cursor) -> R> { c.ws(true); let mut out = vec![]; while !c.starts_with("}") { + let at = c.i; out.push(parse_stmt(c)?); out.append(&mut c.pending); c.ws(true); c.eat(";"); c.ws(true); + // an editor's parse reads a missing statement as an empty one, so at the end of input nothing moves + if c.i == at { + break; + } } c.expect("}")?; Ok(out) @@ -2256,6 +2261,13 @@ mod recovering { assert!(parse_program(src).is_err()); } + #[test] + fn an_unclosed_block_at_the_end_of_input_stops() { + let src = "export default component App() {\n + + + p.id === preset())}> + {(p) =>

{p().blurb}

} +
+ +
+
+
+ + {(name) => ( + + + + + + + )} + + +
+
+