Skip to content

Commit

Permalink
perf(linter): performance improvement for css semantic model (#4044)
Browse files Browse the repository at this point in the history
  • Loading branch information
togami2864 authored Sep 23, 2024
1 parent 20a726c commit a3483e4
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 43 deletions.
1 change: 0 additions & 1 deletion crates/biome_css_analyze/src/services/semantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ impl Visitor for SemanticModelBuilderVisitor {
fn visit(&mut self, event: &WalkEvent<CssSyntaxNode>, _ctx: VisitorContext<CssLanguage>) {
match event {
WalkEvent::Enter(node) => {
self.builder.push_node(node);
self.extractor.enter(node);
}
WalkEvent::Leave(node) => {
Expand Down
18 changes: 11 additions & 7 deletions crates/biome_css_semantic/src/events.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::VecDeque;
use std::{borrow::Cow, collections::VecDeque};

use biome_css_syntax::{
AnyCssSelector, CssDeclarationBlock, CssRelativeSelector, CssSyntaxKind::*,
Expand All @@ -10,6 +10,8 @@ use crate::{
semantic_model::model::Specificity,
};

const ROOT_SELECTOR: &str = ":root";

#[derive(Debug)]
pub enum SemanticEvent {
RuleStart(TextRange),
Expand Down Expand Up @@ -102,22 +104,24 @@ impl SemanticEventExtractor {
}
}

#[inline]
fn process_selector(&mut self, selector: AnyCssSelector) {
match selector {
AnyCssSelector::CssComplexSelector(s) => {
if let Ok(l) = s.left() {
self.add_selector_event(l.text(), l.range());
self.add_selector_event(Cow::Borrowed(&l.text()), l.range());
}
if let Ok(r) = s.right() {
self.add_selector_event(r.text(), r.range());
self.add_selector_event(Cow::Borrowed(&r.text()), r.range());
}
}
AnyCssSelector::CssCompoundSelector(selector) => {
if selector.text() == ":root" {
let selector_text = selector.text();
if selector_text == ROOT_SELECTOR {
self.stash.push_back(SemanticEvent::RootSelectorStart);
self.is_in_root_selector = true;
}
self.add_selector_event(selector.text().to_string(), selector.range())
self.add_selector_event(Cow::Borrowed(&selector_text), selector.range())
}
_ => {}
}
Expand Down Expand Up @@ -194,9 +198,9 @@ impl SemanticEventExtractor {
});
}

fn add_selector_event(&mut self, name: String, range: TextRange) {
fn add_selector_event(&mut self, name: Cow<str>, range: TextRange) {
self.stash.push_back(SemanticEvent::SelectorDeclaration {
name,
name: name.into_owned(),
range,
specificity: Specificity(0, 0, 0), // TODO: Implement this
});
Expand Down
22 changes: 5 additions & 17 deletions crates/biome_css_semantic/src/semantic_model/builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use biome_css_syntax::{CssRoot, CssSyntaxKind, CssSyntaxNode};
use std::collections::BTreeMap;

use biome_css_syntax::CssRoot;
use biome_rowan::TextRange;
use rustc_hash::FxHashMap;

Expand All @@ -10,15 +12,14 @@ use crate::events::SemanticEvent;

pub struct SemanticModelBuilder {
root: CssRoot,
node_by_range: FxHashMap<TextRange, CssSyntaxNode>,
/// List of all top-level rules in the CSS file
rules: Vec<Rule>,
global_custom_variables: FxHashMap<String, CssGlobalCustomVariable>,
/// Stack of rule IDs to keep track of the current rule hierarchy
current_rule_stack: Vec<RuleId>,
next_rule_id: RuleId,
/// Map to get the rule containing the given range of CST nodes
range_to_rule: FxHashMap<TextRange, Rule>,
range_to_rule: BTreeMap<TextRange, Rule>,
rules_by_id: FxHashMap<RuleId, Rule>,
/// Indicates if the current node is within a `:root` selector
is_in_root_selector: bool,
Expand All @@ -28,11 +29,10 @@ impl SemanticModelBuilder {
pub fn new(root: CssRoot) -> Self {
Self {
root,
node_by_range: FxHashMap::default(),
rules: Vec::new(),
current_rule_stack: Vec::new(),
global_custom_variables: FxHashMap::default(),
range_to_rule: FxHashMap::default(),
range_to_rule: BTreeMap::default(),
is_in_root_selector: false,
next_rule_id: RuleId::default(),
rules_by_id: FxHashMap::default(),
Expand All @@ -42,7 +42,6 @@ impl SemanticModelBuilder {
pub fn build(self) -> SemanticModel {
let data = SemanticModelData {
root: self.root,
node_by_range: self.node_by_range,
rules: self.rules,
global_custom_variables: self.global_custom_variables,
range_to_rule: self.range_to_rule,
Expand All @@ -51,17 +50,6 @@ impl SemanticModelBuilder {
SemanticModel::new(data)
}

#[inline]
pub fn push_node(&mut self, node: &CssSyntaxNode) {
use CssSyntaxKind::*;
if matches!(
node.kind(),
CSS_SELECTOR_LIST | CSS_DECLARATION | CSS_DECLARATION_OR_RULE_LIST | CSS_QUALIFIED_RULE
) {
self.node_by_range.insert(node.text_range(), node.clone());
}
}

#[inline]
pub fn push_event(&mut self, event: SemanticEvent) {
match event {
Expand Down
1 change: 0 additions & 1 deletion crates/biome_css_semantic/src/semantic_model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ pub fn semantic_model(root: &CssRoot) -> SemanticModel {
for node in root.preorder() {
match node {
biome_css_syntax::WalkEvent::Enter(node) => {
builder.push_node(&node);
extractor.enter(&node);
}
biome_css_syntax::WalkEvent::Leave(node) => extractor.leave(&node),
Expand Down
41 changes: 24 additions & 17 deletions crates/biome_css_semantic/src/semantic_model/model.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::rc::Rc;
use std::{collections::BTreeMap, rc::Rc};

use biome_css_syntax::{CssRoot, CssSyntaxNode};
use biome_rowan::TextRange;
use biome_css_syntax::CssRoot;
use biome_rowan::{TextRange, TextSize};
use rustc_hash::FxHashMap;

/// The façade for all semantic information of a CSS document.
Expand All @@ -24,11 +24,6 @@ impl SemanticModel {
&self.data.root
}

/// Retrieves a node by its text range.
pub fn node_by_range(&self, range: TextRange) -> Option<&CssSyntaxNode> {
self.data.node_by_range.get(&range)
}

/// Returns a slice of all rules in the CSS document.
pub fn rules(&self) -> &[Rule] {
&self.data.rules
Expand All @@ -44,12 +39,26 @@ impl SemanticModel {

/// Returns the rule that contains the given range.
pub fn get_rule_by_range(&self, target_range: TextRange) -> Option<&Rule> {
self.data
.range_to_rule
.iter()
.filter(|(rule_range, _)| rule_range.contains_range(target_range))
.min_by_key(|(rule_range, _)| rule_range.len())
.map(|(_, rule)| rule)
// Generally, this function narrows down the search before finding the most specific rule for better performance.
// But when the target range starts from 0, the BTreeMap's range method may not work as expected due to
// the comparison semantics of TextRange.

// Handle the edge case where the target range starts from 0.
if target_range.start() == TextSize::from(0) {
self.data
.range_to_rule
.iter()
.rev()
.find(|(&range, _)| range.contains_range(target_range))
.map(|(_, rule)| rule)
} else {
self.data
.range_to_rule
.range(..=target_range)
.rev()
.find(|(&range, _)| range.contains_range(target_range))
.map(|(_, rule)| rule)
}
}
}

Expand All @@ -60,16 +69,14 @@ impl SemanticModel {
#[derive(Debug)]
pub(crate) struct SemanticModelData {
pub(crate) root: CssRoot,
/// Map to each by its range
pub(crate) node_by_range: FxHashMap<TextRange, CssSyntaxNode>,
/// List of all top-level rules in the CSS document
pub(crate) rules: Vec<Rule>,
/// Map of CSS variables declared in the `:root` selector or using the @property rule.
pub(crate) global_custom_variables: FxHashMap<String, CssGlobalCustomVariable>,
/// Map of all the rules by their id
pub(crate) rules_by_id: FxHashMap<RuleId, Rule>,
/// Map of the range of each rule to the rule itself
pub(crate) range_to_rule: FxHashMap<TextRange, Rule>,
pub(crate) range_to_rule: BTreeMap<TextRange, Rule>,
}

/// Represents a CSS rule set, including its selectors, declarations, and nested rules.
Expand Down
16 changes: 16 additions & 0 deletions crates/biome_text_size/src/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@ impl fmt::Debug for TextRange {
}
}

impl PartialOrd for TextRange {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for TextRange {
fn cmp(&self, other: &Self) -> Ordering {
match self.start.cmp(&other.start) {
Ordering::Less => Ordering::Less,
Ordering::Greater => Ordering::Greater,
Ordering::Equal => self.end.cmp(&other.end),
}
}
}

impl TextRange {
/// Creates a new `TextRange` with the given `start` and `end` (`start..end`).
///
Expand Down

0 comments on commit a3483e4

Please sign in to comment.