Skip to content

Commit

Permalink
feat(napi/parser): add source map API (#8584)
Browse files Browse the repository at this point in the history
Boshen authored Jan 18, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 62f1881 commit 1bef911
Showing 7 changed files with 152 additions and 19 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion napi/parser/Cargo.toml
Original file line number Diff line number Diff line change
@@ -25,12 +25,12 @@ oxc = { workspace = true }
oxc_ast = { workspace = true, features = ["serialize"] } # enable feature only
oxc_data_structures = { workspace = true }
oxc_napi = { workspace = true }
oxc_sourcemap = { workspace = true, features = ["napi"] }

rustc-hash = { workspace = true }
self_cell = { workspace = true }
serde_json = { workspace = true }
string_wizard = { workspace = true, features = ["sourcemap", "serde"] }
# oxc_sourcemap = { workspace = true, features = ["napi"] }

napi = { workspace = true, features = ["async"] }
napi-derive = { workspace = true }
34 changes: 34 additions & 0 deletions napi/parser/index.d.ts
Original file line number Diff line number Diff line change
@@ -20,6 +20,20 @@ export declare class MagicString {
prependRight(index: number, input: string): this
relocate(start: number, end: number, to: number): this
remove(start: number, end: number): this
generateMap(options?: Partial<GenerateDecodedMapOptions>): {
toString: () => string;
toUrl: () => string;
toMap: () => {
file?: string
mappings: string
names: Array<string>
sourceRoot?: string
sources: Array<string>
sourcesContent?: Array<string>
version: number
x_google_ignoreList?: Array<number>
}
}
}

export declare class ParseResult {
@@ -121,6 +135,15 @@ export declare const enum ExportLocalNameKind {
None = 'None'
}

export interface GenerateDecodedMapOptions {
/** The filename of the file containing the original source. */
source?: string
/** Whether to include the original content in the map's `sourcesContent` array. */
includeContent: boolean
/** Whether the mapping should be high-resolution. */
hires: boolean | 'boundary'
}

export interface ImportName {
kind: ImportNameKind
name?: string
@@ -192,6 +215,17 @@ export declare const enum Severity {
Advice = 'Advice'
}

export interface SourceMap {
file?: string
mappings: string
names: Array<string>
sourceRoot?: string
sources: Array<string>
sourcesContent?: Array<string>
version: number
x_google_ignoreList?: Array<number>
}

export interface SourceMapOptions {
includeContent?: boolean
source?: string
8 changes: 7 additions & 1 deletion napi/parser/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const bindings = require('./bindings.js');

module.exports.MagicString = bindings.MagicString;
module.exports.ParseResult = bindings.ParseResult;
module.exports.ExportExportNameKind = bindings.ExportExportNameKind;
module.exports.ExportImportNameKind = bindings.ExportImportNameKind;
@@ -30,6 +29,13 @@ function wrap(result) {
},
get magicString() {
if (!magicString) magicString = result.magicString;
magicString.generateMap = function generateMap(options) {
return {
toString: () => magicString.toSourcemapString(options),
toUrl: () => magicString.toSourcemapUrl(options),
toMap: () => magicString.toSourcemapObject(options),
};
};
return magicString;
},
};
102 changes: 88 additions & 14 deletions napi/parser/src/magic_string.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#![allow(clippy::cast_possible_truncation)]
// use std::sync::Arc;
use std::sync::Arc;

use napi::Either;
use napi_derive::napi;
use self_cell::self_cell;
use string_wizard::MagicString as MS;
use string_wizard::{Hires, MagicString as MS};

use oxc_data_structures::rope::{get_line_column, Rope};
// use oxc_sourcemap::napi::SourceMap;

#[napi]
pub struct MagicString {
@@ -49,6 +49,43 @@ pub struct SourceMapOptions {
pub hires: Option<bool>,
}

#[napi(object)]
pub struct GenerateDecodedMapOptions {
/// The filename of the file containing the original source.
pub source: Option<String>,
/// Whether to include the original content in the map's `sourcesContent` array.
pub include_content: bool,
/// Whether the mapping should be high-resolution.
#[napi(ts_type = "boolean | 'boundary'")]
pub hires: Either<bool, String>,
}

impl Default for GenerateDecodedMapOptions {
fn default() -> Self {
Self { source: None, include_content: false, hires: Either::A(false) }
}
}

impl From<GenerateDecodedMapOptions> for string_wizard::SourceMapOptions {
fn from(o: GenerateDecodedMapOptions) -> Self {
Self {
source: Arc::from(o.source.unwrap_or_default()),
include_content: o.include_content,
hires: match o.hires {
Either::A(true) => Hires::True,
Either::A(false) => Hires::False,
Either::B(s) => {
if s == "boundary" {
Hires::Boundary
} else {
Hires::False
}
}
},
}
}
}

#[napi]
impl MagicString {
/// Get source text from utf8 offset.
@@ -85,17 +122,6 @@ impl MagicString {
self.cell.borrow_dependent().to_string()
}

// #[napi]
// pub fn source_map(&self, options: Option<SourceMapOptions>) -> SourceMap {
// let options = options.map(|o| string_wizard::SourceMapOptions {
// include_content: o.include_content.unwrap_or_default(),
// source: o.source.map(Arc::from).unwrap_or_default(),
// hires: o.hires.unwrap_or_default(),
// });
// let map = self.cell.borrow_dependent().source_map(options.unwrap_or_default());
// oxc_sourcemap::napi::SourceMap::from(map)
// }

#[napi]
pub fn append(&mut self, input: String) -> &Self {
self.cell.with_dependent_mut(|_, ms| {
@@ -167,4 +193,52 @@ impl MagicString {
});
self
}

#[napi(
ts_args_type = "options?: Partial<GenerateDecodedMapOptions>",
ts_return_type = r"{
toString: () => string;
toUrl: () => string;
toMap: () => {
file?: string
mappings: string
names: Array<string>
sourceRoot?: string
sources: Array<string>
sourcesContent?: Array<string>
version: number
x_google_ignoreList?: Array<number>
}
}"
)]
pub fn generate_map(&self) {
// only for .d.ts generation
}

#[napi(skip_typescript)]
pub fn to_sourcemap_string(&self, options: Option<GenerateDecodedMapOptions>) -> String {
self.get_sourcemap(options).to_json_string()
}

#[napi(skip_typescript)]
pub fn to_sourcemap_url(&self, options: Option<GenerateDecodedMapOptions>) -> String {
self.get_sourcemap(options).to_data_url()
}

#[napi(skip_typescript)]
pub fn to_sourcemap_object(
&self,
options: Option<GenerateDecodedMapOptions>,
) -> oxc_sourcemap::napi::SourceMap {
oxc_sourcemap::napi::SourceMap::from(self.get_sourcemap(options))
}

fn get_sourcemap(
&self,
options: Option<GenerateDecodedMapOptions>,
) -> oxc_sourcemap::SourceMap {
self.cell
.borrow_dependent()
.source_map(string_wizard::SourceMapOptions::from(options.unwrap_or_default()))
}
}
21 changes: 21 additions & 0 deletions napi/parser/test/magic_string.test.ts
Original file line number Diff line number Diff line change
@@ -32,4 +32,25 @@ describe('simple', () => {
ms.remove(start, end).append(';');
expect(ms.toString()).toEqual('const s: String = /* 🤨 */ "";');
});

it('returns sourcemap', () => {
const { magicString: ms } = parseSync('test.ts', code);
ms.indent();
const map = ms.generateMap({
source: 'test.ts',
includeContent: true,
hires: true,
});
expect(map.toUrl()).toBeTypeOf('string');
expect(map.toString()).toBeTypeOf('string');
console.log(map.toMap());
expect(map.toMap()).toEqual({
mappings:
'CAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC',
names: [],
sources: ['test.ts'],
sourcesContent: ['const s: String = /* 🤨 */ "测试"'],
version: 3,
});
});
});
3 changes: 0 additions & 3 deletions napi/parser/test/parse.test.ts
Original file line number Diff line number Diff line change
@@ -25,9 +25,6 @@ describe('parse', () => {
'value': ' comment ',
});
expect(code.substring(comment.start, comment.end)).toBe('/*' + comment.value + '*/');

const ret2 = await parseAsync('test.js', code);
expect(ret).toEqual(ret2);
});
});

0 comments on commit 1bef911

Please sign in to comment.