oxc_ast 0.156.0

A collection of JavaScript tools written in Rust.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
use std::cell::Cell;

use bitflags::bitflags;

use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds};
use oxc_ast_macros::ast;
use oxc_estree::ESTree;
use oxc_span::{ContentEq, GetSpan, Span};
use oxc_syntax::node::NodeId;

/// Indicates a line or block comment.
#[ast]
#[generate_derive(CloneIn, ContentEq, ESTree)]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
#[estree(no_rename_variants, no_ts_def)]
pub enum CommentKind {
    /// Line comment
    #[default]
    Line = 0,
    /// Single-line comment
    #[estree(rename = "Block")]
    SingleLineBlock = 1,
    /// Multi-line block comment (contains line breaks)
    #[estree(rename = "Block")]
    MultiLineBlock = 2,
    /// HTML opening line comment (`<!--`). The discriminant is the delimiter length.
    #[estree(rename = "Line")]
    HtmlOpen = 4,
    /// HTML closing line comment (`-->`). The discriminant is the delimiter length.
    #[estree(rename = "Line")]
    HtmlClose = 3,
}

/// Information about a comment's position relative to a token.
#[ast]
#[generate_derive(CloneIn, ContentEq)]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
pub enum CommentPosition {
    /// Comments prior to a token until another token or trailing comment.
    ///
    /// e.g.
    ///
    /// ```ignore
    /// /* leading */ token;
    /// /* leading */
    /// // leading
    /// token;
    /// ```
    #[default]
    Leading = 0,

    /// Comments tailing a token until a newline.
    /// e.g. `token /* trailing */ // trailing`
    Trailing = 1,
}

/// Annotation comment that has special meaning.
#[ast]
#[generate_derive(CloneIn, ContentEq)]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
pub enum CommentContent {
    /// No Annotation
    #[default]
    None = 0,

    /// Legal Comment
    /// e.g. `/* @license */`, `/* @preserve */`, or starts with `//!` or `/*!`.
    /// <https://esbuild.github.io/api/#legal-comments>
    Legal = 1,

    /// `/** jsdoc */`
    /// <https://jsdoc.app>
    Jsdoc = 2,

    /// A jsdoc containing legal annotation.
    /// `/** @preserve */`
    JsdocLegal = 3,

    /// `/* #__PURE__ */`
    /// <https://github.com/javascript-compiler-hints/compiler-notations-spec>
    Pure = 4,

    /// `/* #__PURE__ */` that could not be applied (not before a call/new expression)
    /// <https://github.com/oxc-project/oxc/issues/20334>
    PureNotApplied = 5,

    /// `/* #__NO_SIDE_EFFECTS__ */`
    NoSideEffects = 6,

    /// `/* #__NO_SIDE_EFFECTS__ */` that could not be applied to a function
    NoSideEffectsNotApplied = 7,

    /// Webpack magic comment
    /// e.g. `/* webpackChunkName */`
    /// <https://webpack.js.org/api/module-methods/#magic-comments>
    Webpack = 8,

    /// Vite comment
    /// e.g. `/* @vite-ignore */`
    /// <https://github.com/search?q=repo%3Avitejs%2Fvite%20vite-ignore&type=code>
    Vite = 9,

    /// Code Coverage Ignore
    /// `v8 ignore`, `c8 ignore`, `node:coverage`, `istanbul ignore`
    /// <https://github.com/oxc-project/oxc/issues/10091>
    CoverageIgnore = 10,

    /// Turbopack magic comment
    /// e.g. `/* turbopackOptional: true */`
    /// <https://nextjs.org/docs/app/guides/lazy-loading#turbopackoptional-turbopack-only>
    Turbopack = 11,

    /// File-level code coverage ignore.
    ///
    /// `v8 ignore file`, `istanbul ignore file`.
    /// Classified separately because its meaning remains valid if the next AST
    /// node is removed, unlike position-sensitive coverage annotations.
    CoverageIgnoreFile = 12,

    /// Marks the following string or no-substitution template as a property name.
    /// `/* @__KEY__ */` or `/* #__KEY__ */`
    /// <https://esbuild.github.io/api/#mangle-key>
    PropertyKey = 13,
}

bitflags! {
    #[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
    /// State of newlines around a comment.
    pub struct CommentNewlines: u8 {
        /// Preceded by a newline
        const Leading = 1 << 0;
        /// Followed by a newline
        const Trailing = 1 << 1;
        /// No newlines before or after
        const None = 0;
    }
}

/// Dummy type to communicate the content of `CommentFlags` to `oxc_ast_tools`.
#[ast(foreign = CommentNewlines)]
#[expect(dead_code)]
struct CommentNewlinesAlias(u8);

impl ContentEq for CommentNewlines {
    fn content_eq(&self, other: &Self) -> bool {
        self == other
    }
}

impl<'alloc> CloneIn<'alloc> for CommentNewlines {
    type Cloned = Self;

    fn clone_in_impl(
        &self,
        _with_semantic_ids: CloneInSemanticIds,
        _: &'alloc Allocator,
    ) -> Self::Cloned {
        *self
    }
}

/// A comment's position relative to its owning AST node.
///
/// This is separate from the token-relative position stored on the source comment.
#[ast]
#[generate_derive(ContentEq, CloneIn)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CommentPlacement {
    /// Before the node.
    ///
    /// The comment belongs to the `first();` expression statement:
    ///
    /// ```js
    /// // leading
    /// first();
    /// ```
    #[default]
    Leading = 0,
    /// After the node.
    ///
    /// The comment belongs to the `first();` expression statement:
    ///
    /// ```js
    /// first(); // trailing
    /// ```
    Trailing = 1,
    /// Inside the node, in a gap between its children or in an empty container.
    ///
    /// Each comment belongs to its enclosing array expression:
    ///
    /// ```js
    /// const empty = [/* inside */];
    /// const values = [item, /* after the last item */];
    /// ```
    Dangling = 2,
}

/// A comment in source code.
///
/// Borrow comments when reading them, or use [`Clone::clone`] to obtain an owned value.
#[ast]
#[generate_derive(ContentEq, CloneIn, ESTree, GetSpan)]
#[derive(Debug, Default, Clone, Eq, PartialEq)]
#[estree(add_fields(value = CommentValue), no_ts_def, no_parent)]
pub struct Comment {
    /// The span of the comment text, with leading and trailing delimiters.
    pub span: Span,

    /// Source boundary this comment is attached to.
    ///
    /// Leading comments use the start of the following token:
    /// ```text
    /// /* Leading */ token
    ///               ^ attached_to
    /// ```
    ///
    /// Trailing comments use the end of the preceding token:
    /// ```text
    /// token| /* Trailing */
    ///      ^ attached_to (the boundary immediately after `token`)
    /// ```
    #[estree(skip)]
    pub attached_to: u32,

    /// ID of the owning node, remapped by semantic analysis through a shared reference.
    #[estree(skip)]
    pub node_id: Cell<NodeId>,

    /// Where to print the comment relative to its owner.
    #[estree(skip)]
    pub placement: CommentPlacement,

    /// Line or block comment
    #[estree(rename = "type")]
    pub kind: CommentKind,

    /// Leading or trailing comment
    #[estree(skip)]
    pub position: CommentPosition,

    /// Whether this comment has newlines around it.
    /// Used to avoid becoming a trailing comment in codegen.
    #[estree(skip)]
    pub newlines: CommentNewlines,

    /// Content of the comment
    #[estree(skip)]
    pub content: CommentContent,
}

impl Comment {
    /// Create a line or block comment at a given location.
    #[inline]
    pub fn new(start: u32, end: u32, kind: CommentKind) -> Self {
        let span = Span::new(start, end);
        Self {
            span,
            attached_to: 0,
            kind,
            position: CommentPosition::Trailing,
            newlines: CommentNewlines::None,
            content: CommentContent::None,
            node_id: Cell::new(NodeId::DUMMY),
            placement: CommentPlacement::Leading,
        }
    }

    /// Gets the span of the comment content.
    pub fn content_span(&self) -> Span {
        match self.kind {
            CommentKind::Line => Span::new(self.span.start + 2, self.span.end),
            CommentKind::HtmlOpen | CommentKind::HtmlClose => {
                Span::new(self.span.start + self.kind as u32, self.span.end)
            }
            CommentKind::SingleLineBlock | CommentKind::MultiLineBlock => {
                Span::new(self.span.start + 2, self.span.end - 2)
            }
        }
    }

    /// Returns `true` if this is a line comment.
    #[inline]
    pub fn is_line(&self) -> bool {
        matches!(self.kind, CommentKind::Line | CommentKind::HtmlOpen | CommentKind::HtmlClose)
    }

    /// Returns `true` if this is a block comment (either single-line or multi-line).
    #[inline]
    pub fn is_block(&self) -> bool {
        matches!(self.kind, CommentKind::SingleLineBlock | CommentKind::MultiLineBlock)
    }

    /// Returns `true` if this is a multi-line block comment.
    #[inline]
    pub fn is_multiline_block(&self) -> bool {
        self.kind == CommentKind::MultiLineBlock
    }

    /// Returns `true` if this comment is before a token.
    #[inline]
    pub fn is_leading(&self) -> bool {
        self.position == CommentPosition::Leading
    }

    /// Returns `true` if this comment is after a token.
    #[inline]
    pub fn is_trailing(&self) -> bool {
        self.position == CommentPosition::Trailing
    }

    /// Is comment without a special meaning.
    #[inline]
    pub fn is_normal(&self) -> bool {
        self.content == CommentContent::None
    }

    /// Is comment with special meaning.
    #[inline]
    pub fn is_annotation(&self) -> bool {
        self.content != CommentContent::None
            && self.content != CommentContent::Legal
            && self.content != CommentContent::Jsdoc
            && self.content != CommentContent::JsdocLegal
    }

    /// Returns `true` if this comment is a JSDoc comment. Implies `is_leading` and `is_block`.
    #[inline]
    pub fn is_jsdoc(&self) -> bool {
        matches!(self.content, CommentContent::Jsdoc | CommentContent::JsdocLegal)
            && self.is_leading()
    }

    /// Legal comments
    ///
    /// A "legal comment" is considered to be any statement-level comment
    /// that contains `@license` or `@preserve` or that starts with `//!` or `/*!`.
    ///
    /// <https://esbuild.github.io/api/#legal-comments>
    #[inline]
    pub fn is_legal(&self) -> bool {
        matches!(self.content, CommentContent::Legal | CommentContent::JsdocLegal)
            && self.is_leading()
    }

    /// Is `/* @__PURE__*/`.
    #[inline]
    pub fn is_pure(&self) -> bool {
        self.content == CommentContent::Pure
    }

    /// Is `/* @__NO_SIDE_EFFECTS__*/`.
    #[inline]
    pub fn is_no_side_effects(&self) -> bool {
        self.content == CommentContent::NoSideEffects
    }

    /// Is a leading `/* @__KEY__ */` or `/* #__KEY__ */` annotation.
    #[inline]
    pub fn is_property_key_annotation(&self) -> bool {
        self.content == CommentContent::PropertyKey && self.is_leading()
    }

    /// Is webpack magic comment.
    #[inline]
    pub fn is_webpack(&self) -> bool {
        self.content == CommentContent::Webpack
    }

    /// Is turbopack magic comment.
    #[inline]
    pub fn is_turbopack(&self) -> bool {
        self.content == CommentContent::Turbopack
    }

    /// Is vite special comment.
    #[inline]
    pub fn is_vite(&self) -> bool {
        self.content == CommentContent::Vite
    }

    /// Is coverage ignore comment.
    #[inline]
    pub fn is_coverage_ignore(&self) -> bool {
        matches!(self.content, CommentContent::CoverageIgnore | CommentContent::CoverageIgnoreFile)
            && self.is_leading()
    }

    /// Is a file-level coverage ignore comment.
    #[inline]
    pub fn is_coverage_ignore_file(&self) -> bool {
        self.content == CommentContent::CoverageIgnoreFile && self.is_leading()
    }

    /// Returns `true` if this comment is preceded by a newline.
    #[inline]
    pub fn preceded_by_newline(&self) -> bool {
        self.newlines.contains(CommentNewlines::Leading)
    }

    /// Returns `true` if this comment is followed by a newline.
    #[inline]
    pub fn followed_by_newline(&self) -> bool {
        self.newlines.contains(CommentNewlines::Trailing)
    }

    /// Returns `true` if this comment has newlines either before or after it.
    #[inline]
    pub fn has_newlines_around(&self) -> bool {
        self.newlines != CommentNewlines::None
    }

    /// Sets the state of `newlines` to include/exclude a newline before the comment.
    #[inline]
    pub fn set_preceded_by_newline(&mut self, preceded_by_newline: bool) {
        self.newlines.set(CommentNewlines::Leading, preceded_by_newline);
    }

    /// Sets the state of `newlines` to include/exclude a newline after the comment.
    #[inline]
    pub fn set_followed_by_newline(&mut self, followed_by_newline: bool) {
        self.newlines.set(CommentNewlines::Trailing, followed_by_newline);
    }
}