Skip to content

Commit ef44aba

Browse files
committed
refactor(ast): override TS type defs with #[estree(custom_ts_def)] attribute on type
1 parent 6b8e955 commit ef44aba

8 files changed

Lines changed: 153 additions & 98 deletions

File tree

crates/oxc_ast/custom_types.d.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

crates/oxc_ast/src/ast/js.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ inherit_variants! {
298298
#[ast(visit)]
299299
#[derive(Debug)]
300300
#[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ESTree)]
301-
#[estree(custom_ts_def)]
301+
#[estree(no_ts_def)]
302302
pub enum ArrayExpressionElement<'a> {
303303
/// `...[3, 4]` in `const array = [1, 2, ...[3, 4], null];`
304304
SpreadElement(Box<'a, SpreadElement<'a>>) = 64,
@@ -1626,7 +1626,17 @@ pub enum FunctionType {
16261626
#[ast(visit)]
16271627
#[derive(Debug)]
16281628
#[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ESTree)]
1629-
#[estree(custom_serialize)]
1629+
#[estree(
1630+
custom_serialize,
1631+
add_ts_def = "
1632+
interface FormalParameterRest extends Span {
1633+
type: 'RestElement';
1634+
argument: BindingPatternKind;
1635+
typeAnnotation: TSTypeAnnotation | null;
1636+
optional: boolean;
1637+
}
1638+
"
1639+
)]
16301640
pub struct FormalParameters<'a> {
16311641
pub span: Span,
16321642
pub kind: FormalParameterKind,

crates/oxc_ast/src/ast/jsx.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,11 @@ pub struct JSXClosingFragment {
146146
/// JSX Element Name
147147
#[ast(visit)]
148148
#[derive(Debug)]
149-
#[generate_derive(CloneIn, GetSpan, GetSpanMut, GetAddress, ContentEq)]
149+
#[generate_derive(CloneIn, GetSpan, GetSpanMut, GetAddress, ContentEq, ESTree)]
150+
#[estree(
151+
custom_serialize,
152+
custom_ts_def = "type JSXElementName = JSXIdentifier | JSXNamespacedName | JSXMemberExpression"
153+
)]
150154
pub enum JSXElementName<'a> {
151155
/// `<div />`
152156
Identifier(Box<'a, JSXIdentifier<'a>>) = 0,
@@ -224,7 +228,11 @@ pub struct JSXMemberExpression<'a> {
224228
/// ```
225229
#[ast(visit)]
226230
#[derive(Debug)]
227-
#[generate_derive(CloneIn, GetSpan, GetSpanMut, GetAddress, ContentEq)]
231+
#[generate_derive(CloneIn, GetSpan, GetSpanMut, GetAddress, ContentEq, ESTree)]
232+
#[estree(
233+
custom_serialize,
234+
custom_ts_def = "type JSXMemberExpressionObject = JSXIdentifier | JSXMemberExpression"
235+
)]
228236
pub enum JSXMemberExpressionObject<'a> {
229237
/// `<Apple.Orange />`
230238
IdentifierReference(Box<'a, IdentifierReference<'a>>) = 0,

crates/oxc_ast/src/ast/literal.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,5 +203,29 @@ bitflags! {
203203

204204
/// Dummy type to communicate the content of `RegExpFlags` to `oxc_ast_tools`.
205205
#[ast(foreign = RegExpFlags)]
206+
#[generate_derive(ESTree)]
207+
#[estree(
208+
custom_serialize,
209+
custom_ts_def = "
210+
type RegExpFlags = {
211+
/** Global flag */
212+
G: 1;
213+
/** Ignore case flag */
214+
I: 2;
215+
/** Multiline flag */
216+
M: 4;
217+
/** DotAll flag */
218+
S: 8;
219+
/** Unicode flag */
220+
U: 16;
221+
/** Sticky flag */
222+
Y: 32;
223+
/** Indices flag */
224+
D: 64;
225+
/** Unicode sets flag */
226+
V: 128;
227+
}
228+
"
229+
)]
206230
#[expect(dead_code)]
207231
struct RegExpFlagsAlias(u8);

npm/oxc-types/types.d.ts

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,13 @@ export interface FormalParameters extends Span {
735735
items: Array<FormalParameter | FormalParameterRest>;
736736
}
737737

738+
export interface FormalParameterRest extends Span {
739+
type: 'RestElement';
740+
argument: BindingPatternKind;
741+
typeAnnotation: TSTypeAnnotation | null;
742+
optional: boolean;
743+
}
744+
738745
export interface FormalParameter extends Span {
739746
type: 'FormalParameter';
740747
decorators: Array<Decorator>;
@@ -1032,6 +1039,25 @@ export interface RegExp {
10321039

10331040
export type RegExpPattern = string | string | Pattern;
10341041

1042+
export type RegExpFlags = {
1043+
/** Global flag */
1044+
G: 1;
1045+
/** Ignore case flag */
1046+
I: 2;
1047+
/** Multiline flag */
1048+
M: 4;
1049+
/** DotAll flag */
1050+
S: 8;
1051+
/** Unicode flag */
1052+
U: 16;
1053+
/** Sticky flag */
1054+
Y: 32;
1055+
/** Indices flag */
1056+
D: 64;
1057+
/** Unicode sets flag */
1058+
V: 128;
1059+
};
1060+
10351061
export interface JSXElement extends Span {
10361062
type: 'JSXElement';
10371063
openingElement: JSXOpeningElement;
@@ -1067,6 +1093,8 @@ export interface JSXClosingFragment extends Span {
10671093
type: 'JSXClosingFragment';
10681094
}
10691095

1096+
export type JSXElementName = JSXIdentifier | JSXNamespacedName | JSXMemberExpression;
1097+
10701098
export interface JSXNamespacedName extends Span {
10711099
type: 'JSXNamespacedName';
10721100
namespace: JSXIdentifier;
@@ -1079,6 +1107,8 @@ export interface JSXMemberExpression extends Span {
10791107
property: JSXIdentifier;
10801108
}
10811109

1110+
export type JSXMemberExpressionObject = JSXIdentifier | JSXMemberExpression;
1111+
10821112
export interface JSXExpressionContainer extends Span {
10831113
type: 'JSXExpressionContainer';
10841114
expression: JSXExpression;
@@ -1941,36 +1971,3 @@ export interface NamedReference extends Span {
19411971
type: 'NamedReference';
19421972
name: string;
19431973
}
1944-
1945-
export interface FormalParameterRest extends Span {
1946-
type: 'RestElement';
1947-
argument: BindingPatternKind;
1948-
typeAnnotation: TSTypeAnnotation | null;
1949-
optional: boolean;
1950-
}
1951-
1952-
export type RegExpFlags = {
1953-
/** Global flag */
1954-
G: 1;
1955-
/** Ignore case flag */
1956-
I: 2;
1957-
/** Multiline flag */
1958-
M: 4;
1959-
/** DotAll flag */
1960-
S: 8;
1961-
/** Unicode flag */
1962-
U: 16;
1963-
/** Sticky flag */
1964-
Y: 32;
1965-
/** Indices flag */
1966-
D: 64;
1967-
/** Unicode sets flag */
1968-
V: 128;
1969-
};
1970-
1971-
export type JSXElementName =
1972-
| JSXIdentifier
1973-
| JSXNamespacedName
1974-
| JSXMemberExpression;
1975-
1976-
export type JSXMemberExpressionObject = JSXIdentifier | JSXMemberExpression;

tasks/ast_tools/src/derives/estree.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,12 @@ impl Derive for DeriveESTree {
6767
}
6868
generate_body_for_struct(struct_def, schema)
6969
}
70-
StructOrEnum::Enum(enum_def) => generate_body_for_enum(enum_def, schema),
70+
StructOrEnum::Enum(enum_def) => {
71+
if enum_def.estree.custom_serialize {
72+
return quote!();
73+
}
74+
generate_body_for_enum(enum_def, schema)
75+
}
7176
};
7277

7378
let ty = type_def.ty_anon(schema);
@@ -92,6 +97,13 @@ fn parse_estree_attr(location: AttrLocation, part: AttrPart) -> Result<()> {
9297
AttrPart::Tag("flatten") => struct_def.estree.flatten = true,
9398
AttrPart::Tag("no_type") => struct_def.estree.no_type = true,
9499
AttrPart::Tag("custom_serialize") => struct_def.estree.custom_serialize = true,
100+
AttrPart::Tag("no_ts_def") => struct_def.estree.custom_ts_def = Some(String::new()),
101+
AttrPart::String("custom_ts_def", value) => {
102+
struct_def.estree.custom_ts_def = Some(value);
103+
}
104+
AttrPart::String("add_ts_def", value) => {
105+
struct_def.estree.add_ts_def = Some(value);
106+
}
95107
AttrPart::String("rename", value) => struct_def.estree.rename = Some(value),
96108
AttrPart::String("via", value) => struct_def.estree.via = Some(value),
97109
AttrPart::String("add_ts", value) => struct_def.estree.add_ts = Some(value),
@@ -101,7 +113,12 @@ fn parse_estree_attr(location: AttrLocation, part: AttrPart) -> Result<()> {
101113
AttrLocation::Enum(enum_def) => match part {
102114
AttrPart::Tag("skip") => enum_def.estree.skip = true,
103115
AttrPart::Tag("no_rename_variants") => enum_def.estree.no_rename_variants = true,
104-
AttrPart::Tag("custom_ts_def") => enum_def.estree.custom_ts_def = true,
116+
AttrPart::Tag("custom_serialize") => enum_def.estree.custom_serialize = true,
117+
AttrPart::Tag("no_ts_def") => enum_def.estree.custom_ts_def = Some(String::new()),
118+
AttrPart::String("custom_ts_def", value) => enum_def.estree.custom_ts_def = Some(value),
119+
AttrPart::String("add_ts_def", value) => {
120+
enum_def.estree.add_ts_def = Some(value);
121+
}
105122
_ => return Err(()),
106123
},
107124
// `#[estree]` attr on struct field

tasks/ast_tools/src/generators/typescript.rs

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Generator for TypeScript type definitions for all AST types.
22
3-
use std::borrow::Cow;
3+
use std::{borrow::Cow, fmt::Write};
44

55
use itertools::Itertools;
66

@@ -16,8 +16,6 @@ use crate::{
1616

1717
use super::{attr_positions, define_generator, AttrLocation, AttrPart, AttrPositions};
1818

19-
const CUSTOM_TYPESCRIPT: &str = include_str!("../../../../crates/oxc_ast/custom_types.d.ts");
20-
2119
/// Generator for TypeScript type definitions.
2220
pub struct TypescriptGenerator;
2321

@@ -56,27 +54,49 @@ impl Generator for TypescriptGenerator {
5654

5755
let mut code = String::new();
5856
for type_def in &schema.types {
59-
if !type_def.generates_derive(estree_derive_id) {
60-
continue;
57+
if type_def.generates_derive(estree_derive_id) {
58+
generate_ts_type_def(type_def, &mut code, schema);
6159
}
60+
}
6261

63-
let ts_type_def = match type_def {
64-
TypeDef::Struct(struct_def) => generate_ts_type_def_for_struct(struct_def, schema),
65-
TypeDef::Enum(enum_def) => {
66-
let ts_type_def = generate_ts_type_def_for_enum(enum_def, schema);
67-
let Some(ts_type_def) = ts_type_def else { continue };
68-
ts_type_def
69-
}
70-
_ => unreachable!(),
71-
};
62+
Output::Javascript { path: TYPESCRIPT_DEFINITIONS_PATH.to_string(), code }
63+
}
64+
}
7265

73-
code.push_str(&ts_type_def);
74-
code.push_str("\n\n");
75-
}
66+
/// Generate Typescript type definition for a struct or enum.
67+
///
68+
/// Push type defs to `code`.
69+
fn generate_ts_type_def(type_def: &TypeDef, code: &mut String, schema: &Schema) {
70+
// Use custom TS def if provided via `#[estree(custom_ts_def = "...")]` attribute
71+
let custom_ts_def = match type_def {
72+
TypeDef::Struct(struct_def) => &struct_def.estree.custom_ts_def,
73+
TypeDef::Enum(enum_def) => &enum_def.estree.custom_ts_def,
74+
_ => unreachable!(),
75+
};
7676

77-
code.push_str(CUSTOM_TYPESCRIPT);
77+
if let Some(custom_ts_def) = custom_ts_def {
78+
// Empty string means don't output any TS def at all for this type
79+
if !custom_ts_def.is_empty() {
80+
write!(code, "export {custom_ts_def};\n\n").unwrap();
81+
}
82+
} else {
83+
// No custom definition. Generate one.
84+
let ts_def = match type_def {
85+
TypeDef::Struct(struct_def) => generate_ts_type_def_for_struct(struct_def, schema),
86+
TypeDef::Enum(enum_def) => generate_ts_type_def_for_enum(enum_def, schema),
87+
_ => unreachable!(),
88+
};
89+
write!(code, "{ts_def};\n\n").unwrap();
90+
};
7891

79-
Output::Javascript { path: TYPESCRIPT_DEFINITIONS_PATH.to_string(), code }
92+
// Add additional custom TS def if provided via `#[estree(add_ts_def = "...")]` attribute
93+
let add_ts_def = match type_def {
94+
TypeDef::Struct(struct_def) => &struct_def.estree.add_ts_def,
95+
TypeDef::Enum(enum_def) => &enum_def.estree.add_ts_def,
96+
_ => unreachable!(),
97+
};
98+
if let Some(add_ts_def) = add_ts_def {
99+
write!(code, "export {add_ts_def};\n\n").unwrap();
80100
}
81101
}
82102

@@ -163,11 +183,7 @@ fn generate_ts_type_def_for_struct(struct_def: &StructDef, schema: &Schema) -> S
163183
}
164184

165185
/// Generate Typescript type definition for an enum.
166-
fn generate_ts_type_def_for_enum(enum_def: &EnumDef, schema: &Schema) -> Option<String> {
167-
if enum_def.estree.custom_ts_def {
168-
return None;
169-
}
170-
186+
fn generate_ts_type_def_for_enum(enum_def: &EnumDef, schema: &Schema) -> String {
171187
let union = if enum_def.is_fieldless() {
172188
enum_def
173189
.all_variants(schema)
@@ -181,7 +197,7 @@ fn generate_ts_type_def_for_enum(enum_def: &EnumDef, schema: &Schema) -> Option<
181197
};
182198

183199
let enum_name = enum_def.name();
184-
Some(format!("export type {enum_name} = {union};"))
200+
format!("export type {enum_name} = {union};")
185201
}
186202

187203
/// Get TS type name for a type.

tasks/ast_tools/src/schema/extensions/estree.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,34 @@
33
pub struct ESTreeStruct {
44
pub rename: Option<String>,
55
pub via: Option<String>,
6-
pub add_ts: Option<String>,
76
pub skip: bool,
87
pub flatten: bool,
98
pub no_type: bool,
9+
/// `true` if serializer is implemented manually and should not be generated
1010
pub custom_serialize: bool,
11+
/// Additional fields to add to TS type definition
12+
pub add_ts: Option<String>,
13+
/// Custom TS type definition. Does not include `export`.
14+
/// Empty string if type should not have a TS type definition.
15+
pub custom_ts_def: Option<String>,
16+
/// Additional custom TS type definition to add along with the generated one.
17+
/// Does not include `export`.
18+
pub add_ts_def: Option<String>,
1119
}
1220

1321
/// Configuration for ESTree generator on an enum.
1422
#[derive(Default, Debug)]
1523
pub struct ESTreeEnum {
1624
pub skip: bool,
1725
pub no_rename_variants: bool,
18-
pub custom_ts_def: bool,
26+
/// `true` if serializer is implemented manually and should not be generated
27+
pub custom_serialize: bool,
28+
/// Custom TS type definition. Does not include `export`.
29+
/// Empty string if type should not have a TS type definition.
30+
pub custom_ts_def: Option<String>,
31+
/// Additional custom TS type definition to add along with the generated one.
32+
/// Does not include `export`.
33+
pub add_ts_def: Option<String>,
1934
}
2035

2136
/// Configuration for ESTree generator on a struct field.

0 commit comments

Comments
 (0)