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
1 change: 1 addition & 0 deletions crates/oxc_ast/src/ast/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,7 @@ pub enum ChainElement<'a> {
#[ast(visit)]
#[derive(Debug)]
#[generate_derive(CloneIn, Dummy, TakeIn, GetSpan, GetSpanMut, ContentEq, ESTree)]
#[estree(via = ParenthesizedExpressionConverter)]
pub struct ParenthesizedExpression<'a> {
pub span: Span,
pub expression: Expression<'a>,
Expand Down
6 changes: 1 addition & 5 deletions crates/oxc_ast/src/generated/derive_estree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -773,11 +773,7 @@ impl ESTree for ChainElement<'_> {

impl ESTree for ParenthesizedExpression<'_> {
fn serialize<S: Serializer>(&self, serializer: S) {
let mut state = serializer.serialize_struct();
state.serialize_field("type", &JsonSafeString("ParenthesizedExpression"));
state.serialize_field("expression", &self.expression);
state.serialize_span(self.span);
state.end();
crate::serialize::js::ParenthesizedExpressionConverter(self).serialize(serializer)
}
}

Expand Down
35 changes: 35 additions & 0 deletions crates/oxc_ast/src/serialize/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,38 @@ impl ESTree for AssignmentTargetPropertyIdentifierInit<'_> {
}
}
}

/// Converter for [`ParenthesizedExpression`].
///
/// In raw transfer, do not produce a `ParenthesizedExpression` node in AST if `preserveParens` is false.
///
/// Not useful in `oxc-parser`, as can use parser option `preserve_parens`.
/// Required for `oxlint` plugins where we run parser with `preserve_parens` set to `true`,
/// to preserve them on Rust side, but need to remove them on JS side.
///
/// ESTree implementation is unchanged from the auto-generated version.
#[ast_meta]
#[estree(raw_deser = "
let node = DESER[Expression](POS_OFFSET.expression);
if (preserveParens) {
node = {
type: 'ParenthesizedExpression',
expression: node,
start: DESER[u32]( POS_OFFSET.span.start ),
end: DESER[u32]( POS_OFFSET.span.end ),
};
}
node
")]
pub struct ParenthesizedExpressionConverter<'a, 'b>(pub &'b ParenthesizedExpression<'a>);

impl ESTree for ParenthesizedExpressionConverter<'_, '_> {
fn serialize<S: Serializer>(&self, serializer: S) {
let paren_expr = self.0;
let mut state = serializer.serialize_struct();
state.serialize_field("type", &JsonSafeString("ParenthesizedExpression"));
state.serialize_field("expression", &paren_expr.expression);
state.serialize_span(paren_expr.span);
state.end();
}
}
2 changes: 1 addition & 1 deletion napi/parser/bench.bench.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ for (const { filename, code } of fixtures) {
const deserialize = isJsAst(buffer) ? deserializeJS : deserializeTS;

benchRaw('parser_napi_raw_deser_only', () => {
deserialize(buffer, code, sourceByteLen);
deserialize(buffer, code, sourceByteLen, true);
});

// oxlint-disable-next-line no-unused-vars
Expand Down
21 changes: 13 additions & 8 deletions napi/parser/generated/deserialize/js.mjs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// Auto-generated code, DO NOT EDIT DIRECTLY!
// To edit this generated file you have to edit `tasks/ast_tools/src/generators/raw_transfer.rs`.

let uint8, uint32, float64, sourceText, sourceIsAscii, sourceByteLen;
let uint8, uint32, float64, sourceText, sourceIsAscii, sourceByteLen, preserveParens;

const textDecoder = new TextDecoder('utf-8', { ignoreBOM: true }),
decodeStr = textDecoder.decode.bind(textDecoder),
{ fromCodePoint } = String;

export function deserialize(buffer, sourceTextInput, sourceByteLenInput) {
export function deserialize(buffer, sourceTextInput, sourceByteLenInput, preserveParensInput) {
uint8 = buffer;
uint32 = buffer.uint32;
float64 = buffer.float64;

sourceText = sourceTextInput;
sourceByteLen = sourceByteLenInput;
sourceIsAscii = sourceText.length === sourceByteLen;
preserveParens = preserveParensInput;

const data = deserializeRawTransferData(uint32[536870902]);

Expand Down Expand Up @@ -438,12 +439,16 @@ function deserializeChainExpression(pos) {
}

function deserializeParenthesizedExpression(pos) {
return {
type: 'ParenthesizedExpression',
expression: deserializeExpression(pos + 8),
start: deserializeU32(pos),
end: deserializeU32(pos + 4),
};
let node = deserializeExpression(pos + 8);
if (preserveParens) {
node = {
type: 'ParenthesizedExpression',
expression: node,
start: deserializeU32(pos),
end: deserializeU32(pos + 4),
};
}
return node;
}

function deserializeDirective(pos) {
Expand Down
21 changes: 13 additions & 8 deletions napi/parser/generated/deserialize/ts.mjs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// Auto-generated code, DO NOT EDIT DIRECTLY!
// To edit this generated file you have to edit `tasks/ast_tools/src/generators/raw_transfer.rs`.

let uint8, uint32, float64, sourceText, sourceIsAscii, sourceByteLen;
let uint8, uint32, float64, sourceText, sourceIsAscii, sourceByteLen, preserveParens;

const textDecoder = new TextDecoder('utf-8', { ignoreBOM: true }),
decodeStr = textDecoder.decode.bind(textDecoder),
{ fromCodePoint } = String;

export function deserialize(buffer, sourceTextInput, sourceByteLenInput) {
export function deserialize(buffer, sourceTextInput, sourceByteLenInput, preserveParensInput) {
uint8 = buffer;
uint32 = buffer.uint32;
float64 = buffer.float64;

sourceText = sourceTextInput;
sourceByteLen = sourceByteLenInput;
sourceIsAscii = sourceText.length === sourceByteLen;
preserveParens = preserveParensInput;

const data = deserializeRawTransferData(uint32[536870902]);

Expand Down Expand Up @@ -488,12 +489,16 @@ function deserializeChainExpression(pos) {
}

function deserializeParenthesizedExpression(pos) {
return {
type: 'ParenthesizedExpression',
expression: deserializeExpression(pos + 8),
start: deserializeU32(pos),
end: deserializeU32(pos + 4),
};
let node = deserializeExpression(pos + 8);
if (preserveParens) {
node = {
type: 'ParenthesizedExpression',
expression: node,
start: deserializeU32(pos),
end: deserializeU32(pos + 4),
};
}
return node;
}

function deserializeDirective(pos) {
Expand Down
11 changes: 9 additions & 2 deletions napi/parser/raw-transfer/eager.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ function deserialize(buffer, sourceText, sourceByteLen) {
let data;
if (isJsAst(buffer)) {
if (deserializeJS === null) deserializeJS = require('../generated/deserialize/js.mjs').deserialize;
data = deserializeJS(buffer, sourceText, sourceByteLen);

// `preserveParens` argument is unconditionally `true` here. If `options` contains `preserveParens: false`,
// `ParenthesizedExpression` nodes won't be in AST anyway, so the value is irrelevant.
data = deserializeJS(buffer, sourceText, sourceByteLen, true);

// Add a line comment for hashbang
const { hashbang } = data.program;
Expand All @@ -65,7 +68,11 @@ function deserialize(buffer, sourceText, sourceByteLen) {
}
} else {
if (deserializeTS === null) deserializeTS = require('../generated/deserialize/ts.mjs').deserialize;
data = deserializeTS(buffer, sourceText, sourceByteLen);

// `preserveParens` argument is unconditionally `true` here. If `options` contains `preserveParens: false`,
// `ParenthesizedExpression` nodes won't be in AST anyway, so the value is irrelevant.
data = deserializeTS(buffer, sourceText, sourceByteLen, true);

// Note: Do not add line comment for hashbang, to match `@typescript-eslint/parser`.
// See https://github.com/oxc-project/oxc/blob/ea784f5f082e4c53c98afde9bf983afd0b95e44e/napi/parser/src/lib.rs#L106-L130
}
Expand Down
42 changes: 42 additions & 0 deletions napi/parser/test/parse-raw.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,45 @@ it.concurrent('checks semantic', async () => {
ret = parseSync('test.js', code, { experimentalRawTransfer: true, showSemanticErrors: true });
expect(ret.errors.length).toBe(1);
});

describe.concurrent('`preserveParens` option', () => {
describe.concurrent('should not include parens when false', () => {
it.concurrent('JS', async () => {
const code = 'let x = (1 + 2);';

// @ts-ignore
let ret = parseSync('test.js', code, { experimentalRawTransfer: true, preserveParens: false });
expect(ret.errors.length).toBe(0);
expect(ret.program.body[0].declarations[0].init.type).toBe('BinaryExpression');
});

it.concurrent('TS', async () => {
const code = 'let x = (1 + 2);';

// @ts-ignore
let ret = parseSync('test.ts', code, { experimentalRawTransfer: true, preserveParens: false });
expect(ret.errors.length).toBe(0);
expect(ret.program.body[0].declarations[0].init.type).toBe('BinaryExpression');
});
});

describe.concurrent('should include parens when true', () => {
it.concurrent('JS', async () => {
const code = 'let x = (1 + 2);';

// @ts-ignore
let ret = parseSync('test.js', code, { experimentalRawTransfer: true, preserveParens: true });
expect(ret.errors.length).toBe(0);
expect(ret.program.body[0].declarations[0].init.type).toBe('ParenthesizedExpression');
});

it.concurrent('TS', async () => {
const code = 'let x = (1 + 2);';

// @ts-ignore
let ret = parseSync('test.ts', code, { experimentalRawTransfer: true, preserveParens: true });
expect(ret.errors.length).toBe(0);
expect(ret.program.body[0].declarations[0].init.type).toBe('ParenthesizedExpression');
});
});
});
5 changes: 3 additions & 2 deletions tasks/ast_tools/src/generators/raw_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,21 @@ fn generate_deserializers(consts: Constants, schema: &Schema, codegen: &Codegen)

#[rustfmt::skip]
let prelude = format!("
let uint8, uint32, float64, sourceText, sourceIsAscii, sourceByteLen;
let uint8, uint32, float64, sourceText, sourceIsAscii, sourceByteLen, preserveParens;

const textDecoder = new TextDecoder('utf-8', {{ ignoreBOM: true }}),
decodeStr = textDecoder.decode.bind(textDecoder),
{{ fromCodePoint }} = String;

export function deserialize(buffer, sourceTextInput, sourceByteLenInput) {{
export function deserialize(buffer, sourceTextInput, sourceByteLenInput, preserveParensInput) {{
uint8 = buffer;
uint32 = buffer.uint32;
float64 = buffer.float64;

sourceText = sourceTextInput;
sourceByteLen = sourceByteLenInput;
sourceIsAscii = sourceText.length === sourceByteLen;
preserveParens = preserveParensInput;

const data = deserializeRawTransferData(uint32[{data_pointer_pos_32}]);

Expand Down
Loading