mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[rust][sema] For statements and JSX
Adds semantic analysis support for normal `for` statements and for JSX. The main catch with JSX is that there are a bunch of identifiers that we have to ignore since they aren't variable references: jsx attribute names, namespace names, jsx member expression properties, and closing elements.
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
// Manual extensions to generated types
|
||||
use crate::{Pattern, SourceRange, SourceType};
|
||||
use crate::{
|
||||
JSXElementName, JSXMemberExpression, JSXMemberExpressionOrIdentifier, Pattern, SourceRange,
|
||||
SourceType,
|
||||
};
|
||||
|
||||
/// Sentinel trait to distinguish AST *node* types
|
||||
pub trait ESTreeNode {}
|
||||
@@ -21,3 +24,22 @@ impl Pattern {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use forget_diagnostics::Diagnostic;
|
||||
use forget_estree::{
|
||||
AssignmentOperator, AssignmentPropertyOrRestElement, AssignmentTarget, Expression,
|
||||
ExpressionOrSuper, ForInInit, Function, FunctionBody, Identifier, Pattern, Program,
|
||||
SourceRange, SourceType, Statement, VariableDeclarationKind, Visitor2,
|
||||
ExpressionOrSuper, ForInInit, ForInit, Function, FunctionBody, Identifier, JSXElementName,
|
||||
Pattern, Program, SourceRange, SourceType, Statement, VariableDeclarationKind, Visitor2,
|
||||
};
|
||||
|
||||
use crate::{AstNode, DeclarationKind, LabelKind, ReferenceKind, ScopeId, ScopeKind, ScopeManager};
|
||||
@@ -190,7 +190,7 @@ impl Analyzer {
|
||||
left: &ForInInit,
|
||||
right: &Expression,
|
||||
body: &Statement,
|
||||
range: Option<SourceRange>,
|
||||
_range: Option<SourceRange>,
|
||||
) {
|
||||
// Record an anonymous label for the statement to resolve unlabeled break/continue
|
||||
let label = self
|
||||
@@ -455,17 +455,31 @@ impl Visitor2 for Analyzer {
|
||||
);
|
||||
}
|
||||
|
||||
fn visit_identifier(&mut self, ast: &forget_estree::Identifier) {
|
||||
Analyzer::visit_reference_identifier(
|
||||
self,
|
||||
&ast.name,
|
||||
AstNode::from(ast),
|
||||
ReferenceKind::Read,
|
||||
ast.range,
|
||||
);
|
||||
fn visit_for_statement(&mut self, ast: &forget_estree::ForStatement) {
|
||||
let mut for_scope: Option<ScopeId> = None;
|
||||
if let Some(init) = &ast.init {
|
||||
if let ForInit::VariableDeclaration(init) = init {
|
||||
if init.kind != VariableDeclarationKind::Var {
|
||||
for_scope = Some(self.enter_scope(ScopeKind::For));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(init) = &ast.init {
|
||||
self.visit_for_init(init);
|
||||
}
|
||||
if let Some(test) = &ast.test {
|
||||
self.visit_expression(test);
|
||||
}
|
||||
if let Some(update) = &ast.update {
|
||||
self.visit_expression(update);
|
||||
}
|
||||
self.visit_statement(&ast.body);
|
||||
if let Some(for_scope) = for_scope {
|
||||
self.close_scope(for_scope);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_jsxidentifier(&mut self, ast: &forget_estree::JSXIdentifier) {
|
||||
fn visit_identifier(&mut self, ast: &forget_estree::Identifier) {
|
||||
Analyzer::visit_reference_identifier(
|
||||
self,
|
||||
&ast.name,
|
||||
@@ -561,4 +575,82 @@ impl Visitor2 for Analyzer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_jsxattribute(&mut self, ast: &forget_estree::JSXAttribute) {
|
||||
// NOTE: skip visiting the attribute name, attributes are like non-computed
|
||||
// object properties where the identifier is not a variable reference
|
||||
if let Some(value) = &ast.value {
|
||||
self.visit_jsxattribute_value(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_jsxclosing_element(&mut self, _ast: &forget_estree::JSXClosingElement) {
|
||||
// no-op, should not be counted as a reference
|
||||
}
|
||||
|
||||
fn visit_jsxidentifier(&mut self, ast: &forget_estree::JSXIdentifier) {
|
||||
Analyzer::visit_reference_identifier(
|
||||
self,
|
||||
&ast.name,
|
||||
AstNode::from(ast),
|
||||
ReferenceKind::Read,
|
||||
ast.range,
|
||||
);
|
||||
}
|
||||
|
||||
fn visit_jsxfragment(&mut self, ast: &forget_estree::JSXFragment) {
|
||||
// TODO: record the pragmas
|
||||
for child in &ast.children {
|
||||
self.visit_jsxchild_item(child);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_jsxmember_expression(&mut self, ast: &forget_estree::JSXMemberExpression) {
|
||||
// NOTE: ignore the 'property' since JSX doesn't support computed properties
|
||||
self.visit_jsxmember_expression_or_identifier(&ast.object);
|
||||
}
|
||||
|
||||
fn visit_jsxnamespaced_name(&mut self, ast: &forget_estree::JSXNamespacedName) {
|
||||
// NOTE: ignore the 'name' since it doesn't refer to a variable
|
||||
self.visit_jsxidentifier(&ast.namespace);
|
||||
}
|
||||
|
||||
fn visit_jsxopening_element(&mut self, ast: &forget_estree::JSXOpeningElement) {
|
||||
// TODO: record jsx pragma if root_name is not an FBT name
|
||||
let root_name = ast.name.root_name();
|
||||
|
||||
match &ast.name {
|
||||
JSXElementName::JSXIdentifier(name) => {
|
||||
// lowercase names are builtins, only visit if this is a user-defined
|
||||
// component
|
||||
if let Some(first) = root_name.chars().next() {
|
||||
if first == first.to_ascii_uppercase() {
|
||||
self.visit_jsxidentifier(name);
|
||||
}
|
||||
} else {
|
||||
// TODO: this likely indicates a parse error, since a valid parse
|
||||
// should never result in an empty JSXIdentifier node. but just in
|
||||
// case we report this rather than silently fail
|
||||
self.manager.diagnostics.push(Diagnostic::invalid_syntax(
|
||||
"Expected JSXOpenintElement.name to be non-empty",
|
||||
name.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
JSXElementName::JSXMemberExpression(name) => {
|
||||
if root_name != "this" {
|
||||
self.visit_jsxmember_expression(name);
|
||||
}
|
||||
}
|
||||
JSXElementName::JSXNamespacedName(name) => {
|
||||
if root_name != "this" {
|
||||
self.visit_jsxnamespaced_name(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for attribute in &ast.attributes {
|
||||
self.visit_jsxattribute_or_spread(attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@ impl ScopeManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn diagnostics(&mut self) -> Vec<Diagnostic> {
|
||||
std::mem::take(&mut self.diagnostics)
|
||||
}
|
||||
|
||||
pub fn root(&self) -> &Scope {
|
||||
&self.scopes[self.root.0]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use forget_hermes_parser::parse;
|
||||
use forget_semantic_analysis::analyze;
|
||||
use insta::{assert_snapshot, glob};
|
||||
@@ -8,12 +10,17 @@ fn fixtures() {
|
||||
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 mut analysis = analyze(&ast);
|
||||
|
||||
let ast_output = serde_json::to_string_pretty(&ast).unwrap();
|
||||
let analysis_output = format!("{:#?}", analysis.debug());
|
||||
let mut output = String::new();
|
||||
writeln!(&mut output, "{:#?}", analysis.debug()).unwrap();
|
||||
let diagnostics = analysis.diagnostics();
|
||||
for diagnostic in diagnostics {
|
||||
writeln!(&mut output, "{:#?}", diagnostic).unwrap();
|
||||
}
|
||||
assert_snapshot!(format!(
|
||||
"Input:\n{input}\n\nAST:\n{ast_output}\n\nAnalysis:\n{analysis_output}"
|
||||
"Input:\n{input}\n\nAST:\n{ast_output}\n\nAnalysis:\n{output}"
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import Foo from "foo";
|
||||
import * as Bar from "bar";
|
||||
import { Baz } from "baz";
|
||||
|
||||
function Component(props) {
|
||||
let g = global;
|
||||
let y = new Array(props.count);
|
||||
let s = String("hello");
|
||||
let b = Boolean(true);
|
||||
let n = Number(0);
|
||||
let x = Math.min(props.x, props.y);
|
||||
setTimeout(() => {}, 0);
|
||||
setInterval(() => {}, 0);
|
||||
Foo;
|
||||
Bar;
|
||||
Baz;
|
||||
}
|
||||
+1219
File diff suppressed because it is too large
Load Diff
+83
-69
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: crates/forget_semantic_analysis/tests/analysis_test.rs
|
||||
expression: "format!(\"Input:\\n{input}\\n\\nAST:\\n{ast_output}\\n\\nAnalysis:\\n{analysis_output}\")"
|
||||
expression: "format!(\"Input:\\n{input}\\n\\nAST:\\n{ast_output}\\n\\nAnalysis:\\n{output}\")"
|
||||
input_file: crates/forget_semantic_analysis/tests/fixtures/labels.js
|
||||
---
|
||||
Input:
|
||||
@@ -517,43 +517,8 @@ Scope {
|
||||
2,
|
||||
),
|
||||
},
|
||||
"x": Declaration {
|
||||
id: DeclarationId(
|
||||
3,
|
||||
),
|
||||
kind: Let,
|
||||
scope: ScopeId(
|
||||
2,
|
||||
),
|
||||
},
|
||||
},
|
||||
references: [
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
0,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
3,
|
||||
),
|
||||
declaration (name): "x",
|
||||
scope: ScopeId(
|
||||
2,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
1,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
3,
|
||||
),
|
||||
declaration (name): "x",
|
||||
scope: ScopeId(
|
||||
2,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
6,
|
||||
@@ -573,13 +538,23 @@ Scope {
|
||||
id: ScopeId(
|
||||
3,
|
||||
),
|
||||
kind: Block,
|
||||
kind: For,
|
||||
labels: {},
|
||||
declarations: {},
|
||||
declarations: {
|
||||
"x": Declaration {
|
||||
id: DeclarationId(
|
||||
3,
|
||||
),
|
||||
kind: Let,
|
||||
scope: ScopeId(
|
||||
3,
|
||||
),
|
||||
},
|
||||
},
|
||||
references: [
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
2,
|
||||
0,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
@@ -592,20 +567,7 @@ Scope {
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
3,
|
||||
),
|
||||
kind: ReadWrite,
|
||||
declaration: DeclarationId(
|
||||
2,
|
||||
),
|
||||
declaration (name): "y",
|
||||
scope: ScopeId(
|
||||
3,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
4,
|
||||
1,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
@@ -616,19 +578,6 @@ Scope {
|
||||
3,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
5,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
2,
|
||||
),
|
||||
declaration (name): "y",
|
||||
scope: ScopeId(
|
||||
3,
|
||||
),
|
||||
},
|
||||
],
|
||||
children: [
|
||||
Scope {
|
||||
@@ -638,14 +587,78 @@ Scope {
|
||||
kind: Block,
|
||||
labels: {},
|
||||
declarations: {},
|
||||
references: [],
|
||||
children: [],
|
||||
references: [
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
2,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
3,
|
||||
),
|
||||
declaration (name): "x",
|
||||
scope: ScopeId(
|
||||
4,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
3,
|
||||
),
|
||||
kind: ReadWrite,
|
||||
declaration: DeclarationId(
|
||||
2,
|
||||
),
|
||||
declaration (name): "y",
|
||||
scope: ScopeId(
|
||||
4,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
4,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
3,
|
||||
),
|
||||
declaration (name): "x",
|
||||
scope: ScopeId(
|
||||
4,
|
||||
),
|
||||
},
|
||||
Reference {
|
||||
id: ReferenceId(
|
||||
5,
|
||||
),
|
||||
kind: Read,
|
||||
declaration: DeclarationId(
|
||||
2,
|
||||
),
|
||||
declaration (name): "y",
|
||||
scope: ScopeId(
|
||||
4,
|
||||
),
|
||||
},
|
||||
],
|
||||
children: [
|
||||
Scope {
|
||||
id: ScopeId(
|
||||
5,
|
||||
),
|
||||
kind: Block,
|
||||
labels: {},
|
||||
declarations: {},
|
||||
references: [],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
Scope {
|
||||
id: ScopeId(
|
||||
5,
|
||||
6,
|
||||
),
|
||||
kind: Block,
|
||||
labels: {},
|
||||
@@ -659,3 +672,4 @@ Scope {
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user