diff --git a/Cargo.lock b/Cargo.lock index 964a4b0fc..0c7636df5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,12 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -183,7 +189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bff0805f79ecb1b35163f3957a6934ea8d04fcd36ef98b52e7316f63e72e73d1" dependencies = [ "castaway", - "cfg-if", + "cfg-if 1.0.0", "itoa", "ryu", "serde", @@ -203,6 +209,16 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen", +] + [[package]] name = "convert_case" version = "0.6.0" @@ -218,7 +234,7 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -261,7 +277,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -271,7 +287,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-epoch", "crossbeam-utils", ] @@ -283,7 +299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg", - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", "memoffset", "scopeguard", @@ -295,7 +311,7 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -316,7 +332,7 @@ version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -380,7 +396,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi", ] @@ -613,7 +629,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -631,6 +647,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + [[package]] name = "miette" version = "5.5.0" @@ -905,6 +927,25 @@ dependencies = [ "oxc_ast", ] +[[package]] +name = "oxc_wasm" +version = "0.0.0" +dependencies = [ + "console_error_panic_hook", + "miette", + "oxc_allocator", + "oxc_ast", + "oxc_diagnostics", + "oxc_linter", + "oxc_parser", + "oxc_semantic", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-test", + "wee_alloc", +] + [[package]] name = "percent-encoding" version = "2.2.0" @@ -1048,6 +1089,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1239,7 +1286,7 @@ version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", ] @@ -1385,7 +1432,9 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", + "serde", + "serde_json", "wasm-bindgen-macro", ] @@ -1404,6 +1453,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.84" @@ -1433,6 +1494,30 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "web-sys" version = "0.3.61" @@ -1462,6 +1547,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index d4519e5b2..26be2ad18 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -45,6 +45,28 @@ impl Linter { Self { rules } } + #[must_use] + pub fn from_json_str(s: &str) -> Self { + let rules = serde_json::from_str(s) + .ok() + .and_then(|v: serde_json::Value| v.get("rules").cloned()) + .and_then(|v| v.as_object().cloned()) + .map_or_else( + || RULES.to_vec(), + |rules_config| { + RULES + .iter() + .map(|rule| { + let value = rules_config.get(rule.name()); + rule.read_json(value.cloned()) + }) + .collect() + }, + ); + + Self { rules } + } + #[must_use] pub fn from_rules(rules: Vec) -> Self { Self { rules } diff --git a/crates/oxc_wasm/.gitignore b/crates/oxc_wasm/.gitignore new file mode 100644 index 000000000..4e301317e --- /dev/null +++ b/crates/oxc_wasm/.gitignore @@ -0,0 +1,6 @@ +/target +**/*.rs.bk +Cargo.lock +bin/ +pkg/ +wasm-pack.log diff --git a/crates/oxc_wasm/Cargo.toml b/crates/oxc_wasm/Cargo.toml new file mode 100644 index 000000000..d8d460bfb --- /dev/null +++ b/crates/oxc_wasm/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "oxc_wasm" +authors.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +oxc_allocator = {path = "../oxc_allocator"} +oxc_ast = {path = "../oxc_ast"} +oxc_diagnostics = {path = "../oxc_diagnostics"} +oxc_linter = {path = "../oxc_linter"} +oxc_parser = {path = "../oxc_parser"} +oxc_semantic = {path = "../oxc_semantic"} + +miette = {workspace = true, features = ["fancy-no-backtrace"]} +serde = {workspace = true, features = ["derive"]} +serde_json = {workspace = true} + +wasm-bindgen = {version = "0.2", features = ["serde-serialize"]} + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = {version = "0.1.6", optional = true} + +# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size +# compared to the default allocator's ~10K. It is slower than the default +# allocator, however. +# +# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. +wee_alloc = {version = "0.4.5", optional = true} + +[dev-dependencies] +wasm-bindgen-test = "0.3.13" + +[profile.release] +# Tell `rustc` to optimize for small code size. +opt-level = "s" diff --git a/crates/oxc_wasm/README.md b/crates/oxc_wasm/README.md new file mode 100644 index 000000000..6927d35c7 --- /dev/null +++ b/crates/oxc_wasm/README.md @@ -0,0 +1,38 @@ +## About + +Wasm package for oxc compiler. + +## 🚴 Usage + +```js +import oxc from 'oxc-wasm' + +const ast = oxc.main(code, options) +``` + +### 🛠️ Build with `wasm-pack build` + +``` +wasm-pack build +``` + +### 🔬 Test in Headless Browsers with `wasm-pack test` + +``` +wasm-pack test --headless --firefox +``` + +### 🎁 Publish to NPM with `wasm-pack publish` + +``` +wasm-pack publish +``` + +## 🔋 Batteries Included + +- [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating + between WebAssembly and JavaScript. +- [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) + for logging panic messages to the developer console. +- [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized + for small code size. diff --git a/crates/oxc_wasm/src/driver.rs b/crates/oxc_wasm/src/driver.rs new file mode 100644 index 000000000..5669020b1 --- /dev/null +++ b/crates/oxc_wasm/src/driver.rs @@ -0,0 +1,62 @@ +use std::{rc::Rc, sync::Arc}; + +use miette::NamedSource; +use oxc_allocator::Allocator; +use oxc_ast::SourceType; +use oxc_diagnostics::Diagnostics; +use oxc_linter::Linter; +use oxc_parser::Parser; +use oxc_semantic::SemanticBuilder; +use wasm_bindgen::JsValue; + +pub struct Driver { + allocator: Allocator, +} + +impl Driver { + pub fn new() -> Self { + Self { allocator: Allocator::default() } + } + + #[allow(deprecated)] + pub fn run( + &self, + path: &str, + source_text: &str, + source_type: SourceType, + eslintrc: &str, + ) -> JsValue { + let ret = Parser::new(&self.allocator, source_text, source_type) + .allow_return_outside_function(true) + .parse(); + + let program = self.allocator.alloc(ret.program); + + let diagnostics = Diagnostics::default(); + let semantic = SemanticBuilder::new(source_type).build(program, Rc::new(ret.trivias)); + let mut diagnostics = diagnostics.into_inner(); + + let source = Arc::new(NamedSource::new(path, source_text.to_string())); + + diagnostics.extend( + Linter::from_json_str(eslintrc) + .run(&Rc::new(semantic), source_text, false) + .into_iter() + .map(|m| m.error.with_source_code(source.clone())) + .chain(ret.errors), + ); + + if diagnostics.is_empty() { + if let Ok(ast) = JsValue::from_serde(program) { + return ast; + } + } + + let result = diagnostics + .into_iter() + .map(|error| format!("{error:?}")) + .collect::>() + .join("\n"); + JsValue::from_str(&result) + } +} diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs new file mode 100644 index 000000000..06ebb0b19 --- /dev/null +++ b/crates/oxc_wasm/src/lib.rs @@ -0,0 +1,50 @@ +mod driver; +mod utils; + +use driver::Driver; +use oxc_ast::SourceType; +use serde::{Deserialize, Serialize}; +use utils::set_panic_hook; +use wasm_bindgen::prelude::*; + +#[derive(Deserialize, Serialize, PartialEq, Eq)] +pub enum Language { + #[serde(rename = "javascript")] + JavaScript, + #[serde(rename = "typescript")] + TypeScript, +} + +impl Default for Language { + fn default() -> Self { + Self::TypeScript + } +} + +#[derive(Serialize, Deserialize, Default)] +pub struct Options { + pub language: Option, + + pub jsx: Option, + + pub eslintrc: Option, +} + +#[wasm_bindgen] +#[must_use] +#[allow(deprecated)] +pub fn main(text: &str, js_options: &JsValue) -> JsValue { + set_panic_hook(); + let options: Options = js_options.into_serde().unwrap_or_default(); + let path_str = format!( + "test.{}{}", + if matches!(options.language, Some(Language::TypeScript)) { "ts" } else { "js" }, + if options.jsx.unwrap_or_default() { "x" } else { "" } + ); + + let source_type = SourceType::from_path(&path_str).unwrap_or_default(); + + let driver = Driver::new(); + + driver.run(&path_str, text, source_type, &options.eslintrc.unwrap_or_default()) +} diff --git a/crates/oxc_wasm/src/utils.rs b/crates/oxc_wasm/src/utils.rs new file mode 100644 index 000000000..b1d7929dc --- /dev/null +++ b/crates/oxc_wasm/src/utils.rs @@ -0,0 +1,10 @@ +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +}