Skip to main content

rspack_plugin_javascript/parser_plugin/
trait.rs

1use swc_core::{
2  atoms::Atom,
3  common::Span,
4  ecma::ast::{
5    AssignExpr, AwaitExpr, BinExpr, CallExpr, ClassMember, CondExpr, Expr, ForOfStmt, Ident,
6    IfStmt, ImportDecl, MemberExpr, ModuleDecl, NewExpr, OptChainExpr, Program, ThisExpr,
7    UnaryExpr, VarDeclarator,
8  },
9};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12#[repr(u8)]
13pub enum JavascriptParserPluginHook {
14  PreStatement,
15  BlockPreStatement,
16  TopLevelAwaitExpr,
17  TopLevelForOfAwaitStmt,
18  CanRename,
19  Rename,
20  Program,
21  Statement,
22  UnusedStatement,
23  ModuleDeclaration,
24  BlockPreModuleDeclaration,
25  PreDeclarator,
26  Evaluate,
27  EvaluateTypeof,
28  EvaluateCallExpression,
29  EvaluateCallExpressionMember,
30  EvaluateIdentifier,
31  CanCollectDestructuringAssignmentProperties,
32  Pattern,
33  Call,
34  CallMemberChain,
35  Member,
36  MemberChain,
37  UnhandledExpressionMemberChain,
38  MemberChainOfCallMemberChain,
39  CallMemberChainOfCallMemberChain,
40  Typeof,
41  ExpressionLogicalOperator,
42  BinaryExpression,
43  StatementIf,
44  ClassExtendsExpression,
45  ClassBodyElement,
46  ClassBodyValue,
47  Declarator,
48  NewExpression,
49  Identifier,
50  This,
51  Assign,
52  AssignMemberChain,
53  ImportCall,
54  MetaProperty,
55  Import,
56  ImportSpecifier,
57  ExportImport,
58  Export,
59  ExportImportSpecifier,
60  ExportSpecifier,
61  ExportExpression,
62  OptionalChaining,
63  ExpressionConditionalOperation,
64  Finish,
65  IsPure,
66  ImportMetaPropertyInDestructuring,
67}
68
69impl JavascriptParserPluginHook {
70  pub const COUNT: usize = Self::ImportMetaPropertyInDestructuring as usize + 1;
71
72  pub const ALL: [Self; Self::COUNT] = [
73    Self::PreStatement,
74    Self::BlockPreStatement,
75    Self::TopLevelAwaitExpr,
76    Self::TopLevelForOfAwaitStmt,
77    Self::CanRename,
78    Self::Rename,
79    Self::Program,
80    Self::Statement,
81    Self::UnusedStatement,
82    Self::ModuleDeclaration,
83    Self::BlockPreModuleDeclaration,
84    Self::PreDeclarator,
85    Self::Evaluate,
86    Self::EvaluateTypeof,
87    Self::EvaluateCallExpression,
88    Self::EvaluateCallExpressionMember,
89    Self::EvaluateIdentifier,
90    Self::CanCollectDestructuringAssignmentProperties,
91    Self::Pattern,
92    Self::Call,
93    Self::CallMemberChain,
94    Self::Member,
95    Self::MemberChain,
96    Self::UnhandledExpressionMemberChain,
97    Self::MemberChainOfCallMemberChain,
98    Self::CallMemberChainOfCallMemberChain,
99    Self::Typeof,
100    Self::ExpressionLogicalOperator,
101    Self::BinaryExpression,
102    Self::StatementIf,
103    Self::ClassExtendsExpression,
104    Self::ClassBodyElement,
105    Self::ClassBodyValue,
106    Self::Declarator,
107    Self::NewExpression,
108    Self::Identifier,
109    Self::This,
110    Self::Assign,
111    Self::AssignMemberChain,
112    Self::ImportCall,
113    Self::MetaProperty,
114    Self::Import,
115    Self::ImportSpecifier,
116    Self::ExportImport,
117    Self::Export,
118    Self::ExportImportSpecifier,
119    Self::ExportSpecifier,
120    Self::ExportExpression,
121    Self::OptionalChaining,
122    Self::ExpressionConditionalOperation,
123    Self::Finish,
124    Self::IsPure,
125    Self::ImportMetaPropertyInDestructuring,
126  ];
127
128  pub const fn mask(self) -> u64 {
129    1u64 << (self as u8)
130  }
131}
132
133const _: () = assert!(
134  JavascriptParserPluginHook::COUNT <= 64,
135  "The number of JavascriptParserPluginHook variants exceeds 64, which cannot be safely stored in a u64 bitmask."
136);
137
138#[derive(Clone, Copy, Debug, PartialEq, Eq)]
139pub struct JavascriptParserPluginHooks(u64);
140
141impl JavascriptParserPluginHooks {
142  pub const fn empty() -> Self {
143    Self(0)
144  }
145
146  pub const fn all() -> Self {
147    Self(u64::MAX)
148  }
149
150  pub const fn contains(self, hook: JavascriptParserPluginHook) -> bool {
151    self.0 & hook.mask() != 0
152  }
153
154  pub const fn with(self, hook: JavascriptParserPluginHook) -> Self {
155    Self(self.0 | hook.mask())
156  }
157}
158
159use crate::{
160  utils::eval::BasicEvaluatedExpression,
161  visitors::{
162    ClassDeclOrExpr, DestructuringAssignmentProperty, ExportDefaultDeclaration,
163    ExportDefaultExpression, ExportImport, ExportLocal, ExportedVariableInfo, JavascriptParser,
164    Statement, VariableDeclaration,
165  },
166};
167
168type KeepRight = bool;
169
170pub trait JavascriptParserPlugin {
171  /// Used by the parser drive to precompute which hook paths this plugin
172  /// actually implements.
173  ///
174  /// # Why this exists (performance)
175  ///
176  /// `JavaScriptParserPluginDrive` needs to call many hook methods while walking the AST.
177  /// Calling every hook on every plugin would be very expensive.
178  ///
179  /// Instead, rspack precomputes a *hook mask* for each plugin at construction time:
180  ///
181  /// - Each plugin reports which hooks it actually implements via this method.
182  /// - The drive groups plugins by hook up-front.
183  /// - During parsing, the drive only iterates plugins that declared the current hook.
184  ///
185  /// This cuts a large amount of useless dynamic dispatch at runtime.
186  ///
187  /// # How to implement
188  ///
189  /// Do **NOT** implement this method manually.
190  ///
191  /// Use the proc-macro attribute `#[rspack_plugin_javascript::implemented_javascript_parser_hooks]`
192  /// (or `#[rspack_macros::implemented_javascript_parser_hooks]` inside the workspace).
193  /// The macro inspects the `impl JavascriptParserPlugin for ...` block and generates an
194  /// efficient `implemented_hooks` implementation automatically.
195  fn implemented_hooks(&self) -> JavascriptParserPluginHooks {
196    // NOTE: The intended implementation is generated by the
197    // `implemented_javascript_parser_hooks` attribute.
198    // If we end up here, it usually means the attribute was forgotten.
199    if cfg!(debug_assertions) {
200      panic!(
201        "`implemented_hooks` must be generated by the `implemented_javascript_parser_hooks` macro (attribute). \
202Please annotate your `impl JavascriptParserPlugin for ...` block with `#[rspack_plugin_javascript::implemented_javascript_parser_hooks]` \
203(or `#[rspack_macros::implemented_javascript_parser_hooks]` inside the repository)."
204      );
205    }
206
207    // Production fallback: assume the plugin may implement any hook.
208    JavascriptParserPluginHooks::all()
209  }
210
211  /// Return:
212  /// - `Some(true)` signifies the termination of the current
213  ///   statement's visit during the pre-walk phase.
214  /// - Other return values imply that the walk operation ought to continue
215  fn pre_statement(&self, _parser: &mut JavascriptParser, _stmt: Statement) -> Option<bool> {
216    None
217  }
218
219  fn block_pre_statement(&self, _parser: &mut JavascriptParser, _stmt: Statement) -> Option<bool> {
220    None
221  }
222
223  /// The return value will have no effect.
224  fn top_level_await_expr(&self, _parser: &mut JavascriptParser, _expr: &AwaitExpr) {}
225
226  /// The return value will have no effect.
227  fn top_level_for_of_await_stmt(&self, _parser: &mut JavascriptParser, _stmt: &ForOfStmt) {}
228
229  fn can_rename(&self, _parser: &mut JavascriptParser, _str: &str) -> Option<bool> {
230    None
231  }
232
233  fn rename(&self, _parser: &mut JavascriptParser, _expr: &Expr, _str: &str) -> Option<bool> {
234    None
235  }
236
237  fn program(&self, _parser: &mut JavascriptParser, _ast: &Program) -> Option<bool> {
238    None
239  }
240
241  fn statement(&self, _parser: &mut JavascriptParser, _stmt: Statement) -> Option<bool> {
242    None
243  }
244
245  /// Called for statements after a terminating point (when only function
246  /// declarations should still be processed). Plugins may eliminate or
247  /// transform such unused statements.
248  ///
249  /// Return:
250  /// - `Some(true)` means the statement is fully handled and should be skipped
251  /// - Other values mean the parser should still walk the statement
252  fn unused_statement(&self, _parser: &mut JavascriptParser, _stmt: Statement) -> Option<bool> {
253    None
254  }
255
256  fn module_declaration(&self, _parser: &mut JavascriptParser, _decl: &ModuleDecl) -> Option<bool> {
257    None
258  }
259
260  /// Return:
261  /// `None` means continue this `ModuleDecl`
262  /// Others means skip this.
263  ///
264  /// This is similar `hooks.block_pre_statement` in webpack
265  fn block_pre_module_declaration(
266    &self,
267    _parser: &mut JavascriptParser,
268    _decl: &ModuleDecl,
269  ) -> Option<bool> {
270    None
271  }
272
273  fn pre_declarator(
274    &self,
275    _parser: &mut JavascriptParser,
276    _declarator: &VarDeclarator,
277    _declaration: VariableDeclaration<'_>,
278  ) -> Option<bool> {
279    None
280  }
281
282  fn evaluate<'a>(
283    &self,
284    _parser: &mut JavascriptParser,
285    _expr: &'a Expr,
286  ) -> Option<BasicEvaluatedExpression<'a>> {
287    None
288  }
289
290  fn evaluate_typeof<'a>(
291    &self,
292    _parser: &mut JavascriptParser,
293    _expr: &'a UnaryExpr,
294    _for_name: &str,
295  ) -> Option<BasicEvaluatedExpression<'a>> {
296    None
297  }
298
299  fn evaluate_identifier(
300    &self,
301    _parser: &mut JavascriptParser,
302    _for_name: &str,
303    _start: u32,
304    _end: u32,
305  ) -> Option<BasicEvaluatedExpression<'static>> {
306    None
307  }
308
309  /// Evaluate CallExpression when callee is an Identifier (e.g. String(), Number()).
310  /// Mirrors webpack's hooks.evaluateCallExpression.
311  fn evaluate_call_expression<'a>(
312    &self,
313    _parser: &mut JavascriptParser,
314    _name: &str,
315    _expr: &'a CallExpr,
316  ) -> Option<BasicEvaluatedExpression<'a>> {
317    None
318  }
319
320  fn evaluate_call_expression_member<'a>(
321    &self,
322    _parser: &mut JavascriptParser,
323    _property: &str,
324    _expr: &'a CallExpr,
325    _param: BasicEvaluatedExpression<'a>,
326  ) -> Option<BasicEvaluatedExpression<'a>> {
327    None
328  }
329
330  fn can_collect_destructuring_assignment_properties(
331    &self,
332    _parser: &mut JavascriptParser,
333    _expr: &Expr,
334  ) -> Option<bool> {
335    None
336  }
337
338  fn pattern(
339    &self,
340    _parser: &mut JavascriptParser,
341    _ident: &Ident,
342    _for_name: &str,
343  ) -> Option<bool> {
344    None
345  }
346
347  fn call(
348    &self,
349    _parser: &mut JavascriptParser,
350    _expr: &CallExpr,
351    _for_name: &str,
352  ) -> Option<bool> {
353    None
354  }
355
356  fn call_member_chain(
357    &self,
358    _parser: &mut JavascriptParser,
359    _expr: &CallExpr,
360    _for_name: &str,
361    _members: &[Atom],
362    _members_optionals: &[bool],
363    _member_ranges: &[Span],
364  ) -> Option<bool> {
365    None
366  }
367
368  fn member(
369    &self,
370    _parser: &mut JavascriptParser,
371    _expr: &MemberExpr,
372    _for_name: &str,
373  ) -> Option<bool> {
374    None
375  }
376
377  fn member_chain(
378    &self,
379    _parser: &mut JavascriptParser,
380    _expr: &MemberExpr,
381    _for_name: &str,
382    _members: &[Atom],
383    _members_optionals: &[bool],
384    _member_ranges: &[Span],
385  ) -> Option<bool> {
386    None
387  }
388
389  fn unhandled_expression_member_chain(
390    &self,
391    _parser: &mut JavascriptParser,
392    _root_info: &ExportedVariableInfo,
393    _expr: &MemberExpr,
394  ) -> Option<bool> {
395    None
396  }
397
398  #[allow(clippy::too_many_arguments)]
399  fn member_chain_of_call_member_chain(
400    &self,
401    _parser: &mut JavascriptParser,
402    _member_expr: &MemberExpr,
403    _callee_members: &[Atom],
404    _call_expr: &CallExpr,
405    _members: &[Atom],
406    _member_ranges: &[Span],
407    _for_name: &str,
408  ) -> Option<bool> {
409    None
410  }
411
412  #[allow(clippy::too_many_arguments)]
413  fn call_member_chain_of_call_member_chain(
414    &self,
415    _parser: &mut JavascriptParser,
416    _call_expr: &CallExpr,
417    _callee_members: &[Atom],
418    _inner_call_expr: &CallExpr,
419    _members: &[Atom],
420    _member_ranges: &[Span],
421    _for_name: &str,
422  ) -> Option<bool> {
423    None
424  }
425
426  fn r#typeof(
427    &self,
428    _parser: &mut JavascriptParser,
429    _expr: &UnaryExpr,
430    _for_name: &str,
431  ) -> Option<bool> {
432    None
433  }
434
435  /// Return:
436  /// - `None` means should walk left and right;
437  /// - `Some(true)` means should walk right;
438  /// - `Some(false)` means nothing need to do.
439  fn expression_logical_operator(
440    &self,
441    _parser: &mut JavascriptParser,
442    _expr: &BinExpr,
443  ) -> Option<KeepRight> {
444    None
445  }
446
447  /// Return:
448  /// - `None` means should walk left and right;
449  fn binary_expression(&self, _parser: &mut JavascriptParser, _expr: &BinExpr) -> Option<bool> {
450    None
451  }
452
453  /// Return:
454  /// - `None` means need walk `stmt.test`, `stmt.cons` and `stmt.alt`;
455  /// - `Some(true)` means only need walk `stmt.cons`;
456  /// - `Some(false)` means only need walk `stmt.alt`;
457  fn statement_if(&self, _parser: &mut JavascriptParser, _expr: &IfStmt) -> Option<bool> {
458    None
459  }
460
461  fn class_extends_expression(
462    &self,
463    _parser: &mut JavascriptParser,
464    _super_class: &Expr,
465    _class_decl_or_expr: ClassDeclOrExpr,
466  ) -> Option<bool> {
467    None
468  }
469
470  fn class_body_element(
471    &self,
472    _parser: &mut JavascriptParser,
473    _element: &ClassMember,
474    _class_decl_or_expr: ClassDeclOrExpr,
475  ) -> Option<bool> {
476    None
477  }
478
479  fn class_body_value(
480    &self,
481    _parser: &mut JavascriptParser,
482    _element: &swc_core::ecma::ast::ClassMember,
483    _expr_span: Span,
484    _class_decl_or_expr: ClassDeclOrExpr,
485  ) -> Option<bool> {
486    None
487  }
488
489  fn declarator(
490    &self,
491    _parser: &mut JavascriptParser,
492    _expr: &VarDeclarator,
493    _stmt: VariableDeclaration<'_>,
494  ) -> Option<bool> {
495    None
496  }
497
498  fn new_expression(
499    &self,
500    _parser: &mut JavascriptParser,
501    _expr: &NewExpr,
502    _for_name: &str,
503  ) -> Option<bool> {
504    None
505  }
506
507  fn identifier(
508    &self,
509    _parser: &mut JavascriptParser,
510    _ident: &Ident,
511    _for_name: &str,
512  ) -> Option<bool> {
513    None
514  }
515
516  fn this(
517    &self,
518    _parser: &mut JavascriptParser,
519    _expr: &ThisExpr,
520    _for_name: &str,
521  ) -> Option<bool> {
522    None
523  }
524
525  fn assign(
526    &self,
527    _parser: &mut JavascriptParser,
528    _expr: &AssignExpr,
529    _for_name: &str,
530  ) -> Option<bool> {
531    None
532  }
533
534  fn assign_member_chain(
535    &self,
536    _parser: &mut JavascriptParser,
537    _expr: &AssignExpr,
538    _members: &[Atom],
539    _for_name: &str,
540  ) -> Option<bool> {
541    None
542  }
543
544  fn import_call(
545    &self,
546    _parser: &mut JavascriptParser,
547    _expr: &CallExpr,
548    _import_then: Option<&CallExpr>,
549    _members: Option<(&[Atom], bool)>,
550  ) -> Option<bool> {
551    None
552  }
553
554  fn meta_property(
555    &self,
556    _parser: &mut JavascriptParser,
557    _root_name: &Atom,
558    _span: Span,
559  ) -> Option<bool> {
560    None
561  }
562
563  fn import(
564    &self,
565    _parser: &mut JavascriptParser,
566    _statement: &ImportDecl,
567    _source: &str,
568  ) -> Option<bool> {
569    None
570  }
571
572  fn import_specifier(
573    &self,
574    _parser: &mut JavascriptParser,
575    _statement: &ImportDecl,
576    _source: &Atom,
577    _export_name: Option<&Atom>,
578    _identifier_name: &Atom,
579  ) -> Option<bool> {
580    None
581  }
582
583  fn export_import(
584    &self,
585    _parser: &mut JavascriptParser,
586    _statement: ExportImport,
587    _source: &Atom,
588  ) -> Option<bool> {
589    None
590  }
591
592  fn export(&self, _parser: &mut JavascriptParser, _statement: ExportLocal) -> Option<bool> {
593    None
594  }
595
596  fn export_import_specifier(
597    &self,
598    _parser: &mut JavascriptParser,
599    _statement: ExportImport,
600    _source: &Atom,
601    _local_id: Option<&Atom>,
602    _export_name: Option<&Atom>,
603    _export_name_span: Option<Span>,
604  ) -> Option<bool> {
605    None
606  }
607
608  fn export_specifier(
609    &self,
610    _parser: &mut JavascriptParser,
611    _statement: ExportLocal,
612    _local_id: &Atom,
613    _export_name: &Atom,
614    _export_name_span: Span,
615  ) -> Option<bool> {
616    None
617  }
618
619  fn export_expression(
620    &self,
621    _parser: &mut JavascriptParser,
622    _statement: ExportDefaultDeclaration,
623    _expr: ExportDefaultExpression,
624  ) -> Option<bool> {
625    None
626  }
627
628  fn optional_chaining(
629    &self,
630    _parser: &mut JavascriptParser,
631    _expr: &OptChainExpr,
632  ) -> Option<bool> {
633    None
634  }
635
636  fn expression_conditional_operation(
637    &self,
638    _parser: &mut JavascriptParser,
639    _expr: &CondExpr,
640  ) -> Option<bool> {
641    None
642  }
643
644  fn finish(&self, _parser: &mut JavascriptParser) -> Option<bool> {
645    None
646  }
647
648  fn is_pure(&self, _parser: &mut JavascriptParser, _expr: &Expr) -> Option<bool> {
649    None
650  }
651
652  /* plugin interop methods */
653
654  /**
655   * This method is used to interop with other plugins.
656   * It will be called in ImportMetaPlugin when processing destructuring of `import.meta`
657   */
658  fn import_meta_property_in_destructuring(
659    &self,
660    _parser: &mut JavascriptParser,
661    _property: &DestructuringAssignmentProperty,
662  ) -> Option<String> {
663    None
664  }
665}
666
667pub type BoxJavascriptParserPlugin = Box<dyn JavascriptParserPlugin + Send + Sync>;