diff --git a/napi/parser/Cargo.toml b/napi/parser/Cargo.toml index f17bec23d04dc4..1a9f944eeba4ac 100644 --- a/napi/parser/Cargo.toml +++ b/napi/parser/Cargo.toml @@ -27,6 +27,7 @@ oxc_napi = { workspace = true } rustc-hash = { workspace = true } serde_json = { workspace = true } +# string_wizard = { versoin = "0.0.23", features = ["sourcemap"] } napi = { workspace = true, features = ["async"] } napi-derive = { workspace = true } diff --git a/napi/parser/bindings.js b/napi/parser/bindings.js index 2865f9494222eb..b03d2e44ef17ab 100644 --- a/napi/parser/bindings.js +++ b/napi/parser/bindings.js @@ -361,6 +361,8 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } +module.exports.MagicString = nativeBinding.MagicString +module.exports.ParseResult = nativeBinding.ParseResult module.exports.ExportExportNameKind = nativeBinding.ExportExportNameKind module.exports.ExportImportNameKind = nativeBinding.ExportImportNameKind module.exports.ExportLocalNameKind = nativeBinding.ExportLocalNameKind diff --git a/napi/parser/index.d.ts b/napi/parser/index.d.ts index 45cafbe02e3d24..9ed2b83ae181e7 100644 --- a/napi/parser/index.d.ts +++ b/napi/parser/index.d.ts @@ -2,6 +2,21 @@ /* eslint-disable */ export * from '@oxc-project/types'; +export declare class MagicString { + length(): number + toString(): string + append(input: string): this + prepend(input: string): this + appendLeft(index: number, input: string): this + appendRight(index: number, input: string): this + prependLeft(index: number, input: string): this + prependRight(index: number, input: string): this +} + +export declare class ParseResult { + ast(): import("@oxc-project/types").Program +} + export interface Comment { type: 'Line' | 'Block' value: string @@ -108,6 +123,10 @@ export declare const enum ImportNameKind { Default = 'Default' } +export interface OverwriteOptions { + contentOnly: boolean +} + /** * Parse asynchronously. * @@ -115,13 +134,6 @@ export declare const enum ImportNameKind { */ export declare function parseAsync(filename: string, sourceText: string, options?: ParserOptions | undefined | null): Promise -export interface ParseResult { - program: import("@oxc-project/types").Program - module: EcmaScriptModule - comments: Array - errors: Array -} - export interface ParserOptions { sourceType?: 'script' | 'module' | 'unambiguous' | undefined /** Treat the source text as `js`, `jsx`, `ts`, or `tsx`. */ diff --git a/napi/parser/src/lib.rs b/napi/parser/src/lib.rs index ab028921eb3ae7..9596451cd81cec 100644 --- a/napi/parser/src/lib.rs +++ b/napi/parser/src/lib.rs @@ -3,8 +3,11 @@ )] mod convert; +mod magic_string; mod types; +use std::mem; + use napi::{bindgen_prelude::AsyncTask, Task}; use napi_derive::napi; @@ -16,7 +19,10 @@ use oxc::{ }; use oxc_napi::Error; -pub use crate::types::{Comment, EcmaScriptModule, ParseResult, ParserOptions}; +pub use crate::{ + magic_string::MagicString, + types::{Comment, EcmaScriptModule, ParseResult, ParserOptions}, +}; fn get_source_type(filename: &str, options: &ParserOptions) -> SourceType { match options.lang.as_deref() { @@ -62,10 +68,10 @@ pub fn parse_without_return(filename: String, source_text: String, options: Opti parse(&allocator, source_type, &source_text, &options); } -fn parse_with_return(filename: &str, source_text: &str, options: &ParserOptions) -> ParseResult { +fn parse_with_return(filename: &str, source_text: String, options: &ParserOptions) -> ParseResult { let allocator = Allocator::default(); let source_type = get_source_type(filename, options); - let ret = parse(&allocator, source_type, source_text, options); + let ret = parse(&allocator, source_type, &source_text, options); let program = serde_json::to_string(&ret.program).unwrap(); let errors = ret.errors.into_iter().map(Error::from).collect::>(); @@ -79,14 +85,14 @@ fn parse_with_return(filename: &str, source_text: &str, options: &ParserOptions) CommentKind::Line => String::from("Line"), CommentKind::Block => String::from("Block"), }, - value: comment.content_span().source_text(source_text).to_string(), + value: comment.content_span().source_text(&source_text).to_string(), start: comment.span.start, end: comment.span.end, }) .collect::>(); let module = EcmaScriptModule::from(&ret.module_record); - ParseResult { program, module, comments, errors } + ParseResult { source_text, program, module, comments, errors } } /// Parse synchronously. @@ -97,7 +103,7 @@ pub fn parse_sync( options: Option, ) -> ParseResult { let options = options.unwrap_or_default(); - parse_with_return(&filename, &source_text, &options) + parse_with_return(&filename, source_text, &options) } pub struct ResolveTask { @@ -112,7 +118,8 @@ impl Task for ResolveTask { type Output = ParseResult; fn compute(&mut self) -> napi::Result { - Ok(parse_with_return(&self.filename, &self.source_text, &self.options)) + let source_text = mem::take(&mut self.source_text); + Ok(parse_with_return(&self.filename, source_text, &self.options)) } fn resolve(&mut self, _: napi::Env, result: Self::Output) -> napi::Result { diff --git a/napi/parser/src/magic_string.rs b/napi/parser/src/magic_string.rs new file mode 100644 index 00000000000000..ad31fb510de222 --- /dev/null +++ b/napi/parser/src/magic_string.rs @@ -0,0 +1,109 @@ +use napi_derive::napi; + +#[napi] +pub struct MagicString; // (string_wizard::MagicString<'static>); + +impl MagicString { + pub fn new(_s: String) -> Self { + Self //(string_wizard::MagicString::new(s)) + } +} + +#[napi(object)] +pub struct OverwriteOptions { + pub content_only: bool, +} + +#[napi] +impl MagicString { + #[napi] + pub fn length(&self) -> u32 { + // self.0.len() as u32 + unimplemented!() + } + + #[napi] + #[allow(clippy::inherent_to_string)] + pub fn to_string(&self) -> String { + // self.0.to_string() + unimplemented!() + } + + #[napi] + pub fn append(&mut self, _input: String) -> &Self { + // self.0.append(input); + // self + unimplemented!() + } + + #[napi] + pub fn prepend(&mut self, _input: String) -> &Self { + // self.0.prepend(input); + // self + unimplemented!() + } + + #[napi] + pub fn append_left(&mut self, _index: u32, _input: String) -> &Self { + // self.0.append_left(index as usize, input); + // self + unimplemented!() + } + + #[napi] + pub fn append_right(&mut self, _index: u32, _input: String) -> &Self { + // self.0.append_right(index as usize, input); + // self + unimplemented!() + } + + #[napi] + pub fn prepend_left(&mut self, _index: u32, _input: String) -> &Self { + // self.0.prepend_left(index as usize, input); + // self + unimplemented!() + } + + #[napi] + pub fn prepend_right(&mut self, _index: u32, _input: String) -> &Self { + // self.0.prepend_right(index as usize, input); + // self + unimplemented!() + } + + // #[napi] + // pub fn overwrite( + // &mut self, + // start: i64, + // end: i64, + // content: String, + // options: OverwriteOptions, + // ) -> &Self { + // self.0.overwrite(start, end, content, options); + // self + // } + + // #[napi] + // pub fn trim(&mut self, pattern: Option) -> &Self { + // self.0.trim(pattern.as_deref()); + // self + // } + + // #[napi] + // pub fn trim_start(&mut self, pattern: Option) -> &Self { + // self.0.trim_start(pattern.as_deref()); + // self + // } + + // #[napi] + // pub fn trim_end(&mut self, pattern: Option) -> &Self { + // self.0.trim_end(pattern.as_deref()); + // self + // } + + // #[napi] + // pub fn trim_lines(&mut self) -> &Self { + // self.0.trim_lines(); + // self + // } +} diff --git a/napi/parser/src/types.rs b/napi/parser/src/types.rs index 9b80619972536e..7d2bac58afd3a1 100644 --- a/napi/parser/src/types.rs +++ b/napi/parser/src/types.rs @@ -1,7 +1,10 @@ use napi_derive::napi; +use std::mem; use oxc_napi::Error; +use crate::magic_string::MagicString; + #[napi(object)] #[derive(Default)] pub struct ParserOptions { @@ -22,13 +25,38 @@ pub struct ParserOptions { pub preserve_parens: Option, } -#[napi(object)] +#[napi] pub struct ParseResult { - #[napi(ts_type = "import(\"@oxc-project/types\").Program")] - pub program: String, - pub module: EcmaScriptModule, - pub comments: Vec, - pub errors: Vec, + pub(crate) source_text: String, + // #[napi(ts_type = "import(\"@oxc-project/types\").Program")] + pub(crate) program: String, + pub(crate) module: EcmaScriptModule, + pub(crate) comments: Vec, + pub(crate) errors: Vec, +} + +#[napi] +impl ParseResult { + #[napi(ts_return_type = "import(\"@oxc-project/types\").Program")] + pub fn ast(&mut self) -> String { + mem::take(&mut self.program) + } + + pub fn module(&mut self) -> EcmaScriptModule { + mem::take(&mut self.module) + } + + pub fn comments(&mut self) -> Vec { + mem::take(&mut self.comments) + } + + pub fn errors(&mut self) -> Vec { + mem::take(&mut self.errors) + } + + pub fn magic_string(&mut self) -> MagicString { + MagicString::new(mem::take(&mut self.source_text)) + } } #[napi(object)] @@ -41,6 +69,7 @@ pub struct Comment { } #[napi(object)] +#[derive(Default)] pub struct EcmaScriptModule { /// Has ESM syntax. /// diff --git a/napi/parser/test/magic_string.ts b/napi/parser/test/magic_string.ts new file mode 100644 index 00000000000000..8d21aff582b7e0 --- /dev/null +++ b/napi/parser/test/magic_string.ts @@ -0,0 +1,26 @@ +import { assert, describe, it } from 'vitest'; + +import type { StringLiteral, VariableDeclaration } from '../index.js'; +import { parseSync } from '../index.js'; + +describe('simple', () => { + const code = 'const s: String = "测试"'; + + it('calls magic string APIs', () => { + // `oxc` holds a magic string instance on the Rust side. + const { program, magicString } = parseSync('test.ts', code); + const declaration = ast.body[0] as VariableDeclaration; + const stringLiteral = declaration.declarations[0].init as StringLiteral; + + // These spans are in utf8 offsets. + const start = stringLiteral.start + 1; + const end = stringLiteral.end - 1; + + // Access source text by utf8 offset. + assert.equal(oxc.sourceText(start, end), '测试'); + + // Magic string manipulation. + oxc.remove(start, end); + assert.equal(oxc.toString(), 'const s: String = ""'); + }); +});