Skip to content

Commit

Permalink
feat(napi/parser): change parse API to accept mandatory filename an…
Browse files Browse the repository at this point in the history
…d optional `lang` (#7605)
  • Loading branch information
Boshen committed Dec 3, 2024
1 parent 348d4f4 commit 40792b4
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 36 deletions.
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]);
});
});

0 comments on commit 40792b4

Please sign in to comment.