jsx support (#100)

swc_ecma_parser:
 - implement parser for jsx

swc_ecma_transforms:
 - implement react::jsx transform

swc_ecma_codegen:
 - implement code generator for jsx
This commit is contained in:
강동윤 2018-12-30 11:57:27 +09:00 committed by GitHub
parent cb59cd63c6
commit 603b83291d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
451 changed files with 10112 additions and 653 deletions

View File

@ -24,6 +24,11 @@ yarn add --dev swc
# Features
## Parsers
- [x] es2019
- [x] jsx
- [ ] typescript
## Transforms
New generation javascript to old-days javascript.
@ -72,6 +77,9 @@ New generation javascript to old-days javascript.
- [ ] Using symbol as a key
- [ ] optional-catch-binding
- [ ] unicode-property-regex
- react
- [x] jsx
# Performance

View File

@ -1,5 +1,5 @@
#!/bin/bash
set -eu
cat words.txt | sort > words_sorted.txt
cat words.txt | uniq | sort > words_sorted.txt
mv words_sorted.txt words.txt

View File

@ -2,14 +2,15 @@ Infinity
NODE_ENV
NaN
Object
React
RegExp
_extends
_toConsumableArray
abstract
apply
as
async
await
await
break
call
case
@ -19,6 +20,8 @@ concat
const
constructor
continue
createClass
createReactClass
debugger
default
delete
@ -51,10 +54,10 @@ private
process
protected
public
readonly
return
set
static
static
super
switch
target

View File

@ -57,6 +57,19 @@ where
}
}
impl<A, B> Spanned for ::either::Either<A, B>
where
A: Spanned,
B: Spanned,
{
fn span(&self) -> Span {
match *self {
::either::Either::Left(ref n) => n.span(),
::either::Either::Right(ref n) => n.span(),
}
}
}
impl<F> FoldWith<F> for Span {
/// No op as span does not have any child.
fn fold_children(self, _: &mut F) -> Span {

View File

@ -6,9 +6,10 @@ license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_ast/"
description = "Ecmascript ast."
edition = "2018"
[dependencies]
swc_atoms = { version = "0.1", path ="../../atoms" }
swc_common = { version = "0.1", path ="../../common" }
enum_kind = { version = "0.1", path ="../../macros/enum_kind" }
string_enum = { version = "0.1", path ="../../macros/string_enum" }
string_enum = { version = "0.1", path ="../../macros/string_enum" }

View File

@ -1,5 +1,6 @@
use super::{
AssignOp, BinaryOp, BlockStmt, Class, Function, Ident, Lit, Pat, Prop, Str, UnaryOp, UpdateOp,
AssignOp, BinaryOp, BlockStmt, Class, Function, Ident, JSXElement, JSXEmptyExpr, JSXFragment,
JSXMemberExpr, JSXNamespacedName, Lit, Pat, Prop, Str, UnaryOp, UpdateOp,
};
use swc_common::{ast_node, Fold, Span, Spanned};
@ -62,6 +63,12 @@ pub enum Expr {
Await(AwaitExpr),
Paren(ParenExpr),
JSXMebmer(JSXMemberExpr),
JSXNamespacedName(JSXNamespacedName),
JSXEmpty(JSXEmptyExpr),
JSXElement(JSXElement),
JSXFragment(JSXFragment),
}
#[ast_node]
@ -172,12 +179,14 @@ pub struct CallExpr {
pub callee: ExprOrSuper,
pub args: Vec<ExprOrSpread>,
}
#[ast_node]
pub struct NewExpr {
pub span: Span,
pub callee: Box<Expr>,
pub args: Option<(Vec<ExprOrSpread>)>,
}
#[ast_node]
pub struct SeqExpr {
/// TODO: Calculate

147
ecmascript/ast/src/jsx.rs Normal file
View File

@ -0,0 +1,147 @@
use super::{Expr, Ident, Lit, SpreadElement};
use swc_atoms::JsWord;
use swc_common::{ast_node, Span};
/// Used for `obj` property of `JSXMemberExpr`.
#[ast_node]
pub enum JSXObject {
JSXMemberExpr(Box<JSXMemberExpr>),
Ident(Ident),
}
#[ast_node]
pub struct JSXMemberExpr {
#[span(lo)]
pub obj: JSXObject,
#[span(hi)]
pub prop: Ident,
}
/// XML-based namespace syntax:
#[ast_node]
pub struct JSXNamespacedName {
#[span(lo)]
pub ns: Ident,
#[span(hi)]
pub name: Ident,
}
#[ast_node]
#[derive(Copy)]
pub struct JSXEmptyExpr {
pub span: Span,
}
#[ast_node]
pub struct JSXExprContainer {
#[span]
pub expr: JSXExpr,
}
#[ast_node]
#[allow(variant_size_differences)]
pub enum JSXExpr {
Expr(Box<Expr>),
JSXEmptyExpr(JSXEmptyExpr),
}
#[ast_node]
pub struct JSXSpreadChild {
#[span]
pub expr: Box<Expr>,
}
#[ast_node]
pub enum JSXElementName {
Ident(Ident),
JSXMemberExpr(JSXMemberExpr),
JSXNamespacedName(JSXNamespacedName),
}
#[ast_node]
pub struct JSXOpeningElement {
pub name: JSXElementName,
pub span: Span,
pub attrs: Vec<JSXAttrOrSpread>,
pub self_closing: bool,
}
#[ast_node]
#[allow(variant_size_differences)]
pub enum JSXAttrOrSpread {
JSXAttr(JSXAttr),
SpreadElement(SpreadElement),
}
#[ast_node]
pub struct JSXClosingElement {
pub span: Span,
pub name: JSXElementName,
}
#[ast_node]
pub struct JSXAttr {
pub span: Span,
pub name: JSXAttrName,
/// Babel uses Expr instead of JSXAttrValue
pub value: Option<Box<Expr>>,
}
#[ast_node]
pub enum JSXAttrName {
Ident(Ident),
JSXNamespacedName(JSXNamespacedName),
}
#[ast_node]
pub enum JSXAttrValue {
Lit(Lit),
JSXExprContainer(JSXExprContainer),
JSXElement(Box<JSXElement>),
JSXFragment(JSXFragment),
}
#[ast_node]
pub struct JSXText {
pub span: Span,
pub value: JsWord,
pub raw: JsWord,
}
#[ast_node]
pub struct JSXElement {
pub span: Span,
pub opening: JSXOpeningElement,
pub children: Vec<JSXElementChild>,
pub closing: Option<JSXClosingElement>,
}
#[ast_node]
pub enum JSXElementChild {
JSXText(JSXText),
JSXExprContainer(JSXExprContainer),
JSXSpreadChild(JSXSpreadChild),
JSXElement(Box<JSXElement>),
JSXFragment(JSXFragment),
}
#[ast_node]
pub struct JSXFragment {
pub span: Span,
pub opening: JSXOpeningFragment,
pub children: Vec<JSXElementChild>,
pub closing: JSXClosingFragment,
}
#[ast_node]
#[derive(Copy)]
pub struct JSXOpeningFragment {
pub span: Span,
}
#[ast_node]
#[derive(Copy)]
pub struct JSXClosingFragment {
pub span: Span,
}

View File

@ -8,7 +8,6 @@
#![deny(unreachable_pub)]
#![deny(variant_size_differences)]
#[macro_use]
extern crate enum_kind;
#[macro_use]
extern crate string_enum;
@ -25,6 +24,12 @@ pub use self::{
TplElement, TplLit, UnaryExpr, UpdateExpr, YieldExpr,
},
function::Function,
jsx::{
JSXAttr, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXClosingElement, JSXClosingFragment,
JSXElement, JSXElementChild, JSXElementName, JSXEmptyExpr, JSXExpr, JSXExprContainer,
JSXFragment, JSXMemberExpr, JSXNamespacedName, JSXObject, JSXOpeningElement,
JSXOpeningFragment, JSXSpreadChild, JSXText,
},
keywords::IdentExt,
lit::{Bool, Lit, Null, Number, Regex, RegexFlags, Str},
module::{Module, ModuleItem},
@ -51,6 +56,7 @@ mod class;
mod decl;
mod expr;
mod function;
mod jsx;
mod keywords;
mod lit;
mod macros;

View File

@ -1,3 +1,4 @@
use super::JSXText;
use std::fmt::{self, Display, Formatter};
use swc_atoms::JsWord;
use swc_common::{ast_node, Span};
@ -9,6 +10,7 @@ pub enum Lit {
Null(Null),
Num(Number),
Regex(Regex),
JSXText(JSXText),
}
#[ast_node]

View File

@ -11,7 +11,7 @@ extern crate testing;
use sourcemap::SourceMapBuilder;
use swc_common::FileName;
use swc_ecma_codegen::Emitter;
use swc_ecma_parser::{Parser, Session, SourceFileInput};
use swc_ecma_parser::{Parser, Session, SourceFileInput, Syntax};
use test::Bencher;
const SOURCE: &str = r#"
@ -96,7 +96,7 @@ fn emit_colors(b: &mut Bencher) {
cfg: Default::default(),
};
let fm = cm.new_source_file(FileName::Anon(0), SOURCE.into());
let mut parser = Parser::new(session, SourceFileInput::from(&*fm));
let mut parser = Parser::new(session, Syntax::Es2019, SourceFileInput::from(&*fm));
let module = parser.parse_module().unwrap();
b.iter(|| {

View File

@ -80,6 +80,8 @@ fn (&mut self, node: Node) -> Result;
block
};
// Emitter methods return Result<_, _>
// We inject this to avoid writing Ok(()) every time.
#[allow(unreachable_code)]
{
return Ok(());

View File

@ -0,0 +1,166 @@
use super::{Emitter, Result};
use crate::list::ListFormat;
use swc_common::Spanned;
use swc_ecma_ast::*;
use swc_ecma_codegen_macros::emitter;
impl<'a> Emitter<'a> {
#[emitter]
pub fn emit_jsx_element(&mut self, node: &JSXElement) -> Result {
emit!(node.opening);
self.emit_list(
node.span(),
Some(&node.children),
ListFormat::JsxElementOrFragmentChildren,
)?;
if let Some(ref closing) = node.closing {
emit!(closing)
}
}
#[emitter]
pub fn emit_jsx_opening_element(&mut self, node: &JSXOpeningElement) -> Result {
punct!("<");
emit!(node.name);
self.emit_list(
node.span(),
Some(&node.attrs),
ListFormat::JsxElementAttributes,
)?;
if node.self_closing {
punct!("/");
}
punct!(">");
}
#[emitter]
pub fn emit_jsx_element_name(&mut self, node: &JSXElementName) -> Result {
match *node {
JSXElementName::Ident(ref n) => emit!(n),
JSXElementName::JSXMemberExpr(ref n) => emit!(n),
JSXElementName::JSXNamespacedName(ref n) => emit!(n),
}
}
#[emitter]
pub fn emit_jsx_attr(&mut self, node: &JSXAttr) -> Result {
emit!(node.name);
if let Some(ref value) = node.value {
punct!(" ");
emit!(value);
}
}
#[emitter]
pub fn emit_jsx_attr_name(&mut self, node: &JSXAttrName) -> Result {
match *node {
JSXAttrName::Ident(ref n) => emit!(n),
JSXAttrName::JSXNamespacedName(ref n) => emit!(n),
}
}
#[emitter]
pub fn emit_jsx_attr_or_spread(&mut self, node: &JSXAttrOrSpread) -> Result {
match *node {
JSXAttrOrSpread::JSXAttr(ref n) => emit!(n),
JSXAttrOrSpread::SpreadElement(ref n) => emit!(n),
}
}
#[emitter]
pub fn emit_jsx_element_child(&mut self, node: &JSXElementChild) -> Result {
match *node {
JSXElementChild::JSXElement(ref n) => emit!(n),
JSXElementChild::JSXExprContainer(ref n) => emit!(n),
JSXElementChild::JSXFragment(ref n) => emit!(n),
JSXElementChild::JSXSpreadChild(ref n) => emit!(n),
JSXElementChild::JSXText(ref n) => emit!(n),
}
}
#[emitter]
pub fn emit_jsx_spread_child(&mut self, node: &JSXSpreadChild) -> Result {
punct!("{");
punct!("...");
emit!(node.expr);
punct!("}");
}
#[emitter]
pub fn emit_jsx_expr_container(&mut self, node: &JSXExprContainer) -> Result {
punct!("{");
emit!(node.expr);
punct!("}");
}
#[emitter]
pub fn emit_jsx_expr(&mut self, node: &JSXExpr) -> Result {
match *node {
JSXExpr::Expr(ref n) => emit!(n),
JSXExpr::JSXEmptyExpr(ref n) => emit!(n),
}
}
#[emitter]
pub fn emit_jsx_closing_element(&mut self, node: &JSXClosingElement) -> Result {
punct!("</");
emit!(node.name);
punct!(">");
}
#[emitter]
pub fn emit_jsx_fragment(&mut self, node: &JSXFragment) -> Result {
emit!(node.opening);
self.emit_list(
node.span(),
Some(&node.children),
ListFormat::JsxElementOrFragmentChildren,
)?;
emit!(node.closing);
}
#[emitter]
pub fn emit_jsx_opening_fragment(&mut self, node: &JSXOpeningFragment) -> Result {
punct!("<>")
}
#[emitter]
pub fn emit_jsx_closing_fragment(&mut self, node: &JSXClosingFragment) -> Result {
punct!("</>")
}
#[emitter]
pub fn emit_jsx_namespaced_name(&mut self, node: &JSXNamespacedName) -> Result {
emit!(node.ns);
punct!(":");
emit!(node.name);
}
#[emitter]
pub fn emit_jsx_empty_expr(&mut self, node: &JSXEmptyExpr) -> Result {}
#[emitter]
pub fn emit_jsx_text(&mut self, node: &JSXText) -> Result {
self.emit_js_word(node.span(), &node.value)?;
}
#[emitter]
pub fn emit_jsx_member_expr(&mut self, node: &JSXMemberExpr) -> Result {
emit!(node.obj);
punct!(".");
emit!(node.prop);
}
#[emitter]
pub fn emit_jsx_object(&mut self, node: &JSXObject) -> Result {
match *node {
JSXObject::Ident(ref n) => emit!(n),
JSXObject::JSXMemberExpr(ref n) => emit!(n),
}
}
}

View File

@ -32,6 +32,7 @@ pub mod macros;
mod comments;
mod config;
mod decl;
mod jsx;
pub mod list;
#[cfg(test)]
mod tests;
@ -259,6 +260,7 @@ impl<'a> Emitter<'a> {
self.emit_js_word(flags.span, &flags.value)?;
}
}
Lit::JSXText(ref n) => emit!(n),
}
}
@ -372,6 +374,12 @@ impl<'a> Emitter<'a> {
Expr::Unary(ref n) => emit!(n),
Expr::Update(ref n) => emit!(n),
Expr::Yield(ref n) => emit!(n),
Expr::JSXMebmer(ref n) => emit!(n),
Expr::JSXNamespacedName(ref n) => emit!(n),
Expr::JSXEmpty(ref n) => emit!(n),
Expr::JSXElement(ref n) => emit!(n),
Expr::JSXFragment(ref n) => emit!(n),
}
}

View File

@ -1,7 +1,7 @@
extern crate swc_ecma_parser;
extern crate testing;
use self::{
swc_ecma_parser::{PResult, Parser, Session, SourceFileInput},
swc_ecma_parser::{PResult, Parser, Session, SourceFileInput, Syntax},
testing::NormalizedOutput,
};
use super::*;
@ -83,6 +83,7 @@ fn test_from_to(from: &str, to: &str) {
handler: &handler,
cfg: Default::default(),
},
Syntax::Es2019,
(&*src).into(),
));

View File

@ -183,12 +183,19 @@ impl StartsWithAlphaNum for Expr {
_ => false,
},
// TODO: Support `v => {}`
// TODO(kdy1): Support `v => {}`
Expr::Arrow(ArrowExpr { .. }) => false,
Expr::Tpl(_) | Expr::Update(_) | Expr::Array(_) | Expr::Object(_) | Expr::Paren(_) => {
false
}
// it's empty
Expr::JSXEmpty(..) => false,
// start with `<`
Expr::JSXFragment(..) | Expr::JSXElement(..) => false,
Expr::JSXNamespacedName(..) => true,
Expr::JSXMebmer(..) => true,
}
}
}

View File

@ -19,7 +19,7 @@ use std::{
use swc_common::{sync::Lrc, Fold, FoldWith, Span};
use swc_ecma_ast::*;
use swc_ecma_codegen::Emitter;
use swc_ecma_parser::{Parser, Session, SourceFileInput};
use swc_ecma_parser::{Parser, Session, SourceFileInput, Syntax};
use test::{test_main, Options, ShouldPanic::No, TestDesc, TestDescAndFn, TestFn, TestName};
use testing::NormalizedOutput;
@ -152,6 +152,7 @@ fn error_tests(tests: &mut Vec<TestDescAndFn>) -> Result<(), io::Error> {
handler: &handler,
cfg: Default::default(),
},
Syntax::Es2019,
(&*src).into(),
);

View File

@ -9,7 +9,7 @@ extern crate testing;
extern crate walkdir;
use self::{
parser::SourceFileInput,
parser::{SourceFileInput, Syntax},
test::{test_main, Options, ShouldPanic::No, TestDesc, TestDescAndFn, TestFn, TestName},
};
use std::{
@ -95,7 +95,8 @@ fn add_golden_tests(tests: &mut Vec<TestDescAndFn>) -> Result<(), io::Error> {
cfg: Default::default(),
handler: &handler,
};
parser::Parser::new(session, SourceFileInput::from(&*fm)).parse_module()?
parser::Parser::new(session, Syntax::Es2019, SourceFileInput::from(&*fm))
.parse_module()?
};
lints::lint_all(&handler, &module);

View File

@ -17,6 +17,10 @@ unicode-xid = "0.1"
log = { version = "0.4", features = ["release_max_level_debug"] }
either = { version = "1.4" }
smallvec = "0.6.7"
lazy_static = "1"
regex = "1"
[dev-dependencies]
testing = { version = "0.1", path ="../../testing" }
testing = { version = "0.1", path ="../../testing" }
env_logger = "0.6.0"
walkdir = "2"

View File

@ -6,7 +6,7 @@ extern crate test;
extern crate testing;
use swc_common::FileName;
use swc_ecma_parser::{Parser, Session, SourceFileInput};
use swc_ecma_parser::{Parser, Session, SourceFileInput, Syntax};
use test::Bencher;
#[bench]
@ -62,7 +62,7 @@ fn bench_module(b: &mut Bencher, src: &'static str) {
b.iter(|| {
test::black_box({
let mut parser = Parser::new(session, SourceFileInput::from(&*fm));
let mut parser = Parser::new(session, Syntax::Es2019, SourceFileInput::from(&*fm));
let _ = parser.parse_module();
})
});

View File

@ -1,4 +1,5 @@
use self::SyntaxError::*;
use crate::token::Token;
use std::{
borrow::Cow,
fmt::{self, Debug, Formatter},
@ -8,7 +9,6 @@ use swc_common::{
errors::{DiagnosticBuilder, Handler},
Span,
};
use token::Token;
#[derive(Copy, Clone)]
pub(crate) struct Eof<'a> {
@ -87,8 +87,12 @@ pub(crate) enum SyntaxError {
LineBreakBeforeArrow,
/// Unexpected token
Unexpected,
Expected(&'static Token),
Unexpected {
got: String,
},
ReservedWordInImport,
AssignProperty,
Expected(&'static Token, String),
ExpectedSemiForExprStmt {
expr: Span,
},
@ -122,6 +126,14 @@ pub(crate) enum SyntaxError {
YieldParamInGen,
AwaitForStmt,
UnterminatedJSXContents,
EmptyJSXAttr,
InvalidJSXValue,
JSXExpectedClosingTagForLtGt,
JSXExpectedClosingTag {
tag: JsWord,
},
}
impl<'a> From<ErrorToDiag<'a>> for Error {
@ -180,8 +192,11 @@ impl<'a> From<ErrorToDiag<'a>> for DiagnosticBuilder<'a> {
UnaryInExp { .. } => "** cannot be applied to unary expression".into(),
LineBreakInThrow => "LineBreak cannot follow 'throw'".into(),
LineBreakBeforeArrow => "Unexpected line break between arrow head and arrow".into(),
Unexpected => "Unexpected token".into(),
Expected(token) => format!("Expected {:?}", token).into(),
Unexpected { ref got } => format!("Unexpected token {}", got).into(),
ReservedWordInImport => "cannot import as reserved word".into(),
AssignProperty => "assignment property is invalid syntax".into(),
Expected(token, ref got) => format!("Expected {:?}, got {}", token, got).into(),
ExpectedSemiForExprStmt { .. } => "Expected ';', '}' or <eof>".into(),
AwaitStar => "await* has been removed from the async functions proposal. Use \
@ -217,6 +232,16 @@ impl<'a> From<ErrorToDiag<'a>> for DiagnosticBuilder<'a> {
LabelledGenerator => "Generator cannot be labelled".into(),
YieldParamInGen => "'yield' cannot be used as a parameter within generator".into(),
AwaitForStmt => "for await syntax is valid only for for-of statement".into(),
UnterminatedJSXContents => "Unterminated JSX contents".into(),
EmptyJSXAttr => "JSX attributes must only be assigned a non-empty expression".into(),
InvalidJSXValue => {
"JSX value should be either an expression or a quoted JSX text".into()
}
JSXExpectedClosingTagForLtGt => "Expected corresponding JSX closing tag for <>".into(),
JSXExpectedClosingTag { ref tag } => {
format!("Expected corresponding JSX closing tag for <{}>", tag).into()
}
};
let d = e.handler.error(&msg).span(e.span);

View File

@ -6,6 +6,7 @@ pub struct SourceFileInput<'a> {
fm: &'a SourceFile,
start_pos: BytePos,
last_pos: BytePos,
orig: &'a str,
iter: str::CharIndices<'a>,
}
@ -19,6 +20,7 @@ impl<'a> From<&'a SourceFile> for SourceFileInput<'a> {
SourceFileInput {
start_pos: fm.start_pos,
last_pos: fm.start_pos,
orig: src,
iter: src.char_indices(),
fm,
}
@ -46,7 +48,7 @@ impl<'a> Input for SourceFileInput<'a> {
}
}
fn current(&mut self) -> Option<char> {
fn cur(&mut self) -> Option<char> {
self.iter.clone().nth(0).map(|i| i.1)
}
@ -58,6 +60,22 @@ impl<'a> Input for SourceFileInput<'a> {
self.iter.clone().nth(2).map(|i| i.1)
}
fn slice(&mut self, start: BytePos, end: BytePos) -> &str {
assert!(start <= end, "Cannot slice {:?}..{:?}", start, end);
let s = self.orig;
let start_idx = (start - self.fm.start_pos).0 as usize;
let end_idx = (end - self.fm.start_pos).0 as usize;
let ret = &s[start_idx..end_idx];
self.iter = s[end_idx..].char_indices();
self.last_pos = end;
self.start_pos = end;
ret
}
fn uncons_while<F>(&mut self, mut pred: F) -> &str
where
F: FnMut(char) -> bool,
@ -80,10 +98,20 @@ impl<'a> Input for SourceFileInput<'a> {
ret
}
fn reset_to(&mut self, to: BytePos) {
let orig = self.orig;
let idx = (to - self.fm.start_pos).0 as usize;
let s = &orig[idx..];
self.iter = s.char_indices();
self.start_pos = to;
self.last_pos = to;
}
}
pub trait Input {
fn current(&mut self) -> Option<char>;
fn cur(&mut self) -> Option<char>;
fn peek(&mut self) -> Option<char>;
fn peek_ahead(&mut self) -> Option<char>;
fn bump(&mut self);
@ -92,11 +120,15 @@ pub trait Input {
fn last_pos(&self) -> BytePos;
fn slice(&mut self, start: BytePos, end: BytePos) -> &str;
/// Takes items from stream, testing each one with predicate. returns the
/// range of items which passed predicate.
fn uncons_while<F>(&mut self, f: F) -> &str
where
F: FnMut(char) -> bool;
fn reset_to(&mut self, to: BytePos);
}
#[cfg(test)]
@ -104,9 +136,44 @@ mod tests {
use super::*;
use crate::lexer::util::CharExt;
#[test]
fn src_input_slice_1() {
let _ = crate::with_test_sess("foo/d", |_, mut i| {
assert_eq!(i.slice(BytePos(0), BytePos(1)), "f");
assert_eq!(i.last_pos, BytePos(1));
assert_eq!(i.start_pos, BytePos(1));
assert_eq!(i.cur(), Some('o'));
assert_eq!(i.slice(BytePos(1), BytePos(3)), "oo");
assert_eq!(i.slice(BytePos(0), BytePos(3)), "foo");
assert_eq!(i.last_pos, BytePos(3));
assert_eq!(i.start_pos, BytePos(3));
assert_eq!(i.cur(), Some('/'));
Ok(())
});
}
#[test]
fn src_input_reset_to_1() {
let _ = crate::with_test_sess("foad", |_, mut i| {
assert_eq!(i.slice(BytePos(0), BytePos(2)), "fo");
assert_eq!(i.last_pos, BytePos(2));
assert_eq!(i.start_pos, BytePos(2));
assert_eq!(i.cur(), Some('a'));
i.reset_to(BytePos(0));
assert_eq!(i.cur(), Some('f'));
assert_eq!(i.last_pos, BytePos(0));
assert_eq!(i.start_pos, BytePos(0));
Ok(())
});
}
#[test]
fn src_input_smoke_01() {
let _ = ::with_test_sess("foo/d", |_, mut i| {
let _ = crate::with_test_sess("foo/d", |_, mut i| {
assert_eq!(i.cur_pos(), BytePos(0));
assert_eq!(i.last_pos, BytePos(0));
assert_eq!(i.start_pos, BytePos(0));
@ -115,22 +182,22 @@ mod tests {
// assert_eq!(i.cur_pos(), BytePos(4));
assert_eq!(i.last_pos, BytePos(3));
assert_eq!(i.start_pos, BytePos(3));
assert_eq!(i.current(), Some('/'));
assert_eq!(i.cur(), Some('/'));
i.bump();
assert_eq!(i.last_pos, BytePos(4));
assert_eq!(i.current(), Some('d'));
assert_eq!(i.cur(), Some('d'));
i.bump();
assert_eq!(i.last_pos, BytePos(5));
assert_eq!(i.current(), None);
assert_eq!(i.cur(), None);
Ok(())
});
}
#[test]
fn src_input_smoke_02() {
let _ = ::with_test_sess("℘℘/℘℘", |_, mut i| {
let _ = crate::with_test_sess("℘℘/℘℘", |_, mut i| {
assert_eq!(i.iter.as_str(), "℘℘/℘℘");
assert_eq!(i.cur_pos(), BytePos(0));
assert_eq!(i.last_pos, BytePos(0));
@ -140,7 +207,7 @@ mod tests {
assert_eq!(i.iter.as_str(), "/℘℘");
assert_eq!(i.last_pos, BytePos(6));
assert_eq!(i.start_pos, BytePos(6));
assert_eq!(i.current(), Some('/'));
assert_eq!(i.cur(), Some('/'));
i.bump();
assert_eq!(i.last_pos, BytePos(7));
assert_eq!(i.start_pos, BytePos(6));

View File

@ -0,0 +1,475 @@
use super::*;
use either::Either;
use regex::Regex;
impl<'a, I: Input> Lexer<'a, I> {
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 cur_pos == self.state.start {
if cur == '<' && self.state.is_expr_allowed {
self.input.bump();
return Ok(Token::JSXTagStart).map(Some);
}
return self.read_token();
}
out.push_str(self.input.slice(chunk_start, cur_pos));
return Ok(Token::JSXText { raw: out.into() }).map(Some);
}
'&' => {
out.push_str(self.input.slice(chunk_start, cur_pos));
out.push(self.read_jsx_entity()?);
chunk_start = self.input.cur_pos();
}
_ => {
if cur.is_line_break() {
out.push_str(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 {
self.input.bump()
}
}
}
}
}
pub(super) fn read_jsx_entity(&mut self) -> LexResult<char> {
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)
}
lazy_static! {
static ref HEX_NUMBER: Regex = Regex::new(r"^[\da-fA-F]+$").unwrap();
}
lazy_static! {
static ref DECIMAL_NUMBER: Regex = Regex::new(r"^\d+$").unwrap();
}
let mut s = String::new();
let c = self.input.cur();
debug_assert_eq!(c, 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,
};
self.input.bump();
if c == ';' {
if s.starts_with('#') {
if s[1..].starts_with('x') {
if HEX_NUMBER.is_match(&s[2..]) {
return from_code(&s[2..], 16);
}
} else {
if DECIMAL_NUMBER.is_match(&s[1..]) {
return from_code(&s[1..], 10);
}
}
} else {
if let Some(entity) = xhtml(&s) {
return Ok(entity);
}
}
break;
}
s.push(c)
}
self.input.reset_to(start_pos);
Ok('&')
}
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();
self.input.bump();
let out = if ch == '\r' && self.input.cur() == 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;
return Ok(out);
}
pub(super) fn read_jsx_str(&mut self, quote: char) -> LexResult<Token> {
debug_assert!(self.syntax.jsx());
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.error(start, SyntaxError::UnterminatedStrLit)?
}
};
let cur_pos = self.input.cur_pos();
if ch == quote {
break;
}
if ch == '&' {
out.push_str(self.input.slice(chunk_start, cur_pos));
out.push(self.read_jsx_entity()?);
chunk_start = self.input.cur_pos();
} else if ch.is_line_break() {
out.push_str(self.input.slice(chunk_start, cur_pos));
match self.read_jsx_new_line(false)? {
Either::Left(s) => out.push_str(s),
Either::Right(c) => out.push(c),
}
chunk_start = cur_pos;
} else {
self.input.bump();
}
}
let cur_pos = self.input.cur_pos();
out.push_str(self.input.slice(chunk_start, cur_pos));
self.input.bump();
return Ok(Token::Str {
value: out.into(),
has_escape: false,
});
}
/// 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 cur_pos = self.input.cur_pos();
let slice = self.input.uncons_while(|c| 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}',
);

View File

@ -6,43 +6,48 @@
#![allow(unused_variables)]
pub use self::input::Input;
use self::{state::State, util::*};
use crate::{
error::{Error, SyntaxError},
token::*,
Context, Session, Syntax,
};
use ast::Str;
use error::SyntaxError;
use std::char;
use swc_atoms::JsWord;
use swc_common::{BytePos, Span};
use token::*;
use Context;
use Session;
pub mod input;
mod jsx;
mod number;
mod state;
#[cfg(test)]
mod tests;
pub mod util;
pub(crate) type LexResult<T> = Result<T, ::error::Error>;
pub(crate) type LexResult<T> = Result<T, Error>;
pub(crate) struct Lexer<'a, I: Input> {
session: Session<'a>,
pub ctx: Context,
input: I,
state: State,
pub syntax: Syntax,
}
impl<'a, I: Input> Lexer<'a, I> {
pub fn new(session: Session<'a>, input: I) -> Self {
pub fn new(session: Session<'a>, syntax: Syntax, input: I) -> Self {
Lexer {
session,
input,
state: Default::default(),
state: State::new(syntax),
ctx: Default::default(),
syntax,
}
}
/// babel: `getTokenFromCode`
fn read_token(&mut self) -> LexResult<Option<Token>> {
let c = match self.input.current() {
let c = match self.input.cur() {
Some(c) => c,
None => return Ok(None),
};
@ -105,7 +110,7 @@ impl<'a, I: Input> Lexer<'a, I> {
':' => {
self.input.bump();
if self.session.cfg.fn_bind && self.input.current() == Some(':') {
if self.session.cfg.fn_bind && self.input.cur() == Some(':') {
self.input.bump();
return Ok(Some(tok!("::")));
}
@ -138,13 +143,13 @@ impl<'a, I: Input> Lexer<'a, I> {
// check for **
if is_mul {
if self.input.current() == Some('*') {
if self.input.cur() == Some('*') {
self.input.bump();
token = BinOp(Exp)
}
}
if self.input.current() == Some('=') {
if self.input.cur() == Some('=') {
self.input.bump();
token = match token {
BinOp(Mul) => AssignOp(MulAssign),
@ -163,7 +168,7 @@ impl<'a, I: Input> Lexer<'a, I> {
let token = if c == '&' { BitAnd } else { BitOr };
// '|=', '&='
if self.input.current() == Some('=') {
if self.input.cur() == Some('=') {
self.input.bump();
return Ok(Some(AssignOp(match token {
BitAnd => BitAndAssign,
@ -173,7 +178,7 @@ impl<'a, I: Input> Lexer<'a, I> {
}
// '||', '&&'
if self.input.current() == Some(c) {
if self.input.cur() == Some(c) {
self.input.bump();
return Ok(Some(BinOp(match token {
BitAnd => LogicalAnd,
@ -187,7 +192,7 @@ impl<'a, I: Input> Lexer<'a, I> {
'^' => {
// Bitwise xor
self.input.bump();
if self.input.current() == Some('=') {
if self.input.cur() == Some('=') {
self.input.bump();
AssignOp(BitXorAssign)
} else {
@ -199,7 +204,7 @@ impl<'a, I: Input> Lexer<'a, I> {
self.input.bump();
// '++', '--'
if self.input.current() == Some(c) {
if self.input.cur() == Some(c) {
self.input.bump();
// Handle -->
@ -217,7 +222,7 @@ impl<'a, I: Input> Lexer<'a, I> {
} else {
MinusMinus
}
} else if self.input.current() == Some('=') {
} else if self.input.cur() == Some('=') {
self.input.bump();
AssignOp(if c == '+' { AddAssign } else { SubAssign })
} else {
@ -230,11 +235,11 @@ impl<'a, I: Input> Lexer<'a, I> {
'!' | '=' => {
self.input.bump();
if self.input.current() == Some('=') {
if self.input.cur() == Some('=') {
// "=="
self.input.bump();
if self.input.current() == Some('=') {
if self.input.cur() == Some('=') {
self.input.bump();
if c == '!' {
BinOp(NotEqEq)
@ -248,7 +253,7 @@ impl<'a, I: Input> Lexer<'a, I> {
BinOp(EqEq)
}
}
} else if c == '=' && self.input.current() == Some('>') {
} else if c == '=' && self.input.cur() == Some('>') {
// "=>"
self.input.bump();

View File

@ -4,7 +4,7 @@
//! See https://tc39.github.io/ecma262/#sec-literals-numeric-literals
use super::*;
use error::SyntaxError;
use crate::error::SyntaxError;
use std::fmt::Display;
impl<'a, I: Input> Lexer<'a, I> {
@ -287,8 +287,8 @@ mod tests {
where
F: FnOnce(&mut Lexer<SourceFileInput>) -> Ret,
{
::with_test_sess(s, |sess, fm| {
let mut l = Lexer::new(sess, fm.into());
crate::with_test_sess(s, |sess, fm| {
let mut l = Lexer::new(sess, Syntax::Es2019, fm.into());
Ok(f(&mut l))
})
.unwrap()
@ -375,8 +375,8 @@ mod tests {
});
let vec = panic::catch_unwind(|| {
::with_test_sess(case, |mut sess, input| {
let mut l = Lexer::new(sess, input);
crate::with_test_sess(case, |mut sess, input| {
let mut l = Lexer::new(sess, Syntax::Es2019, input);
l.ctx.strict = strict;
Ok(l.map(|ts| ts.token).collect::<Vec<_>>())
})

View File

@ -1,8 +1,8 @@
use super::{Input, Lexer};
use crate::{lexer::util::CharExt, token::*, Syntax};
use enum_kind::Kind;
use smallvec::SmallVec;
use swc_common::BytePos;
use token::*;
/// State of lexer.
///
@ -15,8 +15,12 @@ pub(super) struct State {
pub had_line_break: bool,
/// TODO: Remove this field.
is_first: bool,
pub start: BytePos,
pub cur_line: usize,
pub line_start: BytePos,
context: Context,
syntax: Syntax,
token_type: Option<TokenType>,
}
@ -31,13 +35,23 @@ enum TokenType {
Semi,
BinOp(BinOpToken),
Keyword(Keyword),
JSXName,
JSXText,
JSXTagStart,
JSXTagEnd,
Other { before_expr: bool },
}
impl TokenType {
fn before_expr(self) -> bool {
match self {
TokenType::Template | TokenType::Dot | TokenType::RParen => false,
TokenType::Colon | TokenType::LBrace | TokenType::Semi => true,
TokenType::JSXName
| TokenType::JSXTagStart
| TokenType::JSXTagEnd
| TokenType::Template
| TokenType::Dot
| TokenType::RParen => false,
TokenType::JSXText | TokenType::Colon | TokenType::LBrace | TokenType::Semi => true,
TokenType::BinOp(b) => b.before_expr(),
TokenType::Keyword(k) => k.before_expr(),
TokenType::Other { before_expr } => before_expr,
@ -54,7 +68,12 @@ impl<'a> From<&'a Token> for TokenType {
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::Word(Keyword(k)) => TokenType::Keyword(k),
_ => TokenType::Other {
before_expr: t.before_expr(),
@ -88,17 +107,67 @@ impl<'a, I: Input> Iterator for Lexer<'a, I> {
}
};
let start = self.cur_pos();
let res = if let Some(Type::Tpl {
start: start_pos_of_tpl,
}) = self.state.context.current()
{
self.read_tmpl_token(start_pos_of_tpl).map(Some)
} else {
self.read_token()
match self.input.cur() {
Some(..) => {}
None => return None,
};
// println!(
// "\tContext: ({:?}) {:?}",
// self.input.cur().unwrap(),
// self.state.context.0
// );
let start = self.cur_pos();
self.state.start = start;
let res = (|| -> Result<Option<_>, _> {
if self.syntax.jsx() {
//jsx
if self.state.context.current() == Some(Type::JSXExpr) {
let start = self.cur_pos();
return self.read_jsx_token();
}
let c = self.cur();
if let Some(c) = c {
if self.state.context.current() == Some(Type::JSXOpeningTag)
|| self.state.context.current() == Some(Type::JSXClosingTag)
{
if c.is_ident_start() {
return self.read_jsx_word().map(Some);
}
if c == '>' {
self.input.bump();
return Ok(Some(Token::JSXTagEnd));
}
if (c == '\'' || c == '"')
&& self.state.context.current() == Some(Type::JSXOpeningTag)
{
return self.read_jsx_str(c).map(Some);
}
}
if c == '<' && self.state.is_expr_allowed && self.input.peek() != Some('!') {
self.input.bump();
return Ok(Some(Token::JSXTagStart));
}
}
}
if let Some(Type::Tpl {
start: start_pos_of_tpl,
}) = self.state.context.current()
{
self.read_tmpl_token(start_pos_of_tpl).map(Some)
} else {
self.read_token()
}
})();
let token = match res.map_err(Token::Error).map_err(Some) {
Ok(t) => t,
Err(e) => e,
@ -119,8 +188,8 @@ impl<'a, I: Input> Iterator for Lexer<'a, I> {
}
}
impl Default for State {
fn default() -> Self {
impl State {
pub fn new(syntax: Syntax) -> Self {
State {
is_expr_allowed: true,
octal_pos: None,
@ -128,6 +197,10 @@ impl Default for State {
had_line_break: false,
context: Context(smallvec![Type::BraceStmt]),
token_type: None,
start: BytePos(0),
line_start: BytePos(0),
cur_line: 1,
syntax,
}
}
}
@ -159,6 +232,7 @@ impl State {
self.is_expr_allowed = Self::is_expr_allowed_on_next(
&mut self.context,
self.syntax,
prev,
start,
next,
@ -171,6 +245,7 @@ impl State {
/// `start`: start of newly produced token.
fn is_expr_allowed_on_next(
context: &mut Context,
syntax: Syntax,
prev: Option<TokenType>,
start: BytePos,
next: &Token,
@ -248,16 +323,30 @@ impl State {
}
tok!('{') => {
let next_ctxt = if context.is_brace_block(prev, had_line_break, is_expr_allowed)
{
Type::BraceStmt
let cur = context.current();
if syntax.jsx() && cur == Some(Type::JSXOpeningTag) {
context.push(Type::BraceExpr)
} else if syntax.jsx() && cur == Some(Type::JSXExpr) {
context.push(Type::TplQuasi);
} else {
Type::BraceExpr
};
context.push(next_ctxt);
let next_ctxt =
if context.is_brace_block(prev, had_line_break, is_expr_allowed) {
Type::BraceStmt
} else {
Type::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(Type::JSXClosingTag); // reconsider as closing tag context
false
}
tok!("${") => {
context.push(Type::TplQuasi);
return true;
@ -290,6 +379,27 @@ impl State {
return false;
}
// tt.jsxTagStart.updateContext
Token::JSXTagStart => {
context.push(Type::JSXExpr); // treat as beginning of JSX expression
context.push(Type::JSXOpeningTag); // start opening tag context
return false;
}
// tt.jsxTagEnd.updateContext
Token::JSXTagEnd => {
let out = context.pop();
if (out == Some(Type::JSXOpeningTag)
&& prev == Some(TokenType::BinOp(BinOpToken::Div)))
|| out == Some(Type::JSXClosingTag)
{
context.pop();
return context.current() == Some(Type::JSXExpr);
} else {
return true;
}
}
_ => {
return next.before_expr();
}
@ -393,4 +503,50 @@ enum Type {
},
#[kind(is_expr)]
FnExpr,
JSXOpeningTag,
JSXClosingTag,
#[kind(is_expr, preserve_space)]
JSXExpr,
}
#[cfg(test)]
pub(crate) fn with_lexer<F, Ret>(
syntax: crate::Syntax,
s: &'static str,
f: F,
) -> Result<Ret, ::testing::StdErr>
where
F: FnOnce(&mut Lexer<crate::lexer::input::SourceFileInput>) -> Result<Ret, ()>,
{
crate::with_test_sess(s, |sess, fm| {
let mut l = Lexer::new(sess, syntax, fm);
let res = f(&mut l);
let c: SmallVec<[Type; 32]> = smallvec![Type::BraceStmt];
assert_eq!(l.state.context.0, c);
res
})
}
#[cfg(test)]
pub(crate) fn lex(syntax: Syntax, s: &'static str) -> Vec<TokenAndSpan> {
with_lexer(syntax, s, |l| Ok(l.collect())).unwrap()
}
/// lex `s` within module context.
#[cfg(test)]
pub(crate) fn lex_module(syntax: Syntax, s: &'static str) -> Vec<TokenAndSpan> {
with_lexer(syntax, s, |l| {
l.ctx.strict = true;
l.ctx.module = true;
Ok(l.collect())
})
.unwrap()
}
#[cfg(test)]
pub(crate) fn lex_tokens(syntax: Syntax, s: &'static str) -> Vec<Token> {
with_lexer(syntax, s, |l| Ok(l.map(|ts| ts.token).collect())).unwrap()
}

View File

@ -1,4 +1,7 @@
use super::{input::SourceFileInput, *};
use super::{
state::{lex, lex_module, lex_tokens, with_lexer},
*,
};
use error::{Error, SyntaxError};
use std::{ops::Range, str};
use test::Bencher;
@ -11,31 +14,6 @@ fn sp(r: Range<usize>) -> Span {
)
}
fn with_lexer<F, Ret>(s: &'static str, f: F) -> Result<Ret, ::testing::StdErr>
where
F: FnOnce(&mut Lexer<SourceFileInput>) -> Result<Ret, ()>,
{
::with_test_sess(s, |sess, fm| {
let mut l = Lexer::new(sess, fm);
f(&mut l)
})
}
fn lex(s: &'static str) -> Vec<TokenAndSpan> {
with_lexer(s, |l| Ok(l.collect())).unwrap()
}
fn lex_module(s: &'static str) -> Vec<TokenAndSpan> {
with_lexer(s, |l| {
l.ctx.strict = true;
l.ctx.module = true;
Ok(l.collect())
})
.unwrap()
}
fn lex_tokens(s: &'static str) -> Vec<Token> {
with_lexer(s, |l| Ok(l.map(|ts| ts.token).collect())).unwrap()
}
trait LineBreak: Into<TokenAndSpan> {
fn lb(self) -> TokenAndSpan {
TokenAndSpan {
@ -125,7 +103,7 @@ impl WithSpan for AssignOpToken {
#[test]
fn module_legacy_octal() {
assert_eq!(
lex_module("01"),
lex_module(Syntax::Es2019, "01"),
vec![Token::Error(Error {
span: sp(0..2),
error: SyntaxError::LegacyOctal,
@ -137,7 +115,7 @@ fn module_legacy_octal() {
#[test]
fn module_legacy_decimal() {
assert_eq!(
lex_module("08"),
lex_module(Syntax::Es2019, "08"),
vec![Token::Error(Error {
span: sp(0..2),
error: SyntaxError::LegacyDecimal,
@ -150,7 +128,7 @@ fn module_legacy_decimal() {
#[test]
fn module_legacy_comment_1() {
assert_eq!(
lex_module("<!-- foo oo"),
lex_module(Syntax::Es2019, "<!-- foo oo"),
vec![Token::Error(Error {
span: sp(0..11),
error: SyntaxError::LegacyCommentInModule,
@ -163,7 +141,7 @@ fn module_legacy_comment_1() {
#[test]
fn module_legacy_comment_2() {
assert_eq!(
lex_module("-->"),
lex_module(Syntax::Es2019, "-->"),
vec![Token::Error(Error {
span: sp(0..3),
error: SyntaxError::LegacyCommentInModule,
@ -184,7 +162,7 @@ fn test262_lexer_error_0001() {
1.span(7..8),
RParen.span(8..9),
],
lex("123..a(1)")
lex(Syntax::Es2019, "123..a(1)")
)
}
@ -200,39 +178,49 @@ fn test262_lexer_error_0002() {
.lb(),
Semi.span(15..16),
],
lex(r#"'use\x20strict';"#)
lex(Syntax::Es2019, r#"'use\x20strict';"#)
);
}
#[test]
fn test262_lexer_error_0003() {
assert_eq!(vec!["a".span(0..6).lb()], lex(r#"\u0061"#));
assert_eq!(vec!["a".span(0..6).lb()], lex(Syntax::Es2019, r#"\u0061"#));
}
#[test]
fn test262_lexer_error_0004() {
assert_eq!(
vec![tok!('+'), tok!('{'), tok!('}'), tok!('/'), 1.into_token()],
lex_tokens("+{} / 1")
lex_tokens(Syntax::Es2019, "+{} / 1")
);
}
#[test]
fn ident_escape_unicode() {
assert_eq!(vec!["aa".span(0..7).lb()], lex(r#"a\u0061"#));
assert_eq!(
vec!["aa".span(0..7).lb()],
lex(Syntax::Es2019, r#"a\u0061"#)
);
}
#[test]
fn ident_escape_unicode_2() {
assert_eq!(vec!["℘℘".span(0..6).lb()], lex("℘℘"));
assert_eq!(
vec!["℘℘".span(0..6).lb()],
lex(Syntax::Es2019, "℘℘")
);
assert_eq!(vec!["℘℘".span(0..9).lb()], lex(r#"℘\u2118"#));
assert_eq!(
vec!["℘℘".span(0..9).lb()],
lex(Syntax::Es2019, r#"℘\u2118"#)
);
}
#[test]
fn tpl_multiline() {
assert_eq!(
lex_tokens(
Syntax::Es2019,
"`this
is
multiline`"
@ -252,7 +240,7 @@ multiline`"
#[test]
fn tpl_raw_unicode_escape() {
assert_eq!(
lex_tokens(r"`\u{0010}`"),
lex_tokens(Syntax::Es2019, r"`\u{0010}`"),
vec![
tok!('`'),
Token::Template {
@ -268,7 +256,7 @@ fn tpl_raw_unicode_escape() {
#[test]
fn str_escape() {
assert_eq!(
lex_tokens(r#"'\n'"#),
lex_tokens(Syntax::Es2019, r#"'\n'"#),
vec![Token::Str {
value: "\n".into(),
has_escape: true
@ -279,7 +267,7 @@ fn str_escape() {
#[test]
fn str_escape_2() {
assert_eq!(
lex_tokens(r#"'\\n'"#),
lex_tokens(Syntax::Es2019, r#"'\\n'"#),
vec![Token::Str {
value: "\\n".into(),
has_escape: true
@ -290,7 +278,7 @@ fn str_escape_2() {
#[test]
fn str_escape_hex() {
assert_eq!(
lex(r#"'\x61'"#),
lex(Syntax::Es2019, r#"'\x61'"#),
vec![Token::Str {
value: "a".into(),
has_escape: true,
@ -303,7 +291,7 @@ fn str_escape_hex() {
#[test]
fn str_escape_octal() {
assert_eq!(
lex(r#"'Hello\012World'"#),
lex(Syntax::Es2019, r#"'Hello\012World'"#),
vec![Token::Str {
value: "Hello\nWorld".into(),
has_escape: true,
@ -316,7 +304,7 @@ fn str_escape_octal() {
#[test]
fn str_escape_unicode_long() {
assert_eq!(
lex(r#"'\u{00000000034}'"#),
lex(Syntax::Es2019, r#"'\u{00000000034}'"#),
vec![Token::Str {
value: "4".into(),
has_escape: true,
@ -329,7 +317,7 @@ fn str_escape_unicode_long() {
#[test]
fn regexp_unary_void() {
assert_eq!(
lex("void /test/"),
lex(Syntax::Es2019, "void /test/"),
vec![
Void.span(0..4).lb(),
Regex(
@ -344,7 +332,7 @@ fn regexp_unary_void() {
]
);
assert_eq!(
lex("void (/test/)"),
lex(Syntax::Es2019, "void (/test/)"),
vec![
Void.span(0..4).lb(),
LParen.span(5..6),
@ -365,7 +353,7 @@ fn regexp_unary_void() {
#[test]
fn non_regexp_unary_plus() {
assert_eq!(
lex("+{} / 1"),
lex(Syntax::Es2019, "+{} / 1"),
vec![
tok!('+').span(0..1).lb(),
tok!('{').span(1..2),
@ -378,19 +366,11 @@ fn non_regexp_unary_plus() {
// ----------
#[test]
fn invalid_but_lexable() {
assert_eq!(
vec![LParen.span(0).lb(), LBrace.span(1), Semi.span(2)],
lex("({;")
);
}
#[test]
fn paren_semi() {
assert_eq!(
vec![LParen.span(0).lb(), RParen.span(1), Semi.span(2)],
lex("();")
lex(Syntax::Es2019, "();")
);
}
@ -404,7 +384,7 @@ fn ident_paren() {
RParen.span(4),
Semi.span(5),
],
lex("a(bc);")
lex(Syntax::Es2019, "a(bc);")
);
}
@ -412,14 +392,14 @@ fn ident_paren() {
fn read_word() {
assert_eq!(
vec!["a".span(0).lb(), "b".span(2), "c".span(4)],
lex("a b c"),
lex(Syntax::Es2019, "a b c"),
)
}
#[test]
fn simple_regex() {
assert_eq!(
lex("x = /42/i"),
lex(Syntax::Es2019, "x = /42/i"),
vec![
"x".span(0).lb(),
Assign.span(2),
@ -440,7 +420,7 @@ fn simple_regex() {
);
assert_eq!(
lex("/42/"),
lex(Syntax::Es2019, "/42/"),
vec![Regex(
Str {
span: sp(1..3),
@ -481,7 +461,7 @@ fn complex_regex() {
}),
),
],
lex_tokens("f(); function foo() {} /42/i")
lex_tokens(Syntax::Es2019, "f(); function foo() {} /42/i")
)
}
@ -489,7 +469,7 @@ fn complex_regex() {
fn simple_div() {
assert_eq!(
vec!["a".span(0).lb(), Div.span(2), "b".span(4)],
lex("a / b")
lex(Syntax::Es2019, "a / b")
);
}
@ -510,7 +490,7 @@ fn complex_divide() {
BinOp(Div),
Word(Ident("i".into())),
],
lex_tokens("x = function foo() {} /a/i"),
lex_tokens(Syntax::Es2019, "x = function foo() {} /a/i"),
"/ should be parsed as div operator"
)
}
@ -543,11 +523,15 @@ fn spec_001() {
assert_eq!(
expected,
lex_tokens(
Syntax::Es2019,
"a = b
/hi/g.exec(c).map(d);"
)
);
assert_eq!(expected, lex_tokens("a = b / hi / g.exec(c).map(d);"));
assert_eq!(
expected,
lex_tokens(Syntax::Es2019, "a = b / hi / g.exec(c).map(d);")
);
}
// ---------- Tests ported from esprima
@ -555,7 +539,7 @@ fn spec_001() {
#[test]
fn after_if() {
assert_eq!(
lex("if(x){} /y/.test(z)"),
lex(Syntax::Es2019, "if(x){} /y/.test(z)"),
vec![
Keyword::If.span(0..2).lb(),
LParen.span(2),
@ -583,7 +567,7 @@ fn after_if() {
#[test]
fn empty() {
assert_eq!(lex(""), vec![]);
assert_eq!(lex(Syntax::Es2019, ""), vec![]);
}
#[test]
@ -600,7 +584,7 @@ fn invalid_number_failure() {
// BlockComment(" hello world ".into()).span(0..17),
// Regex("42".into(), "".into()).span(17..21),
// ],
// lex("/* hello world */ /42/")
// lex(Syntax::Es2019, "/* hello world */ /42/")
// )
// }
@ -615,7 +599,7 @@ fn invalid_number_failure() {
// 42.span(13..15),
// LineComment(" the Ultimate".into()).span(17..32),
// ],
// lex("var answer = 42 // the Ultimate"),
// lex(Syntax::Es2019, "var answer = 42 // the Ultimate"),
// )
// }
@ -636,7 +620,7 @@ fn migrated_0002() {
.span(9..13),
RParen.span(13),
],
lex("tokenize(/42/)")
lex(Syntax::Es2019, "tokenize(/42/)")
)
}
@ -651,7 +635,7 @@ fn migrated_0003() {
42.span(9..11),
Div.span(11),
],
lex("(false) /42/"),
lex(Syntax::Es2019, "(false) /42/"),
)
}
@ -675,7 +659,7 @@ fn migrated_0004() {
)
.span(15..19),
],
lex("function f(){} /42/")
lex(Syntax::Es2019, "function f(){} /42/")
);
}
@ -693,7 +677,7 @@ fn migrated_0004() {
// Div.span(13),
// 42.span(14..16),
// ],
// lex("function (){} /42")
// lex(Syntax::Es2019, "function (){} /42")
// );
// }
@ -702,7 +686,7 @@ fn migrated_0006() {
// This test seems wrong.
// assert_eq!(
// vec![LBrace.span(0).lb(), RBrace.span(1), Div.span(3), 42.span(4..6)],
// lex("{} /42")
// lex(Syntax::Es2019, "{} /42")
// )
assert_eq!(
@ -719,7 +703,7 @@ fn migrated_0006() {
)
.span(3..7),
],
lex("{} /42/")
lex(Syntax::Es2019, "{} /42/")
)
}
@ -730,21 +714,21 @@ fn str_lit() {
value: "abcde".into(),
has_escape: false,
}],
lex_tokens("'abcde'")
lex_tokens(Syntax::Es2019, "'abcde'")
);
assert_eq_ignore_span!(
vec![Token::Str {
value: "abc".into(),
has_escape: true,
}],
lex_tokens("'\\\nabc'")
lex_tokens(Syntax::Es2019, "'\\\nabc'")
);
}
#[test]
fn tpl_empty() {
assert_eq!(
lex_tokens(r#"``"#),
lex_tokens(Syntax::Es2019, r#"``"#),
vec![
tok!('`'),
Template {
@ -760,7 +744,7 @@ fn tpl_empty() {
#[test]
fn tpl() {
assert_eq!(
lex_tokens(r#"`${a}`"#),
lex_tokens(Syntax::Es2019, r#"`${a}`"#),
vec![
tok!('`'),
Template {
@ -784,8 +768,11 @@ fn tpl() {
#[test]
fn comment() {
assert_eq!(
lex("// foo
a"),
lex(
Syntax::Es2019,
"// foo
a"
),
vec![TokenAndSpan {
token: Word(Ident("a".into())),
span: sp(7..8),
@ -798,9 +785,12 @@ a"),
#[test]
fn comment_2() {
assert_eq!(
lex("// foo
lex(
Syntax::Es2019,
"// foo
// bar
a"),
a"
),
vec![TokenAndSpan {
token: Word(Ident("a".into())),
span: sp(14..15),
@ -810,12 +800,82 @@ a"),
);
}
#[test]
fn jsx_01() {
assert_eq!(
lex_tokens(Syntax::Jsx, "<a />"),
vec![
Token::JSXTagStart,
Token::JSXName { name: "a".into() },
tok!('/'),
Token::JSXTagEnd,
]
);
}
#[test]
fn jsx_02() {
assert_eq!(
lex_tokens(Syntax::Jsx, "<a>foo</a>"),
vec![
Token::JSXTagStart,
Token::JSXName { name: "a".into() },
Token::JSXTagEnd,
Token::JSXText { raw: "foo".into() },
Token::JSXTagStart,
tok!('/'),
Token::JSXName { name: "a".into() },
Token::JSXTagEnd,
]
);
}
#[test]
fn jsx_03() {
assert_eq!(
lex_tokens(Syntax::Jsx, "<a><br /></a>"),
vec![
Token::JSXTagStart,
Token::JSXName { name: "a".into() },
Token::JSXTagEnd,
//
Token::JSXTagStart,
Token::JSXName { name: "br".into() },
tok!('/'),
Token::JSXTagEnd,
//
Token::JSXTagStart,
tok!('/'),
Token::JSXName { name: "a".into() },
Token::JSXTagEnd,
]
);
}
#[test]
fn jsx_04() {
assert_eq!(
lex_tokens(Syntax::Jsx, "yield <a></a>"),
vec![
Token::Word(Word::Keyword(Yield)),
Token::JSXTagStart,
Token::JSXName { name: "a".into() },
Token::JSXTagEnd,
//
Token::JSXTagStart,
tok!('/'),
Token::JSXName { name: "a".into() },
Token::JSXTagEnd,
]
);
}
#[bench]
fn lex_colors_js(b: &mut Bencher) {
b.bytes = include_str!("../../colors.js").len() as _;
b.iter(|| {
let _ = with_lexer(include_str!("../../colors.js"), |lexer| {
let _ = with_lexer(Syntax::Es2019, include_str!("../../colors.js"), |lexer| {
for _ in lexer {}
Ok(())
});

View File

@ -6,7 +6,7 @@
//!
//! [babylon/util/identifier.js]:https://github.com/babel/babel/blob/master/packages/babylon/src/util/identifier.js
use super::{input::Input, LexResult, Lexer};
use error::{ErrorToDiag, SyntaxError};
use crate::error::{ErrorToDiag, SyntaxError};
use swc_common::{BytePos, Span};
use unicode_xid::UnicodeXID;
@ -67,7 +67,7 @@ impl<'a, I: Input> Lexer<'a, I> {
}
pub(super) fn cur(&mut self) -> Option<char> {
self.input.current()
self.input.cur()
}
pub(super) fn peek(&mut self) -> Option<char> {
self.input.peek()
@ -84,11 +84,13 @@ impl<'a, I: Input> Lexer<'a, I> {
}
/// Shorthand for `let span = self.span(start); self.error_span(span)`
#[cold]
pub(super) fn error(&mut self, start: BytePos, kind: SyntaxError) -> LexResult<!> {
let span = self.span(start);
self.error_span(span, kind)
}
#[cold]
pub(super) fn error_span(&mut self, span: Span, kind: SyntaxError) -> LexResult<!> {
let err = ErrorToDiag {
handler: self.session.handler,

View File

@ -27,7 +27,7 @@
//! sync::Lrc,
//! FileName, FilePathMapping, SourceMap,
//! };
//! use swc_ecma_parser::{Parser, Session, SourceFileInput};
//! use swc_ecma_parser::{Parser, Session, SourceFileInput, Syntax};
//!
//! fn main() {
//! swc_common::GLOBALS.set(&swc_common::Globals::new(), || {
@ -50,7 +50,7 @@
//! "function foo() {}".into(),
//! );
//!
//! let mut parser = Parser::new(session, SourceFileInput::from(&*fm));
//! let mut parser = Parser::new(session, Syntax::Es2019, SourceFileInput::from(&*fm));
//!
//! let _module = parser.parse_module().expect("failed to parser module");
//! });
@ -65,7 +65,6 @@
#![feature(const_fn)]
#![feature(specialization)]
#![feature(never_type)]
// #![feature(nll)]
#![feature(try_from)]
#![feature(try_trait)]
#![cfg_attr(test, feature(test))]
@ -81,12 +80,17 @@ extern crate log;
#[macro_use(js_word)]
extern crate swc_atoms;
extern crate enum_kind;
extern crate regex;
extern crate swc_common;
extern crate swc_ecma_ast as ast;
#[cfg(test)]
#[macro_use]
extern crate lazy_static;
extern crate swc_ecma_ast as ast;
#[macro_use]
#[cfg(test)]
extern crate testing;
#[cfg(test)]
extern crate env_logger;
#[cfg(test)]
extern crate test;
extern crate unicode_xid;
pub use self::{
@ -102,6 +106,23 @@ mod lexer;
mod parser;
mod token;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Syntax {
Es2019,
Jsx,
Typescript,
Tsx,
}
impl Syntax {
/// Should we pare jsx?
pub fn jsx(self) -> bool {
match self {
Syntax::Jsx | Syntax::Tsx => true,
_ => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Config {
/// Support numeric separator.
@ -128,6 +149,8 @@ struct Context {
in_function: bool,
in_parameters: bool,
in_forced_jsx_context: bool,
}
#[derive(Clone, Copy)]

View File

@ -218,6 +218,10 @@ macro_rules! tok {
("yield") => {
Token::Word(Keyword(Yield))
};
(JSXTagEnd) => {
Token::JSXTagEnd
};
}
macro_rules! token_including_semi {

View File

@ -423,11 +423,11 @@ mod tests {
use swc_common::DUMMY_SP;
fn lhs(s: &'static str) -> Box<Expr> {
test_parser(s, |p| p.parse_lhs_expr())
test_parser(s, Syntax::Es2019, |p| p.parse_lhs_expr())
}
fn expr(s: &'static str) -> Box<Expr> {
test_parser(s, |p| p.parse_expr())
test_parser(s, Syntax::Es2019, |p| p.parse_expr())
}
#[allow(non_upper_case_globals)]

View File

@ -1,4 +1,5 @@
use super::{pat::PatType, util::ExprExt, *};
use either::Either;
use swc_common::Spanned;
mod ops;
@ -622,6 +623,39 @@ impl<'a, I: Input> Parser<'a, I> {
pub(super) fn parse_lhs_expr(&mut self) -> PResult<'a, (Box<Expr>)> {
let start = cur_pos!();
// parse jsx
if self.input.syntax().jsx() {
fn into_expr(e: Either<JSXFragment, JSXElement>) -> Box<Expr> {
match e {
Either::Left(l) => box l.into(),
Either::Right(r) => box r.into(),
}
}
match *cur!(true)? {
Token::JSXText { .. } => {
return self
.parse_jsx_text()
.map(Lit::JSXText)
.map(Expr::Lit)
.map(Box::new);
}
Token::JSXTagStart => {
return self.parse_jsx_element().map(into_expr);
}
_ => {}
}
if is!('<') && !peeked_is!('!') {
// In case we encounter an lt token here it will always be the start of
// jsx as the lt sign is not allowed in places that expect an expression
// FIXME:
// this.finishToken(tt.jsxTagStart);
return self.parse_jsx_element().map(into_expr);
}
}
// `super()` can't be handled from parse_new_expr()
if eat!("super") {
let obj = ExprOrSuper::Super(span!(start));

View File

@ -203,7 +203,7 @@ mod tests {
use swc_common::DUMMY_SP;
fn bin(s: &'static str) -> Box<Expr> {
test_parser(s, |p| p.parse_bin_expr())
test_parser(s, Syntax::Es2019, |p| p.parse_bin_expr())
}
#[allow(non_upper_case_globals)]

View File

@ -2,19 +2,19 @@ use super::*;
use swc_common::DUMMY_SP as span;
fn lhs(s: &'static str) -> Box<Expr> {
test_parser(s, |p| p.parse_lhs_expr())
test_parser(s, Syntax::Es2019, |p| p.parse_lhs_expr())
}
fn new_expr(s: &'static str) -> Box<Expr> {
test_parser(s, |p| p.parse_new_expr())
test_parser(s, Syntax::Es2019, |p| p.parse_new_expr())
}
fn member_expr(s: &'static str) -> Box<Expr> {
test_parser(s, |p| p.parse_member_expr())
test_parser(s, Syntax::Es2019, |p| p.parse_member_expr())
}
fn expr(s: &'static str) -> Box<Expr> {
test_parser(s, |p| {
test_parser(s, Syntax::Es2019, |p| {
p.parse_stmt(true).map(|stmt| match stmt {
Stmt::Expr(expr) => expr,
_ => unreachable!(),

View File

@ -2,7 +2,7 @@ use super::*;
use swc_common::{Span, Spanned, Visit, VisitWith};
impl<'a, I: Input> Parser<'a, I> {
pub(in parser) fn verify_expr(&self, expr: Box<Expr>) -> PResult<'a, Box<Expr>> {
pub(in crate::parser) fn verify_expr(&self, expr: Box<Expr>) -> PResult<'a, Box<Expr>> {
let mut v = Verifier { errors: vec![] };
v.visit(&expr);
@ -34,7 +34,7 @@ impl Visit<Prop> for Verifier {
fn visit(&mut self, p: &Prop) {
match *p {
Prop::Assign { .. } => {
self.errors.push((p.span(), SyntaxError::Unexpected));
self.errors.push((p.span(), SyntaxError::AssignProperty));
}
_ => p.visit_children(self),
}

View File

@ -1,7 +1,9 @@
use lexer::{Input, Lexer};
use crate::{
lexer::{Input, Lexer},
token::*,
Context, Syntax,
};
use swc_common::{BytePos, Span, DUMMY_SP};
use token::*;
use Context;
/// This struct is responsible for managing current token and peeked token.
pub(super) struct ParserInput<'a, I: Input> {
@ -164,4 +166,8 @@ impl<'a, I: Input> ParserInput<'a, I> {
pub fn set_ctx(&mut self, ctx: Context) {
self.iter.ctx = ctx;
}
pub const fn syntax(&self) -> Syntax {
self.iter.syntax
}
}

View File

@ -0,0 +1,429 @@
use super::*;
use either::Either;
use swc_common::{Span, Spanned, SyntaxContext};
#[cfg(test)]
mod tests;
#[parser]
impl<'a, I: Input> Parser<'a, I> {
/// Parse next token as JSX identifier
pub(super) fn parse_jsx_ident(&mut self) -> PResult<'a, Ident> {
debug_assert!(self.input.syntax().jsx());
match *cur!(true)? {
Token::JSXName { .. } => match bump!() {
Token::JSXName { name } => {
let span = self.input.prev_span();
Ok(Ident::new(name, span))
}
_ => unreachable!(),
},
_ if self.ctx().in_forced_jsx_context => self.parse_ident_ref(),
_ => unexpected!(),
}
}
/// Parse namespaced identifier.
pub(super) fn parse_jsx_namespaced_name(&mut self) -> PResult<'a, JSXAttrName> {
debug_assert!(self.input.syntax().jsx());
let ns = self.parse_jsx_ident()?;
if !eat!(':') {
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<'a, JSXElementName> {
debug_assert!(self.input.syntax().jsx());
let start_pos = cur_pos!();
let mut node = match self.parse_jsx_namespaced_name()? {
JSXAttrName::Ident(i) => JSXElementName::Ident(i),
JSXAttrName::JSXNamespacedName(i) => JSXElementName::JSXNamespacedName(i),
};
while eat!('.') {
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 i),
_ => unimplemented!("JSXNamespacedName -> JSXObject"),
},
prop,
});
node = new_node;
}
return 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<'a, Box<Expr>> {
debug_assert!(self.input.syntax().jsx());
let start = cur_pos!();
match *cur!(true)? {
tok!('{') => {
let node = self.parse_jsx_expr_container()?;
match node.expr {
JSXExpr::JSXEmptyExpr(..) => {
syntax_error!(span!(start), SyntaxError::EmptyJSXAttr)
}
JSXExpr::Expr(expr) => Ok(expr),
}
}
Token::Str { .. } | Token::JSXTagStart => self.parse_lhs_expr(),
_ => syntax_error!(span!(start), 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<'a, JSXEmptyExpr> {
debug_assert!(self.input.syntax().jsx());
let start = cur_pos!();
Ok(JSXEmptyExpr {
span: Span::new(start, start, SyntaxContext::empty()),
})
}
/// Parse JSX spread child
pub(super) fn parse_jsx_spread_child(&mut self) -> PResult<'a, JSXSpreadChild> {
debug_assert!(self.input.syntax().jsx());
let start = cur_pos!();
expect!('{');
expect!("...");
let expr = self.parse_expr()?;
expect!('}');
Ok(JSXSpreadChild { expr })
}
/// Parses JSX expression enclosed into curly brackets.
pub(super) fn parse_jsx_expr_container(&mut self) -> PResult<'a, JSXExprContainer> {
debug_assert!(self.input.syntax().jsx());
let start = cur_pos!();
bump!();
let expr = if is!('}') {
self.parse_jsx_empty_expr().map(JSXExpr::JSXEmptyExpr)?
} else {
self.parse_expr().map(JSXExpr::Expr)?
};
expect!('}');
return Ok(JSXExprContainer { expr });
}
/// Parses following JSX attribute name-value pair.
pub(super) fn parse_jsx_attr(&mut self) -> PResult<'a, JSXAttrOrSpread> {
debug_assert!(self.input.syntax().jsx());
let start = cur_pos!();
if eat!('{') {
let dot3_start = cur_pos!();
expect!("...");
let dot3_token = span!(dot3_start);
let expr = self.parse_assignment_expr()?;
expect!('}');
return Ok(SpreadElement { dot3_token, expr }.into());
}
let name = self.parse_jsx_namespaced_name()?;
let value = if eat!('=') {
self.parse_jsx_attr_value().map(Some)?
} else {
None
};
Ok(JSXAttr {
span: span!(start),
name,
value,
}
.into())
}
/// Parses JSX opening tag starting after "<".
pub(super) fn parse_jsx_opening_element_at(
&mut self,
start_pos: BytePos,
) -> PResult<'a, Either<JSXOpeningFragment, JSXOpeningElement>> {
debug_assert!(self.input.syntax().jsx());
let start = cur_pos!();
if eat!(JSXTagEnd) {
return Ok(Either::Left(JSXOpeningFragment { span: span!(start) }));
}
let name = self.parse_jsx_element_name()?;
self.parse_jsx_opening_element_after_name(name)
.map(Either::Right)
}
pub(super) fn parse_jsx_opening_element_after_name(
&mut self,
name: JSXElementName,
) -> PResult<'a, JSXOpeningElement> {
debug_assert!(self.input.syntax().jsx());
let start = name.span().lo();
let mut attrs = vec![];
while let Ok(..) = cur!(false) {
if is!('/') || is!(JSXTagEnd) {
break;
}
let attr = self.parse_jsx_attr()?;
attrs.push(attr);
}
let self_closing = eat!('/');
if !eat!(JSXTagEnd) & !(self.ctx().in_forced_jsx_context && eat!('>')) {
unexpected!()
}
Ok(JSXOpeningElement {
span: span!(start),
name,
attrs,
self_closing,
})
}
/// Parses JSX closing tag starting after "</".
fn parse_jsx_closing_element_at(
&mut self,
start_pos: BytePos,
) -> PResult<'a, Either<JSXClosingFragment, JSXClosingElement>> {
debug_assert!(self.input.syntax().jsx());
let start = cur_pos!();
if eat!(JSXTagEnd) {
return Ok(Either::Left(JSXClosingFragment { span: span!(start) }));
}
let name = self.parse_jsx_element_name()?;
expect!(JSXTagEnd);
Ok(Either::Right(JSXClosingElement {
span: span!(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<'a, Either<JSXFragment, JSXElement>> {
debug_assert!(self.input.syntax().jsx());
let start = cur_pos!();
let forced_jsx_context = match bump!() {
tok!('<') => true,
Token::JSXTagStart => false,
_ => unreachable!(),
};
let ctx = Context {
in_forced_jsx_context: forced_jsx_context,
..self.ctx()
};
self.with_ctx(ctx).parse_with(|p| {
let opening_element = p.parse_jsx_opening_element_at(start_pos)?;
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!(true)? {
Token::JSXTagStart => {
let start = cur_pos!();
if peeked_is!('/') {
bump!(); // JSXTagStart
assert_and_bump!('/');
closing_element =
p.parse_jsx_closing_element_at(start_pos).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 e),
})?);
}
Token::JSXText { .. } => {
children.push(p.parse_jsx_text().map(JSXElementChild::from)?)
}
tok!('{') => {
if peeked_is!("...") {
children
.push(p.parse_jsx_spread_child().map(JSXElementChild::from)?);
} else {
children
.push(p.parse_jsx_expr_container().map(JSXElementChild::from)?);
}
}
_ => unexpected!(),
}
}
}
let span = span!(start);
Ok(match (opening_element, closing_element) {
(Either::Left(opening), Some(Either::Right(closing))) => {
syntax_error!(closing.span(), SyntaxError::JSXExpectedClosingTagForLtGt);
}
(Either::Right(opening), Some(Either::Left(closing))) => {
syntax_error!(
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!(
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<'a, Either<JSXFragment, JSXElement>> {
debug_assert!(self.input.syntax().jsx());
debug_assert!({
match *cur!(true)? {
Token::JSXTagStart | tok!('<') => true,
_ => false,
}
});
let start_pos = cur_pos!();
let element = self.parse_jsx_element_at(start_pos);
element
}
pub(super) fn parse_jsx_text(&mut self) -> PResult<'a, JSXText> {
debug_assert!(self.input.syntax().jsx());
assert!({
match *cur!(false)? {
Token::JSXText { .. } => true,
_ => false,
}
});
let token = bump!();
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 {
match *self {
Either::Left(..) => true,
_ => false,
}
}
}
impl IsFragment for Either<JSXClosingFragment, JSXClosingElement> {
fn is_fragment(&self) -> bool {
match *self {
Either::Left(..) => true,
_ => false,
}
}
}
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(box JSXMemberExpr { ref obj, ref prop }) => {
format!("{}.{}", get_qualified_obj_name(obj), 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,76 @@
use super::*;
use crate::parser::test_parser;
use swc_common::DUMMY_SP as span;
fn jsx(src: &'static str) -> Box<Expr> {
test_parser(src, Syntax::Jsx, |p| p.parse_expr())
}
#[test]
fn self_closing_01() {
assert_eq_ignore_span!(
jsx("<a />"),
box Expr::JSXElement(JSXElement {
span,
opening: JSXOpeningElement {
span,
name: JSXElementName::Ident(Ident::new("a".into(), span)),
self_closing: true,
attrs: vec![]
},
children: vec![],
closing: None,
})
);
}
#[test]
fn normal_01() {
assert_eq_ignore_span!(
jsx("<a>foo</a>"),
box Expr::JSXElement(JSXElement {
span,
opening: JSXOpeningElement {
span,
name: JSXElementName::Ident(Ident::new("a".into(), span)),
self_closing: false,
attrs: vec![]
},
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 Expr::JSXElement(JSXElement {
span,
opening: JSXOpeningElement {
span,
attrs: vec![JSXAttrOrSpread::JSXAttr(JSXAttr {
span,
name: JSXAttrName::Ident(Ident::new("id".into(), span)),
value: Some(box Expr::Lit(Lit::Str(Str {
span,
value: "w < w".into(),
has_escape: false,
}))),
})],
name: JSXElementName::Ident(Ident::new("div".into(), span)),
self_closing: true,
},
children: vec![],
closing: None
})
);
}

View File

@ -1,7 +1,7 @@
macro_rules! unexpected {
($p:expr) => {{
// unimplemented!("Unexpected token")
syntax_error!($p, $p.input.cur_span(), SyntaxError::Unexpected)
let got = format!("{:?}", cur!($p, false).ok());
syntax_error!($p, $p.input.cur_span(), SyntaxError::Unexpected { got })
}};
}
@ -122,7 +122,8 @@ macro_rules! expect {
($p:expr, $t:tt) => {{
const TOKEN: &Token = &token_including_semi!($t);
if !eat!($p, $t) {
syntax_error!($p, $p.input.cur_span(), SyntaxError::Expected(TOKEN))
let cur = format!("{:?}", cur!($p, false).ok());
syntax_error!($p, $p.input.cur_span(), SyntaxError::Expected(TOKEN, cur))
}
}};
}
@ -131,7 +132,8 @@ macro_rules! expect_exact {
($p:expr, $t:tt) => {{
const TOKEN: &Token = &token_including_semi!($t);
if !eat_exact!($p, $t) {
syntax_error!($p, $p.input.cur_span(), SyntaxError::Expected(TOKEN))
let cur = format!("{:?}", cur!($p, false).ok());
syntax_error!($p, $p.input.cur_span(), SyntaxError::Expected(TOKEN, cur))
}
}};
}

View File

@ -1,16 +1,17 @@
#![allow(dead_code, unused_variables)]
#![deny(non_snake_case)]
use self::{input::ParserInput, util::ParseObject};
use crate::{
error::SyntaxError,
lexer::{Input, Lexer},
parser_macros::parser,
token::*,
Context, Session, Syntax,
};
use ast::*;
use error::SyntaxError;
use lexer::{Input, Lexer};
use parser_macros::parser;
use std::ops::{Deref, DerefMut};
use swc_atoms::JsWord;
use swc_common::{BytePos, Span};
use token::*;
use Context;
use Session;
#[macro_use]
mod macros;
@ -18,6 +19,7 @@ mod class_and_fn;
mod expr;
mod ident;
pub mod input;
mod jsx;
mod object;
mod pat;
mod stmt;
@ -42,10 +44,10 @@ struct State {
#[parser]
impl<'a, I: Input> Parser<'a, I> {
pub fn new(session: Session<'a>, input: I) -> Self {
pub fn new(session: Session<'a>, syntax: Syntax, input: I) -> Self {
Parser {
session,
input: ParserInput::new(Lexer::new(session, input)),
input: ParserInput::new(Lexer::new(session, syntax, input)),
state: Default::default(),
}
}
@ -83,16 +85,17 @@ impl<'a, I: Input> Parser<'a, I> {
}
#[cfg(test)]
pub fn test_parser<F, Ret>(s: &'static str, f: F) -> Ret
pub fn test_parser<F, Ret>(s: &'static str, syntax: Syntax, f: F) -> Ret
where
F: for<'a> FnOnce(&'a mut Parser<'a, ::SourceFileInput>) -> Result<Ret, ()>,
{
::with_test_sess(s, |sess, input| f(&mut Parser::new(sess, input))).unwrap()
crate::with_test_sess(s, |sess, input| f(&mut Parser::new(sess, syntax, input)))
.unwrap_or_else(|output| panic!("test_parser(): failed to parse \n{}\n{}", s, output))
}
#[test]
fn module_legacy() {
test_parser("<!--", |f| {
test_parser("<!--", Syntax::Es2019, |f| {
let res = f.parse_module();
assert!(f.ctx().module);
assert!(f.ctx().strict);

View File

@ -460,7 +460,7 @@ mod tests {
use swc_common::DUMMY_SP as span;
fn array_pat(s: &'static str) -> Pat {
test_parser(s, |p| p.parse_array_binding_pat())
test_parser(s, Syntax::Es2019, |p| p.parse_array_binding_pat())
}
fn ident(s: &str) -> Ident {

View File

@ -726,10 +726,10 @@ mod tests {
use swc_common::DUMMY_SP as span;
fn stmt(s: &'static str) -> Stmt {
test_parser(s, |p| p.parse_stmt(true))
test_parser(s, Syntax::Es2019, |p| p.parse_stmt(true))
}
fn expr(s: &'static str) -> Box<Expr> {
test_parser(s, |p| p.parse_expr())
test_parser(s, Syntax::Es2019, |p| p.parse_expr())
}
#[test]

View File

@ -99,7 +99,7 @@ impl<'a, I: Input> Parser<'a, I> {
// 'ImportedBinding'
// 'IdentifierName' as 'ImportedBinding'
if self.ctx().is_reserved_word(&orig_name.sym) {
syntax_error!(orig_name.span, SyntaxError::Unexpected)
syntax_error!(orig_name.span, SyntaxError::ReservedWordInImport)
}
let local = orig_name;

View File

@ -102,6 +102,7 @@ impl<'a, I: Input> Parser<'a, I> {
f(self)
}
/// Creates a span from `start` to current pos.
pub(super) fn span(&mut self, start: BytePos) -> Span {
let end = last_pos!(self);
if cfg!(debug_assertions) && start > end {
@ -113,6 +114,10 @@ impl<'a, I: Input> Parser<'a, I> {
}
Span::new(start, end, Default::default())
}
pub(super) const fn syntax(&self) -> Syntax {
self.input.syntax()
}
}
pub trait ParseObject<'a, Obj> {
type Prop;
@ -187,6 +192,13 @@ pub(super) trait ExprExt {
Expr::Yield(..) | Expr::Arrow(..) | Expr::Assign(..) => false,
Expr::Seq(..) => false,
// jsx
Expr::JSXMebmer(..)
| Expr::JSXNamespacedName(..)
| Expr::JSXEmpty(..)
| Expr::JSXElement(..)
| Expr::JSXFragment(..) => false,
}
}
}

View File

@ -2,6 +2,7 @@
//!
//! [babel/bablyon]:https://github.com/babel/babel/blob/2d378d076eb0c5fe63234a8b509886005c01d7ee/packages/babylon/src/tokenizer/types.js
pub(crate) use self::{AssignOpToken::*, BinOpToken::*, Keyword::*, Token::*, Word::*};
use crate::error::Error;
pub(crate) use ast::AssignOp as AssignOpToken;
use ast::{BinaryOp, Str};
use enum_kind::Kind;
@ -113,7 +114,18 @@ pub(crate) enum Token {
#[kind(starts_expr)]
Num(f64),
Error(#[fold(ignore)] ::error::Error),
JSXName {
name: JsWord,
},
#[kind(before_expr)]
JSXText {
raw: JsWord,
},
#[kind(starts_expr)]
JSXTagStart,
JSXTagEnd,
Error(#[fold(ignore)] Error),
}
#[derive(Kind, Debug, Clone, Copy, Eq, PartialEq, Hash, Fold)]

View File

@ -0,0 +1,281 @@
#![feature(box_syntax)]
#![feature(specialization)]
#![feature(test)]
extern crate swc_common;
extern crate swc_ecma_ast;
extern crate swc_ecma_parser;
extern crate test;
extern crate testing;
extern crate walkdir;
use std::{
env,
fs::File,
io::{self, Read},
path::Path,
};
use swc_common::{Fold, FoldWith, Span};
use swc_ecma_ast::*;
use swc_ecma_parser::{PResult, Parser, Session, SourceFileInput, Syntax};
use test::{test_main, Options, ShouldPanic::No, TestDesc, TestDescAndFn, TestFn, TestName};
use testing::{NormalizedOutput, StdErr};
use walkdir::WalkDir;
fn add_test<F: FnOnce() + Send + 'static>(
tests: &mut Vec<TestDescAndFn>,
name: String,
ignore: bool,
f: F,
) {
tests.push(TestDescAndFn {
desc: TestDesc {
name: TestName::DynTestName(name),
ignore,
should_panic: No,
allow_fail: false,
},
testfn: TestFn::DynTestFn(box f),
});
}
fn error_tests(tests: &mut Vec<TestDescAndFn>) -> Result<(), io::Error> {
let root = {
let mut root = Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf();
root.push("tests");
root.push("jsx");
root.push("errors");
root
};
eprintln!("Loading tests from {}", root.display());
let dir = root;
for entry in WalkDir::new(&dir).into_iter() {
let entry = entry?;
if entry.file_type().is_dir() || !entry.file_name().to_string_lossy().ends_with(".js") {
continue;
}
let file_name = entry
.path()
.strip_prefix(&dir)
.expect("failed to strip prefix")
.to_str()
.unwrap()
.to_string();
let input = {
let mut buf = String::new();
File::open(entry.path())?.read_to_string(&mut buf)?;
buf
};
let ignore = false;
let dir = dir.clone();
let name = format!("jsx::error::{}", file_name);
add_test(tests, name, ignore, move || {
eprintln!(
"\n\n========== Running error reporting test {}\nSource:\n{}\n",
file_name, input
);
let path = dir.join(&file_name);
// Parse source
let err = parse_module(&path).expect_err("should fail, but parsed as");
if err
.compare_to_file(format!("{}.stderr", path.display()))
.is_err()
{
panic!()
}
});
}
Ok(())
}
fn reference_tests(tests: &mut Vec<TestDescAndFn>) -> Result<(), io::Error> {
let root = {
let mut root = Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf();
root.push("tests");
root.push("jsx");
root.push("basic");
root
};
eprintln!("Loading tests from {}", root.display());
let dir = root;
for entry in WalkDir::new(&dir).into_iter() {
let entry = entry?;
if entry.file_type().is_dir() || !entry.file_name().to_string_lossy().ends_with(".js") {
continue;
}
let file_name = entry
.path()
.strip_prefix(&dir)
.expect("failed to strip prefix")
.to_str()
.unwrap()
.to_string();
let input = {
let mut buf = String::new();
File::open(entry.path())?.read_to_string(&mut buf)?;
buf
};
let ignore = false;
let dir = dir.clone();
let name = format!("jsx::reference::{}", file_name);
add_test(tests, name, ignore, move || {
eprintln!(
"\n\n========== Running reference test {}\nSource:\n{}\n",
file_name, input
);
let path = dir.join(&file_name);
// Parse source
let module = parse_module(&path).expect("should be parsed");
if StdErr::from(format!("{:#?}", module))
.compare_to_file(format!("{}.stdout", path.display()))
.is_err()
{
panic!()
}
});
}
Ok(())
}
fn parse_module<'a>(file_name: &Path) -> Result<Module, NormalizedOutput> {
with_parser(file_name, |p| p.parse_module())
}
fn with_parser<F, Ret>(file_name: &Path, f: F) -> Result<Ret, StdErr>
where
F: for<'a> FnOnce(&mut Parser<'a, SourceFileInput>) -> PResult<'a, Ret>,
{
let output = ::testing::run_test(|cm, handler| {
let fm = cm
.load_file(file_name)
.unwrap_or_else(|e| panic!("failed to load {}: {}", file_name.display(), e));
let res = f(&mut Parser::new(
Session {
handler: &handler,
cfg: Default::default(),
},
Syntax::Jsx,
(&*fm).into(),
));
res
});
output
}
#[test]
fn references() {
let args: Vec<_> = env::args().collect();
let mut tests = Vec::new();
reference_tests(&mut tests).unwrap();
test_main(&args, tests, Options::new());
}
#[test]
fn error() {
let args: Vec<_> = env::args().collect();
let mut tests = Vec::new();
error_tests(&mut tests).unwrap();
test_main(&args, tests, Options::new());
}
pub fn normalize<T>(t: T) -> T
where
Normalizer: Fold<T>,
{
let mut n = Normalizer;
n.fold(t)
}
pub struct Normalizer;
impl Fold<Span> for Normalizer {
fn fold(&mut self, _: Span) -> Span {
Span::default()
}
}
impl Fold<Str> for Normalizer {
fn fold(&mut self, s: Str) -> Str {
Str {
span: Default::default(),
has_escape: false,
..s
}
}
}
impl Fold<Expr> for Normalizer {
fn fold(&mut self, e: Expr) -> Expr {
let e = e.fold_children(self);
match e {
Expr::Paren(ParenExpr { expr, .. }) => *expr,
Expr::New(NewExpr {
callee,
args: None,
span,
}) => Expr::New(NewExpr {
span,
callee,
args: Some(vec![]),
}),
// Flatten comma expressions.
Expr::Seq(SeqExpr { mut exprs, span }) => {
let need_work = exprs.iter().any(|n| match **n {
Expr::Seq(..) => true,
_ => false,
});
if need_work {
exprs = exprs.into_iter().fold(vec![], |mut v, e| {
match *e {
Expr::Seq(SeqExpr { exprs, .. }) => v.extend(exprs),
_ => v.push(e),
}
v
});
}
Expr::Seq(SeqExpr { exprs, span })
}
_ => e,
}
}
}
impl Fold<PropName> for Normalizer {
fn fold(&mut self, n: PropName) -> PropName {
let n = n.fold_children(self);
match n {
PropName::Ident(Ident { sym, .. }) => PropName::Str(Str {
span: Default::default(),
value: sym,
has_escape: false,
}),
PropName::Num(num) => PropName::Str(Str {
span: Default::default(),
value: num.to_string().into(),
has_escape: false,
}),
_ => n,
}
}
}

View File

@ -0,0 +1 @@
<a />

View File

@ -0,0 +1,59 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
5
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
5
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
a,
Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
5
),
ctxt: #0
},
attrs: [],
self_closing: true
},
children: [],
closing: None
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<a>{/* this is a comment */}</a>

View File

@ -0,0 +1,103 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
32
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
32
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
a,
Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
3
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [
JSXExprContainer(
JSXExprContainer {
expr: JSXEmptyExpr(
JSXEmptyExpr {
span: Span {
lo: BytePos(
27
),
hi: BytePos(
27
),
ctxt: #0
}
}
)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
30
),
hi: BytePos(
32
),
ctxt: #0
},
name: Ident(
Ident(
a,
Span {
lo: BytePos(
30
),
hi: BytePos(
31
),
ctxt: #0
}
)
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<div>@test content</div>

View File

@ -0,0 +1,101 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
24
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
24
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
div,
Span {
lo: BytePos(
1
),
hi: BytePos(
4
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
5
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
5
),
hi: BytePos(
18
),
ctxt: #0
},
value: Atom('@test content' type=dynamic),
raw: Atom('@test content' type=dynamic)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
20
),
hi: BytePos(
24
),
ctxt: #0
},
name: Ident(
Ident(
div,
Span {
lo: BytePos(
20
),
hi: BytePos(
23
),
ctxt: #0
}
)
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<div><br />7x invalid-js-identifier</div>

View File

@ -0,0 +1,143 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
41
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
41
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
div,
Span {
lo: BytePos(
1
),
hi: BytePos(
4
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
5
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
5
),
hi: BytePos(
11
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
br,
Span {
lo: BytePos(
6
),
hi: BytePos(
8
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
6
),
hi: BytePos(
11
),
ctxt: #0
},
attrs: [],
self_closing: true
},
children: [],
closing: None
}
),
JSXText(
JSXText {
span: Span {
lo: BytePos(
11
),
hi: BytePos(
35
),
ctxt: #0
},
value: Atom('7x invalid-js-identifier' type=dynamic),
raw: Atom('7x invalid-js-identifier' type=dynamic)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
37
),
hi: BytePos(
41
),
ctxt: #0
},
name: Ident(
Ident(
div,
Span {
lo: BytePos(
37
),
hi: BytePos(
40
),
ctxt: #0
}
)
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<LeftRight left=<a /> right=<b>monkeys /> gorillas</b> />

View File

@ -0,0 +1,244 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
57
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
57
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
LeftRight,
Span {
lo: BytePos(
1
),
hi: BytePos(
10
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
57
),
ctxt: #0
},
attrs: [
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
11
),
hi: BytePos(
21
),
ctxt: #0
},
name: Ident(
Ident(
left,
Span {
lo: BytePos(
11
),
hi: BytePos(
15
),
ctxt: #0
}
)
),
value: Some(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
16
),
hi: BytePos(
21
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
a,
Span {
lo: BytePos(
17
),
hi: BytePos(
18
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
17
),
hi: BytePos(
21
),
ctxt: #0
},
attrs: [],
self_closing: true
},
children: [],
closing: None
}
)
)
}
),
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
22
),
hi: BytePos(
54
),
ctxt: #0
},
name: Ident(
Ident(
right,
Span {
lo: BytePos(
22
),
hi: BytePos(
27
),
ctxt: #0
}
)
),
value: Some(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
28
),
hi: BytePos(
54
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
b,
Span {
lo: BytePos(
29
),
hi: BytePos(
30
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
29
),
hi: BytePos(
31
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
31
),
hi: BytePos(
50
),
ctxt: #0
},
value: Atom('monkeys /> gorillas' type=dynamic),
raw: Atom('monkeys /> gorillas' type=dynamic)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
52
),
hi: BytePos(
54
),
ctxt: #0
},
name: Ident(
Ident(
b,
Span {
lo: BytePos(
52
),
hi: BytePos(
53
),
ctxt: #0
}
)
)
}
)
}
)
)
}
)
],
self_closing: true
},
children: [],
closing: None
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<a.b></a.b>

View File

@ -0,0 +1,117 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
11
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
11
),
ctxt: #0
},
opening: JSXOpeningElement {
name: JSXMemberExpr(
JSXMemberExpr {
obj: Ident(
Ident(
a,
Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
)
),
prop: Ident(
b,
Span {
lo: BytePos(
3
),
hi: BytePos(
4
),
ctxt: #0
}
)
}
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
5
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
7
),
hi: BytePos(
11
),
ctxt: #0
},
name: JSXMemberExpr(
JSXMemberExpr {
obj: Ident(
Ident(
a,
Span {
lo: BytePos(
7
),
hi: BytePos(
8
),
ctxt: #0
}
)
),
prop: Ident(
b,
Span {
lo: BytePos(
9
),
hi: BytePos(
10
),
ctxt: #0
}
)
}
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<a.b.c></a.b.c>

View File

@ -0,0 +1,149 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
15
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
15
),
ctxt: #0
},
opening: JSXOpeningElement {
name: JSXMemberExpr(
JSXMemberExpr {
obj: JSXMemberExpr(
JSXMemberExpr {
obj: Ident(
Ident(
a,
Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
)
),
prop: Ident(
b,
Span {
lo: BytePos(
3
),
hi: BytePos(
4
),
ctxt: #0
}
)
}
),
prop: Ident(
c,
Span {
lo: BytePos(
5
),
hi: BytePos(
6
),
ctxt: #0
}
)
}
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
7
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
9
),
hi: BytePos(
15
),
ctxt: #0
},
name: JSXMemberExpr(
JSXMemberExpr {
obj: JSXMemberExpr(
JSXMemberExpr {
obj: Ident(
Ident(
a,
Span {
lo: BytePos(
9
),
hi: BytePos(
10
),
ctxt: #0
}
)
),
prop: Ident(
b,
Span {
lo: BytePos(
11
),
hi: BytePos(
12
),
ctxt: #0
}
)
}
),
prop: Ident(
c,
Span {
lo: BytePos(
13
),
hi: BytePos(
14
),
ctxt: #0
}
)
}
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
(<div />) < x;

View File

@ -0,0 +1,100 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
14
),
ctxt: #0
},
body: [
Stmt(
Expr(
Bin(
BinExpr {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
13
),
ctxt: #0
},
op: "<",
left: Paren(
ParenExpr {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
9
),
ctxt: #0
},
expr: JSXElement(
JSXElement {
span: Span {
lo: BytePos(
1
),
hi: BytePos(
8
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
div,
Span {
lo: BytePos(
2
),
hi: BytePos(
5
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
2
),
hi: BytePos(
8
),
ctxt: #0
},
attrs: [],
self_closing: true
},
children: [],
closing: None
}
)
}
),
right: Ident(
Ident(
x,
Span {
lo: BytePos(
12
),
hi: BytePos(
13
),
ctxt: #0
}
)
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<div {...props} />

View File

@ -0,0 +1,87 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
18
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
18
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
div,
Span {
lo: BytePos(
1
),
hi: BytePos(
4
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
18
),
ctxt: #0
},
attrs: [
SpreadElement(
SpreadElement {
dot3_token: Span {
lo: BytePos(
6
),
hi: BytePos(
9
),
ctxt: #0
},
expr: Ident(
Ident(
props,
Span {
lo: BytePos(
9
),
hi: BytePos(
14
),
ctxt: #0
}
)
)
}
)
],
self_closing: true
},
children: [],
closing: None
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<div {...props} post="attribute" />

View File

@ -0,0 +1,133 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
35
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
35
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
div,
Span {
lo: BytePos(
1
),
hi: BytePos(
4
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
35
),
ctxt: #0
},
attrs: [
SpreadElement(
SpreadElement {
dot3_token: Span {
lo: BytePos(
6
),
hi: BytePos(
9
),
ctxt: #0
},
expr: Ident(
Ident(
props,
Span {
lo: BytePos(
9
),
hi: BytePos(
14
),
ctxt: #0
}
)
)
}
),
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
16
),
hi: BytePos(
32
),
ctxt: #0
},
name: Ident(
Ident(
post,
Span {
lo: BytePos(
16
),
hi: BytePos(
20
),
ctxt: #0
}
)
),
value: Some(
Lit(
Str(
Str {
span: Span {
lo: BytePos(
21
),
hi: BytePos(
32
),
ctxt: #0
},
value: Atom('attribute' type=dynamic),
has_escape: false
}
)
)
)
}
)
],
self_closing: true
},
children: [],
closing: None
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<div pre="leading" pre2="attribute" {...props}></div>

View File

@ -0,0 +1,205 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
53
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
53
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
div,
Span {
lo: BytePos(
1
),
hi: BytePos(
4
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
47
),
ctxt: #0
},
attrs: [
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
5
),
hi: BytePos(
18
),
ctxt: #0
},
name: Ident(
Ident(
pre,
Span {
lo: BytePos(
5
),
hi: BytePos(
8
),
ctxt: #0
}
)
),
value: Some(
Lit(
Str(
Str {
span: Span {
lo: BytePos(
9
),
hi: BytePos(
18
),
ctxt: #0
},
value: Atom('leading' type=inline),
has_escape: false
}
)
)
)
}
),
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
19
),
hi: BytePos(
35
),
ctxt: #0
},
name: Ident(
Ident(
pre2,
Span {
lo: BytePos(
19
),
hi: BytePos(
23
),
ctxt: #0
}
)
),
value: Some(
Lit(
Str(
Str {
span: Span {
lo: BytePos(
24
),
hi: BytePos(
35
),
ctxt: #0
},
value: Atom('attribute' type=dynamic),
has_escape: false
}
)
)
)
}
),
SpreadElement(
SpreadElement {
dot3_token: Span {
lo: BytePos(
37
),
hi: BytePos(
40
),
ctxt: #0
},
expr: Ident(
Ident(
props,
Span {
lo: BytePos(
40
),
hi: BytePos(
45
),
ctxt: #0
}
)
)
}
)
],
self_closing: false
},
children: [],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
49
),
hi: BytePos(
53
),
ctxt: #0
},
name: Ident(
Ident(
div,
Span {
lo: BytePos(
49
),
hi: BytePos(
52
),
ctxt: #0
}
)
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<n:a n:v />

View File

@ -0,0 +1,116 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
11
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
11
),
ctxt: #0
},
opening: JSXOpeningElement {
name: JSXNamespacedName(
JSXNamespacedName {
ns: Ident(
n,
Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
),
name: Ident(
a,
Span {
lo: BytePos(
3
),
hi: BytePos(
4
),
ctxt: #0
}
)
}
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
11
),
ctxt: #0
},
attrs: [
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
5
),
hi: BytePos(
8
),
ctxt: #0
},
name: JSXNamespacedName(
JSXNamespacedName {
ns: Ident(
n,
Span {
lo: BytePos(
5
),
hi: BytePos(
6
),
ctxt: #0
}
),
name: Ident(
v,
Span {
lo: BytePos(
7
),
hi: BytePos(
8
),
ctxt: #0
}
)
}
),
value: None
}
)
],
self_closing: true
},
children: [],
closing: None
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<A aa={aa.bb.cc} bb={bb.cc.dd}><div>{aa.b}</div></A>

View File

@ -0,0 +1,412 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
52
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
52
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
A,
Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
31
),
ctxt: #0
},
attrs: [
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
3
),
hi: BytePos(
16
),
ctxt: #0
},
name: Ident(
Ident(
aa,
Span {
lo: BytePos(
3
),
hi: BytePos(
5
),
ctxt: #0
}
)
),
value: Some(
Member(
MemberExpr {
span: Span {
lo: BytePos(
12
),
hi: BytePos(
15
),
ctxt: #0
},
obj: Expr(
Member(
MemberExpr {
span: Span {
lo: BytePos(
9
),
hi: BytePos(
12
),
ctxt: #0
},
obj: Expr(
Ident(
Ident(
aa,
Span {
lo: BytePos(
7
),
hi: BytePos(
9
),
ctxt: #0
}
)
)
),
prop: Ident(
Ident(
bb,
Span {
lo: BytePos(
10
),
hi: BytePos(
12
),
ctxt: #0
}
)
),
computed: false
}
)
),
prop: Ident(
Ident(
cc,
Span {
lo: BytePos(
13
),
hi: BytePos(
15
),
ctxt: #0
}
)
),
computed: false
}
)
)
}
),
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
17
),
hi: BytePos(
30
),
ctxt: #0
},
name: Ident(
Ident(
bb,
Span {
lo: BytePos(
17
),
hi: BytePos(
19
),
ctxt: #0
}
)
),
value: Some(
Member(
MemberExpr {
span: Span {
lo: BytePos(
26
),
hi: BytePos(
29
),
ctxt: #0
},
obj: Expr(
Member(
MemberExpr {
span: Span {
lo: BytePos(
23
),
hi: BytePos(
26
),
ctxt: #0
},
obj: Expr(
Ident(
Ident(
bb,
Span {
lo: BytePos(
21
),
hi: BytePos(
23
),
ctxt: #0
}
)
)
),
prop: Ident(
Ident(
cc,
Span {
lo: BytePos(
24
),
hi: BytePos(
26
),
ctxt: #0
}
)
),
computed: false
}
)
),
prop: Ident(
Ident(
dd,
Span {
lo: BytePos(
27
),
hi: BytePos(
29
),
ctxt: #0
}
)
),
computed: false
}
)
)
}
)
],
self_closing: false
},
children: [
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
31
),
hi: BytePos(
48
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
div,
Span {
lo: BytePos(
32
),
hi: BytePos(
35
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
32
),
hi: BytePos(
36
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [
JSXExprContainer(
JSXExprContainer {
expr: Expr(
Member(
MemberExpr {
span: Span {
lo: BytePos(
39
),
hi: BytePos(
41
),
ctxt: #0
},
obj: Expr(
Ident(
Ident(
aa,
Span {
lo: BytePos(
37
),
hi: BytePos(
39
),
ctxt: #0
}
)
)
),
prop: Ident(
Ident(
b,
Span {
lo: BytePos(
40
),
hi: BytePos(
41
),
ctxt: #0
}
)
),
computed: false
}
)
)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
44
),
hi: BytePos(
48
),
ctxt: #0
},
name: Ident(
Ident(
div,
Span {
lo: BytePos(
44
),
hi: BytePos(
47
),
ctxt: #0
}
)
)
}
)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
50
),
hi: BytePos(
52
),
ctxt: #0
},
name: Ident(
Ident(
A,
Span {
lo: BytePos(
50
),
hi: BytePos(
51
),
ctxt: #0
}
)
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<div {...c}> {...children}{a}{...b}</div>

View File

@ -0,0 +1,185 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
41
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
41
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
div,
Span {
lo: BytePos(
1
),
hi: BytePos(
4
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
12
),
ctxt: #0
},
attrs: [
SpreadElement(
SpreadElement {
dot3_token: Span {
lo: BytePos(
6
),
hi: BytePos(
9
),
ctxt: #0
},
expr: Ident(
Ident(
c,
Span {
lo: BytePos(
9
),
hi: BytePos(
10
),
ctxt: #0
}
)
)
}
)
],
self_closing: false
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
12
),
hi: BytePos(
13
),
ctxt: #0
},
value: Atom(' ' type=inline),
raw: Atom(' ' type=inline)
}
),
JSXSpreadChild(
JSXSpreadChild {
expr: Ident(
Ident(
children,
Span {
lo: BytePos(
17
),
hi: BytePos(
25
),
ctxt: #0
}
)
)
}
),
JSXExprContainer(
JSXExprContainer {
expr: Expr(
Ident(
Ident(
a,
Span {
lo: BytePos(
27
),
hi: BytePos(
28
),
ctxt: #0
}
)
)
)
}
),
JSXSpreadChild(
JSXSpreadChild {
expr: Ident(
Ident(
b,
Span {
lo: BytePos(
33
),
hi: BytePos(
34
),
ctxt: #0
}
)
)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
37
),
hi: BytePos(
41
),
ctxt: #0
},
name: Ident(
Ident(
div,
Span {
lo: BytePos(
37
),
hi: BytePos(
40
),
ctxt: #0
}
)
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<a n:foo="bar"> {value} <b><c /></b></a>

View File

@ -0,0 +1,308 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
40
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
40
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
a,
Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
15
),
ctxt: #0
},
attrs: [
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
3
),
hi: BytePos(
14
),
ctxt: #0
},
name: JSXNamespacedName(
JSXNamespacedName {
ns: Ident(
n,
Span {
lo: BytePos(
3
),
hi: BytePos(
4
),
ctxt: #0
}
),
name: Ident(
foo,
Span {
lo: BytePos(
5
),
hi: BytePos(
8
),
ctxt: #0
}
)
}
),
value: Some(
Lit(
Str(
Str {
span: Span {
lo: BytePos(
9
),
hi: BytePos(
14
),
ctxt: #0
},
value: Atom('bar' type=inline),
has_escape: false
}
)
)
)
}
)
],
self_closing: false
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
15
),
hi: BytePos(
16
),
ctxt: #0
},
value: Atom(' ' type=inline),
raw: Atom(' ' type=inline)
}
),
JSXExprContainer(
JSXExprContainer {
expr: Expr(
Ident(
Ident(
value,
Span {
lo: BytePos(
17
),
hi: BytePos(
22
),
ctxt: #0
}
)
)
)
}
),
JSXText(
JSXText {
span: Span {
lo: BytePos(
23
),
hi: BytePos(
24
),
ctxt: #0
},
value: Atom(' ' type=inline),
raw: Atom(' ' type=inline)
}
),
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
24
),
hi: BytePos(
36
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
b,
Span {
lo: BytePos(
25
),
hi: BytePos(
26
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
25
),
hi: BytePos(
27
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
27
),
hi: BytePos(
32
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
c,
Span {
lo: BytePos(
28
),
hi: BytePos(
29
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
28
),
hi: BytePos(
32
),
ctxt: #0
},
attrs: [],
self_closing: true
},
children: [],
closing: None
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
34
),
hi: BytePos(
36
),
ctxt: #0
},
name: Ident(
Ident(
b,
Span {
lo: BytePos(
34
),
hi: BytePos(
35
),
ctxt: #0
}
)
)
}
)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
38
),
hi: BytePos(
40
),
ctxt: #0
},
name: Ident(
Ident(
a,
Span {
lo: BytePos(
38
),
hi: BytePos(
39
),
ctxt: #0
}
)
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<a b={" "} c=" " d="&amp;" e="&ampr;" />

View File

@ -0,0 +1,244 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
40
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
40
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
a,
Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
40
),
ctxt: #0
},
attrs: [
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
3
),
hi: BytePos(
10
),
ctxt: #0
},
name: Ident(
Ident(
b,
Span {
lo: BytePos(
3
),
hi: BytePos(
4
),
ctxt: #0
}
)
),
value: Some(
Lit(
Str(
Str {
span: Span {
lo: BytePos(
6
),
hi: BytePos(
9
),
ctxt: #0
},
value: Atom(' ' type=inline),
has_escape: false
}
)
)
)
}
),
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
11
),
hi: BytePos(
16
),
ctxt: #0
},
name: Ident(
Ident(
c,
Span {
lo: BytePos(
11
),
hi: BytePos(
12
),
ctxt: #0
}
)
),
value: Some(
Lit(
Str(
Str {
span: Span {
lo: BytePos(
13
),
hi: BytePos(
16
),
ctxt: #0
},
value: Atom(' ' type=inline),
has_escape: false
}
)
)
)
}
),
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
17
),
hi: BytePos(
26
),
ctxt: #0
},
name: Ident(
Ident(
d,
Span {
lo: BytePos(
17
),
hi: BytePos(
18
),
ctxt: #0
}
)
),
value: Some(
Lit(
Str(
Str {
span: Span {
lo: BytePos(
19
),
hi: BytePos(
26
),
ctxt: #0
},
value: Atom('&' type=inline),
has_escape: false
}
)
)
)
}
),
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
27
),
hi: BytePos(
37
),
ctxt: #0
},
name: Ident(
Ident(
e,
Span {
lo: BytePos(
27
),
hi: BytePos(
28
),
ctxt: #0
}
)
),
value: Some(
Lit(
Str(
Str {
span: Span {
lo: BytePos(
29
),
hi: BytePos(
37
),
ctxt: #0
},
value: Atom('&ampr;' type=inline),
has_escape: false
}
)
)
)
}
)
],
self_closing: true
},
children: [],
closing: None
}
)
)
)
]
}

View File

@ -0,0 +1,2 @@
<a
/>

View File

@ -0,0 +1,59 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
5
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
5
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
a,
Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
5
),
ctxt: #0
},
attrs: [],
self_closing: true
},
children: [],
closing: None
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<日本語></>

View File

@ -0,0 +1,85 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
23
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
23
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
日本語,
Span {
lo: BytePos(
1
),
hi: BytePos(
10
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
11
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
13
),
hi: BytePos(
23
),
ctxt: #0
},
name: Ident(
Ident(
日本語,
Span {
lo: BytePos(
13
),
hi: BytePos(
22
),
ctxt: #0
}
)
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1,5 @@
<AbC-def
test="&#x0026;&#38;">
bar
baz
</AbC-def>

View File

@ -0,0 +1,160 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
51
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
51
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
AbC-def,
Span {
lo: BytePos(
1
),
hi: BytePos(
8
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
32
),
ctxt: #0
},
attrs: [
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
11
),
hi: BytePos(
31
),
ctxt: #0
},
name: Ident(
Ident(
test,
Span {
lo: BytePos(
11
),
hi: BytePos(
15
),
ctxt: #0
}
)
),
value: Some(
Lit(
Str(
Str {
span: Span {
lo: BytePos(
16
),
hi: BytePos(
31
),
ctxt: #0
},
value: Atom('&&' type=inline),
has_escape: false
}
)
)
)
}
)
],
self_closing: false
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
32
),
hi: BytePos(
41
),
ctxt: #0
},
value: Atom('
bar
baz
' type=dynamic),
raw: Atom('
bar
baz
' type=dynamic)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
43
),
hi: BytePos(
51
),
ctxt: #0
},
name: Ident(
Ident(
AbC-def,
Span {
lo: BytePos(
43
),
hi: BytePos(
50
),
ctxt: #0
}
)
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<a b={x ? <c /> : <d />} />

View File

@ -0,0 +1,200 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
27
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
27
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
a,
Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
27
),
ctxt: #0
},
attrs: [
JSXAttr(
JSXAttr {
span: Span {
lo: BytePos(
3
),
hi: BytePos(
24
),
ctxt: #0
},
name: Ident(
Ident(
b,
Span {
lo: BytePos(
3
),
hi: BytePos(
4
),
ctxt: #0
}
)
),
value: Some(
Cond(
CondExpr {
span: Span {
lo: BytePos(
6
),
hi: BytePos(
23
),
ctxt: #0
},
test: Ident(
Ident(
x,
Span {
lo: BytePos(
6
),
hi: BytePos(
7
),
ctxt: #0
}
)
),
cons: JSXElement(
JSXElement {
span: Span {
lo: BytePos(
10
),
hi: BytePos(
15
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
c,
Span {
lo: BytePos(
11
),
hi: BytePos(
12
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
11
),
hi: BytePos(
15
),
ctxt: #0
},
attrs: [],
self_closing: true
},
children: [],
closing: None
}
),
alt: JSXElement(
JSXElement {
span: Span {
lo: BytePos(
18
),
hi: BytePos(
23
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
d,
Span {
lo: BytePos(
19
),
hi: BytePos(
20
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
19
),
hi: BytePos(
23
),
ctxt: #0
},
attrs: [],
self_closing: true
},
children: [],
closing: None
}
)
}
)
)
}
)
],
self_closing: true
},
children: [],
closing: None
}
)
)
)
]
}

View File

@ -0,0 +1,4 @@
function x() {
let x
<div />
}

View File

@ -0,0 +1,148 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
34
),
ctxt: #0
},
body: [
Stmt(
Decl(
Fn(
FnDecl {
ident: Ident(
x,
Span {
lo: BytePos(
9
),
hi: BytePos(
10
),
ctxt: #0
}
),
function: Function {
params: [],
span: Span {
lo: BytePos(
0
),
hi: BytePos(
34
),
ctxt: #0
},
body: BlockStmt {
span: Span {
lo: BytePos(
13
),
hi: BytePos(
34
),
ctxt: #0
},
stmts: [
Decl(
Var(
VarDecl {
span: Span {
lo: BytePos(
17
),
hi: BytePos(
22
),
ctxt: #0
},
kind: "let",
decls: [
VarDeclarator {
span: Span {
lo: BytePos(
21
),
hi: BytePos(
22
),
ctxt: #0
},
name: Ident(
Ident(
x,
Span {
lo: BytePos(
21
),
hi: BytePos(
22
),
ctxt: #0
}
)
),
init: None
}
]
}
)
),
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
25
),
hi: BytePos(
32
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
div,
Span {
lo: BytePos(
26
),
hi: BytePos(
29
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
26
),
hi: BytePos(
32
),
ctxt: #0
},
attrs: [],
self_closing: true
},
children: [],
closing: None
}
)
)
]
},
is_generator: false,
is_async: false
}
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<a>{}</a>

View File

@ -0,0 +1,103 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
9
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
9
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
a,
Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
3
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [
JSXExprContainer(
JSXExprContainer {
expr: JSXEmptyExpr(
JSXEmptyExpr {
span: Span {
lo: BytePos(
4
),
hi: BytePos(
4
),
ctxt: #0
}
}
)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
7
),
hi: BytePos(
9
),
ctxt: #0
},
name: Ident(
Ident(
a,
Span {
lo: BytePos(
7
),
hi: BytePos(
8
),
ctxt: #0
}
)
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<A>&#x1f4a9;</A>

View File

@ -0,0 +1,101 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
16
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
16
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
A,
Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
1
),
hi: BytePos(
3
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
3
),
hi: BytePos(
12
),
ctxt: #0
},
value: Atom('💩' type=inline),
raw: Atom('💩' type=inline)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
14
),
hi: BytePos(
16
),
ctxt: #0
},
name: Ident(
Ident(
A,
Span {
lo: BytePos(
14
),
hi: BytePos(
15
),
ctxt: #0
}
)
)
}
)
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<></>

View File

@ -0,0 +1,53 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
5
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXFragment(
JSXFragment {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
5
),
ctxt: #0
},
opening: JSXOpeningFragment {
span: Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
},
children: [],
closing: JSXClosingFragment {
span: Span {
lo: BytePos(
4
),
hi: BytePos(
5
),
ctxt: #0
}
}
}
)
)
)
]
}

View File

@ -0,0 +1 @@
<>Hi, I'm a string!</>

View File

@ -0,0 +1,69 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
22
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXFragment(
JSXFragment {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
22
),
ctxt: #0
},
opening: JSXOpeningFragment {
span: Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
2
),
hi: BytePos(
19
),
ctxt: #0
},
value: Atom('Hi, I'm a string!' type=dynamic),
raw: Atom('Hi, I'm a string!' type=dynamic)
}
)
],
closing: JSXClosingFragment {
span: Span {
lo: BytePos(
21
),
hi: BytePos(
22
),
ctxt: #0
}
}
}
)
)
)
]
}

View File

@ -0,0 +1,6 @@
< >
<span>
hi
</span>
<div>bye</div>
</>

View File

@ -0,0 +1,287 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
50
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXFragment(
JSXFragment {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
50
),
ctxt: #0
},
opening: JSXOpeningFragment {
span: Span {
lo: BytePos(
2
),
hi: BytePos(
3
),
ctxt: #0
}
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
3
),
hi: BytePos(
6
),
ctxt: #0
},
value: Atom('
' type=inline),
raw: Atom('
' type=inline)
}
),
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
6
),
hi: BytePos(
29
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
span,
Span {
lo: BytePos(
7
),
hi: BytePos(
11
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
7
),
hi: BytePos(
12
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
12
),
hi: BytePos(
22
),
ctxt: #0
},
value: Atom('
hi
' type=dynamic),
raw: Atom('
hi
' type=dynamic)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
24
),
hi: BytePos(
29
),
ctxt: #0
},
name: Ident(
Ident(
span,
Span {
lo: BytePos(
24
),
hi: BytePos(
28
),
ctxt: #0
}
)
)
}
)
}
),
JSXText(
JSXText {
span: Span {
lo: BytePos(
29
),
hi: BytePos(
32
),
ctxt: #0
},
value: Atom('
' type=inline),
raw: Atom('
' type=inline)
}
),
JSXElement(
JSXElement {
span: Span {
lo: BytePos(
32
),
hi: BytePos(
46
),
ctxt: #0
},
opening: JSXOpeningElement {
name: Ident(
Ident(
div,
Span {
lo: BytePos(
33
),
hi: BytePos(
36
),
ctxt: #0
}
)
),
span: Span {
lo: BytePos(
33
),
hi: BytePos(
37
),
ctxt: #0
},
attrs: [],
self_closing: false
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
37
),
hi: BytePos(
40
),
ctxt: #0
},
value: Atom('bye' type=inline),
raw: Atom('bye' type=inline)
}
)
],
closing: Some(
JSXClosingElement {
span: Span {
lo: BytePos(
42
),
hi: BytePos(
46
),
ctxt: #0
},
name: Ident(
Ident(
div,
Span {
lo: BytePos(
42
),
hi: BytePos(
45
),
ctxt: #0
}
)
)
}
)
}
),
JSXText(
JSXText {
span: Span {
lo: BytePos(
46
),
hi: BytePos(
47
),
ctxt: #0
},
value: Atom('
' type=inline),
raw: Atom('
' type=inline)
}
)
],
closing: JSXClosingFragment {
span: Span {
lo: BytePos(
49
),
hi: BytePos(
50
),
ctxt: #0
}
}
}
)
)
)
]
}

View File

@ -0,0 +1,7 @@
<>
<>
<>
super deep
</>
</>
</>

View File

@ -0,0 +1,227 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
49
),
ctxt: #0
},
body: [
Stmt(
Expr(
JSXFragment(
JSXFragment {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
49
),
ctxt: #0
},
opening: JSXOpeningFragment {
span: Span {
lo: BytePos(
1
),
hi: BytePos(
2
),
ctxt: #0
}
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
2
),
hi: BytePos(
5
),
ctxt: #0
},
value: Atom('
' type=inline),
raw: Atom('
' type=inline)
}
),
JSXFragment(
JSXFragment {
span: Span {
lo: BytePos(
5
),
hi: BytePos(
45
),
ctxt: #0
},
opening: JSXOpeningFragment {
span: Span {
lo: BytePos(
6
),
hi: BytePos(
7
),
ctxt: #0
}
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
7
),
hi: BytePos(
12
),
ctxt: #0
},
value: Atom('
' type=inline),
raw: Atom('
' type=inline)
}
),
JSXFragment(
JSXFragment {
span: Span {
lo: BytePos(
12
),
hi: BytePos(
39
),
ctxt: #0
},
opening: JSXOpeningFragment {
span: Span {
lo: BytePos(
13
),
hi: BytePos(
14
),
ctxt: #0
}
},
children: [
JSXText(
JSXText {
span: Span {
lo: BytePos(
14
),
hi: BytePos(
36
),
ctxt: #0
},
value: Atom('
super deep
' type=dynamic),
raw: Atom('
super deep
' type=dynamic)
}
)
],
closing: JSXClosingFragment {
span: Span {
lo: BytePos(
38
),
hi: BytePos(
39
),
ctxt: #0
}
}
}
),
JSXText(
JSXText {
span: Span {
lo: BytePos(
39
),
hi: BytePos(
42
),
ctxt: #0
},
value: Atom('
' type=inline),
raw: Atom('
' type=inline)
}
)
],
closing: JSXClosingFragment {
span: Span {
lo: BytePos(
44
),
hi: BytePos(
45
),
ctxt: #0
}
}
}
),
JSXText(
JSXText {
span: Span {
lo: BytePos(
45
),
hi: BytePos(
46
),
ctxt: #0
},
value: Atom('
' type=inline),
raw: Atom('
' type=inline)
}
)
],
closing: JSXClosingFragment {
span: Span {
lo: BytePos(
48
),
hi: BytePos(
49
),
ctxt: #0
}
}
}
)
)
)
]
}

View File

@ -0,0 +1,7 @@
<
// comment1
/* comment2 */
>
<div></div>
<div></div>
</>

Some files were not shown because too many files have changed in this diff Show More