[compiler][be] Remove unused experimental Rust port

I wrote this a couple summers back as an experiment to see how easily we could translate the compiler to Rust. We make extensive use of in-place mutation of the IR, and the experiment proved that this we can get reasonable ergonomics for this in Rust which was cool. We've since ended up using some of the code here for Relay, allowing Relay Compiler to parse JS files to do more fine-grained extraction of data. For React Compiler though, we plan to continue using JavaScript and explore lightweight native wrappers for things like OXC and SWC plugins. We're also working with the Hermes team to eventually compile the compiler with Static Hermes.

As Tomo always says: always bet on JavaScript.

ghstack-source-id: c5770a2efc
Pull Request resolved: https://github.com/facebook/react/pull/32219
This commit is contained in:
Joe Savona
2025-01-24 12:19:10 -08:00
parent a6ce56d9a9
commit 9eabb37338
1146 changed files with 0 additions and 223571 deletions

View File

@@ -1,77 +0,0 @@
name: (Compiler) Rust
on:
push:
branches: ["main"]
paths:
- .github/workflows/**
- compiler/crates/**
- compiler/Cargo.*
- compiler/*.toml
pull_request:
paths:
- .github/workflows/**
- compiler/crates/**
- compiler/Cargo.*
- compiler/*.toml
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
TZ: /usr/share/zoneinfo/America/Los_Angeles
defaults:
run:
working-directory: compiler
jobs:
test:
name: Rust Test (${{ matrix.target.os }})
strategy:
matrix:
target:
- target: ubuntu-latest
os: ubuntu-latest
# TODO: run on more platforms
# - target: macos-latest
# os: macos-latest
# - target: windows-latest
# os: windows-latest
runs-on: ${{ matrix.target.os }}
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: cargo test
run: cargo test --manifest-path=Cargo.toml --locked ${{ matrix.target.features && '--features' }} ${{ matrix.target.features }}
lint:
name: Rust Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
# NOTE: use `rustup run <toolchain> <command>` in commands below
# with this exact same toolchain value
toolchain: nightly-2023-08-01
override: true
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: rustfmt
run: grep -r --include "*.rs" --files-without-match "@generated" crates | xargs rustup run nightly-2023-08-01 rustfmt --check --config="skip_children=true"
# - name: cargo clippy
# run: rustup run nightly-2023-08-01 cargo clippy -- -Dclippy::correctness
build:
name: Rust Build
runs-on: ubuntu-latest
# TODO: build on more platforms, deploy, etc
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: cargo build
run: cargo build --release

1217
compiler/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +0,0 @@
[workspace]
resolver = "2"
members = ["crates/*"]
[workspace.package]
authors = ["The React Team https://react.dev/community/team"]
description = "React Compiler"
edition = "2021"
homepage = "https://github.com/facebook/react"
keywords = ["JavaScript", "TypeScript", "React", "React Compiler", "Compiler"]
license = "MIT"
repository = "https://github.com/facebook/react"
[workspace.dependencies]
# workspace crates
react_build_hir = { path = "crates/react_build_hir" }
react_diagnostics = { path = "crates/react_diagnostics" }
react_estree = { path = "crates/react_estree" }
react_estree_codegen = { path = "crates/react_estree_codegen" }
react_fixtures = { path = "crates/react_fixtures" }
react_hermes_parser = { path = "crates/react_hermes_parser" }
react_hir = { path = "crates/react_hir" }
react_optimization = { path = "crates/react_optimization" }
react_semantic_analysis = { path = "crates/react_semantic_analysis" }
react_ssa = { path = "crates/react_ssa" }
react_utils = { path = "crates/react_utils" }
# dependencies
indexmap = { version = "2.0.0", features = ["serde"] }
insta = { version = "1.30.0", features = ["glob"] }
miette = { version = "5.9.0" }
prettyplease = "0.2.10"
quote = "1.0.29"
serde = { version = "1.0.167", features = ["serde_derive"] }
serde_json = "1.0.100"
stacker = "0.1.15"
static_assertions = "1.1.0"
syn = "2.0.23"
thiserror = "1.0.41"
hermes = { git = "https://github.com/facebook/hermes.git" }
juno_support = { git = "https://github.com/facebook/hermes.git" }
[profile.release]
# configuration adapted from oxc
# https://github.com/Boshen/oxc/blob/ea85ee9f2d64dd284c5b7410f491d81fb879abae/Cargo.toml#L89-L97
opt-level = 3
lto = "fat"
codegen-units = 1
strip = "symbols"
debug = false
panic = "abort" # Let it crash and force ourselves to write safe Rust.
# Make insta run faster by compiling with release mode optimizations
# https://docs.rs/insta/latest/insta/#optional-faster-runs
[profile.dev.package.insta]
opt-level = 3
# Make insta diffing libary faster by compiling with release mode optimizations
# https://docs.rs/insta/latest/insta/#optional-faster-runs
[profile.dev.package.similar]
opt-level = 3

View File

@@ -1,21 +0,0 @@
[package]
name = "react_build_hir"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
react_hir = { workspace = true }
react_estree = { workspace = true}
indexmap = { workspace = true }
react_diagnostics = { workspace = true }
react_semantic_analysis = { workspace = true }
thiserror = { workspace = true }

View File

@@ -1,3 +0,0 @@
# Build-HIR
This crate converts from `react_estree` into React Compiler's HIR format as the first phase of compilation.

View File

@@ -1,746 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::collections::HashSet;
use react_diagnostics::Diagnostic;
use react_estree::{
AssignmentPropertyOrRestElement, AssignmentTarget, BlockStatement, Expression,
ExpressionOrSpread, ExpressionOrSuper, ForInit, Function, IntoFunction, JsValue, Pattern,
Statement, VariableDeclaration, VariableDeclarationKind,
};
use react_hir::{
ArrayDestructureItem, BlockKind, BranchTerminal, Destructure, DestructurePattern, Environment,
ForTerminal, GotoKind, Identifier, IdentifierOperand, InstructionKind, InstructionValue,
JSXAttribute, JSXElement, LValue, LoadGlobal, LoadLocal, ObjectDestructureItem,
ObjectDestructureProperty, PlaceOrSpread, TerminalValue,
};
use crate::builder::{Builder, LoopScope};
use crate::context::get_context_identifiers;
use crate::error::BuildHIRError;
/// Converts a React function in ESTree format into HIR. Returns the HIR
/// if it was constructed sucessfully, otherwise a list of diagnostics
/// if the input could be not be converted to HIR.
///
/// Failures generally include nonsensical input (`delete 1`) or syntax
/// that is not yet supported.
pub fn build(env: &Environment, fun: &Function) -> Result<Box<react_hir::Function>, Diagnostic> {
let mut builder = Builder::new(env);
let mut params = Vec::with_capacity(fun.params.len());
for param in &fun.params {
match param {
Pattern::Identifier(param) => {
let identifier = lower_identifier_for_assignment(
env,
&mut builder,
InstructionKind::Let,
param,
)?;
params.push(identifier);
}
_ => {
return Err(Diagnostic::todo(
"Support non-identifier params",
param.range(),
));
}
}
}
match &fun.body {
Some(react_estree::FunctionBody::BlockStatement(body)) => {
lower_block_statement(env, &mut builder, body)?
}
Some(react_estree::FunctionBody::Expression(body)) => {
lower_expression(env, &mut builder, body)?;
}
None => {
return Err(Diagnostic::invalid_syntax(
BuildHIRError::EmptyFunction,
fun.range,
));
}
}
// In case the function did not explicitly return, terminate the final
// block with an explicit `return undefined`. If the function *did* return,
// this will be unreachable and get pruned later.
let implicit_return_value = builder.push(InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Undefined,
}));
builder.terminate(
TerminalValue::Return(react_hir::ReturnTerminal {
value: implicit_return_value,
}),
react_hir::BlockKind::Block,
);
let body = builder.build()?;
Ok(Box::new(react_hir::Function {
id: fun.id.as_ref().map(|id| id.name.clone()),
body,
params,
// TODO: populate context!
context: Default::default(),
is_async: fun.is_async,
is_generator: fun.is_generator,
}))
}
fn lower_block_statement(
env: &Environment,
builder: &mut Builder,
stmt: &BlockStatement,
) -> Result<(), Diagnostic> {
for stmt in &stmt.body {
lower_statement(env, builder, stmt, None)?;
}
Ok(())
}
/// Convert a statement to HIR. This will often result in multiple instructions and blocks
/// being created as statements often describe control flow.
fn lower_statement(
env: &Environment,
builder: &mut Builder,
stmt: &Statement,
label: Option<String>,
) -> Result<(), Diagnostic> {
match stmt {
Statement::BlockStatement(stmt) => {
lower_block_statement(env, builder, stmt)?;
}
Statement::BreakStatement(stmt) => {
let block = builder.resolve_break(stmt.label.as_ref())?;
builder.terminate(
TerminalValue::Goto(react_hir::GotoTerminal {
block,
kind: GotoKind::Break,
}),
BlockKind::Block,
);
}
Statement::ContinueStatement(stmt) => {
let block = builder.resolve_continue(stmt.label.as_ref())?;
builder.terminate(
TerminalValue::Goto(react_hir::GotoTerminal {
block,
kind: GotoKind::Continue,
}),
BlockKind::Block,
);
}
Statement::ReturnStatement(stmt) => {
let value = match &stmt.argument {
Some(argument) => lower_expression(env, builder, argument)?,
None => builder.push(InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Undefined,
})),
};
builder.terminate(
TerminalValue::Return(react_hir::ReturnTerminal { value }),
BlockKind::Block,
);
}
Statement::ExpressionStatement(stmt) => {
lower_expression(env, builder, &stmt.expression)?;
}
Statement::EmptyStatement(_) => {
// no-op
}
Statement::VariableDeclaration(stmt) => {
lower_variable_declaration(env, builder, stmt)?;
}
Statement::IfStatement(stmt) => {
// block for what follows the if statement, though this may
// not be reachable
let fallthrough_block = builder.reserve(BlockKind::Block);
let consequent_block = builder.enter(BlockKind::Block, |builder| {
lower_statement(env, builder, &stmt.consequent, None)?;
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: fallthrough_block.id,
kind: GotoKind::Break,
}))
})?;
let alternate_block = builder.enter(BlockKind::Block, |builder| {
if let Some(alternate) = &stmt.alternate {
lower_statement(env, builder, alternate, None)?;
}
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: fallthrough_block.id,
kind: GotoKind::Break,
}))
})?;
let test = lower_expression(env, builder, &stmt.test)?;
let terminal = TerminalValue::If(react_hir::IfTerminal {
test,
consequent: consequent_block,
alternate: alternate_block,
fallthrough: Some(fallthrough_block.id),
});
builder.terminate_with_fallthrough(terminal, fallthrough_block);
}
Statement::ForStatement(stmt) => {
// Block for the loop's test condition
let test_block = builder.reserve(BlockKind::Loop);
// Block for code following the loop
let fallthrough_block = builder.reserve(BlockKind::Block);
let init_block = builder.enter(BlockKind::Loop, |builder| {
if let Some(ForInit::VariableDeclaration(decl)) = &stmt.init {
lower_variable_declaration(env, builder, decl)?;
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: test_block.id,
kind: GotoKind::Break,
}))
} else {
Err(Diagnostic::todo(
BuildHIRError::ForStatementIsMissingInitializer,
None,
))
}
})?;
let update_block = stmt
.update
.as_ref()
.map(|update| {
builder.enter(BlockKind::Loop, |builder| {
lower_expression(env, builder, update)?;
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: test_block.id,
kind: GotoKind::Break,
}))
})
})
.transpose()?;
let body_block = builder.enter(BlockKind::Block, |builder| {
let loop_ = LoopScope {
label,
continue_block: update_block.unwrap_or(test_block.id),
break_block: fallthrough_block.id,
};
builder.enter_loop(loop_, |builder| {
lower_statement(env, builder, &stmt.body, None)?;
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: update_block.unwrap_or(test_block.id),
kind: GotoKind::Continue,
}))
})
})?;
let terminal = TerminalValue::For(ForTerminal {
body: body_block,
init: init_block,
test: test_block.id,
fallthrough: fallthrough_block.id,
update: update_block,
});
builder.terminate_with_fallthrough(terminal, test_block);
if let Some(test) = &stmt.test {
let test_value = lower_expression(env, builder, test)?;
let terminal = TerminalValue::Branch(BranchTerminal {
test: test_value,
consequent: body_block,
alternate: fallthrough_block.id,
});
builder.terminate_with_fallthrough(terminal, fallthrough_block);
} else {
return Err(Diagnostic::todo(
BuildHIRError::ForStatementIsMissingTest,
stmt.range,
));
}
}
_ => todo!("Lower {stmt:#?}"),
}
Ok(())
}
fn lower_variable_declaration(
env: &Environment,
builder: &mut Builder,
stmt: &VariableDeclaration,
) -> Result<(), Diagnostic> {
let kind = match stmt.kind {
VariableDeclarationKind::Const => InstructionKind::Const,
VariableDeclarationKind::Let => InstructionKind::Let,
VariableDeclarationKind::Var => {
return Err(Diagnostic::unsupported(
BuildHIRError::VariableDeclarationKindIsVar,
stmt.range,
));
}
};
for declaration in &stmt.declarations {
if let Some(init) = &declaration.init {
let value = lower_expression(env, builder, init)?;
lower_assignment_pattern(env, builder, kind, &declaration.id, value)?;
} else {
match &declaration.id {
Pattern::Identifier(id) => {
let identifier = env.resolve_variable_declaration(id.as_ref(), &id.name);
if let Some(identifier) = identifier {
builder.push(InstructionValue::DeclareLocal(react_hir::DeclareLocal {
lvalue: LValue {
identifier: IdentifierOperand {
identifier,
effect: None,
},
kind,
},
}));
} else {
return Err(Diagnostic::invariant(
BuildHIRError::VariableDeclarationBindingIsNonLocal,
id.range,
));
}
}
_ => {
return Err(Diagnostic::invalid_syntax(
"Expected an identifier for variable declaration without an intializer. Destructuring requires an initial value",
declaration.range,
));
}
}
}
}
Ok(())
}
/// Converts an ESTree Expression into an HIR InstructionValue. Note that while only a single
/// InstructionValue is returned, this function is recursive and may cause multiple instructions
/// to be emitted, possibly across multiple basic blocks (in the case of expressions with control
/// flow semenatics such as logical, conditional, and optional expressions).
fn lower_expression(
env: &Environment,
builder: &mut Builder,
expr: &Expression,
) -> Result<IdentifierOperand, Diagnostic> {
let value = match expr {
Expression::Identifier(expr) => {
let identifier = env.resolve_variable_reference(expr.as_ref());
if let Some(identifier) = identifier {
let place = IdentifierOperand {
effect: None,
identifier,
};
InstructionValue::LoadLocal(LoadLocal { place })
} else {
InstructionValue::LoadGlobal(LoadGlobal {
name: expr.name.clone(),
})
}
}
Expression::Literal(expr) => InstructionValue::Primitive(react_hir::Primitive {
value: expr.value.clone(),
}),
Expression::NumericLiteral(expr) => InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Number(expr.value),
}),
Expression::BooleanLiteral(expr) => InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Boolean(expr.value),
}),
Expression::StringLiteral(expr) => InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::String(expr.value.clone()),
}),
Expression::NullLiteral(_expr) => InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Null,
}),
Expression::ArrayExpression(expr) => {
let mut elements = Vec::with_capacity(expr.elements.len());
for expr in &expr.elements {
let element = match expr {
Some(react_estree::ExpressionOrSpread::SpreadElement(expr)) => Some(
PlaceOrSpread::Spread(lower_expression(env, builder, &expr.argument)?),
),
Some(react_estree::ExpressionOrSpread::Expression(expr)) => {
Some(PlaceOrSpread::Place(lower_expression(env, builder, expr)?))
}
None => None,
};
elements.push(element);
}
InstructionValue::Array(react_hir::Array { elements })
}
Expression::AssignmentExpression(expr) => match expr.operator {
react_estree::AssignmentOperator::Equals => {
let right = lower_expression(env, builder, &expr.right)?;
return lower_assignment(
env,
builder,
InstructionKind::Reassign,
&expr.left,
right,
);
}
_ => todo!("lower assignment expr {:#?}", expr),
},
Expression::BinaryExpression(expr) => {
let left = lower_expression(env, builder, &expr.left)?;
let right = lower_expression(env, builder, &expr.right)?;
InstructionValue::Binary(react_hir::Binary {
left,
operator: expr.operator,
right,
})
}
Expression::FunctionExpression(expr) => {
InstructionValue::Function(lower_function(env, builder, expr.as_ref())?)
}
Expression::ArrowFunctionExpression(expr) => {
InstructionValue::Function(lower_function(env, builder, expr.as_ref())?)
}
Expression::CallExpression(expr) => {
let callee_expr = match &expr.callee {
ExpressionOrSuper::Super(callee) => {
return Err(Diagnostic::unsupported(
BuildHIRError::UnsupportedSuperExpression,
callee.range,
));
}
ExpressionOrSuper::Expression(callee) => callee,
};
if matches!(&callee_expr, Expression::MemberExpression(_)) {
return Err(Diagnostic::todo("Support method calls", expr.range));
}
let callee = lower_expression(env, builder, callee_expr)?;
let arguments = lower_arguments(env, builder, &expr.arguments)?;
InstructionValue::Call(react_hir::Call { callee, arguments })
}
Expression::JSXElement(expr) => {
InstructionValue::JSXElement(lower_jsx_element(env, builder, expr)?)
}
_ => todo!("Lower expr {expr:#?}"),
};
Ok(builder.push(value))
}
fn lower_arguments(
env: &Environment,
builder: &mut Builder,
args: &[ExpressionOrSpread],
) -> Result<Vec<PlaceOrSpread>, Diagnostic> {
let mut arguments = Vec::with_capacity(args.len());
for arg in args {
let element = match arg {
react_estree::ExpressionOrSpread::SpreadElement(arg) => {
PlaceOrSpread::Spread(lower_expression(env, builder, &arg.argument)?)
}
react_estree::ExpressionOrSpread::Expression(arg) => {
PlaceOrSpread::Place(lower_expression(env, builder, arg)?)
}
};
arguments.push(element);
}
Ok(arguments)
}
fn lower_function<T: IntoFunction>(
env: &Environment,
_builder: &mut Builder,
function: &T,
) -> Result<react_hir::FunctionExpression, Diagnostic> {
let context_identifiers = get_context_identifiers(env, function);
let mut context = Vec::new();
let mut seen = HashSet::new();
for declaration_id in context_identifiers {
if let Some(identifier) = env.resolve_declaration_id(declaration_id) {
if !seen.insert(identifier.id) {
continue;
}
context.push(IdentifierOperand {
effect: None,
identifier,
});
}
}
let mut fun = build(env, function.function())?;
fun.context = context;
Ok(react_hir::FunctionExpression {
// TODO: collect dependencies!
dependencies: Default::default(),
lowered_function: fun,
})
}
fn lower_jsx_element(
env: &Environment,
builder: &mut Builder,
expr: &react_estree::JSXElement,
) -> Result<JSXElement, Diagnostic> {
let props: Result<Vec<JSXAttribute>, Diagnostic> = expr
.opening_element
.attributes
.iter()
.map(|attr| lower_jsx_attribute(env, builder, attr))
.collect();
let _props = props?;
let children: Result<Vec<IdentifierOperand>, Diagnostic> = expr
.children
.iter()
.map(|child| {
let child = lower_jsx_child(env, builder, child)?;
Ok(child)
})
.collect();
let _children = children?;
todo!("lower jsx element");
// Ok(JSXElement {
// tag: todo!(),
// props,
// children: if children.is_empty() {
// None
// } else {
// Some(children)
// },
// })
}
fn lower_jsx_attribute(
_env: &Environment,
_builder: &mut Builder,
_attr: &react_estree::JSXAttributeOrSpread,
) -> Result<JSXAttribute, Diagnostic> {
todo!("lower jsx attribute")
}
fn lower_jsx_child(
_env: &Environment,
_builder: &mut Builder,
_child: &react_estree::JSXChildItem,
) -> Result<IdentifierOperand, Diagnostic> {
todo!("lower jsx child")
}
fn lower_assignment(
env: &Environment,
builder: &mut Builder,
kind: InstructionKind,
lvalue: &AssignmentTarget,
value: IdentifierOperand,
) -> Result<IdentifierOperand, Diagnostic> {
Ok(match lvalue {
AssignmentTarget::Pattern(lvalue) => {
lower_assignment_pattern(env, builder, kind, lvalue, value)?
}
_ => todo!("lower assignment for {:#?}", lvalue),
})
}
// TODO: change the success type to void, no caller uses it
fn lower_assignment_pattern(
env: &Environment,
builder: &mut Builder,
kind: InstructionKind,
lvalue: &Pattern,
value: IdentifierOperand,
) -> Result<IdentifierOperand, Diagnostic> {
Ok(match lvalue {
Pattern::Identifier(lvalue) => {
let identifier = lower_identifier_for_assignment(env, builder, kind, lvalue)?;
builder.push(InstructionValue::StoreLocal(react_hir::StoreLocal {
lvalue: LValue { identifier, kind },
value,
}))
}
Pattern::ArrayPattern(lvalue) => {
let mut items = Vec::with_capacity(lvalue.elements.len());
let mut followups: Vec<(Identifier, &Pattern)> = Vec::new();
for element in &lvalue.elements {
match element {
None => items.push(ArrayDestructureItem::Hole),
Some(Pattern::Identifier(element)) => {
let identifier =
lower_identifier_for_assignment(env, builder, kind, element)?;
items.push(ArrayDestructureItem::Value(identifier));
}
Some(Pattern::RestElement(element)) => {
if let Pattern::Identifier(element) = &element.argument {
let identifier = lower_identifier_for_assignment(
env,
builder,
kind,
element.as_ref(),
)?;
items.push(ArrayDestructureItem::Spread(identifier));
} else {
let temporary = env.new_temporary();
items.push(ArrayDestructureItem::Spread(IdentifierOperand {
identifier: temporary.clone(),
effect: None,
}));
followups.push((temporary, &element.argument));
}
}
Some(element) => {
let temporary = env.new_temporary();
items.push(ArrayDestructureItem::Value(IdentifierOperand {
identifier: temporary.clone(),
effect: None,
}));
followups.push((temporary, element));
}
}
}
let temporary = builder.push(InstructionValue::Destructure(Destructure {
kind,
pattern: DestructurePattern::Array(items),
value,
}));
for (temporary, pattern) in followups {
lower_assignment_pattern(
env,
builder,
kind,
pattern,
IdentifierOperand {
identifier: temporary,
effect: None,
},
)?;
}
temporary
}
Pattern::ObjectPattern(lvalue) => {
let mut properties = Vec::with_capacity(lvalue.properties.len());
let mut followups: Vec<(Identifier, &Pattern)> = Vec::new();
for property in &lvalue.properties {
match property {
AssignmentPropertyOrRestElement::RestElement(property) => {
if let Pattern::Identifier(element) = &property.argument {
let identifier = lower_identifier_for_assignment(
env,
builder,
kind,
element.as_ref(),
)?;
properties.push(ObjectDestructureItem::Spread(identifier));
} else {
let temporary = env.new_temporary();
properties.push(ObjectDestructureItem::Spread(IdentifierOperand {
identifier: temporary.clone(),
effect: None,
}));
followups.push((temporary, &property.argument));
}
}
AssignmentPropertyOrRestElement::AssignmentProperty(property) => {
if property.is_computed {
return Err(Diagnostic::todo(
"Handle computed properties in ObjectPattern",
property.range,
));
}
let key = if let Expression::Identifier(key) = &property.key {
key.name.as_str()
} else {
return Err(Diagnostic::todo(
"Support non-identifier object keys in non-computed ObjectPattern",
property.range,
));
};
if let Pattern::Identifier(value) = &property.value {
let value = lower_identifier_for_assignment(env, builder, kind, value)?;
properties.push(ObjectDestructureItem::Property(
ObjectDestructureProperty {
name: key.to_string(),
value,
},
));
} else {
let temporary = env.new_temporary();
properties.push(ObjectDestructureItem::Property(
ObjectDestructureProperty {
name: key.to_string(),
value: IdentifierOperand {
identifier: temporary.clone(),
effect: None,
},
},
));
followups.push((temporary, &property.value));
}
}
}
}
let temporary = builder.push(InstructionValue::Destructure(Destructure {
kind,
pattern: DestructurePattern::Object(properties),
value,
}));
for (temporary, pattern) in followups {
lower_assignment_pattern(
env,
builder,
kind,
pattern,
IdentifierOperand {
identifier: temporary,
effect: None,
},
)?;
}
temporary
}
_ => todo!("lower assignment pattern for {:#?}", lvalue),
})
}
fn lower_identifier_for_assignment(
env: &Environment,
_builder: &mut Builder,
kind: InstructionKind,
node: &react_estree::Identifier,
) -> Result<IdentifierOperand, Diagnostic> {
match kind {
InstructionKind::Reassign => {
let identifier = env.resolve_variable_reference(node);
if let Some(identifier) = identifier {
Ok(IdentifierOperand {
identifier,
effect: None,
})
} else {
// Reassigning a global
Err(
Diagnostic::invalid_react(BuildHIRError::ReassignedGlobal, node.range)
.annotate(format!("Cannot reassign `{}`", &node.name), node.range),
)
}
}
_ => {
// Declaration
let identifier = env.resolve_variable_declaration(node, &node.name).unwrap();
Ok(IdentifierOperand {
identifier,
effect: None,
})
}
}
}

View File

@@ -1,322 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::cell::RefCell;
use std::rc::Rc;
use react_diagnostics::Diagnostic;
use react_hir::{
initialize_hir, BasicBlock, BlockId, BlockKind, Blocks, Environment, GotoKind, IdentifierData,
IdentifierOperand, InstrIx, Instruction, InstructionIdGenerator, InstructionValue, Terminal,
TerminalValue, Type, HIR,
};
use crate::BuildHIRError;
/// Helper struct used when converting from ESTree to HIR. Includes:
/// - Variable resolution
/// - Label resolution (for labeled statements and break/continue)
/// - Access to the environment
///
/// As well as representing the incomplete form of the HIR. Usage
/// generally involves driving calls to enter/exit blocks, resolve
/// labels and variables, and then calling `build()` when the HIR
/// is complete.
pub(crate) struct Builder<'e> {
#[allow(dead_code)]
environment: &'e Environment,
completed: Blocks,
instructions: Vec<Instruction>,
entry: BlockId,
wip: WipBlock,
id_gen: InstructionIdGenerator,
scopes: Vec<ControlFlowScope>,
}
pub(crate) struct WipBlock {
pub id: BlockId,
pub kind: BlockKind,
pub instructions: Vec<InstrIx>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
enum ControlFlowScope {
Loop(LoopScope),
// Switch(SwitchScope),
#[allow(dead_code)]
Label(LabelScope),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct LoopScope {
pub label: Option<String>,
pub continue_block: BlockId,
pub break_block: BlockId,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct LabelScope {
pub label: String,
pub block: BlockId,
}
impl ControlFlowScope {
fn label(&self) -> Option<&String> {
match self {
Self::Loop(scope) => scope.label.as_ref(),
Self::Label(scope) => Some(&scope.label),
}
}
fn break_block(&self) -> BlockId {
match self {
Self::Loop(scope) => scope.break_block,
Self::Label(scope) => scope.block,
}
}
}
impl<'e> Builder<'e> {
pub(crate) fn new(environment: &'e Environment) -> Self {
let entry = environment.next_block_id();
let current = WipBlock {
id: entry,
kind: BlockKind::Block,
instructions: Default::default(),
};
Self {
environment,
completed: Default::default(),
instructions: Default::default(),
entry,
wip: current,
id_gen: InstructionIdGenerator::new(),
scopes: Default::default(),
}
}
/// Completes the builder and returns the HIR if it was valid,
/// or a Diagnostic if a validation error occured.
///
/// TODO: refine the type, only invariants should be possible here,
/// not other types of errors
pub(crate) fn build(self) -> Result<HIR, Diagnostic> {
let mut hir = HIR {
entry: self.entry,
blocks: self.completed,
instructions: self.instructions,
};
// Run all the initialization passes
initialize_hir(&mut hir)?;
Ok(hir)
}
/// Adds a new instruction to the end of the work in progress block
pub(crate) fn push(&mut self, value: InstructionValue) -> IdentifierOperand {
let lvalue = IdentifierOperand {
identifier: self.environment.new_temporary(),
effect: None,
};
let instr = Instruction {
id: self.id_gen.next(),
lvalue: lvalue.clone(),
value,
};
let ix = InstrIx::new(self.instructions.len() as u32);
self.instructions.push(instr);
self.wip.instructions.push(ix);
lvalue
}
/// Terminates the work in progress block with the given terminal, and starts a new
/// work in progress block with the given kind
pub(crate) fn terminate(&mut self, terminal: TerminalValue, next_kind: BlockKind) {
let next_wip = WipBlock {
id: self.environment.next_block_id(),
kind: next_kind,
instructions: Default::default(),
};
self.terminate_with_fallthrough(terminal, next_wip)
}
pub(crate) fn terminate_with_fallthrough(
&mut self,
terminal: TerminalValue,
fallthrough: WipBlock,
) {
let prev_wip = std::mem::replace(&mut self.wip, fallthrough);
self.completed.insert(Box::new(BasicBlock {
id: prev_wip.id,
kind: prev_wip.kind,
instructions: prev_wip.instructions,
terminal: Terminal {
id: self.id_gen.next(),
value: terminal,
},
predecessors: Default::default(),
phis: Default::default(),
}));
}
pub(crate) fn reserve(&mut self, kind: BlockKind) -> WipBlock {
WipBlock {
id: self.environment.next_block_id(),
kind,
instructions: Default::default(),
}
}
pub(crate) fn enter<F>(&mut self, kind: BlockKind, f: F) -> Result<BlockId, Diagnostic>
where
F: FnOnce(&mut Self) -> Result<TerminalValue, Diagnostic>,
{
let wip = self.reserve(kind);
let id = wip.id;
self.enter_reserved(wip, f)?;
Ok(id)
}
fn enter_reserved<F>(&mut self, wip: WipBlock, f: F) -> Result<(), Diagnostic>
where
F: FnOnce(&mut Self) -> Result<TerminalValue, Diagnostic>,
{
let current = std::mem::replace(&mut self.wip, wip);
let (result, terminal) = match f(self) {
Ok(terminal) => (Ok(()), terminal),
Err(error) => (
Err(error),
// TODO: add a `Terminal::Error` variant
TerminalValue::Goto(react_hir::GotoTerminal {
block: current.id,
kind: GotoKind::Break,
}),
),
};
let completed = std::mem::replace(&mut self.wip, current);
self.completed.insert(Box::new(BasicBlock {
id: completed.id,
kind: completed.kind,
instructions: completed.instructions,
terminal: Terminal {
id: self.id_gen.next(),
value: terminal,
},
predecessors: Default::default(),
phis: Default::default(),
}));
result
}
pub(crate) fn enter_loop<F>(
&mut self,
scope: LoopScope,
f: F,
) -> Result<TerminalValue, Diagnostic>
where
F: FnOnce(&mut Self) -> Result<TerminalValue, Diagnostic>,
{
self.scopes.push(ControlFlowScope::Loop(scope.clone()));
let terminal = f(self);
let last = self.scopes.pop().unwrap();
assert_eq!(last, ControlFlowScope::Loop(scope));
terminal
}
/// Returns a new temporary identifier
/// This may be necessary for destructuring with default values. there
/// we synthesize a temporary identifier to store the possibly-missing value
/// into, and emit a later StoreLocal for the original identifier
#[allow(dead_code)]
pub(crate) fn make_temporary(&self) -> react_hir::Identifier {
react_hir::Identifier {
id: self.environment.next_identifier_id(),
name: None,
data: Rc::new(RefCell::new(IdentifierData {
mutable_range: Default::default(),
scope: None,
type_: Type::Var(self.environment.next_type_var_id()),
})),
}
}
/// Resolves the target for the given break label (if present), or returns the default
/// break target given the current context. Returns a diagnostic if the label is
/// provided but cannot be resolved.
pub(crate) fn resolve_break(
&self,
label: Option<&react_estree::Identifier>,
) -> Result<BlockId, Diagnostic> {
for scope in self.scopes.iter().rev() {
match (label, scope.label()) {
// If this is an unlabeled break, return the most recent break target
(None, _) => return Ok(scope.break_block()),
// If the break is labeled and matches the current scope, return its break target
(Some(label), Some(scope_label)) if &label.name == scope_label => {
return Ok(scope.break_block());
}
// Otherwise keep searching
_ => continue,
}
}
Err(Diagnostic::invalid_syntax(
BuildHIRError::UnresolvedBreakTarget,
None,
))
}
/// Resolves the target for the given continue label (if present), or returns the default
/// continue target given the current context. Returns a diagnostic if the label is
/// provided but cannot be resolved.
pub(crate) fn resolve_continue(
&self,
label: Option<&react_estree::Identifier>,
) -> Result<BlockId, Diagnostic> {
for scope in self.scopes.iter().rev() {
match scope {
ControlFlowScope::Loop(scope) => {
match (label, &scope.label) {
// If this is an unlabeled continue, return the first matching loop
(None, _) => return Ok(scope.continue_block),
// If the continue is labeled and matches the current scope, return its continue target
(Some(label), Some(scope_label))
if label.name.as_str() == scope_label.as_str() =>
{
return Ok(scope.continue_block);
}
// Otherwise keep searching
_ => continue,
}
}
_ => {
match (label, scope.label()) {
(Some(label), Some(scope_label)) if label.name.as_str() == scope_label => {
// Error, the continue referred to a label that is not a loop
return Err(Diagnostic::invalid_syntax(
BuildHIRError::ContinueTargetIsNotALoop,
None,
));
}
_ => continue,
}
}
}
}
Err(Diagnostic::invalid_syntax(
BuildHIRError::UnresolvedContinueTarget,
None,
))
}
}

View File

@@ -1,44 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::collections::HashSet;
use react_estree::IntoFunction;
use react_hir::Environment;
use react_semantic_analysis::{DeclarationId, ScopeView};
pub(crate) fn get_context_identifiers<T: IntoFunction>(
env: &Environment,
node: &T,
) -> Vec<DeclarationId> {
let function_scope = env.scope(node.function()).unwrap();
let mut free = FreeVariables::default();
let mut seen = HashSet::new();
populate_free_variable_references(&mut free, &mut seen, function_scope);
free
}
type FreeVariables = Vec<DeclarationId>;
fn populate_free_variable_references(
free: &mut FreeVariables,
seen: &mut HashSet<DeclarationId>,
scope: ScopeView<'_>,
) {
for reference in scope.references() {
if !seen.insert(reference.declaration().id()) {
continue;
}
let declaration_scope = reference.declaration().scope();
if !declaration_scope.is_descendant_of(scope) {
free.push(reference.declaration().id())
}
}
for child in scope.children() {
populate_free_variable_references(free, seen, child);
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use thiserror::Error;
/// Errors which can occur during HIR construction
#[derive(Error, Debug)]
pub enum BuildHIRError {
/// ErrorSeverity::Unsupported
#[error(
"Variable declarations must be `let` or `const`, `var` declarations are not supported"
)]
VariableDeclarationKindIsVar,
/// ErrorSeverity::Invariant
#[error("Invariant: Expected variable declaration to declare a fresh binding")]
VariableDeclarationBindingIsNonLocal,
/// ErrorSeverity::Todo
#[error("`for` statements must have an initializer, eg `for (**let i = 0**; ...)`")]
ForStatementIsMissingInitializer,
/// ErrorSeverity::Todo
#[error(
"`for` statements must have a test condition, eg `for (let i = 0; **i < count**; ...)`"
)]
ForStatementIsMissingTest,
/// ErrorSeverity::Invariant
#[error("Invariant: Expected an expression node")]
NonExpressionInExpressionPosition,
/// ErrorSeverity::InvalidReact
#[error("React functions may not reassign variables defined outside of the component or hook")]
ReassignedGlobal,
/// ErrorSeverity::InvalidSyntax
#[error("Could not resolve a target for `break` statement")]
UnresolvedBreakTarget,
/// ErrorSeverity::InvalidSyntax
#[error("Could not resolve a target for `continue` statement")]
UnresolvedContinueTarget,
/// ErrorSeverity::InvalidSyntax
#[error("Labeled `continue` statements must use the label of a loop statement")]
ContinueTargetIsNotALoop,
/// ErrorSeverity::Invariant
#[error("Invariant: Identifier was not resolved (did name resolution run successfully?)")]
UnknownIdentifier,
/// ErrorSeverity::InvalidSyntax
#[error("Expected function to have a body")]
EmptyFunction,
#[error("`super` is not suppported")]
UnsupportedSuperExpression,
}

View File

@@ -1,14 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
mod build;
mod builder;
mod context;
mod error;
pub use build::build;
pub use error::*;

View File

@@ -1,23 +0,0 @@
[package]
name = "react_diagnostics"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# TODO: extract SourceRange into a separate crate so that
# we don't depend on full estree here
react_estree = { workspace = true }
# TODO: consider extracting a separate react_miette crate which does
# the translation from react_diagnostics::Diagnostic to miette::Diagnostic
miette = { workspace = true }
thiserror = { workspace = true }
static_assertions = { workspace = true }

View File

@@ -1,10 +0,0 @@
# react_diagnostics
Types for representing compiler diagnostics. Includes a general-purpose representation
of diagnostics with related information which can be converted into `miette::Diagnostic` to exploit miette's pretty printing of errors.
Unlike miette, lsp_types, and other diagnostic libraries, the error severities match
React Compiler's semantics. The intent is that a given diagnostic may be displayed as
an error, warning, or not displayed at all depending on the context in which the
compiler is being used. For example, an ESLint plugin powered by React Compiler may ignore
InvalidSyntax diagnostics, whereas the regular compiler may report them as errors.

View File

@@ -1,285 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::error::Error;
use std::fmt::{Debug, Display, Write};
use miette::SourceSpan;
use react_estree::SourceRange;
use static_assertions::assert_impl_all;
use thiserror::Error;
pub type Diagnostics = Vec<Diagnostic>;
pub type DiagnosticsResult<T> = Result<T, Diagnostics>;
#[derive(Debug)]
pub struct WithDiagnostics<T> {
pub item: T,
pub diagnostics: Vec<Diagnostic>,
}
impl<T> From<WithDiagnostics<T>> for Result<T, Diagnostics> {
fn from(s: WithDiagnostics<T>) -> Result<T, Diagnostics> {
if s.diagnostics.is_empty() {
Ok(s.item)
} else {
Err(s.diagnostics)
}
}
}
pub fn diagnostics_result<T>(result: T, diagnostics: Diagnostics) -> DiagnosticsResult<T> {
if diagnostics.is_empty() {
Ok(result)
} else {
Err(diagnostics)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Error)]
pub enum DiagnosticSeverity {
/// A feature that is intended to work but not yet implemented
#[error("Not implemented")]
Todo,
/// Syntax that is valid but intentionally not supported
#[error("Unsupported")]
Unsupported,
/// Invalid syntax
#[error("Invalid JavaScript")]
InvalidSyntax,
/// Valid syntax, but invalid React
#[error("Invalid React")]
InvalidReact,
/// Internal compiler error (ICE)
#[error("Internal error")]
Invariant,
}
/// A diagnostic message as a result of validating some code. This struct is
/// modeled after the LSP Diagnostic type:
/// https://microsoft.github.io/language-server-protocol/specification#diagnostic
///
/// Changes from LSP:
/// - `location` is different from LSP in that it's a file + span instead of
/// just a span.
/// - Unused fields are omitted.
/// - Severity is a custom enum that represents React-specific categories of error.
/// The translation to an LSP error/warning/etc depends on compiler settings and
/// invocation context.
#[derive(Debug)]
pub struct Diagnostic(Box<DiagnosticData>);
impl Diagnostic {
fn with_severity<T: 'static + DiagnosticDisplay>(
severity: DiagnosticSeverity,
message: T,
range: Option<SourceRange>,
) -> Self {
Self(Box::new(DiagnosticData {
message: Box::new(message),
span: range.map(source_span_from_range),
related_information: Vec::new(),
severity,
data: Vec::new(),
}))
}
/// Creates a new Todo Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn todo<T: 'static + DiagnosticDisplay>(message: T, range: Option<SourceRange>) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::Todo, message, range)
}
/// Creates a new Unsupported Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn unsupported<T: 'static + DiagnosticDisplay>(
message: T,
range: Option<SourceRange>,
) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::Unsupported, message, range)
}
/// Creates a new InvalidSyntax Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn invalid_syntax<T: 'static + DiagnosticDisplay>(
message: T,
range: Option<SourceRange>,
) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::InvalidSyntax, message, range)
}
/// Creates a new InvalidReact Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn invalid_react<T: 'static + DiagnosticDisplay>(
message: T,
range: Option<SourceRange>,
) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::InvalidReact, message, range)
}
/// Creates a new InvalidReact Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn invariant<T: 'static + DiagnosticDisplay>(
message: T,
range: Option<SourceRange>,
) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::Invariant, message, range)
}
/// Annotates this error with an additional location and associated message.
pub fn annotate<T: 'static + DiagnosticDisplay>(
mut self,
message: T,
range: Option<SourceRange>,
) -> Self {
self.0
.related_information
.push(DiagnosticRelatedInformation {
message: Box::new(message),
span: range.map(source_span_from_range),
});
self
}
pub fn message(&self) -> &impl DiagnosticDisplay {
&self.0.message
}
pub fn span(&self) -> Option<SourceSpan> {
self.0.span
}
pub fn get_data(&self) -> &[impl DiagnosticDisplay] {
&self.0.data
}
pub fn severity(&self) -> DiagnosticSeverity {
self.0.severity
}
pub fn related_information(&self) -> &[DiagnosticRelatedInformation] {
&self.0.related_information
}
pub fn print_without_source(&self) -> String {
let mut result = String::new();
writeln!(
result,
"{message}:{span:?}",
message = &self.0.message,
span = self.0.span
)
.unwrap();
if !self.0.related_information.is_empty() {
for (ix, related) in self.0.related_information.iter().enumerate() {
writeln!(
result,
"[related {ix}] {message}:{span:?}",
ix = ix + 1,
message = related.message,
span = related.span
)
.unwrap();
}
};
result
}
}
impl Display for Diagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.message)
}
}
impl Error for Diagnostic {}
impl miette::Diagnostic for Diagnostic {
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
Some(Box::new(self.0.message.to_string()))
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
let related_items = &self.0.related_information;
let mut spans: Vec<miette::LabeledSpan> = Vec::new();
for related in related_items {
if let Some(span) = related.span {
spans.push(miette::LabeledSpan::new_with_span(
Some(related.message.to_string()),
span,
))
}
}
if spans.is_empty() {
if let Some(span) = self.0.span {
spans.push(miette::LabeledSpan::new_with_span(
Some(self.0.message.to_string()),
span,
))
}
}
Some(Box::new(spans.into_iter()))
}
}
// Ensure Diagnostic is thread-safe
assert_impl_all!(Diagnostic: Send, Sync);
#[derive(Debug)]
struct DiagnosticData {
/// Human readable error message.
message: Box<dyn DiagnosticDisplay>,
/// The primary location of this diagnostic.
span: Option<SourceSpan>,
/// Related diagnostic information, such as other definitions in the case of
/// a duplicate definition error.
related_information: Vec<DiagnosticRelatedInformation>,
severity: DiagnosticSeverity,
/// A list with data that can be passed to the code actions
/// `data` is used in the LSP protocol:
/// @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
data: Vec<Box<dyn DiagnosticDisplay>>,
}
/// Secondary locations attached to a diagnostic.
#[derive(Debug)]
pub struct DiagnosticRelatedInformation {
/// The message of this related diagnostic information.
pub message: Box<dyn DiagnosticDisplay>,
/// The location of this related diagnostic information.
pub span: Option<SourceSpan>,
}
/// Trait for diagnostic messages to allow structs that capture
/// some data and can lazily convert it to a message.
pub trait DiagnosticDisplay: Debug + Display + Send + Sync {}
/// Automatically implement the trait if constraints are met, so that
/// implementors don't need to.
impl<T> DiagnosticDisplay for T where T: Debug + Display + Send + Sync {}
impl From<Diagnostic> for Diagnostics {
fn from(diagnostic: Diagnostic) -> Self {
vec![diagnostic]
}
}
fn source_span_from_range(range: SourceRange) -> SourceSpan {
SourceSpan::new(
(range.start as usize).into(),
((u32::from(range.end) - range.start) as usize).into(),
)
}

View File

@@ -1,19 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
mod diagnostic;
pub use diagnostic::*;
/// Returns Ok(()) if the condition is true, otherwise returns Err()
/// with the diagnostic produced by the provided callback
pub fn invariant<F>(cond: bool, f: F) -> Result<(), Diagnostic>
where
F: Fn() -> Diagnostic,
{
if cond { Ok(()) } else { Err(f()) }
}

View File

@@ -1,22 +0,0 @@
[package]
name = "react_estree"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
insta = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
static_assertions = { workspace = true }
[build-dependencies]
react_estree_codegen = { workspace = true }

View File

@@ -1,17 +0,0 @@
# react_estree
This crate is a Rust representation of the [ESTree format](https://github.com/estree/estree/tree/master) and
popular extenions including JSX and (eventually) Flow and TypeScript.
This crate is intended as the main interchange format with outside code. A typical integration with React Compiler
will look as follows:
1. Host Compiler parses into the host AST format.
2. Host Compiler converts into `react_estree`.
3. Host Compiler invokes React Compiler to compile the input, which (conceptually)
returns the resulting code in `react_estree` format.
4. Host Compiler convert back from `react_estree` to its host AST format.
Because React Compiler is intended to support JavaScript-based toolchains, `react_estree` is designed to support
accurate serialization to/from estree-compatible JSON. We may also support the Babel AST format
(a variant of ESTree) as well, depending on demand.

View File

@@ -1,31 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use react_estree_codegen::estree;
// Example custom build script.
fn main() {
// Re-run if the codegen files change
println!("cargo:rerun-if-changed=../react_estree_codegen/src/codegen.rs");
println!("cargo:rerun-if-changed=../react_estree_codegen/src/lib.rs");
println!("cargo:rerun-if-changed=../react_estree_codegen/src/ecmascript.json");
println!("cargo:rerun-if-changed=../react_estree_codegen");
let src = estree();
let copyright = "
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
"
.to_string();
let trimmed_copyright = copyright.trim();
let contents = format!("{trimmed_copyright}\n{src}");
std::fs::write("src/generated.rs", contents).unwrap();
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Binding {
Global,
Module(BindingId),
Local(BindingId),
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct BindingId(u32);
impl BindingId {
pub fn new(value: u32) -> Self {
Self(value)
}
}
impl From<BindingId> for u32 {
fn from(value: BindingId) -> Self {
value.0
}
}

View File

@@ -1,528 +0,0 @@
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 7,
"column": 1
}
},
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 7,
"column": 1
}
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"name": "Component",
"typeAnnotation": null,
"optional": false,
"range": [
9,
18
]
},
"params": [
{
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"name": "props",
"typeAnnotation": null,
"optional": false,
"range": [
19,
24
]
}
],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 7,
"column": 1
}
},
"body": [
{
"type": "VariableDeclaration",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 12
}
},
"kind": "let",
"declarations": [
{
"type": "VariableDeclarator",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 6
},
"end": {
"line": 2,
"column": 11
}
},
"init": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 10
},
"end": {
"line": 2,
"column": 11
}
},
"value": 0,
"range": [
38,
39
],
"raw": "0"
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 6
},
"end": {
"line": 2,
"column": 7
}
},
"name": "x",
"typeAnnotation": null,
"optional": false,
"range": [
34,
35
]
},
"range": [
34,
39
]
}
],
"range": [
30,
40
]
},
{
"type": "ForStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 5,
"column": 3
}
},
"init": {
"type": "VariableDeclaration",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 7
},
"end": {
"line": 3,
"column": 16
}
},
"kind": "let",
"declarations": [
{
"type": "VariableDeclarator",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 11
},
"end": {
"line": 3,
"column": 16
}
},
"init": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 15
},
"end": {
"line": 3,
"column": 16
}
},
"value": 0,
"range": [
56,
57
],
"raw": "0"
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 11
},
"end": {
"line": 3,
"column": 12
}
},
"name": "i",
"typeAnnotation": null,
"optional": false,
"range": [
52,
53
]
},
"range": [
52,
57
]
}
],
"range": [
48,
57
]
},
"test": {
"type": "BinaryExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"left": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"name": "i",
"typeAnnotation": null,
"optional": false,
"range": [
59,
60
]
},
"right": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 22
},
"end": {
"line": 3,
"column": 24
}
},
"value": 10,
"range": [
63,
65
],
"raw": "10"
},
"operator": "<",
"range": [
59,
65
]
},
"update": {
"type": "UpdateExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 29
}
},
"operator": "++",
"argument": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 27
}
},
"name": "i",
"typeAnnotation": null,
"optional": false,
"range": [
67,
68
]
},
"prefix": false,
"range": [
67,
70
]
},
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 31
},
"end": {
"line": 5,
"column": 3
}
},
"body": [
{
"type": "ExpressionStatement",
"loc": {
"source": null,
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 11
}
},
"expression": {
"type": "AssignmentExpression",
"loc": {
"source": null,
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 10
}
},
"operator": "+=",
"left": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 5
}
},
"name": "x",
"typeAnnotation": null,
"optional": false,
"range": [
78,
79
]
},
"right": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 4,
"column": 9
},
"end": {
"line": 4,
"column": 10
}
},
"name": "i",
"typeAnnotation": null,
"optional": false,
"range": [
83,
84
]
},
"range": [
78,
84
]
},
"directive": null,
"range": [
78,
85
]
}
],
"range": [
72,
89
]
},
"range": [
43,
89
]
},
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 6,
"column": 2
},
"end": {
"line": 6,
"column": 11
}
},
"argument": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 6,
"column": 9
},
"end": {
"line": 6,
"column": 10
}
},
"name": "x",
"typeAnnotation": null,
"optional": false,
"range": [
99,
100
]
},
"range": [
92,
101
]
}
],
"range": [
26,
103
]
},
"typeParameters": null,
"returnType": null,
"predicate": null,
"generator": false,
"async": false,
"range": [
0,
103
]
}
],
"comments": [],
"interpreter": null,
"range": [
0,
103
],
"sourceType": "script"
}

View File

@@ -1,104 +0,0 @@
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"body": [
{
"type": "ImportDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"local": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"name": "React",
"typeAnnotation": null,
"optional": false,
"range": [
7,
12
]
},
"range": [
7,
12
]
}
],
"source": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 25
}
},
"value": "react",
"range": [
18,
25
],
"raw": "'react'"
},
"attributes": [],
"importKind": "value",
"range": [
0,
26
]
}
],
"comments": [],
"interpreter": null,
"range": [
0,
26
],
"sourceType": "module"
}

View File

@@ -1,189 +0,0 @@
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"range": [
9,
18
],
"name": "Component",
"typeAnnotation": null,
"optional": false
},
"params": [
{
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"range": [
19,
24
],
"name": "props",
"typeAnnotation": null,
"optional": false
}
],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
26,
51
],
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 21
}
},
"range": [
30,
49
],
"argument": {
"type": "MemberExpression",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
37,
48
],
"object": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 14
}
},
"range": [
37,
42
],
"name": "props",
"typeAnnotation": null,
"optional": false
},
"property": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
43,
48
],
"name": "value",
"typeAnnotation": null,
"optional": false
},
"computed": false
}
}
]
},
"async": false,
"generator": false,
"predicate": null,
"expression": false,
"returnType": null,
"typeParameters": null
}
],
"comments": [],
"errors": []
}

View File

@@ -1,351 +0,0 @@
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
}
},
"name": "foo",
"typeAnnotation": null,
"optional": false,
"range": [
10,
13
]
},
"params": [],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 36
}
},
"argument": {
"type": "JSXElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 36
}
},
"openingElement": {
"type": "JSXOpeningElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 26
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 17
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 13
}
},
"name": "Foo",
"range": [
28,
31
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 14
},
"end": {
"line": 3,
"column": 17
}
},
"name": "Bar",
"range": [
32,
35
]
},
"range": [
28,
35
]
},
"attributes": [
{
"type": "JSXAttribute",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"name": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"name": "a",
"range": [
36,
37
]
},
"value": {
"type": "JSXExpressionContainer",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 20
},
"end": {
"line": 3,
"column": 24
}
},
"expression": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 21
},
"end": {
"line": 3,
"column": 23
}
},
"value": 10,
"range": [
39,
41
],
"raw": "10"
},
"range": [
38,
42
]
},
"range": [
36,
42
]
}
],
"selfClosing": false,
"range": [
27,
44
]
},
"children": [],
"closingElement": {
"type": "JSXClosingElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 36
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 35
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 31
}
},
"name": "Foo",
"range": [
46,
49
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 32
},
"end": {
"line": 3,
"column": 35
}
},
"name": "Bar",
"range": [
50,
53
]
},
"range": [
46,
53
]
},
"range": [
44,
54
]
},
"range": [
27,
54
]
},
"range": [
20,
54
]
}
],
"range": [
16,
56
]
},
"typeParameters": null,
"returnType": null,
"predicate": null,
"generator": false,
"async": false,
"range": [
1,
56
]
}
],
"comments": [],
"interpreter": null,
"range": [
1,
56
],
"sourceType": "script"
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,149 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// Manual extensions to generated types
use crate::{
ArrowFunctionExpression, Class, ClassDeclaration, ClassExpression, Function,
FunctionDeclaration, FunctionExpression, ImportDeclarationSpecifier, JSXElementName,
JSXMemberExpression, JSXMemberExpressionOrIdentifier, Pattern, SourceRange, SourceType,
};
/// Sentinel trait to distinguish AST *node* types
pub trait ESTreeNode {}
impl Default for SourceType {
fn default() -> Self {
Self::Module
}
}
impl Pattern {
pub fn range(&self) -> Option<SourceRange> {
match self {
Self::ArrayPattern(pattern) => pattern.range,
Self::AssignmentPattern(pattern) => pattern.range,
Self::Identifier(pattern) => pattern.range,
Self::ObjectPattern(pattern) => pattern.range,
Self::RestElement(pattern) => pattern.range,
}
}
}
impl ImportDeclarationSpecifier {
pub fn range(&self) -> Option<SourceRange> {
match self {
Self::ImportDefaultSpecifier(specifier) => specifier.range,
Self::ImportNamespaceSpecifier(specifier) => specifier.range,
Self::ImportSpecifier(specifier) => specifier.range,
}
}
}
impl JSXElementName {
pub fn root_name(&self) -> &str {
match self {
Self::JSXIdentifier(name) => &name.name,
Self::JSXMemberExpression(name) => name.root_name(),
Self::JSXNamespacedName(name) => &name.namespace.name,
}
}
}
impl JSXMemberExpression {
pub fn root_name(&self) -> &str {
match &self.object {
JSXMemberExpressionOrIdentifier::JSXMemberExpression(object) => object.root_name(),
JSXMemberExpressionOrIdentifier::JSXIdentifier(object) => &object.name,
}
}
}
pub trait IntoFunction: ESTreeNode {
fn function(&self) -> &Function;
fn into_function(self) -> Function;
}
impl IntoFunction for FunctionDeclaration {
fn function(&self) -> &Function {
&self.function
}
fn into_function(self) -> Function {
self.function
}
}
impl IntoFunction for FunctionExpression {
fn function(&self) -> &Function {
&self.function
}
fn into_function(self) -> Function {
self.function
}
}
impl IntoFunction for ArrowFunctionExpression {
fn function(&self) -> &Function {
&self.function
}
fn into_function(self) -> Function {
self.function
}
}
impl ESTreeNode for Function {}
impl IntoFunction for Function {
fn function(&self) -> &Function {
self
}
fn into_function(self) -> Function {
self
}
}
pub trait IntoClass: ESTreeNode {
fn class(&self) -> &Class;
fn into_class(self) -> Class;
}
impl IntoClass for ClassDeclaration {
fn class(&self) -> &Class {
&self.class
}
fn into_class(self) -> Class {
self.class
}
}
impl IntoClass for ClassExpression {
fn class(&self) -> &Class {
&self.class
}
fn into_class(self) -> Class {
self.class
}
}
impl ESTreeNode for Class {}
impl IntoClass for Class {
fn class(&self) -> &Class {
self
}
fn into_class(self) -> Class {
self
}
}

View File

@@ -1,295 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use serde::de::Visitor;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JsValue {
Boolean(bool),
Null,
Number(Number),
String(String),
Undefined,
}
impl JsValue {
pub fn is_truthy(&self) -> bool {
match &self {
JsValue::Boolean(value) => *value,
JsValue::Number(value) => value.is_truthy(),
JsValue::String(value) => !value.is_empty(),
JsValue::Null => false,
JsValue::Undefined => false,
}
}
// Partial implementation of loose equality for javascript, returns Some for supported
// cases w the equality result, and None for unsupported cases
pub fn loosely_equals(&self, other: &Self) -> Option<bool> {
// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-islooselyequal
match (&self, &other) {
// 1. If Type(x) is Type(y), then
// a. Return IsStrictlyEqual(x, y).
(JsValue::Number(left), JsValue::Number(right)) => Some(left.equals(*right)),
(JsValue::Null, JsValue::Null) => Some(true),
(JsValue::Undefined, JsValue::Undefined) => Some(true),
(JsValue::Boolean(left), JsValue::Boolean(right)) => Some(left == right),
(JsValue::String(left), JsValue::String(right)) => Some(left == right),
// 2. If x is null and y is undefined, return true.
(JsValue::Null, JsValue::Undefined) => Some(true),
// 3. If x is undefined and y is null, return true.
(JsValue::Undefined, JsValue::Null) => Some(true),
_ => None,
}
}
pub fn not_loosely_equals(&self, other: &Self) -> Option<bool> {
self.loosely_equals(other).map(|value| !value)
}
// Complete implementation of strict equality for javascript
pub fn strictly_equals(&self, other: &Self) -> bool {
// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal
match (&self, &other) {
(JsValue::Number(left), JsValue::Number(right)) => left.equals(*right),
(JsValue::Null, JsValue::Null) => true,
(JsValue::Undefined, JsValue::Undefined) => true,
(JsValue::Boolean(left), JsValue::Boolean(right)) => left == right,
(JsValue::String(left), JsValue::String(right)) => left == right,
_ => false,
}
}
pub fn not_strictly_equals(&self, other: &Self) -> bool {
!self.strictly_equals(other)
}
}
impl Serialize for JsValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Boolean(b) => serializer.serialize_bool(*b),
Self::Null => serializer.serialize_none(),
Self::Number(n) => serializer.serialize_f64(n.into()),
Self::String(s) => serializer.serialize_str(s),
Self::Undefined => serializer.serialize_unit(),
}
}
}
impl<'de> Deserialize<'de> for JsValue {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<JsValue, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ValueVisitor;
impl<'de> Visitor<'de> for ValueVisitor {
type Value = JsValue;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("valid primitive JSON value (null, boolean, number, or string")
}
#[inline]
fn visit_bool<E>(self, value: bool) -> Result<JsValue, E> {
Ok(JsValue::Boolean(value))
}
#[inline]
fn visit_i64<E>(self, value: i64) -> Result<JsValue, E> {
if (MIN_SAFE_INT..=MAX_SAFE_INT).contains(&value) {
Ok(JsValue::Number((value as f64).into()))
} else {
panic!("Invalid number")
}
}
#[inline]
fn visit_u64<E>(self, value: u64) -> Result<JsValue, E> {
if value as i64 <= MAX_SAFE_INT {
Ok(JsValue::Number((value as f64).into()))
} else {
panic!("Invalid number")
}
}
#[inline]
fn visit_f64<E>(self, value: f64) -> Result<JsValue, E> {
Ok(JsValue::Number(value.into()))
}
#[inline]
fn visit_str<E>(self, value: &str) -> Result<JsValue, E>
where
E: serde::de::Error,
{
self.visit_string(String::from(value))
}
#[inline]
fn visit_string<E>(self, value: String) -> Result<JsValue, E> {
Ok(JsValue::String(value))
}
#[inline]
fn visit_none<E>(self) -> Result<JsValue, E> {
Ok(JsValue::Null)
}
#[inline]
fn visit_some<D>(self, deserializer: D) -> Result<JsValue, D::Error>
where
D: serde::Deserializer<'de>,
{
Deserialize::deserialize(deserializer)
}
#[inline]
fn visit_unit<E>(self) -> Result<JsValue, E> {
Ok(JsValue::Undefined)
}
}
deserializer.deserialize_any(ValueVisitor)
}
}
/// Represents a JavaScript Number as its binary representation so that
/// -1 == -1, NaN == Nan etc.
/// Note: NaN is *always* represented as the f64::NAN constant to allow
/// comparison of NaNs.
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Debug, Hash)]
pub struct Number(u64);
pub const MAX_SAFE_INT: i64 = 9007199254740991;
pub const MIN_SAFE_INT: i64 = -9007199254740991;
impl From<f64> for Number {
fn from(value: f64) -> Self {
if value.is_nan() {
Self(f64::NAN.to_bits())
} else {
Self(value.to_bits())
}
}
}
impl From<u32> for Number {
fn from(value: u32) -> Self {
f64::from(value).into()
}
}
impl From<Number> for f64 {
fn from(number: Number) -> Self {
let value = f64::from_bits(number.0);
assert!(!f64::is_nan(value) || number.0 == f64::NAN.to_bits());
value
}
}
impl From<&Number> for f64 {
fn from(number: &Number) -> Self {
let value = f64::from_bits(number.0);
assert!(!f64::is_nan(value) || number.0 == f64::NAN.to_bits());
value
}
}
impl Number {
pub fn equals(self, other: Self) -> bool {
f64::from(self) == f64::from(other)
}
pub fn not_equals(self, other: Self) -> bool {
!self.equals(other)
}
pub fn is_truthy(self) -> bool {
let value = f64::from(self);
!(self.0 == f64::NAN.to_bits() || value == 0.0 || value == -0.0)
}
}
impl std::ops::Add for Number {
type Output = Number;
fn add(self, rhs: Self) -> Self::Output {
let result = f64::from(self) + f64::from(rhs);
Self::from(result)
}
}
impl std::ops::Sub for Number {
type Output = Number;
fn sub(self, rhs: Self) -> Self::Output {
let result = f64::from(self) - f64::from(rhs);
Self::from(result)
}
}
impl std::ops::Mul for Number {
type Output = Number;
fn mul(self, rhs: Self) -> Self::Output {
let result = f64::from(self) * f64::from(rhs);
Self::from(result)
}
}
impl std::ops::Div for Number {
type Output = Number;
fn div(self, rhs: Self) -> Self::Output {
let result = f64::from(self) / f64::from(rhs);
Self::from(result)
}
}
impl Serialize for Number {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_f64(self.into())
}
}
impl<'de> Deserialize<'de> for Number {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ValueVisitor;
impl<'de> Visitor<'de> for ValueVisitor {
type Value = Number;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("value JavaScript number value")
}
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.into())
}
}
deserializer.deserialize_any(ValueVisitor)
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
mod binding;
mod generated;
mod generated_extensions;
mod js_value;
mod range;
mod visit;
pub use binding::{Binding, BindingId};
pub use generated::*;
pub use generated_extensions::*;
pub use js_value::{JsValue, Number};
pub use range::SourceRange;
pub use visit::*;
#[cfg(test)]
mod tests {
use insta::{assert_snapshot, glob};
use super::*;
#[test]
fn fixtures() {
glob!("fixtures/**.json", |path| {
println!("{:?}", path);
let input = std::fs::read_to_string(path).unwrap();
let ast: Program = serde_json::from_str(&input).unwrap();
let serialized = serde_json::to_string_pretty(&ast).unwrap();
assert_snapshot!(format!("Input:\n{input}\n\nOutput:\n{serialized}"));
});
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::num::NonZeroU32;
use serde::ser::SerializeTuple;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Copy, Clone, Debug, PartialEq, PartialOrd, Hash)]
pub struct SourceRange {
pub start: u32,
pub end: NonZeroU32,
}
// ESTree and Babel store the `range` as `[start, end]`, so we customize
// the serialization to use a tuple representation.
impl Serialize for SourceRange {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut tuple = serializer.serialize_tuple(2)?;
tuple.serialize_element(&self.start)?;
tuple.serialize_element(&self.end)?;
tuple.end()
}
}

View File

@@ -1,213 +0,0 @@
---
source: crates/react_estree/src/lib.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{serialized}\")"
input_file: crates/react_estree/src/fixtures/import.json
---
Input:
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"body": [
{
"type": "ImportDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"local": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"name": "React",
"typeAnnotation": null,
"optional": false,
"range": [
7,
12
]
},
"range": [
7,
12
]
}
],
"source": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 25
}
},
"value": "react",
"range": [
18,
25
],
"raw": "'react'"
},
"attributes": [],
"importKind": "value",
"range": [
0,
26
]
}
],
"comments": [],
"interpreter": null,
"range": [
0,
26
],
"sourceType": "module"
}
Output:
{
"type": "Program",
"body": [
{
"type": "ImportDeclaration",
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"local": {
"type": "Identifier",
"name": "React",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"range": [
7,
12
]
},
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"range": [
7,
12
]
}
],
"source": {
"type": "Literal",
"value": "react",
"raw": "'react'",
"regex": null,
"bigint": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 25
}
},
"range": [
18,
25
]
},
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"range": [
0,
26
]
}
],
"sourceType": "module",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"range": [
0,
26
]
}

View File

@@ -1,379 +0,0 @@
---
source: crates/react_estree/src/lib.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{serialized}\")"
input_file: crates/react_estree/src/fixtures/simple.json
---
Input:
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"range": [
9,
18
],
"name": "Component",
"typeAnnotation": null,
"optional": false
},
"params": [
{
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"range": [
19,
24
],
"name": "props",
"typeAnnotation": null,
"optional": false
}
],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
26,
51
],
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 21
}
},
"range": [
30,
49
],
"argument": {
"type": "MemberExpression",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
37,
48
],
"object": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 14
}
},
"range": [
37,
42
],
"name": "props",
"typeAnnotation": null,
"optional": false
},
"property": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
43,
48
],
"name": "value",
"typeAnnotation": null,
"optional": false
},
"computed": false
}
}
]
},
"async": false,
"generator": false,
"predicate": null,
"expression": false,
"returnType": null,
"typeParameters": null
}
],
"comments": [],
"errors": []
}
Output:
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "Component",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"range": [
9,
18
]
},
"params": [
{
"type": "Identifier",
"name": "props",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"range": [
19,
24
]
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "props",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 14
}
},
"range": [
37,
42
]
},
"property": {
"type": "Identifier",
"name": "value",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
43,
48
]
},
"computed": false,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
37,
48
]
},
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 21
}
},
"range": [
30,
49
]
}
],
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
26,
51
]
},
"generator": false,
"async": false,
"loc": null,
"range": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
]
}
],
"sourceType": "module",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
]
}

View File

@@ -1,708 +0,0 @@
---
source: crates/react_estree/src/lib.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{serialized}\")"
input_file: crates/react_estree/src/fixtures/test.json
---
Input:
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
}
},
"name": "foo",
"typeAnnotation": null,
"optional": false,
"range": [
10,
13
]
},
"params": [],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 36
}
},
"argument": {
"type": "JSXElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 36
}
},
"openingElement": {
"type": "JSXOpeningElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 26
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 17
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 13
}
},
"name": "Foo",
"range": [
28,
31
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 14
},
"end": {
"line": 3,
"column": 17
}
},
"name": "Bar",
"range": [
32,
35
]
},
"range": [
28,
35
]
},
"attributes": [
{
"type": "JSXAttribute",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"name": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"name": "a",
"range": [
36,
37
]
},
"value": {
"type": "JSXExpressionContainer",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 20
},
"end": {
"line": 3,
"column": 24
}
},
"expression": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 21
},
"end": {
"line": 3,
"column": 23
}
},
"value": 10,
"range": [
39,
41
],
"raw": "10"
},
"range": [
38,
42
]
},
"range": [
36,
42
]
}
],
"selfClosing": false,
"range": [
27,
44
]
},
"children": [],
"closingElement": {
"type": "JSXClosingElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 36
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 35
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 31
}
},
"name": "Foo",
"range": [
46,
49
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 32
},
"end": {
"line": 3,
"column": 35
}
},
"name": "Bar",
"range": [
50,
53
]
},
"range": [
46,
53
]
},
"range": [
44,
54
]
},
"range": [
27,
54
]
},
"range": [
20,
54
]
}
],
"range": [
16,
56
]
},
"typeParameters": null,
"returnType": null,
"predicate": null,
"generator": false,
"async": false,
"range": [
1,
56
]
}
],
"comments": [],
"interpreter": null,
"range": [
1,
56
],
"sourceType": "script"
}
Output:
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "foo",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
}
},
"range": [
10,
13
]
},
"params": [],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": {
"type": "JSXMemberExpression",
"object": {
"type": "JSXIdentifier",
"name": "Foo",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 13
}
},
"range": [
28,
31
]
},
"property": {
"type": "JSXIdentifier",
"name": "Bar",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 14
},
"end": {
"line": 3,
"column": 17
}
},
"range": [
32,
35
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 17
}
},
"range": [
28,
35
]
},
"attributes": [
{
"type": "JSXAttribute",
"name": {
"type": "JSXIdentifier",
"name": "a",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"range": [
36,
37
]
},
"value": {
"type": "JSXExpressionContainer",
"expression": {
"type": "Literal",
"value": 10.0,
"raw": "10",
"regex": null,
"bigint": null,
"loc": {
"source": null,
"start": {
"line": 3,
"column": 21
},
"end": {
"line": 3,
"column": 23
}
},
"range": [
39,
41
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 20
},
"end": {
"line": 3,
"column": 24
}
},
"range": [
38,
42
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"range": [
36,
42
]
}
],
"selfClosing": false,
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 26
}
},
"range": [
27,
44
]
},
"children": [],
"closingElement": {
"type": "JSXClosingElement",
"name": {
"type": "JSXMemberExpression",
"object": {
"type": "JSXIdentifier",
"name": "Foo",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 31
}
},
"range": [
46,
49
]
},
"property": {
"type": "JSXIdentifier",
"name": "Bar",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 32
},
"end": {
"line": 3,
"column": 35
}
},
"range": [
50,
53
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 35
}
},
"range": [
46,
53
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 36
}
},
"range": [
44,
54
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 36
}
},
"range": [
27,
54
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 36
}
},
"range": [
20,
54
]
}
],
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 4,
"column": 1
}
},
"range": [
16,
56
]
},
"generator": false,
"async": false,
"loc": null,
"range": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"range": [
1,
56
]
}
],
"sourceType": "script",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"range": [
1,
56
]
}

View File

@@ -1,213 +0,0 @@
---
source: crates/react_estree/src/lib.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{serialized}\")"
input_file: crates/react_estree/src/fixtures/import.json
---
Input:
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"body": [
{
"type": "ImportDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"local": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"name": "React",
"typeAnnotation": null,
"optional": false,
"range": [
7,
12
]
},
"range": [
7,
12
]
}
],
"source": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 25
}
},
"value": "react",
"range": [
18,
25
],
"raw": "'react'"
},
"attributes": [],
"importKind": "value",
"range": [
0,
26
]
}
],
"comments": [],
"interpreter": null,
"range": [
0,
26
],
"sourceType": "module"
}
Output:
{
"type": "Program",
"body": [
{
"type": "ImportDeclaration",
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"local": {
"type": "Identifier",
"name": "React",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"range": [
7,
12
]
},
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"range": [
7,
12
]
}
],
"source": {
"type": "Literal",
"value": "react",
"raw": "'react'",
"regex": null,
"bigint": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 25
}
},
"range": [
18,
25
]
},
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"range": [
0,
26
]
}
],
"sourceType": "module",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"range": [
0,
26
]
}

View File

@@ -1,379 +0,0 @@
---
source: crates/react_estree/src/lib.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{serialized}\")"
input_file: crates/react_estree/src/fixtures/simple.json
---
Input:
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"range": [
9,
18
],
"name": "Component",
"typeAnnotation": null,
"optional": false
},
"params": [
{
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"range": [
19,
24
],
"name": "props",
"typeAnnotation": null,
"optional": false
}
],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
26,
51
],
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 21
}
},
"range": [
30,
49
],
"argument": {
"type": "MemberExpression",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
37,
48
],
"object": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 14
}
},
"range": [
37,
42
],
"name": "props",
"typeAnnotation": null,
"optional": false
},
"property": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
43,
48
],
"name": "value",
"typeAnnotation": null,
"optional": false
},
"computed": false
}
}
]
},
"async": false,
"generator": false,
"predicate": null,
"expression": false,
"returnType": null,
"typeParameters": null
}
],
"comments": [],
"errors": []
}
Output:
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "Component",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"range": [
9,
18
]
},
"params": [
{
"type": "Identifier",
"name": "props",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"range": [
19,
24
]
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "props",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 14
}
},
"range": [
37,
42
]
},
"property": {
"type": "Identifier",
"name": "value",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
43,
48
]
},
"computed": false,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
37,
48
]
},
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 21
}
},
"range": [
30,
49
]
}
],
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
26,
51
]
},
"generator": false,
"async": false,
"loc": null,
"range": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
]
}
],
"sourceType": "module",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
]
}

View File

@@ -1,708 +0,0 @@
---
source: crates/react_estree/src/lib.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{serialized}\")"
input_file: crates/react_estree/src/fixtures/test.json
---
Input:
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
}
},
"name": "foo",
"typeAnnotation": null,
"optional": false,
"range": [
10,
13
]
},
"params": [],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 36
}
},
"argument": {
"type": "JSXElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 36
}
},
"openingElement": {
"type": "JSXOpeningElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 26
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 17
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 13
}
},
"name": "Foo",
"range": [
28,
31
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 14
},
"end": {
"line": 3,
"column": 17
}
},
"name": "Bar",
"range": [
32,
35
]
},
"range": [
28,
35
]
},
"attributes": [
{
"type": "JSXAttribute",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"name": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"name": "a",
"range": [
36,
37
]
},
"value": {
"type": "JSXExpressionContainer",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 20
},
"end": {
"line": 3,
"column": 24
}
},
"expression": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 21
},
"end": {
"line": 3,
"column": 23
}
},
"value": 10,
"range": [
39,
41
],
"raw": "10"
},
"range": [
38,
42
]
},
"range": [
36,
42
]
}
],
"selfClosing": false,
"range": [
27,
44
]
},
"children": [],
"closingElement": {
"type": "JSXClosingElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 36
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 35
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 31
}
},
"name": "Foo",
"range": [
46,
49
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 32
},
"end": {
"line": 3,
"column": 35
}
},
"name": "Bar",
"range": [
50,
53
]
},
"range": [
46,
53
]
},
"range": [
44,
54
]
},
"range": [
27,
54
]
},
"range": [
20,
54
]
}
],
"range": [
16,
56
]
},
"typeParameters": null,
"returnType": null,
"predicate": null,
"generator": false,
"async": false,
"range": [
1,
56
]
}
],
"comments": [],
"interpreter": null,
"range": [
1,
56
],
"sourceType": "script"
}
Output:
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "foo",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
}
},
"range": [
10,
13
]
},
"params": [],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": {
"type": "JSXMemberExpression",
"object": {
"type": "JSXIdentifier",
"name": "Foo",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 13
}
},
"range": [
28,
31
]
},
"property": {
"type": "JSXIdentifier",
"name": "Bar",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 14
},
"end": {
"line": 3,
"column": 17
}
},
"range": [
32,
35
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 17
}
},
"range": [
28,
35
]
},
"attributes": [
{
"type": "JSXAttribute",
"name": {
"type": "JSXIdentifier",
"name": "a",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"range": [
36,
37
]
},
"value": {
"type": "JSXExpressionContainer",
"expression": {
"type": "Literal",
"value": 10.0,
"raw": "10",
"regex": null,
"bigint": null,
"loc": {
"source": null,
"start": {
"line": 3,
"column": 21
},
"end": {
"line": 3,
"column": 23
}
},
"range": [
39,
41
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 20
},
"end": {
"line": 3,
"column": 24
}
},
"range": [
38,
42
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"range": [
36,
42
]
}
],
"selfClosing": false,
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 26
}
},
"range": [
27,
44
]
},
"children": [],
"closingElement": {
"type": "JSXClosingElement",
"name": {
"type": "JSXMemberExpression",
"object": {
"type": "JSXIdentifier",
"name": "Foo",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 31
}
},
"range": [
46,
49
]
},
"property": {
"type": "JSXIdentifier",
"name": "Bar",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 32
},
"end": {
"line": 3,
"column": 35
}
},
"range": [
50,
53
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 35
}
},
"range": [
46,
53
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 36
}
},
"range": [
44,
54
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 36
}
},
"range": [
27,
54
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 36
}
},
"range": [
20,
54
]
}
],
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 4,
"column": 1
}
},
"range": [
16,
56
]
},
"generator": false,
"async": false,
"loc": null,
"range": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"range": [
1,
56
]
}
],
"sourceType": "script",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"range": [
1,
56
]
}

View File

@@ -1,537 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use crate::{
AssignmentPropertyOrRestElement, AssignmentTarget, Class, ClassItem, ClassPrivateProperty,
ClassProperty, Declaration, DeclarationOrExpression, ExportAllDeclaration,
ExportDefaultDeclaration, ExportNamedDeclaration, Expression, ExpressionOrPrivateIdentifier,
ExpressionOrSpread, ExpressionOrSuper, ForInInit, ForInit, Function, FunctionBody,
FunctionDeclaration, Identifier, ImportDeclaration, ImportDeclarationSpecifier,
ImportOrExportDeclaration, Literal, MethodDefinition, ModuleItem, Pattern, PrivateIdentifier,
PrivateName, Program, Statement, StaticBlock, Super, SwitchCase, VariableDeclarator, _Literal,
};
/// Trait for visiting an estree
#[allow(non_camel_case_types)]
#[deprecated]
pub trait Visitor_DEPRECATED<'ast> {
fn visit_lvalue<F>(&mut self, f: F)
where
F: FnOnce(&mut Self),
{
f(self);
}
fn visit_rvalue<F>(&mut self, f: F)
where
F: FnOnce(&mut Self),
{
f(self);
}
fn visit_program(&mut self, program: &'ast Program) {
self.default_visit_program(program)
}
fn default_visit_program(&mut self, program: &'ast Program) {
for item in &program.body {
self.visit_module_item(item);
}
}
fn visit_function(&mut self, function: &'ast Function) {
self.default_visit_function(function);
}
fn default_visit_function(&mut self, function: &'ast Function) {
self.visit_lvalue(|visitor| {
for param in &function.params {
visitor.visit_pattern(param);
}
});
match &function.body {
Some(FunctionBody::BlockStatement(body)) => {
for stmt in &body.body {
self.visit_statement(stmt)
}
}
Some(FunctionBody::Expression(body)) => self.visit_expression(body),
None => {}
}
}
fn visit_module_item(&mut self, item: &'ast ModuleItem) {
match item {
ModuleItem::Statement(item) => self.visit_statement(item),
ModuleItem::ImportOrExportDeclaration(item) => {
self.visit_import_or_export_declaration(item)
}
}
}
fn visit_import_or_export_declaration(&mut self, declaration: &'ast ImportOrExportDeclaration) {
match declaration {
ImportOrExportDeclaration::ImportDeclaration(declaration) => {
self.visit_import_declaration(declaration);
}
ImportOrExportDeclaration::ExportAllDeclaration(declaration) => {
self.visit_export_all_declaration(declaration);
}
ImportOrExportDeclaration::ExportDefaultDeclaration(declaration) => {
self.visit_export_default_declaration(declaration);
}
ImportOrExportDeclaration::ExportNamedDeclaration(declaration) => {
self.visit_export_named_declaration(declaration);
}
}
}
fn visit_import_declaration(&mut self, declaration: &'ast ImportDeclaration) {
self.visit_lvalue(|visitor| {
for specifier in &declaration.specifiers {
visitor.visit_import_declaration_specifier(specifier);
}
});
self.visit_import_source(&declaration.source);
}
fn visit_export_all_declaration(&mut self, declaration: &'ast ExportAllDeclaration) {
self.visit_export_source(&declaration.source);
}
fn visit_export_default_declaration(&mut self, declaration: &'ast ExportDefaultDeclaration) {
match &declaration.declaration {
DeclarationOrExpression::Declaration(declaration) => {
self.visit_declaration(declaration)
}
DeclarationOrExpression::Expression(declaration) => self.visit_expression(declaration),
}
}
fn visit_export_named_declaration(&mut self, declaration: &'ast ExportNamedDeclaration) {
if let Some(declaration) = &declaration.declaration {
self.visit_declaration(declaration)
}
if let Some(source) = &declaration.source {
self.visit_export_source(source);
}
}
fn visit_import_declaration_specifier(&mut self, specifier: &'ast ImportDeclarationSpecifier) {
match specifier {
ImportDeclarationSpecifier::ImportSpecifier(specifier) => {
self.visit_identifier(&specifier.local);
}
ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => {
self.visit_identifier(&specifier.local);
}
ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier) => {
self.visit_identifier(&specifier.local);
}
}
}
fn visit_declaration(&mut self, declaration: &'ast Declaration) {
self.default_visit_declaration(declaration);
}
fn default_visit_declaration(&mut self, declaration: &'ast Declaration) {
match declaration {
Declaration::ClassDeclaration(declaration) => {
self.visit_class(&declaration.class);
}
Declaration::FunctionDeclaration(declaration) => {
self.visit_function_declaration(declaration);
}
Declaration::VariableDeclaration(declaration) => {
for declarator in &declaration.declarations {
self.visit_variable_declarator(declarator)
}
}
Declaration::TSTypeAliasDeclaration(_declaration) => {
todo!("visit TSTypeAliasDeclaration")
}
}
}
fn visit_function_declaration(&mut self, declaration: &'ast FunctionDeclaration) {
self.visit_function(&declaration.function);
}
fn visit_statement(&mut self, stmt: &'ast Statement) {
self.default_visit_statement(stmt);
}
fn default_visit_statement(&mut self, stmt: &'ast Statement) {
match stmt {
Statement::BlockStatement(stmt) => {
for stmt in &stmt.body {
self.visit_statement(stmt)
}
}
Statement::BreakStatement(_stmt) => {
// todo
}
Statement::ContinueStatement(_stmt) => {
// todo
}
Statement::DebuggerStatement(_stmt) => {
// todo
}
Statement::ClassDeclaration(stmt) => {
self.visit_class(&stmt.class);
}
Statement::DoWhileStatement(stmt) => {
self.visit_statement(&stmt.body);
self.visit_expression(&stmt.test);
}
Statement::EmptyStatement(_stmt) => {
// nothing to do
}
Statement::ExpressionStatement(stmt) => {
self.visit_expression(&stmt.expression);
}
Statement::ForInStatement(stmt) => {
self.visit_for_in_init(&stmt.left);
self.visit_expression(&stmt.right);
self.visit_statement(&stmt.body);
}
Statement::ForOfStatement(stmt) => {
self.visit_for_in_init(&stmt.left);
self.visit_expression(&stmt.right);
self.visit_statement(&stmt.body);
}
Statement::ForStatement(stmt) => {
if let Some(init) = &stmt.init {
self.visit_for_init(init);
}
if let Some(test) = &stmt.test {
self.visit_expression(test);
}
if let Some(update) = &stmt.update {
self.visit_expression(update);
}
self.visit_statement(&stmt.body);
}
Statement::FunctionDeclaration(stmt) => {
self.visit_function_declaration(stmt);
}
Statement::IfStatement(stmt) => {
self.visit_expression(&stmt.test);
self.visit_statement(&stmt.consequent);
if let Some(alternate) = &stmt.alternate {
self.visit_statement(alternate);
}
}
Statement::LabeledStatement(stmt) => {
self.visit_statement(&stmt.body);
}
Statement::ReturnStatement(stmt) => {
if let Some(argument) = &stmt.argument {
self.visit_expression(argument);
}
}
Statement::SwitchStatement(stmt) => {
self.visit_expression(&stmt.discriminant);
for case_ in &stmt.cases {
self.visit_case(case_);
}
}
Statement::ThrowStatement(stmt) => {
self.visit_expression(&stmt.argument);
}
Statement::TryStatement(stmt) => {
for item in &stmt.block.body {
self.visit_statement(item);
}
if let Some(handler) = &stmt.handler {
if let Some(param) = &handler.param {
self.visit_lvalue(|visitor| visitor.visit_pattern(param));
}
for item in &handler.body.body {
self.visit_statement(item);
}
}
if let Some(finalizer) = &stmt.finalizer {
for item in &finalizer.body {
self.visit_statement(item);
}
}
}
Statement::VariableDeclaration(stmt) => {
for decl in &stmt.declarations {
self.visit_variable_declarator(decl);
}
}
Statement::WhileStatement(stmt) => {
self.visit_expression(&stmt.test);
self.visit_statement(&stmt.body);
}
Statement::WithStatement(stmt) => {
self.visit_expression(&stmt.object);
self.visit_statement(&stmt.body);
}
Statement::TSTypeAliasDeclaration(_stmt) => {
todo!("visit TSTypeAliasDeclaration")
}
}
}
fn visit_class(&mut self, class: &'ast Class) {
if let Some(id) = &class.id {
self.visit_identifier(id)
}
if let Some(super_class) = &class.super_class {
self.visit_expression(super_class);
}
for item in &class.body.body {
match item {
ClassItem::MethodDefinition(item) => self.visit_method_definition(item),
ClassItem::ClassProperty(item) => {
self.visit_class_property(item);
}
ClassItem::ClassPrivateProperty(item) => {
self.visit_class_private_property(item);
}
ClassItem::StaticBlock(item) => {
self.visit_static_block(item);
}
}
}
}
fn visit_class_property(&mut self, property: &'ast ClassProperty) {
self.visit_expression(&property.key);
if let Some(value) = &property.value {
self.visit_expression(value)
}
}
fn visit_class_private_property(&mut self, property: &'ast ClassPrivateProperty) {
match &property.key {
ExpressionOrPrivateIdentifier::Expression(key) => self.visit_expression(key),
ExpressionOrPrivateIdentifier::PrivateIdentifier(key) => {
self.visit_private_identifier(key)
}
ExpressionOrPrivateIdentifier::PrivateName(key) => self.visit_private_name(key),
}
if let Some(value) = &property.value {
self.visit_expression(value)
}
}
fn visit_static_block(&mut self, property: &'ast StaticBlock) {
for stmt in &property.body {
self.visit_statement(stmt)
}
}
fn visit_method_definition(&mut self, method: &'ast MethodDefinition) {
self.default_visit_method_definition(method);
}
fn default_visit_method_definition(&mut self, method: &'ast MethodDefinition) {
self.visit_expression(&method.key);
self.visit_function(&method.value.function);
}
fn visit_case(&mut self, case_: &'ast SwitchCase) {
if let Some(test) = &case_.test {
self.visit_expression(test);
}
for stmt in &case_.consequent {
self.visit_statement(stmt)
}
}
fn visit_for_init(&mut self, init: &'ast ForInit) {
match init {
ForInit::Expression(init) => {
self.visit_expression(init);
}
ForInit::VariableDeclaration(init) => {
for decl in &init.declarations {
self.visit_variable_declarator(decl);
}
}
}
}
fn visit_for_in_init(&mut self, init: &'ast ForInInit) {
match init {
ForInInit::Pattern(init) => {
self.visit_pattern(init);
}
ForInInit::VariableDeclaration(init) => {
for decl in &init.declarations {
self.visit_variable_declarator(decl);
}
}
}
}
fn visit_pattern(&mut self, pattern: &'ast Pattern) {
match pattern {
Pattern::Identifier(pattern) => self.visit_identifier(pattern),
Pattern::ArrayPattern(pattern) => {
for element in &pattern.elements {
if let Some(element) = element {
self.visit_pattern(element);
}
}
}
Pattern::ObjectPattern(pattern) => {
for property in &pattern.properties {
match property {
AssignmentPropertyOrRestElement::AssignmentProperty(property) => {
self.visit_pattern(&property.value);
}
AssignmentPropertyOrRestElement::RestElement(property) => {
self.visit_pattern(&property.argument);
}
}
}
}
Pattern::RestElement(pattern) => self.visit_pattern(&pattern.argument),
Pattern::AssignmentPattern(pattern) => {
self.visit_pattern(&pattern.left);
self.visit_rvalue(|visitor| {
visitor.visit_expression(&pattern.right);
});
}
}
}
fn visit_variable_declarator(&mut self, decl: &'ast VariableDeclarator) {
self.visit_lvalue(|visitor| {
visitor.visit_pattern(&decl.id);
});
if let Some(init) = &decl.init {
self.visit_expression(init);
}
}
fn visit_assignment_target(&mut self, target: &'ast AssignmentTarget) {
match target {
AssignmentTarget::Expression(target) => {
self.visit_expression(target);
}
AssignmentTarget::Pattern(target) => self.visit_pattern(target),
}
}
fn visit_expression(&mut self, expr: &'ast Expression) {
self.default_visit_expression(expr);
}
fn default_visit_expression(&mut self, expr: &'ast Expression) {
match expr {
Expression::ArrayExpression(expr) => {
for item in &expr.elements {
match item {
Some(ExpressionOrSpread::SpreadElement(item)) => {
self.visit_expression(&item.argument)
}
Some(ExpressionOrSpread::Expression(item)) => self.visit_expression(item),
_ => {}
}
}
}
Expression::AssignmentExpression(expr) => {
self.visit_lvalue(|visitor| visitor.visit_assignment_target(&expr.left));
self.visit_expression(&expr.right);
}
Expression::BinaryExpression(expr) => {
self.visit_expression(&expr.left);
self.visit_expression(&expr.right);
}
Expression::Identifier(expr) => {
self.visit_identifier(expr);
}
Expression::Literal(expr) => self.visit_literal(expr),
Expression::FunctionExpression(expr) => self.visit_function(&expr.function),
Expression::ArrowFunctionExpression(expr) => self.visit_function(&expr.function),
Expression::MemberExpression(expr) => {
match &expr.object {
ExpressionOrSuper::Super(object) => self.visit_super(object),
ExpressionOrSuper::Expression(object) => self.visit_expression(object),
};
if !expr.is_computed {
match &expr.property {
ExpressionOrPrivateIdentifier::Expression(property) => {
self.visit_expression(property)
}
ExpressionOrPrivateIdentifier::PrivateIdentifier(property) => {
self.visit_private_identifier(property);
}
ExpressionOrPrivateIdentifier::PrivateName(property) => {
self.visit_private_name(property);
}
}
}
}
Expression::CallExpression(expr) => {
match &expr.callee {
ExpressionOrSuper::Expression(callee) => self.visit_expression(callee),
ExpressionOrSuper::Super(callee) => self.visit_super(callee),
}
for arg in &expr.arguments {
match arg {
ExpressionOrSpread::Expression(arg) => self.visit_expression(arg),
ExpressionOrSpread::SpreadElement(arg) => {
self.visit_expression(&arg.argument)
}
}
}
}
Expression::UpdateExpression(expr) => {
self.visit_expression(&expr.argument);
}
Expression::BooleanLiteral(_)
| Expression::NullLiteral(_)
| Expression::StringLiteral(_)
| Expression::NumericLiteral(_) => {
// no-op
}
_ => {
todo!("{:#?}", expr)
}
}
}
fn visit_super(&mut self, _super: &'ast Super) {
todo!("Implement visit_super")
}
fn visit_private_identifier(&mut self, _identifier: &'ast PrivateIdentifier) {
todo!("Implement visit_private_identifier()")
}
fn visit_private_name(&mut self, _identifier: &'ast PrivateName) {
todo!("Implement visit_private_name()")
}
fn visit_identifier(&mut self, _identifier: &'ast Identifier) {
todo!("Implement visit_identifier()")
}
fn visit_import_source(&mut self, literal: &'ast _Literal) {
self.visit_any_literal(literal);
}
fn visit_export_source(&mut self, literal: &'ast _Literal) {
self.visit_any_literal(literal);
}
fn visit_any_literal(&mut self, _literal: &'ast _Literal) {
todo!("Implement visit_any_literal()")
}
fn visit_literal(&mut self, _literal: &'ast Literal) {
todo!("Implement visit_literal()")
}
}

View File

@@ -1,21 +0,0 @@
[package]
name = "react_estree_codegen"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
indexmap = { workspace = true }
prettyplease = { workspace = true }
quote = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
syn = { workspace = true }

View File

@@ -1,4 +0,0 @@
# react_estree_codegen
This crate is a build dependency for `react_estree`, and contains codegen logic to produce Rust code to describe the ESTree format
given a JSON schema.

View File

@@ -1,969 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::collections::HashSet;
use indexmap::IndexMap;
use quote::__private::TokenStream;
use quote::{format_ident, quote};
use serde::{Deserialize, Serialize};
use syn::Type;
/// Returns prettyplease-formatted Rust source for estree
pub fn estree() -> String {
let src = include_str!("./ecmascript.json");
let grammar: Grammar = serde_json::from_str(src).unwrap();
let raw = grammar.codegen().to_string();
let parsed = syn::parse_file(&raw).unwrap();
format!(
"// {}generated\n#![cfg_attr(rustfmt, rustfmt_skip)]\n{}",
'\u{0040}',
prettyplease::unparse(&parsed)
)
}
/// Returns prettyplease-formatted Rust source for converting HermesParser results
/// into estree
pub fn estree_hermes() -> String {
let src = include_str!("./ecmascript.json");
let grammar: Grammar = serde_json::from_str(src).unwrap();
let raw = grammar.codegen_hermes().to_string();
let parsed = syn::parse_file(&raw).unwrap();
format!(
"// {}generated\n#![cfg_attr(rustfmt, rustfmt_skip)]\n{}",
'\u{0040}',
prettyplease::unparse(&parsed)
)
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Grammar {
pub objects: IndexMap<String, Object>,
pub nodes: IndexMap<String, Node>,
pub enums: IndexMap<String, Enum>,
pub operators: IndexMap<String, Operator>,
}
impl Grammar {
pub fn codegen(self) -> TokenStream {
let object_defs: Vec<_> = self
.objects
.iter()
.map(|(name, object)| object.codegen(name))
.collect();
let object_visitors: Vec<_> = self
.objects
.iter()
.filter_map(|(name, object)| {
if object.visitor {
Some(object.codegen_visitor(name, &self))
} else {
None
}
})
.collect();
let node_defs: Vec<_> = self
.nodes
.iter()
.map(|(name, node)| node.codegen(name))
.collect();
let node_visitors: Vec<_> = self
.nodes
.iter()
.map(|(name, node)| node.codegen_visitor(name, &self))
.collect();
let enum_defs: Vec<_> = self
.enums
.iter()
.map(|(name, enum_)| enum_.codegen(name, &self.enums))
.collect();
let enum_visitors: Vec<_> = self
.enums
.iter()
.map(|(name, enum_)| enum_.codegen_visitor(name))
.collect();
let operator_defs: Vec<_> = self
.operators
.iter()
.map(|(name, operator)| operator.codegen(name))
.collect();
quote! {
#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(non_snake_case)]
#![allow(clippy::enum_variant_names)]
use std::num::NonZeroU32;
use serde::ser::{Serializer, SerializeMap};
use serde::{Serialize,Deserialize};
use crate::{JsValue, Binding, SourceRange, Number, ESTreeNode};
#(#object_defs)*
#(#node_defs)*
#(#enum_defs)*
#(#operator_defs)*
pub trait Visitor {
#(#object_visitors)*
#(#node_visitors)*
#(#enum_visitors)*
}
}
}
pub fn codegen_hermes(self) -> TokenStream {
let nodes: Vec<_> = self
.nodes
.iter()
.filter(|(_, node)| !node.skip_hermes_codegen)
.map(|(name, node)| node.codegen_hermes(name))
.collect();
let enums: Vec<_> = self
.enums
.iter()
.map(|(name, enum_)| enum_.codegen_hermes(name, &self))
.collect();
let operators: Vec<_> = self
.operators
.iter()
.map(|(name, operator)| operator.codegen_hermes(name))
.collect();
quote! {
#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(clippy::enum_variant_names)]
use react_estree::*;
use hermes::parser::{NodePtr, NodeKind, NodeLabel };
use hermes::utf::{utf8_with_surrogates_to_string};
use crate::generated_extension::*;
#(#nodes)*
#(#enums)*
#(#operators)*
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Object {
#[serde(default)]
pub fields: IndexMap<String, Field>,
#[serde(default)]
pub visitor: bool,
}
impl Object {
pub fn codegen(&self, name: &str) -> TokenStream {
let name = format_ident!("{}", name);
let fields: Vec<_> = self
.fields
.iter()
.map(|(name, field)| field.codegen(name))
.collect();
quote! {
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct #name {
#(#fields),*
}
}
}
pub fn codegen_visitor(&self, name: &str, grammar: &Grammar) -> TokenStream {
let visitor_name = format_ident!("visit_{}", to_lower_snake_case(name));
let name = format_ident!("{}", name);
let field_visitors: Vec<_> = self
.fields
.iter()
.filter_map(|(name, field)| {
let (type_name_str, type_kind) = parse_type(&field.type_).unwrap();
if !grammar.nodes.contains_key(&type_name_str)
&& !grammar.enums.contains_key(&type_name_str)
{
return None;
}
let visitor_name = format_ident!("visit_{}", to_lower_snake_case(&type_name_str));
let field_name = format_ident!("{}", name);
Some(match type_kind {
TypeKind::Named => {
quote! {
self.#visitor_name(&ast.#field_name);
}
}
TypeKind::Option => {
quote! {
if let Some(#field_name) = &ast.#field_name {
self.#visitor_name(#field_name);
}
}
}
TypeKind::Vec => {
quote! {
for #field_name in &ast.#field_name {
self.#visitor_name(#field_name);
}
}
}
TypeKind::VecOfOption => {
quote! {
for #field_name in &ast.#field_name {
if let Some(#field_name) = #field_name {
self.#visitor_name(#field_name);
}
}
}
}
})
})
.collect();
quote! {
fn #visitor_name(&mut self, ast: &#name) {
#(#field_visitors)*
}
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Node {
#[serde(default)]
#[serde(rename = "type")]
pub type_: Option<String>,
#[serde(default)]
pub fields: IndexMap<String, Field>,
#[serde(default)]
pub skip_hermes_codegen: bool,
#[serde(default)]
pub skip_hermes_enum_variant: bool,
#[serde(default)]
#[serde(rename = "TODO")]
pub todo: Option<String>,
}
impl Node {
pub fn codegen(&self, name: &str) -> TokenStream {
let name_str = name;
let name = format_ident!("{}", name);
let fields: Vec<_> = self
.fields
.iter()
.map(|(name, field)| field.codegen_node(name))
.collect();
let type_serializer = if let Some(type_) = &self.type_ {
quote! {
state.serialize_entry("type", #type_)?;
}
} else {
quote! {
state.serialize_entry("type", #name_str)?;
}
};
let mut field_serializers = Vec::with_capacity(self.fields.len()); // type, loc, range
for (field_name_str, field) in &self.fields {
if field.skip {
continue;
}
let field_name = format_ident!("{}", field_name_str);
let serialized_field_name = field.rename.as_ref().unwrap_or(field_name_str);
let serializer = if field.flatten {
quote! {
Serialize::serialize(&self.#field_name, serde::__private::ser::FlatMapSerializer(&mut state))?;
}
} else {
quote! {
state.serialize_entry(#serialized_field_name, &self.#field_name)?;
}
};
field_serializers.push(serializer);
}
quote! {
#[derive(Deserialize, Clone, Debug)]
pub struct #name {
#(#fields,)*
#[serde(default)]
pub loc: Option<SourceLocation>,
#[serde(default)]
pub range: Option<SourceRange>,
}
impl ESTreeNode for #name {}
impl Serialize for #name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_map(None)?;
#type_serializer
#(#field_serializers)*
state.serialize_entry("loc", &self.loc)?;
state.serialize_entry("range", &self.range)?;
state.end()
}
}
}
}
pub fn codegen_visitor(&self, name: &str, grammar: &Grammar) -> TokenStream {
let visitor_name = format_ident!("visit_{}", to_lower_snake_case(name));
let name = format_ident!("{}", name);
let field_visitors: Vec<_> = self
.fields
.iter()
.filter_map(|(name, field)| {
let (type_name_str, type_kind) = parse_type(&field.type_).unwrap();
if (!grammar.objects.contains_key(&type_name_str)
|| !grammar.objects.get(&type_name_str).unwrap().visitor)
&& !grammar.nodes.contains_key(&type_name_str)
&& !grammar.enums.contains_key(&type_name_str)
{
return None;
}
let visitor_name = format_ident!("visit_{}", to_lower_snake_case(&type_name_str));
let field_name = format_ident!("{}", name);
Some(match type_kind {
TypeKind::Named => {
quote! {
self.#visitor_name(&ast.#field_name);
}
}
TypeKind::Option => {
quote! {
if let Some(#field_name) = &ast.#field_name {
self.#visitor_name(#field_name);
}
}
}
TypeKind::Vec => {
quote! {
for #field_name in &ast.#field_name {
self.#visitor_name(#field_name);
}
}
}
TypeKind::VecOfOption => {
quote! {
for #field_name in &ast.#field_name {
if let Some(#field_name) = #field_name {
self.#visitor_name(#field_name);
}
}
}
}
})
})
.collect();
quote! {
fn #visitor_name(&mut self, ast: &#name) {
#(#field_visitors)*
}
}
}
pub fn codegen_hermes(&self, name: &str) -> TokenStream {
let name_str = name;
let name = format_ident!("{}", name);
let field_names: Vec<_> = self
.fields
.iter()
.map(|(name, _field)| format_ident!("{}", name))
.collect();
let fields: Vec<_> = self
.fields
.iter()
.map(|(name, field)| {
let (type_name_str, type_kind) = parse_type(&field.type_).unwrap();
let camelcase_name = field.rename.as_ref().unwrap_or(name);
let field_name = format_ident!("{}", name);
let helper = format_ident!("hermes_get_{}_{}", name_str, camelcase_name);
let type_name = format_ident!("{}", type_name_str);
if field.skip || field.hermes_default {
return quote! {
let #field_name = Default::default();
};
}
if let Some(convert_with) = &field.hermes_convert_with {
let convert_with = format_ident!("{}", convert_with);
return quote! {
let #field_name = #convert_with(cx, unsafe { hermes::parser::#helper(node) } );
}
}
match type_kind {
TypeKind::Named => {
match type_name_str.as_ref() {
"bool" => {
quote! {
let #field_name = unsafe { hermes::parser::#helper(node) };
}
}
"Number" => {
quote! {
let #field_name = convert_number(unsafe { hermes::parser::#helper(node) });
}
}
"String" => {
quote! {
let #field_name = convert_string(cx, unsafe { hermes::parser::#helper(node) });
}
}
_ => {
quote! {
let #field_name = #type_name::convert(cx, unsafe { hermes::parser::#helper(node) });
}
}
}
}
TypeKind::Option => {
match type_name_str.as_ref() {
"String" => {
quote! {
let #field_name = convert_option_string(cx, unsafe { hermes::parser::#helper(node) });
}
}
_ => {
quote! {
let #field_name = convert_option(unsafe { hermes::parser::#helper(node) }, |node| #type_name::convert(cx, node));
}
}
}
}
TypeKind::Vec => {
quote! {
let #field_name = convert_vec(unsafe { hermes::parser::#helper(node) }, |node| #type_name::convert(cx, node));
}
}
TypeKind::VecOfOption => {
quote! {
let #field_name = convert_vec_of_option(unsafe { hermes::parser::#helper(node) }, |node| #type_name::convert(cx, node));
}
}
}
})
.collect();
let type_ = format_ident!("{}", self.type_.as_ref().unwrap_or(&name_str.to_string()));
quote! {
impl FromHermes for #name {
fn convert(cx: &mut Context, node: NodePtr) -> Self {
let node_ref = node.as_ref();
assert_eq!(node_ref.kind, NodeKind::#type_);
let range = convert_range(cx, node);
#(#fields)*
Self {
#(#field_names,)*
loc: None,
range: Some(range),
}
}
}
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Field {
// TODO: deserialize with `parse_type` into a custom type
#[serde(rename = "type")]
pub type_: String,
#[serde(default)]
pub optional: bool,
#[serde(default)]
pub flatten: bool,
#[serde(default)]
pub rename: Option<String>,
#[serde(default)]
pub skip: bool,
#[serde(default)]
#[serde(rename = "TODO")]
pub todo: Option<String>,
#[serde(default)]
pub hermes_convert_with: Option<String>,
#[serde(default)]
pub hermes_default: bool,
}
impl Field {
pub fn codegen(&self, name: &str) -> TokenStream {
let name = format_ident!("{}", name);
parse_type(&self.type_).unwrap();
let type_name: Type = syn::parse_str(&self.type_)
.unwrap_or_else(|_| panic!("Expected a type name, got `{}`", &self.type_));
let type_ = quote!(#type_name);
let mut field = quote!(pub #name: #type_);
if self.optional {
field = quote! {
#[serde(default)]
#field
}
}
if self.flatten {
field = quote! {
#[serde(flatten)]
#field
}
}
if self.skip {
field = quote! {
#[serde(skip)]
#field
}
}
if let Some(rename) = &self.rename {
field = quote! {
#[serde(rename = #rename)]
#field
}
}
field
}
pub fn codegen_node(&self, name: &str) -> TokenStream {
let name = format_ident!("{}", name);
parse_type(&self.type_).unwrap();
let type_name: Type = syn::parse_str(&self.type_)
.unwrap_or_else(|_| panic!("Expected a type name, got `{}`", &self.type_));
let type_ = quote!(#type_name);
let mut field = quote!(pub #name: #type_);
if self.optional {
field = quote! {
#[serde(default)]
#field
}
}
if self.flatten {
field = quote! {
#[serde(flatten)]
#field
}
}
if self.skip {
field = quote! {
#[serde(skip)]
#field
}
}
if let Some(rename) = &self.rename {
field = quote! {
#[serde(rename = #rename)]
#field
}
}
field
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(transparent)]
#[serde(deny_unknown_fields)]
pub struct Enum {
pub variants: Vec<String>,
}
impl Enum {
pub fn codegen(&self, name: &str, enums: &IndexMap<String, Enum>) -> TokenStream {
let mut sorted_variants: Vec<_> = self.variants.iter().collect();
sorted_variants.sort();
let name_str = name;
let name = format_ident!("{}", name);
let variants: Vec<_> = sorted_variants
.iter()
.map(|name| {
let variant = format_ident!("{}", name);
if enums.contains_key(*name) {
quote!(#variant(#variant))
} else {
quote!(#variant(Box<#variant>))
}
})
.collect();
let enum_tag = format_ident!("__{}Tag", name);
let mut seen = HashSet::new();
// tag_variants is used to generate an enum of all the possible type tags (`type` values)
// that can appear in this enum. we emit this enum and derive a deserializer for it so that
// our enum deserializer can first decode the tag in order to know how to decode the data
let mut tag_variants = Vec::new();
// once the tag is decoded, we need to match against it and deserialize according the tag (`type`)
// tag_matches are the match arms for each type.
let mut tag_matches = Vec::new();
// Imagine a case like:
// enum ModuleItem {
// ImportDeclaration, // struct
// Statement // another enum
// }
// We need to generate matches for all the possible *concrete* `type` values, which means
// we have to expand nested enums such as `Statement`
for variant in self.variants.iter() {
if let Some(nested_enum) = enums.get(variant) {
let outer_variant = format_ident!("{}", variant);
for variant in nested_enum.variants.iter() {
// Skip variants that appear in multiple nested enums, we deserialize
// as the first listed outer variant
if !seen.insert(variant.to_string()) {
continue;
}
// Modeling ESTree only requires a single level of nested enums,
// so that's all we support. Though in theory we could support arbitrary nesting,
// since ultimately we're matching based on the final concrete types.
assert!(!enums.contains_key(variant));
let inner_variant = format_ident!("{}", variant);
tag_variants.push(quote!(#inner_variant));
tag_matches.push(quote! {
#enum_tag::#inner_variant => {
let node: Box<#inner_variant> = <Box<#inner_variant> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(#name::#outer_variant(#outer_variant::#inner_variant(node)))
}
});
}
} else {
if !seen.insert(variant.to_string()) {
panic!(
"Concrete variant {} was already added by a nested enum",
variant
);
}
let variant_name = format_ident!("{}", variant);
tag_variants.push(quote!(#variant_name));
tag_matches.push(quote! {
#enum_tag::#variant_name => {
let node: Box<#variant_name> = <Box<#variant_name> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(#name::#variant_name(node))
}
})
}
}
quote! {
#[derive(Serialize, Clone, Debug)]
#[serde(untagged)]
pub enum #name {
#(#variants),*
}
#[derive(Deserialize, Debug)]
enum #enum_tag {
#(#tag_variants),*
}
impl <'de> serde::Deserialize<'de> for #name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de> {
let tagged = serde::Deserializer::deserialize_any(
deserializer,
serde::__private::de::TaggedContentVisitor::<#enum_tag>::new("type", #name_str)
)?;
match tagged.0 {
#(#tag_matches),*
}
}
}
}
}
pub fn codegen_visitor(&self, name: &str) -> TokenStream {
let visitor_name = format_ident!("visit_{}", to_lower_snake_case(name));
let name = format_ident!("{}", name);
let mut tag_matches = Vec::new();
for variant in self.variants.iter() {
let node_variant = format_ident!("{}", variant);
let visitor_name = format_ident!("visit_{}", to_lower_snake_case(variant));
tag_matches.push(quote! {
#name::#node_variant(ast) => {
self.#visitor_name(ast);
}
})
}
quote! {
fn #visitor_name(&mut self, ast: &#name) {
match ast {
#(#tag_matches),*
}
}
}
}
pub fn codegen_hermes(&self, name: &str, grammar: &Grammar) -> TokenStream {
let name_str = name;
let name = format_ident!("{}", name);
let mut tag_matches = Vec::new();
let mut seen = HashSet::new();
// Imagine a case like:
// enum ModuleItem {
// ImportDeclaration, // struct
// Statement // another enum
// }
// We need to generate matches for all the possible *concrete* `type` values, which means
// we have to expand nested enums such as `Statement`
for variant in self.variants.iter() {
if let Some(nested_enum) = grammar.enums.get(variant) {
let outer_variant = format_ident!("{}", variant);
for variant in nested_enum.variants.iter() {
// Skip variants that appear in multiple nested enums, we deserialize
// as the first listed outer variant
if !seen.insert(variant.to_string()) {
continue;
}
// Modeling ESTree only requires a single level of nested enums,
// so that's all we support. Though in theory we could support arbitrary nesting,
// since ultimately we're matching based on the final concrete types.
assert!(!grammar.enums.contains_key(variant));
let node = grammar.nodes.get(variant).unwrap();
if node.skip_hermes_enum_variant {
continue;
}
let inner_variant = format_ident!("{}", variant);
let node_variant_name = node.type_.as_ref().unwrap_or(variant);
let node_variant = format_ident!("{}", node_variant_name);
tag_matches.push(quote! {
NodeKind::#node_variant => {
let node = #inner_variant::convert(cx, node);
#name::#outer_variant(#outer_variant::#inner_variant(Box::new(node)))
}
});
}
} else {
if !seen.insert(variant.to_string()) {
panic!(
"Concrete variant {} was already added by a nested enum",
variant
);
}
let variant_name = format_ident!("{}", variant);
let node = grammar.nodes.get(variant).unwrap();
if node.skip_hermes_enum_variant {
continue;
}
let node_variant_name = node.type_.as_ref().unwrap_or(variant);
let node_variant = format_ident!("{}", node_variant_name);
tag_matches.push(quote! {
NodeKind::#node_variant => {
let node = #variant_name::convert(cx, node);
#name::#variant_name(Box::new(node))
}
})
}
}
quote! {
impl FromHermes for #name {
fn convert(cx: &mut Context, node: NodePtr) -> Self {
let node_ref = node.as_ref();
match node_ref.kind {
#(#tag_matches),*
_ => panic!("Unexpected node kind `{:?}` for `{}`", node_ref.kind, #name_str)
}
}
}
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(transparent)]
#[serde(deny_unknown_fields)]
pub struct Operator {
pub variants: IndexMap<String, String>,
}
impl Operator {
pub fn codegen(&self, name: &str) -> TokenStream {
let mut sorted_variants: Vec<_> = self.variants.iter().collect();
sorted_variants.sort();
let name = format_ident!("{}", name);
let variants: Vec<_> = sorted_variants
.iter()
.map(|(name, operator)| {
let name = format_ident!("{}", name);
let comment = format!(" {}", &operator);
quote! {
#[doc = #comment]
#[serde(rename = #operator)]
#name
}
})
.collect();
let display_matches: Vec<_> = sorted_variants
.iter()
.map(|(name, operator)| {
let name = format_ident!("{}", name);
quote!(Self::#name => #operator)
})
.collect();
let fromstr_matches: Vec<_> = sorted_variants
.iter()
.map(|(name, operator)| {
let name = format_ident!("{}", name);
quote!(#operator => Ok(Self::#name))
})
.collect();
quote! {
#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum #name {
#(#variants),*
}
impl std::fmt::Display for #name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
#(#display_matches),*
};
f.write_str(name)
}
}
impl std::str::FromStr for #name {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
#(#fromstr_matches,)*
_ => Err(()),
}
}
}
}
}
pub fn codegen_hermes(&self, name: &str) -> TokenStream {
let mut sorted_variants: Vec<_> = self.variants.iter().collect();
sorted_variants.sort();
let name = format_ident!("{}", name);
quote! {
impl FromHermesLabel for #name {
fn convert(cx: &mut Context, label: NodeLabel) -> Self {
let utf_str = utf8_with_surrogates_to_string(label.as_slice()).unwrap();
utf_str.parse().unwrap()
}
}
}
}
}
enum TypeKind {
/// T
Named,
/// Option<T>
Option,
/// Vec<T>
Vec,
/// Vec<Option<T>>
VecOfOption,
}
/// Parses a given type into the underlying type name plus a descriptor of the
/// kind of type. Only a subset of Rust types are supported:
/// - T
/// - Option<T>
/// - Vec<T>
/// - Vec<Option<T>>
fn parse_type(type_: &str) -> Result<(String, TypeKind), String> {
let mut current = type_;
let mut is_list = false;
let mut is_option = false;
if current.starts_with("Vec<") {
current = &current[4..current.len() - 1];
is_list = true;
}
if current.starts_with("Option<") {
current = &current[7..current.len() - 1];
is_option = true;
}
if current.contains('<') {
Err(format!(
"Unsupported type `{current}` expected named type (`Identifier`), optional type (`Option<Identifier>`), list type (`Vec<Identifier>`), or optional list (`Vec<Option<Identifier>>`)"
))
} else {
let kind = match (is_list, is_option) {
(true, true) => TypeKind::VecOfOption,
(true, false) => TypeKind::Vec,
(false, true) => TypeKind::Option,
(false, false) => TypeKind::Named,
};
Ok((current.to_string(), kind))
}
}
// from https://github.com/rust-lang/rust-analyzer/blob/4105378dc7479a3dbd39a4afb3eba67d083bd7f8/xtask/src/codegen/gen_syntax.rs#L406C1-L418C2
fn to_lower_snake_case(s: &str) -> String {
let mut buf = String::with_capacity(s.len());
let mut prev = false;
for c in s.chars() {
if c.is_ascii_uppercase() {
if prev {
buf.push('_')
}
prev = false;
} else {
prev = true;
}
buf.push(c.to_ascii_lowercase());
}
buf
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
mod codegen;
pub use codegen::{estree, estree_hermes};

View File

@@ -1,24 +0,0 @@
[package]
name = "react_fixtures"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dev-dependencies]
insta = { workspace = true }
react_estree = { workspace = true }
react_hermes_parser = { workspace = true }
react_hir = { workspace = true }
react_optimization = { workspace = true }
react_semantic_analysis = { workspace = true }
react_ssa = { workspace = true }
react_build_hir = { workspace = true }
miette = { workspace = true, features = ["backtrace", "fancy"] }

View File

@@ -1,3 +0,0 @@
# fixtures
This crate is for tests only, and runs the suite of compiler fixture tests.

View File

@@ -1,6 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

View File

@@ -1,34 +0,0 @@
function Component() {
let a = 1;
let b;
if (a === 1) {
b = true;
} else {
b = false;
}
let c;
if (b) {
c = "hello";
} else {
c = null;
}
let d;
if (c === "hello") {
d = 42.0;
} else {
d = 42.001;
}
let e;
if (d === 42.0) {
e = "ok";
} else {
e = "nope";
}
// should constant-propagate to "ok"
return e;
}

View File

@@ -1,60 +0,0 @@
function Component(props) {
// global propagation
let a;
a = Math;
a; // Math
// primitive propagation w phi
let b;
if (props) {
b = true;
} else {
b = true;
}
b; // true
// primitive propagation fails if different values
let c;
if (props) {
c = true;
} else {
c = 42;
}
c; // <no change>
// constant evaluation
42 + 1; // 43
42 - 1; // 41
42 * 2; // 84
42 / 2; // 21
0 == 1; // false
0 != 1; // true
0 === 1; // false
0 !== 1; // true
0 == 0; // true
// TODO: unary operators
// 0 == -0; // false
// 0 != -0; // true
// 0 === -0; // false
// 0 !== -0; // true
NaN == NaN; // false
NaN != NaN; // true
NaN !== NaN; // true
NaN !== NaN; // true
"hello" == "hello"; // true
"hello" != "hello"; // false
"hello" === "hello"; // true
"hello" !== "hello"; // false
"hello" == "world"; // false
"hello" != "world"; // true
"hello" === "world"; // false
"hello" !== "world"; // true
true == true; // true
true != true; // false
true === true; // true
true !== true; // false
// constant evaluation through variable
let x = 5 * 60 * 60 * 1000; // 5 hours in milliseconds
x;
}

View File

@@ -1,5 +0,0 @@
function Component(a, b) {
const [c, , ...d] = a;
const [[[e]], ...[f]] = b;
return [c, d, e, f];
}

View File

@@ -1,15 +0,0 @@
function Component(a, b) {
const {
c,
d,
e: { e },
f: { _f: f },
g: {
g: {
g: { g, ...h },
},
},
...i
} = a;
return [c, d, e, f, g, h, i];
}

View File

@@ -1,3 +0,0 @@
function foo() {
x = true;
}

View File

@@ -1,7 +0,0 @@
function foo() {
let x = 0;
for (let i = 0; i < 10; i = i + 1) {
x = x + i;
}
return x;
}

View File

@@ -1,21 +0,0 @@
function Component(props) {
const x = 2;
const foo = function foo(y) {
let a = 1;
let b;
if (a === 1) {
b = 5 + 3;
} else {
b = false;
}
x + y + a + b;
const bar = function bar(z) {
let c = 2;
let d;
d = 3;
x + y + a + b + z + c + d;
};
bar;
foo;
};
}

View File

@@ -1,16 +0,0 @@
// import React from "react";
// const FOO = false;
function id(x) {
// React;
// FOO;
Math;
id;
let y = true;
y = false;
y;
let z;
z;
return x;
}

View File

@@ -1,8 +0,0 @@
function foo(a, b, c, d) {
if (a) {
return b;
} else {
c;
}
d;
}

View File

@@ -1,9 +0,0 @@
function Component(a) {
Math;
let b = 0;
const foo = function foo_(c) {
let d = 1;
return a + b + c + d;
};
return foo();
}

View File

@@ -1,9 +0,0 @@
function Component(props) {
let a;
if (props) {
a = 1;
} else {
a = 2;
}
return a;
}

View File

@@ -1,4 +0,0 @@
function test() {
[true, false, null, 1, 3.14, ...["hello world!"]];
return 2;
}

View File

@@ -1,16 +0,0 @@
function Component(a, b) {
let x;
let y = 0;
let z = 10;
if (a) {
x = 1;
if (b) {
z = 20;
} else {
z = 30;
}
} else {
x = 2;
}
return x + y + z;
}

View File

@@ -1,8 +0,0 @@
import { useMemo } from "react";
function Component(x) {
const y = useMemo(() => {
return x;
});
return y;
}

View File

@@ -1,88 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::env;
use std::fmt::Write;
use insta::{assert_snapshot, glob};
use miette::{NamedSource, Report};
use react_build_hir::build;
use react_estree::{ModuleItem, Statement};
use react_hermes_parser::parse;
use react_hir::{inline_use_memo, Environment, Features, Print, Registry};
use react_optimization::constant_propagation;
use react_semantic_analysis::analyze;
use react_ssa::{eliminate_redundant_phis, enter_ssa};
#[test]
fn fixtures() {
glob!("fixtures/**.js", |path| {
println!("fixture {}", path.to_str().unwrap());
let input = std::fs::read_to_string(path).unwrap();
let ast = parse(&input, path.to_str().unwrap()).unwrap();
println!("ok parse");
let mut output = String::new();
let mut analysis = analyze(&ast, Default::default());
let diagnostics = analysis.diagnostics();
if !diagnostics.is_empty() {
for diagnostic in diagnostics {
eprintln!(
"{:?}",
Report::new(diagnostic)
.with_source_code(NamedSource::new(path.to_string_lossy(), input.clone(),))
);
}
}
let environment = Environment::new(
Features {
validate_frozen_lambdas: true,
},
Registry,
analysis,
);
for (ix, item) in ast.body.iter().enumerate() {
if let ModuleItem::Statement(stmt) = item {
if let Statement::FunctionDeclaration(fun) = stmt {
if ix != 0 {
output.push_str("\n\n");
}
match build(&environment, &fun.function) {
Ok(mut fun) => {
println!("ok build");
enter_ssa(&environment, &mut fun).unwrap();
println!("ok enter_ssa");
eliminate_redundant_phis(&environment, &mut fun);
println!("ok eliminate_redundant_phis");
constant_propagation(&environment, &mut fun).unwrap();
println!("ok constant_propagation");
inline_use_memo(&environment, &mut fun).unwrap();
println!("ok inline_use_memo");
fun.print(&fun.body, &mut output).unwrap();
println!("ok print");
}
Err(error) => {
write!(&mut output, "{}", error,).unwrap();
eprintln!(
"{:?}",
Report::new(error).with_source_code(NamedSource::new(
path.to_string_lossy(),
input.clone(),
))
);
continue;
}
};
}
}
}
let output = output.trim();
assert_snapshot!(format!("Input:\n{input}\n\nOutput:\n{output}"));
});
}

View File

@@ -1,73 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/constant-propagation-constant-if-condition.js
---
Input:
function Component() {
let a = 1;
let b;
if (a === 1) {
b = true;
} else {
b = false;
}
let c;
if (b) {
c = "hello";
} else {
c = null;
}
let d;
if (c === "hello") {
d = 42.0;
} else {
d = 42.001;
}
let e;
if (d === 42.0) {
e = "ok";
} else {
e = "nope";
}
// should constant-propagate to "ok"
return e;
}
Output:
function Component(
)
entry bb0
bb0 (block)
[0] unknown $39 = 1
[1] unknown $41 = StoreLocal Let unknown a$40 = unknown $39
[2] unknown $43 = DeclareLocal Let unknown b$42
[3] unknown $44 = 1
[4] unknown $45 = 1
[5] unknown $46 = true
[6] unknown $47 = true
[7] unknown $49 = StoreLocal Reassign unknown b$48 = unknown $47
[8] unknown $54 = DeclareLocal Let unknown c$53
[9] unknown $56 = true
[10] unknown $57 = "hello"
[11] unknown $59 = StoreLocal Reassign unknown c$58 = unknown $57
[12] unknown $64 = DeclareLocal Let unknown d$63
[13] unknown $66 = "hello"
[14] unknown $67 = "hello"
[15] unknown $68 = true
[16] unknown $69 = 42
[17] unknown $71 = StoreLocal Reassign unknown d$70 = unknown $69
[18] unknown $76 = DeclareLocal Let unknown e$75
[19] unknown $78 = 42
[20] unknown $79 = 42
[21] unknown $80 = true
[22] unknown $81 = "ok"
[23] unknown $83 = StoreLocal Reassign unknown e$82 = unknown $81
[24] unknown $88 = "ok"
[25] Return unknown $88

View File

@@ -1,198 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/constant-propagation.js
---
Input:
function Component(props) {
// global propagation
let a;
a = Math;
a; // Math
// primitive propagation w phi
let b;
if (props) {
b = true;
} else {
b = true;
}
b; // true
// primitive propagation fails if different values
let c;
if (props) {
c = true;
} else {
c = 42;
}
c; // <no change>
// constant evaluation
42 + 1; // 43
42 - 1; // 41
42 * 2; // 84
42 / 2; // 21
0 == 1; // false
0 != 1; // true
0 === 1; // false
0 !== 1; // true
0 == 0; // true
// TODO: unary operators
// 0 == -0; // false
// 0 != -0; // true
// 0 === -0; // false
// 0 !== -0; // true
NaN == NaN; // false
NaN != NaN; // true
NaN !== NaN; // true
NaN !== NaN; // true
"hello" == "hello"; // true
"hello" != "hello"; // false
"hello" === "hello"; // true
"hello" !== "hello"; // false
"hello" == "world"; // false
"hello" != "world"; // true
"hello" === "world"; // false
"hello" !== "world"; // true
true == true; // true
true != true; // false
true === true; // true
true !== true; // false
// constant evaluation through variable
let x = 5 * 60 * 60 * 1000; // 5 hours in milliseconds
x;
}
Output:
function Component(
unknown props$108,
)
entry bb0
bb0 (block)
[0] unknown $110 = DeclareLocal Let unknown a$109
[1] unknown $111 = LoadGlobal Math
[2] unknown $113 = StoreLocal Reassign unknown a$112 = unknown $111
[3] unknown $114 = LoadGlobal Math
[4] unknown $116 = DeclareLocal Let unknown b$115
[5] unknown $117 = LoadLocal unknown props$108
[6] If unknown $117 consequent=bb2 alternate=bb3 fallthrough=bb1
bb2 (block)
predecessors: bb0
[7] unknown $118 = true
[8] unknown $120 = StoreLocal Reassign unknown b$119 = unknown $118
[9] Goto bb1
bb3 (block)
predecessors: bb0
[10] unknown $121 = true
[11] unknown $123 = StoreLocal Reassign unknown b$122 = unknown $121
[12] Goto bb1
bb1 (block)
predecessors: bb2, bb3
b$124: phi(bb2: b$119, bb3: b$122)
[13] unknown $125 = true
[14] unknown $127 = DeclareLocal Let unknown c$126
[15] unknown $129 = LoadLocal unknown props$108
[16] If unknown $129 consequent=bb5 alternate=bb6 fallthrough=bb4
bb5 (block)
predecessors: bb1
[17] unknown $130 = true
[18] unknown $132 = StoreLocal Reassign unknown c$131 = unknown $130
[19] Goto bb4
bb6 (block)
predecessors: bb1
[20] unknown $133 = 42
[21] unknown $135 = StoreLocal Reassign unknown c$134 = unknown $133
[22] Goto bb4
bb4 (block)
predecessors: bb5, bb6
c$136: phi(bb5: c$131, bb6: c$134)
[23] unknown $137 = LoadLocal unknown c$136
[24] unknown $138 = 42
[25] unknown $139 = 1
[26] unknown $140 = 43
[27] unknown $141 = 42
[28] unknown $142 = 1
[29] unknown $143 = 41
[30] unknown $144 = 42
[31] unknown $145 = 2
[32] unknown $146 = 84
[33] unknown $147 = 42
[34] unknown $148 = 2
[35] unknown $149 = 21
[36] unknown $150 = 0
[37] unknown $151 = 1
[38] unknown $152 = false
[39] unknown $153 = 0
[40] unknown $154 = 1
[41] unknown $155 = true
[42] unknown $156 = 0
[43] unknown $157 = 1
[44] unknown $158 = false
[45] unknown $159 = 0
[46] unknown $160 = 1
[47] unknown $161 = true
[48] unknown $162 = 0
[49] unknown $163 = 0
[50] unknown $164 = true
[51] unknown $165 = LoadGlobal NaN
[52] unknown $166 = LoadGlobal NaN
[53] unknown $167 = Binary unknown $165 == unknown $166
[54] unknown $168 = LoadGlobal NaN
[55] unknown $169 = LoadGlobal NaN
[56] unknown $170 = Binary unknown $168 != unknown $169
[57] unknown $171 = LoadGlobal NaN
[58] unknown $172 = LoadGlobal NaN
[59] unknown $173 = Binary unknown $171 !== unknown $172
[60] unknown $174 = LoadGlobal NaN
[61] unknown $175 = LoadGlobal NaN
[62] unknown $176 = Binary unknown $174 !== unknown $175
[63] unknown $177 = "hello"
[64] unknown $178 = "hello"
[65] unknown $179 = true
[66] unknown $180 = "hello"
[67] unknown $181 = "hello"
[68] unknown $182 = false
[69] unknown $183 = "hello"
[70] unknown $184 = "hello"
[71] unknown $185 = true
[72] unknown $186 = "hello"
[73] unknown $187 = "hello"
[74] unknown $188 = false
[75] unknown $189 = "hello"
[76] unknown $190 = "world"
[77] unknown $191 = false
[78] unknown $192 = "hello"
[79] unknown $193 = "world"
[80] unknown $194 = true
[81] unknown $195 = "hello"
[82] unknown $196 = "world"
[83] unknown $197 = false
[84] unknown $198 = "hello"
[85] unknown $199 = "world"
[86] unknown $200 = true
[87] unknown $201 = true
[88] unknown $202 = true
[89] unknown $203 = true
[90] unknown $204 = true
[91] unknown $205 = true
[92] unknown $206 = false
[93] unknown $207 = true
[94] unknown $208 = true
[95] unknown $209 = true
[96] unknown $210 = true
[97] unknown $211 = true
[98] unknown $212 = false
[99] unknown $213 = 5
[100] unknown $214 = 60
[101] unknown $215 = 300
[102] unknown $216 = 60
[103] unknown $217 = 18000
[104] unknown $218 = 1000
[105] unknown $219 = 18000000
[106] unknown $221 = StoreLocal Let unknown x$220 = unknown $219
[107] unknown $222 = 18000000
[108] unknown $223 = <undefined>
[109] Return unknown $223

View File

@@ -1,33 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/destructure-array.js
---
Input:
function Component(a, b) {
const [c, , ...d] = a;
const [[[e]], ...[f]] = b;
return [c, d, e, f];
}
Output:
function Component(
unknown a$22,
unknown b$23,
)
entry bb0
bb0 (block)
[0] unknown $24 = LoadLocal unknown a$22
[1] unknown $27 = Destructure [ unknown c$25, <hole>, ...unknown d$26 ] = unknown $24
[2] unknown $28 = LoadLocal unknown b$23
[3] unknown $31 = Destructure [ unknown $29, ...unknown $30 ] = unknown $28
[4] unknown $33 = Destructure [ unknown $32 ] = unknown $29
[5] unknown $35 = Destructure [ unknown e$34 ] = unknown $32
[6] unknown $37 = Destructure [ unknown f$36 ] = unknown $30
[7] unknown $38 = LoadLocal unknown c$25
[8] unknown $39 = LoadLocal unknown d$26
[9] unknown $40 = LoadLocal unknown e$34
[10] unknown $41 = LoadLocal unknown f$36
[11] unknown $42 = Array [unknown $38, unknown $39, unknown $40, unknown $41]
[12] Return unknown $42

View File

@@ -1,46 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/destructure-object.js
---
Input:
function Component(a, b) {
const {
c,
d,
e: { e },
f: { _f: f },
g: {
g: {
g: { g, ...h },
},
},
...i
} = a;
return [c, d, e, f, g, h, i];
}
Output:
function Component(
unknown a$30,
unknown b$31,
)
entry bb0
bb0 (block)
[0] unknown $32 = LoadLocal unknown a$30
[1] unknown $39 = Destructure { c: unknown c$33, d: unknown d$34, e: unknown $35, f: unknown $36, g: unknown $37, ...unknown i$38 } = unknown $32
[2] unknown $41 = Destructure { e: unknown e$40 } = unknown $35
[3] unknown $43 = Destructure { _f: unknown f$42 } = unknown $36
[4] unknown $45 = Destructure { g: unknown $44 } = unknown $37
[5] unknown $47 = Destructure { g: unknown $46 } = unknown $44
[6] unknown $50 = Destructure { g: unknown g$48, ...unknown h$49 } = unknown $46
[7] unknown $51 = LoadLocal unknown c$33
[8] unknown $52 = LoadLocal unknown d$34
[9] unknown $53 = LoadLocal unknown e$40
[10] unknown $54 = LoadLocal unknown f$42
[11] unknown $55 = LoadLocal unknown g$48
[12] unknown $56 = LoadLocal unknown h$49
[13] unknown $57 = LoadLocal unknown i$38
[14] unknown $58 = Array [unknown $51, unknown $52, unknown $53, unknown $54, unknown $55, unknown $56, unknown $57]
[15] Return unknown $58

View File

@@ -1,13 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/error.assign-to-global.js
---
Input:
function foo() {
x = true;
}
Output:
React functions may not reassign variables defined outside of the component or hook

View File

@@ -1,54 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/for-statement.js
---
Input:
function foo() {
let x = 0;
for (let i = 0; i < 10; i = i + 1) {
x = x + i;
}
return x;
}
Output:
function foo(
)
entry bb0
bb0 (block)
[0] unknown $19 = 0
[1] unknown $21 = StoreLocal Let unknown x$20 = unknown $19
[2] For init=bb3 test=bb1 update=bb4 body=bb5 fallthrough=bb2
bb3 (loop)
predecessors: bb0
[3] unknown $22 = 0
[4] unknown $24 = StoreLocal Let unknown i$23 = unknown $22
[5] Goto bb1
bb1 (loop)
predecessors: bb3, bb4
i$25: phi(bb3: i$23, bb4: i$38)
x$29: phi(bb3: x$20, bb4: x$33)
[6] unknown $26 = LoadLocal unknown i$25
[7] unknown $27 = 10
[8] unknown $28 = Binary unknown $26 < unknown $27
[9] Branch unknown $28 consequent=bb5 alternate=bb2
bb5 (block)
predecessors: bb1
[10] unknown $30 = LoadLocal unknown x$29
[11] unknown $31 = LoadLocal unknown i$25
[12] unknown $32 = Binary unknown $30 + unknown $31
[13] unknown $34 = StoreLocal Reassign unknown x$33 = unknown $32
[14] Goto bb4
bb4 (loop)
predecessors: bb5
[15] unknown $35 = LoadLocal unknown i$25
[16] unknown $36 = 1
[17] unknown $37 = Binary unknown $35 + unknown $36
[18] unknown $39 = StoreLocal Reassign unknown i$38 = unknown $37
[19] Goto bb1
bb2 (block)
predecessors: bb1
[20] unknown $40 = LoadLocal unknown x$29
[21] Return unknown $40

View File

@@ -1,94 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/function-expressions.js
---
Input:
function Component(props) {
const x = 2;
const foo = function foo(y) {
let a = 1;
let b;
if (a === 1) {
b = 5 + 3;
} else {
b = false;
}
x + y + a + b;
const bar = function bar(z) {
let c = 2;
let d;
d = 3;
x + y + a + b + z + c + d;
};
bar;
foo;
};
}
Output:
function Component(
unknown props$58,
)
entry bb0
bb0 (block)
[0] unknown $59 = 2
[1] unknown $61 = StoreLocal Const unknown x$60 = unknown $59
[2] unknown $62 = Function @deps[] @context[unknown x$60]:
function foo(
unknown y$63,
)
entry bb1
bb1 (block)
[0] unknown $64 = 1
[1] unknown $66 = StoreLocal Let unknown a$65 = unknown $64
[2] unknown $68 = DeclareLocal Let unknown b$67
[3] unknown $69 = 1
[4] unknown $70 = 1
[5] unknown $71 = true
[6] unknown $72 = 5
[7] unknown $73 = 3
[8] unknown $74 = 8
[9] unknown $76 = StoreLocal Reassign unknown b$75 = unknown $74
[10] unknown $81 = 2
[11] unknown $83 = LoadLocal unknown y$63
[12] unknown $84 = Binary unknown $81 + unknown $83
[13] unknown $86 = 1
[14] unknown $87 = Binary unknown $84 + unknown $86
[15] unknown $89 = 8
[16] unknown $90 = Binary unknown $87 + unknown $89
[17] unknown $91 = Function @deps[] @context[unknown x$60, unknown y$63, unknown a$65, unknown b$75]:
function bar(
unknown z$92,
)
entry bb5
bb5 (block)
[0] unknown $93 = 2
[1] unknown $95 = StoreLocal Let unknown c$94 = unknown $93
[2] unknown $97 = DeclareLocal Let unknown d$96
[3] unknown $98 = 3
[4] unknown $100 = StoreLocal Reassign unknown d$99 = unknown $98
[5] unknown $101 = LoadLocal unknown x$80
[6] unknown $102 = LoadLocal unknown y$82
[7] unknown $103 = Binary unknown $101 + unknown $102
[8] unknown $104 = LoadLocal unknown a$85
[9] unknown $105 = Binary unknown $103 + unknown $104
[10] unknown $106 = LoadLocal unknown b$88
[11] unknown $107 = Binary unknown $105 + unknown $106
[12] unknown $108 = LoadLocal unknown z$92
[13] unknown $109 = Binary unknown $107 + unknown $108
[14] unknown $110 = 2
[15] unknown $111 = Binary unknown $109 + unknown $110
[16] unknown $112 = 3
[17] unknown $113 = Binary unknown $111 + unknown $112
[18] unknown $114 = <undefined>
[19] Return unknown $114
[18] unknown $116 = StoreLocal Const unknown bar$115 = unknown $91
[19] unknown $117 = LoadLocal unknown bar$115
[20] unknown $118 = LoadGlobal foo
[21] unknown $119 = <undefined>
[22] Return unknown $119
[3] unknown $121 = StoreLocal Const unknown foo$120 = unknown $62
[4] unknown $122 = <undefined>
[5] Return unknown $122

View File

@@ -1,41 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/identifiers.js
---
Input:
// import React from "react";
// const FOO = false;
function id(x) {
// React;
// FOO;
Math;
id;
let y = true;
y = false;
y;
let z;
z;
return x;
}
Output:
function id(
unknown x$14,
)
entry bb0
bb0 (block)
[0] unknown $15 = LoadGlobal Math
[1] unknown $16 = LoadGlobal id
[2] unknown $17 = true
[3] unknown $19 = StoreLocal Let unknown y$18 = unknown $17
[4] unknown $20 = false
[5] unknown $22 = StoreLocal Reassign unknown y$21 = unknown $20
[6] unknown $23 = false
[7] unknown $25 = DeclareLocal Let unknown z$24
[8] unknown $26 = LoadLocal unknown z$24
[9] unknown $27 = LoadLocal unknown x$14
[10] Return unknown $27

View File

@@ -1,40 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/if-statement.js
---
Input:
function foo(a, b, c, d) {
if (a) {
return b;
} else {
c;
}
d;
}
Output:
function foo(
unknown a$9,
unknown b$10,
unknown c$11,
unknown d$12,
)
entry bb0
bb0 (block)
[0] unknown $13 = LoadLocal unknown a$9
[1] If unknown $13 consequent=bb2 alternate=bb4 fallthrough=bb1
bb2 (block)
predecessors: bb0
[2] unknown $14 = LoadLocal unknown b$10
[3] Return unknown $14
bb4 (block)
predecessors: bb0
[4] unknown $15 = LoadLocal unknown c$11
[5] Goto bb1
bb1 (block)
predecessors: bb4
[6] unknown $16 = LoadLocal unknown d$12
[7] unknown $17 = <undefined>
[8] Return unknown $17

View File

@@ -1,46 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/simple-function.js
---
Input:
function Component(a) {
Math;
let b = 0;
const foo = function foo_(c) {
let d = 1;
return a + b + c + d;
};
return foo();
}
Output:
function Component(
unknown a$23,
)
entry bb0
bb0 (block)
[0] unknown $24 = LoadGlobal Math
[1] unknown $25 = 0
[2] unknown $27 = StoreLocal Let unknown b$26 = unknown $25
[3] unknown $28 = Function @deps[] @context[unknown a$23, unknown b$26]:
function foo_(
unknown c$29,
)
entry bb1
bb1 (block)
[0] unknown $30 = 1
[1] unknown $32 = StoreLocal Let unknown d$31 = unknown $30
[2] unknown $33 = LoadLocal unknown a$23
[3] unknown $34 = 0
[4] unknown $35 = Binary unknown $33 + unknown $34
[5] unknown $36 = LoadLocal unknown c$29
[6] unknown $37 = Binary unknown $35 + unknown $36
[7] unknown $38 = 1
[8] unknown $39 = Binary unknown $37 + unknown $38
[9] Return unknown $39
[4] unknown $41 = StoreLocal Const unknown foo$40 = unknown $28
[5] unknown $42 = LoadLocal unknown foo$40
[6] unknown $43 = Call unknown $42()
[7] Return unknown $43

View File

@@ -1,41 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/simple-ssa.js
---
Input:
function Component(props) {
let a;
if (props) {
a = 1;
} else {
a = 2;
}
return a;
}
Output:
function Component(
unknown props$10,
)
entry bb0
bb0 (block)
[0] unknown $12 = DeclareLocal Let unknown a$11
[1] unknown $13 = LoadLocal unknown props$10
[2] If unknown $13 consequent=bb2 alternate=bb3 fallthrough=bb1
bb2 (block)
predecessors: bb0
[3] unknown $14 = 1
[4] unknown $16 = StoreLocal Reassign unknown a$15 = unknown $14
[5] Goto bb1
bb3 (block)
predecessors: bb0
[6] unknown $17 = 2
[7] unknown $19 = StoreLocal Reassign unknown a$18 = unknown $17
[8] Goto bb1
bb1 (block)
predecessors: bb2, bb3
a$20: phi(bb2: a$15, bb3: a$18)
[9] unknown $21 = LoadLocal unknown a$20
[10] Return unknown $21

View File

@@ -1,27 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/simple.js
---
Input:
function test() {
[true, false, null, 1, 3.14, ...["hello world!"]];
return 2;
}
Output:
function test(
)
entry bb0
bb0 (block)
[0] unknown $10 = true
[1] unknown $11 = false
[2] unknown $12 = null
[3] unknown $13 = 1
[4] unknown $14 = 3.14
[5] unknown $15 = "hello world!"
[6] unknown $16 = Array [unknown $15]
[7] unknown $17 = Array [unknown $10, unknown $11, unknown $12, unknown $13, unknown $14, ...unknown $16]
[8] unknown $18 = 2
[9] Return unknown $18

View File

@@ -1,73 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/ssa-reassign-if.js
---
Input:
function Component(a, b) {
let x;
let y = 0;
let z = 10;
if (a) {
x = 1;
if (b) {
z = 20;
} else {
z = 30;
}
} else {
x = 2;
}
return x + y + z;
}
Output:
function Component(
unknown a$26,
unknown b$27,
)
entry bb0
bb0 (block)
[0] unknown $29 = DeclareLocal Let unknown x$28
[1] unknown $30 = 0
[2] unknown $32 = StoreLocal Let unknown y$31 = unknown $30
[3] unknown $33 = 10
[4] unknown $35 = StoreLocal Let unknown z$34 = unknown $33
[5] unknown $36 = LoadLocal unknown a$26
[6] If unknown $36 consequent=bb2 alternate=bb6 fallthrough=bb1
bb2 (block)
predecessors: bb0
[7] unknown $37 = 1
[8] unknown $39 = StoreLocal Reassign unknown x$38 = unknown $37
[9] unknown $40 = LoadLocal unknown b$27
[10] If unknown $40 consequent=bb4 alternate=bb5 fallthrough=bb3
bb4 (block)
predecessors: bb2
[11] unknown $41 = 20
[12] unknown $43 = StoreLocal Reassign unknown z$42 = unknown $41
[13] Goto bb3
bb5 (block)
predecessors: bb2
[14] unknown $44 = 30
[15] unknown $46 = StoreLocal Reassign unknown z$45 = unknown $44
[16] Goto bb3
bb3 (block)
predecessors: bb4, bb5
z$58: phi(bb4: z$42, bb5: z$45)
[17] Goto bb1
bb6 (block)
predecessors: bb0
[18] unknown $47 = 2
[19] unknown $49 = StoreLocal Reassign unknown x$48 = unknown $47
[20] Goto bb1
bb1 (block)
predecessors: bb3, bb6
x$50: phi(bb3: x$38, bb6: x$48)
z$57: phi(bb3: z$58, bb6: z$34)
[21] unknown $52 = LoadLocal unknown x$50
[22] unknown $55 = 0
[23] unknown $56 = Binary unknown $52 + unknown $55
[24] unknown $59 = LoadLocal unknown z$57
[25] unknown $60 = Binary unknown $56 + unknown $59
[26] Return unknown $60

View File

@@ -1,36 +0,0 @@
---
source: crates/react_fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/react_fixtures/tests/fixtures/use-memo.js
---
Input:
import { useMemo } from "react";
function Component(x) {
const y = useMemo(() => {
return x;
});
return y;
}
Output:
function Component(
unknown x$10,
)
entry bb0
bb0 (block)
[0] unknown $11 = LoadGlobal useMemo
[1] unknown $20 = DeclareLocal Let unknown t$18
[2] Label block=bb1 fallthrough=bb6
bb1 (block)
predecessors: bb0
[3] unknown $13 = LoadLocal unknown x$10
[4] unknown $19 = StoreLocal Reassign unknown t$18 = unknown $13
[5] Goto bb6
bb6 (block)
predecessors: bb1
[6] unknown $14 = LoadLocal unknown t$18
[7] unknown $16 = StoreLocal Const unknown y$15 = unknown $14
[8] unknown $17 = LoadLocal unknown y$15
[9] Return unknown $17

View File

@@ -1,23 +0,0 @@
[package]
name = "react_hermes_parser"
version = "0.1.0"
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
insta = { workspace = true }
react_diagnostics = { workspace = true }
react_estree = { workspace = true }
hermes = { workspace = true }
juno_support = { workspace = true }
serde_json = { workspace = true }
[build-dependencies]
react_estree_codegen = { workspace = true }

View File

@@ -1,3 +0,0 @@
# react_hermes_parser
Wrapper around the Hermes parser that exposes parse results as a `react_estree` AST.

View File

@@ -1,31 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use react_estree_codegen::estree_hermes;
// Example custom build script.
fn main() {
// Re-run if the codegen files change
println!("cargo:rerun-if-changed=../react_estree_codegen/src/codegen.rs");
println!("cargo:rerun-if-changed=../react_estree_codegen/src/lib.rs");
println!("cargo:rerun-if-changed=../react_estree_codegen/src/ecmascript.json");
println!("cargo:rerun-if-changed=../react_estree_codegen");
let src = estree_hermes();
let copyright = "
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
"
.to_string();
let trimmed_copyright = copyright.trim();
let contents = format!("{trimmed_copyright}\n{src}");
std::fs::write("src/generated.rs", contents).unwrap();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,319 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::num::NonZeroU32;
use hermes::parser::{
hermes_get_ArrowFunctionExpression_async, hermes_get_ArrowFunctionExpression_body,
hermes_get_ArrowFunctionExpression_expression, hermes_get_ArrowFunctionExpression_id,
hermes_get_ArrowFunctionExpression_params, hermes_get_ClassDeclaration_body,
hermes_get_ClassDeclaration_id, hermes_get_ClassDeclaration_superClass,
hermes_get_ClassExpression_body, hermes_get_ClassExpression_id,
hermes_get_ClassExpression_superClass, hermes_get_FunctionDeclaration_async,
hermes_get_FunctionDeclaration_body, hermes_get_FunctionDeclaration_generator,
hermes_get_FunctionDeclaration_id, hermes_get_FunctionDeclaration_params,
hermes_get_FunctionExpression_async, hermes_get_FunctionExpression_body,
hermes_get_FunctionExpression_generator, hermes_get_FunctionExpression_id,
hermes_get_FunctionExpression_params, hermes_get_Property_computed, hermes_get_Property_key,
hermes_get_Property_kind, hermes_get_Property_method, hermes_get_Property_shorthand,
hermes_get_Property_value, NodeKind, NodeLabel, NodeLabelOpt, NodeListRef, NodePtr, NodePtrOpt,
NodeString, NodeStringOpt, SMRange,
};
use hermes::utf::utf8_with_surrogates_to_string;
use juno_support::NullTerminatedBuf;
use react_estree::{
ArrowFunctionExpression, AssignmentProperty, Class, ClassBody, ClassDeclaration,
ClassExpression, Expression, ExpressionOrSpread, Function, FunctionBody, FunctionDeclaration,
FunctionExpression, Identifier, Number, Pattern, SourceRange, TemplateElement,
TemplateElementValue,
};
pub struct Context {
start: usize,
}
impl Context {
pub fn new(parser: &NullTerminatedBuf) -> Self {
// SAFETY: This function returns a pointer to the underlying
// buffer. It is safe to get the pointer, it is only unsafe to
// use that pointer in unsafe ways. We only use the value to
// calculate offsets (and only use safe APIs to access the string
// based on those offsets).
let ptr = unsafe { parser.as_ptr() };
let start = ptr as usize;
Self { start }
}
}
pub trait FromHermes {
fn convert(cx: &mut Context, node: NodePtr) -> Self;
}
pub trait FromHermesLabel {
fn convert(cx: &mut Context, label: NodeLabel) -> Self;
}
pub fn convert_option<F, T>(node: NodePtrOpt, f: F) -> Option<T>
where
F: FnMut(NodePtr) -> T,
{
node.as_node_ptr().map(f)
}
pub fn convert_vec<F, T>(node: NodeListRef, mut f: F) -> Vec<T>
where
F: FnMut(NodePtr) -> T,
{
node.iter().map(|node| f(NodePtr::new(node))).collect()
}
pub fn convert_vec_of_option<F, T>(node: NodeListRef, mut f: F) -> Vec<Option<T>>
where
F: FnMut(NodePtr) -> T,
{
node.iter()
.map(|node| {
let node = NodePtr::new(node);
let node_ref = node.as_ref();
match node_ref.kind {
NodeKind::Empty => None,
_ => Some(f(node)),
}
})
.collect()
}
pub fn convert_range(cx: &Context, node: NodePtr) -> SourceRange {
let range = node.as_ref().source_range;
let absolute_start: usize = range.start.as_ptr() as usize;
let start = absolute_start - cx.start;
let absolute_end: usize = range.end.as_ptr() as usize;
let end = absolute_end - cx.start;
SourceRange {
start: start as u32,
end: NonZeroU32::new(end as u32).unwrap(),
}
}
#[allow(dead_code)]
pub fn convert_smrange(_range: SMRange) -> SourceRange {
todo!()
}
pub fn convert_string(_cx: &mut Context, label: NodeLabel) -> String {
utf8_with_surrogates_to_string(label.as_slice()).unwrap()
}
#[allow(dead_code)]
pub fn convert_option_string(cx: &mut Context, label: NodeLabelOpt) -> Option<String> {
label.as_node_label().map(|label| convert_string(cx, label))
}
pub fn convert_string_value(_cx: &mut Context, label: NodeString) -> String {
utf8_with_surrogates_to_string(label.as_slice()).unwrap()
}
pub fn convert_option_string_value(_cx: &mut Context, label: NodeStringOpt) -> Option<String> {
label
.as_node_string()
.map(|label| utf8_with_surrogates_to_string(label.as_slice()).unwrap())
}
pub fn convert_number(value: f64) -> Number {
value.into()
}
pub fn convert_array_expression_elements(
cx: &mut Context,
node: NodeListRef,
) -> Vec<Option<ExpressionOrSpread>> {
convert_vec_of_option(node, |node| ExpressionOrSpread::convert(cx, node))
}
impl FromHermes for TemplateElement {
fn convert(cx: &mut Context, node: NodePtr) -> Self {
let range = convert_range(cx, node);
let tail = unsafe { hermes::parser::hermes_get_TemplateElement_tail(node) };
let value = TemplateElementValue {
cooked: convert_option_string_value(cx, unsafe {
hermes::parser::hermes_get_TemplateElement_cooked(node)
}),
raw: convert_string(cx, unsafe {
hermes::parser::hermes_get_TemplateElement_raw(node)
}),
};
Self {
tail,
value,
loc: None,
range: Some(range),
}
}
}
impl FromHermes for AssignmentProperty {
fn convert(cx: &mut Context, node: NodePtr) -> Self {
let key = FromHermes::convert(cx, unsafe { hermes_get_Property_key(node) });
let value = FromHermes::convert(cx, unsafe { hermes_get_Property_value(node) });
let kind = FromHermesLabel::convert(cx, unsafe { hermes_get_Property_kind(node) });
let is_method = unsafe { hermes_get_Property_method(node) };
let is_computed = unsafe { hermes_get_Property_computed(node) };
let is_shorthand = unsafe { hermes_get_Property_shorthand(node) };
let loc = None;
let range = convert_range(cx, node);
AssignmentProperty {
key,
value,
kind,
is_method,
is_computed,
is_shorthand,
loc,
range: Some(range),
}
}
}
impl FromHermes for FunctionDeclaration {
fn convert(cx: &mut Context, node: NodePtr) -> Self {
let id = convert_option(unsafe { hermes_get_FunctionDeclaration_id(node) }, |node| {
Identifier::convert(cx, node)
});
let params = convert_vec(
unsafe { hermes_get_FunctionDeclaration_params(node) },
|node| Pattern::convert(cx, node),
);
let body = FunctionBody::convert(cx, unsafe { hermes_get_FunctionDeclaration_body(node) });
let is_generator = unsafe { hermes_get_FunctionDeclaration_generator(node) };
let is_async = unsafe { hermes_get_FunctionDeclaration_async(node) };
let loc = None;
let range = convert_range(cx, node);
FunctionDeclaration {
function: Function {
id,
params,
body: Some(body),
is_generator,
is_async,
loc: loc.clone(),
range: Some(range),
},
loc,
range: Some(range),
}
}
}
impl FromHermes for FunctionExpression {
fn convert(cx: &mut Context, node: NodePtr) -> Self {
let id = convert_option(unsafe { hermes_get_FunctionExpression_id(node) }, |node| {
Identifier::convert(cx, node)
});
let params = convert_vec(
unsafe { hermes_get_FunctionExpression_params(node) },
|node| Pattern::convert(cx, node),
);
let body = FunctionBody::convert(cx, unsafe { hermes_get_FunctionExpression_body(node) });
let is_generator = unsafe { hermes_get_FunctionExpression_generator(node) };
let is_async = unsafe { hermes_get_FunctionExpression_async(node) };
let loc = None;
let range = convert_range(cx, node);
FunctionExpression {
function: Function {
id,
params,
body: Some(body),
is_generator,
is_async,
loc: loc.clone(),
range: Some(range),
},
loc,
range: Some(range),
}
}
}
impl FromHermes for ArrowFunctionExpression {
fn convert(cx: &mut Context, node: NodePtr) -> Self {
let id = convert_option(
unsafe { hermes_get_ArrowFunctionExpression_id(node) },
|node| Identifier::convert(cx, node),
);
let params = convert_vec(
unsafe { hermes_get_ArrowFunctionExpression_params(node) },
|node| Pattern::convert(cx, node),
);
let body =
FunctionBody::convert(cx, unsafe { hermes_get_ArrowFunctionExpression_body(node) });
let is_generator = unsafe { hermes_get_FunctionExpression_generator(node) };
let is_async = unsafe { hermes_get_ArrowFunctionExpression_async(node) };
let is_expression = unsafe { hermes_get_ArrowFunctionExpression_expression(node) };
let loc = None;
let range = convert_range(cx, node);
ArrowFunctionExpression {
function: Function {
id,
params,
body: Some(body),
is_generator,
is_async,
loc: loc.clone(),
range: Some(range),
},
is_expression,
loc,
range: Some(range),
}
}
}
impl FromHermes for ClassDeclaration {
fn convert(cx: &mut Context, node: NodePtr) -> Self {
let id = convert_option(unsafe { hermes_get_ClassDeclaration_id(node) }, |node| {
Identifier::convert(cx, node)
});
let super_class = convert_option(
unsafe { hermes_get_ClassDeclaration_superClass(node) },
|node| Expression::convert(cx, node),
);
let body = ClassBody::convert(cx, unsafe { hermes_get_ClassDeclaration_body(node) });
let loc = None;
let range = convert_range(cx, node);
ClassDeclaration {
class: Class {
id,
super_class,
body,
},
loc,
range: Some(range),
}
}
}
impl FromHermes for ClassExpression {
fn convert(cx: &mut Context, node: NodePtr) -> Self {
let id = convert_option(unsafe { hermes_get_ClassExpression_id(node) }, |node| {
Identifier::convert(cx, node)
});
let super_class = convert_option(
unsafe { hermes_get_ClassExpression_superClass(node) },
|node| Expression::convert(cx, node),
);
let body = ClassBody::convert(cx, unsafe { hermes_get_ClassExpression_body(node) });
let loc = None;
let range = convert_range(cx, node);
ClassExpression {
class: Class {
id,
super_class,
body,
},
loc,
range: Some(range),
}
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
mod generated;
mod generated_extension;
use generated_extension::{Context, FromHermes};
use hermes::parser::{HermesParser, ParserDialect, ParserFlags};
use hermes::utf::utf8_with_surrogates_to_string;
use juno_support::NullTerminatedBuf;
use react_diagnostics::Diagnostic;
use react_estree::Program;
pub fn parse(source: &str, _file: &str) -> Result<Program, Vec<Diagnostic>> {
let buf = NullTerminatedBuf::from_str_check(source);
let result = HermesParser::parse(
ParserFlags {
dialect: ParserDialect::TypeScript,
enable_jsx: true,
store_doc_block: true,
strict_mode: true,
},
&buf,
);
let mut cx = Context::new(&buf);
if result.has_errors() {
let error_messages = result.messages();
return Err(error_messages
.iter()
.map(|diag| {
let message = utf8_with_surrogates_to_string(diag.message.as_slice()).unwrap();
Diagnostic::invalid_syntax(message, None)
})
.collect());
}
Ok(FromHermes::convert(&mut cx, result.root().unwrap()))
}

View File

@@ -1,11 +0,0 @@
function Component() {
// a's mutable range should be the same as x's mutable range,
// since a is captured into x (which gets mutated later)
let a = someObj();
let x = [];
x.push(a);
mutate(x);
return [x, a];
}

View File

@@ -1,10 +0,0 @@
function Component() {
// a's mutable range should be limited
// the following line
let a = someObj();
let x = [];
x.push(a);
return [x, a];
}

View File

@@ -1,8 +0,0 @@
function component(a) {
let x = { a };
let y = {};
y.x = x["a"];
mutate(y);
return x;
}

View File

@@ -1,9 +0,0 @@
function component() {
let z = [];
let y = {};
y.z = z;
let x = {};
x.y = y;
mutate(x.y.z);
return x;
}

View File

@@ -1,8 +0,0 @@
function component() {
let z = [];
let y = {};
y.z = z;
let x = {};
x.y = y;
return x;
}

View File

@@ -1,18 +0,0 @@
function foo(cond) {
let a = {};
let b = {};
let c = {};
while (cond) {
let z = a;
a = b;
b = c;
c = z;
mutate(a, b);
}
a;
b;
c;
return a;
}
function mutate(x, y) {}

View File

@@ -1,11 +0,0 @@
// bar(props.b) is an allocating expression that produces a primitive, which means
// that Forget should memoize it.
// Correctness:
// - y depends on either bar(props.b) or bar(props.b) + 1
function AllocatingPrimitiveAsDepNested(props) {
let x = {};
mutate(x);
let y = foo(bar(props.b) + 1);
mutate(x, props.a);
return [x, y];
}

View File

@@ -1,8 +0,0 @@
// bar(props.b) is an allocating expression that produces a primitive, which means
// that Forget should memoize it.
// Correctness:
// - y depends on either bar(props.b) or bar(props.b) + 1
function AllocatingPrimitiveAsDep(props) {
let y = foo(bar(props).b + 1);
return y;
}

View File

@@ -1,4 +0,0 @@
function Component(props) {
const ref = useRef(null);
return <Foo ref={ref} />;
}

View File

@@ -1,8 +0,0 @@
function foo(a, b, c) {
const x = [a];
const y = [null, b];
const z = [[], [], [c]];
x[0] = y[1];
z[0][0] = x[0];
return [x, z];
}

View File

@@ -1,9 +0,0 @@
function Component(props) {
const x = foo(props.x);
const fn = function () {
const arr = [...bar(props)];
return arr.at(x);
};
const fnResult = fn();
return fnResult;
}

View File

@@ -1,9 +0,0 @@
// arrayInstance.at should have the following effects:
// - read on arg0
// - read on receiver
// - mutate on lvalue
function ArrayAtTest(props) {
const arr = [foo(props.x)];
const result = arr.at(bar(props.y));
return result;
}

View File

@@ -1,10 +0,0 @@
// x's mutable range should extend to `mutate(y)`
function Component(props) {
let x = [42, {}];
const idx = foo(props.b);
let y = x.at(idx);
mutate(y);
return x;
}

View File

@@ -1,4 +0,0 @@
function Component(props) {
const x = [0, ...props.foo, null, ...props.bar, "z"];
return x;
}

View File

@@ -1,6 +0,0 @@
function Component(props) {
const x = [{}, [], props.value];
const y = x.join(() => "this closure gets stringified, not called");
foo(y);
return [x, y];
}

View File

@@ -1,6 +0,0 @@
function Component(props) {
const x = [];
<dif>{x}</dif>;
const y = x.map((item) => item);
return [x, y];
}

View File

@@ -1,8 +0,0 @@
function Component(props) {
const x = [];
const y = x.map((item) => {
item.updated = true;
return item;
});
return [x, y];
}

View File

@@ -1,5 +0,0 @@
function component([a, b]) {
let y = { a };
let z = { b };
return [y, z];
}

View File

@@ -1,6 +0,0 @@
function Component(props) {
const a = [props.a, props.b, "hello"];
const x = a.length;
const y = a.push;
return { a, x, y, z: a.concat };
}

View File

@@ -1,7 +0,0 @@
function Component(props) {
const a = [props.a, props.b, "hello"];
const x = a.push(42);
const y = a.at(props.c);
return { a, x, y };
}

View File

@@ -1,11 +0,0 @@
// arrayInstance.push should have the following effects:
// - read on all args (rest parameter)
// - mutate on receiver
function Component(props) {
const x = foo(props.x);
const y = { y: props.y };
const arr = [];
arr.push({});
arr.push(x, y);
return arr;
}

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