mirror of
https://github.com/danbulant/oxc
synced 2026-05-22 21:58:36 +00:00
feat(napi/parser): change parse API to accept mandatory filename and optional lang (#7605)
This commit is contained in:
parent
348d4f451a
commit
40792b4440
6 changed files with 67 additions and 36 deletions
|
|
@ -5,13 +5,16 @@ use rustc_hash::FxHashMap;
|
|||
use oxc_span::Span;
|
||||
use oxc_syntax::module_record::{self, ModuleRecord};
|
||||
|
||||
/// Babel Parser Options
|
||||
#[napi(object)]
|
||||
#[derive(Default)]
|
||||
pub struct ParserOptions {
|
||||
#[napi(ts_type = "'script' | 'module' | 'unambiguous' | undefined")]
|
||||
pub source_type: Option<String>,
|
||||
pub source_filename: Option<String>,
|
||||
|
||||
/// Treat the source text as `js`, `jsx`, `ts`, or `tsx`.
|
||||
#[napi(ts_type = "'js' | 'jsx' | 'ts' | 'tsx'")]
|
||||
pub lang: Option<String>,
|
||||
|
||||
/// Emit `ParenthesizedExpression` in AST.
|
||||
///
|
||||
/// If this option is true, parenthesized expressions are represented by
|
||||
|
|
@ -23,6 +26,7 @@ pub struct ParserOptions {
|
|||
}
|
||||
|
||||
#[napi(object)]
|
||||
#[derive(Default)]
|
||||
pub struct ParseResult {
|
||||
#[napi(ts_type = "import(\"@oxc-project/types\").Program")]
|
||||
pub program: String,
|
||||
|
|
@ -41,6 +45,7 @@ pub struct Comment {
|
|||
}
|
||||
|
||||
#[napi(object)]
|
||||
#[derive(Default)]
|
||||
pub struct EcmaScriptModule {
|
||||
/// Import Statements.
|
||||
pub static_imports: Vec<StaticImport>,
|
||||
|
|
|
|||
10
napi/parser/index.d.ts
vendored
10
napi/parser/index.d.ts
vendored
|
|
@ -136,7 +136,7 @@ export declare const enum ImportNameKind {
|
|||
*
|
||||
* Note: This function can be slower than `parseSync` due to the overhead of spawning a thread.
|
||||
*/
|
||||
export declare function parseAsync(sourceText: string, options?: ParserOptions | undefined | null): Promise<ParseResult>
|
||||
export declare function parseAsync(filename: string, sourceText: string, options?: ParserOptions | undefined | null): Promise<ParseResult>
|
||||
|
||||
export interface ParseResult {
|
||||
program: import("@oxc-project/types").Program
|
||||
|
|
@ -145,10 +145,10 @@ export interface ParseResult {
|
|||
errors: Array<string>
|
||||
}
|
||||
|
||||
/** Babel Parser Options */
|
||||
export interface ParserOptions {
|
||||
sourceType?: 'script' | 'module' | 'unambiguous' | undefined
|
||||
sourceFilename?: string
|
||||
/** Treat the source text as `js`, `jsx`, `ts`, or `tsx`. */
|
||||
lang?: 'js' | 'jsx' | 'ts' | 'tsx'
|
||||
/**
|
||||
* Emit `ParenthesizedExpression` in AST.
|
||||
*
|
||||
|
|
@ -162,14 +162,14 @@ export interface ParserOptions {
|
|||
}
|
||||
|
||||
/** Parse synchronously. */
|
||||
export declare function parseSync(sourceText: string, options?: ParserOptions | undefined | null): ParseResult
|
||||
export declare function parseSync(filename: string, sourceText: string, options?: ParserOptions | undefined | null): ParseResult
|
||||
|
||||
/**
|
||||
* Parse without returning anything.
|
||||
*
|
||||
* This is for benchmark purposes such as measuring napi communication overhead.
|
||||
*/
|
||||
export declare function parseWithoutReturn(sourceText: string, options?: ParserOptions | undefined | null): void
|
||||
export declare function parseWithoutReturn(filename: string, sourceText: string, options?: ParserOptions | undefined | null): void
|
||||
|
||||
export interface StaticExport {
|
||||
start: number
|
||||
|
|
|
|||
|
|
@ -16,21 +16,31 @@ use oxc::{
|
|||
span::SourceType,
|
||||
};
|
||||
|
||||
fn get_source_type(filename: &str, options: &ParserOptions) -> SourceType {
|
||||
match options.lang.as_deref() {
|
||||
Some("js") => SourceType::mjs(),
|
||||
Some("jsx") => SourceType::jsx(),
|
||||
Some("ts") => SourceType::ts(),
|
||||
Some("tsx") => SourceType::tsx(),
|
||||
_ => {
|
||||
let mut source_type = SourceType::from_path(filename).unwrap_or_default();
|
||||
// Force `script` or `module`
|
||||
match options.source_type.as_deref() {
|
||||
Some("script") => source_type = source_type.with_script(true),
|
||||
Some("module") => source_type = source_type.with_module(true),
|
||||
_ => {}
|
||||
}
|
||||
source_type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse<'a>(
|
||||
allocator: &'a Allocator,
|
||||
source_type: SourceType,
|
||||
source_text: &'a str,
|
||||
options: &ParserOptions,
|
||||
) -> ParserReturn<'a> {
|
||||
let source_type = options
|
||||
.source_filename
|
||||
.as_ref()
|
||||
.and_then(|name| SourceType::from_path(name).ok())
|
||||
.unwrap_or_default();
|
||||
let source_type = match options.source_type.as_deref() {
|
||||
Some("script") => source_type.with_script(true),
|
||||
Some("module") => source_type.with_module(true),
|
||||
_ => source_type,
|
||||
};
|
||||
Parser::new(allocator, source_text, source_type)
|
||||
.with_options(ParseOptions {
|
||||
preserve_parens: options.preserve_parens.unwrap_or(true),
|
||||
|
|
@ -43,22 +53,23 @@ fn parse<'a>(
|
|||
///
|
||||
/// This is for benchmark purposes such as measuring napi communication overhead.
|
||||
#[napi]
|
||||
pub fn parse_without_return(source_text: String, options: Option<ParserOptions>) {
|
||||
pub fn parse_without_return(filename: String, source_text: String, options: Option<ParserOptions>) {
|
||||
let options = options.unwrap_or_default();
|
||||
let allocator = Allocator::default();
|
||||
parse(&allocator, &source_text, &options);
|
||||
let source_type = get_source_type(&filename, &options);
|
||||
parse(&allocator, source_type, &source_text, &options);
|
||||
}
|
||||
|
||||
fn parse_with_return(source_text: &str, options: &ParserOptions) -> ParseResult {
|
||||
fn parse_with_return(filename: &str, source_text: &str, options: &ParserOptions) -> ParseResult {
|
||||
let allocator = Allocator::default();
|
||||
let ret = parse(&allocator, source_text, options);
|
||||
let source_type = get_source_type(filename, options);
|
||||
let ret = parse(&allocator, source_type, source_text, options);
|
||||
let program = serde_json::to_string(&ret.program).unwrap();
|
||||
|
||||
let errors = if ret.errors.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
let file_name = options.source_filename.clone().unwrap_or_default();
|
||||
let source = Arc::new(NamedSource::new(file_name, source_text.to_string()));
|
||||
let source = Arc::new(NamedSource::new(filename, source_text.to_string()));
|
||||
ret.errors
|
||||
.into_iter()
|
||||
.map(|diagnostic| Error::from(diagnostic).with_source_code(Arc::clone(&source)))
|
||||
|
|
@ -87,12 +98,17 @@ fn parse_with_return(source_text: &str, options: &ParserOptions) -> ParseResult
|
|||
|
||||
/// Parse synchronously.
|
||||
#[napi]
|
||||
pub fn parse_sync(source_text: String, options: Option<ParserOptions>) -> ParseResult {
|
||||
pub fn parse_sync(
|
||||
filename: String,
|
||||
source_text: String,
|
||||
options: Option<ParserOptions>,
|
||||
) -> ParseResult {
|
||||
let options = options.unwrap_or_default();
|
||||
parse_with_return(&source_text, &options)
|
||||
parse_with_return(&filename, &source_text, &options)
|
||||
}
|
||||
|
||||
pub struct ResolveTask {
|
||||
filename: String,
|
||||
source_text: String,
|
||||
options: ParserOptions,
|
||||
}
|
||||
|
|
@ -103,7 +119,7 @@ impl Task for ResolveTask {
|
|||
type Output = ParseResult;
|
||||
|
||||
fn compute(&mut self) -> napi::Result<Self::Output> {
|
||||
Ok(parse_with_return(&self.source_text, &self.options))
|
||||
Ok(parse_with_return(&self.filename, &self.source_text, &self.options))
|
||||
}
|
||||
|
||||
fn resolve(&mut self, _: napi::Env, result: Self::Output) -> napi::Result<Self::JsValue> {
|
||||
|
|
@ -115,7 +131,11 @@ impl Task for ResolveTask {
|
|||
///
|
||||
/// Note: This function can be slower than `parseSync` due to the overhead of spawning a thread.
|
||||
#[napi]
|
||||
pub fn parse_async(source_text: String, options: Option<ParserOptions>) -> AsyncTask<ResolveTask> {
|
||||
pub fn parse_async(
|
||||
filename: String,
|
||||
source_text: String,
|
||||
options: Option<ParserOptions>,
|
||||
) -> AsyncTask<ResolveTask> {
|
||||
let options = options.unwrap_or_default();
|
||||
AsyncTask::new(ResolveTask { source_text, options })
|
||||
AsyncTask::new(ResolveTask { filename, source_text, options })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it, test } from 'vitest';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import * as oxc from '../index.js';
|
||||
import { parseSync } from '../index.js';
|
||||
|
||||
describe('esm', () => {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#syntax
|
||||
|
|
@ -48,7 +48,7 @@ export { default as name1 } from "module-name";
|
|||
`.split('\n').map((s) => s.trim()).filter(Boolean);
|
||||
|
||||
test.each(code)('%s', (s) => {
|
||||
const ret = oxc.parseSync(s, { sourceFilename: 'test.ts' });
|
||||
const ret = parseSync('test.js', s);
|
||||
expect(ret.program.body.length).toBeGreaterThan(0);
|
||||
expect(ret.errors.length).toBe(0);
|
||||
expect(JSON.stringify(ret.module, null, 2)).toMatchSnapshot();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import * as oxc from '../index.js';
|
||||
import { parseAsync, parseSync } from '../index.js';
|
||||
|
||||
describe('parse', () => {
|
||||
const code = '/* comment */ foo';
|
||||
|
||||
it('uses the `lang` option', () => {
|
||||
const ret = parseSync('test.vue', code, { lang: 'ts' });
|
||||
expect(ret.program.body.length).toBe(1);
|
||||
expect(ret.errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('matches output', async () => {
|
||||
const ret = oxc.parseSync(code);
|
||||
const ret = await parseAsync('test.js', code);
|
||||
expect(ret.program.body.length).toBe(1);
|
||||
expect(ret.errors.length).toBe(0);
|
||||
expect(ret.comments.length).toBe(1);
|
||||
|
|
@ -20,7 +26,7 @@ describe('parse', () => {
|
|||
});
|
||||
expect(code.substring(comment.start, comment.end)).toBe('/*' + comment.value + '*/');
|
||||
|
||||
const ret2 = await oxc.parseAsync(code);
|
||||
const ret2 = await parseAsync('test.js', code);
|
||||
expect(ret).toEqual(ret2);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { assertType, describe, it } from 'vitest';
|
||||
|
||||
import type { Statement } from '../index';
|
||||
import * as oxc from '../index';
|
||||
import { parseSync } from '../index';
|
||||
|
||||
describe('parse', () => {
|
||||
const code = '/* comment */ foo';
|
||||
|
||||
it('checks type', async () => {
|
||||
const ret = oxc.parseSync(code);
|
||||
const ret = parseSync('test.js', code);
|
||||
assertType<Statement>(ret.program.body[0]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue