Skip to content

Commit 7e4d04f

Browse files
committed
feat(napi/parser): add option to add parent prop to AST nodes with raw transfer (#14344)
Add an option to `oxc-parser` to add `parent` to all AST nodes. This option is only available with raw transfer (not really feasible with JSON transfer, as JSON doesn't support circular references). This is really for the purposes of JS plugins in Oxlint, so have not included the new deserializers which add `parent` in the `oxc-parser` NPM package, to avoid bloating download size. But it's ideal home is in `napi/parser` so can add it to the conformance tests. `parent` is correct for all nodes in all Test262 and TypeScript test cases.
1 parent 08f14e6 commit 7e4d04f

File tree

26 files changed

+38686
-7483
lines changed

26 files changed

+38686
-7483
lines changed

.github/generated/ast_changes_watch_list.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,14 @@ src:
6868
- 'crates/oxc_traverse/src/generated/scopes_collector.rs'
6969
- 'napi/parser/generated/constants.js'
7070
- 'napi/parser/generated/deserialize/js.js'
71+
- 'napi/parser/generated/deserialize/js_parent.js'
7172
- 'napi/parser/generated/deserialize/js_range.js'
73+
- 'napi/parser/generated/deserialize/js_range_parent.js'
7274
- 'napi/parser/generated/deserialize/ts.js'
75+
- 'napi/parser/generated/deserialize/ts_parent.js'
7376
- 'napi/parser/generated/deserialize/ts_range.js'
7477
- 'napi/parser/generated/deserialize/ts_range_no_parens.js'
78+
- 'napi/parser/generated/deserialize/ts_range_parent.js'
7579
- 'napi/parser/generated/lazy/constructors.js'
7680
- 'napi/parser/generated/lazy/types.js'
7781
- 'napi/parser/generated/lazy/walk.js'

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ jobs:
196196
- if: steps.filter.outputs.src == 'true'
197197
name: Run tests in workspace
198198
env:
199-
RUN_RAW_TESTS: "true"
199+
RUN_RAW_RANGE_TESTS: "true"
200200
run: |
201201
rustup target add wasm32-wasip1-threads
202202
pnpm run build-test

crates/oxc_ast/src/serialize/js.rs

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ use super::{EmptyArray, Null};
2020
#[estree(raw_deser = "
2121
const pattern = DESER[BindingPatternKind](POS_OFFSET.kind);
2222
if (IS_TS) {
23+
const previousParent = parent;
24+
parent = pattern;
2325
pattern.optional = DESER[bool](POS_OFFSET.optional);
2426
pattern.typeAnnotation = DESER[Option<Box<TSTypeAnnotation>>](POS_OFFSET.type_annotation);
27+
if (PARENT) parent = previousParent;
2528
}
2629
pattern
2730
")]
@@ -119,22 +122,31 @@ impl ESTree for CatchParameterConverter<'_, '_> {
119122
const params = DESER[Vec<FormalParameter>](POS_OFFSET.items);
120123
if (uint32[(POS_OFFSET.rest) >> 2] !== 0 && uint32[(POS_OFFSET.rest + 4) >> 2] !== 0) {
121124
pos = uint32[(POS_OFFSET.rest) >> 2];
125+
122126
let start, end;
123-
params.push({
127+
const previousParent = parent;
128+
const rest = parent = {
124129
type: 'RestElement',
125130
...(IS_TS && { decorators: [] }),
126-
argument: DESER[BindingPatternKind]( POS_OFFSET<BindingRestElement>.argument.kind ),
131+
argument: null,
127132
...(IS_TS && {
128133
optional: DESER[bool]( POS_OFFSET<BindingRestElement>.argument.optional ),
129-
typeAnnotation: DESER[Option<Box<TSTypeAnnotation>>](
130-
POS_OFFSET<BindingRestElement>.argument.type_annotation
131-
),
134+
typeAnnotation: null,
132135
value: null,
133136
}),
134137
start: start = DESER[u32]( POS_OFFSET<BindingRestElement>.span.start ),
135138
end: end = DESER[u32]( POS_OFFSET<BindingRestElement>.span.end ),
136139
...(RANGE && { range: [start, end] }),
137-
});
140+
...(PARENT && { parent }),
141+
};
142+
rest.argument = DESER[BindingPatternKind]( POS_OFFSET<BindingRestElement>.argument.kind );
143+
if (IS_TS) {
144+
rest.typeAnnotation = DESER[Option<Box<TSTypeAnnotation>>](
145+
POS_OFFSET<BindingRestElement>.argument.type_annotation
146+
);
147+
}
148+
params.push(rest);
149+
if (PARENT) parent = previousParent;
138150
}
139151
params
140152
"
@@ -187,27 +199,32 @@ impl ESTree for FormalParametersRest<'_, '_> {
187199
if (IS_TS) {
188200
const accessibility = DESER[Option<TSAccessibility>](POS_OFFSET.accessibility),
189201
readonly = DESER[bool](POS_OFFSET.readonly),
190-
override = DESER[bool](POS_OFFSET.override);
202+
override = DESER[bool](POS_OFFSET.override),
203+
previousParent = parent;
191204
if (accessibility === null && !readonly && !override) {
192-
param = DESER[BindingPatternKind](POS_OFFSET.pattern.kind);
205+
param = parent = DESER[BindingPatternKind](POS_OFFSET.pattern.kind);
193206
param.decorators = DESER[Vec<Decorator>](POS_OFFSET.decorators);
194207
param.optional = DESER[bool](POS_OFFSET.pattern.optional);
195208
param.typeAnnotation = DESER[Option<Box<TSTypeAnnotation>>](POS_OFFSET.pattern.type_annotation);
196209
} else {
197210
let start, end;
198-
param = {
211+
param = parent = {
199212
type: 'TSParameterProperty',
200213
accessibility,
201-
decorators: DESER[Vec<Decorator>](POS_OFFSET.decorators),
214+
decorators: null,
202215
override,
203-
parameter: DESER[BindingPattern](POS_OFFSET.pattern),
216+
parameter: null,
204217
readonly,
205218
static: false,
206219
start: start = DESER[u32]( POS_OFFSET<BindingRestElement>.span.start ),
207220
end: end = DESER[u32]( POS_OFFSET<BindingRestElement>.span.end ),
208221
...(RANGE && { range: [start, end] }),
222+
...(PARENT && { parent }),
209223
};
224+
param.decorators = DESER[Vec<Decorator>](POS_OFFSET.decorators);
225+
param.parameter = DESER[BindingPattern](POS_OFFSET.pattern);
210226
}
227+
if (PARENT) parent = previousParent;
211228
} else {
212229
param = DESER[BindingPatternKind](POS_OFFSET.pattern.kind);
213230
}
@@ -384,7 +401,11 @@ impl ESTree for ExportAllDeclarationWithClause<'_, '_> {
384401
ts_type = "FunctionBody | Expression",
385402
raw_deser = "
386403
let body = DESER[Box<FunctionBody>](POS_OFFSET.body);
387-
THIS.expression ? body.body[0].expression : body
404+
if (THIS.expression === true) {
405+
body = body.body[0].expression;
406+
if (PARENT) body.parent = parent;
407+
}
408+
body
388409
"
389410
)]
390411
pub struct ArrowFunctionExpressionBody<'a>(pub &'a ArrowFunctionExpression<'a>);
@@ -405,23 +426,31 @@ impl ESTree for ArrowFunctionExpressionBody<'_> {
405426
#[estree(
406427
ts_type = "IdentifierReference | AssignmentTargetWithDefault",
407428
raw_deser = "
408-
const init = DESER[Option<Expression>](POS_OFFSET.init),
409-
keyCopy = { ...THIS.key },
410-
value = init === null
411-
? keyCopy
412-
: {
413-
type: 'AssignmentPattern',
414-
...(IS_TS && { decorators: [] }),
415-
left: keyCopy,
416-
right: init,
417-
...(IS_TS && {
418-
optional: false,
419-
typeAnnotation: null,
420-
}),
421-
start: THIS.start,
422-
end: THIS.end,
423-
...(RANGE && { range: [THIS.start, THIS.end] }),
424-
};
429+
const init = DESER[Option<Expression>](POS_OFFSET.init);
430+
let value = { ...THIS.key };
431+
if (init !== null) {
432+
const left = value;
433+
const previousParent = parent;
434+
value = parent = {
435+
type: 'AssignmentPattern',
436+
...(IS_TS && { decorators: [] }),
437+
left,
438+
right: init,
439+
...(IS_TS && {
440+
optional: false,
441+
typeAnnotation: null,
442+
}),
443+
start: THIS.start,
444+
end: THIS.end,
445+
...(RANGE && { range: [THIS.start, THIS.end] }),
446+
...(PARENT && { parent }),
447+
};
448+
if (PARENT) {
449+
left.parent = value;
450+
init.parent = value;
451+
parent = previousParent;
452+
}
453+
}
425454
value
426455
"
427456
)]
@@ -458,16 +487,22 @@ impl ESTree for AssignmentTargetPropertyIdentifierInit<'_> {
458487
/// ESTree implementation is unchanged from the auto-generated version.
459488
#[ast_meta]
460489
#[estree(raw_deser = "
461-
let node = DESER[Expression](POS_OFFSET.expression);
490+
let node;
462491
if (PRESERVE_PARENS) {
463492
let start, end;
464-
node = {
493+
const previousParent = parent;
494+
node = parent = {
465495
type: 'ParenthesizedExpression',
466-
expression: node,
496+
expression: null,
467497
start: start = DESER[u32]( POS_OFFSET.span.start ),
468498
end: end = DESER[u32]( POS_OFFSET.span.end ),
469499
...(RANGE && { range: [start, end] }),
500+
...(PARENT && { parent }),
470501
};
502+
node.expression = DESER[Expression](POS_OFFSET.expression);
503+
if (PARENT) parent = previousParent;
504+
} else {
505+
node = DESER[Expression](POS_OFFSET.expression);
471506
}
472507
node
473508
")]

crates/oxc_ast/src/serialize/jsx.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl ESTree for JSXOpeningElementSelfClosing<'_, '_> {
5656
ts_type = "JSXIdentifier",
5757
raw_deser = "
5858
const ident = DESER[Box<IdentifierReference>](POS);
59-
{ type: 'JSXIdentifier', name: ident.name, start: ident.start, end: ident.end, ...(RANGE && { range: ident.range }) }
59+
{ type: 'JSXIdentifier', name: ident.name, start: ident.start, end: ident.end, ...(RANGE && { range: ident.range }), ...(PARENT && { parent }) }
6060
"
6161
)]
6262
pub struct JSXElementIdentifierReference<'a, 'b>(pub &'b IdentifierReference<'a>);
@@ -75,7 +75,7 @@ impl ESTree for JSXElementIdentifierReference<'_, '_> {
7575
ts_type = "JSXIdentifier",
7676
raw_deser = "
7777
const thisExpr = DESER[Box<ThisExpression>](POS);
78-
{ type: 'JSXIdentifier', name: 'this', start: thisExpr.start, end: thisExpr.end, ...(RANGE && { range: thisExpr.range }) }
78+
{ type: 'JSXIdentifier', name: 'this', start: thisExpr.start, end: thisExpr.end, ...(RANGE && { range: thisExpr.range }), ...(PARENT && { parent }) }
7979
"
8080
)]
8181
pub struct JSXElementThisExpression<'b>(pub &'b ThisExpression);

crates/oxc_ast/src/serialize/literal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ impl ESTree for RegExpFlagsConverter<'_> {
202202
value.cooked = value.cooked
203203
.replace(/\uFFFD(.{4})/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16)));
204204
}
205-
{ type: 'TemplateElement', value, tail, start, end, ...(RANGE && { range: [start, end] }) }
205+
{ type: 'TemplateElement', value, tail, start, end, ...(RANGE && { range: [start, end] }), ...(PARENT && { parent }) }
206206
"#)]
207207
pub struct TemplateElementConverter<'a, 'b>(pub &'b TemplateElement<'a>);
208208

crates/oxc_ast/src/serialize/mod.rs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,27 @@ impl Program<'_> {
121121
/// `Program` span start is 0 (not 5).
122122
#[ast_meta]
123123
#[estree(raw_deser = "
124-
const body = DESER[Vec<Directive>](POS_OFFSET.directives);
125-
body.push(...DESER[Vec<Statement>](POS_OFFSET.body));
124+
const start = IS_TS ? 0 : DESER[u32](POS_OFFSET.span.start),
125+
end = DESER[u32](POS_OFFSET.span.end);
126+
127+
const program = parent = {
128+
type: 'Program',
129+
body: null,
130+
sourceType: DESER[ModuleKind](POS_OFFSET.source_type.module_kind),
131+
hashbang: null,
132+
start,
133+
end,
134+
...(RANGE && { range: [start, end] }),
135+
...(PARENT && { parent: null }),
136+
};
126137
127-
const end = DESER[u32](POS_OFFSET.span.end);
138+
program.hashbang = DESER[Option<Hashbang>](POS_OFFSET.hashbang);
139+
140+
const body = program.body = DESER[Vec<Directive>](POS_OFFSET.directives);
141+
body.push(...DESER[Vec<Statement>](POS_OFFSET.body));
128142
129-
let start;
130143
if (IS_TS) {
144+
let start;
131145
if (body.length > 0) {
132146
const first = body[0];
133147
start = first.start;
@@ -144,19 +158,16 @@ impl Program<'_> {
144158
} else {
145159
start = end;
146160
}
147-
} else {
148-
start = DESER[u32](POS_OFFSET.span.start);
161+
162+
if (RANGE) {
163+
program.start = program.range[0] = start;
164+
} else {
165+
program.start = start;
166+
}
149167
}
150168
151-
const program = {
152-
type: 'Program',
153-
body,
154-
sourceType: DESER[ModuleKind](POS_OFFSET.source_type.module_kind),
155-
hashbang: DESER[Option<Hashbang>](POS_OFFSET.hashbang),
156-
start,
157-
end,
158-
...(RANGE && { range: [start, end] }),
159-
};
169+
if (PARENT) parent = null;
170+
160171
program
161172
")]
162173
pub struct ProgramConverter<'a, 'b>(pub &'b Program<'a>);

0 commit comments

Comments
 (0)