oxc_ast 0.157.0

A collection of JavaScript tools written in Rust.
Documentation
use oxc_ast_macros::ast_meta;
use oxc_estree::{
    CompactFixesSerializer, CompactSerializer, Concat2, ESTree, JsonSafeString,
    PrettyFixesSerializer, PrettySerializer, Serializer, StructSerializer,
};

use crate::ast::*;

pub mod basic;
pub mod js;
pub mod jsx;
pub mod literal;
pub mod ts;
use basic::{EmptyArray, Null};

/// Main serialization methods for `Program`.
///
/// Note: 4 separate methods for the different serialization options, rather than 1 method
/// with behavior controlled by flags
/// (e.g. `fn to_estree_json(&self, with_ts: bool, pretty: bool, fixes: bool)`)
/// to avoid bloating binary size.
///
/// Most consumers (and Oxc crates) will use only 1 of these methods, so we don't want to needlessly
/// compile all 4 serializers when only 1 is used.
///
/// Initial capacity for serializer's buffer is an estimate based on our benchmark fixtures
/// of ratio of source text size to JSON size.
///
/// | File                       | Compact TS | Compact JS | Pretty TS | Pretty JS |
/// |----------------------------|------------|------------|-----------|-----------|
/// | antd.js                    |         10 |          9 |        76 |        72 |
/// | checker.ts                 |          7 |          6 |        27 |        24 |
/// | pdf.mjs                    |         13 |         12 |        71 |        67 |
/// | RadixUIAdoptionSection.jsx |         10 |          9 |        45 |        44 |
/// |----------------------------|------------|------------|-----------|-----------|
/// | Maximum                    |         13 |         12 |        76 |        72 |
///
/// It's better to over-estimate than under-estimate, as having to grow the buffer is expensive,
/// so have gone on the generous side.
const JSON_CAPACITY_RATIO_COMPACT: usize = 16;
const JSON_CAPACITY_RATIO_PRETTY: usize = 80;

impl Program<'_> {
    /// Serialize AST to ESTree JSON.
    pub fn to_estree_json(&self, include_ts_fields: bool, ranges: bool) -> String {
        let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT;
        let mut serializer = CompactSerializer::with_capacity(capacity, include_ts_fields, ranges);
        self.serialize(&mut serializer);
        serializer.into_string()
    }

    /// Serialize AST to pretty-printed ESTree JSON.
    pub fn to_pretty_estree_json(&self, include_ts_fields: bool, ranges: bool) -> String {
        let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY;
        let mut serializer = PrettySerializer::with_capacity(capacity, include_ts_fields, ranges);
        self.serialize(&mut serializer);
        serializer.into_string()
    }

    /// Serialize AST to ESTree JSON, with list of fixes.
    pub fn to_estree_json_with_fixes(&self, include_ts_fields: bool, ranges: bool) -> String {
        let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT;
        let serializer = CompactFixesSerializer::with_capacity(capacity, include_ts_fields, ranges);
        serializer.serialize_with_fixes(self)
    }

    /// Serialize AST to pretty-printed ESTree JSON, with list of fixes.
    pub fn to_pretty_estree_json_with_fixes(
        &self,
        include_ts_fields: bool,
        ranges: bool,
    ) -> String {
        let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY;
        let serializer = PrettyFixesSerializer::with_capacity(capacity, include_ts_fields, ranges);
        serializer.serialize_with_fixes(self)
    }
}

/// Serializer for `Program`.
///
/// Serializes directives and statements together as `body`.
/// The span covers the entire source text, including leading and trailing comments and whitespace.
#[ast_meta]
#[estree(raw_deser = "
    const start = DESER[i32](POS_OFFSET.span.start),
        end = DESER[i32](POS_OFFSET.span.end);

    const program = parent = {
        type: 'Program',
        body: null,
        sourceType: DESER[ModuleKind](POS_OFFSET.source_type.module_kind),
        /* IF !LINTER */
        hashbang: null,
        /* END_IF */
        /* IF LINTER */
        get comments() {
            if (comments === null) initComments();
            return comments;
        },
        get tokens() {
            if (tokens === null) initTokens();
            return tokens;
        },
        /* END_IF */
        start,
        end,
        ...(RANGE && { range: [start, end] }),
        ...(PARENT && { parent: null }),
    };

    if (!LINTER) program.hashbang = DESER[Option<Hashbang>](POS_OFFSET.hashbang);

    const body = program.body = DESER[Vec<Directive>](POS_OFFSET.directives);
    body.push(...DESER[Vec<Statement>](POS_OFFSET.body));

    if (PARENT) parent = null;

    program
")]
pub struct ProgramConverter<'a, 'b>(pub &'b Program<'a>);

impl ESTree for ProgramConverter<'_, '_> {
    fn serialize<S: Serializer>(&self, serializer: S) {
        let program = self.0;

        let mut state = serializer.serialize_struct();
        state.serialize_field("type", &JsonSafeString("Program"));
        state.serialize_field("body", &Concat2(&program.directives, &program.body));
        state.serialize_field("sourceType", &program.source_type.module_kind());
        state.serialize_field("hashbang", &program.hashbang);
        state.serialize_span(program.span);

        state.end();
    }
}

/// Serialize `value` field of `Comment`.
///
/// This serializer does not work for JSON serializer, because there's no access to source text
/// in `fn serialize`. But in any case, comments often contain characters which need escaping in JSON,
/// which is slow, so it's probably faster to transfer comments as NAPI types (which we do).
///
/// This meta type is only present for raw transfer, which can transfer faster.
#[ast_meta]
#[estree(
    ts_type = "string",
    raw_deser = "
        // HTML comment discriminants equal their delimiter lengths (3 or 4).
        const kind = DESER[u8](POS_OFFSET.kind);
        SOURCE_TEXT.slice(THIS.start + (kind < 3 ? 2 : kind), THIS.end - (THIS.type === 'Line' ? 0 : 2))
    ",
    raw_deser_inline
)]
pub struct CommentValue<'b>(#[expect(dead_code)] pub &'b Comment);

impl ESTree for CommentValue<'_> {
    #[expect(clippy::unimplemented)]
    fn serialize<S: Serializer>(&self, _serializer: S) {
        unimplemented!();
    }
}