From cf4189cbf1581e876a476fd3606ca4b15794faed Mon Sep 17 00:00:00 2001 From: Daniel Frederico Lins Leite Date: Mon, 20 Jun 2022 18:05:05 +0100 Subject: [PATCH] feature(rome_js_semantic): extraction of reference (read) events (#2725) * extraction of read events --- crates/rome_js_semantic/src/events.rs | 122 +++++++++++++++--- .../rome_js_semantic/src/tests/assertions.rs | 77 ++++++++++- crates/rome_js_semantic/src/tests/mod.rs | 1 + .../rome_js_semantic/src/tests/references.rs | 17 +++ crates/rome_rowan/src/cursor/token.rs | 8 ++ crates/rome_rowan/src/syntax/token.rs | 4 + crates/rome_rowan/src/syntax_token_text.rs | 11 ++ xtask/coverage/src/symbols/msts.rs | 11 +- 8 files changed, 226 insertions(+), 25 deletions(-) create mode 100644 crates/rome_js_semantic/src/tests/references.rs diff --git a/crates/rome_js_semantic/src/events.rs b/crates/rome_js_semantic/src/events.rs index a34b51d0731..e7027c1f25f 100644 --- a/crates/rome_js_semantic/src/events.rs +++ b/crates/rome_js_semantic/src/events.rs @@ -1,16 +1,19 @@ //! Events emitted by the [SemanticEventExtractor] which are then constructed into the Semantic Model -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; -use rome_js_syntax::{JsLanguage, JsSyntaxNode, TextRange, TextSize}; -use rome_rowan::syntax::Preorder; +use rome_js_syntax::{ + JsIdentifierBinding, JsLanguage, JsReferenceIdentifier, JsSyntaxNode, JsSyntaxToken, TextRange, + TextSize, +}; +use rome_rowan::{syntax::Preorder, SyntaxNodeCast, SyntaxTokenText}; /// Events emitted by the [SemanticEventExtractor]. These events are later /// made into the Semantic Model. #[derive(Debug)] pub enum SemanticEvent { /// Signifies that a new symbol declaration was found. - /// Currently is generated for: + /// Generated for: /// - Variable Declarations /// - Import bindings /// - Functions parameters @@ -19,14 +22,22 @@ pub enum SemanticEvent { scope_started_at: TextSize, }, + /// Signifies that a symbol value is being read. + /// Generated for: + /// - All reference identifiers + Read { + range: TextRange, + declaration_at: Option, + }, + /// Signifies that a new scope was started - /// Currently generated for: + /// Generated for: /// - Blocks /// - Function body ScopeStarted { range: TextRange }, /// Signifies that a new scope was ended - /// Currently generated for: + /// Generated for: /// - Blocks /// - Function body ScopeEnded { @@ -41,6 +52,7 @@ impl SemanticEvent { SemanticEvent::DeclarationFound { range, .. } => range, SemanticEvent::ScopeStarted { range } => range, SemanticEvent::ScopeEnded { range, .. } => range, + SemanticEvent::Read { range, .. } => range, } } @@ -87,10 +99,17 @@ impl SemanticEvent { pub struct SemanticEventExtractor { stash: VecDeque, scopes: Vec, + declared_names: HashMap, +} + +struct ScopeDeclaration { + name: SyntaxTokenText, } struct Scope { started_at: TextSize, + declared: Vec, + shadowed: Vec<(SyntaxTokenText, TextRange)>, } impl SemanticEventExtractor { @@ -98,6 +117,7 @@ impl SemanticEventExtractor { Self { stash: VecDeque::new(), scopes: vec![], + declared_names: HashMap::new(), } } @@ -105,13 +125,31 @@ impl SemanticEventExtractor { /// of which ```SyntaxNode``` generates which events. pub fn enter(&mut self, node: &JsSyntaxNode) { use rome_js_syntax::JsSyntaxKind::*; - use SemanticEvent::*; match node.kind() { - JS_IDENTIFIER_BINDING => self.stash.push_back(DeclarationFound { - range: node.text_range(), - scope_started_at: self.current_scope_start(), - }), + JS_IDENTIFIER_BINDING => { + if let Some(name_token) = node + .clone() + .cast::() + .and_then(|id| id.name_token().ok()) + { + self.declare_name(&name_token); + } + } + JS_REFERENCE_IDENTIFIER => { + if let Some(name_token) = node + .clone() + .cast::() + .and_then(|reference| reference.value_token().ok()) + { + self.stash.push_back(SemanticEvent::Read { + range: node.text_range(), + declaration_at: self + .get_declaration_range_by_trimmed_text(&name_token) + .cloned(), + }) + } + } JS_MODULE | JS_SCRIPT => self.push_scope(node.text_range()), JS_FUNCTION_DECLARATION @@ -155,15 +193,6 @@ impl SemanticEventExtractor { } } - fn current_scope_start(&self) -> TextSize { - let started_at = self.scopes.last().map(|x| x.started_at); - - // We should always have, at least, the global scope - debug_assert!(started_at.is_some()); - - started_at.unwrap_or_else(|| TextSize::of("")) - } - /// Return any previous extracted [SemanticEvent]. pub fn pop(&mut self) -> Option { self.stash.pop_front() @@ -173,6 +202,8 @@ impl SemanticEventExtractor { self.stash.push_back(SemanticEvent::ScopeStarted { range }); self.scopes.push(Scope { started_at: range.start(), + declared: vec![], + shadowed: vec![], }); } @@ -182,8 +213,59 @@ impl SemanticEventExtractor { range, started_at: scope.started_at, }); + + // remove all declarations + for decl in scope.declared { + self.declared_names.remove(&decl.name); + } + + // return all shadowed names + for (name, range) in scope.shadowed { + self.declared_names.insert(name, range); + } + } + } + + fn current_scope_mut(&mut self) -> &mut Scope { + // We should at least have the global scope + debug_assert!(!self.scopes.is_empty()); + + match self.scopes.last_mut() { + None => unreachable!(), + Some(scope) => scope, } } + + fn declare_name(&mut self, name_token: &JsSyntaxToken) { + let name = name_token.token_text_trimmed(); + + let declaration_range = name_token.text_range(); + + // insert this name into the list of available names + // and save shadowed names to be used later + let shadowed = self + .declared_names + .insert(name.clone(), declaration_range) + .map(|shadowed_range| (name.clone(), shadowed_range)); + + let current_scope = self.current_scope_mut(); + current_scope.declared.push(ScopeDeclaration { name }); + current_scope.shadowed.extend(shadowed); + let scope_started_at = current_scope.started_at; + + self.stash.push_back(SemanticEvent::DeclarationFound { + range: declaration_range, + scope_started_at, + }); + } + + fn get_declaration_range_by_trimmed_text( + &self, + name_token: &JsSyntaxToken, + ) -> Option<&TextRange> { + let name = name_token.token_text_trimmed(); + self.declared_names.get(&name) + } } /// Extracts [SemanticEvent] from [SyntaxNode]. diff --git a/crates/rome_js_semantic/src/tests/assertions.rs b/crates/rome_js_semantic/src/tests/assertions.rs index 13841fcb3e2..56991d770e7 100644 --- a/crates/rome_js_semantic/src/tests/assertions.rs +++ b/crates/rome_js_semantic/src/tests/assertions.rs @@ -24,6 +24,17 @@ use std::collections::{BTreeMap, HashMap}; /// let a/*#A*/ = 1; /// ``` /// +/// #### Read Assertion +/// +/// Test if the attached token is reference "reading" the value of a symbol. +/// Pattern: ```/*READ