Skip to content
Merged
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
9 changes: 7 additions & 2 deletions crates/oxc/src/napi/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -41,6 +45,7 @@ pub struct Comment {
}

#[napi(object)]
#[derive(Default)]
pub struct EcmaScriptModule {
/// Import Statements.
pub static_imports: Vec<StaticImport>,
Expand Down
10 changes: 5 additions & 5 deletions napi/parser/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
*
Expand All @@ -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
Expand Down
62 changes: 41 additions & 21 deletions napi/parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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)))
Expand Down Expand Up @@ -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,
}
Expand All @@ -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> {
Expand All @@ -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 })
}
6 changes: 3 additions & 3 deletions napi/parser/test/esm.test.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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();
Expand Down
12 changes: 9 additions & 3 deletions napi/parser/test/parse.test.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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);
});
});
4 changes: 2 additions & 2 deletions napi/parser/test/parser.test-d.ts
Original file line number Diff line number Diff line change
@@ -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]);
});
});