mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[rust] Data structures for semantic analysis
This is two things: * A toy semantic analysis that handles a tiny subset of JS, including labeled statements, labeled break/continue, and variable declaration/reference/reassignment. This only exists as a way to prove out the API for the more important bit: * More importantly, this defines a data model for the semantic analysis results and an API for building up the semantic analysis. Subsequent diffs will replace the first bit (toy analysis impl), while keeping the second part.
This commit is contained in:
Generated
+5
@@ -566,8 +566,13 @@ dependencies = [
|
||||
name = "forget_semantic_analysis"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"forget_diagnostics",
|
||||
"forget_estree",
|
||||
"forget_hermes_parser",
|
||||
"forget_utils",
|
||||
"indexmap 2.0.0",
|
||||
"insta",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -22,6 +22,7 @@ forget_fixtures = { path = "crates/forget_fixtures" }
|
||||
forget_hermes_parser = { path = "crates/forget_hermes_parser" }
|
||||
forget_hir = { path = "crates/forget_hir" }
|
||||
forget_optimization = { path = "crates/forget_optimization" }
|
||||
forget_semantic_analysis = { path = "crates/forget_semantic_analysis" }
|
||||
forget_ssa = { path = "crates/forget_ssa" }
|
||||
forget_swc_demo = { path = "crates/forget_swc_demo" }
|
||||
forget_utils = { path = "crates/forget_utils" }
|
||||
|
||||
@@ -2,10 +2,10 @@ use crate::{
|
||||
AssignmentPropertyOrRestElement, AssignmentTarget, Class, ClassItem, ClassPrivateProperty,
|
||||
ClassProperty, Declaration, DeclarationOrExpression, ExportAllDeclaration,
|
||||
ExportDefaultDeclaration, ExportNamedDeclaration, Expression, ExpressionOrPrivateIdentifier,
|
||||
ExpressionOrSpread, ExpressionOrSuper, ForInInit, ForInit, Function, FunctionBody, Identifier,
|
||||
ImportDeclaration, ImportDeclarationSpecifier, ImportOrExportDeclaration, Literal,
|
||||
MethodDefinition, ModuleItem, Pattern, PrivateIdentifier, PrivateName, Program, Statement,
|
||||
StaticBlock, Super, SwitchCase, VariableDeclarator, _Literal,
|
||||
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
|
||||
@@ -17,7 +17,18 @@ pub trait Visitor<'ast> {
|
||||
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);
|
||||
}
|
||||
@@ -73,7 +84,7 @@ pub trait Visitor<'ast> {
|
||||
fn visit_import_declaration(&mut self, declaration: &'ast ImportDeclaration) {
|
||||
self.visit_lvalue(|visitor| {
|
||||
for specifier in &declaration.specifiers {
|
||||
visitor.visit_import_declaration_specifier(specifier, &declaration.source)
|
||||
visitor.visit_import_declaration_specifier(specifier);
|
||||
}
|
||||
});
|
||||
self.visit_import_source(&declaration.source);
|
||||
@@ -101,11 +112,7 @@ pub trait Visitor<'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_import_declaration_specifier(
|
||||
&mut self,
|
||||
specifier: &'ast ImportDeclarationSpecifier,
|
||||
_source: &'ast _Literal,
|
||||
) {
|
||||
fn visit_import_declaration_specifier(&mut self, specifier: &'ast ImportDeclarationSpecifier) {
|
||||
match specifier {
|
||||
ImportDeclarationSpecifier::ImportSpecifier(specifier) => {
|
||||
self.visit_identifier(&specifier.local);
|
||||
@@ -129,7 +136,7 @@ pub trait Visitor<'ast> {
|
||||
self.visit_class(&declaration.class);
|
||||
}
|
||||
Declaration::FunctionDeclaration(declaration) => {
|
||||
self.visit_function(&declaration.function);
|
||||
self.visit_function_declaration(declaration);
|
||||
}
|
||||
Declaration::VariableDeclaration(declaration) => {
|
||||
for declarator in &declaration.declarations {
|
||||
@@ -142,6 +149,10 @@ pub trait Visitor<'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -198,7 +209,7 @@ pub trait Visitor<'ast> {
|
||||
self.visit_statement(&stmt.body);
|
||||
}
|
||||
Statement::FunctionDeclaration(stmt) => {
|
||||
self.visit_function(&stmt.function);
|
||||
self.visit_function_declaration(stmt);
|
||||
}
|
||||
Statement::IfStatement(stmt) => {
|
||||
self.visit_expression(&stmt.test);
|
||||
@@ -270,32 +281,28 @@ pub trait Visitor<'ast> {
|
||||
}
|
||||
for item in &class.body.body {
|
||||
match item {
|
||||
ClassItem::MethodDefinition(item) => self.visit_method_definition(class, item),
|
||||
ClassItem::MethodDefinition(item) => self.visit_method_definition(item),
|
||||
ClassItem::ClassProperty(item) => {
|
||||
self.visit_class_property(class, item);
|
||||
self.visit_class_property(item);
|
||||
}
|
||||
ClassItem::ClassPrivateProperty(item) => {
|
||||
self.visit_class_private_property(class, item);
|
||||
self.visit_class_private_property(item);
|
||||
}
|
||||
ClassItem::StaticBlock(item) => {
|
||||
self.visit_static_block(class, item);
|
||||
self.visit_static_block(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_class_property(&mut self, _class: &'ast Class, property: &'ast ClassProperty) {
|
||||
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,
|
||||
_class: &'ast Class,
|
||||
property: &'ast ClassPrivateProperty,
|
||||
) {
|
||||
fn visit_class_private_property(&mut self, property: &'ast ClassPrivateProperty) {
|
||||
match &property.key {
|
||||
ExpressionOrPrivateIdentifier::Expression(key) => self.visit_expression(key),
|
||||
ExpressionOrPrivateIdentifier::PrivateIdentifier(key) => {
|
||||
@@ -308,21 +315,17 @@ pub trait Visitor<'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_static_block(&mut self, _class: &'ast Class, property: &'ast StaticBlock) {
|
||||
fn visit_static_block(&mut self, property: &'ast StaticBlock) {
|
||||
for stmt in &property.body {
|
||||
self.visit_statement(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_method_definition(&mut self, class: &'ast Class, method: &'ast MethodDefinition) {
|
||||
self.default_visit_method_definition(class, method);
|
||||
fn visit_method_definition(&mut self, method: &'ast MethodDefinition) {
|
||||
self.default_visit_method_definition(method);
|
||||
}
|
||||
|
||||
fn default_visit_method_definition(
|
||||
&mut self,
|
||||
_class: &'ast Class,
|
||||
method: &'ast MethodDefinition,
|
||||
) {
|
||||
fn default_visit_method_definition(&mut self, method: &'ast MethodDefinition) {
|
||||
self.visit_expression(&method.key);
|
||||
self.visit_function(&method.value.function);
|
||||
}
|
||||
@@ -386,8 +389,10 @@ pub trait Visitor<'ast> {
|
||||
}
|
||||
Pattern::RestElement(pattern) => self.visit_pattern(&pattern.argument),
|
||||
Pattern::AssignmentPattern(pattern) => {
|
||||
self.visit_expression(&pattern.right);
|
||||
self.visit_pattern(&pattern.left);
|
||||
self.visit_rvalue(|visitor| {
|
||||
visitor.visit_expression(&pattern.right);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -460,8 +465,31 @@ pub trait Visitor<'ast> {
|
||||
}
|
||||
}
|
||||
}
|
||||
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!("more expression types")
|
||||
todo!("{:#?}", expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ use swc_core::ecma::ast::{
|
||||
AssignOp, BinaryOp, BlockStmt, BlockStmtOrExpr, Callee, Decl, EsVersion, Expr, ExprOrSpread,
|
||||
Function, Ident, JSXAttr, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXElement,
|
||||
JSXElementChild, JSXElementName, JSXExpr, JSXMemberExpr, JSXObject, Lit, MemberExpr,
|
||||
MemberProp, ModuleItem, OptChainBase, Pat, PatOrExpr, Program, Stmt, UnaryOp, VarDecl,
|
||||
VarDeclKind, VarDeclOrExpr,
|
||||
MemberProp, ModuleItem, OptChainBase, Pat, PatOrExpr, Program, Stmt, UnaryOp, UpdateOp,
|
||||
VarDecl, VarDeclKind, VarDeclOrExpr,
|
||||
};
|
||||
use swc_core::ecma::parser::{Syntax, TsConfig};
|
||||
use swc_core::ecma::transforms::base::resolver;
|
||||
@@ -271,6 +271,14 @@ fn convert_statement(cx: &Context, stmt: &Stmt) -> forget_estree::Statement {
|
||||
range: convert_span(&item.span),
|
||||
}))
|
||||
}
|
||||
Stmt::Labeled(item) => {
|
||||
forget_estree::Statement::LabeledStatement(Box::new(forget_estree::LabeledStatement {
|
||||
label: convert_identifier(cx, &item.label),
|
||||
body: convert_statement(cx, &item.body),
|
||||
loc: None,
|
||||
range: convert_span(&item.span),
|
||||
}))
|
||||
}
|
||||
_ => todo!("translate statement {:#?}", stmt),
|
||||
}
|
||||
}
|
||||
@@ -461,6 +469,15 @@ fn convert_expression(cx: &Context, expr: &Expr) -> forget_estree::Expression {
|
||||
forget_estree::Expression::JSXElement(Box::new(convert_jsx_element(cx, expr)))
|
||||
}
|
||||
Expr::Paren(expr) => convert_expression(cx, &expr.expr),
|
||||
Expr::Update(expr) => {
|
||||
forget_estree::Expression::UpdateExpression(Box::new(forget_estree::UpdateExpression {
|
||||
operator: convert_update_operator(expr.op),
|
||||
argument: convert_expression(cx, &expr.arg),
|
||||
prefix: expr.prefix,
|
||||
loc: None,
|
||||
range: convert_span(&expr.span),
|
||||
}))
|
||||
}
|
||||
_ => todo!("translate expression {:#?}", expr),
|
||||
}
|
||||
}
|
||||
@@ -885,6 +902,13 @@ fn convert_binary_operator(op: BinaryOp) -> Operator {
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_update_operator(op: UpdateOp) -> forget_estree::UpdateOperator {
|
||||
match op {
|
||||
UpdateOp::MinusMinus => forget_estree::UpdateOperator::Decrement,
|
||||
UpdateOp::PlusPlus => forget_estree::UpdateOperator::Increment,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_pattern(cx: &Context, pat: &Pat) -> forget_estree::Pattern {
|
||||
match pat {
|
||||
Pat::Ident(pat) => {
|
||||
|
||||
@@ -12,7 +12,7 @@ repository.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
forget_estree = { workspace = true }
|
||||
forget_estree_swc = { workspace = true }
|
||||
|
||||
@@ -12,5 +12,12 @@ repository.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
forget_diagnostics = { workspace = true }
|
||||
forget_estree = { workspace = true }
|
||||
forget_utils = { workspace = true }
|
||||
forget_utils = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
forget_hermes_parser = { workspace = true }
|
||||
insta = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
@@ -0,0 +1,500 @@
|
||||
use forget_diagnostics::Diagnostic;
|
||||
use forget_estree::{
|
||||
BreakStatement, ContinueStatement, ESTreeNode, Identifier, LabeledStatement, Program,
|
||||
Statement, Visitor,
|
||||
};
|
||||
use forget_utils::PointerAddress;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
pub fn analyze(ast: &Program) -> SemanticAnalysis {
|
||||
let mut analyzer = Analyzer::new();
|
||||
analyzer.visit_program(ast);
|
||||
analyzer.results
|
||||
}
|
||||
|
||||
pub struct SemanticAnalysis {
|
||||
root: ScopeId,
|
||||
|
||||
// Storage of the semantic information
|
||||
scopes: Vec<Scope>,
|
||||
labels: Vec<Label>,
|
||||
declarations: Vec<Declaration>,
|
||||
references: Vec<Reference>,
|
||||
|
||||
// Mapping of AST nodes (by pointer address) to semantic information
|
||||
// Not all nodes will have all types of information available
|
||||
node_scopes: IndexMap<AstNode, ScopeId>,
|
||||
node_labels: IndexMap<AstNode, LabelId>,
|
||||
node_declarations: IndexMap<AstNode, DeclarationId>,
|
||||
node_references: IndexMap<AstNode, ReferenceId>,
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct SemanticAnalysisDebug<'a> {
|
||||
root: ScopeId,
|
||||
|
||||
// Storage of the semantic information
|
||||
scopes: &'a Vec<Scope>,
|
||||
labels: &'a Vec<Label>,
|
||||
declarations: &'a Vec<Declaration>,
|
||||
references: &'a Vec<Reference>,
|
||||
}
|
||||
|
||||
impl SemanticAnalysis {
|
||||
fn new() -> Self {
|
||||
let root_id = ScopeId(0);
|
||||
Self {
|
||||
root: root_id,
|
||||
scopes: vec![Scope {
|
||||
id: root_id,
|
||||
kind: ScopeKind::Global,
|
||||
parent: None,
|
||||
labels: Default::default(),
|
||||
declarations: Default::default(),
|
||||
references: Default::default(),
|
||||
children: Default::default(),
|
||||
}],
|
||||
labels: Default::default(),
|
||||
declarations: Default::default(),
|
||||
references: Default::default(),
|
||||
node_scopes: Default::default(),
|
||||
node_labels: Default::default(),
|
||||
node_declarations: Default::default(),
|
||||
node_references: Default::default(),
|
||||
diagnostics: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug(&self) -> SemanticAnalysisDebug<'_> {
|
||||
SemanticAnalysisDebug {
|
||||
root: self.root,
|
||||
scopes: &self.scopes,
|
||||
labels: &self.labels,
|
||||
declarations: &self.declarations,
|
||||
references: &self.references,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root(&self) -> &Scope {
|
||||
&self.scopes[self.root.0]
|
||||
}
|
||||
|
||||
pub fn scope(&self, id: ScopeId) -> &Scope {
|
||||
&self.scopes[id.0]
|
||||
}
|
||||
|
||||
pub fn label(&self, id: LabelId) -> &Label {
|
||||
&self.labels[id.0]
|
||||
}
|
||||
|
||||
pub fn declaration(&self, id: DeclarationId) -> &Declaration {
|
||||
&self.declarations[id.0]
|
||||
}
|
||||
|
||||
pub fn reference(&self, id: ScopeId) -> &Reference {
|
||||
&self.references[id.0]
|
||||
}
|
||||
|
||||
pub fn node_scope<T: ESTreeNode>(&self, node: &T) -> Option<&Scope> {
|
||||
self.node_scopes
|
||||
.get(&AstNode::from(node))
|
||||
.map(|id| &self.scopes[id.0])
|
||||
}
|
||||
|
||||
pub fn node_label(&self, node: &LabeledStatement) -> Option<&Label> {
|
||||
self.node_labels
|
||||
.get(&AstNode::from(node))
|
||||
.map(|id| &self.labels[id.0])
|
||||
}
|
||||
|
||||
pub fn break_label(&self, node: &BreakStatement) -> Option<&Label> {
|
||||
self.node_labels
|
||||
.get(&AstNode::from(node))
|
||||
.map(|id| &self.labels[id.0])
|
||||
}
|
||||
|
||||
pub fn continue_label(&self, node: &ContinueStatement) -> Option<&Label> {
|
||||
self.node_labels
|
||||
.get(&AstNode::from(node))
|
||||
.map(|id| &self.labels[id.0])
|
||||
}
|
||||
|
||||
pub fn node_declaration(&self, node: &Identifier) -> Option<&Declaration> {
|
||||
self.node_declarations
|
||||
.get(&AstNode::from(node))
|
||||
.map(|id| &self.declarations[id.0])
|
||||
}
|
||||
|
||||
pub fn node_reference(&self, node: &Identifier) -> Option<&Reference> {
|
||||
self.node_references
|
||||
.get(&AstNode::from(node))
|
||||
.map(|id| &self.references[id.0])
|
||||
}
|
||||
|
||||
pub fn lookup_label(&self, scope: ScopeId, name: &str) -> Option<&Label> {
|
||||
let mut current = &self.scopes[scope.0];
|
||||
loop {
|
||||
if let Some(id) = current.labels.get(name) {
|
||||
return Some(&self.labels[id.0]);
|
||||
}
|
||||
if let Some(parent) = current.parent {
|
||||
current = &self.scopes[parent.0];
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup_declaration(&self, scope: ScopeId, name: &str) -> Option<&Declaration> {
|
||||
let mut current = &self.scopes[scope.0];
|
||||
loop {
|
||||
if let Some(id) = current.declarations.get(name) {
|
||||
return Some(&self.declarations[id.0]);
|
||||
}
|
||||
if let Some(parent) = current.parent {
|
||||
current = &self.scopes[parent.0];
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn root_id(&self) -> ScopeId {
|
||||
self.root
|
||||
}
|
||||
|
||||
pub(crate) fn add_scope(&mut self, parent: ScopeId, kind: ScopeKind) -> ScopeId {
|
||||
let id = ScopeId(self.scopes.len());
|
||||
self.scopes.push(Scope {
|
||||
id,
|
||||
kind,
|
||||
parent: Some(parent),
|
||||
labels: Default::default(),
|
||||
declarations: Default::default(),
|
||||
references: Default::default(),
|
||||
children: Default::default(),
|
||||
});
|
||||
self.scopes[parent.0].children.push(id);
|
||||
id
|
||||
}
|
||||
|
||||
pub(crate) fn add_label(&mut self, scope: ScopeId, kind: LabelKind, name: String) -> LabelId {
|
||||
let id = LabelId(self.labels.len());
|
||||
self.labels.push(Label { id, kind, scope });
|
||||
self.scopes[scope.0].labels.insert(name, id);
|
||||
id
|
||||
}
|
||||
|
||||
pub(crate) fn add_declaration(
|
||||
&mut self,
|
||||
scope: ScopeId,
|
||||
name: String,
|
||||
kind: DeclarationKind,
|
||||
) -> DeclarationId {
|
||||
let id = DeclarationId(self.declarations.len());
|
||||
self.declarations.push(Declaration { id, kind, scope });
|
||||
self.scopes[scope.0].declarations.insert(name, id);
|
||||
id
|
||||
}
|
||||
|
||||
pub(crate) fn add_reference(
|
||||
&mut self,
|
||||
scope: ScopeId,
|
||||
kind: ReferenceKind,
|
||||
declaration: DeclarationId,
|
||||
) -> ReferenceId {
|
||||
let id = ReferenceId(self.references.len());
|
||||
self.references.push(Reference {
|
||||
id,
|
||||
kind,
|
||||
declaration,
|
||||
scope,
|
||||
});
|
||||
self.scopes[scope.0].references.push(id);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
|
||||
pub struct ScopeId(usize);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
|
||||
pub struct DeclarationId(usize);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
|
||||
pub struct ReferenceId(usize);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
|
||||
pub struct LabelId(usize);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
|
||||
pub enum ScopeKind {
|
||||
Global,
|
||||
Function,
|
||||
Class,
|
||||
Block,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Scope {
|
||||
pub id: ScopeId,
|
||||
pub kind: ScopeKind,
|
||||
pub parent: Option<ScopeId>,
|
||||
pub labels: IndexMap<String, LabelId>,
|
||||
pub declarations: IndexMap<String, DeclarationId>,
|
||||
pub references: Vec<ReferenceId>,
|
||||
pub children: Vec<ScopeId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
|
||||
pub enum LabelKind {
|
||||
Loop,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Label {
|
||||
pub id: LabelId,
|
||||
pub kind: LabelKind,
|
||||
pub scope: ScopeId,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
|
||||
pub enum DeclarationKind {
|
||||
Const,
|
||||
Var,
|
||||
Let,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Declaration {
|
||||
pub id: DeclarationId,
|
||||
pub kind: DeclarationKind,
|
||||
pub scope: ScopeId,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
|
||||
pub enum ReferenceKind {
|
||||
Read,
|
||||
Write,
|
||||
ReadWrite,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Reference {
|
||||
pub id: ReferenceId,
|
||||
pub kind: ReferenceKind,
|
||||
pub declaration: DeclarationId,
|
||||
pub scope: ScopeId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
struct AstNode(PointerAddress);
|
||||
|
||||
impl AstNode {
|
||||
fn new<T: ESTreeNode>(node: &T) -> Self {
|
||||
Self(PointerAddress::new(node))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<&T> for AstNode
|
||||
where
|
||||
T: ESTreeNode,
|
||||
{
|
||||
fn from(value: &T) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<&mut T> for AstNode
|
||||
where
|
||||
T: ESTreeNode,
|
||||
{
|
||||
fn from(value: &mut T) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
struct Analyzer {
|
||||
results: SemanticAnalysis,
|
||||
current: ScopeId,
|
||||
is_lvalue: bool,
|
||||
}
|
||||
|
||||
impl Analyzer {
|
||||
fn new() -> Self {
|
||||
let results = SemanticAnalysis::new();
|
||||
let current = results.root_id();
|
||||
Self {
|
||||
results,
|
||||
current,
|
||||
is_lvalue: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn enter<F>(&mut self, kind: ScopeKind, mut f: F) -> ScopeId
|
||||
where
|
||||
F: FnMut(&mut Self) -> (),
|
||||
{
|
||||
let scope = self.results.add_scope(self.current, kind);
|
||||
let previous = std::mem::replace(&mut self.current, scope);
|
||||
f(self);
|
||||
let scope = std::mem::replace(&mut self.current, previous);
|
||||
scope
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ast> Visitor<'ast> for Analyzer {
|
||||
fn visit_function_declaration(
|
||||
&mut self,
|
||||
declaration: &'ast forget_estree::FunctionDeclaration,
|
||||
) {
|
||||
let scope = self.enter(ScopeKind::Function, |visitor| {
|
||||
visitor.visit_function(&declaration.function);
|
||||
});
|
||||
self.results
|
||||
.node_scopes
|
||||
.insert(AstNode::from(declaration), scope);
|
||||
}
|
||||
|
||||
fn visit_statement(&mut self, stmt: &'ast forget_estree::Statement) {
|
||||
match stmt {
|
||||
Statement::LabeledStatement(stmt) => {
|
||||
let inner = &stmt.body;
|
||||
let kind = match inner {
|
||||
Statement::ForStatement(_)
|
||||
| Statement::ForInStatement(_)
|
||||
| Statement::ForOfStatement(_)
|
||||
| Statement::WhileStatement(_)
|
||||
| Statement::DoWhileStatement(_) => LabelKind::Loop,
|
||||
_ => LabelKind::Other,
|
||||
};
|
||||
let id = self
|
||||
.results
|
||||
.add_label(self.current, kind, stmt.label.name.clone());
|
||||
self.results
|
||||
.node_labels
|
||||
.insert(AstNode::from(stmt.as_ref()), id);
|
||||
self.visit_statement(&stmt.body);
|
||||
}
|
||||
Statement::BreakStatement(stmt) => {
|
||||
if let Some(label) = &stmt.label {
|
||||
if let Some(label) = self.results.lookup_label(self.current, &label.name) {
|
||||
self.results
|
||||
.node_labels
|
||||
.insert(AstNode::from(stmt.as_ref()), label.id);
|
||||
} else {
|
||||
self.results.diagnostics.push(Diagnostic::invalid_syntax(
|
||||
"Undefined break label",
|
||||
label.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Statement::ContinueStatement(stmt) => {
|
||||
if let Some(label_node) = &stmt.label {
|
||||
if let Some(label) = self.results.lookup_label(self.current, &label_node.name) {
|
||||
if label.kind == LabelKind::Loop {
|
||||
self.results
|
||||
.node_labels
|
||||
.insert(AstNode::from(stmt.as_ref()), label.id);
|
||||
} else {
|
||||
self.results.diagnostics.push(Diagnostic::invalid_syntax(
|
||||
"Invalid continue statement, can only continue to a label associated with a loop statement (for, for..in, for..of, etc)",
|
||||
label_node.range,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
self.results.diagnostics.push(Diagnostic::invalid_syntax(
|
||||
"Undefined continue label",
|
||||
label_node.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Statement::BlockStatement(stmt) => {
|
||||
let scope = self.enter(ScopeKind::Block, |visitor| {
|
||||
for item in &stmt.body {
|
||||
visitor.visit_statement(item);
|
||||
}
|
||||
});
|
||||
self.results
|
||||
.node_scopes
|
||||
.insert(AstNode::from(stmt.as_ref()), scope);
|
||||
}
|
||||
_ => {
|
||||
self.default_visit_statement(stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_identifier(&mut self, identifier: &'ast Identifier) {
|
||||
if self.is_lvalue {
|
||||
let declaration = self
|
||||
.results
|
||||
.lookup_declaration(self.current, &identifier.name);
|
||||
if let Some(declaration) = declaration {
|
||||
let id = self.results.add_reference(
|
||||
self.current,
|
||||
ReferenceKind::ReadWrite,
|
||||
declaration.id,
|
||||
);
|
||||
self.results
|
||||
.node_references
|
||||
.insert(AstNode::from(identifier), id);
|
||||
} else {
|
||||
let id = self.results.add_declaration(
|
||||
self.current,
|
||||
identifier.name.clone(),
|
||||
DeclarationKind::Let,
|
||||
); // TODO: determine the correct kind!
|
||||
self.results
|
||||
.node_declarations
|
||||
.insert(AstNode::from(identifier), id);
|
||||
}
|
||||
} else {
|
||||
let declaration = self
|
||||
.results
|
||||
.lookup_declaration(self.current, &identifier.name);
|
||||
if let Some(declaration) = declaration {
|
||||
let declaration_id = declaration.id;
|
||||
let id =
|
||||
self.results
|
||||
.add_reference(self.current, ReferenceKind::Read, declaration.id);
|
||||
self.results
|
||||
.node_references
|
||||
.insert(AstNode::from(identifier), id);
|
||||
} else {
|
||||
// Oops, undefined variable
|
||||
self.results.diagnostics.push(Diagnostic::invalid_syntax(
|
||||
"Undefined variable",
|
||||
identifier.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_literal(&mut self, _literal: &'ast forget_estree::Literal) {}
|
||||
|
||||
fn visit_lvalue<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Self) -> (),
|
||||
{
|
||||
let prev = self.is_lvalue;
|
||||
self.is_lvalue = true;
|
||||
f(self);
|
||||
self.is_lvalue = prev;
|
||||
}
|
||||
|
||||
fn visit_rvalue<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Self) -> (),
|
||||
{
|
||||
let prev = self.is_lvalue;
|
||||
self.is_lvalue = false;
|
||||
f(self);
|
||||
self.is_lvalue = prev;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,3 @@
|
||||
use std::collections::HashMap;
|
||||
mod analyze;
|
||||
|
||||
use forget_estree::{ESTreeNode, Identifier, Program};
|
||||
use forget_utils::PointerAddress;
|
||||
|
||||
pub fn analyze<'ast>(ast: &'ast Program) -> SemanticAnalysis {
|
||||
todo!("Actually analyze code")
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SemanticAnalysis {
|
||||
scopes: HashMap<AstNode, Scope>,
|
||||
references: HashMap<AstNode, Reference>,
|
||||
}
|
||||
|
||||
impl SemanticAnalysis {
|
||||
pub(crate) fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn scope<T: ESTreeNode>(&self, node: &T) -> Option<&Scope> {
|
||||
self.scopes.get(&node.into())
|
||||
}
|
||||
|
||||
fn reference(&self, identifier: &Identifier) -> Option<&Reference> {
|
||||
self.references.get(&identifier.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Scope {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Reference {}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
struct AstNode(PointerAddress);
|
||||
|
||||
impl AstNode {
|
||||
fn new<T: ESTreeNode>(node: &T) -> Self {
|
||||
Self(PointerAddress::new(node))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<&T> for AstNode
|
||||
where
|
||||
T: ESTreeNode,
|
||||
{
|
||||
fn from(value: &T) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
pub use analyze::analyze;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
use forget_hermes_parser::parse;
|
||||
use forget_semantic_analysis::analyze;
|
||||
use insta::{assert_snapshot, glob};
|
||||
|
||||
#[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();
|
||||
let analysis = analyze(&ast);
|
||||
|
||||
let ast_output = serde_json::to_string_pretty(&ast).unwrap();
|
||||
let analysis_output = format!("{:#?}", analysis.debug());
|
||||
assert_snapshot!(format!(
|
||||
"Input:\n{input}\n\nAST:\n{ast_output}\n\nAnalysis:\n{analysis_output}"
|
||||
));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
function Component(props) {
|
||||
let y = 0;
|
||||
foo: for (let x = 0; x < 10; x++) {
|
||||
if (x == 7) {
|
||||
break foo;
|
||||
}
|
||||
y = x + y;
|
||||
continue foo;
|
||||
}
|
||||
bar: if (props) {
|
||||
break bar;
|
||||
}
|
||||
}
|
||||
+715
@@ -0,0 +1,715 @@
|
||||
---
|
||||
source: crates/forget_semantic_analysis/tests/analysis_test.rs
|
||||
expression: "format!(\"Input:\\n{input}\\n\\nAST:\\n{ast_output}\\n\\nAnalysis:\\n{analysis_output}\")"
|
||||
input_file: crates/forget_semantic_analysis/tests/fixtures/labels.js
|
||||
---
|
||||
Input:
|
||||
function Component(props) {
|
||||
let y = 0;
|
||||
foo: for (let x = 0; x < 10; x++) {
|
||||
if (x == 7) {
|
||||
break foo;
|
||||
}
|
||||
y = x + y;
|
||||
continue foo;
|
||||
}
|
||||
bar: if (props) {
|
||||
break bar;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AST:
|
||||
{
|
||||
"type": "Program",
|
||||
"body": [
|
||||
{
|
||||
"type": "FunctionDeclaration",
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"name": "Component",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"params": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"name": "props",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"type": "BlockStatement",
|
||||
"body": [
|
||||
{
|
||||
"type": "VariableDeclaration",
|
||||
"kind": "let",
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"name": "y",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"init": {
|
||||
"type": "NumericLiteral",
|
||||
"value": 0,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "LabeledStatement",
|
||||
"label": {
|
||||
"type": "Identifier",
|
||||
"name": "foo",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"type": "ForStatement",
|
||||
"init": {
|
||||
"type": "VariableDeclaration",
|
||||
"kind": "let",
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"name": "x",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"init": {
|
||||
"type": "NumericLiteral",
|
||||
"value": 0,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"type": "BinaryExpression",
|
||||
"left": {
|
||||
"type": "Identifier",
|
||||
"name": "x",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"operator": "<",
|
||||
"right": {
|
||||
"type": "NumericLiteral",
|
||||
"value": 4621819117588971520,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"type": "UpdateExpression",
|
||||
"operator": "++",
|
||||
"argument": {
|
||||
"type": "Identifier",
|
||||
"name": "x",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"prefix": false,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"type": "BlockStatement",
|
||||
"body": [
|
||||
{
|
||||
"type": "IfStatement",
|
||||
"test": {
|
||||
"type": "BinaryExpression",
|
||||
"left": {
|
||||
"type": "Identifier",
|
||||
"name": "x",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"operator": "==",
|
||||
"right": {
|
||||
"type": "NumericLiteral",
|
||||
"value": 4619567317775286272,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"consequent": {
|
||||
"type": "BlockStatement",
|
||||
"body": [
|
||||
{
|
||||
"type": "BreakStatement",
|
||||
"label": {
|
||||
"type": "Identifier",
|
||||
"name": "foo",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"alternate": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ExpressionStatement",
|
||||
"expression": {
|
||||
"type": "AssignmentExpression",
|
||||
"operator": "=",
|
||||
"left": {
|
||||
"type": "Identifier",
|
||||
"name": "y",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
"type": "BinaryExpression",
|
||||
"left": {
|
||||
"type": "Identifier",
|
||||
"name": "x",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"operator": "+",
|
||||
"right": {
|
||||
"type": "Identifier",
|
||||
"name": "y",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"directive": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ContinueStatement",
|
||||
"label": {
|
||||
"type": "Identifier",
|
||||
"name": "foo",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "LabeledStatement",
|
||||
"label": {
|
||||
"type": "Identifier",
|
||||
"name": "bar",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"type": "IfStatement",
|
||||
"test": {
|
||||
"type": "Identifier",
|
||||
"name": "props",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"consequent": {
|
||||
"type": "BlockStatement",
|
||||
"body": [
|
||||
{
|
||||
"type": "BreakStatement",
|
||||
"label": {
|
||||
"type": "Identifier",
|
||||
"name": "bar",
|
||||
"typeAnnotation": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"alternate": null,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
},
|
||||
"generator": false,
|
||||
"async": false,
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
},
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"sourceType": "script",
|
||||
"loc": null,
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
}
|
||||
|
||||
Analysis:
|
||||
SemanticAnalysisDebug {
|
||||
root: ScopeId(
|
||||
0,
|
||||
),
|
||||
scopes: [
|
||||
Scope {
|
||||
id: ScopeId(
|
||||
0,
|
||||
),
|
||||
kind: Global,
|
||||
parent: None,
|
||||
labels: {},
|
||||
declarations: {},
|
||||
references: [],
|
||||
children: [
|
||||
ScopeId(
|
||||
1,
|
||||
),
|
||||
],
|
||||
},
|
||||
Scope {
|
||||
id: ScopeId(
|
||||
1,
|
||||
),
|
||||
kind: Function,
|
||||
parent: Some(
|
||||
ScopeId(
|
||||
0,
|
||||
),
|
||||
),
|
||||
labels: {
|
||||
"foo": LabelId(
|
||||
0,
|
||||
),
|
||||
"bar": LabelId(
|
||||
1,
|
||||
),
|
||||
},
|
||||
declarations: {
|
||||
"props": DeclarationId(
|
||||
0,
|
||||
),
|
||||
"y": DeclarationId(
|
||||
1,
|
||||
),
|
||||
"x": DeclarationId(
|
||||
2,
|
||||
),
|
||||
},
|
||||
references: [
|
||||
ReferenceId(
|
||||
0,
|
||||
),
|
||||
ReferenceId(
|
||||
1,
|
||||
),
|
||||
ReferenceId(
|
||||
6,
|
||||
),
|
||||
],
|
||||
children: [
|
||||
ScopeId(
|
||||
2,
|
||||
),
|
||||
ScopeId(
|
||||
4,
|
||||
),
|
||||
],
|
||||
},
|
||||
Scope {
|
||||
id: ScopeId(
|
||||
2,
|
||||
),
|
||||
kind: Block,
|
||||
parent: Some(
|
||||
ScopeId(
|
||||
1,
|
||||
),
|
||||
),
|
||||
labels: {},
|
||||
declarations: {},
|
||||
references: [
|
||||
ReferenceId(
|
||||
2,
|
||||
),
|
||||
ReferenceId(
|
||||
3,
|
||||
),
|
||||
ReferenceId(
|
||||
4,
|
||||
),
|
||||
ReferenceId(
|
||||
5,
|
||||
),
|
||||
],
|
||||
children: [
|
||||
ScopeId(
|
||||
3,
|
||||
),
|
||||
],
|
||||
},
|
||||
Scope {
|
||||
id: ScopeId(
|
||||
3,
|
||||
),
|
||||
kind: Block,
|
||||
parent: Some(
|
||||
ScopeId(
|
||||
2,
|
||||
),
|
||||
),
|
||||
labels: {},
|
||||
declarations: {},
|
||||
references: [],
|
||||
children: [],
|
||||
},
|
||||
Scope {
|
||||
id: ScopeId(
|
||||
4,
|
||||
),
|
||||
kind: Block,
|
||||
parent: Some(
|
||||
ScopeId(
|
||||
1,
|
||||
),
|
||||
),
|
||||
labels: {},
|
||||
declarations: {},
|
||||
references: [],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
labels: [
|
||||
Label {
|
||||
id: LabelId(
|
||||
0,
|
||||
),
|
||||
kind: Loop,
|
||||
scope: ScopeId(
|
||||
1,
|
||||
),
|
||||
},
|
||||
Label {
|
||||
id: LabelId(
|
||||
1,
|
||||
),
|
||||
kind: Other,
|
||||
scope: ScopeId(
|
||||
1,
|
||||
),
|
||||
},
|
||||
],
|
||||
declarations: [
|
||||
Declaration {
|
||||
id: DeclarationId(
|
||||
0,
|
||||
),
|
||||
kind: Let,
|
||||
scope: ScopeId(
|
||||
1,
|
||||
),
|
||||
},
|
||||
Declaration {
|
||||
id: DeclarationId(
|
||||
1,
|
||||
),
|
||||
kind: Let,
|
||||
scope: ScopeId(
|
||||
1,
|
||||
),
|
||||
},
|
||||
Declaration {
|
||||
id: DeclarationId(
|
||||
2,
|
||||
),
|
||||
kind: Let,
|
||||
scope: ScopeId(
|
||||
1,
|
||||
),
|
||||
},
|
||||
],
|
||||
references: [
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
0,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
2,
|
||||
),
|
||||
scope: ScopeId(
|
||||
1,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
1,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
2,
|
||||
),
|
||||
scope: ScopeId(
|
||||
1,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
2,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
2,
|
||||
),
|
||||
scope: ScopeId(
|
||||
2,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
3,
|
||||
),
|
||||
kind: ReadWrite,
|
||||
declaration: DeclarationId(
|
||||
1,
|
||||
),
|
||||
scope: ScopeId(
|
||||
2,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
4,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
2,
|
||||
),
|
||||
scope: ScopeId(
|
||||
2,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
5,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
1,
|
||||
),
|
||||
scope: ScopeId(
|
||||
2,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
6,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
0,
|
||||
),
|
||||
scope: ScopeId(
|
||||
1,
|
||||
),
|
||||
},
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user