use std::{
fmt::{self, Debug, Display},
iter,
mem::{self, MaybeUninit},
num::NonZeroU16,
};
use oxc_ast::ast::TSAccessibility;
use oxc_data_structures::fieldless_enum;
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::Span;
use crate::{ParserConfig as Config, ParserImpl, diagnostics, lexer::Kind};
#[derive(Debug)]
pub struct Modifier {
pub span_start: u32,
pub kind: ModifierKind,
}
impl Modifier {
#[inline]
pub const fn new(span_start: u32, kind: ModifierKind) -> Self {
Self { span_start, kind }
}
#[inline]
pub const fn span(&self) -> Span {
Span::new(self.span_start, self.span_start + self.kind.len())
}
}
#[expect(clippy::module_inception)]
mod modifiers {
use super::*;
#[repr(C)]
pub struct Modifiers {
kinds: ModifierKinds,
offsets: [MaybeUninit<u32>; ModifierKind::VARIANTS.len()],
}
impl Modifiers {
#[inline]
pub const fn empty() -> Self {
Self {
kinds: ModifierKinds::none(),
offsets: [MaybeUninit::uninit(); ModifierKind::VARIANTS.len()],
}
}
#[inline]
pub const fn new_single(kind: ModifierKind, start: u32) -> Self {
let mut modifiers = Self::empty();
modifiers.add(kind, start);
modifiers
}
#[inline]
pub(super) const fn add(&mut self, kind: ModifierKind, start: u32) {
self.kinds = self.kinds.with(kind);
self.offsets[kind as usize] = MaybeUninit::new(start);
}
#[inline]
pub fn contains(&self, target: ModifierKind) -> bool {
self.kinds.contains(target)
}
#[inline]
pub fn kinds(&self) -> ModifierKinds {
self.kinds
}
pub fn iter(&self) -> impl Iterator<Item = Modifier> {
self.kinds.iter().map(|kind| {
let start = unsafe { self.offsets[kind as usize].assume_init() };
Modifier::new(start, kind)
})
}
pub fn get(&self, kind: ModifierKind) -> Option<Modifier> {
if self.kinds.contains(kind) {
let start = unsafe { self.offsets[kind as usize].assume_init() };
Some(Modifier::new(start, kind))
} else {
None
}
}
#[inline]
pub fn contains_async(&self) -> bool {
self.kinds.contains(ModifierKind::Async)
}
#[inline]
pub fn contains_const(&self) -> bool {
self.kinds.contains(ModifierKind::Const)
}
#[inline]
pub fn contains_declare(&self) -> bool {
self.kinds.contains(ModifierKind::Declare)
}
#[inline]
pub fn contains_abstract(&self) -> bool {
self.kinds.contains(ModifierKind::Abstract)
}
#[inline]
pub fn contains_readonly(&self) -> bool {
self.kinds.contains(ModifierKind::Readonly)
}
#[inline]
pub fn contains_override(&self) -> bool {
self.kinds.contains(ModifierKind::Override)
}
#[inline]
pub fn contains_accessibility(&self) -> bool {
self.kinds.intersects(ModifierKinds::new([
ModifierKind::Private,
ModifierKind::Protected,
ModifierKind::Public,
]))
}
#[inline]
pub fn accessibility(&self) -> Option<TSAccessibility> {
self.kinds.accessibility()
}
}
impl Debug for Modifiers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
}
pub use modifiers::Modifiers;
fieldless_enum! {
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum ModifierKind {
Declare = 0,
Private = 1,
Protected = 2,
Public = 3,
Static = 4,
Readonly = 5,
Abstract = 6,
Override = 7,
Async = 8,
Const = 9,
In = 10,
Out = 11,
Default = 12,
Accessor = 13,
Export = 14,
}
}
static MODIFIER_LENGTHS: [u8; ModifierKind::VARIANTS.len()] = {
let mut lengths = [0; ModifierKind::VARIANTS.len()];
let mut i = 0;
while i < ModifierKind::VARIANTS.len() {
let kind = ModifierKind::VARIANTS[i];
#[expect(clippy::cast_possible_truncation)]
let len = kind.as_str().len() as u8;
lengths[kind as usize] = len;
i += 1;
}
lengths
};
impl ModifierKind {
#[inline]
unsafe fn from_usize_unchecked(value: usize) -> Self {
debug_assert!(Self::VARIANTS.iter().any(|&kind| kind as usize == value));
#[expect(clippy::cast_possible_truncation)]
unsafe {
mem::transmute::<u8, Self>(value as u8)
}
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Abstract => "abstract",
Self::Accessor => "accessor",
Self::Async => "async",
Self::Const => "const",
Self::Declare => "declare",
Self::In => "in",
Self::Public => "public",
Self::Private => "private",
Self::Protected => "protected",
Self::Readonly => "readonly",
Self::Static => "static",
Self::Out => "out",
Self::Override => "override",
Self::Default => "default",
Self::Export => "export",
}
}
#[inline]
pub const fn len(self) -> u32 {
MODIFIER_LENGTHS[self as usize] as u32
}
}
impl TryFrom<Kind> for ModifierKind {
type Error = ();
fn try_from(kind: Kind) -> Result<Self, Self::Error> {
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::Accessor => Ok(Self::Accessor),
Kind::Default => Ok(Self::Default),
Kind::Export => Ok(Self::Export),
_ => Err(()),
}
}
}
impl Display for ModifierKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
mod modifier_kinds {
use super::*;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ModifierKinds(u16);
impl ModifierKinds {
#[inline]
pub const fn new<const N: usize>(kinds: [ModifierKind; N]) -> Self {
let mut out = Self::none();
let mut i = 0;
while i < N {
out = out.with(kinds[i]);
i += 1;
}
out
}
#[inline]
pub const fn all_except<const N: usize>(kinds: [ModifierKind; N]) -> Self {
const ALL: ModifierKinds = ModifierKinds::new(ModifierKind::VARIANTS);
Self(Self::new(kinds).0 ^ ALL.0)
}
#[inline]
pub const fn none() -> Self {
Self(0)
}
#[inline]
pub const fn contains(self, kind: ModifierKind) -> bool {
self.0 & (1 << (kind as u8)) != 0
}
#[inline]
pub const fn intersects(self, other: Self) -> bool {
self.0 & other.0 != 0
}
#[inline]
pub const fn has_any_not_in(self, other: Self) -> bool {
self.0 & !other.0 != 0
}
#[inline]
pub const fn with(self, kind: ModifierKind) -> Self {
Self(self.0 | (1 << (kind as u8)))
}
#[inline]
pub const fn without(self, kind: ModifierKind) -> Self {
Self(self.0 & !(1 << (kind as u8)))
}
#[inline]
pub const fn intersection(self, other: Self) -> Self {
Self(self.0 & other.0)
}
#[inline]
pub const fn count(self) -> usize {
self.0.count_ones() as usize
}
pub fn iter(self) -> impl Iterator<Item = ModifierKind> {
let mut remaining = self.0;
iter::from_fn(move || {
let bits = NonZeroU16::new(remaining)?;
let bit = bits.trailing_zeros();
remaining &= remaining - 1;
let kind = unsafe { ModifierKind::from_usize_unchecked(bit as usize) };
Some(kind)
})
}
#[inline]
pub fn accessibility(self) -> Option<TSAccessibility> {
const MIN_DISCRIMINANT: usize = min(
min(ModifierKind::Private as usize, ModifierKind::Protected as usize),
ModifierKind::Public as usize,
);
const _: () = {
assert!(ModifierKind::Private as usize <= MIN_DISCRIMINANT + 2);
assert!(ModifierKind::Protected as usize <= MIN_DISCRIMINANT + 2);
assert!(ModifierKind::Public as usize <= MIN_DISCRIMINANT + 2);
};
const ACCESSIBILITY_KINDS: ModifierKinds = ModifierKinds::new([
ModifierKind::Private,
ModifierKind::Protected,
ModifierKind::Public,
]);
static LUT: [Option<TSAccessibility>; 8] = {
let combinations: [ModifierKinds; 8] = [
ModifierKinds::none(),
ModifierKinds::new([ModifierKind::Private]),
ModifierKinds::new([ModifierKind::Protected]),
ModifierKinds::new([ModifierKind::Public]),
ModifierKinds::new([ModifierKind::Private, ModifierKind::Protected]),
ModifierKinds::new([ModifierKind::Private, ModifierKind::Public]),
ModifierKinds::new([ModifierKind::Protected, ModifierKind::Public]),
ACCESSIBILITY_KINDS,
];
let mut lut: [Option<TSAccessibility>; 8] = [None; 8];
let mut i = 0;
while i < lut.len() {
let combination = combinations[i];
let accessibility = match combination {
_ if combination.contains(ModifierKind::Public) => {
Some(TSAccessibility::Public)
}
_ if combination.contains(ModifierKind::Protected) => {
Some(TSAccessibility::Protected)
}
_ if combination.contains(ModifierKind::Private) => {
Some(TSAccessibility::Private)
}
_ => None,
};
let index = (combination.0 >> MIN_DISCRIMINANT) as usize;
lut[index] = accessibility;
i += 1;
}
lut
};
let access_kinds = self.intersection(ACCESSIBILITY_KINDS);
let index = (access_kinds.0 >> MIN_DISCRIMINANT) as usize;
LUT[index]
}
}
impl Display for ModifierKinds {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, kind) in self.iter().enumerate() {
if i != 0 {
f.write_str(", ")?;
}
f.write_str(kind.as_str())?;
}
Ok(())
}
}
impl Debug for ModifierKinds {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
}
pub use modifier_kinds::ModifierKinds;
impl<C: Config> ParserImpl<'_, C> {
pub(crate) fn eat_modifiers_before_declaration(&mut self) -> Modifiers {
let mut modifiers = Modifiers::empty();
while let Some(modifier_kind) = self.get_modifier() {
let modifier = Modifier::new(self.start_span(), modifier_kind);
self.bump_any();
self.check_modifier(modifiers.kinds(), &modifier);
modifiers.add(modifier.kind, modifier.span_start);
}
modifiers
}
fn get_modifier(&mut self) -> Option<ModifierKind> {
let modifier_kind = ModifierKind::try_from(self.cur_kind()).ok()?;
if self.lookahead(Self::get_modifier_worker) { Some(modifier_kind) } else { None }
}
fn get_modifier_worker(&mut self) -> bool {
match self.cur_kind() {
Kind::Const => {
self.bump_any();
self.at(Kind::Enum)
}
Kind::Accessor | Kind::Static | Kind::Get | Kind::Set => {
self.bump_any();
self.can_follow_modifier()
}
_ => {
self.bump_any();
self.can_follow_modifier() && !self.cur_token().is_on_new_line()
}
}
}
fn modifier(&mut self, kind: Kind, span_start: u32) -> Modifier {
let modifier_kind = ModifierKind::try_from(kind).unwrap_or_else(|()| {
self.set_unexpected();
ModifierKind::Abstract });
Modifier::new(span_start, modifier_kind)
}
pub(crate) fn parse_modifiers(
&mut self,
permit_const_as_modifier: bool,
stop_on_start_of_class_static_block: bool,
) -> Modifiers {
let mut modifiers = Modifiers::empty();
while let Some(modifier) = self.try_parse_modifier(
modifiers.kinds(),
permit_const_as_modifier,
stop_on_start_of_class_static_block,
) {
self.check_modifier(modifiers.kinds(), &modifier);
modifiers.add(modifier.kind, modifier.span_start);
}
modifiers
}
fn try_parse_modifier(
&mut self,
seen_modifier_kinds: ModifierKinds,
permit_const_as_modifier: bool,
stop_on_start_of_class_static_block: bool,
) -> Option<Modifier> {
let span_start = self.start_span();
let kind = self.cur_kind();
if kind == Kind::Const {
if !permit_const_as_modifier {
return None;
}
self.try_parse(Self::try_next_token_is_on_same_line_and_can_follow_modifier)?;
} else if
(stop_on_start_of_class_static_block
&& kind == Kind::Static
&& self.lexer.peek_token().kind() == Kind::LCurly)
|| (kind == Kind::Static && seen_modifier_kinds.contains(ModifierKind::Static))
|| (!self.parse_any_contextual_modifier())
{
return None;
}
Some(self.modifier(kind, span_start))
}
pub(crate) fn parse_contextual_modifier(&mut self, kind: Kind) -> bool {
self.at(kind) && self.try_parse(Self::next_token_can_follow_modifier).is_some()
}
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()
}
pub(crate) fn next_token_can_follow_modifier(&mut self) {
let b = match self.cur_kind() {
Kind::Const => {
self.bump_any();
self.at(Kind::Enum)
}
Kind::Static => {
self.bump_any();
self.can_follow_modifier()
}
Kind::Get | Kind::Set => {
self.bump_any();
self.can_follow_get_or_set_keyword()
}
_ => self.next_token_is_on_same_line_and_can_follow_modifier(),
};
if !b {
self.set_unexpected();
}
}
fn try_next_token_is_on_same_line_and_can_follow_modifier(&mut self) {
if !self.next_token_is_on_same_line_and_can_follow_modifier() {
self.set_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 can_follow_modifier(&self) -> bool {
match self.cur_kind() {
Kind::PrivateIdentifier | Kind::LBrack | Kind::LCurly | Kind::Star | Kind::Dot3 => true,
kind => kind.is_identifier_or_keyword(),
}
}
fn can_follow_get_or_set_keyword(&self) -> bool {
let kind = self.cur_kind();
kind == Kind::LBrack || kind == Kind::PrivateIdentifier || kind.is_literal_property_name()
}
}
static ILLEGAL_PRECEDING_MODIFIERS: [ModifierKinds; ModifierKind::VARIANTS.len()] = {
let mut illegal = [ModifierKinds::none(); ModifierKind::VARIANTS.len()];
let mut i = 0;
while i < illegal.len() {
let kind = ModifierKind::VARIANTS[i];
let illegal_kinds = get_illegal_preceding_modifiers(kind);
assert!(illegal_kinds.contains(kind), "Same modifier twice is always illegal");
illegal[kind as usize] = illegal_kinds;
i += 1;
}
illegal
};
const fn get_illegal_preceding_modifiers(kind: ModifierKind) -> ModifierKinds {
match kind {
ModifierKind::Public | ModifierKind::Private | ModifierKind::Protected => {
ModifierKinds::new([
ModifierKind::Public,
ModifierKind::Private,
ModifierKind::Protected,
ModifierKind::Override,
ModifierKind::Static,
ModifierKind::Accessor,
ModifierKind::Readonly,
ModifierKind::Async,
ModifierKind::Abstract,
])
}
ModifierKind::Static => ModifierKinds::new([
ModifierKind::Static,
ModifierKind::Readonly,
ModifierKind::Async,
ModifierKind::Accessor,
ModifierKind::Override,
]),
ModifierKind::Override => ModifierKinds::new([
ModifierKind::Override,
ModifierKind::Readonly,
ModifierKind::Accessor,
ModifierKind::Async,
]),
ModifierKind::Abstract => ModifierKinds::new([
ModifierKind::Abstract,
ModifierKind::Override,
ModifierKind::Accessor,
]),
ModifierKind::Export => ModifierKinds::new([
ModifierKind::Export,
ModifierKind::Declare,
ModifierKind::Abstract,
ModifierKind::Async,
]),
_ => ModifierKinds::new([kind]),
}
}
impl<C: Config> ParserImpl<'_, C> {
#[inline]
fn check_modifier(&mut self, existing_kinds: ModifierKinds, modifier: &Modifier) {
let illegal_preceding_modifier_kinds = ILLEGAL_PRECEDING_MODIFIERS[modifier.kind as usize];
if existing_kinds.intersects(illegal_preceding_modifier_kinds) {
self.illegal_modifier_error(existing_kinds, modifier);
}
}
#[cold]
#[inline(never)]
fn illegal_modifier_error(&mut self, existing_kinds: ModifierKinds, modifier: &Modifier) {
const ACCESSIBILITY_KINDS: ModifierKinds = ModifierKinds::new([
ModifierKind::Public,
ModifierKind::Private,
ModifierKind::Protected,
]);
let this_kind = modifier.kind;
let this_kinds = ModifierKinds::new([this_kind]);
if this_kinds.intersects(ACCESSIBILITY_KINDS) {
if existing_kinds.intersects(ACCESSIBILITY_KINDS) {
self.error(diagnostics::accessibility_modifier_already_seen(modifier));
return;
}
} else {
if existing_kinds.intersects(this_kinds) {
self.error(diagnostics::modifier_already_seen(modifier));
return;
}
}
let illegal_preceding_modifier_kinds = ILLEGAL_PRECEDING_MODIFIERS[this_kind as usize];
let illegal_kinds = illegal_preceding_modifier_kinds.intersection(existing_kinds);
let illegal_kind = illegal_kinds.iter().next().unwrap();
self.error(diagnostics::modifier_must_precede_other_modifier(modifier, illegal_kind));
}
#[inline]
pub(crate) fn verify_modifiers<F>(
&mut self,
modifiers: &Modifiers,
allowed: ModifierKinds,
strict: bool,
create_diagnostic: F,
) where
F: Fn(&Modifier, Option<ModifierKinds>) -> OxcDiagnostic,
{
if modifiers.kinds().has_any_not_in(allowed) {
#[cold]
#[inline(never)]
fn report<C: Config, F>(
parser: &mut ParserImpl<'_, C>,
modifiers: &Modifiers,
allowed: ModifierKinds,
strict: bool,
create_diagnostic: F,
) where
F: Fn(&Modifier, Option<ModifierKinds>) -> OxcDiagnostic,
{
let mut disallowed_modifiers = modifiers
.iter()
.filter(|modifier| !allowed.contains(modifier.kind))
.collect::<Vec<_>>();
disallowed_modifiers.sort_unstable_by_key(|modifier| modifier.span_start);
debug_assert!(!disallowed_modifiers.is_empty());
for modifier in &disallowed_modifiers {
parser.error(create_diagnostic(modifier, strict.then_some(allowed)));
}
}
report(self, modifiers, allowed, strict, create_diagnostic);
}
}
}
const fn min(a: usize, b: usize) -> usize {
if a < b { a } else { b }
}
#[cfg(test)]
mod tests {
use oxc_ast::ast::TSAccessibility;
use super::{ModifierKind, ModifierKinds};
#[test]
fn accessibility_none() {
assert_eq!(ModifierKinds::none().accessibility(), None);
}
#[test]
fn accessibility_non_accessibility_modifiers() {
for kind in ModifierKind::VARIANTS {
if matches!(
kind,
ModifierKind::Private | ModifierKind::Protected | ModifierKind::Public
) {
continue;
}
assert_eq!(ModifierKinds::new([kind]).accessibility(), None, "{kind}");
}
}
#[test]
fn accessibility_single() {
assert_eq!(
ModifierKinds::new([ModifierKind::Private]).accessibility(),
Some(TSAccessibility::Private),
);
assert_eq!(
ModifierKinds::new([ModifierKind::Protected]).accessibility(),
Some(TSAccessibility::Protected),
);
assert_eq!(
ModifierKinds::new([ModifierKind::Public]).accessibility(),
Some(TSAccessibility::Public),
);
}
#[test]
fn accessibility_with_other_modifiers() {
let kinds = ModifierKinds::new([ModifierKind::Static, ModifierKind::Public]);
assert_eq!(kinds.accessibility(), Some(TSAccessibility::Public));
let kinds = ModifierKinds::new([
ModifierKind::Async,
ModifierKind::Protected,
ModifierKind::Override,
]);
assert_eq!(kinds.accessibility(), Some(TSAccessibility::Protected));
}
#[test]
fn accessibility_multiple_accessibility_modifiers() {
let kinds = ModifierKinds::new([ModifierKind::Private, ModifierKind::Protected]);
assert_eq!(kinds.accessibility(), Some(TSAccessibility::Protected));
let kinds = ModifierKinds::new([ModifierKind::Private, ModifierKind::Public]);
assert_eq!(kinds.accessibility(), Some(TSAccessibility::Public));
let kinds = ModifierKinds::new([ModifierKind::Protected, ModifierKind::Public]);
assert_eq!(kinds.accessibility(), Some(TSAccessibility::Public));
let kinds = ModifierKinds::new([
ModifierKind::Private,
ModifierKind::Protected,
ModifierKind::Public,
]);
assert_eq!(kinds.accessibility(), Some(TSAccessibility::Public));
}
}