fix(ast): correct JSON serialization of TSModuleBlock (#3858)

Fix #3848.

#3830 added a `directives` field to `TSModuleBlock`. ESTree AST treats directives in module blocks as `StringLiteral`s. Add a custom `Serialize` impl to combine any directives back into `body` in JS AST.

NB: `#[serde(skip)]` attr on `directives` field is for benefit of `Tsify` derive macro, so TS defs match the actual JSON AST.
This commit is contained in:
overlookmotel 2024-06-24 02:25:49 +00:00
parent 66f404c2f3
commit 063cfdeb40
2 changed files with 55 additions and 3 deletions

View file

@ -832,13 +832,15 @@ pub enum TSModuleDeclarationBody<'a> {
TSModuleBlock(Box<'a, TSModuleBlock<'a>>),
}
// See serializer in serialize.rs
#[visited_node]
#[derive(Debug, Hash)]
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
#[cfg_attr(feature = "serialize", derive(Tsify))]
#[cfg_attr(feature = "serialize", serde(tag = "type", rename_all = "camelCase"))]
pub struct TSModuleBlock<'a> {
#[cfg_attr(feature = "serialize", serde(flatten))]
pub span: Span,
#[cfg_attr(feature = "serialize", serde(skip))]
pub directives: Vec<'a, Directive<'a>>,
pub body: Vec<'a, Statement<'a>>,
}

View file

@ -8,8 +8,9 @@ use serde::{
use crate::ast::{
ArrayAssignmentTarget, ArrayPattern, AssignmentTargetMaybeDefault, AssignmentTargetProperty,
AssignmentTargetRest, BindingPattern, BindingPatternKind, BindingProperty, BindingRestElement,
Elision, FormalParameter, FormalParameterKind, FormalParameters, ObjectAssignmentTarget,
ObjectPattern, Program, RegExpFlags, TSTypeAnnotation,
Directive, Elision, FormalParameter, FormalParameterKind, FormalParameters,
ObjectAssignmentTarget, ObjectPattern, Program, RegExpFlags, Statement, StringLiteral,
TSModuleBlock, TSTypeAnnotation,
};
pub struct EcmaFormatter;
@ -199,3 +200,52 @@ impl<'a, 'b, E: Serialize, R: Serialize> Serialize for ElementsAndRest<'a, 'b, E
}
}
}
/// Serialize `TSModuleBlock` to be ESTree compatible, with `body` and `directives` fields combined,
/// and directives output as `StringLiteral` expression statements
impl<'a> Serialize for TSModuleBlock<'a> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let converted = SerTSModuleBlock {
span: self.span,
body: DirectivesAndStatements { directives: &self.directives, body: &self.body },
};
converted.serialize(serializer)
}
}
#[derive(Serialize)]
#[serde(tag = "type", rename = "TSModuleBlock")]
struct SerTSModuleBlock<'a, 'b> {
#[serde(flatten)]
span: Span,
body: DirectivesAndStatements<'a, 'b>,
}
struct DirectivesAndStatements<'a, 'b> {
directives: &'b [Directive<'a>],
body: &'b [Statement<'a>],
}
impl<'a, 'b> Serialize for DirectivesAndStatements<'a, 'b> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut seq = serializer.serialize_seq(Some(self.directives.len() + self.body.len()))?;
for directive in self.directives {
seq.serialize_element(&DirectiveAsStatement {
span: directive.span,
expression: &directive.expression,
})?;
}
for stmt in self.body {
seq.serialize_element(stmt)?;
}
seq.end()
}
}
#[derive(Serialize)]
#[serde(tag = "type", rename = "ExpressionStatement")]
struct DirectiveAsStatement<'a, 'b> {
#[serde(flatten)]
span: Span,
expression: &'b StringLiteral<'a>,
}