Skip to content

Commit 2fa6607

Browse files
committed
refactor(ast_tools): move variants generator into output module
1 parent 0061ce7 commit 2fa6607

File tree

3 files changed

+319
-156
lines changed

3 files changed

+319
-156
lines changed

tasks/ast_tools/src/generators/raw_transfer.rs

Lines changed: 56 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,16 @@ use proc_macro2::TokenStream;
88
use quote::quote;
99
use rustc_hash::FxHashSet;
1010

11-
use oxc_allocator::{Allocator, CloneIn};
11+
use oxc_allocator::Allocator;
1212
use oxc_ast::{
1313
AstBuilder, NONE,
1414
ast::{
15-
Argument, BindingPatternKind, Expression, FormalParameterKind, FunctionType,
16-
LogicalOperator, ObjectExpression, ObjectPropertyKind, Program, PropertyKind, Statement,
17-
UnaryOperator,
15+
Argument, Expression, FormalParameterKind, FunctionType, LogicalOperator, ObjectExpression,
16+
ObjectPropertyKind, Program, PropertyKind,
1817
},
1918
};
20-
use oxc_ast_visit::{VisitMut, walk_mut};
21-
use oxc_codegen::Codegen as Printer;
22-
use oxc_minifier::{
23-
CompressOptions, CompressOptionsKeepNames, Minifier, MinifierOptions, PropertyReadSideEffects,
24-
TreeShakeOptions,
25-
};
26-
use oxc_parser::Parser;
27-
use oxc_span::{SPAN, SourceType};
19+
use oxc_ast_visit::VisitMut;
20+
use oxc_span::SPAN;
2821

2922
use crate::{
3023
ALLOCATOR_CRATE_PATH, Generator, NAPI_PARSER_PACKAGE_PATH, OXLINT_APP_PATH,
@@ -33,7 +26,7 @@ use crate::{
3326
get_fieldless_variant_value, get_struct_field_name, should_flatten_field,
3427
should_skip_enum_variant, should_skip_field,
3528
},
36-
output::Output,
29+
output::{Output, javascript::VariantGenerator},
3730
schema::{
3831
BoxDef, CellDef, Def, EnumDef, FieldDef, MetaType, OptionDef, PointerDef, PrimitiveDef,
3932
Schema, StructDef, TypeDef, TypeId, VecDef,
@@ -129,6 +122,7 @@ impl Generator for RawTransferGenerator {
129122
///
130123
/// When printing the JS and TS deserializers, the value of `IS_TS` is set to `true` or `false`,
131124
/// and minifier then shakes out the dead code for each.
125+
#[expect(clippy::items_after_statements)]
132126
fn generate_deserializers(
133127
consts: Constants,
134128
schema: &Schema,
@@ -145,12 +139,6 @@ fn generate_deserializers(
145139
let mut code = format!("
146140
let uint8, uint32, float64, sourceText, sourceIsAscii, sourceByteLen;
147141
148-
const IS_TS = false;
149-
const RANGE = false;
150-
const LOC = false;
151-
const PARENT = false;
152-
const PRESERVE_PARENS = false;
153-
154142
const textDecoder = new TextDecoder('utf-8', {{ ignoreBOM: true }}),
155143
decodeStr = textDecoder.decode.bind(textDecoder),
156144
{{ fromCodePoint }} = String;
@@ -215,62 +203,64 @@ fn generate_deserializers(
215203
}
216204
}
217205

218-
// Parse generated code
219-
let allocator = Allocator::new();
220-
let source_type = SourceType::mjs();
221-
let parser_ret = Parser::new(&allocator, &code, source_type).parse();
222-
assert!(parser_ret.errors.is_empty(), "Parse errors: {:#?}", parser_ret.errors);
223-
let program = parser_ret.program;
224-
225-
// Create deserializers with various settings, by setting `IS_TS`, `RANGE` and `PRESERVE_PARENS` consts,
226-
// and running through minifier to shake out irrelevant code
227-
let mut print_allocator = Allocator::new();
228-
let mut deserializers = vec![];
229-
let mut create_deserializer = |is_ts, range, loc, parent, preserve_parens| {
230-
let mut program = program.clone_in(&print_allocator);
231-
replace_const(&mut program, "IS_TS", is_ts);
232-
replace_const(&mut program, "RANGE", range);
233-
replace_const(&mut program, "LOC", loc);
234-
replace_const(&mut program, "PARENT", parent);
235-
replace_const(&mut program, "PRESERVE_PARENS", preserve_parens);
236-
237-
if loc {
238-
assert!(range, "`loc` requires `range`");
239-
LocFieldAdder::new(&allocator).visit_program(&mut program);
240-
}
206+
// Create deserializers with various settings, by setting `IS_TS`, `RANGE`, `LOC`, `PARENT`
207+
// and `PRESERVE_PARENS` consts, and running through minifier to shake out irrelevant code
208+
struct VariantGen {
209+
variant_names: Vec<String>,
210+
}
241211

242-
let code = print_minified(&mut program, &print_allocator);
243-
print_allocator.reset();
212+
impl VariantGenerator<5> for VariantGen {
213+
const FLAG_NAMES: [&str; 5] = ["IS_TS", "RANGE", "LOC", "PARENT", "PRESERVE_PARENS"];
244214

245-
let mut name = if is_ts { "ts" } else { "js" }.to_string();
246-
if range {
247-
name.push_str("_range");
248-
}
249-
if loc {
250-
name.push_str("_loc");
251-
}
252-
if parent {
253-
name.push_str("_parent");
254-
}
255-
if !preserve_parens {
256-
name.push_str("_no_parens");
257-
}
215+
fn variants(&mut self) -> Vec<[bool; 5]> {
216+
let mut variants = Vec::with_capacity(9);
258217

259-
deserializers.push((name, code));
260-
};
218+
for is_ts in [false, true] {
219+
for range in [false, true] {
220+
for parent in [false, true] {
221+
let mut name = if is_ts { "ts" } else { "js" }.to_string();
222+
if range {
223+
name.push_str("_range");
224+
}
225+
if parent {
226+
name.push_str("_parent");
227+
}
228+
self.variant_names.push(name);
229+
230+
variants.push([
231+
is_ts, range, /* loc */ false, parent,
232+
/* preserve_parens */ true,
233+
]);
234+
}
235+
}
236+
}
261237

262-
for is_ts in [false, true] {
263-
for range in [false, true] {
264-
for parent in [false, true] {
265-
create_deserializer(is_ts, range, false, parent, true);
238+
self.variant_names.push("ts_range_loc_parent_no_parens".to_string());
239+
variants.push([
240+
/* is_ts */ true, /* range */ true, /* loc */ true,
241+
/* parent */ true, /* preserve_parens */ false,
242+
]);
243+
244+
variants
245+
}
246+
247+
fn pre_process_variant<'a>(
248+
&mut self,
249+
program: &mut Program<'a>,
250+
flags: [bool; 5],
251+
allocator: &'a Allocator,
252+
) {
253+
if flags[2] {
254+
// `loc` enabled
255+
LocFieldAdder::new(allocator).visit_program(program);
266256
}
267257
}
268258
}
269259

270-
// `PRESERVE_PARENS = false` is only required for linter
271-
create_deserializer(true, true, true, true, false);
260+
let mut generator = VariantGen { variant_names: vec![] };
261+
let codes = generator.generate(&code);
272262

273-
deserializers
263+
generator.variant_names.into_iter().zip(codes).collect()
274264
}
275265

276266
/// Type of deserializer in which some code appears.
@@ -1283,94 +1273,6 @@ fn get_constants(schema: &Schema) -> Constants {
12831273
}
12841274
}
12851275

1286-
/// Replace the value of a `const` declaration with `true` / `false`.
1287-
///
1288-
/// Only replaces `const`s defined at top level which are currently defined as a boolean.
1289-
fn replace_const(program: &mut Program<'_>, const_name: &str, value: bool) {
1290-
for stmt in &mut program.body {
1291-
let Statement::VariableDeclaration(var_decl) = stmt else { continue };
1292-
if !var_decl.kind.is_const() {
1293-
continue;
1294-
}
1295-
1296-
for declarator in &mut var_decl.declarations {
1297-
if let BindingPatternKind::BindingIdentifier(ident) = &declarator.id.kind
1298-
&& ident.name == const_name
1299-
{
1300-
let init = declarator.init.as_mut().unwrap();
1301-
let Expression::BooleanLiteral(bool_lit) = init else { continue };
1302-
bool_lit.value = value;
1303-
return;
1304-
}
1305-
}
1306-
}
1307-
1308-
panic!("`{const_name}` const not found");
1309-
}
1310-
1311-
/// Print AST with minified syntax.
1312-
///
1313-
/// Do not remove whitespace, or mangle symbols.
1314-
/// Purpose is not to compress length of code, but to remove dead code.
1315-
fn print_minified<'a>(program: &mut Program<'a>, allocator: &'a Allocator) -> String {
1316-
// Minify
1317-
let minify_options = MinifierOptions {
1318-
mangle: None,
1319-
compress: Some(CompressOptions {
1320-
keep_names: CompressOptionsKeepNames::all_true(),
1321-
sequences: false,
1322-
treeshake: TreeShakeOptions {
1323-
property_read_side_effects: PropertyReadSideEffects::None,
1324-
..TreeShakeOptions::default()
1325-
},
1326-
..CompressOptions::default()
1327-
}),
1328-
};
1329-
Minifier::new(minify_options).minify(allocator, program);
1330-
1331-
// Revert minification of `true` to `!0` and `false` to `!1`. It hurts readability.
1332-
let mut unminifier = BooleanUnminifier::new(allocator);
1333-
unminifier.visit_program(program);
1334-
1335-
// Print. Add back line breaks between functions to aid readability.
1336-
let mut code = Printer::new().build(program).code;
1337-
1338-
#[expect(clippy::items_after_statements)]
1339-
static RE: Lazy<Regex> = lazy_regex!(r"\n(function|export) ");
1340-
code = RE
1341-
.replace_all(&code, |caps: &Captures| {
1342-
// `format!("\n\n{} ", &caps[1])` would be simpler, but this avoids allocations
1343-
if &caps[1] == "function" { "\n\nfunction " } else { "\n\nexport " }
1344-
})
1345-
.into_owned();
1346-
1347-
code
1348-
}
1349-
1350-
/// Visitor which converts `!0` to `true` and `!1` to `false`.
1351-
struct BooleanUnminifier<'a> {
1352-
ast: AstBuilder<'a>,
1353-
}
1354-
1355-
impl<'a> BooleanUnminifier<'a> {
1356-
fn new(allocator: &'a Allocator) -> Self {
1357-
Self { ast: AstBuilder::new(allocator) }
1358-
}
1359-
}
1360-
1361-
impl<'a> VisitMut<'a> for BooleanUnminifier<'a> {
1362-
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
1363-
if let Expression::UnaryExpression(unary_expr) = expr
1364-
&& unary_expr.operator == UnaryOperator::LogicalNot
1365-
&& let Expression::NumericLiteral(lit) = &unary_expr.argument
1366-
{
1367-
*expr = self.ast.expression_boolean_literal(unary_expr.span, lit.value == 0.0);
1368-
return;
1369-
}
1370-
walk_mut::walk_expression(self, expr);
1371-
}
1372-
}
1373-
13741276
/// Visitor to add `loc` field after `range` in all deserialize functions.
13751277
///
13761278
/// Works on AST pre-minification.

0 commit comments

Comments
 (0)