mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(napi/parser): add source map API (#8584)
This commit is contained in:
parent
62f1881ec4
commit
1bef911e59
7 changed files with 152 additions and 19 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1947,6 +1947,7 @@ dependencies = [
|
|||
"oxc_ast",
|
||||
"oxc_data_structures",
|
||||
"oxc_napi",
|
||||
"oxc_sourcemap",
|
||||
"rustc-hash",
|
||||
"self_cell",
|
||||
"serde_json",
|
||||
|
|
|
|||
|
|
@ -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
napi/parser/index.d.ts
vendored
34
napi/parser/index.d.ts
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue