Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View file

@ -0,0 +1,787 @@
#![allow(dead_code)]
use std::{borrow::Cow, fmt::Debug};
use swc_atoms::JsWord;
use swc_common::{
errors::{DiagnosticBuilder, Handler},
Span, Spanned,
};
use crate::token::Token;
/// Note: this struct is 8 bytes.
#[derive(Debug, Clone, PartialEq)]
pub struct Error {
error: Box<(Span, SyntaxError)>,
}
impl Spanned for Error {
fn span(&self) -> Span {
(*self.error).0
}
}
impl Error {
#[cold]
pub(crate) fn new(span: Span, error: SyntaxError) -> Self {
Self {
error: Box::new((span, error)),
}
}
pub fn kind(&self) -> &SyntaxError {
&self.error.1
}
pub fn into_kind(self) -> SyntaxError {
self.error.1
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum SyntaxError {
Eof,
DeclNotAllowed,
UsingDeclNotAllowed,
UsingDeclNotAllowedForForInLoop,
UsingDeclNotEnabled,
InvalidNameInUsingDecl,
InitRequiredForUsingDecl,
PrivateNameInInterface,
InvalidSuperCall,
InvalidSuper,
InvalidSuperPrivateName,
InvalidNewTarget,
InvalidImport,
ArrowNotAllowed,
ExportNotAllowed,
GetterSetterCannotBeReadonly,
GetterParam,
SetterParam,
TopLevelAwaitInScript,
LegacyDecimal,
LegacyOctal,
InvalidIdentChar,
ExpectedDigit {
radix: u8,
},
SetterParamRequired,
RestPatInSetter,
UnterminatedBlockComment,
UnterminatedStrLit,
ExpectedUnicodeEscape,
EscapeInReservedWord {
word: JsWord,
},
UnterminatedRegExp,
UnterminatedTpl,
IdentAfterNum,
UnexpectedChar {
c: char,
},
InvalidStrEscape,
InvalidUnicodeEscape,
BadCharacterEscapeSequence {
expected: &'static str,
},
NumLitTerminatedWithExp,
LegacyCommentInModule,
/// "implements", "interface", "let", "package", "private", "protected",
/// "public", "static", or "yield"
InvalidIdentInStrict(JsWord),
InvalidIdentInAsync,
/// 'eval' and 'arguments' are invalid identifier in strict mode.
EvalAndArgumentsInStrict,
ArgumentsInClassField,
IllegalLanguageModeDirective,
UnaryInExp {
left: String,
left_span: Span,
},
Hash,
LineBreakInThrow,
LineBreakBeforeArrow,
/// Unexpected token
Unexpected {
got: String,
expected: &'static str,
},
UnexpectedTokenWithSuggestions {
candidate_list: Vec<&'static str>,
},
ReservedWordInImport,
AssignProperty,
Expected(&'static Token, String),
ExpectedSemiForExprStmt {
expr: Span,
},
AwaitStar,
ReservedWordInObjShorthandOrPat,
NullishCoalescingWithLogicalOp,
MultipleDefault {
/// Span of the previous default case
previous: Span,
},
CommaAfterRestElement,
NonLastRestParam,
SpreadInParenExpr,
/// `()`
EmptyParenExpr,
InvalidPat,
InvalidExpr,
NotSimpleAssign,
ExpectedIdent,
ExpectedSemi,
DuplicateLabel(JsWord),
AsyncGenerator,
NonTopLevelImportExport,
ImportExportInScript,
ImportMetaInScript,
PatVarWithoutInit,
WithInStrict,
ReturnNotAllowed,
TooManyVarInForInHead,
VarInitializerInForInHead,
LabelledGeneratorOrAsync,
LabelledFunctionInStrict,
YieldParamInGen,
AwaitParamInAsync,
AwaitForStmt,
AwaitInFunction,
UnterminatedJSXContents,
EmptyJSXAttr,
InvalidJSXValue,
JSXExpectedClosingTagForLtGt,
JSXExpectedClosingTag {
tag: JsWord,
},
InvalidLeadingDecorator,
DecoratorOnExport,
TsRequiredAfterOptional,
TsInvalidParamPropPat,
SpaceBetweenHashAndIdent,
AsyncConstructor,
PropertyNamedConstructor,
PrivateConstructor,
PrivateNameModifier(JsWord),
ConstructorAccessor,
ReadOnlyMethod,
GeneratorConstructor,
DuplicateConstructor,
TsBindingPatCannotBeOptional,
SuperCallOptional,
OptChainCannotFollowConstructorCall,
TaggedTplInOptChain,
TrailingCommaInsideImport,
ExportDefaultWithOutFrom,
DotsWithoutIdentifier,
NumericSeparatorIsAllowedOnlyBetweenTwoDigits,
ImportBindingIsString(JsWord),
ExportBindingIsString,
ConstDeclarationsRequireInitialization,
DuplicatedRegExpFlags(char),
UnknownRegExpFlags,
TS1003,
TS1005,
TS1009,
TS1014,
TS1015,
TS1029(JsWord, JsWord),
TS1030(JsWord),
TS1031,
TS1038,
TS1042,
TS1047,
TS1048,
TS1056,
TS1085,
TS1089(JsWord),
TS1092,
TS1096,
TS1098,
TS1100,
TS1102,
TS1105,
TS1106,
TS1107,
TS1109,
TS1110,
TS1114,
TS1115,
TS1116,
TS1123,
TS1141,
TS1162,
TS1164,
TS1171,
TS1172,
TS1173,
TS1174,
TS1175,
TS1183,
TS1184,
TS1185,
TS1093,
TS1196,
TS1242,
TS1243(JsWord, JsWord),
TS1244,
TS1245,
TS1267,
TS1273(JsWord),
TS1274(JsWord),
TS1277(JsWord),
TS2206,
TS2207,
TS2369,
TS2371,
TS2406,
TS2410,
TS2414,
TS2427,
TS2452,
TS2483,
TS2491,
TS2499,
TS2703,
TS4112,
TS8038,
TSTypeAnnotationAfterAssign,
TsNonNullAssertionNotAllowed(JsWord),
WithLabel {
inner: Box<Error>,
span: Span,
note: &'static str,
},
ReservedTypeAssertion,
ReservedArrowTypeParam,
}
impl SyntaxError {
#[cold]
#[inline(never)]
pub fn msg(&self) -> Cow<'static, str> {
match self {
SyntaxError::PrivateNameInInterface => {
"private names are not allowed in interface".into()
}
SyntaxError::TopLevelAwaitInScript => {
"top level await is only allowed in module".into()
}
SyntaxError::LegacyDecimal => {
"Legacy decimal escape is not permitted in strict mode".into()
}
SyntaxError::LegacyOctal => {
"Legacy octal escape is not permitted in strict mode".into()
}
SyntaxError::InvalidIdentChar => "Invalid character in identifier".into(),
SyntaxError::ExpectedDigit { radix } => format!(
"Expected {} digit",
match radix {
2 => "a binary",
8 => "an octal",
10 => "a decimal",
16 => "a hexadecimal",
_ => unreachable!(),
}
)
.into(),
SyntaxError::UnterminatedBlockComment => "Unterminated block comment".into(),
SyntaxError::UnterminatedStrLit => "Unterminated string constant".into(),
SyntaxError::ExpectedUnicodeEscape => "Expected unicode escape".into(),
SyntaxError::EscapeInReservedWord { ref word } => {
format!("Unexpected escape sequence in reserved word: {}", word).into()
}
SyntaxError::UnterminatedRegExp => "Unterminated regexp literal".into(),
SyntaxError::UnterminatedTpl => "Unterminated template".into(),
SyntaxError::IdentAfterNum => "Identifier cannot follow number".into(),
SyntaxError::UnexpectedChar { c } => format!("Unexpected character {:?}", c).into(),
SyntaxError::InvalidStrEscape => "Invalid string escape".into(),
SyntaxError::InvalidUnicodeEscape => "Invalid unicode escape".into(),
SyntaxError::BadCharacterEscapeSequence { expected } => {
format!("Bad character escape sequence, expected {}", expected).into()
}
SyntaxError::LegacyCommentInModule => {
"Legacy comments cannot be used in module code".into()
}
SyntaxError::NumLitTerminatedWithExp => "Expected +, - or decimal digit after e".into(),
SyntaxError::InvalidIdentInStrict(identifier_name) => format!(
"`{}` cannot be used as an identifier in strict mode",
identifier_name
)
.into(),
SyntaxError::InvalidIdentInAsync => {
"`await` cannot be used as an identifier in an async context".into()
}
SyntaxError::EvalAndArgumentsInStrict => "'eval' and 'arguments' cannot be used as a \
binding identifier in strict mode"
.into(),
SyntaxError::ArgumentsInClassField => {
"'arguments' is only allowed in functions and class methods".into()
}
SyntaxError::IllegalLanguageModeDirective => {
"Illegal 'use strict' directive in function with non-simple parameter list.".into()
}
SyntaxError::UnaryInExp { .. } => {
"'**' cannot be applied to unary/await expression.".into()
}
SyntaxError::Hash => "Unexpected token '#'".into(),
SyntaxError::LineBreakInThrow => "LineBreak cannot follow 'throw'".into(),
SyntaxError::LineBreakBeforeArrow => {
"Unexpected line break between arrow head and arrow".into()
}
SyntaxError::Unexpected {
ref got,
ref expected,
} => format!("Unexpected token `{}`. Expected {}", got, expected).into(),
SyntaxError::ReservedWordInImport => "cannot import as reserved word".into(),
SyntaxError::AssignProperty => "assignment property is invalid syntax".into(),
SyntaxError::Expected(token, ref got) => {
format!("Expected '{:?}', got '{}'", token, got).into()
}
SyntaxError::ExpectedSemiForExprStmt { .. } => "Expected ';', '}' or <eof>".into(),
SyntaxError::AwaitStar => "await* has been removed from the async functions proposal. \
Use Promise.all() instead."
.into(),
SyntaxError::ReservedWordInObjShorthandOrPat => {
"Cannot use a reserved word as a shorthand property".into()
}
SyntaxError::MultipleDefault { .. } => {
"A switch block cannot have multiple defaults".into()
}
SyntaxError::CommaAfterRestElement => {
"Trailing comma isn't permitted after a rest element".into()
}
SyntaxError::NonLastRestParam => "Rest element must be final element".into(),
SyntaxError::SpreadInParenExpr => {
"Parenthesized expression cannot contain spread operator".into()
}
SyntaxError::EmptyParenExpr => "Parenthesized expression cannot be empty".into(),
SyntaxError::InvalidPat => "Not a pattern".into(),
SyntaxError::InvalidExpr => "Not an expression".into(),
// TODO
SyntaxError::NotSimpleAssign => "Cannot assign to this".into(),
SyntaxError::ExpectedIdent => "Expected ident".into(),
SyntaxError::ExpectedSemi => "Expected ';' or line break".into(),
SyntaxError::DuplicateLabel(ref label) => {
format!("Label {} is already declared", label).into()
}
SyntaxError::AsyncGenerator => "An async function cannot be generator".into(),
SyntaxError::NonTopLevelImportExport => {
"'import', and 'export' are not permitted here".into()
}
SyntaxError::ImportExportInScript => {
"'import', and 'export' cannot be used outside of module code".into()
}
SyntaxError::ImportMetaInScript => {
"'import.meta' cannot be used outside of module code.".into()
}
SyntaxError::PatVarWithoutInit => "Destructuring bindings require initializers".into(),
SyntaxError::WithInStrict => "With statement are not allowed in strict mode".into(),
SyntaxError::ReturnNotAllowed => "Return statement is not allowed here".into(),
SyntaxError::TooManyVarInForInHead => "Expected one variable binding".into(),
SyntaxError::VarInitializerInForInHead => {
"Unexpected initializer in for in/of loop".into()
}
SyntaxError::LabelledGeneratorOrAsync => {
"Generator or async function cannot be labelled".into()
}
SyntaxError::LabelledFunctionInStrict => {
"Function cannot be labelled in strict mode".into()
}
SyntaxError::YieldParamInGen => {
"'yield' cannot be used as a parameter within generator".into()
}
SyntaxError::AwaitParamInAsync => {
"`await` expressions cannot be used in a parameter initializer.".into()
}
SyntaxError::AwaitForStmt => {
"for await syntax is valid only for for-of statement".into()
}
SyntaxError::AwaitInFunction => "await isn't allowed in non-async function".into(),
SyntaxError::UnterminatedJSXContents => "Unterminated JSX contents".into(),
SyntaxError::EmptyJSXAttr => {
"JSX attributes must only be assigned a non-empty expression".into()
}
SyntaxError::InvalidJSXValue => {
"JSX value should be either an expression or a quoted JSX text".into()
}
SyntaxError::JSXExpectedClosingTagForLtGt => {
"Expected corresponding JSX closing tag for <>".into()
}
SyntaxError::JSXExpectedClosingTag { ref tag } => {
format!("Expected corresponding JSX closing tag for <{}>", tag).into()
}
SyntaxError::InvalidLeadingDecorator => {
"Leading decorators must be attached to a class declaration".into()
}
SyntaxError::DecoratorOnExport => "Using the export keyword between a decorator and a \
class is not allowed. Please use `export @dec \
class` instead."
.into(),
SyntaxError::TsRequiredAfterOptional => {
"A required element cannot follow an optional element.".into()
}
SyntaxError::SuperCallOptional => "Super call cannot be optional".into(),
SyntaxError::OptChainCannotFollowConstructorCall => {
"Constructor in/after an optional chaining is not allowed.".into()
}
SyntaxError::TaggedTplInOptChain => {
"Tagged template literal is not allowed in optional chain.".into()
}
SyntaxError::TsInvalidParamPropPat => {
"Typescript parameter property must be an identifier or assignment pattern".into()
}
SyntaxError::SpaceBetweenHashAndIdent => {
"Unexpected space between # and identifier".into()
}
SyntaxError::AsyncConstructor => "Constructor can't be an async function".into(),
SyntaxError::PropertyNamedConstructor => {
"Classes may not have a non-static field named 'constructor'".into()
}
SyntaxError::PrivateConstructor => {
"Classes can't have a private field named '#constructor'.".into()
}
SyntaxError::DuplicateConstructor => "A class can only have one constructor".into(),
SyntaxError::PrivateNameModifier(modifier) => format!(
"'{}' modifier cannot be used with a private identifier",
modifier
)
.into(),
SyntaxError::ConstructorAccessor => "Class constructor can't be an accessor.".into(),
SyntaxError::ReadOnlyMethod => "A method cannot be readonly".into(),
SyntaxError::TsBindingPatCannotBeOptional => "A binding pattern parameter cannot be \
optional in an implementation signature."
.into(),
SyntaxError::TrailingCommaInsideImport => {
"Trailing comma is disallowed inside import(...) arguments".into()
}
SyntaxError::ExportDefaultWithOutFrom => {
"export default statements required from '...';".into()
}
SyntaxError::DotsWithoutIdentifier => {
"`...` must be followed by an identifier in declaration contexts".into()
}
SyntaxError::NumericSeparatorIsAllowedOnlyBetweenTwoDigits => {
"A numeric separator is only allowed between two digits".into()
}
SyntaxError::NullishCoalescingWithLogicalOp => {
"Nullish coalescing operator(??) requires parens when mixing with logical operators"
.into()
}
SyntaxError::TS1056 => {
"jsc.target should be es5 or upper to use getter / setter".into()
}
SyntaxError::TS1110 => "type expected".into(),
SyntaxError::TS1141 => "literal in an import type should be string literal".into(),
SyntaxError::Eof => "Unexpected eof".into(),
SyntaxError::TS2703 => {
"The operand of a delete operator must be a property reference.".into()
}
SyntaxError::DeclNotAllowed => "Declaration is not allowed".into(),
SyntaxError::UsingDeclNotAllowed => "Using declaration is not allowed".into(),
SyntaxError::UsingDeclNotAllowedForForInLoop => {
"Using declaration is not allowed in for-in loop".into()
}
SyntaxError::UsingDeclNotEnabled => {
"Using declaration is not enabled. Set jsc.parser.usingDecl to true".into()
}
SyntaxError::InvalidNameInUsingDecl => {
"Using declaration only allows identifiers".into()
}
SyntaxError::InitRequiredForUsingDecl => {
"Using declaration requires initializer".into()
}
SyntaxError::InvalidSuperCall => "Invalid `super()`".into(),
SyntaxError::InvalidSuper => "Invalid access to super".into(),
SyntaxError::InvalidSuperPrivateName => {
"Index super with private name is not allowed".into()
}
SyntaxError::InvalidNewTarget => "'new.target' is only allowed in the body of a \
function declaration, function expression, or class."
.into(),
SyntaxError::InvalidImport => "Import is not allowed here".into(),
SyntaxError::ArrowNotAllowed => "An arrow function is not allowed here".into(),
SyntaxError::ExportNotAllowed => "`export` is not allowed here".into(),
SyntaxError::GetterSetterCannotBeReadonly => {
"A getter or a setter cannot be readonly".into()
}
SyntaxError::GetterParam => "A `get` accessor cannot have parameters".into(),
SyntaxError::SetterParam => "A `set` accessor must have exactly one parameter".into(),
SyntaxError::RestPatInSetter => "Rest pattern is not allowed in setter".into(),
SyntaxError::GeneratorConstructor => "A constructor cannot be generator".into(),
SyntaxError::ImportBindingIsString(s) => format!(
"A string literal cannot be used as an imported binding.\n- Did you mean `import \
{{ \"{}\" as foo }}`?",
s
)
.into(),
SyntaxError::ExportBindingIsString => {
"A string literal cannot be used as an exported binding without `from`.".into()
}
SyntaxError::ConstDeclarationsRequireInitialization => {
"'const' declarations must be initialized".into()
}
SyntaxError::DuplicatedRegExpFlags(flag) => {
format!("Duplicated regular expression flag '{}'.", flag).into()
}
SyntaxError::UnknownRegExpFlags => "Unknown regular expression flags.".into(),
SyntaxError::TS1003 => "Expected an identifier".into(),
SyntaxError::TS1005 => "Expected a semicolon".into(),
SyntaxError::TS1009 => "Trailing comma is not allowed".into(),
SyntaxError::TS1014 => "A rest parameter must be last in a parameter list".into(),
SyntaxError::TS1015 => "Parameter cannot have question mark and initializer".into(),
SyntaxError::TS1029(left, right) => {
format!("'{}' modifier must precede '{}' modifier.", left, right).into()
}
SyntaxError::TS1030(word) => format!("'{}' modifier already seen.", word).into(),
SyntaxError::TS1031 => {
"`declare` modifier cannot appear on class elements of this kind".into()
}
SyntaxError::TS1038 => {
"`declare` modifier not allowed for code already in an ambient context".into()
}
SyntaxError::TS1042 => "`async` modifier cannot be used here".into(),
SyntaxError::TS1047 => "A rest parameter cannot be optional".into(),
SyntaxError::TS1048 => "A rest parameter cannot have an initializer".into(),
SyntaxError::TS1085 => "Legacy octal literals are not available when targeting \
ECMAScript 5 and higher"
.into(),
SyntaxError::TS1089(word) => format!(
"'{}' modifier cannot appear on a constructor declaration",
word
)
.into(),
SyntaxError::TS1092 => {
"Type parameters cannot appear on a constructor declaration".into()
}
SyntaxError::TS1096 => "An index signature must have exactly one parameter".into(),
SyntaxError::TS1098 => "Type parameter list cannot be empty".into(),
SyntaxError::TS1100 => "Invalid use of 'arguments' in strict mode".into(),
SyntaxError::TS1102 => {
"'delete' cannot be called on an identifier in strict mode".into()
}
SyntaxError::TS1105 => "A 'break' statement can only be used within an enclosing \
iteration or switch statement"
.into(),
SyntaxError::TS1106 => {
"The left-hand side of a `for...of` statement may not be `async`".into()
}
SyntaxError::TS1107 => "Jump target cannot cross function boundary".into(),
SyntaxError::TS1109 => "Expression expected".into(),
SyntaxError::TS1114 => "Duplicate label".into(),
SyntaxError::TS1115 => "A 'continue' statement can only jump to a label of an \
enclosing iteration statement"
.into(),
SyntaxError::TS1116 => {
"A 'break' statement can only jump to a label of an enclosing statement".into()
}
SyntaxError::TS1123 => "Variable declaration list cannot be empty".into(),
SyntaxError::TS1162 => "An object member cannot be declared optional".into(),
SyntaxError::TS1164 => "Computed property names are not allowed in enums".into(),
SyntaxError::TS1171 => {
"A comma expression is not allowed in a computed property name".into()
}
SyntaxError::TS1172 => "`extends` clause already seen.".into(),
SyntaxError::TS1173 => "'extends' clause must precede 'implements' clause.".into(),
SyntaxError::TS1174 => "Classes can only extend a single class".into(),
SyntaxError::TS1175 => "`implements` clause already seen".into(),
SyntaxError::TS1183 => {
"An implementation cannot be declared in ambient contexts".into()
}
SyntaxError::TS1184 => "Modifiers cannot appear here".into(),
SyntaxError::TS1185 => "Merge conflict marker encountered.".into(),
SyntaxError::TS1093 => {
"Type annotation cannot appear on a constructor declaration".into()
}
SyntaxError::TS1196 => "Catch clause variable cannot have a type annotation".into(),
SyntaxError::TS1242 => {
"`abstract` modifier can only appear on a class or method declaration".into()
}
SyntaxError::TS1244 => {
"Abstract methods can only appear within an abstract class.".into()
}
SyntaxError::TS1243(left, right) => format!(
"'{}' modifier cannot be used with '{}' modifier.",
left, right
)
.into(),
SyntaxError::TS1245 => "Abstract method cannot have an implementation.".into(),
SyntaxError::TS1267 => "Abstract property cannot have an initializer.".into(),
SyntaxError::TS1273(word) => {
format!("'{}' modifier cannot appear on a type parameter", word).into()
}
SyntaxError::TS1274(word) => format!(
"'{}' modifier can only appear on a type parameter of a class, interface or type \
alias",
word
)
.into(),
SyntaxError::TS1277(word) => format!(
"'{}' modifier can only appear on a type parameter of a function, method or class",
word
)
.into(),
SyntaxError::TS2206 => "The 'type' modifier cannot be used on a named import when \
'import type' is used on its import statement."
.into(),
SyntaxError::TS2207 => "The 'type' modifier cannot be used on a named export when \
'export type' is used on its export statement."
.into(),
SyntaxError::TS2369 => {
"A parameter property is only allowed in a constructor implementation".into()
}
SyntaxError::TS2371 => "A parameter initializer is only allowed in a function or \
constructor implementation"
.into(),
SyntaxError::TS2406 => "The left-hand side of an assignment expression must be a \
variable or a property access."
.into(),
SyntaxError::TS2410 => "The 'with' statement is not supported. All symbols in a \
'with' block will have type 'any'."
.into(),
SyntaxError::TS2414 => "Invalid class name".into(),
SyntaxError::TS2427 => "interface name is invalid".into(),
SyntaxError::TS2452 => "An enum member cannot have a numeric name".into(),
SyntaxError::TS2483 => {
"The left-hand side of a 'for...of' statement cannot use a type annotation".into()
}
SyntaxError::TS2491 => "The left-hand side of a 'for...in' statement cannot be a \
destructuring pattern"
.into(),
SyntaxError::TS2499 => "An interface can only extend an identifier/qualified-name \
with optional type arguments."
.into(),
SyntaxError::TS4112 => "This member cannot have an 'override' modifier because its \
containing class does not extend another class."
.into(),
SyntaxError::TS8038 => "Decorators may not appear after `export` or `export default` \
if they also appear before `export`."
.into(),
SyntaxError::TSTypeAnnotationAfterAssign => {
"Type annotations must come before default assignments".into()
}
SyntaxError::TsNonNullAssertionNotAllowed(word) => format!(
"Typescript non-null assertion operator is not allowed with '{}'",
word
)
.into(),
SyntaxError::SetterParamRequired => "Setter should have exactly one parameter".into(),
SyntaxError::UnexpectedTokenWithSuggestions {
candidate_list: token_list,
} => {
let did_you_mean = if token_list.len() <= 2 {
token_list.join(" or ")
} else {
token_list[0..token_list.len() - 1].join(" , ")
+ &*format!("or {}", token_list[token_list.len() - 1])
};
format!("Unexpected token. Did you mean {}?", did_you_mean).into()
}
SyntaxError::WithLabel { inner, .. } => inner.error.1.msg(),
SyntaxError::ReservedTypeAssertion => "This syntax is reserved in files with the .mts \
or .cts extension. Use an `as` expression \
instead."
.into(),
SyntaxError::ReservedArrowTypeParam => "This syntax is reserved in files with the \
.mts or .cts extension. Add a trailing comma, \
as in `<T,>() => ...`."
.into(),
}
}
}
impl Error {
#[cold]
#[inline(never)]
pub fn into_diagnostic(self, handler: &Handler) -> DiagnosticBuilder {
if let SyntaxError::WithLabel { inner, note, span } = self.error.1 {
let mut db = inner.into_diagnostic(handler);
db.span_label(span, note);
return db;
}
let span = self.span();
let kind = self.into_kind();
let msg = kind.msg();
let mut db = handler.struct_span_err(span, &msg);
match kind {
SyntaxError::ExpectedSemiForExprStmt { expr } => {
db.span_label(
expr,
"This is the expression part of an expression statement",
);
}
SyntaxError::MultipleDefault { previous } => {
db.span_label(previous, "previous default case is declared at here");
}
_ => {}
}
db
}
}
#[test]
fn size_of_error() {
assert_eq!(std::mem::size_of::<Error>(), 8);
}

View file

@ -0,0 +1,87 @@
use std::{iter::Rev, rc::Rc, vec::IntoIter};
use swc_common::{comments::Comment, BytePos};
#[derive(Clone)]
pub(crate) struct BufferedComment {
pub kind: BufferedCommentKind,
pub pos: BytePos,
pub comment: Comment,
}
#[derive(Clone)]
pub(crate) enum BufferedCommentKind {
Leading,
Trailing,
}
#[derive(Clone)]
pub(crate) struct CommentsBuffer {
comments: OneDirectionalList<BufferedComment>,
pending_leading: OneDirectionalList<Comment>,
}
impl CommentsBuffer {
pub fn new() -> Self {
Self {
comments: OneDirectionalList::new(),
pending_leading: OneDirectionalList::new(),
}
}
pub fn push(&mut self, comment: BufferedComment) {
self.comments.push(comment);
}
pub fn push_pending_leading(&mut self, comment: Comment) {
self.pending_leading.push(comment);
}
pub fn take_comments(&mut self) -> Rev<IntoIter<BufferedComment>> {
self.comments.take_all()
}
pub fn take_pending_leading(&mut self) -> Rev<IntoIter<Comment>> {
self.pending_leading.take_all()
}
}
/// A one direction linked list that can be cheaply
/// cloned with the clone maintaining its position in the list.
#[derive(Clone)]
struct OneDirectionalList<T: Clone> {
last_node: Option<Rc<OneDirectionalListNode<T>>>,
}
impl<T: Clone> OneDirectionalList<T> {
pub fn new() -> Self {
Self { last_node: None }
}
pub fn take_all(&mut self) -> Rev<IntoIter<T>> {
// these are stored in reverse, so we need to reverse them back
let mut items = Vec::new();
let mut current_node = self.last_node.take();
while let Some(node) = current_node {
let mut node = match Rc::try_unwrap(node) {
Ok(n) => n,
Err(n) => n.as_ref().clone(),
};
items.push(node.item);
current_node = node.previous.take();
}
items.into_iter().rev()
}
pub fn push(&mut self, item: T) {
let previous = self.last_node.take();
let new_item = OneDirectionalListNode { item, previous };
self.last_node = Some(Rc::new(new_item));
}
}
#[derive(Clone)]
struct OneDirectionalListNode<T: Clone> {
item: T,
previous: Option<Rc<OneDirectionalListNode<T>>>,
}

View file

@ -0,0 +1 @@
pub use swc_common::input::*;

View file

@ -0,0 +1,626 @@
use either::Either;
use swc_atoms::Atom;
use super::*;
impl<'a> Lexer<'a> {
pub(super) fn read_jsx_token(&mut self) -> LexResult<Option<Token>> {
debug_assert!(self.syntax.jsx());
let mut chunk_start = self.input.cur_pos();
let mut out = String::new();
loop {
let cur = match self.input.cur() {
Some(c) => c,
None => {
let start = self.state.start;
self.error(start, SyntaxError::UnterminatedJSXContents)?
}
};
let cur_pos = self.input.cur_pos();
match cur {
'<' if self.had_line_break_before_last() && self.is_str("<<<<<< ") => {
let span = Span::new(cur_pos, cur_pos + BytePos(7), Default::default());
self.emit_error_span(span, SyntaxError::TS1185);
self.skip_line_comment(6);
self.skip_space::<true>()?;
return self.read_token();
}
'<' | '{' => {
//
if cur_pos == self.state.start {
if cur == '<' && self.state.is_expr_allowed {
unsafe {
// Safety: cur() was Some('<')
self.input.bump();
}
return Ok(Token::JSXTagStart).map(Some);
}
return self.read_token();
}
out.push_str(unsafe {
// Safety: We already checked for the range
self.input.slice(chunk_start, cur_pos)
});
return Ok(Token::JSXText {
raw: Atom::new(out),
})
.map(Some);
}
'>' => {
self.emit_error(
cur_pos,
SyntaxError::UnexpectedTokenWithSuggestions {
candidate_list: vec!["`{'>'}`", "`&gt;`"],
},
);
unsafe {
// Safety: cur() was Some('>')
self.input.bump()
}
}
'}' => {
self.emit_error(
cur_pos,
SyntaxError::UnexpectedTokenWithSuggestions {
candidate_list: vec!["`{'}'}`", "`&rbrace;`"],
},
);
unsafe {
// Safety: cur() was Some('}')
self.input.bump()
}
}
'&' => {
out.push_str(unsafe {
// Safety: We already checked for the range
self.input.slice(chunk_start, cur_pos)
});
let jsx_entity = self.read_jsx_entity()?;
out.push(jsx_entity.0);
chunk_start = self.input.cur_pos();
}
_ => {
if cur.is_line_terminator() {
out.push_str(unsafe {
// Safety: We already checked for the range
self.input.slice(chunk_start, cur_pos)
});
match self.read_jsx_new_line(true)? {
Either::Left(s) => out.push_str(s),
Either::Right(c) => out.push(c),
}
chunk_start = cur_pos;
} else {
unsafe {
// Safety: cur() was Some(c)
self.input.bump()
}
}
}
}
}
}
pub(super) fn read_jsx_entity(&mut self) -> LexResult<(char, String)> {
debug_assert!(self.syntax.jsx());
fn from_code(s: &str, radix: u32) -> LexResult<char> {
// TODO(kdy1): unwrap -> Err
let c = char::from_u32(
u32::from_str_radix(s, radix).expect("failed to parse string as number"),
)
.expect("failed to parse number as char");
Ok(c)
}
fn is_hex(s: &str) -> bool {
s.chars().all(|c| c.is_ascii_hexdigit())
}
fn is_dec(s: &str) -> bool {
s.chars().all(|c| c.is_ascii_digit())
}
let mut s = String::new();
let c = self.input.cur();
debug_assert_eq!(c, Some('&'));
unsafe {
// Safety: cur() was Some('&')
self.input.bump();
}
let start_pos = self.input.cur_pos();
for _ in 0..10 {
let c = match self.input.cur() {
Some(c) => c,
None => break,
};
unsafe {
// Safety: cur() was Some(c)
self.input.bump();
}
if c == ';' {
if let Some(stripped) = s.strip_prefix('#') {
if stripped.starts_with('x') {
if is_hex(&s[2..]) {
let value = from_code(&s[2..], 16)?;
return Ok((value, format!("&{};", s)));
}
} else if is_dec(stripped) {
let value = from_code(stripped, 10)?;
return Ok((value, format!("&{};", s)));
}
} else if let Some(entity) = xhtml(&s) {
return Ok((entity, format!("&{};", s)));
}
break;
}
s.push(c)
}
unsafe {
// Safety: start_pos is a valid position because we got it from self.input
self.input.reset_to(start_pos);
}
Ok(('&', "&".to_string()))
}
pub(super) fn read_jsx_new_line(
&mut self,
normalize_crlf: bool,
) -> LexResult<Either<&'static str, char>> {
debug_assert!(self.syntax.jsx());
let ch = self.input.cur().unwrap();
unsafe {
// Safety: cur() was Some(ch)
self.input.bump();
}
let out = if ch == '\r' && self.input.cur() == Some('\n') {
unsafe {
// Safety: cur() was Some('\n')
self.input.bump();
}
Either::Left(if normalize_crlf { "\n" } else { "\r\n" })
} else {
Either::Right(ch)
};
let cur_pos = self.input.cur_pos();
self.state.cur_line += 1;
self.state.line_start = cur_pos;
Ok(out)
}
pub(super) fn read_jsx_str(&mut self, quote: char) -> LexResult<Token> {
debug_assert!(self.syntax.jsx());
let mut raw = String::new();
raw.push(quote);
unsafe {
// Safety: cur() was Some(quote)
self.input.bump(); // `quote`
}
let mut out = String::new();
let mut chunk_start = self.input.cur_pos();
loop {
let ch = match self.input.cur() {
Some(c) => c,
None => {
let start = self.state.start;
self.emit_error(start, SyntaxError::UnterminatedStrLit);
break;
}
};
let cur_pos = self.input.cur_pos();
if ch == '\\' {
let value = unsafe {
// Safety: We already checked for the range
self.input.slice(chunk_start, cur_pos)
};
out.push_str(value);
out.push('\\');
raw.push_str(value);
raw.push('\\');
self.bump();
chunk_start = self.input.cur_pos();
continue;
}
if ch == quote {
break;
}
if ch == '&' {
let value = unsafe {
// Safety: We already checked for the range
self.input.slice(chunk_start, cur_pos)
};
out.push_str(value);
raw.push_str(value);
let jsx_entity = self.read_jsx_entity()?;
out.push(jsx_entity.0);
raw.push_str(&jsx_entity.1);
chunk_start = self.input.cur_pos();
} else if ch.is_line_terminator() {
let value = unsafe {
// Safety: We already checked for the range
self.input.slice(chunk_start, cur_pos)
};
out.push_str(value);
raw.push_str(value);
match self.read_jsx_new_line(false)? {
Either::Left(s) => {
out.push_str(s);
raw.push_str(s);
}
Either::Right(c) => {
out.push(c);
raw.push(c);
}
}
chunk_start = cur_pos + BytePos(ch.len_utf8() as _);
} else {
unsafe {
// Safety: cur() was Some(ch)
self.input.bump();
}
}
}
let cur_pos = self.input.cur_pos();
let value = unsafe {
// Safety: We already checked for the range
self.input.slice(chunk_start, cur_pos)
};
out.push_str(value);
raw.push_str(value);
// it might be at the end of the file when
// the string literal is unterminated
if self.input.peek_ahead().is_some() {
unsafe {
// Safety: We called peek_ahead() which means cur() was Some
self.input.bump();
}
}
raw.push(quote);
Ok(Token::Str {
value: out.into(),
raw: Atom::new(raw),
})
}
/// Read a JSX identifier (valid tag or attribute name).
///
/// Optimized version since JSX identifiers can"t contain
/// escape characters and so can be read as single slice.
/// Also assumes that first character was already checked
/// by isIdentifierStart in readToken.
pub(super) fn read_jsx_word(&mut self) -> LexResult<Token> {
debug_assert!(self.syntax.jsx());
debug_assert!(self.input.cur().is_some());
debug_assert!(self.input.cur().unwrap().is_ident_start());
let mut first = true;
let slice = self.input.uncons_while(|c| {
if first {
first = false;
c.is_ident_start()
} else {
c.is_ident_part() || c == '-'
}
});
Ok(Token::JSXName { name: slice.into() })
}
}
macro_rules! xhtml {
(
$(
$i:ident : $s:expr,
)*
) => {
fn xhtml(s: &str) -> Option<char> {
match s{
$(stringify!($i) => Some($s),)*
_ => None,
}
}
};
}
xhtml!(
quot: '\u{0022}',
amp: '&',
apos: '\u{0027}',
lt: '<',
gt: '>',
nbsp: '\u{00A0}',
iexcl: '\u{00A1}',
cent: '\u{00A2}',
pound: '\u{00A3}',
curren: '\u{00A4}',
yen: '\u{00A5}',
brvbar: '\u{00A6}',
sect: '\u{00A7}',
uml: '\u{00A8}',
copy: '\u{00A9}',
ordf: '\u{00AA}',
laquo: '\u{00AB}',
not: '\u{00AC}',
shy: '\u{00AD}',
reg: '\u{00AE}',
macr: '\u{00AF}',
deg: '\u{00B0}',
plusmn: '\u{00B1}',
sup2: '\u{00B2}',
sup3: '\u{00B3}',
acute: '\u{00B4}',
micro: '\u{00B5}',
para: '\u{00B6}',
middot: '\u{00B7}',
cedil: '\u{00B8}',
sup1: '\u{00B9}',
ordm: '\u{00BA}',
raquo: '\u{00BB}',
frac14: '\u{00BC}',
frac12: '\u{00BD}',
frac34: '\u{00BE}',
iquest: '\u{00BF}',
Agrave: '\u{00C0}',
Aacute: '\u{00C1}',
Acirc: '\u{00C2}',
Atilde: '\u{00C3}',
Auml: '\u{00C4}',
Aring: '\u{00C5}',
AElig: '\u{00C6}',
Ccedil: '\u{00C7}',
Egrave: '\u{00C8}',
Eacute: '\u{00C9}',
Ecirc: '\u{00CA}',
Euml: '\u{00CB}',
Igrave: '\u{00CC}',
Iacute: '\u{00CD}',
Icirc: '\u{00CE}',
Iuml: '\u{00CF}',
ETH: '\u{00D0}',
Ntilde: '\u{00D1}',
Ograve: '\u{00D2}',
Oacute: '\u{00D3}',
Ocirc: '\u{00D4}',
Otilde: '\u{00D5}',
Ouml: '\u{00D6}',
times: '\u{00D7}',
Oslash: '\u{00D8}',
Ugrave: '\u{00D9}',
Uacute: '\u{00DA}',
Ucirc: '\u{00DB}',
Uuml: '\u{00DC}',
Yacute: '\u{00DD}',
THORN: '\u{00DE}',
szlig: '\u{00DF}',
agrave: '\u{00E0}',
aacute: '\u{00E1}',
acirc: '\u{00E2}',
atilde: '\u{00E3}',
auml: '\u{00E4}',
aring: '\u{00E5}',
aelig: '\u{00E6}',
ccedil: '\u{00E7}',
egrave: '\u{00E8}',
eacute: '\u{00E9}',
ecirc: '\u{00EA}',
euml: '\u{00EB}',
igrave: '\u{00EC}',
iacute: '\u{00ED}',
icirc: '\u{00EE}',
iuml: '\u{00EF}',
eth: '\u{00F0}',
ntilde: '\u{00F1}',
ograve: '\u{00F2}',
oacute: '\u{00F3}',
ocirc: '\u{00F4}',
otilde: '\u{00F5}',
ouml: '\u{00F6}',
divide: '\u{00F7}',
oslash: '\u{00F8}',
ugrave: '\u{00F9}',
uacute: '\u{00FA}',
ucirc: '\u{00FB}',
uuml: '\u{00FC}',
yacute: '\u{00FD}',
thorn: '\u{00FE}',
yuml: '\u{00FF}',
OElig: '\u{0152}',
oelig: '\u{0153}',
Scaron: '\u{0160}',
scaron: '\u{0161}',
Yuml: '\u{0178}',
fnof: '\u{0192}',
circ: '\u{02C6}',
tilde: '\u{02DC}',
Alpha: '\u{0391}',
Beta: '\u{0392}',
Gamma: '\u{0393}',
Delta: '\u{0394}',
Epsilon: '\u{0395}',
Zeta: '\u{0396}',
Eta: '\u{0397}',
Theta: '\u{0398}',
Iota: '\u{0399}',
Kappa: '\u{039A}',
Lambda: '\u{039B}',
Mu: '\u{039C}',
Nu: '\u{039D}',
Xi: '\u{039E}',
Omicron: '\u{039F}',
Pi: '\u{03A0}',
Rho: '\u{03A1}',
Sigma: '\u{03A3}',
Tau: '\u{03A4}',
Upsilon: '\u{03A5}',
Phi: '\u{03A6}',
Chi: '\u{03A7}',
Psi: '\u{03A8}',
Omega: '\u{03A9}',
alpha: '\u{03B1}',
beta: '\u{03B2}',
gamma: '\u{03B3}',
delta: '\u{03B4}',
epsilon: '\u{03B5}',
zeta: '\u{03B6}',
eta: '\u{03B7}',
theta: '\u{03B8}',
iota: '\u{03B9}',
kappa: '\u{03BA}',
lambda: '\u{03BB}',
mu: '\u{03BC}',
nu: '\u{03BD}',
xi: '\u{03BE}',
omicron: '\u{03BF}',
pi: '\u{03C0}',
rho: '\u{03C1}',
sigmaf: '\u{03C2}',
sigma: '\u{03C3}',
tau: '\u{03C4}',
upsilon: '\u{03C5}',
phi: '\u{03C6}',
chi: '\u{03C7}',
psi: '\u{03C8}',
omega: '\u{03C9}',
thetasym: '\u{03D1}',
upsih: '\u{03D2}',
piv: '\u{03D6}',
ensp: '\u{2002}',
emsp: '\u{2003}',
thinsp: '\u{2009}',
zwnj: '\u{200C}',
zwj: '\u{200D}',
lrm: '\u{200E}',
rlm: '\u{200F}',
ndash: '\u{2013}',
mdash: '\u{2014}',
lsquo: '\u{2018}',
rsquo: '\u{2019}',
sbquo: '\u{201A}',
ldquo: '\u{201C}',
rdquo: '\u{201D}',
bdquo: '\u{201E}',
dagger: '\u{2020}',
Dagger: '\u{2021}',
bull: '\u{2022}',
hellip: '\u{2026}',
permil: '\u{2030}',
prime: '\u{2032}',
Prime: '\u{2033}',
lsaquo: '\u{2039}',
rsaquo: '\u{203A}',
oline: '\u{203E}',
frasl: '\u{2044}',
euro: '\u{20AC}',
image: '\u{2111}',
weierp: '\u{2118}',
real: '\u{211C}',
trade: '\u{2122}',
alefsym: '\u{2135}',
larr: '\u{2190}',
uarr: '\u{2191}',
rarr: '\u{2192}',
darr: '\u{2193}',
harr: '\u{2194}',
crarr: '\u{21B5}',
lArr: '\u{21D0}',
uArr: '\u{21D1}',
rArr: '\u{21D2}',
dArr: '\u{21D3}',
hArr: '\u{21D4}',
forall: '\u{2200}',
part: '\u{2202}',
exist: '\u{2203}',
empty: '\u{2205}',
nabla: '\u{2207}',
isin: '\u{2208}',
notin: '\u{2209}',
ni: '\u{220B}',
prod: '\u{220F}',
sum: '\u{2211}',
minus: '\u{2212}',
lowast: '\u{2217}',
radic: '\u{221A}',
prop: '\u{221D}',
infin: '\u{221E}',
ang: '\u{2220}',
and: '\u{2227}',
or: '\u{2228}',
cap: '\u{2229}',
cup: '\u{222A}',
int: '\u{222B}',
there4: '\u{2234}',
sim: '\u{223C}',
cong: '\u{2245}',
asymp: '\u{2248}',
ne: '\u{2260}',
equiv: '\u{2261}',
le: '\u{2264}',
ge: '\u{2265}',
sub: '\u{2282}',
sup: '\u{2283}',
nsub: '\u{2284}',
sube: '\u{2286}',
supe: '\u{2287}',
oplus: '\u{2295}',
otimes: '\u{2297}',
perp: '\u{22A5}',
sdot: '\u{22C5}',
lceil: '\u{2308}',
rceil: '\u{2309}',
lfloor: '\u{230A}',
rfloor: '\u{230B}',
lang: '\u{2329}',
rang: '\u{232A}',
loz: '\u{25CA}',
spades: '\u{2660}',
clubs: '\u{2663}',
hearts: '\u{2665}',
diams: '\u{2666}',
);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,852 @@
//! Lexer methods related to reading numbers.
//!
//!
//! See https://tc39.github.io/ecma262/#sec-literals-numeric-literals
use std::{borrow::Cow, fmt::Write};
use either::Either;
use num_bigint::BigInt as BigIntValue;
use num_traits::{Num as NumTrait, ToPrimitive};
use smartstring::{LazyCompact, SmartString};
use swc_common::SyntaxContext;
use tracing::trace;
use super::*;
use crate::error::SyntaxError;
struct LazyBigInt<const RADIX: u8> {
value: String,
}
impl<const RADIX: u8> LazyBigInt<RADIX> {
fn new(value: String) -> Self {
Self { value }
}
#[inline]
fn into_value(self) -> BigIntValue {
BigIntValue::parse_bytes(self.value.as_bytes(), RADIX as _)
.expect("failed to parse string as a bigint")
}
}
impl<'a> Lexer<'a> {
/// Reads an integer, octal integer, or floating-point number
pub(super) fn read_number(
&mut self,
starts_with_dot: bool,
) -> LexResult<Either<(f64, Atom), (Box<BigIntValue>, Atom)>> {
debug_assert!(self.cur().is_some());
if starts_with_dot {
debug_assert_eq!(
self.cur(),
Some('.'),
"read_number(starts_with_dot = true) expects current char to be '.'"
);
}
let start = self.cur_pos();
let mut raw_val = SmartString::<LazyCompact>::new();
let mut raw_str = SmartString::<LazyCompact>::new();
let val = if starts_with_dot {
// first char is '.'
0f64
} else {
let starts_with_zero = self.cur().unwrap() == '0';
// Use read_number_no_dot to support long numbers.
let (val, s, mut raw, not_octal) = self.read_number_no_dot_as_str::<10>()?;
if self.eat(b'n') {
raw.push('n');
return Ok(Either::Right((
Box::new(s.into_value()),
self.atoms.borrow_mut().intern(&*raw),
)));
}
write!(raw_val, "{}", &s.value).unwrap();
raw_str.push_str(&raw);
if starts_with_zero {
// TODO: I guess it would be okay if I don't use -ffast-math
// (or something like that), but needs review.
if val == 0.0f64 {
// If only one zero is used, it's decimal.
// And if multiple zero is used, it's octal.
//
// e.g. `0` is decimal (so it can be part of float)
//
// e.g. `000` is octal
if start.0 != self.last_pos().0 - 1 {
// `-1` is utf 8 length of `0`
return self.make_legacy_octal(start, 0f64).map(|value| {
Either::Left((value, self.atoms.borrow_mut().intern(&*raw)))
});
}
} else {
// strict mode hates non-zero decimals starting with zero.
// e.g. 08.1 is strict mode violation but 0.1 is valid float.
if val.fract() == 0.0 {
let val_str = &s.value;
// if it contains '8' or '9', it's decimal.
if not_octal {
// Continue parsing
self.emit_strict_mode_error(start, SyntaxError::LegacyDecimal);
} else {
// It's Legacy octal, and we should reinterpret value.
let val = BigIntValue::from_str_radix(val_str, 8)
.unwrap_or_else(|err| {
panic!(
"failed to parse {} using `from_str_radix`: {:?}",
val_str, err
)
})
.to_f64()
.unwrap_or_else(|| {
panic!("failed to parse {} into float using BigInt", val_str)
});
return self.make_legacy_octal(start, val).map(|value| {
Either::Left((value, self.atoms.borrow_mut().intern(&*raw)))
});
}
}
}
}
val
};
// At this point, number cannot be an octal literal.
let mut val: f64 = val;
// `0.a`, `08.a`, `102.a` are invalid.
//
// `.1.a`, `.1e-4.a` are valid,
if self.cur() == Some('.') {
raw_val.push('.');
raw_str.push('.');
self.bump();
if starts_with_dot {
debug_assert!(self.cur().is_some());
debug_assert!(self.cur().unwrap().is_ascii_digit());
}
let mut raw = Raw(Some(Default::default()));
// Read numbers after dot
let dec_val = self.read_int::<10>(0, &mut raw)?;
raw_str.push_str(raw.0.as_ref().unwrap());
val = {
if dec_val.is_some() {
raw_val.push_str(raw.0.as_ref().unwrap());
}
// Remove number separator from number
if raw_val.contains('_') {
Cow::Owned(raw_val.replace('_', ""))
} else {
Cow::Borrowed(&*raw_val)
}
.parse()
.expect("failed to parse float using rust's impl")
};
}
// Handle 'e' and 'E'
//
// .5e1 = 5
// 1e2 = 100
// 1e+2 = 100
// 1e-2 = 0.01
match self.cur() {
Some(e @ 'e') | Some(e @ 'E') => {
self.bump();
let next = match self.cur() {
Some(next) => next,
None => {
let pos = self.cur_pos();
self.error(pos, SyntaxError::NumLitTerminatedWithExp)?
}
};
raw_val.push('e');
raw_str.push(e);
let positive = if next == '+' || next == '-' {
self.bump(); // remove '+', '-'
raw_str.push(next);
next == '+'
} else {
true
};
let mut raw = Raw(Some(Default::default()));
let exp = self.read_number_no_dot::<10>(&mut raw)?;
raw_str.push_str(&raw.0.take().unwrap());
val = if exp == f64::INFINITY {
if positive && val != 0.0 {
f64::INFINITY
} else {
0.0
}
} else {
let flag = if positive { '+' } else { '-' };
raw_val.push(flag);
write!(raw_val, "{}", exp).unwrap();
if raw_val.contains('_') {
Cow::Owned(raw_val.replace('_', ""))
} else {
Cow::Borrowed(&*raw_val)
}
.parse()
.expect("failed to parse float literal")
}
}
_ => {}
}
self.ensure_not_ident()?;
Ok(Either::Left((
val,
self.atoms.borrow_mut().intern(&*raw_str),
)))
}
/// Returns `Left(value)` or `Right(BigInt)`
pub(super) fn read_radix_number<const RADIX: u8>(
&mut self,
) -> LexResult<Either<(f64, Atom), (Box<BigIntValue>, Atom)>> {
debug_assert!(
RADIX == 2 || RADIX == 8 || RADIX == 16,
"radix should be one of 2, 8, 16, but got {}",
RADIX
);
debug_assert_eq!(self.cur(), Some('0'));
self.with_buf(|l, buf| {
l.bump();
buf.push('0');
let c = match l.input.cur() {
Some(c) => {
l.bump();
c
}
_ => {
unreachable!();
}
};
buf.push(c);
let (val, s, raw, _) = l.read_number_no_dot_as_str::<RADIX>()?;
buf.push_str(&raw);
if l.eat(b'n') {
buf.push('n');
return Ok(Either::Right((
Box::new(s.into_value()),
l.atoms.borrow_mut().intern(&**buf),
)));
}
l.ensure_not_ident()?;
Ok(Either::Left((val, l.atoms.borrow_mut().intern(&**buf))))
})
}
/// This can read long integers like
/// "13612536612375123612312312312312312312312".
fn read_number_no_dot<const RADIX: u8>(&mut self, raw: &mut Raw) -> LexResult<f64> {
debug_assert!(
RADIX == 2 || RADIX == 8 || RADIX == 10 || RADIX == 16,
"radix for read_number_no_dot should be one of 2, 8, 10, 16, but got {}",
RADIX
);
let start = self.cur_pos();
let mut read_any = false;
let res = self.read_digits::<_, f64, RADIX>(
|total, radix, v| {
read_any = true;
Ok((f64::mul_add(total, radix as f64, v as f64), true))
},
raw,
true,
);
if !read_any {
self.error(start, SyntaxError::ExpectedDigit { radix: RADIX })?;
}
res
}
/// This can read long integers like
/// "13612536612375123612312312312312312312312".
///
///
/// Returned bool is `true` is there was `8` or `9`.
fn read_number_no_dot_as_str<const RADIX: u8>(
&mut self,
) -> LexResult<(f64, LazyBigInt<RADIX>, SmartString<LazyCompact>, bool)> {
debug_assert!(
RADIX == 2 || RADIX == 8 || RADIX == 10 || RADIX == 16,
"radix for read_number_no_dot should be one of 2, 8, 10, 16, but got {}",
RADIX
);
let start = self.cur_pos();
let mut non_octal = false;
let mut read_any = false;
let mut raw = Raw(Some(Default::default()));
self.read_digits::<_, f64, RADIX>(
|total, radix, v| {
read_any = true;
if v == 8 || v == 9 {
non_octal = true;
}
Ok((f64::mul_add(total, radix as f64, v as f64), true))
},
&mut raw,
true,
)?;
if !read_any {
self.error(start, SyntaxError::ExpectedDigit { radix: RADIX })?;
}
let raw_str = raw.0.take().unwrap();
// Remove number separator from number
let raw_number_str = raw_str.replace('_', "");
let parsed_float = BigIntValue::from_str_radix(&raw_number_str, RADIX as u32)
.expect("failed to parse float using BigInt")
.to_f64()
.expect("failed to parse float using BigInt");
Ok((
parsed_float,
LazyBigInt::new(raw_number_str),
raw_str,
non_octal,
))
}
/// Ensure that ident cannot directly follow numbers.
fn ensure_not_ident(&mut self) -> LexResult<()> {
match self.cur() {
Some(c) if c.is_ident_start() => {
let span = pos_span(self.cur_pos());
self.error_span(span, SyntaxError::IdentAfterNum)?
}
_ => Ok(()),
}
}
/// Read an integer in the given radix. Return `None` if zero digits
/// were read, the integer value otherwise.
/// When `len` is not zero, this
/// will return `None` unless the integer has exactly `len` digits.
pub(super) fn read_int<const RADIX: u8>(
&mut self,
len: u8,
raw: &mut Raw,
) -> LexResult<Option<f64>> {
let mut count = 0u16;
let v = self.read_digits::<_, Option<f64>, RADIX>(
|opt: Option<f64>, radix, val| {
count += 1;
let total = opt.unwrap_or_default() * radix as f64 + val as f64;
Ok((Some(total), count != len as u16))
},
raw,
true,
)?;
if len != 0 && count != len as u16 {
Ok(None)
} else {
Ok(v)
}
}
pub(super) fn read_int_u32<const RADIX: u8>(
&mut self,
len: u8,
raw: &mut Raw,
) -> LexResult<Option<u32>> {
let start = self.state.start;
let mut count = 0;
let v = self.read_digits::<_, Option<u32>, RADIX>(
|opt: Option<u32>, radix, val| {
count += 1;
let total = opt
.unwrap_or_default()
.checked_mul(radix as u32)
.and_then(|v| v.checked_add(val))
.ok_or_else(|| {
let span = Span::new(start, start, SyntaxContext::empty());
Error::new(span, SyntaxError::InvalidUnicodeEscape)
})?;
Ok((Some(total), count != len))
},
raw,
true,
)?;
if len != 0 && count != len {
Ok(None)
} else {
Ok(v)
}
}
/// `op`- |total, radix, value| -> (total * radix + value, continue)
fn read_digits<F, Ret, const RADIX: u8>(
&mut self,
mut op: F,
raw: &mut Raw,
allow_num_separator: bool,
) -> LexResult<Ret>
where
F: FnMut(Ret, u8, u32) -> LexResult<(Ret, bool)>,
Ret: Copy + Default,
{
debug_assert!(
RADIX == 2 || RADIX == 8 || RADIX == 10 || RADIX == 16,
"radix for read_int should be one of 2, 8, 10, 16, but got {}",
RADIX
);
if cfg!(feature = "debug") {
trace!("read_digits(radix = {}), cur = {:?}", RADIX, self.cur());
}
let start = self.cur_pos();
let mut total: Ret = Default::default();
let mut prev = None;
while let Some(c) = self.cur() {
if allow_num_separator && c == '_' {
let is_allowed = |c: Option<char>| {
if c.is_none() {
return false;
}
let c = c.unwrap();
c.is_digit(RADIX as _)
};
let is_forbidden = |c: Option<char>| {
if c.is_none() {
return true;
}
if RADIX == 16 {
matches!(c.unwrap(), '.' | 'X' | '_' | 'x')
} else {
matches!(c.unwrap(), '.' | 'B' | 'E' | 'O' | '_' | 'b' | 'e' | 'o')
}
};
let next = self.input.peek();
if !is_allowed(next) || is_forbidden(prev) || is_forbidden(next) {
self.emit_error(
start,
SyntaxError::NumericSeparatorIsAllowedOnlyBetweenTwoDigits,
);
}
// Ignore this _ character
unsafe {
// Safety: cur() returns Some(c) where c is a valid char
self.input.bump();
}
raw.push(c);
continue;
}
// e.g. (val for a) = 10 where radix = 16
let val = if let Some(val) = c.to_digit(RADIX as _) {
val
} else {
return Ok(total);
};
raw.push(c);
self.bump();
let (t, cont) = op(total, RADIX, val)?;
total = t;
if !cont {
return Ok(total);
}
prev = Some(c);
}
Ok(total)
}
fn make_legacy_octal(&mut self, start: BytePos, val: f64) -> LexResult<f64> {
self.ensure_not_ident()?;
if self.syntax.typescript() && self.target >= EsVersion::Es5 {
self.emit_error(start, SyntaxError::TS1085);
}
self.emit_strict_mode_error(start, SyntaxError::LegacyOctal);
Ok(val)
}
}
#[cfg(test)]
mod tests {
use std::{f64::INFINITY, panic};
use super::*;
fn lex<F, Ret>(s: &'static str, f: F) -> Ret
where
F: FnOnce(&mut Lexer<'_>) -> Ret,
{
crate::with_test_sess(s, |_, input| {
let mut l = Lexer::new(
Syntax::Es(Default::default()),
Default::default(),
input,
None,
);
let ret = f(&mut l);
assert_eq!(l.input.cur(), None);
Ok(ret)
})
.unwrap()
}
fn num(s: &'static str) -> (f64, Atom) {
lex(s, |l| {
l.read_number(s.starts_with('.')).unwrap().left().unwrap()
})
}
fn int<const RADIX: u8>(s: &'static str) -> u32 {
lex(s, |l| {
l.read_int_u32::<RADIX>(0, &mut Raw(None))
.unwrap()
.expect("read_int returned None")
})
}
const LONG: &str = "1e10000000000000000000000000000000000000000\
0000000000000000000000000000000000000000000000000000";
#[test]
fn num_inf() {
assert_eq!(num(LONG), (INFINITY, LONG.into()));
}
/// Number >= 2^53
#[test]
fn num_big_exp() {
assert_eq!((1e30, "1e30".into()), num("1e30"));
}
#[test]
fn num_very_big_exp() {
const LARGE_POSITIVE_EXP: &str =
"1e100000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000";
const LARGE_NEGATIVE_EXP: &str =
"1e-10000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
000000000000000000000000000000000000000000000000000000";
const ZERO_WITH_LARGE_POSITIVE_EXP: &str =
"0e100000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000";
const ZERO_WITH_LARGE_NEGATIVE_EXP: &str =
"0e-10000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
000000000000000000000000000000000000000000000000000000";
const LARGE_MANTISSA_WITH_LARGE_NEGATIVE_EXP: &str =
"10000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
000000000000000000000000000000000000000000000000000000\
e-100000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
00000000000000000000000000000000000000000000000000000000000000000\
000000000000000000000000000000000000000000000000000000";
assert_eq!(
num(LARGE_POSITIVE_EXP),
(INFINITY, LARGE_POSITIVE_EXP.into())
);
assert_eq!(num(LARGE_NEGATIVE_EXP), (0.0, LARGE_NEGATIVE_EXP.into()));
assert_eq!(
num(ZERO_WITH_LARGE_POSITIVE_EXP),
(0.0, ZERO_WITH_LARGE_POSITIVE_EXP.into())
);
assert_eq!(
num(ZERO_WITH_LARGE_NEGATIVE_EXP),
(0.0, ZERO_WITH_LARGE_NEGATIVE_EXP.into())
);
assert_eq!(
num(LARGE_MANTISSA_WITH_LARGE_NEGATIVE_EXP),
(0.0, LARGE_MANTISSA_WITH_LARGE_NEGATIVE_EXP.into())
);
}
#[test]
fn num_big_many_zero() {
assert_eq!(
(
1_000_000_000_000_000_000_000_000_000_000f64,
"1000000000000000000000000000000".into()
),
num("1000000000000000000000000000000")
);
assert_eq!(
(3.402_823_466_385_288_6e38, "34028234663852886e22".into()),
num("34028234663852886e22"),
);
}
#[test]
fn big_number_with_fract() {
assert_eq!(
(77777777777777777.1f64, "77777777777777777.1".into()),
num("77777777777777777.1")
)
}
#[test]
fn issue_480() {
assert_eq!((9.09, "9.09".into()), num("9.09"))
}
#[test]
fn num_legacy_octal() {
assert_eq!((0o12 as f64, "0012".into()), num("0012"));
assert_eq!((10f64, "012".into()), num("012"));
}
#[test]
fn read_int_1() {
assert_eq!(60, int::<10>("60"));
assert_eq!(0o73, int::<8>("73"));
}
#[test]
fn read_int_short() {
assert_eq!(7, int::<10>("7"));
assert_eq!(10, int::<10>("10"));
}
#[test]
fn read_radix_number() {
assert_eq!(
(0o73 as f64, "0o73".into()),
lex("0o73", |l| l
.read_radix_number::<8>()
.unwrap()
.left()
.unwrap())
);
}
#[test]
fn read_num_sep() {
assert_eq!(1_000, int::<10>("1_000"));
assert_eq!(0xaebece, int::<16>("AE_BE_CE"));
assert_eq!(0b1010000110000101, int::<2>("1010_0001_1000_0101"));
assert_eq!(0o0666, int::<8>("0_6_6_6"));
}
#[test]
fn read_bigint() {
assert_eq!(
lex(
"10000000000000000000000000000000000000000000000000000n",
|l| l.read_number(false).unwrap().right().unwrap()
),
(
Box::new(
"10000000000000000000000000000000000000000000000000000"
.parse::<BigIntValue>()
.unwrap()
),
Atom::from("10000000000000000000000000000000000000000000000000000n")
),
);
}
#[test]
fn large_bin_number() {
const LONG: &str =
"0B11111111111111111111111111111111111111111111111101001010100000010111110001111111111";
const VERY_LARGE_BINARY_NUMBER: &str =
"0B1111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
111111111111111111111111111111111111111111111111111111111111111111\
0010111110001111111111";
assert_eq!(
lex(LONG, |l| l
.read_radix_number::<2>()
.unwrap()
.left()
.unwrap()),
(9.671_406_556_917_009e24, LONG.into())
);
assert_eq!(
lex(VERY_LARGE_BINARY_NUMBER, |l| l
.read_radix_number::<2>()
.unwrap()
.left()
.unwrap()),
(1.0972248137587377e304, VERY_LARGE_BINARY_NUMBER.into())
);
}
#[test]
fn large_float_number() {
const LONG: &str = "9.671406556917009e+24";
assert_eq!(num(LONG), (9.671_406_556_917_009e24, LONG.into()));
}
/// Valid even on strict mode.
const VALID_CASES: &[&str] = &[".0", "0.e-1", "0e8", ".8e1", "0.8e1", "1.18e1"];
const INVALID_CASES_ON_STRICT: &[&str] = &["08e1", "08.1", "08.8e1", "08", "01"];
const INVALID_CASES: &[&str] = &["01.8e1", "012e1", "00e1", "00.0"];
fn test_floats(strict: bool, success: bool, cases: &'static [&'static str]) {
for case in cases {
println!(
"Testing {} (when strict = {}); Expects success = {}",
case, strict, success
);
// lazy way to get expected values
let expected: f64 = (i64::from_str_radix(case, 8).map(|v| v as f64))
.or_else(|_| case.parse::<i64>().map(|v| v as f64))
.or_else(|_| case.parse::<f64>())
.unwrap_or_else(|err| {
panic!(
"failed to parse '{}' as float using str.parse(): {}",
case, err
)
});
let vec = panic::catch_unwind(|| {
crate::with_test_sess(case, |_, input| {
let mut l = Lexer::new(Syntax::default(), Default::default(), input, None);
l.ctx.strict = strict;
Ok(l.map(|ts| ts.token).collect::<Vec<_>>())
})
.unwrap()
});
if success {
let vec = match vec {
Ok(vec) => vec,
Err(err) => panic::resume_unwind(err),
};
assert_eq!(vec.len(), 1);
let token = vec.into_iter().next().unwrap();
let value = match token {
Token::Num { value, .. } => value,
_ => {
panic!("expected num token in test")
}
};
assert_eq!(expected, value);
} else if let Ok(vec) = vec {
assert_ne!(
vec![Num {
value: expected,
raw: expected.to_string().into()
}],
vec
)
}
}
}
// #[test]
// fn strict_mode() {
// test_floats(true, true, VALID_CASES);
// test_floats(true, false, INVALID_CASES_ON_STRICT);
// test_floats(true, false, INVALID_CASES);
// }
#[test]
fn non_strict() {
test_floats(false, true, VALID_CASES);
test_floats(false, true, INVALID_CASES_ON_STRICT);
test_floats(false, false, INVALID_CASES);
}
}

View file

@ -0,0 +1,861 @@
use std::mem::take;
use swc_common::{BytePos, Span};
use tracing::trace;
use super::{
comments_buffer::{BufferedComment, BufferedCommentKind},
Context, Input, Lexer,
};
use crate::{
error::{Error, SyntaxError},
input::Tokens,
lexer::util::CharExt,
token::*,
EsVersion, Syntax,
};
/// State of lexer.
///
/// Ported from babylon.
#[derive(Clone)]
pub(super) struct State {
pub is_expr_allowed: bool,
pub next_regexp: Option<BytePos>,
/// if line break exists between previous token and new token?
pub had_line_break: bool,
/// if line break exists before last?
pub had_line_break_before_last: bool,
/// TODO: Remove this field.
is_first: bool,
pub start: BytePos,
pub cur_line: usize,
pub line_start: BytePos,
pub prev_hi: BytePos,
context: TokenContexts,
syntax: Syntax,
token_type: Option<TokenType>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum TokenType {
Template,
Dot,
Colon,
LBrace,
RParen,
Semi,
BinOp(BinOpToken),
Keyword(Keyword),
JSXName,
JSXText,
JSXTagStart,
JSXTagEnd,
Arrow,
Other {
before_expr: bool,
can_have_trailing_comment: bool,
},
}
impl TokenType {
#[inline]
const fn before_expr(self) -> bool {
match self {
TokenType::JSXName
| TokenType::JSXTagStart
| TokenType::JSXTagEnd
| TokenType::Template
| TokenType::Dot
| TokenType::RParen => false,
TokenType::JSXText
| TokenType::Colon
| TokenType::LBrace
| TokenType::Semi
| TokenType::Arrow => true,
TokenType::BinOp(b) => b.before_expr(),
TokenType::Keyword(k) => k.before_expr(),
TokenType::Other { before_expr, .. } => before_expr,
}
}
}
impl<'a> From<&'a Token> for TokenType {
#[inline]
fn from(t: &Token) -> Self {
match *t {
Token::Template { .. } => TokenType::Template,
Token::Dot => TokenType::Dot,
Token::Colon => TokenType::Colon,
Token::LBrace => TokenType::LBrace,
Token::RParen => TokenType::RParen,
Token::Semi => TokenType::Semi,
Token::JSXTagEnd => TokenType::JSXTagEnd,
Token::JSXTagStart => TokenType::JSXTagStart,
Token::JSXText { .. } => TokenType::JSXText,
Token::JSXName { .. } => TokenType::JSXName,
Token::BinOp(op) => TokenType::BinOp(op),
Token::Arrow => TokenType::Arrow,
Token::Word(Word::Keyword(k)) => TokenType::Keyword(k),
_ => TokenType::Other {
before_expr: t.before_expr(),
can_have_trailing_comment: matches!(
*t,
Token::Num { .. }
| Token::Str { .. }
| Token::Word(Word::Ident(..))
| Token::DollarLBrace
| Token::Regex(..)
| Token::BigInt { .. }
| Token::JSXText { .. }
| Token::RBrace
),
},
}
}
}
impl Tokens for Lexer<'_> {
#[inline]
fn set_ctx(&mut self, ctx: Context) {
if ctx.module && !self.module_errors.borrow().is_empty() {
let mut module_errors = self.module_errors.borrow_mut();
self.errors.borrow_mut().append(&mut *module_errors);
}
self.ctx = ctx
}
#[inline]
fn ctx(&self) -> Context {
self.ctx
}
#[inline]
fn syntax(&self) -> Syntax {
self.syntax
}
#[inline]
fn target(&self) -> EsVersion {
self.target
}
#[inline]
fn start_pos(&self) -> BytePos {
self.start_pos
}
#[inline]
fn set_expr_allowed(&mut self, allow: bool) {
self.set_expr_allowed(allow)
}
#[inline]
fn set_next_regexp(&mut self, start: Option<BytePos>) {
self.state.next_regexp = start;
}
#[inline]
fn token_context(&self) -> &TokenContexts {
&self.state.context
}
#[inline]
fn token_context_mut(&mut self) -> &mut TokenContexts {
&mut self.state.context
}
#[inline]
fn set_token_context(&mut self, c: TokenContexts) {
self.state.context = c;
}
fn add_error(&self, error: Error) {
self.errors.borrow_mut().push(error);
}
fn add_module_mode_error(&self, error: Error) {
if self.ctx.module {
self.add_error(error);
return;
}
self.module_errors.borrow_mut().push(error);
}
fn take_errors(&mut self) -> Vec<Error> {
take(&mut self.errors.borrow_mut())
}
}
impl<'a> Iterator for Lexer<'a> {
type Item = TokenAndSpan;
fn next(&mut self) -> Option<Self::Item> {
let mut start = self.cur_pos();
let res = (|| -> Result<Option<_>, _> {
if let Some(start) = self.state.next_regexp {
return Ok(Some(self.read_regexp(start)?));
}
if self.state.is_first {
if let Some(shebang) = self.read_shebang()? {
return Ok(Some(Token::Shebang(shebang)));
}
}
self.state.had_line_break = self.state.is_first;
self.state.is_first = false;
// skip spaces before getting next character, if we are allowed to.
if self.state.can_skip_space() {
self.skip_space::<true>()?;
start = self.input.cur_pos();
};
match self.input.cur() {
Some(..) => {}
// End of input.
None => {
if let Some(comments) = self.comments.as_mut() {
let comments_buffer = self.comments_buffer.as_mut().unwrap();
let last = self.state.prev_hi;
// move the pending to the leading or trailing
for c in comments_buffer.take_pending_leading() {
// if the file had no tokens and no shebang, then treat any
// comments in the leading comments buffer as leading.
// Otherwise treat them as trailing.
if last == self.start_pos {
comments_buffer.push(BufferedComment {
kind: BufferedCommentKind::Leading,
pos: last,
comment: c,
});
} else {
comments_buffer.push(BufferedComment {
kind: BufferedCommentKind::Trailing,
pos: last,
comment: c,
});
}
}
// now fill the user's passed in comments
for comment in comments_buffer.take_comments() {
match comment.kind {
BufferedCommentKind::Leading => {
comments.add_leading(comment.pos, comment.comment);
}
BufferedCommentKind::Trailing => {
comments.add_trailing(comment.pos, comment.comment);
}
}
}
}
return Ok(None);
}
};
// println!(
// "\tContext: ({:?}) {:?}",
// self.input.cur().unwrap(),
// self.state.context.0
// );
self.state.start = start;
if self.syntax.jsx() && !self.ctx.in_property_name && !self.ctx.in_type {
//jsx
if self.state.context.current() == Some(TokenContext::JSXExpr) {
return self.read_jsx_token();
}
let c = self.cur();
if let Some(c) = c {
if self.state.context.current() == Some(TokenContext::JSXOpeningTag)
|| self.state.context.current() == Some(TokenContext::JSXClosingTag)
{
if c.is_ident_start() {
return self.read_jsx_word().map(Some);
}
if c == '>' {
unsafe {
// Safety: cur() is Some('>')
self.input.bump();
}
return Ok(Some(Token::JSXTagEnd));
}
if (c == '\'' || c == '"')
&& self.state.context.current() == Some(TokenContext::JSXOpeningTag)
{
return self.read_jsx_str(c).map(Some);
}
}
if c == '<' && self.state.is_expr_allowed && self.input.peek() != Some('!') {
let had_line_break_before_last = self.had_line_break_before_last();
let cur_pos = self.input.cur_pos();
unsafe {
// Safety: cur() is Some('<')
self.input.bump();
}
if had_line_break_before_last && self.is_str("<<<<<< ") {
let span = Span::new(cur_pos, cur_pos + BytePos(7), Default::default());
self.emit_error_span(span, SyntaxError::TS1185);
self.skip_line_comment(6);
self.skip_space::<true>()?;
return self.read_token();
}
return Ok(Some(Token::JSXTagStart));
}
}
}
if let Some(TokenContext::Tpl {
start: start_pos_of_tpl,
}) = self.state.context.current()
{
return self.read_tmpl_token(start_pos_of_tpl).map(Some);
}
self.read_token()
})();
let token = match res.map_err(Token::Error).map_err(Some) {
Ok(t) => t,
Err(e) => e,
};
let span = self.span(start);
if let Some(ref token) = token {
if let Some(comments) = self.comments_buffer.as_mut() {
for comment in comments.take_pending_leading() {
comments.push(BufferedComment {
kind: BufferedCommentKind::Leading,
pos: start,
comment,
});
}
}
self.state.update(start, token);
self.state.prev_hi = self.last_pos();
self.state.had_line_break_before_last = self.had_line_break_before_last();
}
token.map(|token| {
// Attach span to token.
TokenAndSpan {
token,
had_line_break: self.had_line_break_before_last(),
span,
}
})
}
}
impl State {
pub fn new(syntax: Syntax, start_pos: BytePos) -> Self {
let context = TokenContexts(vec![TokenContext::BraceStmt]);
State {
is_expr_allowed: true,
next_regexp: None,
is_first: true,
had_line_break: false,
had_line_break_before_last: false,
prev_hi: start_pos,
context,
token_type: None,
start: BytePos(0),
line_start: BytePos(0),
cur_line: 1,
syntax,
}
}
}
impl State {
pub fn can_skip_space(&self) -> bool {
!self
.context
.current()
.map(|t| t.preserve_space())
.unwrap_or(false)
}
pub fn can_have_trailing_line_comment(&self) -> bool {
match self.token_type {
Some(TokenType::BinOp(..)) => false,
_ => true,
}
}
pub fn can_have_trailing_comment(&self) -> bool {
match self.token_type {
Some(TokenType::Keyword(..)) => false,
Some(TokenType::Semi) | Some(TokenType::LBrace) => true,
Some(TokenType::Other {
can_have_trailing_comment,
..
}) => can_have_trailing_comment,
_ => false,
}
}
pub fn last_was_tpl_element(&self) -> bool {
matches!(self.token_type, Some(TokenType::Template))
}
fn update(&mut self, start: BytePos, next: &Token) {
if cfg!(feature = "debug") {
trace!(
"updating state: next={:?}, had_line_break={} ",
next,
self.had_line_break
);
}
let prev = self.token_type.take();
self.token_type = Some(TokenType::from(next));
self.is_expr_allowed = Self::is_expr_allowed_on_next(
&mut self.context,
self.syntax,
prev,
start,
next,
self.had_line_break,
self.had_line_break_before_last,
self.is_expr_allowed,
);
}
/// `is_expr_allowed`: previous value.
/// `start`: start of newly produced token.
fn is_expr_allowed_on_next(
context: &mut TokenContexts,
syntax: Syntax,
prev: Option<TokenType>,
start: BytePos,
next: &Token,
had_line_break: bool,
had_line_break_before_last: bool,
is_expr_allowed: bool,
) -> bool {
let is_next_keyword = matches!(*next, Word(Word::Keyword(..)));
if is_next_keyword && prev == Some(TokenType::Dot) {
false
} else {
// ported updateContext
match *next {
tok!(')') | tok!('}') => {
// TODO: Verify
if context.len() == 1 {
return true;
}
let out = context.pop().unwrap();
// let a = function(){}
if out == TokenContext::BraceStmt
&& matches!(
context.current(),
Some(TokenContext::FnExpr | TokenContext::ClassExpr)
)
{
context.pop();
return false;
}
// ${} in template
if out == TokenContext::TplQuasi {
match context.current() {
Some(TokenContext::Tpl { .. }) => return false,
_ => return true,
}
}
// expression cannot follow expression
!out.is_expr()
}
tok!("function") => {
// This is required to lex
// `x = function(){}/42/i`
if is_expr_allowed
&& !context.is_brace_block(prev, had_line_break, is_expr_allowed)
{
context.push(TokenContext::FnExpr);
}
false
}
tok!("class") => {
if is_expr_allowed
&& !context.is_brace_block(prev, had_line_break, is_expr_allowed)
{
context.push(TokenContext::ClassExpr);
}
false
}
tok!(':')
if matches!(
context.current(),
Some(TokenContext::FnExpr | TokenContext::ClassExpr)
) =>
{
// `function`/`class` keyword is object prop
//
// ```JavaScript
// { function: expr, class: expr }
// ```
context.pop(); // Remove FnExpr or ClassExpr
true
}
// for (a of b) {}
tok!("of")
if Some(TokenContext::ParenStmt { is_for_loop: true }) == context.current() =>
{
// e.g. for (a of _) => true
!prev
.expect("context.current() if ParenStmt, so prev token cannot be None")
.before_expr()
}
Word(Word::Ident(..)) => {
// variable declaration
match prev {
Some(prev) => match prev {
// handle automatic semicolon insertion.
TokenType::Keyword(Let)
| TokenType::Keyword(Const)
| TokenType::Keyword(Var)
if had_line_break_before_last =>
{
true
}
_ => false,
},
_ => false,
}
}
tok!('{') => {
let cur = context.current();
if syntax.jsx() && cur == Some(TokenContext::JSXOpeningTag) {
context.push(TokenContext::BraceExpr)
} else if syntax.jsx() && cur == Some(TokenContext::JSXExpr) {
context.push(TokenContext::TplQuasi);
} else {
let next_ctxt =
if context.is_brace_block(prev, had_line_break, is_expr_allowed) {
TokenContext::BraceStmt
} else {
TokenContext::BraceExpr
};
context.push(next_ctxt);
}
true
}
tok!('/') if syntax.jsx() && prev == Some(TokenType::JSXTagStart) => {
context.pop();
context.pop(); // do not consider JSX expr -> JSX open tag -> ... anymore
context.push(TokenContext::JSXClosingTag); // reconsider as closing tag context
false
}
tok!("${") => {
context.push(TokenContext::TplQuasi);
true
}
tok!('(') => {
// if, for, with, while is statement
context.push(match prev {
Some(TokenType::Keyword(k)) => match k {
If | With | While => TokenContext::ParenStmt { is_for_loop: false },
For => TokenContext::ParenStmt { is_for_loop: true },
_ => TokenContext::ParenExpr,
},
_ => TokenContext::ParenExpr,
});
true
}
// remains unchanged.
tok!("++") | tok!("--") => is_expr_allowed,
tok!('`') => {
// If we are in template, ` terminates template.
if let Some(TokenContext::Tpl { .. }) = context.current() {
context.pop();
} else {
context.push(TokenContext::Tpl { start });
}
false
}
// tt.jsxTagStart.updateContext
Token::JSXTagStart => {
context.push(TokenContext::JSXExpr); // treat as beginning of JSX expression
context.push(TokenContext::JSXOpeningTag); // start opening tag context
false
}
// tt.jsxTagEnd.updateContext
Token::JSXTagEnd => {
let out = context.pop();
if (out == Some(TokenContext::JSXOpeningTag)
&& prev == Some(TokenType::BinOp(BinOpToken::Div)))
|| out == Some(TokenContext::JSXClosingTag)
{
context.pop();
context.current() == Some(TokenContext::JSXExpr)
} else {
true
}
}
_ => next.before_expr(),
}
}
}
}
#[derive(Clone, Default)]
pub struct TokenContexts(pub(crate) Vec<TokenContext>);
impl TokenContexts {
/// Returns true if following `LBrace` token is `block statement` according
/// to `ctx`, `prev`, `is_expr_allowed`.
fn is_brace_block(
&self,
prev: Option<TokenType>,
had_line_break: bool,
is_expr_allowed: bool,
) -> bool {
if let Some(TokenType::Colon) = prev {
match self.current() {
Some(TokenContext::BraceStmt) => return true,
// `{ a: {} }`
// ^ ^
Some(TokenContext::BraceExpr) => return false,
_ => {}
};
}
match prev {
// function a() {
// return { a: "" };
// }
// function a() {
// return
// {
// function b(){}
// };
// }
Some(TokenType::Keyword(Return)) | Some(TokenType::Keyword(Yield)) => {
return had_line_break;
}
Some(TokenType::Keyword(Else))
| Some(TokenType::Semi)
| None
| Some(TokenType::RParen) => {
return true;
}
// If previous token was `{`
Some(TokenType::LBrace) => {
// https://github.com/swc-project/swc/issues/3241#issuecomment-1029584460
// <Blah blah={function (): void {}} />
if self.current() == Some(TokenContext::BraceExpr) {
let len = self.len();
if let Some(TokenContext::JSXOpeningTag) = self.0.get(len - 2) {
return true;
}
}
return self.current() == Some(TokenContext::BraceStmt);
}
// `class C<T> { ... }`
Some(TokenType::BinOp(Lt)) | Some(TokenType::BinOp(Gt)) => return true,
// () => {}
Some(TokenType::Arrow) => return true,
_ => {}
}
if had_line_break {
if let Some(TokenType::Other {
before_expr: false, ..
}) = prev
{
return true;
}
}
!is_expr_allowed
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub fn pop(&mut self) -> Option<TokenContext> {
let opt = self.0.pop();
if cfg!(feature = "debug") {
trace!("context.pop({:?}): {:?}", opt, self.0);
}
opt
}
#[inline]
pub fn current(&self) -> Option<TokenContext> {
self.0.last().cloned()
}
#[inline]
fn push(&mut self, t: TokenContext) {
self.0.push(t);
if cfg!(feature = "debug") {
trace!("context.push({:?}): {:?}", t, self.0);
}
}
}
/// The algorithm used to determine whether a regexp can appear at a
/// given point in the program is loosely based on sweet.js' approach.
/// See https://github.com/mozilla/sweet.js/wiki/design
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenContext {
BraceStmt,
BraceExpr,
TplQuasi,
ParenStmt {
/// Is this `for` loop?
is_for_loop: bool,
},
ParenExpr,
Tpl {
/// Start of a template literal.
start: BytePos,
},
FnExpr,
ClassExpr,
JSXOpeningTag,
JSXClosingTag,
JSXExpr,
}
impl TokenContext {
pub(crate) const fn is_expr(&self) -> bool {
matches!(
self,
Self::BraceExpr
| Self::TplQuasi
| Self::ParenExpr
| Self::Tpl { .. }
| Self::FnExpr
| Self::ClassExpr
| Self::JSXExpr
)
}
pub(crate) const fn preserve_space(&self) -> bool {
match self {
Self::Tpl { .. } | Self::JSXExpr => true,
_ => false,
}
}
}
#[cfg(test)]
pub(crate) fn with_lexer<F, Ret>(
syntax: Syntax,
target: EsVersion,
s: &str,
f: F,
) -> Result<Ret, ::testing::StdErr>
where
F: FnOnce(&mut Lexer<'_>) -> Result<Ret, ()>,
{
crate::with_test_sess(s, |_, fm| {
let mut l = Lexer::new(syntax, target, fm, None);
let res = f(&mut l);
#[cfg(debug_assertions)]
let c = vec![TokenContext::BraceStmt];
#[cfg(debug_assertions)]
debug_assert_eq!(l.state.context.0, c);
res
})
}
#[cfg(test)]
pub(crate) fn lex(syntax: Syntax, s: &'static str) -> Vec<TokenAndSpan> {
with_lexer(syntax, Default::default(), s, |l| Ok(l.collect())).unwrap()
}
/// lex `s` within module context.
#[cfg(test)]
pub(crate) fn lex_module_errors(syntax: Syntax, s: &'static str) -> Vec<Error> {
with_lexer(syntax, Default::default(), s, |l| {
l.ctx.strict = true;
l.ctx.module = true;
let _: Vec<_> = l.collect();
Ok(l.take_errors())
})
.unwrap()
}
#[cfg(test)]
pub(crate) fn lex_tokens(syntax: Syntax, s: &'static str) -> Vec<Token> {
with_lexer(syntax, Default::default(), s, |l| {
Ok(l.map(|ts| ts.token).collect())
})
.unwrap()
}
/// Returns `(tokens, recovered_errors)`. `(tokens)` may contain an error token
/// if the lexer fails to recover from it.
#[cfg(test)]
pub(crate) fn lex_errors(syntax: Syntax, s: &'static str) -> (Vec<Token>, Vec<Error>) {
with_lexer(syntax, EsVersion::Es2020, s, |l| {
let tokens = l.map(|ts| ts.token).collect();
let errors = l.take_errors();
Ok((tokens, errors))
})
.unwrap()
}

View file

@ -0,0 +1,181 @@
//! Lookup table for byte handlers.
//!
//! Idea is taken from ratel.
//!
//! https://github.com/ratel-rust/ratel-core/blob/e55a1310ba69a3f5ce2a9a6eef643feced02ac08/ratel/src/lexer/mod.rs#L665
use either::Either;
use swc_common::input::Input;
use super::{pos_span, util::CharExt, LexResult, Lexer};
use crate::{
error::SyntaxError,
token::{AssignOpToken, BinOpToken, Token},
};
pub(super) type ByteHandler = Option<for<'aa> fn(&mut Lexer<'aa>) -> LexResult<Option<Token>>>;
/// Lookup table mapping any incoming byte to a handler function defined below.
pub(super) static BYTE_HANDLERS: [ByteHandler; 256] = [
// 0 1 2 3 4 5 6 7 8 9 A B C D E F //
EOF, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 0
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 1
___, EXL, QOT, HSH, IDT, PRC, AMP, QOT, PNO, PNC, ATR, PLS, COM, MIN, PRD, SLH, // 2
ZER, DIG, DIG, DIG, DIG, DIG, DIG, DIG, DIG, DIG, COL, SEM, LSS, EQL, MOR, QST, // 3
AT_, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, // 4
IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, BTO, IDT, BTC, CRT, IDT, // 5
TPL, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, // 6
IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, IDT, BEO, PIP, BEC, TLD, ERR, // 7
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // 8
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // 9
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // A
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // B
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // C
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // D
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // E
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // F
];
const ___: ByteHandler = None;
const EOF: ByteHandler = Some(|lexer| {
lexer.input.bump_bytes(1);
Ok(None)
});
const ERR: ByteHandler = Some(|lexer| {
let c = unsafe {
// Safety: Byte handler is only called for non-last chracters
lexer.input.cur().unwrap_unchecked()
};
let start = lexer.cur_pos();
unsafe {
// Safety: Byte handler is only called for non-last chracters
lexer.input.bump();
}
lexer.error_span(pos_span(start), SyntaxError::UnexpectedChar { c })?
});
/// Identifier
const IDT: ByteHandler = Some(|lexer| lexer.read_ident_or_keyword().map(Some));
/// `0`
const ZER: ByteHandler = Some(|lexer| lexer.read_token_zero().map(Some));
/// Numbers
const DIG: ByteHandler = Some(|lexer| {
lexer
.read_number(false)
.map(|v| match v {
Either::Left((value, raw)) => Token::Num { value, raw },
Either::Right((value, raw)) => Token::BigInt { value, raw },
})
.map(Some)
});
/// String literals with `'` or `"`
const QOT: ByteHandler = Some(|lexer| lexer.read_str_lit().map(Some));
/// Unicode
const UNI: ByteHandler = Some(|lexer| {
let c = unsafe {
// Safety: Byte handler is only called for non-last chracters
lexer.input.cur().unwrap_unchecked()
};
// Identifier or keyword. '\uXXXX' sequences are allowed in
// identifiers, so '\' also dispatches to that.
if c == '\\' || c.is_ident_start() {
return lexer.read_ident_or_keyword().map(Some);
}
let start = lexer.cur_pos();
unsafe {
// Safety: Byte handler is only called for non-last chracters
lexer.input.bump();
}
lexer.error_span(pos_span(start), SyntaxError::UnexpectedChar { c })?
});
/// `:`
const COL: ByteHandler = Some(|lexer| lexer.read_token_colon().map(Some));
/// `%`
const PRC: ByteHandler = Some(|lexer| lexer.read_token_mul_mod(b'%').map(Some));
/// `*`
const ATR: ByteHandler = Some(|lexer| lexer.read_token_mul_mod(b'*').map(Some));
/// `?`
const QST: ByteHandler = Some(|lexer| lexer.read_token_question_mark().map(Some));
/// `&`
const AMP: ByteHandler = Some(|lexer| lexer.read_token_logical(b'&').map(Some));
/// `|`
const PIP: ByteHandler = Some(|lexer| lexer.read_token_logical(b'|').map(Some));
macro_rules! single_char {
($name:ident, $c:literal, $token:ident) => {
const $name: ByteHandler = Some(|lexer| {
lexer.input.bump_bytes(1);
Ok(Some(Token::$token))
});
};
}
single_char!(SEM, b';', Semi);
single_char!(COM, b',', Comma);
single_char!(TPL, b'`', BackQuote);
single_char!(TLD, b'~', Tilde);
single_char!(AT_, b'@', At);
single_char!(PNO, b'(', LParen);
single_char!(PNC, b')', RParen);
single_char!(BTO, b'[', LBracket);
single_char!(BTC, b']', RBracket);
single_char!(BEO, b'{', LBrace);
single_char!(BEC, b'}', RBrace);
/// `^`
const CRT: ByteHandler = Some(|lexer| {
// Bitwise xor
lexer.input.bump_bytes(1);
Ok(Some(if lexer.input.cur_as_ascii() == Some(b'=') {
lexer.input.bump_bytes(1);
Token::AssignOp(AssignOpToken::BitXorAssign)
} else {
Token::BinOp(BinOpToken::BitXor)
}))
});
/// `+`
const PLS: ByteHandler = Some(|lexer| lexer.read_token_plus_minus(b'+'));
/// `-`
const MIN: ByteHandler = Some(|lexer| lexer.read_token_plus_minus(b'-'));
/// `!`
const EXL: ByteHandler = Some(|lexer| lexer.read_token_bang_or_eq(b'!'));
/// `=`
const EQL: ByteHandler = Some(|lexer| lexer.read_token_bang_or_eq(b'='));
/// `.`
const PRD: ByteHandler = Some(|lexer| lexer.read_token_dot().map(Some));
/// `<`
const LSS: ByteHandler = Some(|lexer| lexer.read_token_lt_gt());
/// `>`
const MOR: ByteHandler = Some(|lexer| lexer.read_token_lt_gt());
/// `/`
const SLH: ByteHandler = Some(|lexer| lexer.read_slash());
/// `#`
const HSH: ByteHandler = Some(|lexer| lexer.read_token_number_sign());

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,430 @@
//! Ported from [babylon/util/identifier.js][]
//!
//!
//! [babylon/util/identifier.js]:https://github.com/babel/babel/blob/master/packages/babylon/src/util/identifier.js
use std::char;
use smartstring::{LazyCompact, SmartString};
use swc_common::{
comments::{Comment, CommentKind},
BytePos, Span, SyntaxContext,
};
use swc_ecma_ast::Ident;
use tracing::warn;
use super::{
comments_buffer::BufferedComment, input::Input, whitespace::SkipWhitespace, Char, LexResult,
Lexer,
};
use crate::{
error::{Error, SyntaxError},
lexer::comments_buffer::BufferedCommentKind,
Tokens,
};
/// Collector for raw string.
///
/// Methods of this struct is noop if the value is [None].
pub(super) struct Raw(pub Option<SmartString<LazyCompact>>);
impl Raw {
#[inline]
pub fn push_str(&mut self, s: &str) {
if let Some(ref mut st) = self.0 {
st.push_str(s)
}
}
#[inline]
pub fn push(&mut self, c: char) {
if let Some(ref mut st) = self.0 {
st.push(c)
}
}
}
// pub const BACKSPACE: char = 8 as char;
// pub const SHIFT_OUT: char = 14 as char;
// pub const OGHAM_SPACE_MARK: char = '\u{1680}'; // ''
// pub const LINE_FEED: char = '\n';
// pub const LINE_SEPARATOR: char = '\u{2028}';
// pub const PARAGRAPH_SEPARATOR: char = '\u{2029}';
impl<'a> Lexer<'a> {
pub(super) fn span(&self, start: BytePos) -> Span {
let end = self.last_pos();
if cfg!(debug_assertions) && start > end {
unreachable!(
"assertion failed: (span.start <= span.end).
start = {}, end = {}",
start.0, end.0
)
}
Span {
lo: start,
hi: end,
ctxt: SyntaxContext::empty(),
}
}
#[inline(always)]
pub(super) fn bump(&mut self) {
unsafe {
// Safety: Actually this is not safe but this is an internal method.
self.input.bump()
}
}
#[inline(always)]
pub(super) fn is(&mut self, c: u8) -> bool {
self.input.is_byte(c)
}
#[inline(always)]
pub(super) fn is_str(&self, s: &str) -> bool {
self.input.is_str(s)
}
#[inline(always)]
pub(super) fn eat(&mut self, c: u8) -> bool {
self.input.eat_byte(c)
}
#[inline(always)]
pub(super) fn cur(&mut self) -> Option<char> {
self.input.cur()
}
#[inline(always)]
pub(super) fn peek(&mut self) -> Option<char> {
self.input.peek()
}
#[inline(always)]
pub(super) fn peek_ahead(&mut self) -> Option<char> {
self.input.peek_ahead()
}
#[inline(always)]
pub(super) fn cur_pos(&mut self) -> BytePos {
self.input.cur_pos()
}
#[inline(always)]
pub(super) fn last_pos(&self) -> BytePos {
self.input.last_pos()
}
/// Shorthand for `let span = self.span(start); self.error_span(span)`
#[cold]
#[inline(never)]
pub(super) fn error<T>(&mut self, start: BytePos, kind: SyntaxError) -> LexResult<T> {
let span = self.span(start);
self.error_span(Span::new(span.lo, span.hi, span.ctxt), kind)
}
#[cold]
#[inline(never)]
pub(super) fn error_span<T>(&mut self, span: Span, kind: SyntaxError) -> LexResult<T> {
Err(Error::new(span, kind))
}
#[cold]
#[inline(never)]
pub(super) fn emit_error(&mut self, start: BytePos, kind: SyntaxError) {
let span = self.span(start);
self.emit_error_span(Span::new(span.lo, span.hi, span.ctxt), kind)
}
#[cold]
#[inline(never)]
pub(super) fn emit_error_span(&mut self, span: Span, kind: SyntaxError) {
if self.ctx.ignore_error {
return;
}
warn!("Lexer error at {:?}", span);
let err = Error::new(span, kind);
self.errors.borrow_mut().push(err);
}
#[cold]
#[inline(never)]
pub(super) fn emit_strict_mode_error(&mut self, start: BytePos, kind: SyntaxError) {
let span = self.span(start);
self.emit_strict_mode_error_span(Span::new(span.lo, span.hi, span.ctxt), kind)
}
#[cold]
#[inline(never)]
pub(super) fn emit_strict_mode_error_span(&mut self, span: Span, kind: SyntaxError) {
if self.ctx.strict {
self.emit_error_span(span, kind);
return;
}
let err = Error::new(span, kind);
self.add_module_mode_error(err);
}
#[cold]
#[inline(never)]
pub(super) fn emit_module_mode_error(&mut self, start: BytePos, kind: SyntaxError) {
let span = self.span(start);
self.emit_module_mode_error_span(Span::new(span.lo, span.hi, span.ctxt), kind)
}
/// Some codes are valid in a strict mode script but invalid in module
/// code.
#[cold]
#[inline(never)]
pub(super) fn emit_module_mode_error_span(&mut self, span: Span, kind: SyntaxError) {
let err = Error::new(span, kind);
self.add_module_mode_error(err);
}
/// Skip comments or whitespaces.
///
/// See https://tc39.github.io/ecma262/#sec-white-space
pub(super) fn skip_space<const LEX_COMMENTS: bool>(&mut self) -> LexResult<()> {
loop {
let (offset, newline) = {
let mut skip = SkipWhitespace {
input: self.input.as_str(),
newline: false,
offset: 0,
};
skip.scan();
(skip.offset, skip.newline)
};
self.input.bump_bytes(offset);
self.state.had_line_break |= newline;
if LEX_COMMENTS && self.input.is_byte(b'/') {
if self.peek() == Some('/') {
self.skip_line_comment(2);
continue;
} else if self.peek() == Some('*') {
self.skip_block_comment()?;
continue;
}
}
break;
}
Ok(())
}
#[inline(never)]
pub(super) fn skip_line_comment(&mut self, start_skip: usize) {
let start = self.cur_pos();
self.input.bump_bytes(start_skip);
let slice_start = self.cur_pos();
// foo // comment for foo
// bar
//
// foo
// // comment for bar
// bar
//
let is_for_next = self.state.had_line_break || !self.state.can_have_trailing_line_comment();
let idx = self
.input
.as_str()
.find(['\r', '\n', '\u{2028}', '\u{2029}'])
.map_or(self.input.as_str().len(), |v| {
self.state.had_line_break = true;
v
});
self.input.bump_bytes(idx);
let end = self.cur_pos();
if let Some(comments) = self.comments_buffer.as_mut() {
let s = unsafe {
// Safety: We know that the start and the end are valid
self.input.slice(slice_start, end)
};
let cmt = Comment {
kind: CommentKind::Line,
span: Span::new(start, end, SyntaxContext::empty()),
text: self.atoms.borrow_mut().intern(s),
};
if is_for_next {
comments.push_pending_leading(cmt);
} else {
comments.push(BufferedComment {
kind: BufferedCommentKind::Trailing,
pos: self.state.prev_hi,
comment: cmt,
});
}
}
unsafe {
// Safety: We got end from self.input
self.input.reset_to(end);
}
}
/// Expects current char to be '/' and next char to be '*'.
#[inline(never)]
pub(super) fn skip_block_comment(&mut self) -> LexResult<()> {
let start = self.cur_pos();
debug_assert_eq!(self.cur(), Some('/'));
debug_assert_eq!(self.peek(), Some('*'));
self.input.bump_bytes(2);
// jsdoc
let slice_start = self.cur_pos();
let mut was_star = if self.input.is_byte(b'*') {
self.bump();
true
} else {
false
};
let mut is_for_next = self.state.had_line_break || !self.state.can_have_trailing_comment();
while let Some(c) = self.cur() {
if was_star && c == '/' {
debug_assert_eq!(self.cur(), Some('/'));
self.bump(); // '/'
let end = self.cur_pos();
self.skip_space::<false>()?;
if self.input.is_byte(b';') {
is_for_next = false;
}
if let Some(comments) = self.comments_buffer.as_mut() {
let src = unsafe {
// Safety: We got slice_start and end from self.input so those are valid.
self.input.slice(slice_start, end)
};
let s = &src[..src.len() - 2];
let cmt = Comment {
kind: CommentKind::Block,
span: Span::new(start, end, SyntaxContext::empty()),
text: self.atoms.borrow_mut().intern(s),
};
let _ = self.input.peek();
if is_for_next {
comments.push_pending_leading(cmt);
} else {
comments.push(BufferedComment {
kind: BufferedCommentKind::Trailing,
pos: self.state.prev_hi,
comment: cmt,
});
}
}
return Ok(());
}
if c.is_line_terminator() {
self.state.had_line_break = true;
}
was_star = c == '*';
self.bump();
}
self.error(start, SyntaxError::UnterminatedBlockComment)?
}
}
/// Implemented for `char`.
pub trait CharExt: Copy {
fn to_char(self) -> Option<char>;
/// Test whether a given character code starts an identifier.
///
/// https://tc39.github.io/ecma262/#prod-IdentifierStart
#[inline]
fn is_ident_start(self) -> bool {
let c = match self.to_char() {
Some(c) => c,
None => return false,
};
Ident::is_valid_start(c)
}
/// Test whether a given character is part of an identifier.
#[inline]
fn is_ident_part(self) -> bool {
let c = match self.to_char() {
Some(c) => c,
None => return false,
};
Ident::is_valid_continue(c)
}
/// See https://tc39.github.io/ecma262/#sec-line-terminators
#[inline]
fn is_line_terminator(self) -> bool {
let c = match self.to_char() {
Some(c) => c,
None => return false,
};
matches!(c, '\r' | '\n' | '\u{2028}' | '\u{2029}')
}
/// See https://tc39.github.io/ecma262/#sec-literals-string-literals
#[inline]
fn is_line_break(self) -> bool {
let c = match self.to_char() {
Some(c) => c,
None => return false,
};
matches!(c, '\r' | '\n')
}
/// See https://tc39.github.io/ecma262/#sec-white-space
#[inline]
fn is_ws(self) -> bool {
let c = match self.to_char() {
Some(c) => c,
None => return false,
};
match c {
'\u{0009}' | '\u{000b}' | '\u{000c}' | '\u{0020}' | '\u{00a0}' | '\u{feff}' => true,
_ => {
if self.is_line_terminator() {
// NOTE: Line terminator is not whitespace.
false
} else {
c.is_whitespace()
}
}
}
}
}
impl CharExt for Char {
#[inline(always)]
fn to_char(self) -> Option<char> {
char::from_u32(self.0)
}
}
impl CharExt for char {
#[inline(always)]
fn to_char(self) -> Option<char> {
Some(self)
}
}

View file

@ -0,0 +1,100 @@
/// Returns true if it's done
pub(super) type ByteHandler = Option<for<'aa> fn(&mut SkipWhitespace<'aa>) -> usize>;
/// Lookup table for whitespace
static BYTE_HANDLERS: [ByteHandler; 256] = [
// 0 1 2 3 4 5 6 7 8 9 A B C D E F //
___, ___, ___, ___, ___, ___, ___, ___, ___, SPC, NLN, SPC, SPC, NLN, ___, ___, // 0
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 1
SPC, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 2
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 3
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 4
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 5
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 6
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 7
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // 8
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // 9
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // A
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // B
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // C
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // D
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // E
UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // F
];
/// Stop
const ___: ByteHandler = None;
/// Newline
const NLN: ByteHandler = Some(|skip| {
skip.newline = true;
1
});
/// Space
const SPC: ByteHandler = Some(|_| 1);
/// Unicode
const UNI: ByteHandler = Some(|skip| {
let s = unsafe {
// Safety: `skip.offset` is always valid
skip.input.get_unchecked(skip.offset..)
};
let c = unsafe {
// Safety: Byte handlers are called only when `skip.input` is not empty
s.chars().next().unwrap_unchecked()
};
match c {
// white spaces
'\u{feff}' => {}
// line breaks
'\u{2028}' | '\u{2029}' => {
skip.newline = true;
}
_ if c.is_whitespace() => {}
_ => return 0,
}
c.len_utf8()
});
/// API is taked from oxc by Boshen (https://github.com/Boshen/oxc/pull/26)
pub(super) struct SkipWhitespace<'a> {
pub input: &'a str,
/// Total offset
pub offset: usize,
/// Found newline
pub newline: bool,
}
impl SkipWhitespace<'_> {
#[inline(always)]
pub fn scan(&mut self) {
let mut byte;
loop {
byte = match self.input.as_bytes().get(self.offset).copied() {
Some(v) => v,
None => return,
};
let handler = unsafe { *(&BYTE_HANDLERS as *const ByteHandler).offset(byte as isize) };
if let Some(handler) = handler {
let delta = handler(self);
if delta == 0 {
return;
}
self.offset += delta;
} else {
return;
}
}
}
}

View file

@ -0,0 +1,508 @@
//! EcmaScript/TypeScript parser for the rust programming language.
//!
//! # Features
//!
//! ## Heavily tested
//!
//! Passes almost all tests from [tc39/test262][].
//!
//! ## Error reporting
//!
//! ```sh
//! error: 'implements', 'interface', 'let', 'package', 'private', 'protected', 'public', 'static', or 'yield' cannot be used as an identifier in strict mode
//! --> invalid.js:3:10
//! |
//! 3 | function yield() {
//! | ^^^^^
//! ```
//!
//! ## Error recovery
//!
//! The parser can recover from some parsing errors. For example, parser returns
//! `Ok(Module)` for the code below, while emitting error to handler.
//!
//! ```ts
//! const CONST = 9000 % 2;
//! const enum D {
//! // Comma is required, but parser can recover because of the newline.
//! d = 10
//! g = CONST
//! }
//! ```
//!
//! # Example (lexer)
//!
//! See `lexer.rs` in examples directory.
//!
//! # Example (parser)
//!
//! ```
//! #[macro_use]
//! extern crate swc_common;
//! extern crate swc_ecma_parser;
//! use swc_common::sync::Lrc;
//! use swc_common::{
//! errors::{ColorConfig, Handler},
//! FileName, FilePathMapping, SourceMap,
//! };
//! use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax};
//!
//! fn main() {
//! let cm: Lrc<SourceMap> = Default::default();
//! let handler =
//! Handler::with_tty_emitter(ColorConfig::Auto, true, false,
//! Some(cm.clone()));
//!
//! // Real usage
//! // let fm = cm
//! // .load_file(Path::new("test.js"))
//! // .expect("failed to load test.js");
//! let fm = cm.new_source_file(
//! FileName::Custom("test.js".into()),
//! "function foo() {}".into(),
//! );
//! let lexer = Lexer::new(
//! // We want to parse ecmascript
//! Syntax::Es(Default::default()),
//! // EsVersion defaults to es5
//! Default::default(),
//! StringInput::from(&*fm),
//! None,
//! );
//!
//! let mut parser = Parser::new_from(lexer);
//!
//! for e in parser.take_errors() {
//! e.into_diagnostic(&handler).emit();
//! }
//!
//! let _module = parser
//! .parse_module()
//! .map_err(|mut e| {
//! // Unrecoverable fatal error occurred
//! e.into_diagnostic(&handler).emit()
//! })
//! .expect("failed to parser module");
//! }
//! ```
//!
//! ## Cargo features
//!
//! ### `typescript`
//!
//! Enables typescript parser.
//!
//! ### `verify`
//!
//! Verify more errors, using `swc_ecma_visit`.
//!
//! ## Known issues
//!
//! ### Null character after `\`
//!
//! Because [String] of rust should only contain valid utf-8 characters while
//! javascript allows non-utf8 characters, the parser stores invalid utf8
//! characters in escaped form.
//!
//! As a result, swc needs a way to distinguish invalid-utf8 code points and
//! input specified by the user. The parser stores a null character right after
//! `\\` for non-utf8 code points. Note that other parts of swc is aware of this
//! fact.
//!
//! Note that this can be changed at anytime with a breaking change.
//!
//! [tc39/test262]:https://github.com/tc39/test262
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(test, feature(test))]
#![deny(clippy::all)]
#![deny(unused)]
#![allow(clippy::nonminimal_bool)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::unnecessary_unwrap)]
#![allow(clippy::vec_box)]
#![allow(clippy::wrong_self_convention)]
#![allow(clippy::match_like_matches_macro)]
use error::Error;
use lexer::Lexer;
use serde::{Deserialize, Serialize};
use swc_common::{comments::Comments, input::SourceFileInput, SourceFile};
use swc_ecma_ast::*;
pub use self::{
lexer::input::{Input, StringInput},
parser::*,
};
#[deprecated(note = "Use `EsVersion` instead")]
pub type JscTarget = EsVersion;
#[macro_use]
mod macros;
pub mod error;
pub mod lexer;
mod parser;
pub mod token;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
#[serde(deny_unknown_fields, tag = "syntax")]
pub enum Syntax {
/// Standard
#[serde(rename = "ecmascript")]
Es(EsConfig),
/// This variant requires the cargo feature `typescript` to be enabled.
#[cfg(feature = "typescript")]
#[cfg_attr(docsrs, doc(cfg(feature = "typescript")))]
#[serde(rename = "typescript")]
Typescript(TsConfig),
}
impl Default for Syntax {
fn default() -> Self {
Syntax::Es(Default::default())
}
}
impl Syntax {
fn auto_accessors(self) -> bool {
match self {
Syntax::Es(EsConfig {
auto_accessors: true,
..
}) => true,
Syntax::Typescript(_) => true,
_ => false,
}
}
pub fn import_attributes(self) -> bool {
match self {
Syntax::Es(EsConfig {
import_attributes, ..
}) => import_attributes,
Syntax::Typescript(_) => true,
}
}
/// Should we parse jsx?
pub fn jsx(self) -> bool {
matches!(
self,
Syntax::Es(EsConfig { jsx: true, .. }) | Syntax::Typescript(TsConfig { tsx: true, .. })
)
}
pub fn fn_bind(self) -> bool {
matches!(self, Syntax::Es(EsConfig { fn_bind: true, .. }))
}
pub fn decorators(self) -> bool {
matches!(
self,
Syntax::Es(EsConfig {
decorators: true,
..
}) | Syntax::Typescript(TsConfig {
decorators: true,
..
})
)
}
pub fn decorators_before_export(self) -> bool {
matches!(
self,
Syntax::Es(EsConfig {
decorators_before_export: true,
..
}) | Syntax::Typescript(..)
)
}
/// Should we parse typescript?
#[cfg(not(feature = "typescript"))]
pub const fn typescript(self) -> bool {
false
}
/// Should we parse typescript?
#[cfg(feature = "typescript")]
pub const fn typescript(self) -> bool {
matches!(self, Syntax::Typescript(..))
}
pub fn export_default_from(self) -> bool {
matches!(
self,
Syntax::Es(EsConfig {
export_default_from: true,
..
})
)
}
pub fn dts(self) -> bool {
match self {
Syntax::Typescript(t) => t.dts,
_ => false,
}
}
pub(crate) fn allow_super_outside_method(self) -> bool {
match self {
Syntax::Es(EsConfig {
allow_super_outside_method,
..
}) => allow_super_outside_method,
Syntax::Typescript(_) => true,
}
}
pub(crate) fn allow_return_outside_function(self) -> bool {
match self {
Syntax::Es(EsConfig {
allow_return_outside_function,
..
}) => allow_return_outside_function,
Syntax::Typescript(_) => false,
}
}
pub(crate) fn early_errors(self) -> bool {
match self {
Syntax::Typescript(t) => !t.no_early_errors,
Syntax::Es(..) => true,
}
}
fn disallow_ambiguous_jsx_like(self) -> bool {
match self {
Syntax::Typescript(t) => t.disallow_ambiguous_jsx_like,
_ => false,
}
}
pub fn explicit_resource_management(&self) -> bool {
match self {
Syntax::Es(EsConfig {
explicit_resource_management: using_decl,
..
}) => *using_decl,
Syntax::Typescript(_) => true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TsConfig {
#[serde(default)]
pub tsx: bool,
#[serde(default)]
pub decorators: bool,
/// `.d.ts`
#[serde(skip, default)]
pub dts: bool,
#[serde(skip, default)]
pub no_early_errors: bool,
/// babel: `disallowAmbiguousJSXLike`
/// Even when JSX parsing is not enabled, this option disallows using syntax
/// that would be ambiguous with JSX (`<X> y` type assertions and
/// `<X>()=>{}` type arguments)
/// see: https://babeljs.io/docs/en/babel-plugin-transform-typescript#disallowambiguousjsxlike
#[serde(skip, default)]
pub disallow_ambiguous_jsx_like: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EsConfig {
#[serde(default)]
pub jsx: bool,
/// Support function bind expression.
#[serde(rename = "functionBind")]
#[serde(default)]
pub fn_bind: bool,
/// Enable decorators.
#[serde(default)]
pub decorators: bool,
/// babel: `decorators.decoratorsBeforeExport`
///
/// Effective only if `decorator` is true.
#[serde(rename = "decoratorsBeforeExport")]
#[serde(default)]
pub decorators_before_export: bool,
#[serde(default)]
pub export_default_from: bool,
/// Stage 3.
#[serde(default, alias = "importAssertions")]
pub import_attributes: bool,
#[serde(default, rename = "allowSuperOutsideMethod")]
pub allow_super_outside_method: bool,
#[serde(default, rename = "allowReturnOutsideFunction")]
pub allow_return_outside_function: bool,
#[serde(default)]
pub auto_accessors: bool,
#[serde(default)]
pub explicit_resource_management: bool,
}
/// Syntactic context.
#[derive(Debug, Clone, Copy, Default)]
pub struct Context {
/// `true` while backtracking
ignore_error: bool,
/// Is in module code?
module: bool,
can_be_module: bool,
strict: bool,
expr_ctx: ExpressionContext,
include_in_expr: bool,
/// If true, await expression is parsed, and "await" is treated as a
/// keyword.
in_async: bool,
/// If true, yield expression is parsed, and "yield" is treated as a
/// keyword.
in_generator: bool,
is_continue_allowed: bool,
is_break_allowed: bool,
in_type: bool,
/// Typescript extension.
should_not_lex_lt_or_gt_as_type: bool,
/// Typescript extension.
in_declare: bool,
/// If true, `:` should not be treated as a type annotation.
in_cond_expr: bool,
will_expect_colon_for_cond: bool,
in_class: bool,
in_class_field: bool,
in_function: bool,
/// This indicates current scope or the scope out of arrow function is
/// function declaration or function expression or not.
inside_non_arrow_function_scope: bool,
in_parameters: bool,
has_super_class: bool,
in_property_name: bool,
in_forced_jsx_context: bool,
// If true, allow super.x and super[x]
allow_direct_super: bool,
ignore_else_clause: bool,
disallow_conditional_types: bool,
allow_using_decl: bool,
}
#[derive(Debug, Clone, Copy, Default)]
struct ExpressionContext {
// TODO:
// - include_in
for_loop_init: bool,
for_await_loop_init: bool,
}
#[cfg(test)]
fn with_test_sess<F, Ret>(src: &str, f: F) -> Result<Ret, ::testing::StdErr>
where
F: FnOnce(&swc_common::errors::Handler, StringInput<'_>) -> Result<Ret, ()>,
{
use swc_common::FileName;
::testing::run_test(false, |cm, handler| {
let fm = cm.new_source_file(FileName::Real("testing".into()), src.into());
f(handler, (&*fm).into())
})
}
pub fn with_file_parser<T>(
fm: &SourceFile,
syntax: Syntax,
target: EsVersion,
comments: Option<&dyn Comments>,
recovered_errors: &mut Vec<Error>,
op: impl for<'aa> FnOnce(&mut Parser<Lexer>) -> PResult<T>,
) -> PResult<T> {
let lexer = Lexer::new(syntax, target, SourceFileInput::from(fm), comments);
let mut p = Parser::new_from(lexer);
let ret = op(&mut p);
recovered_errors.append(&mut p.take_errors());
ret
}
macro_rules! expose {
(
$name:ident,
$T:ty,
$($t:tt)*
) => {
/// Note: This is recommended way to parse a file.
///
/// This is an alias for [Parser], [Lexer] and [SourceFileInput], but
/// instantiation of generics occur in `swc_ecma_parser` crate.
pub fn $name(
fm: &SourceFile,
syntax: Syntax,
target: EsVersion,
comments: Option<&dyn Comments>,
recovered_errors: &mut Vec<Error>,
) -> PResult<$T> {
with_file_parser(fm, syntax, target, comments, recovered_errors, $($t)*)
}
};
}
expose!(parse_file_as_expr, Box<Expr>, |p| {
// This allow to parse `import.meta`
p.input().ctx.can_be_module = true;
p.parse_expr()
});
expose!(parse_file_as_module, Module, |p| { p.parse_module() });
expose!(parse_file_as_script, Script, |p| { p.parse_script() });
expose!(parse_file_as_program, Program, |p| { p.parse_program() });
#[inline(always)]
#[cfg(any(target_arch = "wasm32", target_arch = "arm", not(feature = "stacker")))]
fn maybe_grow<R, F: FnOnce() -> R>(_red_zone: usize, _stack_size: usize, callback: F) -> R {
callback()
}
#[inline(always)]
#[cfg(all(
not(any(target_arch = "wasm32", target_arch = "arm")),
feature = "stacker"
))]
fn maybe_grow<R, F: FnOnce() -> R>(red_zone: usize, stack_size: usize, callback: F) -> R {
stacker::maybe_grow(red_zone, stack_size, callback)
}

View file

@ -0,0 +1,435 @@
#[allow(unused)]
macro_rules! tok {
('`') => {
crate::token::Token::BackQuote
};
// (';') => { Token::Semi };
('@') => {
crate::token::Token::At
};
('#') => {
crate::token::Token::Hash
};
('&') => {
crate::token::Token::BinOp(crate::token::BinOpToken::BitAnd)
};
('|') => {
crate::token::Token::BinOp(crate::token::BinOpToken::BitOr)
};
('^') => {
crate::token::Token::BinOp(crate::token::BinOpToken::BitXor)
};
('+') => {
crate::token::Token::BinOp(crate::token::BinOpToken::Add)
};
('-') => {
crate::token::Token::BinOp(crate::token::BinOpToken::Sub)
};
("??") => {
crate::token::Token::BinOp(crate::token::BinOpToken::NullishCoalescing)
};
('~') => {
crate::token::Token::Tilde
};
('!') => {
crate::token::Token::Bang
};
("&&") => {
crate::token::Token::BinOp(crate::token::BinOpToken::LogicalAnd)
};
("||") => {
crate::token::Token::BinOp(crate::token::BinOpToken::LogicalOr)
};
("&&=") => {
crate::token::Token::AssignOp(crate::token::AssignOpToken::AndAssign)
};
("||=") => {
crate::token::Token::AssignOp(crate::token::AssignOpToken::OrAssign)
};
("??=") => {
crate::token::Token::AssignOp(crate::token::AssignOpToken::NullishAssign)
};
("==") => {
crate::token::Token::BinOp(crate::token::BinOpToken::EqEq)
};
("===") => {
crate::token::Token::BinOp(crate::token::BinOpToken::EqEqEq)
};
("!=") => {
crate::token::Token::BinOp(crate::token::BinOpToken::NotEq)
};
("!==") => {
crate::token::Token::BinOp(crate::token::BinOpToken::NotEqEq)
};
(',') => {
crate::token::Token::Comma
};
('?') => {
crate::token::Token::QuestionMark
};
(':') => {
crate::token::Token::Colon
};
('.') => {
crate::token::Token::Dot
};
("=>") => {
crate::token::Token::Arrow
};
("...") => {
crate::token::Token::DotDotDot
};
("${") => {
crate::token::Token::DollarLBrace
};
('+') => {
crate::token::Token::BinOp(crate::token::BinOpToken::Add)
};
('-') => {
crate::token::Token::BinOp(crate::token::BinOpToken::Sub)
};
('*') => {
crate::token::Token::BinOp(crate::token::BinOpToken::Mul)
};
('/') => {
crate::token::Token::BinOp(crate::token::BinOpToken::Div)
};
("/=") => {
crate::token::Token::AssignOp(crate::token::AssignOpToken::DivAssign)
};
('%') => {
crate::token::Token::BinOp(crate::token::BinOpToken::Mod)
};
('~') => {
crate::token::Token::Tilde
};
('<') => {
crate::token::Token::BinOp(crate::token::BinOpToken::Lt)
};
('>') => {
crate::token::Token::BinOp(crate::token::BinOpToken::Gt)
};
(">>") => {
crate::token::Token::BinOp(crate::token::BinOpToken::RShift)
};
(">=") => {
crate::token::Token::BinOp(crate::token::BinOpToken::GtEq)
};
("++") => {
crate::token::Token::PlusPlus
};
("--") => {
crate::token::Token::MinusMinus
};
('=') => {
crate::token::Token::AssignOp(crate::token::AssignOpToken::Assign)
};
('(') => {
crate::token::Token::LParen
};
(')') => {
crate::token::Token::RParen
};
('{') => {
crate::token::Token::LBrace
};
('}') => {
crate::token::Token::RBrace
};
('[') => {
crate::token::Token::LBracket
};
(']') => {
crate::token::Token::RBracket
};
("async") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("async")))
};
("as") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("as")))
};
("await") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Await))
};
("break") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Break))
};
("case") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Case))
};
("catch") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Catch))
};
("class") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Class))
};
("const") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Const))
};
("continue") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Continue))
};
("debugger") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Debugger))
};
("default") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Default_))
};
("delete") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Delete))
};
("do") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Do))
};
("else") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Else))
};
("export") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Export))
};
("extends") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Extends))
};
("false") => {
crate::token::Token::Word(crate::token::Word::False)
};
("finally") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Finally))
};
("for") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::For))
};
("from") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("from")))
};
("function") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Function))
};
("if") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::If))
};
("in") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::In))
};
("instanceof") => {
crate::token::Token::Word(crate::token::Word::Keyword(
crate::token::Keyword::InstanceOf,
))
};
("import") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Import))
};
("let") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Let))
};
("new") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::New))
};
("null") => {
crate::token::Token::Word(crate::token::Word::Null)
};
("of") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("of")))
};
("return") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Return))
};
("super") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Super))
};
("static") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("static")))
};
("switch") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Switch))
};
("target") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("target")))
};
("this") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::This))
};
("throw") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Throw))
};
("true") => {
crate::token::Token::Word(crate::token::Word::True)
};
("try") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Try))
};
("typeof") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::TypeOf))
};
("var") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Var))
};
("void") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Void))
};
("while") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::While))
};
("with") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::With))
};
("yield") => {
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Yield))
};
("accessor") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("accessor")))
};
// ----------
// JSX
// ----------
(JSXTagStart) => {
crate::token::Token::JSXTagStart
};
(JSXTagEnd) => {
crate::token::Token::JSXTagEnd
};
// ----------
// Typescript
// ----------
("asserts") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("asserts")))
};
("implements") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("implements")))
};
("is") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("is")))
};
("new") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("new")))
};
("keyof") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("keyof")))
};
("unique") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("unique")))
};
("object") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("object")))
};
("global") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("global")))
};
("require") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("require")))
};
("enum") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("enum")))
};
("readonly") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("readonly")))
};
("as") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("as")))
};
("satisfies") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("satisfies")))
};
("namespace") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("namespace")))
};
("abstract") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("abstract")))
};
("infer") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("infer")))
};
("any") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("any")))
};
("boolean") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("boolean")))
};
("bigint") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("bigint")))
};
("intrinsic") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("intrinsic")))
};
("never") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("never")))
};
("number") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("number")))
};
("string") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("string")))
};
("symbol") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("symbol")))
};
("unknown") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("unknown")))
};
("require") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("require")))
};
("interface") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("interface")))
};
("declare") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("declare")))
};
("override") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("override")))
};
("undefined") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("undefined")))
};
("meta") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("meta")))
};
("type") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("type")))
};
("assert") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("assert")))
};
("get") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("get")))
};
("set") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("set")))
};
("out") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("out")))
};
("public") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("public")))
};
("private") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("private")))
};
("protected") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("protected")))
};
("using") => {
crate::token::Token::Word(crate::token::Word::Ident(swc_atoms::js_word!("using")))
};
}
macro_rules! token_including_semi {
(';') => {
Token::Semi
};
($t:tt) => {
tok!($t)
};
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,437 @@
//! Parser for unary operations and binary operations.
use swc_common::Spanned;
use tracing::trace;
use super::*;
impl<I: Tokens> Parser<I> {
/// Name from spec: 'LogicalORExpression'
pub(super) fn parse_bin_expr(&mut self) -> PResult<Box<Expr>> {
trace_cur!(self, parse_bin_expr);
let ctx = self.ctx();
let left = match self.parse_unary_expr() {
Ok(v) => v,
Err(err) => {
trace_cur!(self, parse_bin_expr__recovery_unary_err);
match cur!(self, true)? {
&tok!("in") if ctx.include_in_expr => {
self.emit_err(self.input.cur_span(), SyntaxError::TS1109);
Box::new(Expr::Invalid(Invalid { span: err.span() }))
}
&tok!("instanceof") | &Token::BinOp(..) => {
self.emit_err(self.input.cur_span(), SyntaxError::TS1109);
Box::new(Expr::Invalid(Invalid { span: err.span() }))
}
_ => return Err(err),
}
}
};
return_if_arrow!(self, left);
self.parse_bin_op_recursively(left, 0)
}
/// Parse binary operators with the operator precedence parsing
/// algorithm. `left` is the left-hand side of the operator.
/// `minPrec` provides context that allows the function to stop and
/// defer further parser to one of its callers when it encounters an
/// operator that has a lower precedence than the set it is parsing.
///
/// `parseExprOp`
pub(in crate::parser) fn parse_bin_op_recursively(
&mut self,
mut left: Box<Expr>,
mut min_prec: u8,
) -> PResult<Box<Expr>> {
loop {
let (next_left, next_prec) = self.parse_bin_op_recursively_inner(left, min_prec)?;
match &*next_left {
Expr::Bin(BinExpr {
span,
left,
op: op!("&&"),
..
})
| Expr::Bin(BinExpr {
span,
left,
op: op!("||"),
..
}) => {
if let Expr::Bin(BinExpr { op: op!("??"), .. }) = &**left {
self.emit_err(*span, SyntaxError::NullishCoalescingWithLogicalOp);
}
}
_ => {}
}
min_prec = match next_prec {
Some(v) => v,
None => return Ok(next_left),
};
left = next_left;
}
}
/// Returns `(left, Some(next_prec))` or `(expr, None)`.
fn parse_bin_op_recursively_inner(
&mut self,
left: Box<Expr>,
min_prec: u8,
) -> PResult<(Box<Expr>, Option<u8>)> {
const PREC_OF_IN: u8 = 7;
if self.input.syntax().typescript()
&& PREC_OF_IN > min_prec
&& !self.input.had_line_break_before_cur()
&& is!(self, "as")
{
let start = left.span_lo();
let expr = left;
let node = if peeked_is!(self, "const") {
bump!(self); // as
let _ = cur!(self, false);
bump!(self); // const
Box::new(Expr::TsConstAssertion(TsConstAssertion {
span: span!(self, start),
expr,
}))
} else {
let type_ann = self.next_then_parse_ts_type()?;
Box::new(Expr::TsAs(TsAsExpr {
span: span!(self, start),
expr,
type_ann,
}))
};
return self.parse_bin_op_recursively_inner(node, min_prec);
}
if self.input.syntax().typescript()
&& !self.input.had_line_break_before_cur()
&& is!(self, "satisfies")
{
let start = left.span_lo();
let expr = left;
let node = {
let type_ann = self.next_then_parse_ts_type()?;
Box::new(Expr::TsSatisfies(TsSatisfiesExpr {
span: span!(self, start),
expr,
type_ann,
}))
};
return self.parse_bin_op_recursively_inner(node, min_prec);
}
let ctx = self.ctx();
// Return left on eof
let word = match cur!(self, false) {
Ok(cur) => cur,
Err(..) => return Ok((left, None)),
};
let op = match *word {
tok!("in") if ctx.include_in_expr => op!("in"),
tok!("instanceof") => op!("instanceof"),
Token::BinOp(op) => op.into(),
_ => {
return Ok((left, None));
}
};
if op.precedence() <= min_prec {
if cfg!(feature = "debug") {
trace!(
"returning {:?} without parsing {:?} because min_prec={}, prec={}",
left,
op,
min_prec,
op.precedence()
);
}
return Ok((left, None));
}
bump!(self);
if cfg!(feature = "debug") {
trace!(
"parsing binary op {:?} min_prec={}, prec={}",
op,
min_prec,
op.precedence()
);
}
match *left {
// This is invalid syntax.
Expr::Unary { .. } | Expr::Await(..) if op == op!("**") => {
// Correct implementation would be returning Ok(left) and
// returning "unexpected token '**'" on next.
// But it's not useful error message.
syntax_error!(
self,
SyntaxError::UnaryInExp {
// FIXME: Use display
left: format!("{:?}", left),
left_span: left.span(),
}
)
}
_ => {}
}
let right = {
let left_of_right = self.parse_unary_expr()?;
self.parse_bin_op_recursively(
left_of_right,
if op == op!("**") {
// exponential operator is right associative
op.precedence() - 1
} else {
op.precedence()
},
)?
};
/* this check is for all ?? operators
* a ?? b && c for this example
* b && c => This is considered as a logical expression in the ast tree
* a => Identifier
* so for ?? operator we need to check in this case the right expression to
* have parenthesis second case a && b ?? c
* here a && b => This is considered as a logical expression in the ast tree
* c => identifier
* so now here for ?? operator we need to check the left expression to have
* parenthesis if the parenthesis is missing we raise an error and
* throw it
*/
if op == op!("??") {
match *left {
Expr::Bin(BinExpr { span, op, .. }) if op == op!("&&") || op == op!("||") => {
self.emit_err(span, SyntaxError::NullishCoalescingWithLogicalOp);
}
_ => {}
}
match *right {
Expr::Bin(BinExpr { span, op, .. }) if op == op!("&&") || op == op!("||") => {
self.emit_err(span, SyntaxError::NullishCoalescingWithLogicalOp);
}
_ => {}
}
}
let node = Box::new(Expr::Bin(BinExpr {
span: Span::new(left.span_lo(), right.span_hi(), Default::default()),
op,
left,
right,
}));
Ok((node, Some(min_prec)))
}
/// Parse unary expression and update expression.
///
/// spec: 'UnaryExpression'
pub(in crate::parser) fn parse_unary_expr(&mut self) -> PResult<Box<Expr>> {
trace_cur!(self, parse_unary_expr);
let start = cur_pos!(self);
if !self.input.syntax().jsx() && self.input.syntax().typescript() && eat!(self, '<') {
if eat!(self, "const") {
expect!(self, '>');
let expr = self.parse_unary_expr()?;
return Ok(Box::new(Expr::TsConstAssertion(TsConstAssertion {
span: span!(self, start),
expr,
})));
}
return self
.parse_ts_type_assertion(start)
.map(Expr::from)
.map(Box::new);
}
// Parse update expression
if is!(self, "++") || is!(self, "--") {
let op = if bump!(self) == tok!("++") {
op!("++")
} else {
op!("--")
};
let arg = self.parse_unary_expr()?;
let span = Span::new(start, arg.span_hi(), Default::default());
self.check_assign_target(&arg, false);
return Ok(Box::new(Expr::Update(UpdateExpr {
span,
prefix: true,
op,
arg,
})));
}
// Parse unary expression
if is_one_of!(self, "delete", "void", "typeof", '+', '-', '~', '!') {
let op = match bump!(self) {
tok!("delete") => op!("delete"),
tok!("void") => op!("void"),
tok!("typeof") => op!("typeof"),
tok!('+') => op!(unary, "+"),
tok!('-') => op!(unary, "-"),
tok!('~') => op!("~"),
tok!('!') => op!("!"),
_ => unreachable!(),
};
let arg_start = cur_pos!(self) - BytePos(1);
let arg = match self.parse_unary_expr() {
Ok(expr) => expr,
Err(err) => {
self.emit_error(err);
Box::new(Expr::Invalid(Invalid {
span: Span::new(arg_start, arg_start, Default::default()),
}))
}
};
if op == op!("delete") {
if let Expr::Ident(ref i) = *arg {
self.emit_strict_mode_err(i.span, SyntaxError::TS1102)
}
}
if self.input.syntax().typescript() && op == op!("delete") {
match arg.unwrap_parens() {
Expr::Member(..) => {}
Expr::OptChain(OptChainExpr { base, .. })
if matches!(&**base, OptChainBase::Member(..)) => {}
expr => {
self.emit_err(expr.span(), SyntaxError::TS2703);
}
}
}
return Ok(Box::new(Expr::Unary(UnaryExpr {
span: Span::new(start, arg.span_hi(), Default::default()),
op,
arg,
})));
}
if is!(self, "await") {
return self.parse_await_expr();
}
// UpdateExpression
let expr = self.parse_lhs_expr()?;
return_if_arrow!(self, expr);
// Line terminator isn't allowed here.
if self.input.had_line_break_before_cur() {
return Ok(expr);
}
if is_one_of!(self, "++", "--") {
self.check_assign_target(&expr, false);
let op = if bump!(self) == tok!("++") {
op!("++")
} else {
op!("--")
};
return Ok(Box::new(Expr::Update(UpdateExpr {
span: span!(self, expr.span_lo()),
prefix: false,
op,
arg: expr,
})));
}
Ok(expr)
}
pub(crate) fn parse_await_expr(&mut self) -> PResult<Box<Expr>> {
let start = cur_pos!(self);
assert_and_bump!(self, "await");
if is!(self, '*') {
syntax_error!(self, SyntaxError::AwaitStar);
}
let ctx = self.ctx();
let span = span!(self, start);
if is_one_of!(self, ')', ']', ';', ',') && !ctx.in_async {
if ctx.module {
self.emit_err(span, SyntaxError::InvalidIdentInAsync);
}
return Ok(Box::new(Expr::Ident(Ident::new(js_word!("await"), span))));
}
if ctx.in_function && !ctx.in_async {
self.emit_err(self.input.cur_span(), SyntaxError::AwaitInFunction);
}
if ctx.in_parameters && !ctx.in_function {
self.emit_err(span, SyntaxError::AwaitParamInAsync);
}
let arg = self.parse_unary_expr()?;
Ok(Box::new(Expr::Await(AwaitExpr {
span: span!(self, start),
arg,
})))
}
}
#[cfg(test)]
mod tests {
use swc_common::DUMMY_SP as span;
use swc_ecma_visit::assert_eq_ignore_span;
use super::*;
fn bin(s: &'static str) -> Box<Expr> {
test_parser(s, Syntax::default(), |p| p.parse_bin_expr())
}
#[test]
fn simple() {
assert_eq_ignore_span!(
bin("5 + 4 * 7"),
Box::new(Expr::Bin(BinExpr {
span,
op: op!(bin, "+"),
left: bin("5"),
right: bin("4 * 7"),
}))
);
}
#[test]
fn same_prec() {
assert_eq_ignore_span!(
bin("5 + 4 + 7"),
Box::new(Expr::Bin(BinExpr {
span,
op: op!(bin, "+"),
left: bin("5 + 4"),
right: bin("7"),
}))
);
}
}

View file

@ -0,0 +1,574 @@
extern crate test;
use std::hint::black_box;
use swc_common::{FileName, SourceMap, DUMMY_SP as span};
use swc_ecma_visit::assert_eq_ignore_span;
use test::Bencher;
use super::*;
use crate::{parse_file_as_expr, EsConfig};
fn syntax() -> Syntax {
Syntax::Es(EsConfig {
allow_super_outside_method: true,
..Default::default()
})
}
fn lhs(s: &'static str) -> Box<Expr> {
test_parser(s, syntax(), |p| p.parse_lhs_expr())
}
fn new_expr(s: &'static str) -> Box<Expr> {
test_parser(s, syntax(), |p| p.parse_new_expr())
}
fn member_expr(s: &'static str) -> Box<Expr> {
test_parser(s, syntax(), |p| p.parse_member_expr())
}
fn expr(s: &'static str) -> Box<Expr> {
test_parser(s, syntax(), |p| {
p.parse_stmt(true).map(|stmt| match stmt {
Stmt::Expr(expr) => expr.expr,
_ => unreachable!(),
})
})
}
fn regex_expr() -> Box<Expr> {
Box::new(Expr::Assign(AssignExpr {
span,
left: PatOrExpr::Pat(Box::new(Pat::Ident(Ident::new("re".into(), span).into()))),
op: AssignOp::Assign,
right: Box::new(Expr::Lit(Lit::Regex(Regex {
span,
exp: "w+".into(),
flags: "".into(),
}))),
}))
}
#[test]
fn regex_single_line_comment() {
assert_eq_ignore_span!(
expr(
r#"re = // ...
/w+/"#
),
regex_expr()
)
}
#[test]
fn regex_multi_line_comment() {
assert_eq_ignore_span!(expr(r#"re = /* ... *//w+/"#), regex_expr())
}
#[test]
fn regex_multi_line_comment_with_lines() {
assert_eq_ignore_span!(
expr(
r#"re =
/*
...
*/
/w+/"#
),
regex_expr()
)
}
#[test]
fn arrow_assign() {
assert_eq_ignore_span!(
expr("a = b => false"),
Box::new(Expr::Assign(AssignExpr {
span,
left: PatOrExpr::Pat(Box::new(Ident::new("a".into(), span).into())),
op: op!("="),
right: expr("b => false"),
}))
);
}
#[test]
fn async_call() {
assert_eq_ignore_span!(
expr("async()"),
Box::new(Expr::Call(CallExpr {
span,
callee: Callee::Expr(expr("async")),
args: vec![],
type_args: None,
}))
);
}
#[test]
fn async_arrow() {
assert_eq_ignore_span!(
expr("async () => foo"),
Box::new(Expr::Arrow(ArrowExpr {
span,
is_async: true,
is_generator: false,
params: vec![],
body: Box::new(BlockStmtOrExpr::Expr(expr("foo"))),
return_type: None,
type_params: None,
}))
);
}
#[test]
fn object_rest_pat() {
assert_eq_ignore_span!(
expr("({ ...a34 }) => {}"),
Box::new(Expr::Arrow(ArrowExpr {
span,
is_async: false,
is_generator: false,
params: vec![Pat::Object(ObjectPat {
span,
optional: false,
props: vec![ObjectPatProp::Rest(RestPat {
span,
dot3_token: span,
arg: Box::new(Pat::Ident(Ident::new("a34".into(), span).into())),
type_ann: None,
})],
type_ann: None
})],
body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
span,
stmts: vec![]
})),
return_type: None,
type_params: None,
}))
);
}
#[test]
fn object_spread() {
assert_eq_ignore_span!(
expr("foo = {a, ...bar, b}"),
Box::new(Expr::Assign(AssignExpr {
span,
left: PatOrExpr::Pat(Box::new(Pat::Ident(Ident::new("foo".into(), span).into()))),
op: op!("="),
right: Box::new(Expr::Object(ObjectLit {
span,
props: vec![
PropOrSpread::Prop(Box::new(Ident::new("a".into(), span).into())),
PropOrSpread::Spread(SpreadElement {
dot3_token: span,
expr: Box::new(Expr::Ident(Ident::new("bar".into(), span))),
}),
PropOrSpread::Prop(Box::new(Ident::new("b".into(), span).into())),
]
}))
}))
);
}
#[test]
fn new_expr_should_not_eat_too_much() {
assert_eq_ignore_span!(
new_expr("new Date().toString()"),
Box::new(Expr::Member(MemberExpr {
span,
obj: member_expr("new Date()"),
prop: MemberProp::Ident(Ident::new("toString".into(), span)),
}))
);
}
#[test]
fn lhs_expr_as_new_expr_prod() {
assert_eq_ignore_span!(
lhs("new Date.toString()"),
Box::new(Expr::New(NewExpr {
span,
callee: lhs("Date.toString"),
args: Some(vec![]),
type_args: None,
}))
);
}
#[test]
fn lhs_expr_as_call() {
assert_eq_ignore_span!(
lhs("new Date.toString()()"),
Box::new(Expr::Call(CallExpr {
span,
callee: Callee::Expr(lhs("new Date.toString()")),
args: vec![],
type_args: None,
}))
)
}
#[test]
fn arrow_fn_no_args() {
assert_eq_ignore_span!(
expr("() => 1"),
Box::new(Expr::Arrow(ArrowExpr {
span,
is_async: false,
is_generator: false,
params: vec![],
body: Box::new(BlockStmtOrExpr::Expr(expr("1"))),
return_type: None,
type_params: None,
}))
);
}
#[test]
fn arrow_fn() {
assert_eq_ignore_span!(
expr("(a) => 1"),
Box::new(Expr::Arrow(ArrowExpr {
span,
is_async: false,
is_generator: false,
params: vec![Pat::Ident(Ident::new("a".into(), span).into())],
body: Box::new(BlockStmtOrExpr::Expr(expr("1"))),
return_type: None,
type_params: None,
}))
);
}
#[test]
fn arrow_fn_rest() {
assert_eq_ignore_span!(
expr("(...a) => 1"),
Box::new(Expr::Arrow(ArrowExpr {
span,
is_async: false,
is_generator: false,
params: vec![Pat::Rest(RestPat {
span,
dot3_token: span,
arg: Box::new(Pat::Ident(Ident::new("a".into(), span).into())),
type_ann: None
})],
body: Box::new(BlockStmtOrExpr::Expr(expr("1"))),
return_type: None,
type_params: None,
}))
);
}
#[test]
fn arrow_fn_no_paren() {
assert_eq_ignore_span!(
expr("a => 1"),
Box::new(Expr::Arrow(ArrowExpr {
span,
is_async: false,
is_generator: false,
params: vec![Pat::Ident(Ident::new("a".into(), span).into())],
body: Box::new(BlockStmtOrExpr::Expr(expr("1"))),
type_params: None,
return_type: None,
}))
);
}
#[test]
fn new_no_paren() {
assert_eq_ignore_span!(
expr("new a"),
Box::new(Expr::New(NewExpr {
span,
callee: expr("a"),
args: None,
type_args: None,
}))
);
}
#[test]
fn new_new_no_paren() {
assert_eq_ignore_span!(
expr("new new a"),
Box::new(Expr::New(NewExpr {
span,
callee: expr("new a"),
args: None,
type_args: None,
}))
);
}
#[test]
fn array_lit() {
assert_eq_ignore_span!(
expr("[a,,,,, ...d,, e]"),
Box::new(Expr::Array(ArrayLit {
span,
elems: vec![
Some(ExprOrSpread {
spread: None,
expr: Box::new(Expr::Ident(Ident::new("a".into(), span))),
}),
None,
None,
None,
None,
Some(ExprOrSpread {
spread: Some(span),
expr: Box::new(Expr::Ident(Ident::new("d".into(), span))),
}),
None,
Some(ExprOrSpread {
spread: None,
expr: Box::new(Expr::Ident(Ident::new("e".into(), span))),
}),
]
}))
);
}
#[test]
fn max_integer() {
assert_eq_ignore_span!(
expr("1.7976931348623157e+308"),
Box::new(Expr::Lit(Lit::Num(Number {
span,
value: 1.797_693_134_862_315_7e308,
raw: Some("1.7976931348623157e+308".into()),
})))
)
}
#[test]
fn iife() {
assert_eq_ignore_span!(
expr("(function(){})()"),
Box::new(Expr::Call(CallExpr {
span,
callee: Callee::Expr(expr("(function(){})")),
args: vec![],
type_args: Default::default(),
}))
)
}
#[test]
fn issue_319_1() {
assert_eq_ignore_span!(
expr("obj(({ async f() { await g(); } }));"),
Box::new(Expr::Call(CallExpr {
span,
callee: Callee::Expr(expr("obj")),
args: vec![ExprOrSpread {
spread: None,
expr: expr("({ async f() { await g(); } })"),
}],
type_args: Default::default(),
}))
);
}
#[test]
fn issue_328() {
assert_eq_ignore_span!(
test_parser("import('test')", Syntax::Es(Default::default()), |p| {
p.parse_stmt(true)
}),
Stmt::Expr(ExprStmt {
span,
expr: Box::new(Expr::Call(CallExpr {
span,
callee: Callee::Import(Import { span }),
args: vec![ExprOrSpread {
spread: None,
expr: Box::new(Expr::Lit(Lit::Str(Str {
span,
value: "test".into(),
raw: Some("'test'".into()),
}))),
}],
type_args: Default::default(),
}))
})
);
}
#[test]
fn issue_337() {
test_parser(
"const foo = 'bar' in bas ? 'beep' : 'boop';",
Default::default(),
|p| p.parse_module(),
);
}
#[test]
fn issue_350() {
assert_eq_ignore_span!(
expr(
r#""ok\
ok\
hehe.";"#,
),
Box::new(Expr::Lit(Lit::Str(Str {
span,
value: "okokhehe.".into(),
raw: Some("\"ok\\\nok\\\nhehe.\"".into()),
})))
);
}
#[test]
fn issue_380() {
expr(
" import('../foo/bar')
.then(bar => {
// bar should be {default: DEFAULT_EXPORTED_THING_IN_BAR} or at least what it is supposed \
to be
})
}",
);
}
#[test]
fn issue_675() {
expr("fn = function () { Object.setPrototypeOf(this, new.target.prototype); }");
}
#[test]
fn super_expr() {
assert_eq_ignore_span!(
expr("super.foo();"),
Box::new(Expr::Call(CallExpr {
span,
callee: Callee::Expr(Box::new(Expr::SuperProp(SuperPropExpr {
span,
obj: Super { span },
prop: SuperProp::Ident(Ident {
span,
sym: "foo".into(),
optional: false
})
}))),
args: Vec::new(),
type_args: Default::default(),
}))
);
}
#[test]
fn super_expr_computed() {
assert_eq_ignore_span!(
expr("super[a] ??= 123;"),
Box::new(Expr::Assign(AssignExpr {
span,
op: AssignOp::NullishAssign,
left: PatOrExpr::Expr(Box::new(Expr::SuperProp(SuperPropExpr {
span,
obj: Super { span },
prop: SuperProp::Computed(ComputedPropName {
span,
expr: Box::new(Expr::Ident(Ident {
span,
sym: "a".into(),
optional: false
})),
})
}))),
right: Box::new(Expr::Lit(Lit::Num(Number {
span,
value: 123f64,
raw: Some("123".into()),
})))
}))
);
}
#[test]
fn issue_3672_1() {
test_parser(
"report({
fix: fixable ? null : (): RuleFix => {},
});",
Syntax::Typescript(Default::default()),
|p| p.parse_module(),
);
}
#[test]
fn issue_3672_2() {
test_parser(
"f(a ? (): void => { } : (): void => { })",
Syntax::Typescript(Default::default()),
|p| p.parse_module(),
);
}
#[test]
fn issue_5947() {
test_parser(
"[a as number, b as number, c as string] = [1, 2, '3']",
Syntax::Typescript(Default::default()),
|p| p.parse_module(),
);
}
#[test]
fn issue_6781() {
let cm = SourceMap::default();
let fm = cm.new_source_file(FileName::Anon, "import.meta.env".to_string());
let mut errors = vec![];
let expr = parse_file_as_expr(
&fm,
Default::default(),
Default::default(),
None,
&mut errors,
);
assert!(expr.is_ok());
assert!(errors.is_empty());
}
#[bench]
fn bench_new_expr_ts(b: &mut Bencher) {
bench_parser(
b,
"new Foo()",
Syntax::Typescript(Default::default()),
|p| {
black_box(p.parse_expr()?);
Ok(())
},
);
}
#[bench]
fn bench_new_expr_es(b: &mut Bencher) {
bench_parser(b, "new Foo()", Syntax::Es(Default::default()), |p| {
black_box(p.parse_expr()?);
Ok(())
});
}
#[bench]
fn bench_member_expr_ts(b: &mut Bencher) {
bench_parser(
b,
"a.b.c.d.e.f",
Syntax::Typescript(Default::default()),
|p| {
black_box(p.parse_expr()?);
Ok(())
},
);
}
#[bench]
fn bench_member_expr_es(b: &mut Bencher) {
bench_parser(b, "a.b.c.d.e.f", Syntax::Es(Default::default()), |p| {
black_box(p.parse_expr()?);
Ok(())
});
}

View file

@ -0,0 +1,47 @@
#[cfg(feature = "verify")]
use swc_common::{Span, Spanned};
#[cfg(feature = "verify")]
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use super::*;
impl<I: Tokens> Parser<I> {
#[cfg(feature = "verify")]
pub(in crate::parser) fn verify_expr(&mut self, expr: Box<Expr>) -> PResult<Box<Expr>> {
let mut v = Verifier { errors: vec![] };
v.visit_expr(&expr);
for (span, error) in v.errors {
self.emit_err(span, error);
}
return Ok(expr);
}
#[cfg(not(feature = "verify"))]
pub(in crate::parser) fn verify_expr(&mut self, expr: Box<Expr>) -> PResult<Box<Expr>> {
Ok(expr)
}
}
#[cfg(feature = "verify")]
pub(super) struct Verifier {
pub errors: Vec<(Span, SyntaxError)>,
}
#[cfg(feature = "verify")]
impl Visit for Verifier {
noop_visit_type!();
fn visit_assign_prop(&mut self, p: &AssignProp) {
self.errors.push((p.span(), SyntaxError::AssignProperty));
}
fn visit_expr(&mut self, e: &Expr) {
match *e {
Expr::Fn(..) | Expr::Arrow(..) => {}
_ => e.visit_children_with(self),
}
}
}

View file

@ -0,0 +1,173 @@
//! 12.1 Identifiers
use either::Either;
use swc_atoms::js_word;
use super::*;
use crate::token::Keyword;
impl<I: Tokens> Parser<I> {
pub(super) fn parse_maybe_private_name(&mut self) -> PResult<Either<PrivateName, Ident>> {
let is_private = is!(self, '#');
if is_private {
self.parse_private_name().map(Either::Left)
} else {
self.parse_ident_name().map(Either::Right)
}
}
pub(super) fn parse_private_name(&mut self) -> PResult<PrivateName> {
let start = cur_pos!(self);
assert_and_bump!(self, '#');
let hash_end = self.input.prev_span().hi;
if self.input.cur_pos() - hash_end != BytePos(0) {
syntax_error!(
self,
span!(self, start),
SyntaxError::SpaceBetweenHashAndIdent
);
}
let id = self.parse_ident_name()?;
Ok(PrivateName {
span: span!(self, start),
id,
})
}
/// IdentifierReference
pub(super) fn parse_ident_ref(&mut self) -> PResult<Ident> {
let ctx = self.ctx();
self.parse_ident(!ctx.in_generator, !ctx.in_async)
}
/// LabelIdentifier
pub(super) fn parse_label_ident(&mut self) -> PResult<Ident> {
let ctx = self.ctx();
self.parse_ident(!ctx.in_generator, !ctx.in_async)
}
/// Use this when spec says "IdentifierName".
/// This allows idents like `catch`.
pub(super) fn parse_ident_name(&mut self) -> PResult<Ident> {
let in_type = self.ctx().in_type;
let start = cur_pos!(self);
let w = match cur!(self, true) {
Ok(&Word(..)) => match bump!(self) {
Word(w) => w.into(),
_ => unreachable!(),
},
Ok(&Token::JSXName { .. }) if in_type => match bump!(self) {
Token::JSXName { name } => name,
_ => unreachable!(),
},
_ => syntax_error!(self, SyntaxError::ExpectedIdent),
};
Ok(Ident::new(w, span!(self, start)))
}
// https://tc39.es/ecma262/#prod-ModuleExportName
pub(super) fn parse_module_export_name(&mut self) -> PResult<ModuleExportName> {
let module_export_name = match cur!(self, false) {
Ok(&Token::Str { .. }) => match self.parse_lit()? {
Lit::Str(str_lit) => ModuleExportName::Str(str_lit),
_ => unreachable!(),
},
Ok(&Word(..)) => ModuleExportName::Ident(self.parse_ident_name()?),
_ => {
unexpected!(self, "identifier or string");
}
};
Ok(module_export_name)
}
/// Identifier
///
/// In strict mode, "yield" is SyntaxError if matched.
pub(super) fn parse_ident(&mut self, incl_yield: bool, incl_await: bool) -> PResult<Ident> {
trace_cur!(self, parse_ident);
let start = cur_pos!(self);
let word = self.parse_with(|p| {
let w = match cur!(p, true) {
Ok(&Word(..)) => match bump!(p) {
Word(w) => w,
_ => unreachable!(),
},
_ => syntax_error!(p, SyntaxError::ExpectedIdent),
};
// Spec:
// It is a Syntax Error if this phrase is contained in strict mode code and the
// StringValue of IdentifierName is: "implements", "interface", "let",
// "package", "private", "protected", "public", "static", or "yield".
match w {
Word::Ident(ref name @ js_word!("enum")) => {
p.emit_err(
p.input.prev_span(),
SyntaxError::InvalidIdentInStrict(name.clone()),
);
}
Word::Keyword(name @ Keyword::Yield) | Word::Keyword(name @ Keyword::Let) => {
p.emit_strict_mode_err(
p.input.prev_span(),
SyntaxError::InvalidIdentInStrict(name.into_js_word()),
);
}
Word::Ident(ref name @ js_word!("static"))
| Word::Ident(ref name @ js_word!("implements"))
| Word::Ident(ref name @ js_word!("interface"))
| Word::Ident(ref name @ js_word!("package"))
| Word::Ident(ref name @ js_word!("private"))
| Word::Ident(ref name @ js_word!("protected"))
| Word::Ident(ref name @ js_word!("public")) => {
p.emit_strict_mode_err(
p.input.prev_span(),
SyntaxError::InvalidIdentInStrict(name.clone()),
);
}
_ => {}
}
// Spec:
// It is a Syntax Error if StringValue of IdentifierName is the same String
// value as the StringValue of any ReservedWord except for yield or await.
match w {
Word::Keyword(Keyword::Await) if p.ctx().in_declare => Ok(js_word!("await")),
// It is a Syntax Error if the goal symbol of the syntactic grammar is Module
// and the StringValue of IdentifierName is "await".
Word::Keyword(Keyword::Await) if p.ctx().module | p.ctx().in_async => {
syntax_error!(p, p.input.prev_span(), SyntaxError::InvalidIdentInAsync)
}
Word::Keyword(Keyword::This) if p.input.syntax().typescript() => {
Ok(js_word!("this"))
}
Word::Keyword(Keyword::Let) => Ok(js_word!("let")),
Word::Ident(ident) => {
if ident == js_word!("arguments") && p.ctx().in_class_field {
p.emit_err(p.input.prev_span(), SyntaxError::ArgumentsInClassField)
}
Ok(ident)
}
Word::Keyword(Keyword::Yield) if incl_yield => Ok(js_word!("yield")),
Word::Keyword(Keyword::Await) if incl_await => Ok(js_word!("await")),
Word::Keyword(..) | Word::Null | Word::True | Word::False => {
syntax_error!(p, p.input.prev_span(), SyntaxError::ExpectedIdent)
}
}
})?;
Ok(Ident::new(word, span!(self, start)))
}
}

View file

@ -0,0 +1,497 @@
use std::{cell::RefCell, mem, mem::take, rc::Rc};
use lexer::TokenContexts;
use swc_common::{BytePos, Span};
use super::Parser;
use crate::{
error::Error,
lexer::{self},
token::*,
Context, EsVersion, Syntax,
};
/// Clone should be cheap if you are parsing typescript because typescript
/// syntax requires backtracking.
pub trait Tokens: Clone + Iterator<Item = TokenAndSpan> {
fn set_ctx(&mut self, ctx: Context);
fn ctx(&self) -> Context;
fn syntax(&self) -> Syntax;
fn target(&self) -> EsVersion;
fn start_pos(&self) -> BytePos {
BytePos(0)
}
fn set_expr_allowed(&mut self, allow: bool);
fn set_next_regexp(&mut self, start: Option<BytePos>);
fn token_context(&self) -> &lexer::TokenContexts;
fn token_context_mut(&mut self) -> &mut lexer::TokenContexts;
fn set_token_context(&mut self, _c: lexer::TokenContexts);
/// Implementors should use Rc<RefCell<Vec<Error>>>.
///
/// It is required because parser should backtrack while parsing typescript
/// code.
fn add_error(&self, error: Error);
/// Add an error which is valid syntax in script mode.
///
/// This errors should be dropped if it's not a module.
///
/// Implementor should check for if [Context].module, and buffer errors if
/// module is false. Also, implementors should move errors to the error
/// buffer on set_ctx if the parser mode become module mode.
fn add_module_mode_error(&self, error: Error);
fn take_errors(&mut self) -> Vec<Error>;
}
#[derive(Clone)]
pub struct TokensInput {
iter: <Vec<TokenAndSpan> as IntoIterator>::IntoIter,
ctx: Context,
syntax: Syntax,
start_pos: BytePos,
target: EsVersion,
token_ctx: TokenContexts,
errors: Rc<RefCell<Vec<Error>>>,
module_errors: Rc<RefCell<Vec<Error>>>,
}
impl TokensInput {
pub fn new(tokens: Vec<TokenAndSpan>, ctx: Context, syntax: Syntax, target: EsVersion) -> Self {
let start_pos = tokens.first().map(|t| t.span.lo).unwrap_or(BytePos(0));
TokensInput {
iter: tokens.into_iter(),
ctx,
syntax,
start_pos,
target,
token_ctx: Default::default(),
errors: Default::default(),
module_errors: Default::default(),
}
}
}
impl Iterator for TokensInput {
type Item = TokenAndSpan;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
}
impl Tokens for TokensInput {
fn set_ctx(&mut self, ctx: Context) {
if ctx.module && !self.module_errors.borrow().is_empty() {
let mut module_errors = self.module_errors.borrow_mut();
self.errors.borrow_mut().append(&mut *module_errors);
}
self.ctx = ctx;
}
fn ctx(&self) -> Context {
self.ctx
}
fn syntax(&self) -> Syntax {
self.syntax
}
fn target(&self) -> EsVersion {
self.target
}
fn start_pos(&self) -> BytePos {
self.start_pos
}
fn set_expr_allowed(&mut self, _: bool) {}
fn set_next_regexp(&mut self, _: Option<BytePos>) {}
fn token_context(&self) -> &TokenContexts {
&self.token_ctx
}
fn token_context_mut(&mut self) -> &mut TokenContexts {
&mut self.token_ctx
}
fn set_token_context(&mut self, c: TokenContexts) {
self.token_ctx = c;
}
fn add_error(&self, error: Error) {
self.errors.borrow_mut().push(error);
}
fn add_module_mode_error(&self, error: Error) {
if self.ctx.module {
self.add_error(error);
return;
}
self.module_errors.borrow_mut().push(error);
}
fn take_errors(&mut self) -> Vec<Error> {
take(&mut self.errors.borrow_mut())
}
}
/// Note: Lexer need access to parser's context to lex correctly.
#[derive(Debug)]
pub struct Capturing<I: Tokens> {
inner: I,
captured: Rc<RefCell<Vec<TokenAndSpan>>>,
}
impl<I: Tokens> Clone for Capturing<I> {
fn clone(&self) -> Self {
Capturing {
inner: self.inner.clone(),
captured: self.captured.clone(),
}
}
}
impl<I: Tokens> Capturing<I> {
pub fn new(input: I) -> Self {
Capturing {
inner: input,
captured: Default::default(),
}
}
/// Take captured tokens
pub fn take(&mut self) -> Vec<TokenAndSpan> {
mem::take(&mut *self.captured.borrow_mut())
}
}
impl<I: Tokens> Iterator for Capturing<I> {
type Item = TokenAndSpan;
fn next(&mut self) -> Option<Self::Item> {
let next = self.inner.next();
match next {
Some(ts) => {
let mut v = self.captured.borrow_mut();
// remove tokens that could change due to backtracing
while let Some(last) = v.last() {
if last.span.lo >= ts.span.lo {
v.pop();
} else {
break;
}
}
v.push(ts.clone());
Some(ts)
}
None => None,
}
}
}
impl<I: Tokens> Tokens for Capturing<I> {
fn set_ctx(&mut self, ctx: Context) {
self.inner.set_ctx(ctx)
}
fn ctx(&self) -> Context {
self.inner.ctx()
}
fn syntax(&self) -> Syntax {
self.inner.syntax()
}
fn target(&self) -> EsVersion {
self.inner.target()
}
fn start_pos(&self) -> BytePos {
self.inner.start_pos()
}
fn set_expr_allowed(&mut self, allow: bool) {
self.inner.set_expr_allowed(allow)
}
fn set_next_regexp(&mut self, start: Option<BytePos>) {
self.inner.set_next_regexp(start);
}
fn token_context(&self) -> &TokenContexts {
self.inner.token_context()
}
fn token_context_mut(&mut self) -> &mut TokenContexts {
self.inner.token_context_mut()
}
fn set_token_context(&mut self, c: TokenContexts) {
self.inner.set_token_context(c)
}
fn add_error(&self, error: Error) {
self.inner.add_error(error);
}
fn add_module_mode_error(&self, error: Error) {
self.inner.add_module_mode_error(error)
}
fn take_errors(&mut self) -> Vec<Error> {
self.inner.take_errors()
}
}
/// This struct is responsible for managing current token and peeked token.
#[derive(Clone)]
pub(super) struct Buffer<I: Tokens> {
iter: I,
/// Span of the previous token.
prev_span: Span,
cur: Option<TokenAndSpan>,
/// Peeked token
next: Option<TokenAndSpan>,
}
impl<I: Tokens> Parser<I> {
pub fn input(&mut self) -> &mut I {
&mut self.input.iter
}
pub(crate) fn input_ref(&self) -> &I {
&self.input.iter
}
}
impl<I: Tokens> Buffer<I> {
pub fn new(lexer: I) -> Self {
let start_pos = lexer.start_pos();
Buffer {
iter: lexer,
cur: None,
prev_span: Span::new(start_pos, start_pos, Default::default()),
next: None,
}
}
pub fn store(&mut self, token: Token) {
debug_assert!(self.next.is_none());
debug_assert!(self.cur.is_none());
let span = self.prev_span;
self.cur = Some(TokenAndSpan {
span,
token,
had_line_break: false,
});
}
#[inline(never)]
fn bump_inner(&mut self) {
let prev = self.cur.take();
self.prev_span = match prev {
Some(TokenAndSpan { span, .. }) => span,
_ => self.prev_span,
};
// If we have peeked a token, take it instead of calling lexer.next()
self.cur = self.next.take().or_else(|| self.iter.next());
}
#[allow(dead_code)]
pub fn cur_debug(&self) -> Option<&Token> {
self.cur.as_ref().map(|it| &it.token)
}
#[cold]
#[inline(never)]
pub fn dump_cur(&mut self) -> String {
match self.cur() {
Some(v) => format!("{:?}", v),
None => "<eof>".to_string(),
}
}
/// Returns current token.
pub fn bump(&mut self) -> Token {
#[cold]
#[inline(never)]
fn invalid_state() -> ! {
unreachable!(
"Current token is `None`. Parser should not call bump() without knowing current \
token"
)
}
let prev = match self.cur.take() {
Some(t) => t,
None => invalid_state(),
};
self.prev_span = prev.span;
prev.token
}
pub fn knows_cur(&self) -> bool {
self.cur.is_some()
}
pub fn peek(&mut self) -> Option<&Token> {
debug_assert!(
self.cur.is_some(),
"parser should not call peek() without knowing current token"
);
if self.next.is_none() {
self.next = self.iter.next();
}
self.next.as_ref().map(|ts| &ts.token)
}
/// Returns true on eof.
pub fn had_line_break_before_cur(&mut self) -> bool {
self.cur();
self.cur
.as_ref()
.map(|it| it.had_line_break)
.unwrap_or_else(|| true)
}
/// This returns true on eof.
pub fn has_linebreak_between_cur_and_peeked(&mut self) -> bool {
let _ = self.peek();
self.next
.as_ref()
.map(|item| item.had_line_break)
.unwrap_or({
// return true on eof.
true
})
}
/// Get current token. Returns `None` only on eof.
#[inline]
pub fn cur(&mut self) -> Option<&Token> {
if self.cur.is_none() {
self.bump_inner();
}
match &self.cur {
Some(v) => Some(&v.token),
None => None,
}
}
#[inline]
pub fn is(&mut self, expected: &Token) -> bool {
match self.cur() {
Some(t) => *expected == *t,
_ => false,
}
}
#[inline]
pub fn eat(&mut self, expected: &Token) -> bool {
let v = self.is(expected);
if v {
self.bump();
}
v
}
/// Returns start of current token.
#[inline]
pub fn cur_pos(&mut self) -> BytePos {
let _ = self.cur();
self.cur
.as_ref()
.map(|item| item.span.lo)
.unwrap_or_else(|| {
// eof
self.last_pos()
})
}
#[inline]
pub fn cur_span(&self) -> Span {
let data = self
.cur
.as_ref()
.map(|item| item.span)
.unwrap_or(self.prev_span);
Span::new(data.lo, data.hi, data.ctxt)
}
/// Returns last byte position of previous token.
#[inline]
pub fn last_pos(&self) -> BytePos {
self.prev_span.hi
}
/// Returns span of the previous token.
#[inline]
pub fn prev_span(&self) -> Span {
self.prev_span
}
#[inline]
pub(crate) fn get_ctx(&self) -> Context {
self.iter.ctx()
}
#[inline]
pub(crate) fn set_ctx(&mut self, ctx: Context) {
self.iter.set_ctx(ctx);
}
#[inline]
pub fn syntax(&self) -> Syntax {
self.iter.syntax()
}
#[inline]
pub fn target(&self) -> EsVersion {
self.iter.target()
}
#[inline]
pub(crate) fn set_expr_allowed(&mut self, allow: bool) {
self.iter.set_expr_allowed(allow)
}
#[inline]
pub fn set_next_regexp(&mut self, start: Option<BytePos>) {
self.iter.set_next_regexp(start);
}
#[inline]
pub(crate) fn token_context(&self) -> &lexer::TokenContexts {
self.iter.token_context()
}
#[inline]
pub(crate) fn token_context_mut(&mut self) -> &mut lexer::TokenContexts {
self.iter.token_context_mut()
}
#[inline]
pub(crate) fn set_token_context(&mut self, c: lexer::TokenContexts) {
self.iter.set_token_context(c)
}
}

View file

@ -0,0 +1,478 @@
use either::Either;
use swc_common::{Span, Spanned, SyntaxContext};
use super::*;
#[cfg(test)]
mod tests;
impl<I: Tokens> Parser<I> {
/// Parse next token as JSX identifier
pub(super) fn parse_jsx_ident(&mut self) -> PResult<Ident> {
debug_assert!(self.input.syntax().jsx());
trace_cur!(self, parse_jsx_ident);
let ctx = self.ctx();
match *cur!(self, true)? {
Token::JSXName { .. } => match bump!(self) {
Token::JSXName { name } => {
let span = self.input.prev_span();
Ok(Ident::new(name, span))
}
_ => unreachable!(),
},
_ if ctx.in_forced_jsx_context => self.parse_ident_ref(),
_ => unexpected!(self, "jsx identifier"),
}
}
/// Parse namespaced identifier.
pub(super) fn parse_jsx_namespaced_name(&mut self) -> PResult<JSXAttrName> {
debug_assert!(self.input.syntax().jsx());
trace_cur!(self, parse_jsx_namespaced_name);
let ns = self.parse_jsx_ident()?;
if !eat!(self, ':') {
return Ok(JSXAttrName::Ident(ns));
}
let name = self.parse_jsx_ident()?;
Ok(JSXAttrName::JSXNamespacedName(JSXNamespacedName {
ns,
name,
}))
}
/// Parses element name in any form - namespaced, member or single
/// identifier.
pub(super) fn parse_jsx_element_name(&mut self) -> PResult<JSXElementName> {
debug_assert!(self.input.syntax().jsx());
trace_cur!(self, parse_jsx_element_name);
let mut node = match self.parse_jsx_namespaced_name()? {
JSXAttrName::Ident(i) => JSXElementName::Ident(i),
JSXAttrName::JSXNamespacedName(i) => JSXElementName::JSXNamespacedName(i),
};
while eat!(self, '.') {
let prop = self.parse_jsx_ident()?;
let new_node = JSXElementName::JSXMemberExpr(JSXMemberExpr {
obj: match node {
JSXElementName::Ident(i) => JSXObject::Ident(i),
JSXElementName::JSXMemberExpr(i) => JSXObject::JSXMemberExpr(Box::new(i)),
_ => unimplemented!("JSXNamespacedName -> JSXObject"),
},
prop,
});
node = new_node;
}
Ok(node)
}
/// Parses any type of JSX attribute value.
///
/// TODO(kdy1): Change return type to JSXAttrValue
pub(super) fn parse_jsx_attr_value(&mut self) -> PResult<JSXAttrValue> {
debug_assert!(self.input.syntax().jsx());
trace_cur!(self, parse_jsx_attr_value);
let start = cur_pos!(self);
match *cur!(self, true)? {
tok!('{') => {
let node = self.parse_jsx_expr_container(start)?;
match node.expr {
JSXExpr::JSXEmptyExpr(..) => {
syntax_error!(self, span!(self, start), SyntaxError::EmptyJSXAttr)
}
JSXExpr::Expr(..) => Ok(node.into()),
}
}
Token::Str { .. } => {
let lit = self.parse_lit()?;
Ok(JSXAttrValue::Lit(lit))
}
Token::JSXTagStart => {
let expr = self.parse_jsx_element()?;
match expr {
Either::Left(n) => Ok(JSXAttrValue::JSXFragment(n)),
Either::Right(n) => Ok(JSXAttrValue::JSXElement(Box::new(n))),
}
}
_ => {
let span = self.input.cur_span();
syntax_error!(self, span, SyntaxError::InvalidJSXValue)
}
}
}
/// JSXEmptyExpression is unique type since it doesn't actually parse
/// anything, and so it should start at the end of last read token (left
/// brace) and finish at the beginning of the next one (right brace).
pub(super) fn parse_jsx_empty_expr(&mut self) -> PResult<JSXEmptyExpr> {
debug_assert!(self.input.syntax().jsx());
let start = cur_pos!(self);
Ok(JSXEmptyExpr {
span: Span::new(start, start, SyntaxContext::empty()),
})
}
/// Parse JSX spread child
pub(super) fn parse_jsx_spread_child(&mut self) -> PResult<JSXSpreadChild> {
debug_assert!(self.input.syntax().jsx());
let start = cur_pos!(self);
expect!(self, '{');
expect!(self, "...");
let expr = self.parse_expr()?;
expect!(self, '}');
Ok(JSXSpreadChild {
span: span!(self, start),
expr,
})
}
/// Parses JSX expression enclosed into curly brackets.
pub(super) fn parse_jsx_expr_container(&mut self, _: BytePos) -> PResult<JSXExprContainer> {
debug_assert!(self.input.syntax().jsx());
let start = cur_pos!(self);
bump!(self);
let expr = if is!(self, '}') {
self.parse_jsx_empty_expr().map(JSXExpr::JSXEmptyExpr)?
} else {
if is!(self, "...") {
bump!(self);
}
self.parse_expr().map(JSXExpr::Expr)?
};
expect!(self, '}');
Ok(JSXExprContainer {
span: span!(self, start),
expr,
})
}
/// Parses following JSX attribute name-value pair.
pub(super) fn parse_jsx_attr(&mut self) -> PResult<JSXAttrOrSpread> {
debug_assert!(self.input.syntax().jsx());
let start = cur_pos!(self);
let _tracing = debug_tracing!(self, "parse_jsx_attr");
if eat!(self, '{') {
let dot3_start = cur_pos!(self);
expect!(self, "...");
let dot3_token = span!(self, dot3_start);
let expr = self.parse_assignment_expr()?;
expect!(self, '}');
return Ok(SpreadElement { dot3_token, expr }.into());
}
let name = self.parse_jsx_namespaced_name()?;
let value = if eat!(self, '=') {
let ctx = Context {
in_cond_expr: false,
will_expect_colon_for_cond: false,
..self.ctx()
};
self.with_ctx(ctx).parse_jsx_attr_value().map(Some)?
} else {
None
};
Ok(JSXAttr {
span: span!(self, start),
name,
value,
}
.into())
}
/// Parses JSX opening tag starting after "<".
pub(super) fn parse_jsx_opening_element_at(
&mut self,
start: BytePos,
) -> PResult<Either<JSXOpeningFragment, JSXOpeningElement>> {
debug_assert!(self.input.syntax().jsx());
if eat!(self, JSXTagEnd) {
return Ok(Either::Left(JSXOpeningFragment {
span: span!(self, start),
}));
}
let ctx = Context {
should_not_lex_lt_or_gt_as_type: false,
..self.ctx()
};
let name = self.with_ctx(ctx).parse_jsx_element_name()?;
self.parse_jsx_opening_element_after_name(start, name)
.map(Either::Right)
}
/// `jsxParseOpeningElementAfterName`
pub(super) fn parse_jsx_opening_element_after_name(
&mut self,
start: BytePos,
name: JSXElementName,
) -> PResult<JSXOpeningElement> {
debug_assert!(self.input.syntax().jsx());
let type_args = if self.input.syntax().typescript() && is!(self, '<') {
self.try_parse_ts(|p| p.parse_ts_type_args().map(Some))
} else {
None
};
let mut attrs = vec![];
while cur!(self, false).is_ok() {
trace_cur!(self, parse_jsx_opening__attrs_loop);
if is!(self, '/') || is!(self, JSXTagEnd) {
break;
}
let attr = self.parse_jsx_attr()?;
attrs.push(attr);
}
let self_closing = eat!(self, '/');
if !eat!(self, JSXTagEnd) & !(self.ctx().in_forced_jsx_context && eat!(self, '>')) {
unexpected!(self, "> (jsx closing tag)");
}
Ok(JSXOpeningElement {
span: span!(self, start),
name,
attrs,
self_closing,
type_args,
})
}
/// Parses JSX closing tag starting after "</".
fn parse_jsx_closing_element_at(
&mut self,
start: BytePos,
) -> PResult<Either<JSXClosingFragment, JSXClosingElement>> {
debug_assert!(self.input.syntax().jsx());
if eat!(self, JSXTagEnd) {
return Ok(Either::Left(JSXClosingFragment {
span: span!(self, start),
}));
}
let name = self.parse_jsx_element_name()?;
expect!(self, JSXTagEnd);
Ok(Either::Right(JSXClosingElement {
span: span!(self, start),
name,
}))
}
/// Parses entire JSX element, including it"s opening tag
/// (starting after "<"), attributes, contents and closing tag.
///
/// babel: `jsxParseElementAt`
pub(super) fn parse_jsx_element_at(
&mut self,
start_pos: BytePos,
) -> PResult<Either<JSXFragment, JSXElement>> {
debug_assert!(self.input.syntax().jsx());
let _ = cur!(self, true);
let start = cur_pos!(self);
let forced_jsx_context = match bump!(self) {
tok!('<') => true,
Token::JSXTagStart => false,
_ => unreachable!(),
};
let ctx = Context {
in_forced_jsx_context: forced_jsx_context,
should_not_lex_lt_or_gt_as_type: false,
..self.ctx()
};
self.with_ctx(ctx).parse_with(|p| {
let _tracing = debug_tracing!(p, "parse_jsx_element");
let opening_element = p.parse_jsx_opening_element_at(start_pos)?;
trace_cur!(p, parse_jsx_element__after_opening_element);
let mut children = vec![];
let mut closing_element = None;
let self_closing = match opening_element {
Either::Right(ref el) => el.self_closing,
_ => false,
};
if !self_closing {
'contents: loop {
match *cur!(p, true)? {
Token::JSXTagStart => {
let start = cur_pos!(p);
if peeked_is!(p, '/') {
bump!(p); // JSXTagStart
let _ = cur!(p, true);
assert_and_bump!(p, '/');
closing_element =
p.parse_jsx_closing_element_at(start).map(Some)?;
break 'contents;
}
children.push(p.parse_jsx_element_at(start).map(|e| match e {
Either::Left(e) => JSXElementChild::from(e),
Either::Right(e) => JSXElementChild::from(Box::new(e)),
})?);
}
Token::JSXText { .. } => {
children.push(p.parse_jsx_text().map(JSXElementChild::from)?)
}
tok!('{') => {
let start = cur_pos!(p);
if peeked_is!(p, "...") {
children
.push(p.parse_jsx_spread_child().map(JSXElementChild::from)?);
} else {
children.push(
p.parse_jsx_expr_container(start)
.map(JSXElementChild::from)?,
);
}
}
_ => unexpected!(p, "< (jsx tag start), jsx text or {"),
}
}
}
let span = span!(p, start);
Ok(match (opening_element, closing_element) {
(Either::Left(..), Some(Either::Right(closing))) => {
syntax_error!(p, closing.span(), SyntaxError::JSXExpectedClosingTagForLtGt);
}
(Either::Right(opening), Some(Either::Left(closing))) => {
syntax_error!(
p,
closing.span(),
SyntaxError::JSXExpectedClosingTag {
tag: get_qualified_jsx_name(&opening.name)
}
);
}
(Either::Left(opening), Some(Either::Left(closing))) => Either::Left(JSXFragment {
span,
opening,
children,
closing,
}),
(Either::Right(opening), None) => Either::Right(JSXElement {
span,
opening,
children,
closing: None,
}),
(Either::Right(opening), Some(Either::Right(closing))) => {
if get_qualified_jsx_name(&closing.name)
!= get_qualified_jsx_name(&opening.name)
{
syntax_error!(
p,
closing.span(),
SyntaxError::JSXExpectedClosingTag {
tag: get_qualified_jsx_name(&opening.name)
}
);
}
Either::Right(JSXElement {
span,
opening,
children,
closing: Some(closing),
})
}
_ => unreachable!(),
})
})
}
/// Parses entire JSX element from current position.
///
/// babel: `jsxParseElement`
pub(super) fn parse_jsx_element(&mut self) -> PResult<Either<JSXFragment, JSXElement>> {
trace_cur!(self, parse_jsx_element);
debug_assert!(self.input.syntax().jsx());
debug_assert!({ matches!(*cur!(self, true)?, Token::JSXTagStart | tok!('<')) });
let start_pos = cur_pos!(self);
self.parse_jsx_element_at(start_pos)
}
pub(super) fn parse_jsx_text(&mut self) -> PResult<JSXText> {
debug_assert!(self.input.syntax().jsx());
debug_assert!(matches!(cur!(self, false), Ok(&Token::JSXText { .. })));
let token = bump!(self);
let span = self.input.prev_span();
match token {
Token::JSXText { raw } => Ok(JSXText {
span,
// TODO
value: raw.clone(),
raw,
}),
_ => unreachable!(),
}
}
}
trait IsFragment {
fn is_fragment(&self) -> bool;
}
impl IsFragment for Either<JSXOpeningFragment, JSXOpeningElement> {
fn is_fragment(&self) -> bool {
matches!(*self, Either::Left(..))
}
}
impl IsFragment for Either<JSXClosingFragment, JSXClosingElement> {
fn is_fragment(&self) -> bool {
matches!(*self, Either::Left(..))
}
}
impl<T: IsFragment> IsFragment for Option<T> {
fn is_fragment(&self) -> bool {
self.as_ref().map(|s| s.is_fragment()).unwrap_or(false)
}
}
fn get_qualified_jsx_name(name: &JSXElementName) -> JsWord {
fn get_qualified_obj_name(obj: &JSXObject) -> JsWord {
match *obj {
JSXObject::Ident(ref i) => i.sym.clone(),
JSXObject::JSXMemberExpr(ref member) => format!(
"{}.{}",
get_qualified_obj_name(&member.obj),
member.prop.sym
)
.into(),
}
}
match *name {
JSXElementName::Ident(ref i) => i.sym.clone(),
JSXElementName::JSXNamespacedName(JSXNamespacedName { ref ns, ref name }) => {
format!("{}:{}", ns.sym, name.sym).into()
}
JSXElementName::JSXMemberExpr(JSXMemberExpr { ref obj, ref prop }) => {
format!("{}.{}", get_qualified_obj_name(obj), prop.sym).into()
}
}
}

View file

@ -0,0 +1,118 @@
use swc_common::DUMMY_SP as span;
use swc_ecma_visit::assert_eq_ignore_span;
use super::*;
use crate::parser::test_parser;
fn jsx(src: &'static str) -> Box<Expr> {
test_parser(
src,
crate::Syntax::Es(crate::EsConfig {
jsx: true,
..Default::default()
}),
|p| p.parse_expr(),
)
}
#[test]
fn self_closing_01() {
assert_eq_ignore_span!(
jsx("<a />"),
Box::new(Expr::JSXElement(Box::new(JSXElement {
span,
opening: JSXOpeningElement {
span,
name: JSXElementName::Ident(Ident::new("a".into(), span)),
self_closing: true,
attrs: vec![],
type_args: None,
},
children: vec![],
closing: None,
})))
);
}
#[test]
fn normal_01() {
assert_eq_ignore_span!(
jsx("<a>foo</a>"),
Box::new(Expr::JSXElement(Box::new(JSXElement {
span,
opening: JSXOpeningElement {
span,
name: JSXElementName::Ident(Ident::new("a".into(), span)),
self_closing: false,
attrs: vec![],
type_args: None,
},
children: vec![JSXElementChild::JSXText(JSXText {
span,
raw: "foo".into(),
value: "foo".into(),
})],
closing: Some(JSXClosingElement {
span,
name: JSXElementName::Ident(Ident::new("a".into(), span)),
})
})))
);
}
#[test]
fn escape_in_attr() {
assert_eq_ignore_span!(
jsx(r#"<div id="w &lt; w" />;"#),
Box::new(Expr::JSXElement(Box::new(JSXElement {
span,
opening: JSXOpeningElement {
span,
attrs: vec![JSXAttrOrSpread::JSXAttr(JSXAttr {
span,
name: JSXAttrName::Ident(Ident::new("id".into(), span)),
value: Some(JSXAttrValue::Lit(Lit::Str(Str {
span,
value: "w < w".into(),
raw: Some("\"w &lt; w\"".into()),
}))),
})],
name: JSXElementName::Ident(Ident::new("div".into(), span)),
self_closing: true,
type_args: None,
},
children: vec![],
closing: None
})))
);
}
#[test]
fn issue_584() {
assert_eq_ignore_span!(
jsx(r#"<test other={4} />;"#),
Box::new(Expr::JSXElement(Box::new(JSXElement {
span,
opening: JSXOpeningElement {
span,
name: JSXElementName::Ident(Ident::new("test".into(), span)),
attrs: vec![JSXAttrOrSpread::JSXAttr(JSXAttr {
span,
name: JSXAttrName::Ident(Ident::new("other".into(), span)),
value: Some(JSXAttrValue::JSXExprContainer(JSXExprContainer {
span,
expr: JSXExpr::Expr(Box::new(Expr::Lit(Lit::Num(Number {
span,
value: 4.0,
raw: Some("4".into())
}))))
})),
})],
self_closing: true,
type_args: None,
},
children: vec![],
closing: None
})))
);
}

View file

@ -0,0 +1,394 @@
macro_rules! unexpected {
($p:expr, $expected:literal) => {{
let got = $p.input.dump_cur();
syntax_error!(
$p,
$p.input.cur_span(),
SyntaxError::Unexpected {
got,
expected: $expected
}
)
}};
}
/// This handles automatic semicolon insertion.
///
/// Returns bool.
macro_rules! is {
($p:expr, BindingIdent) => {{
let ctx = $p.ctx();
match cur!($p, false) {
Ok(&Word(ref w)) => !ctx.is_reserved_word(&w.cow()),
_ => false,
}
}};
($p:expr, IdentRef) => {{
let ctx = $p.ctx();
match cur!($p, false) {
Ok(&Word(ref w)) => !ctx.is_reserved_word(&w.cow()),
_ => false,
}
}};
($p:expr,IdentName) => {{
match cur!($p, false) {
Ok(&Word(..)) => true,
_ => false,
}
}};
($p:expr,Str) => {{
match cur!($p, false) {
Ok(&Token::Str { .. }) => true,
_ => false,
}
}};
($p:expr,Num) => {{
match cur!($p, false) {
Ok(&Token::Num { .. }) => true,
_ => false,
}
}};
($p:expr,Regex) => {{
match cur!($p, false) {
Ok(&Token::Regex { .. }) => true,
_ => false,
}
}};
($p:expr,BigInt) => {{
match cur!($p, false) {
Ok(&Token::BigInt { .. }) => true,
_ => false,
}
}};
($p:expr,';') => {{
match $p.input.cur() {
Some(&Token::Semi) | None | Some(&tok!('}')) => true,
_ => $p.input.had_line_break_before_cur(),
}
}};
($p:expr, $t:tt) => {
is_exact!($p, $t)
};
}
#[allow(unused)]
macro_rules! peeked_is {
($p:expr, BindingIdent) => {{
let ctx = $p.ctx();
match peek!($p) {
Ok(&Word(ref w)) => !ctx.is_reserved_word(&w.cow()),
_ => false,
}
}};
($p:expr, IdentRef) => {{
let ctx = $p.ctx();
match peek!($p) {
Ok(&Word(ref w)) => !ctx.is_reserved_word(&w.cow()),
_ => false,
}
}};
($p:expr,IdentName) => {{
match peek!($p) {
Ok(&Word(..)) => true,
_ => false,
}
}};
($p:expr, JSXName) => {{
match peek!($p) {
Ok(&Token::JSXName { .. }) => true,
_ => false,
}
}};
($p:expr, ';') => {{
compile_error!("peeked_is!(self, ';') is invalid");
}};
($p:expr, $t:tt) => {
match peek!($p).ok() {
Some(&token_including_semi!($t)) => true,
_ => false,
}
};
}
/// Returns true on eof.
macro_rules! eof {
($p:expr) => {
cur!($p, false).is_err()
};
}
macro_rules! is_one_of {
($p:expr, $($t:tt),+) => {{
false
$(
|| is!($p, $t)
)*
}};
}
// This will panic if current != token
macro_rules! assert_and_bump {
($p:expr, $t:tt) => {{
const TOKEN: &Token = &tok!($t);
if cfg!(debug_assertions) && !is!($p, $t) {
unreachable!(
"assertion failed: expected {:?}, got {:?}",
TOKEN,
$p.input.cur()
);
}
let _ = cur!($p, true)?;
bump!($p);
}};
}
/// This handles automatic semicolon insertion.
///
/// Returns bool if token is static, and Option<Token>
/// if token has data like string.
macro_rules! eat {
($p:expr, ';') => {{
if cfg!(feature = "debug") {
tracing::trace!("eat(';'): cur={:?}", cur!($p, false));
}
match $p.input.cur() {
Some(&Token::Semi) => {
$p.input.bump();
true
}
None | Some(&tok!('}')) => true,
_ => $p.input.had_line_break_before_cur(),
}
}};
($p:expr, $t:tt) => {{
if is!($p, $t) {
bump!($p);
true
} else {
false
}
}};
}
macro_rules! eat_exact {
($p:expr, $t:tt) => {{
if is_exact!($p, $t) {
bump!($p);
true
} else {
false
}
}};
}
macro_rules! is_exact {
($p:expr, $t:tt) => {{
match $p.input.cur() {
Some(&token_including_semi!($t)) => true,
_ => false,
}
}};
}
/// This handles automatic semicolon insertion.
macro_rules! expect {
($p:expr, $t:tt) => {{
const TOKEN: &Token = &token_including_semi!($t);
if !eat!($p, $t) {
let cur = $p.input.dump_cur();
syntax_error!($p, $p.input.cur_span(), SyntaxError::Expected(TOKEN, cur))
}
}};
}
macro_rules! expect_exact {
($p:expr, $t:tt) => {{
const TOKEN: &Token = &token_including_semi!($t);
if !eat_exact!($p, $t) {
let cur = $p.input.dump_cur();
syntax_error!($p, $p.input.cur_span(), SyntaxError::Expected(TOKEN, cur))
}
}};
}
macro_rules! store {
($p:expr, $t:tt) => {{
const TOKEN: Token = token_including_semi!($t);
$p.input.store(TOKEN);
}};
}
/// cur!($parser, required:bool)
macro_rules! cur {
($p:expr, $required:expr) => {{
let pos = $p.input.last_pos();
let last = Span::new(pos, pos, Default::default());
let is_err_token = match $p.input.cur() {
Some(&$crate::token::Token::Error(..)) => true,
_ => false,
};
if is_err_token {
match $p.input.bump() {
$crate::token::Token::Error(e) => {
return Err(e);
}
_ => unreachable!(),
}
}
match $p.input.cur() {
Some(c) => Ok(c),
None => {
if $required {
let err = crate::error::Error::new(last, crate::error::SyntaxError::Eof);
return Err(err);
}
Err(crate::error::Error::new(
last,
crate::error::SyntaxError::Eof,
))
}
}
}};
}
macro_rules! peek {
($p:expr) => {{
debug_assert!(
$p.input.knows_cur(),
"parser should not call peek() without knowing current token.
Current token is {:?}",
cur!($p, false),
);
let pos = cur_pos!($p);
let last = Span::new(pos, pos, Default::default());
match $p.input.peek() {
Some(c) => Ok(c),
None => {
let err = crate::error::Error::new(last, crate::error::SyntaxError::Eof);
Err(err)
}
}
}};
}
macro_rules! bump {
($p:expr) => {{
debug_assert!(
$p.input.knows_cur(),
"parser should not call bump() without knowing current token"
);
$p.input.bump()
}};
}
macro_rules! cur_pos {
($p:expr) => {{
$p.input.cur_pos()
}};
}
macro_rules! last_pos {
($p:expr) => {
$p.input.prev_span().hi
};
}
macro_rules! return_if_arrow {
($p:expr, $expr:expr) => {{
// FIXME:
//
//
// let is_cur = match $p.state.potential_arrow_start {
// Some(start) => $expr.span.lo() == start,
// None => false
// };
// if is_cur {
if let Expr::Arrow { .. } = *$expr {
return Ok($expr);
}
// }
}};
}
macro_rules! trace_cur {
($p:expr, $name:ident) => {{
if cfg!(feature = "debug") {
tracing::debug!("{}: {:?}", stringify!($name), $p.input.cur());
}
}};
}
/// This macro requires macro named 'last_pos' to be in scope.
macro_rules! span {
($p:expr, $start:expr) => {{
let start: ::swc_common::BytePos = $start;
let end: ::swc_common::BytePos = last_pos!($p);
if cfg!(debug_assertions) && start > end {
unreachable!(
"assertion failed: (span.start <= span.end).
start = {}, end = {}",
start.0, end.0
)
}
::swc_common::Span::new(start, end, ::swc_common::SyntaxContext::empty())
}};
}
macro_rules! make_error {
($p:expr, $span:expr, $err:expr) => {{
crate::error::Error::new($span, $err)
}};
}
macro_rules! syntax_error {
($p:expr, $err:expr) => {
syntax_error!($p, $p.input.cur_span(), $err)
};
($p:expr, $span:expr, $err:expr) => {{
let err = make_error!($p, $span, $err);
if cfg!(feature = "debug") {
tracing::error!(
"Syntax error called from {}:{}:{}\nCurrent token = {:?}",
file!(),
line!(),
column!(),
$p.input.cur()
);
}
return Err(err.into());
}};
}
macro_rules! debug_tracing {
($p:expr, $name:tt) => {{
#[cfg(feature = "debug")]
{
tracing::span!(
tracing::Level::ERROR,
$name,
cur = tracing::field::debug(&$p.input.cur())
)
.entered()
}
}};
}

View file

@ -0,0 +1,308 @@
#![allow(clippy::let_unit_value)]
#![deny(non_snake_case)]
use std::ops::{Deref, DerefMut};
use swc_atoms::{Atom, JsWord};
use swc_common::{collections::AHashMap, comments::Comments, input::StringInput, BytePos, Span};
use swc_ecma_ast::*;
pub use self::input::{Capturing, Tokens, TokensInput};
use self::{input::Buffer, util::ParseObject};
use crate::{
error::SyntaxError,
lexer::Lexer,
token::{Token, Word},
Context, EsVersion, Syntax, TsConfig,
};
#[cfg(test)]
extern crate test;
#[cfg(test)]
use test::Bencher;
use crate::error::Error;
#[macro_use]
mod macros;
mod class_and_fn;
mod expr;
mod ident;
pub mod input;
mod jsx;
mod object;
mod pat;
mod stmt;
#[cfg(test)]
mod tests;
mod typescript;
mod util;
/// When error occurs, error is emitted and parser returns Err(()).
pub type PResult<T> = Result<T, Error>;
/// EcmaScript parser.
#[derive(Clone)]
pub struct Parser<I: Tokens> {
state: State,
input: Buffer<I>,
}
#[derive(Clone, Default)]
struct State {
labels: Vec<JsWord>,
/// Start position of an assignment expression.
potential_arrow_start: Option<BytePos>,
found_module_item: bool,
/// Start position of an AST node and the span of its trailing comma.
trailing_commas: AHashMap<BytePos, Span>,
}
impl<'a> Parser<Lexer<'a>> {
pub fn new(syntax: Syntax, input: StringInput<'a>, comments: Option<&'a dyn Comments>) -> Self {
Self::new_from(Lexer::new(syntax, Default::default(), input, comments))
}
}
impl<I: Tokens> Parser<I> {
pub fn new_from(mut input: I) -> Self {
let in_declare = matches!(
input.syntax(),
Syntax::Typescript(TsConfig { dts: true, .. })
);
let ctx = Context {
in_declare,
..input.ctx()
};
input.set_ctx(ctx);
Parser {
state: Default::default(),
input: Buffer::new(input),
}
}
pub fn take_errors(&mut self) -> Vec<Error> {
self.input().take_errors()
}
pub fn parse_script(&mut self) -> PResult<Script> {
trace_cur!(self, parse_script);
let ctx = Context {
module: false,
..self.ctx()
};
self.set_ctx(ctx);
let start = cur_pos!(self);
let shebang = self.parse_shebang()?;
self.parse_block_body(true, true, None).map(|body| Script {
span: span!(self, start),
body,
shebang,
})
}
pub fn parse_typescript_module(&mut self) -> PResult<Module> {
trace_cur!(self, parse_typescript_module);
debug_assert!(self.syntax().typescript());
//TODO: parse() -> PResult<Program>
let ctx = Context {
module: true,
strict: false,
..self.ctx()
};
// Module code is always in strict mode
self.set_ctx(ctx);
let start = cur_pos!(self);
let shebang = self.parse_shebang()?;
self.parse_block_body(true, true, None).map(|body| Module {
span: span!(self, start),
body,
shebang,
})
}
/// Returns [Module] if it's a module and returns [Script] if it's not a
/// module.
///
/// Note: This is not perfect yet. It means, some strict mode violations may
/// not be reported even if the method returns [Module].
pub fn parse_program(&mut self) -> PResult<Program> {
let start = cur_pos!(self);
let shebang = self.parse_shebang()?;
let ctx = Context {
can_be_module: true,
..self.ctx()
};
let body: Vec<ModuleItem> = self.with_ctx(ctx).parse_block_body(true, true, None)?;
let has_module_item = self.state.found_module_item
|| body
.iter()
.any(|item| matches!(item, ModuleItem::ModuleDecl(..)));
if has_module_item && !self.ctx().module {
let ctx = Context {
module: true,
can_be_module: true,
strict: true,
..self.ctx()
};
// Emit buffered strict mode / module code violations
self.input.set_ctx(ctx);
}
Ok(if has_module_item {
Program::Module(Module {
span: span!(self, start),
body,
shebang,
})
} else {
let body = body
.into_iter()
.map(|item| match item {
ModuleItem::ModuleDecl(_) => unreachable!("Module is handled above"),
ModuleItem::Stmt(stmt) => stmt,
})
.collect();
Program::Script(Script {
span: span!(self, start),
body,
shebang,
})
})
}
pub fn parse_module(&mut self) -> PResult<Module> {
let ctx = Context {
module: true,
can_be_module: true,
strict: true,
..self.ctx()
};
// Module code is always in strict mode
self.set_ctx(ctx);
let start = cur_pos!(self);
let shebang = self.parse_shebang()?;
self.parse_block_body(true, true, None).map(|body| Module {
span: span!(self, start),
body,
shebang,
})
}
fn parse_shebang(&mut self) -> PResult<Option<Atom>> {
match cur!(self, false) {
Ok(&Token::Shebang(..)) => match bump!(self) {
Token::Shebang(v) => Ok(Some(v)),
_ => unreachable!(),
},
_ => Ok(None),
}
}
fn ctx(&self) -> Context {
self.input.get_ctx()
}
#[cold]
fn emit_err(&self, span: Span, error: SyntaxError) {
if self.ctx().ignore_error || !self.syntax().early_errors() {
return;
}
self.emit_error(Error::new(span, error))
}
#[cold]
fn emit_error(&self, error: Error) {
if self.ctx().ignore_error || !self.syntax().early_errors() {
return;
}
self.input_ref().add_error(error);
}
#[cold]
fn emit_strict_mode_err(&self, span: Span, error: SyntaxError) {
if self.ctx().ignore_error {
return;
}
let error = Error::new(span, error);
self.input_ref().add_module_mode_error(error);
}
}
#[cfg(test)]
pub fn test_parser<F, Ret>(s: &'static str, syntax: Syntax, f: F) -> Ret
where
F: FnOnce(&mut Parser<Lexer>) -> Result<Ret, Error>,
{
crate::with_test_sess(s, |handler, input| {
let lexer = Lexer::new(syntax, EsVersion::Es2019, input, None);
let mut p = Parser::new_from(lexer);
let ret = f(&mut p);
let mut error = false;
for err in p.take_errors() {
error = true;
err.into_diagnostic(handler).emit();
}
let res = ret.map_err(|err| err.into_diagnostic(handler).emit())?;
if error {
return Err(());
}
Ok(res)
})
.unwrap_or_else(|output| panic!("test_parser(): failed to parse \n{}\n{}", s, output))
}
#[cfg(test)]
pub fn test_parser_comment<F, Ret>(c: &dyn Comments, s: &'static str, syntax: Syntax, f: F) -> Ret
where
F: FnOnce(&mut Parser<Lexer>) -> Result<Ret, Error>,
{
crate::with_test_sess(s, |handler, input| {
let lexer = Lexer::new(syntax, EsVersion::Es2019, input, Some(&c));
let mut p = Parser::new_from(lexer);
let ret = f(&mut p);
for err in p.take_errors() {
err.into_diagnostic(handler).emit();
}
ret.map_err(|err| err.into_diagnostic(handler).emit())
})
.unwrap_or_else(|output| panic!("test_parser(): failed to parse \n{}\n{}", s, output))
}
#[cfg(test)]
pub fn bench_parser<F>(b: &mut Bencher, s: &'static str, syntax: Syntax, mut f: F)
where
F: for<'a> FnMut(&'a mut Parser<Lexer<'a>>) -> PResult<()>,
{
b.bytes = s.len() as u64;
let _ = crate::with_test_sess(s, |handler, input| {
b.iter(|| {
let lexer = Lexer::new(syntax, Default::default(), input.clone(), None);
let _ =
f(&mut Parser::new_from(lexer)).map_err(|err| err.into_diagnostic(handler).emit());
});
Ok(())
});
}

View file

@ -0,0 +1,490 @@
//! Parser for object literal.
use swc_atoms::js_word;
use swc_common::Spanned;
use super::*;
use crate::parser::class_and_fn::is_not_this;
impl<I: Tokens> Parser<I> {
/// Parse a object literal or object pattern.
pub(super) fn parse_object<T>(&mut self) -> PResult<T>
where
Self: ParseObject<T>,
{
let ctx = Context {
will_expect_colon_for_cond: false,
..self.ctx()
};
self.with_ctx(ctx).parse_with(|p| {
trace_cur!(p, parse_object);
let start = cur_pos!(p);
let mut trailing_comma = None;
assert_and_bump!(p, '{');
let mut props = vec![];
while !eat!(p, '}') {
props.push(p.parse_object_prop()?);
if !is!(p, '}') {
expect!(p, ',');
if is!(p, '}') {
trailing_comma = Some(p.input.prev_span());
}
}
}
p.make_object(span!(p, start), props, trailing_comma)
})
}
/// spec: 'PropertyName'
pub(super) fn parse_prop_name(&mut self) -> PResult<PropName> {
trace_cur!(self, parse_prop_name);
let ctx = self.ctx();
self.with_ctx(Context {
in_property_name: true,
..ctx
})
.parse_with(|p| {
let start = cur_pos!(p);
let v = match *cur!(p, true)? {
Token::Str { .. } => match bump!(p) {
Token::Str { value, raw } => PropName::Str(Str {
span: span!(p, start),
value,
raw: Some(raw),
}),
_ => unreachable!(),
},
Token::Num { .. } => match bump!(p) {
Token::Num { value, raw } => PropName::Num(Number {
span: span!(p, start),
value,
raw: Some(raw),
}),
_ => unreachable!(),
},
Token::BigInt { .. } => match bump!(p) {
Token::BigInt { value, raw } => PropName::BigInt(BigInt {
span: span!(p, start),
value,
raw: Some(raw),
}),
_ => unreachable!(),
},
Word(..) => match bump!(p) {
Word(w) => PropName::Ident(Ident::new(w.into(), span!(p, start))),
_ => unreachable!(),
},
tok!('[') => {
bump!(p);
let inner_start = cur_pos!(p);
let mut expr = p.include_in_expr(true).parse_assignment_expr()?;
if p.syntax().typescript() && is!(p, ',') {
let mut exprs = vec![expr];
while eat!(p, ',') {
exprs.push(p.include_in_expr(true).parse_assignment_expr()?);
}
p.emit_err(span!(p, inner_start), SyntaxError::TS1171);
expr = Box::new(
SeqExpr {
span: span!(p, inner_start),
exprs,
}
.into(),
);
}
expect!(p, ']');
PropName::Computed(ComputedPropName {
span: span!(p, start),
expr,
})
}
_ => unexpected!(
p,
"identifier, string literal, numeric literal or [ for the computed key"
),
};
Ok(v)
})
}
}
impl<I: Tokens> ParseObject<Box<Expr>> for Parser<I> {
type Prop = PropOrSpread;
fn make_object(
&mut self,
span: Span,
props: Vec<Self::Prop>,
trailing_comma: Option<Span>,
) -> PResult<Box<Expr>> {
if let Some(trailing_comma) = trailing_comma {
self.state.trailing_commas.insert(span.lo, trailing_comma);
}
Ok(Box::new(Expr::Object(ObjectLit { span, props })))
}
/// spec: 'PropertyDefinition'
fn parse_object_prop(&mut self) -> PResult<Self::Prop> {
trace_cur!(self, parse_object_prop);
let start = cur_pos!(self);
// Parse as 'MethodDefinition'
if eat!(self, "...") {
// spread element
let dot3_token = span!(self, start);
let expr = self.include_in_expr(true).parse_assignment_expr()?;
return Ok(PropOrSpread::Spread(SpreadElement { dot3_token, expr }));
}
if eat!(self, '*') {
let name = self.parse_prop_name()?;
return self
.with_ctx(Context {
allow_direct_super: true,
in_class_field: false,
..self.ctx()
})
.parse_fn_args_body(
// no decorator in an object literal
vec![],
start,
|p| p.parse_unique_formal_params(),
false,
true,
)
.map(|function| {
PropOrSpread::Prop(Box::new(Prop::Method(MethodProp {
key: name,
function,
})))
});
}
let has_modifiers = self.eat_any_ts_modifier()?;
let modifiers_span = self.input.prev_span();
let key = self.parse_prop_name()?;
if self.input.syntax().typescript()
&& !is_one_of!(self, '(', '[', ':', ',', '?', '=', '*', IdentName, Str, Num)
&& !(self.input.syntax().typescript() && is!(self, '<'))
&& !(is!(self, '}') && matches!(key, PropName::Ident(..)))
{
trace_cur!(self, parse_object_prop_error);
self.emit_err(self.input.cur_span(), SyntaxError::TS1005);
return Ok(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key,
value: Box::new(Expr::Invalid(Invalid {
span: span!(self, start),
})),
}))));
}
//
// {[computed()]: a,}
// { 'a': a, }
// { 0: 1, }
// { a: expr, }
if eat!(self, ':') {
let value = self.include_in_expr(true).parse_assignment_expr()?;
return Ok(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key,
value,
}))));
}
// Handle `a(){}` (and async(){} / get(){} / set(){})
if (self.input.syntax().typescript() && is!(self, '<')) || is!(self, '(') {
return self
.with_ctx(Context {
allow_direct_super: true,
in_class_field: false,
..self.ctx()
})
.parse_fn_args_body(
// no decorator in an object literal
vec![],
start,
|p| p.parse_unique_formal_params(),
false,
false,
)
.map(|function| Box::new(Prop::Method(MethodProp { key, function })))
.map(PropOrSpread::Prop);
}
let ident = match key {
PropName::Ident(ident) => ident,
// TODO
_ => unexpected!(self, "identifier"),
};
if eat!(self, '?') {
self.emit_err(self.input.prev_span(), SyntaxError::TS1162);
}
// `ident` from parse_prop_name is parsed as 'IdentifierName'
// It means we should check for invalid expressions like { for, }
if is_one_of!(self, '=', ',', '}') {
let is_reserved_word = { self.ctx().is_reserved_word(&ident.sym) };
if is_reserved_word {
self.emit_err(ident.span, SyntaxError::ReservedWordInObjShorthandOrPat);
}
if eat!(self, '=') {
let value = self.include_in_expr(true).parse_assignment_expr()?;
return Ok(PropOrSpread::Prop(Box::new(Prop::Assign(AssignProp {
key: ident,
value,
}))));
}
return Ok(PropOrSpread::Prop(Box::new(Prop::from(ident))));
}
// get a(){}
// set a(v){}
// async a(){}
match ident.sym {
js_word!("get") | js_word!("set") | js_word!("async") => {
trace_cur!(self, parse_object_prop__after_accessor);
if has_modifiers {
self.emit_err(modifiers_span, SyntaxError::TS1042);
}
let is_generator = ident.sym == js_word!("async") && eat!(self, '*');
let key = self.parse_prop_name()?;
let key_span = key.span();
self.with_ctx(Context {
allow_direct_super: true,
in_class_field: false,
..self.ctx()
})
.parse_with(|parser| {
match ident.sym {
js_word!("get") => parser
.parse_fn_args_body(
// no decorator in an object literal
vec![],
start,
|p| {
let params = p.parse_formal_params()?;
if params.iter().filter(|p| is_not_this(p)).count() != 0 {
p.emit_err(key_span, SyntaxError::GetterParam);
}
Ok(params)
},
false,
false,
)
.map(|v| *v)
.map(
|Function {
body, return_type, ..
}| {
if parser.input.syntax().typescript()
&& parser.input.target() == EsVersion::Es3
{
parser.emit_err(key_span, SyntaxError::TS1056);
}
PropOrSpread::Prop(Box::new(Prop::Getter(GetterProp {
span: span!(parser, start),
key,
type_ann: return_type,
body,
})))
},
),
js_word!("set") => {
parser
.parse_fn_args_body(
// no decorator in an object literal
vec![],
start,
|p| {
let params = p.parse_formal_params()?;
if params.iter().filter(|p| is_not_this(p)).count() != 1 {
p.emit_err(key_span, SyntaxError::SetterParam);
}
if !params.is_empty() {
if let Pat::Rest(..) = params[0].pat {
p.emit_err(
params[0].span(),
SyntaxError::RestPatInSetter,
);
}
}
if p.input.syntax().typescript()
&& p.input.target() == EsVersion::Es3
{
p.emit_err(key_span, SyntaxError::TS1056);
}
Ok(params)
},
false,
false,
)
.map(|v| *v)
.map(|Function { params, body, .. }| {
// debug_assert_eq!(params.len(), 1);
PropOrSpread::Prop(Box::new(Prop::Setter(SetterProp {
span: span!(parser, start),
key,
body,
param: Box::new(
params.into_iter().map(|p| p.pat).next().unwrap_or(
Pat::Invalid(Invalid { span: key_span }),
),
),
})))
})
}
js_word!("async") => parser
.parse_fn_args_body(
// no decorator in an object literal
vec![],
start,
|p| p.parse_unique_formal_params(),
true,
is_generator,
)
.map(|function| {
PropOrSpread::Prop(Box::new(Prop::Method(MethodProp {
key,
function,
})))
}),
_ => unreachable!(),
}
})
}
_ => {
if self.input.syntax().typescript() {
unexpected!(
self,
"... , *, (, [, :, , ?, =, an identifier, public, protected, private, \
readonly, <."
)
} else {
unexpected!(self, "... , *, (, [, :, , ?, = or an identifier")
}
}
}
}
}
impl<I: Tokens> ParseObject<Pat> for Parser<I> {
type Prop = ObjectPatProp;
fn make_object(
&mut self,
span: Span,
props: Vec<Self::Prop>,
trailing_comma: Option<Span>,
) -> PResult<Pat> {
let len = props.len();
for (i, p) in props.iter().enumerate() {
if i == len - 1 {
if let ObjectPatProp::Rest(ref rest) = p {
match *rest.arg {
Pat::Ident(..) => {
if let Some(trailing_comma) = trailing_comma {
self.emit_err(trailing_comma, SyntaxError::CommaAfterRestElement);
}
}
_ => syntax_error!(self, p.span(), SyntaxError::DotsWithoutIdentifier),
}
}
continue;
}
if let ObjectPatProp::Rest(..) = p {
self.emit_err(p.span(), SyntaxError::NonLastRestParam)
}
}
let optional = (self.input.syntax().dts() || self.ctx().in_declare) && eat!(self, '?');
Ok(Pat::Object(ObjectPat {
span,
props,
optional,
type_ann: None,
}))
}
/// Production 'BindingProperty'
fn parse_object_prop(&mut self) -> PResult<Self::Prop> {
let start = cur_pos!(self);
if eat!(self, "...") {
// spread element
let dot3_token = span!(self, start);
let arg = Box::new(self.parse_binding_pat_or_ident()?);
return Ok(ObjectPatProp::Rest(RestPat {
span: span!(self, start),
dot3_token,
arg,
type_ann: None,
}));
}
let key = self.parse_prop_name()?;
if eat!(self, ':') {
let value = Box::new(self.parse_binding_element()?);
return Ok(ObjectPatProp::KeyValue(KeyValuePatProp { key, value }));
}
let key = match key {
PropName::Ident(ident) => ident,
_ => unexpected!(self, "an identifier"),
};
let value = if eat!(self, '=') {
self.include_in_expr(true)
.parse_assignment_expr()
.map(Some)?
} else {
if self.ctx().is_reserved_word(&key.sym) {
self.emit_err(key.span, SyntaxError::ReservedWordInObjShorthandOrPat);
}
None
};
Ok(ObjectPatProp::Assign(AssignPatProp {
span: span!(self, start),
key,
value,
}))
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,884 @@
use super::*;
impl<I: Tokens> Parser<I> {
fn parse_import(&mut self) -> PResult<ModuleItem> {
let start = cur_pos!(self);
if peeked_is!(self, '.') {
let expr = self.parse_expr()?;
eat!(self, ';');
return Ok(Stmt::Expr(ExprStmt {
span: span!(self, start),
expr,
})
.into());
}
if peeked_is!(self, '(') {
let expr = self.parse_expr()?;
eat!(self, ';');
return Ok(Stmt::Expr(ExprStmt {
span: span!(self, start),
expr,
})
.into());
}
// It's now import statement
if !self.ctx().module {
// Switch to module mode
let ctx = Context {
module: true,
strict: true,
..self.ctx()
};
self.set_ctx(ctx);
}
expect!(self, "import");
if self.input.syntax().typescript() && is!(self, IdentRef) && peeked_is!(self, '=') {
return self
.parse_ts_import_equals_decl(
start, /* is_export */ false, /* is_type_only */ false,
)
.map(ModuleDecl::from)
.map(ModuleItem::from);
}
// Handle import 'mod.js'
let str_start = cur_pos!(self);
if let Ok(&Token::Str { .. }) = cur!(self, false) {
let src = match bump!(self) {
Token::Str { value, raw, .. } => Box::new(Str {
span: span!(self, str_start),
value,
raw: Some(raw),
}),
_ => unreachable!(),
};
let _ = cur!(self, false);
let with = if self.input.syntax().import_attributes()
&& !self.input.had_line_break_before_cur()
&& (eat!(self, "assert") || eat!(self, "with"))
{
match *self.parse_object::<Box<Expr>>()? {
Expr::Object(v) => Some(Box::new(v)),
_ => unreachable!(),
}
} else {
None
};
expect!(self, ';');
return Ok(ModuleDecl::Import(ImportDecl {
span: span!(self, start),
src,
specifiers: vec![],
type_only: false,
with,
}))
.map(ModuleItem::from);
}
let type_only = self.input.syntax().typescript()
&& is!(self, "type")
&& (peeked_is!(self, '{') || !peeked_is!(self, "from") && !peeked_is!(self, ','));
if type_only {
assert_and_bump!(self, "type");
if is!(self, IdentRef) && peeked_is!(self, '=') {
return self
.parse_ts_import_equals_decl(
start, /* is_export */ false, /* is_type_only */ true,
)
.map(ModuleDecl::from)
.map(ModuleItem::from);
}
}
let mut specifiers = vec![];
if is!(self, BindingIdent) {
let local = self.parse_imported_default_binding()?;
//TODO: Better error reporting
if !is!(self, "from") {
expect!(self, ',');
}
specifiers.push(ImportSpecifier::Default(ImportDefaultSpecifier {
span: local.span,
local,
}));
}
{
let import_spec_start = cur_pos!(self);
if eat!(self, '*') {
expect!(self, "as");
let local = self.parse_imported_binding()?;
specifiers.push(ImportSpecifier::Namespace(ImportStarAsSpecifier {
span: span!(self, import_spec_start),
local,
}));
} else if eat!(self, '{') {
while !eof!(self) && !is!(self, '}') {
specifiers.push(self.parse_import_specifier(type_only)?);
if is!(self, '}') {
break;
} else {
expect!(self, ',');
}
}
expect!(self, '}');
}
}
let src = {
expect!(self, "from");
let str_start = cur_pos!(self);
match *cur!(self, true)? {
Token::Str { .. } => match bump!(self) {
Token::Str { value, raw, .. } => Box::new(Str {
span: span!(self, str_start),
value,
raw: Some(raw),
}),
_ => unreachable!(),
},
_ => unexpected!(self, "a string literal"),
}
};
let _ = cur!(self, false);
let with = if self.input.syntax().import_attributes()
&& !self.input.had_line_break_before_cur()
&& (eat!(self, "assert") || eat!(self, "with"))
{
match *self.parse_object::<Box<Expr>>()? {
Expr::Object(v) => Some(Box::new(v)),
_ => unreachable!(),
}
} else {
None
};
expect!(self, ';');
Ok(ModuleDecl::Import(ImportDecl {
span: span!(self, start),
specifiers,
src,
type_only,
with,
}))
.map(ModuleItem::from)
}
/// Parse `foo`, `foo2 as bar` in `import { foo, foo2 as bar }`
fn parse_import_specifier(&mut self, type_only: bool) -> PResult<ImportSpecifier> {
let start = cur_pos!(self);
match self.parse_module_export_name()? {
ModuleExportName::Ident(mut orig_name) => {
let mut is_type_only = false;
// Handle:
// `import { type xx } from 'mod'`
// `import { type xx as yy } from 'mod'`
// `import { type as } from 'mod'`
// `import { type as as } from 'mod'`
// `import { type as as as } from 'mod'`
if self.syntax().typescript()
&& orig_name.sym == js_word!("type")
&& is!(self, IdentName)
{
let possibly_orig_name = self.parse_ident_name()?;
if possibly_orig_name.sym == js_word!("as") {
// `import { type as } from 'mod'`
if !is!(self, IdentName) {
if self.ctx().is_reserved_word(&possibly_orig_name.sym) {
syntax_error!(
self,
possibly_orig_name.span,
SyntaxError::ReservedWordInImport
)
}
if type_only {
self.emit_err(orig_name.span, SyntaxError::TS2206);
}
return Ok(ImportSpecifier::Named(ImportNamedSpecifier {
span: span!(self, start),
local: possibly_orig_name,
imported: None,
is_type_only: true,
}));
}
let maybe_as = self.parse_binding_ident()?.id;
if maybe_as.sym == js_word!("as") {
if is!(self, IdentName) {
// `import { type as as as } from 'mod'`
// `import { type as as foo } from 'mod'`
let local = self.parse_binding_ident()?.id;
if type_only {
self.emit_err(orig_name.span, SyntaxError::TS2206);
}
return Ok(ImportSpecifier::Named(ImportNamedSpecifier {
span: Span::new(start, orig_name.span.hi(), Default::default()),
local,
imported: Some(ModuleExportName::Ident(possibly_orig_name)),
is_type_only: true,
}));
} else {
// `import { type as as } from 'mod'`
return Ok(ImportSpecifier::Named(ImportNamedSpecifier {
span: Span::new(start, maybe_as.span.hi(), Default::default()),
local: maybe_as,
imported: Some(ModuleExportName::Ident(orig_name)),
is_type_only: false,
}));
}
} else {
// `import { type as xxx } from 'mod'`
return Ok(ImportSpecifier::Named(ImportNamedSpecifier {
span: Span::new(start, orig_name.span.hi(), Default::default()),
local: maybe_as,
imported: Some(ModuleExportName::Ident(orig_name)),
is_type_only: false,
}));
}
} else {
// `import { type xx } from 'mod'`
// `import { type xx as yy } from 'mod'`
if type_only {
self.emit_err(orig_name.span, SyntaxError::TS2206);
}
orig_name = possibly_orig_name;
is_type_only = true;
}
}
if eat!(self, "as") {
let local = self.parse_binding_ident()?.id;
return Ok(ImportSpecifier::Named(ImportNamedSpecifier {
span: Span::new(start, local.span.hi(), Default::default()),
local,
imported: Some(ModuleExportName::Ident(orig_name)),
is_type_only,
}));
}
// Handle difference between
//
// 'ImportedBinding'
// 'IdentifierName' as 'ImportedBinding'
if self.ctx().is_reserved_word(&orig_name.sym) {
syntax_error!(self, orig_name.span, SyntaxError::ReservedWordInImport)
}
let local = orig_name;
Ok(ImportSpecifier::Named(ImportNamedSpecifier {
span: span!(self, start),
local,
imported: None,
is_type_only,
}))
}
ModuleExportName::Str(orig_str) => {
if eat!(self, "as") {
let local = self.parse_binding_ident()?.id;
Ok(ImportSpecifier::Named(ImportNamedSpecifier {
span: Span::new(start, local.span.hi(), Default::default()),
local,
imported: Some(ModuleExportName::Str(orig_str)),
is_type_only: false,
}))
} else {
syntax_error!(
self,
orig_str.span,
SyntaxError::ImportBindingIsString(orig_str.value)
)
}
}
}
}
fn parse_imported_default_binding(&mut self) -> PResult<Ident> {
self.parse_imported_binding()
}
fn parse_imported_binding(&mut self) -> PResult<Ident> {
let ctx = Context {
in_async: false,
in_generator: false,
..self.ctx()
};
Ok(self.with_ctx(ctx).parse_binding_ident()?.id)
}
fn parse_export(&mut self, mut decorators: Vec<Decorator>) -> PResult<ModuleDecl> {
if !self.ctx().module {
// Switch to module mode
let ctx = Context {
module: true,
strict: true,
..self.ctx()
};
self.set_ctx(ctx);
}
let start = cur_pos!(self);
assert_and_bump!(self, "export");
let _ = cur!(self, true);
let after_export_start = cur_pos!(self);
// "export declare" is equivalent to just "export".
let declare = self.input.syntax().typescript() && eat!(self, "declare");
if declare {
// TODO: Remove
if let Some(decl) = self.try_parse_ts_declare(after_export_start, decorators.clone())? {
return Ok(ModuleDecl::ExportDecl(ExportDecl {
span: span!(self, start),
decl,
}));
}
}
if self.input.syntax().typescript() && is!(self, IdentName) {
let sym = match *cur!(self, true)? {
Token::Word(ref w) => w.clone().into(),
_ => unreachable!(),
};
// TODO: remove clone
if let Some(decl) = self.try_parse_ts_export_decl(decorators.clone(), sym) {
return Ok(ModuleDecl::ExportDecl(ExportDecl {
span: span!(self, start),
decl,
}));
}
}
if self.input.syntax().typescript() {
if eat!(self, "import") {
let is_type_only = is!(self, "type") && peeked_is!(self, IdentRef);
if is_type_only {
assert_and_bump!(self, "type");
}
// export import A = B
return self
.parse_ts_import_equals_decl(start, /* is_export */ true, is_type_only)
.map(From::from);
}
if eat!(self, '=') {
// `export = x;`
let expr = self.parse_expr()?;
expect!(self, ';');
return Ok(TsExportAssignment {
span: span!(self, start),
expr,
}
.into());
}
if eat!(self, "as") {
// `export as namespace A;`
// See `parseNamespaceExportDeclaration` in TypeScript's own parser
expect!(self, "namespace");
let id = self.parse_ident(false, false)?;
expect!(self, ';');
return Ok(TsNamespaceExportDecl {
span: span!(self, start),
id,
}
.into());
}
}
let ns_export_specifier_start = cur_pos!(self);
let type_only = self.input.syntax().typescript() && eat!(self, "type");
// Some("default") if default is exported from 'src'
let mut export_default = None;
if !type_only && eat!(self, "default") {
if is!(self, '@') {
let start = cur_pos!(self);
let after_decorators = self.parse_decorators(false)?;
if !decorators.is_empty() {
syntax_error!(self, span!(self, start), SyntaxError::TS8038);
}
decorators = after_decorators;
}
if self.input.syntax().typescript() {
if is!(self, "abstract")
&& peeked_is!(self, "class")
&& !self.input.has_linebreak_between_cur_and_peeked()
{
let class_start = cur_pos!(self);
assert_and_bump!(self, "abstract");
let _ = cur!(self, true);
return self
.parse_default_class(start, class_start, decorators, true)
.map(ModuleDecl::ExportDefaultDecl);
}
if is!(self, "abstract") && peeked_is!(self, "interface") {
self.emit_err(self.input.cur_span(), SyntaxError::TS1242);
assert_and_bump!(self, "abstract");
}
if is!(self, "interface") {
let interface_start = cur_pos!(self);
assert_and_bump!(self, "interface");
let decl = self
.parse_ts_interface_decl(interface_start)
.map(DefaultDecl::from)?;
return Ok(ExportDefaultDecl {
span: span!(self, start),
decl,
}
.into());
}
}
if is!(self, "class") {
let class_start = cur_pos!(self);
let decl = self.parse_default_class(start, class_start, decorators, false)?;
return Ok(ModuleDecl::ExportDefaultDecl(decl));
} else if is!(self, "async")
&& peeked_is!(self, "function")
&& !self.input.has_linebreak_between_cur_and_peeked()
{
let decl = self.parse_default_async_fn(start, decorators)?;
return Ok(ModuleDecl::ExportDefaultDecl(decl));
} else if is!(self, "function") {
let decl = self.parse_default_fn(start, decorators)?;
return Ok(ModuleDecl::ExportDefaultDecl(decl));
} else if self.input.syntax().export_default_from()
&& (is!(self, "from")
|| (is!(self, ',') && (peeked_is!(self, '{') || peeked_is!(self, '*'))))
{
export_default = Some(Ident::new("default".into(), self.input.prev_span()))
} else {
let expr = self.include_in_expr(true).parse_assignment_expr()?;
expect!(self, ';');
return Ok(ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
span: span!(self, start),
expr,
}));
}
}
if is!(self, '@') {
let start = cur_pos!(self);
let after_decorators = self.parse_decorators(false)?;
if !decorators.is_empty() {
syntax_error!(self, span!(self, start), SyntaxError::TS8038);
}
decorators = after_decorators;
}
let decl = if !type_only && is!(self, "class") {
let class_start = cur_pos!(self);
self.parse_class_decl(start, class_start, decorators, false)?
} else if !type_only
&& is!(self, "async")
&& peeked_is!(self, "function")
&& !self.input.has_linebreak_between_cur_and_peeked()
{
self.parse_async_fn_decl(decorators)?
} else if !type_only && is!(self, "function") {
self.parse_fn_decl(decorators)?
} else if !type_only
&& self.input.syntax().typescript()
&& is!(self, "const")
&& peeked_is!(self, "enum")
{
let enum_start = cur_pos!(self);
assert_and_bump!(self, "const");
let _ = cur!(self, true);
assert_and_bump!(self, "enum");
return self
.parse_ts_enum_decl(enum_start, /* is_const */ true)
.map(Decl::from)
.map(|decl| {
ModuleDecl::ExportDecl(ExportDecl {
span: span!(self, start),
decl,
})
});
} else if !type_only
&& (is!(self, "var")
|| is!(self, "const")
|| (is!(self, "let"))
&& peek!(self)
.map(|t| {
// module code is always in strict mode.
t.follows_keyword_let(true)
})
.unwrap_or(false))
{
self.parse_var_stmt(false).map(Decl::Var)?
} else {
// ```javascript
// export foo, * as bar, { baz } from "mod"; // *
// export * as bar, { baz } from "mod"; // *
// export foo, { baz } from "mod"; // *
// export foo, * as bar from "mod"; // *
// export foo from "mod"; // *
// export * as bar from "mod"; //
// export { baz } from "mod"; //
// export { baz } ; //
// export * from "mod"; //
// ```
// export default
// export foo
let default = match export_default {
Some(default) => Some(default),
None => {
if self.input.syntax().export_default_from() && is!(self, IdentName) {
Some(self.parse_ident(false, false)?)
} else {
None
}
}
};
if default.is_none() && is!(self, '*') && !peeked_is!(self, "as") {
assert_and_bump!(self, '*');
// improve error message for `export * from foo`
let (src, with) = self.parse_from_clause_and_semi()?;
return Ok(ModuleDecl::ExportAll(ExportAll {
span: span!(self, start),
src,
type_only,
with,
}));
}
let mut specifiers = vec![];
let mut has_default = false;
let mut has_ns = false;
if let Some(default) = default {
has_default = true;
specifiers.push(ExportSpecifier::Default(ExportDefaultSpecifier {
exported: default,
}))
}
// export foo, * as bar
// ^
if !specifiers.is_empty() && is!(self, ',') && peeked_is!(self, '*') {
assert_and_bump!(self, ',');
has_ns = true;
}
// export * as bar
// ^
else if specifiers.is_empty() && is!(self, '*') {
has_ns = true;
}
if has_ns {
assert_and_bump!(self, '*');
expect!(self, "as");
let name = self.parse_module_export_name()?;
specifiers.push(ExportSpecifier::Namespace(ExportNamespaceSpecifier {
span: span!(self, ns_export_specifier_start),
name,
}));
}
if has_default || has_ns {
if is!(self, "from") {
let (src, with) = self.parse_from_clause_and_semi()?;
return Ok(ModuleDecl::ExportNamed(NamedExport {
span: span!(self, start),
specifiers,
src: Some(src),
type_only,
with,
}));
} else if !self.input.syntax().export_default_from() {
// emit error
expect!(self, "from");
}
expect!(self, ',');
}
expect!(self, '{');
let mut string_export_binding_span = None;
while !eof!(self) && !is!(self, '}') {
let specifier = self.parse_named_export_specifier(type_only)?;
if let ModuleExportName::Str(str_export) = &specifier.orig {
string_export_binding_span = Some(str_export.span);
}
specifiers.push(ExportSpecifier::Named(specifier));
if is!(self, '}') {
break;
} else {
expect!(self, ',');
}
}
expect!(self, '}');
let opt = if is!(self, "from") {
Some(self.parse_from_clause_and_semi()?)
} else {
eat!(self, ';');
if has_default || has_ns {
syntax_error!(
self,
span!(self, start),
SyntaxError::ExportDefaultWithOutFrom
);
}
if let Some(span) = string_export_binding_span {
syntax_error!(self, span, SyntaxError::ExportBindingIsString);
}
None
};
let (src, with) = match opt {
Some(v) => (Some(v.0), v.1),
None => (None, None),
};
return Ok(ModuleDecl::ExportNamed(NamedExport {
span: span!(self, start),
specifiers,
src,
type_only,
with,
}));
};
Ok(ModuleDecl::ExportDecl(ExportDecl {
span: span!(self, start),
decl,
}))
}
fn parse_named_export_specifier(&mut self, type_only: bool) -> PResult<ExportNamedSpecifier> {
let start = cur_pos!(self);
let mut is_type_only = false;
let orig = match self.parse_module_export_name()? {
ModuleExportName::Ident(orig_ident) => {
// Handle:
// `export { type xx }`
// `export { type xx as yy }`
// `export { type as }`
// `export { type as as }`
// `export { type as as as }`
if self.syntax().typescript()
&& orig_ident.sym == js_word!("type")
&& is!(self, IdentName)
{
let possibly_orig = self.parse_ident_name()?;
if possibly_orig.sym == js_word!("as") {
// `export { type as }`
if !is!(self, IdentName) {
if type_only {
self.emit_err(orig_ident.span, SyntaxError::TS2207);
}
return Ok(ExportNamedSpecifier {
span: span!(self, start),
orig: ModuleExportName::Ident(possibly_orig),
exported: None,
is_type_only: true,
});
}
let maybe_as = self.parse_ident_name()?;
if maybe_as.sym == js_word!("as") {
if is!(self, IdentName) {
// `export { type as as as }`
// `export { type as as foo }`
let exported = self.parse_ident_name()?;
if type_only {
self.emit_err(orig_ident.span, SyntaxError::TS2207);
}
return Ok(ExportNamedSpecifier {
span: Span::new(
start,
orig_ident.span.hi(),
Default::default(),
),
orig: ModuleExportName::Ident(possibly_orig),
exported: Some(ModuleExportName::Ident(exported)),
is_type_only: true,
});
} else {
// `export { type as as }`
return Ok(ExportNamedSpecifier {
span: Span::new(
start,
orig_ident.span.hi(),
Default::default(),
),
orig: ModuleExportName::Ident(orig_ident),
exported: Some(ModuleExportName::Ident(maybe_as)),
is_type_only: false,
});
}
} else {
// `export { type as xxx }`
return Ok(ExportNamedSpecifier {
span: Span::new(start, orig_ident.span.hi(), Default::default()),
orig: ModuleExportName::Ident(orig_ident),
exported: Some(ModuleExportName::Ident(maybe_as)),
is_type_only: false,
});
}
} else {
// `export { type xx }`
// `export { type xx as yy }`
if type_only {
self.emit_err(orig_ident.span, SyntaxError::TS2207);
}
is_type_only = true;
ModuleExportName::Ident(possibly_orig)
}
} else {
ModuleExportName::Ident(orig_ident)
}
}
module_export_name => module_export_name,
};
let exported = if eat!(self, "as") {
Some(self.parse_module_export_name()?)
} else {
None
};
Ok(ExportNamedSpecifier {
span: span!(self, start),
orig,
exported,
is_type_only,
})
}
/// Parses `from 'foo.js' with {};` or `from 'foo.js' assert {};`
fn parse_from_clause_and_semi(&mut self) -> PResult<(Box<Str>, Option<Box<ObjectLit>>)> {
expect!(self, "from");
let str_start = cur_pos!(self);
let src = match *cur!(self, true)? {
Token::Str { .. } => match bump!(self) {
Token::Str { value, raw, .. } => Box::new(Str {
span: span!(self, str_start),
value,
raw: Some(raw),
}),
_ => unreachable!(),
},
_ => unexpected!(self, "a string literal"),
};
let _ = cur!(self, false);
let with = if self.input.syntax().import_attributes()
&& !self.input.had_line_break_before_cur()
&& (eat!(self, "assert") || eat!(self, "with"))
{
match *self.parse_object::<Box<Expr>>()? {
Expr::Object(v) => Some(Box::new(v)),
_ => unreachable!(),
}
} else {
None
};
expect!(self, ';');
Ok((src, with))
}
}
impl IsDirective for ModuleItem {
fn as_ref(&self) -> Option<&Stmt> {
match *self {
ModuleItem::Stmt(ref s) => Some(s),
_ => None,
}
}
}
impl<'a, I: Tokens> StmtLikeParser<'a, ModuleItem> for Parser<I> {
fn handle_import_export(
&mut self,
top_level: bool,
decorators: Vec<Decorator>,
) -> PResult<ModuleItem> {
if !top_level {
syntax_error!(self, SyntaxError::NonTopLevelImportExport);
}
let decl = if is!(self, "import") {
self.parse_import()?
} else if is!(self, "export") {
self.parse_export(decorators).map(ModuleItem::from)?
} else {
unreachable!(
"handle_import_export should not be called if current token isn't import nor \
export"
)
};
Ok(decl)
}
}
#[cfg(test)]
mod tests {
use crate::{EsConfig, Syntax};
#[test]
fn test_legacy_decorator() {
crate::test_parser(
"@foo
export default class Foo {
bar() {
class Baz {}
}
}",
Syntax::Es(EsConfig {
decorators: true,
decorators_before_export: true,
..Default::default()
}),
|p| p.parse_module(),
);
}
}

View file

@ -0,0 +1,350 @@
use swc_common::{comments::SingleThreadedComments, BytePos};
use super::*;
use crate::{test_parser, EsConfig, TsConfig};
fn program(src: &'static str) -> Program {
test_parser(src, Default::default(), |p| p.parse_program())
}
/// Assert that Parser.parse_program returns [Program::Module].
fn module(src: &'static str) -> Module {
program(src).expect_module()
}
/// Assert that Parser.parse_program returns [Program::Script].
fn script(src: &'static str) -> Script {
program(src).expect_script()
}
/// Assert that Parser.parse_program returns [Program::Module] and has errors.
#[track_caller]
fn assert_module_error(src: &'static str) -> Module {
test_parser(src, Default::default(), |p| {
let program = p.parse_program()?;
let errors = p.take_errors();
assert_ne!(errors, vec![]);
let module = program.expect_module();
Ok(module)
})
}
#[test]
fn parse_program_module_01() {
module("import 'foo';");
module("export const a = 1;");
}
#[test]
fn parse_program_script_01() {
script("let a = 5;");
script("function foo() {}");
script("const a = 00176;");
}
#[test]
fn parse_program_module_02() {
module(
"
function foo() {}
export default foo;
",
);
module(
"
export function foo() {}
export default foo;
",
);
}
#[test]
fn parse_program_module_error_01() {
assert_module_error(
"
const a = 01234;
export default a;
",
);
}
#[test]
fn issue_1813() {
test_parser(
"\\u{cccccccccsccccccQcXt[uc(~).const[uctor().const[uctor())tbr())",
Default::default(),
|p| {
p.parse_program().expect_err("should fail");
Ok(())
},
)
}
#[test]
fn parse_module_export_named_span() {
let m = module("export function foo() {}");
if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { span, .. })) = &m.body[0] {
assert_eq!(span.lo, BytePos(1));
} else {
panic!("expected ExportDecl");
}
}
#[test]
fn parse_module_export_default_fn_span() {
let m = module("export default function foo() {}");
if let ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
span, ..
})) = &m.body[0]
{
assert_eq!(span.lo, BytePos(1));
assert_eq!(span.hi, BytePos(33));
} else {
panic!("expected ExportDefaultDecl");
}
}
#[test]
fn parse_module_export_default_async_fn_span() {
let m = module("export default async function foo() {}");
if let ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
span, ..
})) = &m.body[0]
{
assert_eq!(span.lo, BytePos(1));
assert_eq!(span.hi, BytePos(39));
} else {
panic!("expected ExportDefaultDecl");
}
}
#[test]
fn parse_module_export_default_class_span() {
let m = module("export default class Foo {}");
if let ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
span, ..
})) = &m.body[0]
{
assert_eq!(span.lo, BytePos(1));
assert_eq!(span.hi, BytePos(28));
} else {
panic!("expected ExportDefaultDecl");
}
}
#[test]
fn issue_1878() {
// file with only comments should have the comments
// in the leading map instead of the trailing
{
let c = SingleThreadedComments::default();
let s = "
// test
";
let _ = super::test_parser_comment(&c, s, Syntax::Typescript(Default::default()), |p| {
p.parse_typescript_module()
});
let (leading, trailing) = c.take_all();
assert!(trailing.borrow().is_empty());
assert_eq!(leading.borrow().len(), 1);
assert!(leading.borrow().get(&BytePos(1)).is_some());
}
// file with shebang and comments should still work with the comments trailing
// the shebang
{
let c = SingleThreadedComments::default();
let s = "#!/foo/bar
// test
";
let _ = super::test_parser_comment(&c, s, Syntax::Typescript(Default::default()), |p| {
p.parse_typescript_module()
});
let (leading, trailing) = c.take_all();
assert!(leading.borrow().is_empty());
assert_eq!(trailing.borrow().len(), 1);
assert!(trailing.borrow().get(&BytePos(11)).is_some());
}
}
#[test]
fn issue_2264_1() {
let c = SingleThreadedComments::default();
let s = "
const t = <Switch>
// 1
/* 2 */
</Switch>
";
let _ = super::test_parser_comment(
&c,
s,
Syntax::Typescript(TsConfig {
tsx: true,
..Default::default()
}),
|p| p.parse_typescript_module(),
);
let (_leading, trailing) = c.take_all();
// assert!(leading.borrow().is_empty());
assert!(trailing.borrow().is_empty());
}
#[test]
fn issue_2264_2() {
let c = SingleThreadedComments::default();
let s = "
const t = <Switch>
// 1
/* 2 */
</Switch>
";
let _ = super::test_parser_comment(
&c,
s,
Syntax::Es(EsConfig {
jsx: true,
..Default::default()
}),
|p| p.parse_module(),
);
let (leading, trailing) = c.take_all();
assert!(leading.borrow().is_empty());
assert!(trailing.borrow().is_empty());
}
#[test]
fn issue_2264_3() {
let c = SingleThreadedComments::default();
let s = "const foo = <h1>/* no */{/* 1 */ bar /* 2 */}/* no */</h1>;";
let _ = super::test_parser_comment(
&c,
s,
Syntax::Typescript(TsConfig {
tsx: true,
..Default::default()
}),
|p| p.parse_typescript_module(),
);
let (leading, trailing) = c.take_all();
assert!(leading.borrow().is_empty());
assert_eq!(trailing.borrow().len(), 2);
assert_eq!(trailing.borrow().get(&BytePos(26)).unwrap().len(), 1);
assert_eq!(trailing.borrow().get(&BytePos(37)).unwrap().len(), 1);
}
#[test]
fn issue_2339_1() {
let c = SingleThreadedComments::default();
let s = "
const t = <T>() => {
// 1
/* 2 */
test;
};
";
let _ = super::test_parser_comment(
&c,
s,
Syntax::Typescript(TsConfig {
tsx: true,
..Default::default()
}),
|p| p.parse_typescript_module(),
);
let (leading, trailing) = c.take_all();
assert_eq!(leading.borrow().len(), 1);
assert_eq!(leading.borrow().get(&BytePos(80)).unwrap().len(), 2);
assert!(trailing.borrow().is_empty());
}
#[test]
fn issue_2853_1() {
test_parser("const a = \"\\0a\";", Default::default(), |p| {
let program = p.parse_program()?;
let errors = p.take_errors();
assert_eq!(errors, vec![]);
assert_eq!(errors, vec![]);
Ok(program)
});
}
#[test]
fn issue_2853_2() {
test_parser("const a = \"\u{0000}a\";", Default::default(), |p| {
let program = p.parse_program()?;
let errors = p.take_errors();
assert_eq!(errors, vec![]);
Ok(program)
});
}
#[test]
fn illegal_language_mode_directive1() {
test_parser(
r#"function f(a = 0) { "use strict"; }"#,
Default::default(),
|p| {
let program = p.parse_program()?;
let errors = p.take_errors();
assert_eq!(
errors,
vec![Error::new(
Span {
lo: BytePos(21),
hi: BytePos(34),
ctxt: swc_common::SyntaxContext::empty()
},
crate::parser::SyntaxError::IllegalLanguageModeDirective
)]
);
Ok(program)
},
);
}
#[test]
fn illegal_language_mode_directive2() {
test_parser(
r#"let f = (a = 0) => { "use strict"; }"#,
Default::default(),
|p| {
let program = p.parse_program()?;
let errors = p.take_errors();
assert_eq!(
errors,
vec![Error::new(
Span {
lo: BytePos(22),
hi: BytePos(35),
ctxt: swc_common::SyntaxContext::empty()
},
crate::parser::SyntaxError::IllegalLanguageModeDirective
)]
);
Ok(program)
},
);
}
#[test]
fn parse_non_strict_for_loop() {
script("for (var v1 = 1 in v3) {}");
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,338 @@
use swc_atoms::js_word;
use super::*;
use crate::token::Keyword;
impl Context {
pub(crate) fn is_reserved(self, word: &Word) -> bool {
match *word {
Word::Keyword(Keyword::Let) => self.strict,
Word::Keyword(Keyword::Await) => self.in_async || self.strict,
Word::Keyword(Keyword::Yield) => self.in_generator || self.strict,
Word::Null
| Word::True
| Word::False
| Word::Keyword(Keyword::Break)
| Word::Keyword(Keyword::Case)
| Word::Keyword(Keyword::Catch)
| Word::Keyword(Keyword::Continue)
| Word::Keyword(Keyword::Debugger)
| Word::Keyword(Keyword::Default_)
| Word::Keyword(Keyword::Do)
| Word::Keyword(Keyword::Export)
| Word::Keyword(Keyword::Else)
| Word::Keyword(Keyword::Finally)
| Word::Keyword(Keyword::For)
| Word::Keyword(Keyword::Function)
| Word::Keyword(Keyword::If)
| Word::Keyword(Keyword::Return)
| Word::Keyword(Keyword::Switch)
| Word::Keyword(Keyword::Throw)
| Word::Keyword(Keyword::Try)
| Word::Keyword(Keyword::Var)
| Word::Keyword(Keyword::Const)
| Word::Keyword(Keyword::While)
| Word::Keyword(Keyword::With)
| Word::Keyword(Keyword::New)
| Word::Keyword(Keyword::This)
| Word::Keyword(Keyword::Super)
| Word::Keyword(Keyword::Class)
| Word::Keyword(Keyword::Extends)
| Word::Keyword(Keyword::Import)
| Word::Keyword(Keyword::In)
| Word::Keyword(Keyword::InstanceOf)
| Word::Keyword(Keyword::TypeOf)
| Word::Keyword(Keyword::Void)
| Word::Keyword(Keyword::Delete) => true,
// Future reserved word
Word::Ident(js_word!("enum")) => true,
Word::Ident(js_word!("implements"))
| Word::Ident(js_word!("package"))
| Word::Ident(js_word!("protected"))
| Word::Ident(js_word!("interface"))
| Word::Ident(js_word!("private"))
| Word::Ident(js_word!("public"))
if self.strict =>
{
true
}
_ => false,
}
}
pub fn is_reserved_word(self, word: &JsWord) -> bool {
match *word {
js_word!("let") => self.strict,
// SyntaxError in the module only, not in the strict.
// ```JavaScript
// function foo() {
// "use strict";
// let await = 1;
// }
// ```
js_word!("await") => self.in_async || self.module,
js_word!("yield") => self.in_generator || self.strict,
js_word!("null")
| js_word!("true")
| js_word!("false")
| js_word!("break")
| js_word!("case")
| js_word!("catch")
| js_word!("continue")
| js_word!("debugger")
| js_word!("default")
| js_word!("do")
| js_word!("export")
| js_word!("else")
| js_word!("finally")
| js_word!("for")
| js_word!("function")
| js_word!("if")
| js_word!("return")
| js_word!("switch")
| js_word!("throw")
| js_word!("try")
| js_word!("var")
| js_word!("const")
| js_word!("while")
| js_word!("with")
| js_word!("new")
| js_word!("this")
| js_word!("super")
| js_word!("class")
| js_word!("extends")
| js_word!("import")
| js_word!("in")
| js_word!("instanceof")
| js_word!("typeof")
| js_word!("void")
| js_word!("delete") => true,
// Future reserved word
js_word!("enum") => true,
js_word!("implements")
| js_word!("package")
| js_word!("protected")
| js_word!("interface")
| js_word!("private")
| js_word!("public")
if self.strict =>
{
true
}
_ => false,
}
}
}
impl<I: Tokens> Parser<I> {
/// Original context is restored when returned guard is dropped.
pub(super) fn with_ctx(&mut self, ctx: Context) -> WithCtx<I> {
let orig_ctx = self.ctx();
self.set_ctx(ctx);
WithCtx {
orig_ctx,
inner: self,
}
}
/// Original state is restored when returned guard is dropped.
pub(super) fn with_state(&mut self, state: State) -> WithState<I> {
let orig_state = std::mem::replace(&mut self.state, state);
WithState {
orig_state,
inner: self,
}
}
pub(super) fn set_ctx(&mut self, ctx: Context) {
self.input.set_ctx(ctx);
}
pub(super) fn strict_mode(&mut self) -> WithCtx<I> {
let ctx = Context {
strict: true,
..self.ctx()
};
self.with_ctx(ctx)
}
/// Original context is restored when returned guard is dropped.
pub(super) fn in_type(&mut self) -> WithCtx<I> {
let ctx = Context {
in_type: true,
..self.ctx()
};
self.with_ctx(ctx)
}
/// Original context is restored when returned guard is dropped.
pub(super) fn include_in_expr(&mut self, include_in_expr: bool) -> WithCtx<I> {
let ctx = Context {
include_in_expr,
..self.ctx()
};
self.with_ctx(ctx)
}
/// Parse with given closure
#[inline(always)]
pub(super) fn parse_with<F, Ret>(&mut self, f: F) -> PResult<Ret>
where
F: FnOnce(&mut Self) -> PResult<Ret>,
{
f(self)
}
pub(super) fn syntax(&self) -> Syntax {
self.input.syntax()
}
}
pub trait ParseObject<Obj> {
type Prop;
fn make_object(
&mut self,
span: Span,
props: Vec<Self::Prop>,
trailing_comma: Option<Span>,
) -> PResult<Obj>;
fn parse_object_prop(&mut self) -> PResult<Self::Prop>;
}
pub struct WithState<'w, I: 'w + Tokens> {
inner: &'w mut Parser<I>,
orig_state: State,
}
impl<'w, I: Tokens> Deref for WithState<'w, I> {
type Target = Parser<I>;
fn deref(&self) -> &Parser<I> {
self.inner
}
}
impl<'w, I: Tokens> DerefMut for WithState<'w, I> {
fn deref_mut(&mut self) -> &mut Parser<I> {
self.inner
}
}
impl<'w, I: Tokens> Drop for WithState<'w, I> {
fn drop(&mut self) {
std::mem::swap(&mut self.inner.state, &mut self.orig_state);
}
}
pub struct WithCtx<'w, I: 'w + Tokens> {
inner: &'w mut Parser<I>,
orig_ctx: Context,
}
impl<'w, I: Tokens> Deref for WithCtx<'w, I> {
type Target = Parser<I>;
fn deref(&self) -> &Parser<I> {
self.inner
}
}
impl<'w, I: Tokens> DerefMut for WithCtx<'w, I> {
fn deref_mut(&mut self) -> &mut Parser<I> {
self.inner
}
}
impl<'w, I: Tokens> Drop for WithCtx<'w, I> {
fn drop(&mut self) {
self.inner.set_ctx(self.orig_ctx);
}
}
pub(super) trait ExprExt {
fn as_expr(&self) -> &Expr;
/// "IsValidSimpleAssignmentTarget" from spec.
fn is_valid_simple_assignment_target(&self, strict: bool) -> bool {
match self.as_expr() {
Expr::Ident(ident) => {
if strict && ident.is_reserved_in_strict_bind() {
return false;
}
true
}
Expr::This(..)
| Expr::Lit(..)
| Expr::Array(..)
| Expr::Object(..)
| Expr::Fn(..)
| Expr::Class(..)
| Expr::Tpl(..)
| Expr::TaggedTpl(..) => false,
Expr::Paren(ParenExpr { expr, .. }) => expr.is_valid_simple_assignment_target(strict),
Expr::Member(MemberExpr { obj, .. }) => match obj.as_ref() {
Expr::Member(..) => obj.is_valid_simple_assignment_target(strict),
Expr::OptChain(..) => false,
_ => true,
},
Expr::SuperProp(..) => true,
Expr::New(..) | Expr::Call(..) => false,
// TODO: Spec only mentions `new.target`
Expr::MetaProp(..) => false,
Expr::Update(..) => false,
Expr::Unary(..) | Expr::Await(..) => false,
Expr::Bin(..) => false,
Expr::Cond(..) => false,
Expr::Yield(..) | Expr::Arrow(..) | Expr::Assign(..) => false,
Expr::Seq(..) => false,
Expr::OptChain(..) => false,
// MemberExpression is valid assignment target
Expr::PrivateName(..) => false,
// jsx
Expr::JSXMember(..)
| Expr::JSXNamespacedName(..)
| Expr::JSXEmpty(..)
| Expr::JSXElement(..)
| Expr::JSXFragment(..) => false,
// typescript
Expr::TsNonNull(TsNonNullExpr { ref expr, .. })
| Expr::TsTypeAssertion(TsTypeAssertion { ref expr, .. })
| Expr::TsAs(TsAsExpr { ref expr, .. })
| Expr::TsInstantiation(TsInstantiation { ref expr, .. })
| Expr::TsSatisfies(TsSatisfiesExpr { ref expr, .. }) => {
expr.is_valid_simple_assignment_target(strict)
}
Expr::TsConstAssertion(..) => false,
Expr::Invalid(..) => false,
}
}
}
impl ExprExt for Box<Expr> {
fn as_expr(&self) -> &Expr {
self
}
}
impl ExprExt for Expr {
fn as_expr(&self) -> &Expr {
self
}
}

View file

@ -0,0 +1,674 @@
//! Ported from [babel/babylon][]
//!
//! [babel/babylon]:https://github.com/babel/babel/blob/2d378d076eb0c5fe63234a8b509886005c01d7ee/packages/babylon/src/tokenizer/types.js
use std::{
borrow::Cow,
fmt::{self, Debug, Display, Formatter},
};
use num_bigint::BigInt as BigIntValue;
use swc_atoms::{js_word, Atom, JsWord};
use swc_common::{Span, Spanned};
pub(crate) use swc_ecma_ast::AssignOp as AssignOpToken;
use swc_ecma_ast::BinaryOp;
pub(crate) use self::{AssignOpToken::*, BinOpToken::*, Keyword::*, Token::*};
use crate::{error::Error, lexer::LexResult};
#[derive(Clone, PartialEq)]
pub enum Token {
/// Identifier, "null", "true", "false".
///
/// Contains `null` and ``
Word(Word),
/// '=>'
Arrow,
/// '#'
Hash,
/// '@'
At,
/// '.'
Dot,
/// '...'
DotDotDot,
/// '!'
Bang,
/// '('
LParen,
/// ')'
RParen,
/// `[`
LBracket,
/// ']'
RBracket,
/// '{'
LBrace,
/// '}'
RBrace,
/// ';'
Semi,
/// ','
Comma,
/// '`'
BackQuote,
Template {
raw: Atom,
cooked: LexResult<Atom>,
},
/// ':'
Colon,
///
BinOp(BinOpToken),
///
AssignOp(AssignOpToken),
/// '${'
DollarLBrace,
/// '?'
QuestionMark,
/// `++`
PlusPlus,
/// `--`
MinusMinus,
/// `~`
Tilde,
/// String literal. Span of this token contains quote.
Str {
value: JsWord,
raw: Atom,
},
/// Regexp literal.
Regex(Atom, Atom),
/// TODO: Make Num as enum and separate decimal, binary, ..etc
Num {
value: f64,
raw: Atom,
},
BigInt {
value: Box<BigIntValue>,
raw: Atom,
},
JSXName {
name: JsWord,
},
JSXText {
raw: Atom,
},
JSXTagStart,
JSXTagEnd,
Shebang(Atom),
Error(Error),
}
impl Token {
pub(crate) const fn before_expr(&self) -> bool {
match self {
Self::Word(w) => w.before_expr(),
Self::BinOp(w) => w.before_expr(),
Self::Arrow
| Self::DotDotDot
| Self::Bang
| Self::LParen
| Self::LBrace
| Self::LBracket
| Self::Semi
| Self::Comma
| Self::Colon
| Self::AssignOp(..)
| Self::DollarLBrace
| Self::QuestionMark
| Self::PlusPlus
| Self::MinusMinus
| Self::Tilde
| Self::JSXText { .. } => true,
_ => false,
}
}
pub(crate) const fn starts_expr(&self) -> bool {
match self {
Self::Word(w) => w.starts_expr(),
Self::BinOp(w) => w.starts_expr(),
Self::Bang
| Self::LParen
| Self::LBrace
| Self::LBracket
| Self::BackQuote
| Self::DollarLBrace
| Self::PlusPlus
| Self::MinusMinus
| Self::Tilde
| Self::Str { .. }
| Self::Regex(..)
| Self::Num { .. }
| Self::BigInt { .. }
| Self::JSXTagStart => true,
_ => false,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum BinOpToken {
/// `==`
EqEq,
/// `!=`
NotEq,
/// `===`
EqEqEq,
/// `!==`
NotEqEq,
/// `<`
Lt,
/// `<=`
LtEq,
/// `>`
Gt,
/// `>=`
GtEq,
/// `<<`
LShift,
/// `>>`
RShift,
/// `>>>`
ZeroFillRShift,
/// `+`
Add,
/// `-`
Sub,
/// `*`
Mul,
/// `/`
Div,
/// `%`
Mod,
/// `|`
BitOr,
/// `^`
BitXor,
/// `&`
BitAnd,
// /// `in`
// #[kind(precedence = "7")]
// In,
// /// `instanceof`
// #[kind(precedence = "7")]
// InstanceOf,
/// `**`
Exp,
/// `||`
LogicalOr,
/// `&&`
LogicalAnd,
/// `??`
NullishCoalescing,
}
impl BinOpToken {
pub(crate) const fn starts_expr(&self) -> bool {
matches!(self, Self::Add | Self::Sub)
}
pub(crate) const fn before_expr(self) -> bool {
true
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TokenAndSpan {
pub token: Token,
/// Had a line break before this token?
pub had_line_break: bool,
pub span: Span,
}
impl Spanned for TokenAndSpan {
#[inline(always)]
fn span(&self) -> Span {
self.span
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum Word {
Keyword(Keyword),
Null,
True,
False,
Ident(JsWord),
}
impl Word {
pub(crate) const fn before_expr(&self) -> bool {
match self {
Word::Keyword(k) => k.before_expr(),
_ => false,
}
}
pub(crate) const fn starts_expr(&self) -> bool {
match self {
Word::Keyword(k) => k.starts_expr(),
_ => true,
}
}
}
impl From<JsWord> for Word {
fn from(i: JsWord) -> Self {
match i {
js_word!("null") => Word::Null,
js_word!("true") => Word::True,
js_word!("false") => Word::False,
js_word!("await") => Await.into(),
js_word!("break") => Break.into(),
js_word!("case") => Case.into(),
js_word!("catch") => Catch.into(),
js_word!("continue") => Continue.into(),
js_word!("debugger") => Debugger.into(),
js_word!("default") => Default_.into(),
js_word!("do") => Do.into(),
js_word!("export") => Export.into(),
js_word!("else") => Else.into(),
js_word!("finally") => Finally.into(),
js_word!("for") => For.into(),
js_word!("function") => Function.into(),
js_word!("if") => If.into(),
js_word!("return") => Return.into(),
js_word!("switch") => Switch.into(),
js_word!("throw") => Throw.into(),
js_word!("try") => Try.into(),
js_word!("var") => Var.into(),
js_word!("let") => Let.into(),
js_word!("const") => Const.into(),
js_word!("while") => While.into(),
js_word!("with") => With.into(),
js_word!("new") => New.into(),
js_word!("this") => This.into(),
js_word!("super") => Super.into(),
js_word!("class") => Class.into(),
js_word!("extends") => Extends.into(),
js_word!("import") => Import.into(),
js_word!("yield") => Yield.into(),
js_word!("in") => In.into(),
js_word!("instanceof") => InstanceOf.into(),
js_word!("typeof") => TypeOf.into(),
js_word!("void") => Void.into(),
js_word!("delete") => Delete.into(),
_ => Word::Ident(i),
}
}
}
impl From<Keyword> for Word {
fn from(kwd: Keyword) -> Self {
Word::Keyword(kwd)
}
}
impl From<Word> for JsWord {
fn from(w: Word) -> Self {
match w {
Word::Keyword(k) => match k {
Await => js_word!("await"),
Break => js_word!("break"),
Case => js_word!("case"),
Catch => js_word!("catch"),
Continue => js_word!("continue"),
Debugger => js_word!("debugger"),
Default_ => js_word!("default"),
Do => js_word!("do"),
Else => js_word!("else"),
Finally => js_word!("finally"),
For => js_word!("for"),
Function => js_word!("function"),
If => js_word!("if"),
Return => js_word!("return"),
Switch => js_word!("switch"),
Throw => js_word!("throw"),
Try => js_word!("try"),
Var => js_word!("var"),
Let => js_word!("let"),
Const => js_word!("const"),
While => js_word!("while"),
With => js_word!("with"),
New => js_word!("new"),
This => js_word!("this"),
Super => js_word!("super"),
Class => js_word!("class"),
Extends => js_word!("extends"),
Export => js_word!("export"),
Import => js_word!("import"),
Yield => js_word!("yield"),
In => js_word!("in"),
InstanceOf => js_word!("instanceof"),
TypeOf => js_word!("typeof"),
Void => js_word!("void"),
Delete => js_word!("delete"),
},
Word::Null => js_word!("null"),
Word::True => js_word!("true"),
Word::False => js_word!("false"),
Word::Ident(w) => w,
}
}
}
impl Debug for Word {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Word::Ident(ref s) => Display::fmt(s, f),
_ => {
let s: JsWord = self.clone().into();
Display::fmt(&s, f)
}
}
}
}
/// Keywords
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum Keyword {
/// Spec says this might be identifier.
Await,
Break,
Case,
Catch,
Continue,
Debugger,
Default_,
Do,
Else,
Finally,
For,
Function,
If,
Return,
Switch,
Throw,
Try,
Var,
Let,
Const,
While,
With,
New,
This,
Super,
Class,
Extends,
Export,
Import,
/// Spec says this might be identifier.
Yield,
In,
InstanceOf,
TypeOf,
Void,
Delete,
}
impl Keyword {
pub(crate) const fn before_expr(&self) -> bool {
matches!(
self,
Self::Await
| Self::Case
| Self::Default_
| Self::Do
| Self::Else
| Self::Return
| Self::Throw
| Self::New
| Self::Extends
| Self::Yield
| Self::In
| Self::InstanceOf
| Self::TypeOf
| Self::Void
| Self::Delete
)
}
pub(crate) const fn starts_expr(&self) -> bool {
matches!(
self,
Self::Await
| Self::Function
| Self::Throw
| Self::New
| Self::This
| Self::Super
| Self::Class
| Self::Import
| Self::Yield
| Self::TypeOf
| Self::Void
| Self::Delete
)
}
pub(crate) const fn into_js_word(self) -> JsWord {
match self {
Await => js_word!("await"),
Break => js_word!("break"),
Case => js_word!("case"),
Catch => js_word!("catch"),
Continue => js_word!("continue"),
Debugger => js_word!("debugger"),
Default_ => js_word!("default"),
Do => js_word!("do"),
Else => js_word!("else"),
Finally => js_word!("finally"),
For => js_word!("for"),
Function => js_word!("function"),
If => js_word!("if"),
Return => js_word!("return"),
Switch => js_word!("switch"),
Throw => js_word!("throw"),
Try => js_word!("try"),
Var => js_word!("var"),
Let => js_word!("let"),
Const => js_word!("const"),
While => js_word!("while"),
With => js_word!("with"),
New => js_word!("new"),
This => js_word!("this"),
Super => js_word!("super"),
Class => js_word!("class"),
Extends => js_word!("extends"),
Export => js_word!("export"),
Import => js_word!("import"),
Yield => js_word!("yield"),
In => js_word!("in"),
InstanceOf => js_word!("instanceof"),
TypeOf => js_word!("typeof"),
Void => js_word!("void"),
Delete => js_word!("delete"),
}
}
}
impl Debug for Keyword {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "keyword '{}'", self.into_js_word())?;
Ok(())
}
}
impl From<BinOpToken> for BinaryOp {
fn from(t: BinOpToken) -> Self {
use self::BinaryOp::*;
match t {
BinOpToken::EqEq => EqEq,
BinOpToken::NotEq => NotEq,
BinOpToken::EqEqEq => EqEqEq,
BinOpToken::NotEqEq => NotEqEq,
BinOpToken::Lt => Lt,
BinOpToken::LtEq => LtEq,
BinOpToken::Gt => Gt,
BinOpToken::GtEq => GtEq,
BinOpToken::LShift => LShift,
BinOpToken::RShift => RShift,
BinOpToken::ZeroFillRShift => ZeroFillRShift,
BinOpToken::Add => Add,
BinOpToken::Sub => Sub,
BinOpToken::Mul => Mul,
BinOpToken::Div => Div,
BinOpToken::Mod => Mod,
BinOpToken::BitOr => BitOr,
BinOpToken::BitXor => BitXor,
BinOpToken::BitAnd => BitAnd,
BinOpToken::LogicalOr => LogicalOr,
BinOpToken::LogicalAnd => LogicalAnd,
BinOpToken::Exp => Exp,
BinOpToken::NullishCoalescing => NullishCoalescing,
}
}
}
impl Token {
/// Returns true if `self` can follow keyword let.
///
/// e.g. `let a = xx;`, `let {a:{}} = 1`
pub(crate) fn follows_keyword_let(&self, _strict: bool) -> bool {
matches!(
*self,
crate::token::Token::Word(crate::token::Word::Keyword(crate::token::Keyword::Let))
| tok!('{')
| tok!('[')
| Word(Word::Ident(..))
| tok!("yield")
| tok!("await")
)
}
}
impl Word {
pub(crate) fn cow(&self) -> Cow<JsWord> {
match *self {
Word::Keyword(k) => Cow::Owned(k.into_js_word()),
Word::Ident(ref w) => Cow::Borrowed(w),
Word::False => Cow::Owned(js_word!("false")),
Word::True => Cow::Owned(js_word!("true")),
Word::Null => Cow::Owned(js_word!("null")),
}
}
}
impl Debug for Token {
/// This method is called only in the case of parsing failure.
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Token::Word(w) => write!(f, "{:?}", w)?,
Arrow => write!(f, "=>")?,
Hash => write!(f, "#")?,
At => write!(f, "@")?,
Dot => write!(f, ".")?,
DotDotDot => write!(f, "...")?,
Bang => write!(f, "!")?,
LParen => write!(f, "(")?,
RParen => write!(f, ")")?,
LBracket => write!(f, "[")?,
RBracket => write!(f, "]")?,
LBrace => write!(f, "{{")?,
RBrace => write!(f, "}}")?,
Semi => write!(f, ";")?,
Comma => write!(f, ",")?,
BackQuote => write!(f, "`")?,
Template { raw, .. } => write!(f, "template token ({})", raw)?,
Colon => write!(f, ":")?,
BinOp(op) => write!(f, "{}", BinaryOp::from(*op).as_str())?,
AssignOp(op) => write!(f, "{}", op.as_str())?,
DollarLBrace => write!(f, "${{")?,
QuestionMark => write!(f, "?")?,
PlusPlus => write!(f, "++")?,
MinusMinus => write!(f, "--")?,
Tilde => write!(f, "~")?,
Str { value, raw } => write!(f, "string literal ({}, {})", value, raw)?,
Regex(exp, flags) => write!(f, "regexp literal ({}, {})", exp, flags)?,
Num { value, raw, .. } => write!(f, "numeric literal ({}, {})", value, raw)?,
BigInt { value, raw } => write!(f, "bigint literal ({}, {})", value, raw)?,
JSXName { name } => write!(f, "jsx name ({})", name)?,
JSXText { raw } => write!(f, "jsx text ({})", raw)?,
JSXTagStart => write!(f, "< (jsx tag start)")?,
JSXTagEnd => write!(f, "> (jsx tag end)")?,
Shebang(_) => write!(f, "#!")?,
Token::Error(e) => write!(f, "<lexing error: {:?}>", e)?,
}
Ok(())
}
}