Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
feat(rome_js_semantic): simple globals resolution (#3354)
Browse files Browse the repository at this point in the history
* semantic model support for globals
  • Loading branch information
xunilrj committed Oct 6, 2022
1 parent d69bef2 commit 879811c
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 25 deletions.
6 changes: 3 additions & 3 deletions crates/rome_js_analyze/src/utils/tests.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use super::rename::*;
use crate::utils::batch::JsBatchMutation;
use rome_diagnostics::file::FileId;
use rome_js_semantic::semantic_model;
use rome_js_semantic::{semantic_model, SemanticModelOptions};
use rome_js_syntax::{JsFormalParameter, JsIdentifierBinding, JsVariableDeclarator, SourceType};
use rome_rowan::{AstNode, BatchMutationExt, SyntaxNodeCast};

/// Search and renames a binding named "a" to "b".
/// Asserts the renaming worked.
pub fn assert_rename_ok(before: &str, expected: &str) {
let r = rome_js_parser::parse(before, FileId::zero(), SourceType::js_module());
let model = semantic_model(&r.tree());
let model = semantic_model(&r.tree(), SemanticModelOptions::default());

let binding_a = r
.syntax()
Expand All @@ -30,7 +30,7 @@ pub fn assert_rename_ok(before: &str, expected: &str) {
/// Asserts the renaming to fail.
pub fn assert_rename_nok(before: &str) {
let r = rome_js_parser::parse(before, FileId::zero(), SourceType::js_module());
let model = semantic_model(&r.tree());
let model = semantic_model(&r.tree(), SemanticModelOptions::default());

let binding_a = r
.syntax()
Expand Down
121 changes: 103 additions & 18 deletions crates/rome_js_semantic/src/semantic_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,12 @@ struct SemanticModelData {
declaration_all_reads: HashMap<TextRange, Vec<(ReferenceType, TextRange)>>,
// Maps a declaration range to the range of its "writes"
declaration_all_writes: HashMap<TextRange, Vec<(ReferenceType, TextRange)>>,
/// All references that could not be resolved
unresolved_references: Vec<(ReferenceType, TextRange)>,
// All bindings that were exported
exported: HashSet<TextRange>,
/// All references that could not be resolved
unresolved_references: Vec<(ReferenceType, TextRange)>,
/// All references that are resolved to globals
global_references: Vec<(ReferenceType, TextRange)>,
}

impl SemanticModelData {
Expand Down Expand Up @@ -475,6 +477,34 @@ impl<'a> ExactSizeIterator for UnresolvedReferencesIter<'a> {

impl<'a> FusedIterator for UnresolvedReferencesIter<'a> {}

pub struct GlobalsReferencesIter<'a> {
data: Arc<SemanticModelData>,
iter: std::slice::Iter<'a, (ReferenceType, TextRange)>,
}

impl<'a> Iterator for GlobalsReferencesIter<'a> {
type Item = Reference;

fn next(&mut self) -> Option<Self::Item> {
let (ty, range) = self.iter.next()?;
let node = self.data.node_by_range.get(range)?;
Some(Reference {
data: self.data.clone(),
node: node.clone(),
range: *range,
ty: *ty,
})
}
}

impl<'a> ExactSizeIterator for GlobalsReferencesIter<'a> {
fn len(&self) -> usize {
self.iter.len()
}
}

impl<'a> FusedIterator for GlobalsReferencesIter<'a> {}

/// Iterate all bindings that were bound in a given scope. It **does
/// not** Returns bindings of parent scopes.
pub struct ScopeBindingsIter {
Expand Down Expand Up @@ -556,11 +586,11 @@ impl SemanticModel {
/// ```rust
/// use rome_rowan::{AstNode, SyntaxNodeCast};
/// use rome_js_syntax::{SourceType, JsReferenceIdentifier};
/// use rome_js_semantic::{semantic_model, SemanticScopeExtensions};
/// use rome_js_semantic::{semantic_model, SemanticModelOptions, SemanticScopeExtensions};
/// use rome_diagnostics::file::FileId;
///
/// let r = rome_js_parser::parse("function f(){let a = arguments[0]; let b = a + 1;}", FileId::zero(), SourceType::js_module());
/// let model = semantic_model(&r.tree());
/// let model = semantic_model(&r.tree(), SemanticModelOptions::default());
///
/// let arguments_reference = r
/// .syntax()
Expand Down Expand Up @@ -599,11 +629,11 @@ impl SemanticModel {
/// ```rust
/// use rome_rowan::{AstNode, SyntaxNodeCast};
/// use rome_js_syntax::{SourceType, JsReferenceIdentifier};
/// use rome_js_semantic::{semantic_model, DeclarationExtensions};
/// use rome_js_semantic::{semantic_model, DeclarationExtensions, SemanticModelOptions};
/// use rome_diagnostics::file::FileId;
///
/// let r = rome_js_parser::parse("function f(){let a = arguments[0]; let b = a + 1;}", FileId::zero(), SourceType::js_module());
/// let model = semantic_model(&r.tree());
/// let model = semantic_model(&r.tree(), SemanticModelOptions::default());
///
/// let arguments_reference = r
/// .syntax()
Expand Down Expand Up @@ -633,11 +663,11 @@ impl SemanticModel {
/// ```rust
/// use rome_rowan::{AstNode, SyntaxNodeCast};
/// use rome_js_syntax::{SourceType, JsIdentifierBinding};
/// use rome_js_semantic::{semantic_model, AllReferencesExtensions};
/// use rome_js_semantic::{semantic_model, AllReferencesExtensions, SemanticModelOptions};
/// use rome_diagnostics::file::FileId;
///
/// let r = rome_js_parser::parse("function f(){let a = arguments[0]; let b = a + 1;}", FileId::zero(), SourceType::js_module());
/// let model = semantic_model(&r.tree());
/// let model = semantic_model(&r.tree(), SemanticModelOptions::default());
///
/// let a_binding = r
/// .syntax()
Expand Down Expand Up @@ -684,6 +714,14 @@ impl SemanticModel {
}
}

/// Returns an iterator of all the globals references in the program
pub fn all_globals(&self) -> GlobalsReferencesIter<'_> {
GlobalsReferencesIter {
data: self.data.clone(),
iter: self.data.global_references.iter(),
}
}

/// Returns an iterator of all the unresolved references in the program
pub fn all_unresolved_references(&self) -> UnresolvedReferencesIter<'_> {
UnresolvedReferencesIter {
Expand Down Expand Up @@ -796,32 +834,36 @@ impl<T: HasClosureAstNode> ClosureExtensions for T {}
/// and stored inside the [SemanticModel].
pub struct SemanticModelBuilder {
root: JsAnyRoot,
node_by_range: HashMap<TextRange, JsSyntaxNode>,
globals: HashSet<String>,
scopes: Vec<SemanticModelScopeData>,
scope_range_by_start: HashMap<TextSize, BTreeSet<Interval<usize, usize>>>,
scope_hoisted_to_by_range: HashMap<TextSize, usize>,
node_by_range: HashMap<TextRange, JsSyntaxNode>,
declarations_by_range: HashMap<TextRange, TextRange>,
declaration_all_references: HashMap<TextRange, Vec<(ReferenceType, TextRange)>>,
declaration_all_reads: HashMap<TextRange, Vec<(ReferenceType, TextRange)>>,
declaration_all_writes: HashMap<TextRange, Vec<(ReferenceType, TextRange)>>,
unresolved_references: Vec<(ReferenceType, TextRange)>,
exported: HashSet<TextRange>,
unresolved_references: Vec<(ReferenceType, TextRange)>,
global_references: Vec<(ReferenceType, TextRange)>,
}

impl SemanticModelBuilder {
pub fn new(root: JsAnyRoot) -> Self {
Self {
root,
node_by_range: HashMap::new(),
globals: HashSet::new(),
scopes: vec![],
scope_range_by_start: HashMap::new(),
scope_hoisted_to_by_range: HashMap::new(),
node_by_range: HashMap::new(),
declarations_by_range: HashMap::new(),
declaration_all_references: HashMap::new(),
declaration_all_reads: HashMap::new(),
declaration_all_writes: HashMap::new(),
unresolved_references: Vec::new(),
exported: HashSet::new(),
unresolved_references: Vec::new(),
global_references: Vec::new(),
}
}

Expand All @@ -830,6 +872,11 @@ impl SemanticModelBuilder {
self.node_by_range.insert(node.text_range(), node.clone());
}

#[inline]
pub fn push_global(&mut self, name: impl Into<String>) {
self.globals.insert(name.into());
}

#[inline]
pub fn push_event(&mut self, e: SemanticEvent) {
use SemanticEvent::*;
Expand Down Expand Up @@ -970,7 +1017,14 @@ impl SemanticModelBuilder {
ReferenceType::Write { hoisted: false }
};

self.unresolved_references.push((ty, range));
let node = &self.node_by_range[&range];
let name = node.text_trimmed().to_string();

if self.globals.get(&name).is_some() {
self.global_references.push((ty, range));
} else {
self.unresolved_references.push((ty, range));
}
}
Exported { range } => {
self.exported.insert(range);
Expand All @@ -996,19 +1050,33 @@ impl SemanticModelBuilder {
declaration_all_references: self.declaration_all_references,
declaration_all_reads: self.declaration_all_reads,
declaration_all_writes: self.declaration_all_writes,
unresolved_references: self.unresolved_references,
exported: self.exported,
unresolved_references: self.unresolved_references,
global_references: self.global_references,
};
SemanticModel::new(data)
}
}

#[derive(Default)]
/// Extra options for the [SemanticModel] creation.
pub struct SemanticModelOptions {
/// All the allowed globals names
pub globals: HashSet<String>,
}

/// Build the complete [SemanticModel] of a parsed file.
/// For a push based model to build the [SemanticModel], see [SemanticModelBuilder].
pub fn semantic_model(root: &JsAnyRoot) -> SemanticModel {
pub fn semantic_model(root: &JsAnyRoot, options: SemanticModelOptions) -> SemanticModel {
let mut extractor = SemanticEventExtractor::default();
let mut builder = SemanticModelBuilder::new(root.clone());

let SemanticModelOptions { globals } = options;

for global in globals {
builder.push_global(global);
}

let root = root.syntax();
for node in root.preorder() {
match node {
Expand Down Expand Up @@ -1041,7 +1109,7 @@ mod test {
FileId::zero(),
SourceType::js_module(),
);
let model = semantic_model(&r.tree());
let model = semantic_model(&r.tree(), SemanticModelOptions::default());

let arguments_reference = r
.syntax()
Expand Down Expand Up @@ -1145,7 +1213,7 @@ mod test {
FileId::zero(),
SourceType::js_module(),
);
let model = semantic_model(&r.tree());
let model = semantic_model(&r.tree(), SemanticModelOptions::default());

let function_f = r
.syntax()
Expand Down Expand Up @@ -1183,7 +1251,7 @@ mod test {
/// Finds the last time a token named "name" is used and see if its node is marked as exported
fn assert_is_exported(is_exported: bool, name: &str, code: &str) {
let r = rome_js_parser::parse(code, FileId::zero(), SourceType::tsx());
let model = semantic_model(&r.tree());
let model = semantic_model(&r.tree(), SemanticModelOptions::default());

let node = r
.syntax()
Expand Down Expand Up @@ -1312,4 +1380,21 @@ mod test {
assert_is_exported(true, "A", "enum A {}; exports = A");
assert_is_exported(true, "A", "enum A {}; exports.A = A");
}

#[test]
pub fn ok_semantic_model_globals() {
let r = rome_js_parser::parse("console.log()", FileId::zero(), SourceType::js_module());

let mut options = SemanticModelOptions::default();
options.globals.insert("console".into());

let model = semantic_model(&r.tree(), options);

let globals: Vec<_> = model.all_globals().collect();

assert_eq!(globals.len(), 1);
assert!(globals[0].declaration().is_none());
assert!(globals[0].is_read());
assert_eq!(globals[0].node().text_trimmed(), "console");
}
}
4 changes: 2 additions & 2 deletions crates/rome_js_semantic/src/semantic_model/closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ mod test {

fn assert_closure(code: &str, name: &str, captures: &[&str]) {
let r = rome_js_parser::parse(code, FileId::zero(), SourceType::tsx());
let model = semantic_model(&r.tree());
let model = semantic_model(&r.tree(), SemanticModelOptions::default());

let closure = if name != "ARROWFUNCTION" {
let node = r
Expand Down Expand Up @@ -327,7 +327,7 @@ mod test {

fn get_closure_children(code: &str, name: &str) -> Vec<Closure> {
let r = rome_js_parser::parse(code, FileId::zero(), SourceType::tsx());
let model = semantic_model(&r.tree());
let model = semantic_model(&r.tree(), SemanticModelOptions::default());

let closure = if name != "ARROWFUNCTION" {
let node = r
Expand Down
4 changes: 2 additions & 2 deletions crates/rome_service/src/file_handlers/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rome_js_analyze::{analyze, analyze_with_inspect_matcher, metadata, RuleError
use rome_js_formatter::context::{QuoteProperties, QuoteStyle};
use rome_js_formatter::{context::JsFormatOptions, format_node};
use rome_js_parser::Parse;
use rome_js_semantic::semantic_model;
use rome_js_semantic::{semantic_model, SemanticModelOptions};
use rome_js_syntax::{
JsAnyRoot, JsLanguage, JsSyntaxNode, SourceType, TextRange, TextSize, TokenAtOffset,
};
Expand Down Expand Up @@ -456,7 +456,7 @@ fn rename(
new_name: String,
) -> Result<RenameResult, RomeError> {
let root = parse.tree();
let model = semantic_model(&root);
let model = semantic_model(&root, SemanticModelOptions::default());

if let Some(node) = parse
.syntax()
Expand Down

0 comments on commit 879811c

Please sign in to comment.