use bitflags::bitflags; use oxc_allocator::Vec; use oxc_ast::ast::TSAccessibility; use oxc_diagnostics::{OxcDiagnostic, Result}; use oxc_span::{GetSpan, Span, SPAN}; use crate::{ diagnostics, lexer::{Kind, Token}, ParserImpl, }; bitflags! { /// Bitflag of modifiers and contextual modifiers. /// Useful to cheaply track all already seen modifiers of a member (instead of using a HashSet). #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ModifierFlags: u16 { const DECLARE = 1 << 0; const PRIVATE = 1 << 1; const PROTECTED = 1 << 2; const PUBLIC = 1 << 3; const STATIC = 1 << 4; const READONLY = 1 << 5; const ABSTRACT = 1 << 6; const OVERRIDE = 1 << 7; const ASYNC = 1 << 8; const CONST = 1 << 9; const IN = 1 << 10; const OUT = 1 << 11; const EXPORT = 1 << 12; const DEFAULT = 1 << 13; const ACCESSOR = 1 << 14; const ACCESSIBILITY = Self::PRIVATE.bits() | Self::PROTECTED.bits() | Self::PUBLIC.bits(); } } /// It is the caller's safety to always check by `Kind::is_modifier_kind` /// before converting [`Kind`] to [`ModifierFlags`] so that we can assume here that /// the conversion always succeeds. impl From for ModifierFlags { fn from(value: Kind) -> Self { match value { Kind::Abstract => Self::ABSTRACT, Kind::Declare => Self::DECLARE, Kind::Private => Self::PRIVATE, Kind::Protected => Self::PROTECTED, Kind::Public => Self::PUBLIC, Kind::Static => Self::STATIC, Kind::Readonly => Self::READONLY, Kind::Override => Self::OVERRIDE, Kind::Async => Self::ASYNC, Kind::Const => Self::CONST, Kind::In => Self::IN, Kind::Out => Self::OUT, Kind::Export => Self::EXPORT, Kind::Default => Self::DEFAULT, Kind::Accessor => Self::ACCESSOR, _ => unreachable!(), } } } impl From for ModifierFlags { fn from(kind: ModifierKind) -> Self { match kind { ModifierKind::Abstract => Self::ABSTRACT, ModifierKind::Declare => Self::DECLARE, ModifierKind::Private => Self::PRIVATE, ModifierKind::Protected => Self::PROTECTED, ModifierKind::Public => Self::PUBLIC, ModifierKind::Static => Self::STATIC, ModifierKind::Readonly => Self::READONLY, ModifierKind::Override => Self::OVERRIDE, ModifierKind::Async => Self::ASYNC, ModifierKind::Const => Self::CONST, ModifierKind::In => Self::IN, ModifierKind::Out => Self::OUT, ModifierKind::Export => Self::EXPORT, ModifierKind::Default => Self::DEFAULT, ModifierKind::Accessor => Self::ACCESSOR, } } } impl ModifierFlags { pub(crate) fn accessibility(self) -> Option { if self.contains(Self::PUBLIC) { return Some(TSAccessibility::Public); } if self.contains(Self::PROTECTED) { return Some(TSAccessibility::Protected); } if self.contains(Self::PRIVATE) { return Some(TSAccessibility::Private); } None } } #[derive(Debug, Hash)] pub struct Modifier { pub span: Span, pub kind: ModifierKind, } impl Modifier { #[inline] pub fn is_static(&self) -> bool { matches!(self.kind, ModifierKind::Static) } } impl TryFrom for Modifier { type Error = >::Error; fn try_from(tok: Token) -> std::result::Result { ModifierKind::try_from(tok.kind).map(|kind| Self { span: tok.span(), kind }) } } /// Symbol modifiers. Primarily used in TypeScript code, but some are also used /// in JavaScript. /// /// ```ts /// class Foo { /// public readonly x: number /// // ^^^^^^ ^^^^^^^^ /// // these are modifiers /// } /// export const f = new foo() /// // ^^^ This also counts as a modifier, but is also recorded separately as a /// // named export declaration /// ``` #[derive(Debug, Hash)] pub struct Modifiers<'a> { /// May contain duplicates. modifiers: Option>, /// Bitflag representation of modifier kinds stored in [`Self::modifiers`]. /// Pre-computed to save CPU cycles on [`Self::contains`] checks (`O(1)` /// bitflag intersection vs `O(n)` linear search). flags: ModifierFlags, } impl<'a> Default for Modifiers<'a> { fn default() -> Self { Self::empty() } } impl<'a> Modifiers<'a> { /// Create a new set of modifiers /// /// # Invariants /// `flags` must correctly reflect the [`ModifierKind`]s within /// `modifiers`. E.g., if `modifiers` is empty, then so is `flags``. #[must_use] pub(crate) fn new(modifiers: Vec<'a, Modifier>, flags: ModifierFlags) -> Self { if modifiers.is_empty() { debug_assert!(flags.is_empty()); Self::empty() } else { Self { modifiers: Some(modifiers), flags } } } pub fn empty() -> Self { Self { modifiers: None, flags: ModifierFlags::empty() } } pub fn contains(&self, target: ModifierKind) -> bool { self.flags.contains(target.into()) } pub fn iter(&self) -> impl Iterator + '_ { self.modifiers.as_ref().into_iter().flat_map(|modifiers| modifiers.iter()) } pub fn accessibility(&self) -> Option { self.flags.accessibility() } #[inline] pub fn contains_async(&self) -> bool { self.flags.contains(ModifierFlags::ASYNC) } #[inline] pub fn contains_const(&self) -> bool { self.flags.contains(ModifierFlags::CONST) } #[inline] pub fn contains_declare(&self) -> bool { self.flags.contains(ModifierFlags::DECLARE) } #[inline] pub fn contains_abstract(&self) -> bool { self.flags.contains(ModifierFlags::ABSTRACT) } #[inline] pub fn contains_readonly(&self) -> bool { self.flags.contains(ModifierFlags::READONLY) } #[inline] pub fn contains_override(&self) -> bool { self.flags.contains(ModifierFlags::OVERRIDE) } } impl GetSpan for Modifiers<'_> { fn span(&self) -> Span { let Some(modifiers) = &self.modifiers else { return SPAN }; debug_assert!(!modifiers.is_empty()); // SAFETY: One of Modifier's invariants is that Some(modifiers) always // contains a non-empty Vec; otherwise it must be `None`. unsafe { modifiers.iter().map(|m| m.span).reduce(|a, b| a.merge(&b)).unwrap_unchecked() } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ModifierKind { Abstract, Accessor, Async, Const, Declare, Default, Export, In, Public, Private, Protected, Readonly, Static, Out, Override, } impl ModifierKind { pub fn as_str(self) -> &'static str { match self { Self::Abstract => "abstract", Self::Accessor => "accessor", Self::Async => "async", Self::Const => "const", Self::Declare => "declare", Self::Default => "default", Self::Export => "export", Self::In => "in", Self::Public => "public", Self::Private => "private", Self::Protected => "protected", Self::Readonly => "readonly", Self::Static => "static", Self::Out => "out", Self::Override => "override", } } } impl TryFrom for ModifierKind { type Error = (); fn try_from(kind: Kind) -> std::result::Result { match kind { Kind::Abstract => Ok(Self::Abstract), Kind::Declare => Ok(Self::Declare), Kind::Private => Ok(Self::Private), Kind::Protected => Ok(Self::Protected), Kind::Public => Ok(Self::Public), Kind::Static => Ok(Self::Static), Kind::Readonly => Ok(Self::Readonly), Kind::Override => Ok(Self::Override), Kind::Async => Ok(Self::Async), Kind::Const => Ok(Self::Const), Kind::In => Ok(Self::In), Kind::Out => Ok(Self::Out), Kind::Export => Ok(Self::Export), Kind::Default => Ok(Self::Default), Kind::Accessor => Ok(Self::Accessor), _ => Err(()), } } } impl std::fmt::Display for ModifierKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_str()) } } impl<'a> ParserImpl<'a> { pub(crate) fn eat_modifiers_before_declaration(&mut self) -> Result> { let mut flags = ModifierFlags::empty(); let mut modifiers = self.ast.vec(); while self.at_modifier() { let span = self.start_span(); let modifier_flag = self.cur_kind().into(); let kind = self.cur_kind(); self.bump_any(); let modifier = self.modifier(kind, self.end_span(span))?; self.check_for_duplicate_modifiers(flags, &modifier); flags.set(modifier_flag, true); modifiers.push(modifier); } Ok(Modifiers::new(modifiers, flags)) } fn at_modifier(&mut self) -> bool { self.lookahead(Self::at_modifier_worker) } fn at_modifier_worker(&mut self) -> bool { if !self.cur_kind().is_modifier_kind() { return false; } match self.cur_kind() { Kind::Const => !self.peek_token().is_on_new_line && self.peek_kind() == Kind::Enum, Kind::Export => { self.bump_any(); match self.cur_kind() { Kind::Default => self.next_token_can_follow_default_keyword(), Kind::Type => { self.bump_any(); self.can_follow_export_modifier() } _ => self.can_follow_modifier(), } } Kind::Default => self.next_token_can_follow_default_keyword(), Kind::Accessor | Kind::Static | Kind::Get | Kind::Set => { // These modifiers can cross line. self.bump_any(); self.can_follow_modifier() } // Rest modifiers cannot cross line _ => { self.bump_any(); self.can_follow_modifier() && !self.cur_token().is_on_new_line } } } fn modifier(&mut self, kind: Kind, span: Span) -> Result { Ok(Modifier { span, kind: kind.try_into().map_err(|()| self.unexpected())? }) } pub(crate) fn parse_modifiers( &mut self, allow_decorators: bool, permit_const_as_modifier: bool, stop_on_start_of_class_static_block: bool, ) -> Modifiers<'a> { let mut has_seen_static_modifier = false; let mut has_leading_modifier = false; let mut has_trailing_decorator = false; let mut modifiers = self.ast.vec(); let mut modifier_flags = ModifierFlags::empty(); // parse leading decorators if allow_decorators && matches!(self.cur_kind(), Kind::At) { self.try_parse(Self::eat_decorators); } // parse leading modifiers while let Some(modifier) = self.try_parse_modifier( has_seen_static_modifier, permit_const_as_modifier, stop_on_start_of_class_static_block, ) { if modifier.is_static() { has_seen_static_modifier = true; } self.check_for_duplicate_modifiers(modifier_flags, &modifier); modifier_flags.set(modifier.kind.into(), true); modifiers.push(modifier); has_leading_modifier = true; } // parse trailing decorators, but only if we parsed any leading modifiers if allow_decorators && has_leading_modifier && matches!(self.cur_kind(), Kind::At) { has_trailing_decorator = self.try_parse(Self::eat_decorators).is_some(); } // parse trailing modifiers, but only if we parsed any trailing decorators if has_trailing_decorator { while let Some(modifier) = self.try_parse_modifier( has_seen_static_modifier, permit_const_as_modifier, stop_on_start_of_class_static_block, ) { if modifier.is_static() { has_seen_static_modifier = true; } self.check_for_duplicate_modifiers(modifier_flags, &modifier); modifier_flags.set(modifier.kind.into(), true); modifiers.push(modifier); } } Modifiers::new(modifiers, modifier_flags) } fn try_parse_modifier( &mut self, has_seen_static_modifier: bool, permit_const_as_modifier: bool, stop_on_start_of_class_static_block: bool, ) -> Option { let span = self.start_span(); let kind = self.cur_kind(); if matches!(self.cur_kind(), Kind::Const) { if !permit_const_as_modifier { return None; } // We need to ensure that any subsequent modifiers appear on the same line // so that when 'const' is a standalone declaration, we don't issue // an error. self.try_parse(Self::try_next_token_is_on_same_line_and_can_follow_modifier)?; } else if // we're at the start of a static block (stop_on_start_of_class_static_block && matches!(self.cur_kind(), Kind::Static) && self.lookahead(Self::next_token_is_open_brace)) // we may be at the start of a static block || (has_seen_static_modifier && matches!(self.cur_kind(), Kind::Static)) // next token is not a modifier || (!self.parse_any_contextual_modifier()) { return None; } self.modifier(kind, self.end_span(span)).ok() } fn next_token_is_open_brace(&mut self) -> bool { self.bump_any(); self.at(Kind::LCurly) } fn parse_any_contextual_modifier(&mut self) -> bool { self.cur_kind().is_modifier_kind() && self.try_parse(Self::next_token_can_follow_modifier).is_some() } fn next_token_can_follow_modifier(&mut self) -> Result<()> { let b = match self.cur_kind() { Kind::Const => self.peek_at(Kind::Enum), Kind::Export => { self.bump_any(); match self.cur_kind() { Kind::Default => self.lookahead(Self::next_token_can_follow_default_keyword), Kind::Type => self.lookahead(Self::next_token_can_follow_export_modifier), _ => self.can_follow_export_modifier(), } } Kind::Default => self.next_token_can_follow_default_keyword(), Kind::Static | Kind::Get | Kind::Set => { self.bump_any(); self.can_follow_modifier() } _ => self.next_token_is_on_same_line_and_can_follow_modifier(), }; if b { Ok(()) } else { Err(self.unexpected()) } } fn try_next_token_is_on_same_line_and_can_follow_modifier(&mut self) -> Result<()> { if self.next_token_is_on_same_line_and_can_follow_modifier() { Ok(()) } else { Err(self.unexpected()) } } fn next_token_is_on_same_line_and_can_follow_modifier(&mut self) -> bool { self.bump_any(); if self.cur_token().is_on_new_line { return false; } self.can_follow_modifier() } fn next_token_can_follow_default_keyword(&mut self) -> bool { self.bump_any(); match self.cur_kind() { Kind::Class | Kind::Function | Kind::Interface | Kind::At => true, Kind::Abstract if self.lookahead(Self::next_token_is_class_keyword_on_same_line) => { true } Kind::Async if self.lookahead(Self::next_token_is_function_keyword_on_same_line) => { true } _ => false, } } fn next_token_can_follow_export_modifier(&mut self) -> bool { self.bump_any(); self.can_follow_export_modifier() } fn can_follow_export_modifier(&mut self) -> bool { let kind = self.cur_kind(); kind == Kind::At && kind != Kind::Star && kind != Kind::As && kind != Kind::LCurly && self.can_follow_modifier() } fn can_follow_modifier(&mut self) -> bool { match self.cur_kind() { Kind::PrivateIdentifier | Kind::LBrack | Kind::LCurly | Kind::Star | Kind::Dot3 => true, kind => kind.is_identifier_or_keyword(), } } fn next_token_is_class_keyword_on_same_line(&mut self) -> bool { self.bump_any(); self.cur_kind() == Kind::Class && !self.cur_token().is_on_new_line } fn next_token_is_function_keyword_on_same_line(&mut self) -> bool { self.bump_any(); self.cur_kind() == Kind::Function && !self.cur_token().is_on_new_line } fn check_for_duplicate_modifiers(&mut self, seen_flags: ModifierFlags, modifier: &Modifier) { if seen_flags.contains(modifier.kind.into()) { self.error(diagnostics::modifier_already_seen(modifier)); } } pub(crate) fn verify_modifiers( &mut self, modifiers: &Modifiers<'a>, allowed: ModifierFlags, diagnose: F, ) where F: Fn(&Modifier) -> OxcDiagnostic, { for modifier in modifiers.iter() { if !allowed.contains(modifier.kind.into()) { self.error(diagnose(modifier)); } } } }