Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/oxc_wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,5 @@ oxc_prettier = { workspace = true }
console_error_panic_hook = { workspace = true }
serde = { workspace = true }
serde-wasm-bindgen = { workspace = true }
serde_json = { workspace = true }
tsify = { workspace = true }
wasm-bindgen = { workspace = true }
25 changes: 8 additions & 17 deletions crates/oxc_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,14 @@ export * from "@oxc-project/types";
#[derive(Default, Tsify)]
#[serde(rename_all = "camelCase")]
pub struct Oxc {
#[wasm_bindgen(readonly, skip_typescript)]
// Dummy field, only present to make `tsify` include it in the type definition for `Oxc`.
// The getter for this field in WASM bindings is generated by `update-bindings.mjs` script.
#[wasm_bindgen(skip)]
#[tsify(type = "Program")]
pub ast: JsValue,
pub ast: (),

#[wasm_bindgen(readonly, skip_typescript, js_name = astJson)]
pub ast_json: String,

#[wasm_bindgen(readonly, skip_typescript)]
pub ir: String,
Expand Down Expand Up @@ -466,22 +471,8 @@ impl Oxc {
}

fn convert_ast(&mut self, program: &mut Program) {
use serde::Deserialize;

Utf8ToUtf16::new().convert(program);

// Convert:
// 1. `Program` to JSON string using `ESTree`.
// 2. JSON string to `serde_json::Value`.
// 3. `serde_json::Value` to `wasm_bindgen::JsValue`.
// TODO: There has to be a better way!
let json = program.to_json();
let s = serde_json::de::StrRead::new(&json);
let mut deserializer = serde_json::Deserializer::new(s);
let value = serde_json::Value::deserialize(&mut deserializer).unwrap();
deserializer.end().unwrap();
self.ast = value.serialize(&self.serializer).unwrap();

self.ast_json = program.to_pretty_json();
self.comments = Self::map_comments(program.source_text, &program.comments);
}

Expand Down
50 changes: 50 additions & 0 deletions crates/oxc_wasm/update-bindings.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Script to inject code for an extra `ast` getter on `class Oxc` in WASM binding file.

import assert from 'assert';
import { readFileSync, writeFileSync } from 'fs';
import { join as pathJoin } from 'path';
import { fileURLToPath } from 'url';

const path = pathJoin(fileURLToPath(import.meta.url), '../../../npm/oxc-wasm/oxc_wasm.js');

// Extra getter on `Oxc` `get ast() { ... }` that gets the AST as JSON string,
// and parses it to a `Program` object.
//
// JSON parsing uses a reviver function that sets `value` field of `Literal`s for `BigInt`s and `RegExp`s.
// This is not possible to do on Rust side, as neither can be represented correctly in JSON.
// Invalid regexp, or valid regexp using syntax not supported by the platform is ignored.
//
// Note: This code is repeated in `napi/parser/index.js` and `wasm/parser/update-bindings.mjs`.
// Any changes should be applied in those 2 places too.
//
// Unlike `wasm/parser/update-bindings.mjs`, the getter does not cache the `JSON.parse`-ed value,
// because I (@overlookmotel) believe that the `Oxc` class instance is used as a singleton in playground,
// and the value of `astJson` may change after the source text is changed.
// TODO: Check this assumption is correct.
const getterCode = `
get ast() {
return JSON.parse(this.astJson, function(key, value) {
if (value === null && key === 'value' && Object.hasOwn(this, 'type') && this.type === 'Literal') {
if (Object.hasOwn(this, 'bigint')) {
return BigInt(this.bigint);
}
if (Object.hasOwn(this, 'regex')) {
const { regex } = this;
try {
return RegExp(regex.pattern, regex.flags);
} catch (_err) {}
}
}
return value;
});
}
`.trimEnd().replace(/ /g, ' ');

const insertGetterAfter = 'export class Oxc {';

const code = readFileSync(path, 'utf8');
const parts = code.split(insertGetterAfter);
assert(parts.length === 2);
const [before, after] = parts;
const updatedCode = [before, insertGetterAfter, getterCode, after].join('');
writeFileSync(path, updatedCode);
1 change: 1 addition & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ build-wasm mode="release":
wasm-pack build crates/oxc_wasm --no-pack --target web --scope oxc --out-dir ../../npm/oxc-wasm --{{mode}}
cp crates/oxc_wasm/package.json npm/oxc-wasm/package.json
rm npm/oxc-wasm/.gitignore
node ./crates/oxc_wasm/update-bindings.mjs

# Generate the JavaScript global variables. See `tasks/javascript_globals`
javascript-globals:
Expand Down
4 changes: 2 additions & 2 deletions napi/parser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ function wrap(result) {
return {
get program() {
if (!program) {
// Note: This code is repeated in `wasm/parser/update-bindings.mjs`.
// Any changes should be applied in both places.
// Note: This code is repeated in `wasm/parser/update-bindings.mjs` and `crates/oxc-wasm/update-bindings.mjs`.
// Any changes should be applied in those 2 scripts too.
program = JSON.parse(result.program, function(key, value) {
// Set `value` field of `Literal`s for `BigInt`s and `RegExp`s.
// This is not possible to do on Rust side, as neither can be represented correctly in JSON.
Expand Down
3 changes: 2 additions & 1 deletion npm/oxc-wasm/oxc_wasm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export * from "@oxc-project/types";

export interface Oxc {
ast: Program;
astJson: string;
ir: string;
controlFlowGraph: string;
symbols: any;
Expand Down Expand Up @@ -146,7 +147,7 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly __wbg_oxc_free: (a: number, b: number) => void;
readonly __wbg_get_oxc_ast: (a: number) => any;
readonly __wbg_get_oxc_astJson: (a: number) => [number, number];
readonly __wbg_get_oxc_ir: (a: number) => [number, number];
readonly __wbg_get_oxc_controlFlowGraph: (a: number) => [number, number];
readonly __wbg_get_oxc_symbols: (a: number) => any;
Expand Down
32 changes: 28 additions & 4 deletions npm/oxc-wasm/oxc_wasm.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,22 @@ const OxcFinalization = (typeof FinalizationRegistry === 'undefined')
: new FinalizationRegistry(ptr => wasm.__wbg_oxc_free(ptr >>> 0, 1));

export class Oxc {
get ast() {
return JSON.parse(this.astJson, function(key, value) {
if (value === null && key === 'value' && Object.hasOwn(this, 'type') && this.type === 'Literal') {
if (Object.hasOwn(this, 'bigint')) {
return BigInt(this.bigint);
}
if (Object.hasOwn(this, 'regex')) {
const { regex } = this;
try {
return RegExp(regex.pattern, regex.flags);
} catch (_err) {}
}
}
return value;
});
}

__destroy_into_raw() {
const ptr = this.__wbg_ptr;
Expand All @@ -216,11 +232,19 @@ export class Oxc {
wasm.__wbg_oxc_free(ptr, 0);
}
/**
* @returns {any}
* @returns {string}
*/
get ast() {
const ret = wasm.__wbg_get_oxc_ast(this.__wbg_ptr);
return ret;
get astJson() {
let deferred1_0;
let deferred1_1;
try {
const ret = wasm.__wbg_get_oxc_astJson(this.__wbg_ptr);
deferred1_0 = ret[0];
deferred1_1 = ret[1];
return getStringFromWasm0(ret[0], ret[1]);
} finally {
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
}
}
/**
* @returns {string}
Expand Down
2 changes: 1 addition & 1 deletion npm/oxc-wasm/oxc_wasm_bg.wasm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable */
export const memory: WebAssembly.Memory;
export const __wbg_oxc_free: (a: number, b: number) => void;
export const __wbg_get_oxc_ast: (a: number) => any;
export const __wbg_get_oxc_astJson: (a: number) => [number, number];
export const __wbg_get_oxc_ir: (a: number) => [number, number];
export const __wbg_get_oxc_controlFlowGraph: (a: number) => [number, number];
export const __wbg_get_oxc_symbols: (a: number) => any;
Expand Down
4 changes: 2 additions & 2 deletions wasm/parser/update-bindings.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ const bindingFilename = 'oxc_parser_wasm.js';
//
// The getter caches the result to avoid re-parsing JSON every time `result.program` is accessed.
//
// Note: This code is repeated in `napi/parser/index.js`.
// Any changes should be applied in both places.
// Note: This code is repeated in `napi/parser/index.js` and `crates/oxc-wasm/update-bindings.mjs`.
// Any changes should be applied in those 2 places too.
const getterCode = `
__program;

Expand Down
Loading