Skip to content
Open
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
node_modules
dist
package-lock.json
deps/swc/bindings/target/
deps/swc/target/
2 changes: 2 additions & 0 deletions deps/swc/Cargo.lock

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

2 changes: 2 additions & 0 deletions deps/swc/bindings/binding_typescript_wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ owo-colors = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde-wasm-bindgen = { workspace = true }
swc_common = { path = "../../crates/swc_common" }
swc_ecma_ast = { path = "../../crates/swc_ecma_ast", features = ["serde-impl"] }
swc_ecma_parser = { path = "../../crates/swc_ecma_parser", default-features = false, features = ["typescript"] }
swc_error_reporters = { path = "../../crates/swc_error_reporters" }
swc_ts_fast_strip = { path = "../../crates/swc_ts_fast_strip", features = [
"wasm-bindgen",
Expand Down
129 changes: 113 additions & 16 deletions deps/swc/bindings/binding_typescript_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ use js_sys::Uint8Array;
use miette::{GraphicalTheme, LabeledSpan, ThemeCharacters, ThemeStyles};
use serde::Serialize;
use swc_common::{
comments::SingleThreadedComments,
errors::{DiagnosticBuilder, DiagnosticId, Emitter, Handler, HANDLER},
sync::Lrc,
SourceMap, Span, GLOBALS,
FileName, SourceMap, Span, GLOBALS,
};
use swc_ecma_ast::{EsVersion, Program};
use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax};
use swc_error_reporters::{convert_span, to_pretty_source_code};
use swc_ts_fast_strip::{Options, TransformOutput};
use wasm_bindgen::prelude::*;
Expand All @@ -27,7 +30,27 @@ mod error_reporter;
const INTERFACE_DEFINITIONS: &'static str = r#"
export declare function transform(src: string | Uint8Array, opts?: Options): Promise<TransformOutput>;
export declare function transformSync(src: string | Uint8Array, opts?: Options): TransformOutput;
export type { Options, TransformOutput };
export declare function parse(src: string | Uint8Array, opts?: Options): Promise<Program>;
export declare function parseSync(src: string | Uint8Array, opts?: Options): Program;
export type { Options, TransformOutput, Program };

interface Program {
type: "Module" | "Script";
span: Span;
body: Node[];
[key: string]: unknown;
}

interface Node {
type: string;
span: Span;
[key: string]: unknown;
}

interface Span {
start: number;
end: number;
}
"#;

#[wasm_bindgen(skip_typescript)]
Expand All @@ -37,32 +60,61 @@ pub fn transform(input: JsValue, options: JsValue) -> Promise {

#[wasm_bindgen(js_name = "transformSync", skip_typescript)]
pub fn transform_sync(input: JsValue, options: JsValue) -> Result<JsValue, JsValue> {
let options: Options = if options.is_falsy() {
let options = deserialize_options(options)?;
let input = coerce_input(input)?;

let result = GLOBALS.set(&Default::default(), || operate(input, options));

match result {
Ok(v) => Ok(serde_wasm_bindgen::to_value(&v)?),
Err(errors) => Err(serde_wasm_bindgen::to_value(&errors[0])?),
}
}

#[wasm_bindgen(skip_typescript)]
pub fn parse(input: JsValue, options: JsValue) -> Promise {
future_to_promise(async move { parse_sync(input, options) })
}

#[wasm_bindgen(js_name = "parseSync", skip_typescript)]
pub fn parse_sync(input: JsValue, options: JsValue) -> Result<JsValue, JsValue> {
let options = deserialize_options(options)?;
let input = coerce_input(input)?;

let result = GLOBALS.set(&Default::default(), || parse_program(input, options));

match result {
Ok(v) => Ok(serde_wasm_bindgen::to_value(&v)?),
Err(errors) => Err(serde_wasm_bindgen::to_value(&errors[0])?),
}
}

/// Deserialize the JS options object shared by every binding, falling back to
/// the defaults when nothing (or a falsy value) is provided.
fn deserialize_options(options: JsValue) -> Result<Options, JsValue> {
Ok(if options.is_falsy() {
Default::default()
} else {
serde_wasm_bindgen::from_value(options)?
};
})
}

let input = match input.as_string() {
Some(input) => input,
/// Coerce the JS input into a UTF-8 string, accepting either a string or a
/// `Uint8Array` as every binding does.
fn coerce_input(input: JsValue) -> Result<String, JsValue> {
match input.as_string() {
Some(input) => Ok(input),
None => {
if input.is_instance_of::<Uint8Array>() {
let input = input.unchecked_into::<Uint8Array>();
match input.to_string().as_string() {
Some(input) => input,
None => return Err(JsValue::from_str("Input Uint8Array is not valid utf-8")),
Some(input) => Ok(input),
None => Err(JsValue::from_str("Input Uint8Array is not valid utf-8")),
}
} else {
return Err(JsValue::from_str("Input is not a string or Uint8Array"));
Err(JsValue::from_str("Input is not a string or Uint8Array"))
}
}
};

let result = GLOBALS.set(&Default::default(), || operate(input, options));

match result {
Ok(v) => Ok(serde_wasm_bindgen::to_value(&v)?),
Err(errors) => Err(serde_wasm_bindgen::to_value(&errors[0])?),
}
}

Expand All @@ -74,6 +126,51 @@ fn operate(input: String, options: Options) -> Result<TransformOutput, Vec<JsonD
})
}

fn parse_program(input: String, options: Options) -> Result<Program, Vec<JsonDiagnostic>> {
let cm = Lrc::new(SourceMap::default());

try_with_json_handler(cm.clone(), |handler| {
let filename = options
.filename
.map_or(FileName::Anon, |f| FileName::Real(f.into()));
let fm = cm.new_source_file(filename.into(), input);

let syntax = Syntax::Typescript(options.parser);
let target = EsVersion::latest();
let comments = SingleThreadedComments::default();

let lexer = Lexer::new(syntax, target, StringInput::from(&*fm), Some(&comments));
let mut parser = Parser::new_from(lexer);

let program = match options.module {
Some(true) => parser.parse_module().map(Program::Module),
Some(false) => parser.parse_script().map(Program::Script),
None => parser.parse_program(),
};
let errors = parser.take_errors();

// A fatal parse error aborts before any AST is produced; emit it as an
// `InvalidSyntax` diagnostic so it surfaces through the shared handler.
let program = program.map_err(|err| {
err.into_diagnostic(handler)
.code(DiagnosticId::Error("InvalidSyntax".into()))
.emit();
anyhow::Error::msg("Syntax error")
})?;

// Recoverable errors still yield an AST, but we report them the same way
// `operate` does; `try_with_json_handler` turns any emitted diagnostic
// into a failed result.
for e in errors {
e.into_diagnostic(handler)
.code(DiagnosticId::Error("InvalidSyntax".into()))
.emit();
}

Ok(program)
})
}

#[derive(Clone)]
struct JsonErrorWriter {
errors: Arc<Mutex<Vec<JsonDiagnostic>>>,
Expand Down
22 changes: 21 additions & 1 deletion lib/wasm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,27 @@

export declare function transform(src: string | Uint8Array, opts?: Options): Promise<TransformOutput>;
export declare function transformSync(src: string | Uint8Array, opts?: Options): TransformOutput;
export type { Options, TransformOutput };
export declare function parse(src: string | Uint8Array, opts?: Options): Promise<Program>;
export declare function parseSync(src: string | Uint8Array, opts?: Options): Program;
export type { Options, TransformOutput, Program };

interface Program {
type: "Module" | "Script";
span: Span;
body: Node[];
[key: string]: unknown;
}

interface Node {
type: string;
span: Span;
[key: string]: unknown;
}

interface Span {
start: number;
end: number;
}



Expand Down
51 changes: 46 additions & 5 deletions lib/wasm.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions lib/wasm_bg.wasm.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export const parse: (a: number, b: number) => number;
export const parseSync: (a: number, b: number, c: number) => void;
export const transform: (a: number, b: number) => number;
export const transformSync: (a: number, b: number, c: number) => void;
export const __wbindgen_export_0: (a: number) => void;
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { transformSync } from "./transform.ts";
export { parseSync } from "./parse.ts";
8 changes: 8 additions & 0 deletions src/parse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Options, Program } from "../lib/wasm";
import swc from "../lib/wasm.js";

export function parseSync(source: string, options?: Options): Program {
// Ensure that the source code is a string
const input = `${source ?? ""}`;
return swc.parseSync(input, options);
}
Loading
Loading