scope resolution improvements
MODIFY
crates/rome_js_analyze/src/semantic_analyzers/js/no_unused_variables.rs
crates/rome_js_analyze/src/semantic_analyzers/js/no_unused_variables.rs
@@ -128,7 +128,6 @@ impl Rule for NoUnusedVariables {
}
let all_references = binding.all_references(model);
-
if all_references.count() == 0 {
Some(())
} else {
@@ -180,6 +179,9 @@ impl Rule for NoUnusedVariables {
let symbol_type = match binding.syntax().parent().unwrap().kind() {
JsSyntaxKind::JS_FORMAL_PARAMETER => "parameter",
JsSyntaxKind::JS_FUNCTION_DECLARATION => "function",
+ JsSyntaxKind::JS_CLASS_DECLARATION => "class",
+ JsSyntaxKind::TS_INTERFACE_DECLARATION => "interface",
+ JsSyntaxKind::TS_TYPE_ALIAS_DECLARATION => "type alias",
_ => "variable",
};

MODIFY
crates/rome_js_semantic/src/events.rs
crates/rome_js_semantic/src/events.rs
@@ -256,10 +256,20 @@ impl SemanticEventExtractor {
| JS_FUNCTION_EXPORT_DEFAULT_DECLARATION
| JS_FUNCTION_EXPRESSION
| JS_ARROW_FUNCTION_EXPRESSION
+ | JS_CLASS_DECLARATION
+ | JS_CLASS_EXPORT_DEFAULT_DECLARATION
+ | JS_CLASS_EXPRESSION
| JS_CONSTRUCTOR_CLASS_MEMBER
+ | JS_METHOD_CLASS_MEMBER
| JS_GETTER_CLASS_MEMBER
| JS_SETTER_CLASS_MEMBER
- | JS_FUNCTION_BODY => {
+ | JS_METHOD_OBJECT_MEMBER
+ | JS_GETTER_OBJECT_MEMBER
+ | JS_SETTER_OBJECT_MEMBER
+ | JS_FUNCTION_BODY
+ | TS_INTERFACE_DECLARATION
+ | TS_ENUM_DECLARATION
+ | TS_TYPE_ALIAS_DECLARATION => {
self.push_scope(
node.text_range(),
ScopeHoisting::DontHoistDeclarationsToParent,
@@ -267,7 +277,7 @@ impl SemanticEventExtractor {
}
JS_BLOCK_STATEMENT | JS_FOR_STATEMENT | JS_FOR_OF_STATEMENT | JS_FOR_IN_STATEMENT
- | JS_CATCH_CLAUSE => {
+ | JS_SWITCH_STATEMENT | JS_CATCH_CLAUSE => {
self.push_scope(node.text_range(), ScopeHoisting::HoistDeclarationsToParent);
}
@@ -341,23 +351,28 @@ impl SemanticEventExtractor {
self.export_function_expression(node, &parent);
}
JS_CLASS_DECLARATION | JS_CLASS_EXPORT_DEFAULT_DECLARATION => {
- self.push_binding_into_scope(None, &name_token);
+ let hoisted_scope_id = self.scope_index_to_hoist_declarations(1);
+ self.push_binding_into_scope(hoisted_scope_id, &name_token);
self.export_declaration(node, &parent);
}
JS_CLASS_EXPRESSION => {
- self.push_binding_into_scope(None, &name_token);
+ let hoisted_scope_id = self.scope_index_to_hoist_declarations(1);
+ self.push_binding_into_scope(hoisted_scope_id, &name_token);
self.export_class_expression(node, &parent);
}
TS_TYPE_ALIAS_DECLARATION => {
- self.push_binding_into_scope(None, &name_token);
+ let hoisted_scope_id = self.scope_index_to_hoist_declarations(1);
+ self.push_binding_into_scope(hoisted_scope_id, &name_token);
self.export_declaration(node, &parent);
}
TS_ENUM_DECLARATION => {
- self.push_binding_into_scope(None, &name_token);
+ let hoisted_scope_id = self.scope_index_to_hoist_declarations(1);
+ self.push_binding_into_scope(hoisted_scope_id, &name_token);
self.export_declaration(node, &parent);
}
TS_INTERFACE_DECLARATION => {
- self.push_binding_into_scope(None, &name_token);
+ let hoisted_scope_id = self.scope_index_to_hoist_declarations(1);
+ self.push_binding_into_scope(hoisted_scope_id, &name_token);
self.export_declaration(node, &parent);
}
_ => {
@@ -456,15 +471,26 @@ impl SemanticEventExtractor {
| JS_FUNCTION_EXPORT_DEFAULT_DECLARATION
| JS_FUNCTION_EXPRESSION
| JS_ARROW_FUNCTION_EXPRESSION
+ | JS_CLASS_DECLARATION
+ | JS_CLASS_EXPORT_DEFAULT_DECLARATION
+ | JS_CLASS_EXPRESSION
| JS_CONSTRUCTOR_CLASS_MEMBER
+ | JS_METHOD_CLASS_MEMBER
| JS_GETTER_CLASS_MEMBER
| JS_SETTER_CLASS_MEMBER
+ | JS_METHOD_OBJECT_MEMBER
+ | JS_GETTER_OBJECT_MEMBER
+ | JS_SETTER_OBJECT_MEMBER
+ | JS_FUNCTION_BODY
+ | TS_INTERFACE_DECLARATION
+ | TS_ENUM_DECLARATION
+ | TS_TYPE_ALIAS_DECLARATION
| JS_BLOCK_STATEMENT
| JS_FOR_STATEMENT
| JS_FOR_OF_STATEMENT
| JS_FOR_IN_STATEMENT
- | JS_CATCH_CLAUSE
- | JS_FUNCTION_BODY => {
+ | JS_SWITCH_STATEMENT
+ | JS_CATCH_CLAUSE => {
self.pop_scope(node.text_range());
}
_ => {}
@@ -606,7 +632,7 @@ impl SemanticEventExtractor {
self.scopes[0].hoisting,
ScopeHoisting::DontHoistDeclarationsToParent
));
- debug_assert!(!self.scopes.is_empty());
+ debug_assert!(self.scopes.len() > skip);
let scope_id_hoisted_to = self
.scopes
@@ -731,8 +757,8 @@ impl SemanticEventExtractor {
use JsSyntaxKind::*;
debug_assert!(matches!(class_expression.kind(), JS_CLASS_EXPRESSION));
- // export can only exist in the global scope
- if self.scopes.len() > 1 {
+ // scope[0] = global, scope[1] = the class expression itself
+ if self.scopes.len() != 2 {
return;
}
@@ -759,8 +785,8 @@ impl SemanticEventExtractor {
| TS_INTERFACE_DECLARATION
));
- // export can only exist in the global scope
- if self.scopes.len() > 1 {
+ // scope[0] = global, scope[1] = what is being exported
+ if self.scopes.len() != 2 {
return;
}
@@ -768,6 +794,7 @@ impl SemanticEventExtractor {
declaration.parent().map(|p| p.kind()),
Some(JS_EXPORT | JS_EXPORT_DEFAULT_DECLARATION_CLAUSE)
);
+
if is_exported {
self.stash.push_back(SemanticEvent::Exported {
range: binding.text_range(),

MODIFY
crates/rome_js_semantic/src/semantic_model.rs
crates/rome_js_semantic/src/semantic_model.rs
@@ -69,6 +69,7 @@ impl<T: HasDeclarationAstNode> IsExportedCanBeQueried for T {
#[derive(Debug)]
struct SemanticModelScopeData {
+ range: TextRange,
parent: Option<usize>,
children: Vec<usize>,
bindings: Vec<TextRange>,
@@ -235,6 +236,14 @@ impl Scope {
pub fn is_ancestor_of(&self, other: &Scope) -> bool {
other.ancestors().any(|s| s == *self)
}
+
+ pub fn range(&self) -> &TextRange {
+ &self.data.scopes[self.id].range
+ }
+
+ pub fn syntax(&self) -> &JsSyntaxNode {
+ &self.data.node_by_range[self.range()]
+ }
}
/// Provides all information regarding to a specific binding.
@@ -429,6 +438,14 @@ impl SemanticModel {
}
}
+ /// Iterate all scopes
+ pub fn scopes(&self) -> impl Iterator<Item = Scope> + '_ {
+ self.data.scopes.iter().enumerate().map(|(id, _)| Scope {
+ data: self.data.clone(),
+ id,
+ })
+ }
+
/// Returns the global scope of the model
pub fn global_scope(&self) -> Scope {
Scope {
@@ -701,6 +718,7 @@ impl SemanticModelBuilder {
debug_assert!(scope_id == self.scopes.len());
self.scopes.push(SemanticModelScopeData {
+ range,
parent: parent_scope_id,
children: vec![],
bindings: vec![],

MODIFY
crates/rome_js_semantic/src/tests/assertions.rs
crates/rome_js_semantic/src/tests/assertions.rs
@@ -467,7 +467,7 @@ impl SemanticAssertions {
if let Some(declaration_at_range) = declaration_at_range {
unused_match = Some(format!(
- "{} == {}",
+ "{} != {}",
&code[declaration_at_range], &code[decl.range]
));
code[declaration_at_range] == code[decl.range]

MODIFY
crates/rome_js_semantic/src/tests/references.rs
crates/rome_js_semantic/src/tests/references.rs
@@ -3,9 +3,11 @@ use crate::assert_semantics;
// Reads
assert_semantics! {
- ok_reference_read_global, "let a/*#A*/ = 1; let b = a/*READ A*/ + 1;",
+ ok_reference_read_global,
+ "let a/*#A*/ = 1; let b = a/*READ A*/ + 1;",
- ok_reference_read_inner_scope, r#"function f(a/*#A1*/) {
+ ok_reference_read_inner_scope,
+ r#"function f(a/*#A1*/) {
let b = a/*READ A1*/ + 1;
console.log(b);
if (true) {
@@ -17,6 +19,17 @@ assert_semantics! {
console.log(b);
}
f(1);"#,
+
+ ok_reference_switch,
+ "let b = 1;
+ let a/*#A1*/ = 1;
+ switch (b) {
+ case 1: let a/*#A2*/ = 2; console.log(1, a/*READ A2*/);
+ case 2: let c/*#C*/ = 2; console.log(2, a/*READ A2*/, c/*READ C*/);
+ case 3: { let d/*#D*/ = 2; console.log(3, a/*READ A2*/, c/*READ C*/, d/*READ D*/); }
+ case 4: console.log(4, a/*READ A2*/, c/*READ C*/, d/*?*/);
+ }
+ console.log(5, a/*READ A1*/);",
}
// Read Hoisting
@@ -84,6 +97,13 @@ function f() {
}
}
f()"#,
+
+ ok_hoisting_inside_switch,
+ "var a/*#A1*/ = 1;
+switch (a) {
+ case 1: var a/*#A2*/ = 2;
+};
+console.log(a/*READ A2*/);",
}
// Write
@@ -117,15 +137,40 @@ f();",
// Functions
assert_semantics! {
- ok_read_function, r#"function f/*#F*/() {} console.log(f/*READ F*/);"#,
- ok_write_function, r#"function f/*#F*/() {} f/*WRITE F*/ = null;"#,
- ok_scope_function_expression_read, "var f/*#F1*/ = function f/*#F2*/() {console.log(f/*READ F2*/);}; f/*READ F1*/();",
- ok_scope_function_expression_read1 ,"var f/*#F1*/ = function () {console.log(f/*READ F1*/);}",
- ok_scope_function_expression_read2, "let f/*#F1*/ = 1; let g = function f/*#F2*/() {console.log(2, f/*READ F2*/);}; console.log(f/*READ F1*/);",
+ ok_read_function,
+ r#"function f/*#F*/() {} console.log(f/*READ F*/);"#,
+ ok_write_function,
+ r#"function f/*#F*/() {} f/*WRITE F*/ = null;"#,
ok_read_self_invoking_function,
r#"(function f/*#F*/(){console.log(1)})/*READ F*/()"#,
ok_read_self_invoking_function2,
r#"(1,2,3,function f/*#F*/(){console.log(1)})/*READ F*/()"#,
+
+ ok_scope_function_expression_read,
+ "var f/*#F1*/ = function f/*#F2*/() {console.log(f/*READ F2*/);}; f/*READ F1*/();",
+ ok_scope_function_expression_read1 ,
+ "var f/*#F1*/ = function () {console.log(f/*READ F1*/);}",
+ ok_scope_function_expression_read2,
+ "let f/*#F1*/ = 1; let g = function f/*#F2*/() {console.log(2, f/*READ F2*/);}; console.log(f/*READ F1*/);",
+ ok_function_parameter,
+ "function t({ a/*#A*/ = 0, b/*#B*/ = a/*READ A*/ }, c = a/*READ A*/) { console.log(a/*READ A*/, b/*READ B*/); }",
+ ok_function_parameter_array,
+ "let b/*#B*/ = 5;
+let c/*#C*/ = 6;
+let d/*#D*/ = 7;
+function f({a/*#A*/} = {a: [b/*READ B*/,c/*READ C*/,d/*READ D*/]}) {
+ console.log(a/*READ A*/, b/*READ B*/);
+}
+f()",
+ ok_function_parameter_array_with_name_conflict,
+ "let b/*#B1*/ = 5;
+let c/*#C*/ = 6;
+let d/*#D*/ = 7;
+function f({a/*#A*/} = {a: [b/*READ B2*/,c/*READ C*/,d/*READ D*/]}, b/*#B2*/) {
+ var b/*#B3*/;
+ console.log(a/*READ A*/, b/*READ B3*/);
+}
+f()",
}
// Imports
@@ -138,7 +183,15 @@ assert_semantics! {
ok_function_expression_read,"let f/*#F*/ = function g/*#G*/(){}; g/*?*/();",
}
+// Exports
assert_semantics! {
ok_export_hoisted_variable,
"var a/*#A1*/ = 2; export {a/*READ A2*/}; var a/*#A2*/ = 1;",
}
+
+// Classes
+
+assert_semantics! {
+ ok_class_reference,
+ "class A/*#A*/ {} new A/*READ A*/();",
+}

MODIFY
xtask/coverage/src/symbols/msts.rs
xtask/coverage/src/symbols/msts.rs
@@ -79,7 +79,11 @@ impl TestCase for SymbolsMicrosoftTestCase {
// We do the same below because TS classifies some string literals as symbols and we also
// filter them below.
match x {
- SemanticEvent::DeclarationFound { .. } | SemanticEvent::Read { .. } => {
+ SemanticEvent::DeclarationFound { .. }
+ | SemanticEvent::Read { .. }
+ | SemanticEvent::HoistedRead { .. }
+ | SemanticEvent::Write { .. }
+ | SemanticEvent::HoistedWrite { .. } => {
let name = x.str(&code);
!name.contains('\"') && !name.contains('\'')
}
@@ -134,7 +138,7 @@ impl TestCase for SymbolsMicrosoftTestCase {
}
} else {
for (expected, actual) in expected.symbols.iter().zip(actual) {
- let are_names_eq = expected.name == actual.str(&code);
+ let are_names_eq = expected.name == actual.str(&code).trim();
if !are_names_eq {
return TestRunOutcome::IncorrectlyErrored {
files: t,

scope resolution improvements (#3084)