use std::collections::{BTreeMap, BTreeSet};
use crate::{
ParsedAnimationFactKind, ParsedCssModuleComposesEdgeKind, ParsedCssModuleComposesFactKind,
ParsedCssModuleValueFactKind, ParsedSassModuleEdgeFactKind, ParsedSassSymbolFactKind,
ParsedSelectorFactKind, ParsedStyleFacts, ParsedVariableFactKind, ParserByteSpanV0,
ParserPositionV0, ParserRangeV0, StyleDialect, collect_style_facts,
summarize_omena_parser_parity_lite,
};
use cstree::text::TextRange;
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ParserIndexSummaryV0 {
schema_version: &'static str,
language: &'static str,
selectors: ParserIndexSelectorFactsV0,
values: ParserIndexValueFactsV0,
custom_properties: ParserIndexCustomPropertyFactsV0,
sass: ParserIndexSassFactsV0,
keyframes: ParserIndexKeyframesFactsV0,
composes: ParserIndexComposesFactsV0,
wrappers: ParserIndexWrapperFactsV0,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ParserCanonicalCandidateBundleV0 {
schema_version: &'static str,
language: &'static str,
parity_lite: crate::OmenaParserParityLiteSummaryV0,
css_modules_intermediate: ParserIndexSummaryV0,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
struct ParserEvaluatorCandidateV0 {
kind: &'static str,
selector_name: String,
nested_safety_kind: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
bem_suffix_parent_name: Option<String>,
under_media: bool,
under_supports: bool,
under_layer: bool,
has_value_refs: bool,
has_local_value_refs: bool,
has_imported_value_refs: bool,
has_custom_property_refs: bool,
has_animation_ref: bool,
has_animation_name_ref: bool,
has_composes: bool,
has_local_composes: bool,
has_imported_composes: bool,
has_global_composes: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ParserEvaluatorCandidatesV0 {
schema_version: &'static str,
language: &'static str,
results: Vec<ParserEvaluatorCandidateV0>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ParserCanonicalProducerSignalV0 {
schema_version: &'static str,
language: &'static str,
canonical_candidate: ParserCanonicalCandidateBundleV0,
evaluator_candidates: ParserEvaluatorCandidatesV0,
public_product_gate: ParserPublicProductGateSignalV0,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
struct ParserPublicProductGateSignalV0 {
canonical_candidate_command: &'static str,
consumer_boundary_command: &'static str,
public_product_gate_command: &'static str,
included_in_parser_lane: bool,
included_in_rust_lane_bundle: bool,
included_in_rust_release_bundle: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexSelectorFactsV0 {
names: Vec<String>,
definition_facts: Vec<ParserIndexSelectorDefinitionFactV0>,
bem_suffix_parent_names: Vec<String>,
bem_suffix_safe_names: Vec<String>,
nested_unsafe_names: Vec<String>,
selectors_with_value_refs_names: Vec<String>,
selectors_with_animation_ref_names: Vec<String>,
selectors_with_animation_name_ref_names: Vec<String>,
bem_suffix_count: usize,
nested_safety_counts: NestedSafetyCountsV0,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexSelectorDefinitionFactV0 {
name: String,
source_order: usize,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
rule_byte_span: ParserByteSpanV0,
rule_range: ParserRangeV0,
full_selector: String,
declarations: String,
nested_safety_kind: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
bem_suffix_parent_name: Option<String>,
under_media: bool,
under_supports: bool,
under_layer: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexValueFactsV0 {
decl_names: Vec<String>,
decl_facts: Vec<ParserIndexValueDeclFactV0>,
decl_names_with_local_refs: Vec<String>,
decl_names_with_imported_refs: Vec<String>,
import_names: Vec<String>,
import_facts: Vec<ParserIndexValueImportFactV0>,
import_sources: Vec<String>,
import_alias_count: usize,
ref_names: Vec<String>,
ref_facts: Vec<ParserIndexValueRefFactV0>,
local_ref_names: Vec<String>,
imported_ref_names: Vec<String>,
imported_ref_sources: Vec<String>,
declaration_ref_names: Vec<String>,
declaration_imported_ref_sources: Vec<String>,
value_decl_ref_names: Vec<String>,
value_decl_imported_ref_sources: Vec<String>,
selectors_with_refs_names: Vec<String>,
selectors_with_local_refs_names: Vec<String>,
selectors_with_imported_refs_names: Vec<String>,
selectors_with_refs_under_media_names: Vec<String>,
selectors_with_refs_under_supports_names: Vec<String>,
selectors_with_refs_under_layer_names: Vec<String>,
selectors_with_local_refs_under_media_names: Vec<String>,
selectors_with_local_refs_under_supports_names: Vec<String>,
selectors_with_local_refs_under_layer_names: Vec<String>,
selectors_with_imported_refs_under_media_names: Vec<String>,
selectors_with_imported_refs_under_supports_names: Vec<String>,
selectors_with_imported_refs_under_layer_names: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexValueDeclFactV0 {
name: String,
value: String,
source_order: usize,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
rule_byte_span: ParserByteSpanV0,
rule_range: ParserRangeV0,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexValueImportFactV0 {
name: String,
imported_name: String,
from: String,
source_order: usize,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
#[serde(skip_serializing_if = "Option::is_none")]
imported_name_byte_span: Option<ParserByteSpanV0>,
#[serde(skip_serializing_if = "Option::is_none")]
imported_name_range: Option<ParserRangeV0>,
rule_byte_span: ParserByteSpanV0,
rule_range: ParserRangeV0,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexValueRefFactV0 {
name: String,
source: &'static str,
source_order: usize,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexCustomPropertyFactsV0 {
decl_names: Vec<String>,
decl_facts: Vec<ParserIndexCustomPropertyDeclFactV0>,
decl_context_selectors: Vec<String>,
decl_names_under_media: Vec<String>,
decl_names_under_supports: Vec<String>,
decl_names_under_layer: Vec<String>,
ref_names: Vec<String>,
ref_facts: Vec<ParserIndexCustomPropertyRefFactV0>,
selectors_with_refs_names: Vec<String>,
selectors_with_refs_under_media_names: Vec<String>,
selectors_with_refs_under_supports_names: Vec<String>,
selectors_with_refs_under_layer_names: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexCustomPropertyDeclFactV0 {
name: String,
value: String,
source_order: usize,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
rule_byte_span: ParserByteSpanV0,
rule_range: ParserRangeV0,
selector_contexts: Vec<String>,
wrapper_at_rules: Vec<ParserIndexAtRuleContextV0>,
under_media: bool,
under_supports: bool,
under_layer: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexCustomPropertyRefFactV0 {
name: String,
source_order: usize,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
selector_contexts: Vec<String>,
wrapper_at_rules: Vec<ParserIndexAtRuleContextV0>,
under_media: bool,
under_supports: bool,
under_layer: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexAtRuleContextV0 {
name: String,
params: String,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexSassFactsV0 {
variable_decl_names: Vec<String>,
symbol_decl_facts: Vec<ParserIndexSassSymbolDeclFactV0>,
variable_parameter_names: Vec<String>,
variable_ref_names: Vec<String>,
selectors_with_variable_refs_names: Vec<String>,
selectors_with_resolved_variable_refs_names: Vec<String>,
selectors_with_unresolved_variable_refs_names: Vec<String>,
mixin_decl_names: Vec<String>,
mixin_include_names: Vec<String>,
selectors_with_mixin_includes_names: Vec<String>,
selectors_with_resolved_mixin_includes_names: Vec<String>,
selectors_with_unresolved_mixin_includes_names: Vec<String>,
function_decl_names: Vec<String>,
function_call_names: Vec<String>,
selectors_with_function_calls_names: Vec<String>,
selector_symbol_facts: Vec<ParserIndexSassSelectorSymbolFactV0>,
module_use_sources: Vec<String>,
module_use_edges: Vec<ParserIndexSassModuleUseFactV0>,
module_forward_sources: Vec<String>,
module_forward_edges: Vec<ParserIndexSassModuleForwardFactV0>,
module_import_sources: Vec<String>,
same_file_resolution: ParserIndexSassSameFileResolutionFactsV0,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexSassSymbolDeclFactV0 {
symbol_kind: &'static str,
name: String,
role: &'static str,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "camelCase")]
struct ParserIndexSassModuleUseFactV0 {
source: String,
namespace_kind: &'static str,
namespace: Option<String>,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "camelCase")]
struct ParserIndexSassModuleForwardFactV0 {
source: String,
prefix: String,
visibility_kind: &'static str,
visibility_members: Vec<ParserIndexSassModuleForwardMemberV0>,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
rule_byte_span: ParserByteSpanV0,
rule_range: ParserRangeV0,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "camelCase")]
struct ParserIndexSassModuleForwardMemberV0 {
name: String,
symbol_kind: Option<&'static str>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexSassSameFileResolutionFactsV0 {
resolved_variable_ref_names: Vec<String>,
unresolved_variable_ref_names: Vec<String>,
resolved_mixin_include_names: Vec<String>,
unresolved_mixin_include_names: Vec<String>,
resolved_function_call_names: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "camelCase")]
struct ParserIndexSassSelectorSymbolFactV0 {
selector_name: String,
symbol_kind: &'static str,
name: String,
namespace: Option<String>,
role: &'static str,
resolution: &'static str,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexKeyframesFactsV0 {
names: Vec<String>,
decl_facts: Vec<ParserIndexKeyframesDeclFactV0>,
names_under_media: Vec<String>,
names_under_supports: Vec<String>,
names_under_layer: Vec<String>,
animation_ref_names: Vec<String>,
animation_name_ref_names: Vec<String>,
ref_facts: Vec<ParserIndexAnimationNameRefFactV0>,
selectors_with_animation_ref_names: Vec<String>,
selectors_with_animation_name_ref_names: Vec<String>,
selectors_with_animation_refs_under_media_names: Vec<String>,
selectors_with_animation_refs_under_supports_names: Vec<String>,
selectors_with_animation_refs_under_layer_names: Vec<String>,
selectors_with_animation_name_refs_under_media_names: Vec<String>,
selectors_with_animation_name_refs_under_supports_names: Vec<String>,
selectors_with_animation_name_refs_under_layer_names: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexKeyframesDeclFactV0 {
name: String,
source_order: usize,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
rule_byte_span: ParserByteSpanV0,
rule_range: ParserRangeV0,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexAnimationNameRefFactV0 {
name: String,
property: &'static str,
source_order: usize,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexComposesFactsV0 {
edges: Vec<ParserIndexComposesEdgeFactV0>,
selectors_with_composes_names: Vec<String>,
selectors_with_composes_under_media_names: Vec<String>,
selectors_with_composes_under_supports_names: Vec<String>,
selectors_with_composes_under_layer_names: Vec<String>,
local_selector_names: Vec<String>,
imported_selector_names: Vec<String>,
global_selector_names: Vec<String>,
local_selector_names_under_media: Vec<String>,
local_selector_names_under_supports: Vec<String>,
local_selector_names_under_layer: Vec<String>,
imported_selector_names_under_media: Vec<String>,
imported_selector_names_under_supports: Vec<String>,
imported_selector_names_under_layer: Vec<String>,
global_selector_names_under_media: Vec<String>,
global_selector_names_under_supports: Vec<String>,
global_selector_names_under_layer: Vec<String>,
import_sources: Vec<String>,
import_sources_under_media: Vec<String>,
import_sources_under_supports: Vec<String>,
import_sources_under_layer: Vec<String>,
class_name_count: usize,
local_class_name_count: usize,
imported_class_name_count: usize,
global_class_name_count: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "camelCase")]
struct ParserIndexComposesEdgeFactV0 {
kind: &'static str,
owner_selector_names: Vec<String>,
target_names: Vec<String>,
import_source: Option<String>,
class_tokens: Vec<ParserIndexComposesClassTokenV0>,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "camelCase")]
struct ParserIndexComposesClassTokenV0 {
class_name: String,
byte_span: ParserByteSpanV0,
range: ParserRangeV0,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct ParserIndexWrapperFactsV0 {
selectors_under_media_names: Vec<String>,
selectors_under_supports_names: Vec<String>,
selectors_under_layer_names: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
#[serde(rename_all = "camelCase")]
struct NestedSafetyCountsV0 {
flat: usize,
bem_suffix_safe: usize,
nested_unsafe: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct SelectorBranch {
name: String,
name_span: ParserByteSpanV0,
bare_suffix_base: bool,
amp_suffix_depth: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct SassVariableDeclScope {
name: String,
selector_names: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct StyleBlock {
names: Vec<String>,
context_text: Option<String>,
start: usize,
end: usize,
rule_start: usize,
rule_end: usize,
body_start: usize,
body_end: usize,
header_text: Option<String>,
under_media: bool,
under_supports: bool,
under_layer: bool,
wrapper_at_rules: Vec<ParserIndexAtRuleContextV0>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
struct WrapperContext {
under_media: bool,
under_supports: bool,
under_layer: bool,
wrapper_at_rules: Vec<ParserIndexAtRuleContextV0>,
}
pub fn summarize_css_modules_intermediate(
source: &str,
dialect: StyleDialect,
) -> ParserIndexSummaryV0 {
let line_index = SourceLineIndex::new(source);
let facts = collect_style_facts(source, dialect);
let blocks = collect_style_blocks(source, &line_index);
let selectors = summarize_selectors(source, &line_index, &facts, &blocks);
let values = summarize_values(source, &line_index, &facts, &blocks);
let custom_properties = summarize_custom_properties(source, &line_index, &facts, &blocks);
let sass = summarize_sass(source, &line_index, &facts, &blocks);
let keyframes = summarize_keyframes(source, &line_index, &facts, &blocks);
let composes = summarize_composes(source, &line_index, &facts, &blocks);
let wrappers = summarize_wrappers(&blocks);
ParserIndexSummaryV0 {
schema_version: "0",
language: dialect_label(dialect),
selectors: ParserIndexSelectorFactsV0 {
selectors_with_value_refs_names: values.selectors_with_refs_names.clone(),
selectors_with_animation_ref_names: keyframes
.selectors_with_animation_ref_names
.clone(),
selectors_with_animation_name_ref_names: keyframes
.selectors_with_animation_name_ref_names
.clone(),
..selectors
},
values,
custom_properties,
sass,
keyframes,
composes,
wrappers,
}
}
pub fn summarize_parser_canonical_candidate(
source: &str,
dialect: StyleDialect,
) -> ParserCanonicalCandidateBundleV0 {
let parity_lite = summarize_omena_parser_parity_lite(source, dialect);
let css_modules_intermediate = summarize_css_modules_intermediate(source, dialect);
ParserCanonicalCandidateBundleV0 {
schema_version: "0",
language: parity_lite.language,
parity_lite,
css_modules_intermediate,
}
}
pub fn summarize_parser_evaluator_candidates(
source: &str,
dialect: StyleDialect,
) -> ParserEvaluatorCandidatesV0 {
let intermediate = summarize_css_modules_intermediate(source, dialect);
let bem_suffix_safe_names: BTreeSet<&str> = intermediate
.selectors
.bem_suffix_safe_names
.iter()
.map(String::as_str)
.collect();
let nested_unsafe_names: BTreeSet<&str> = intermediate
.selectors
.nested_unsafe_names
.iter()
.map(String::as_str)
.collect();
let selectors_under_media_names: BTreeSet<&str> = intermediate
.wrappers
.selectors_under_media_names
.iter()
.map(String::as_str)
.collect();
let selectors_under_supports_names: BTreeSet<&str> = intermediate
.wrappers
.selectors_under_supports_names
.iter()
.map(String::as_str)
.collect();
let selectors_under_layer_names: BTreeSet<&str> = intermediate
.wrappers
.selectors_under_layer_names
.iter()
.map(String::as_str)
.collect();
let selectors_with_refs_names: BTreeSet<&str> = intermediate
.values
.selectors_with_refs_names
.iter()
.map(String::as_str)
.collect();
let selectors_with_local_refs_names: BTreeSet<&str> = intermediate
.values
.selectors_with_local_refs_names
.iter()
.map(String::as_str)
.collect();
let selectors_with_imported_refs_names: BTreeSet<&str> = intermediate
.values
.selectors_with_imported_refs_names
.iter()
.map(String::as_str)
.collect();
let selectors_with_custom_property_refs_names: BTreeSet<&str> = intermediate
.custom_properties
.selectors_with_refs_names
.iter()
.map(String::as_str)
.collect();
let selectors_with_animation_ref_names: BTreeSet<&str> = intermediate
.keyframes
.selectors_with_animation_ref_names
.iter()
.map(String::as_str)
.collect();
let selectors_with_animation_name_ref_names: BTreeSet<&str> = intermediate
.keyframes
.selectors_with_animation_name_ref_names
.iter()
.map(String::as_str)
.collect();
let selectors_with_composes_names: BTreeSet<&str> = intermediate
.composes
.selectors_with_composes_names
.iter()
.map(String::as_str)
.collect();
let local_selector_names: BTreeSet<&str> = intermediate
.composes
.local_selector_names
.iter()
.map(String::as_str)
.collect();
let imported_selector_names: BTreeSet<&str> = intermediate
.composes
.imported_selector_names
.iter()
.map(String::as_str)
.collect();
let global_selector_names: BTreeSet<&str> = intermediate
.composes
.global_selector_names
.iter()
.map(String::as_str)
.collect();
let results = intermediate
.selectors
.names
.iter()
.map(|selector_name| {
let selector = selector_name.as_str();
let nested_safety_kind = if nested_unsafe_names.contains(selector) {
"nestedUnsafe"
} else if bem_suffix_safe_names.contains(selector) {
"bemSuffixSafe"
} else {
"flat"
};
ParserEvaluatorCandidateV0 {
kind: "selector-index-facts",
selector_name: selector_name.clone(),
nested_safety_kind,
bem_suffix_parent_name: if nested_safety_kind == "bemSuffixSafe" {
bem_suffix_parent_name(selector)
} else {
None
},
under_media: selectors_under_media_names.contains(selector),
under_supports: selectors_under_supports_names.contains(selector),
under_layer: selectors_under_layer_names.contains(selector),
has_value_refs: selectors_with_refs_names.contains(selector),
has_local_value_refs: selectors_with_local_refs_names.contains(selector),
has_imported_value_refs: selectors_with_imported_refs_names.contains(selector),
has_custom_property_refs: selectors_with_custom_property_refs_names
.contains(selector),
has_animation_ref: selectors_with_animation_ref_names.contains(selector),
has_animation_name_ref: selectors_with_animation_name_ref_names.contains(selector),
has_composes: selectors_with_composes_names.contains(selector),
has_local_composes: local_selector_names.contains(selector),
has_imported_composes: imported_selector_names.contains(selector),
has_global_composes: global_selector_names.contains(selector),
}
})
.collect();
ParserEvaluatorCandidatesV0 {
schema_version: "0",
language: intermediate.language,
results,
}
}
pub fn summarize_parser_canonical_producer_signal(
source: &str,
dialect: StyleDialect,
) -> ParserCanonicalProducerSignalV0 {
let canonical_candidate = summarize_parser_canonical_candidate(source, dialect);
let evaluator_candidates = summarize_parser_evaluator_candidates(source, dialect);
ParserCanonicalProducerSignalV0 {
schema_version: "0",
language: canonical_candidate.language,
canonical_candidate,
evaluator_candidates,
public_product_gate: ParserPublicProductGateSignalV0 {
canonical_candidate_command: "pnpm check:rust-parser-canonical-candidate",
consumer_boundary_command: "pnpm check:rust-parser-consumer-boundary",
public_product_gate_command: "pnpm check:rust-parser-public-product",
included_in_parser_lane: true,
included_in_rust_lane_bundle: true,
included_in_rust_release_bundle: true,
},
}
}
fn summarize_selectors(
source: &str,
line_index: &SourceLineIndex,
facts: &ParsedStyleFacts,
blocks: &[StyleBlock],
) -> ParserIndexSelectorFactsV0 {
let mut names = Vec::new();
let mut definition_facts = Vec::new();
let mut bem_suffix_parent_names = Vec::new();
let mut bem_suffix_safe_names = Vec::new();
let mut nested_unsafe_names = Vec::new();
let mut nested_safety_counts = NestedSafetyCountsV0::default();
for selector in &facts.selectors {
if selector.kind != ParsedSelectorFactKind::Class {
continue;
}
let name = selector.name.clone();
names.push(name.clone());
let byte_span = byte_span_for_range(selector.range);
let nested_safety_kind = nested_safety_for_selector(blocks, &name).unwrap_or("flat");
let rule_block = selector_rule_block(blocks, &name, byte_span.start);
let rule_byte_span = rule_block
.map(|block| ParserByteSpanV0 {
start: block.rule_start,
end: block.rule_end,
})
.unwrap_or(byte_span);
let full_selector = rule_block
.and_then(|block| block.header_text.clone())
.unwrap_or_else(|| format!(".{name}"));
let declarations = rule_block
.and_then(|block| source.get(block.body_start..block.body_end))
.unwrap_or_default()
.trim()
.to_string();
let bem_suffix_parent_name = if nested_safety_kind == "bemSuffixSafe" {
bem_suffix_parent_name(&name)
} else {
None
};
match nested_safety_kind {
"bemSuffixSafe" => {
nested_safety_counts.bem_suffix_safe += 1;
bem_suffix_safe_names.push(name.clone());
if let Some(parent) = &bem_suffix_parent_name {
bem_suffix_parent_names.push(parent.clone());
}
}
"nestedUnsafe" => {
nested_safety_counts.nested_unsafe += 1;
nested_unsafe_names.push(name.clone());
}
_ => nested_safety_counts.flat += 1,
}
let wrapper = wrapper_for_offset(blocks, byte_span.start);
definition_facts.push(ParserIndexSelectorDefinitionFactV0 {
name,
source_order: definition_facts.len(),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
rule_byte_span,
rule_range: parser_range_for_byte_span(source, line_index, rule_byte_span),
full_selector,
declarations,
nested_safety_kind,
bem_suffix_parent_name,
under_media: wrapper.under_media,
under_supports: wrapper.under_supports,
under_layer: wrapper.under_layer,
});
}
names.sort();
definition_facts.sort();
bem_suffix_parent_names.sort();
bem_suffix_safe_names.sort();
nested_unsafe_names.sort();
ParserIndexSelectorFactsV0 {
names,
definition_facts,
bem_suffix_count: bem_suffix_safe_names.len(),
bem_suffix_parent_names,
bem_suffix_safe_names,
nested_unsafe_names,
nested_safety_counts,
..ParserIndexSelectorFactsV0::default()
}
}
fn summarize_values(
source: &str,
line_index: &SourceLineIndex,
facts: &ParsedStyleFacts,
blocks: &[StyleBlock],
) -> ParserIndexValueFactsV0 {
let imported_sources_by_name = facts
.css_module_value_import_edges
.iter()
.map(|edge| (edge.local_name.clone(), edge.import_source.clone()))
.collect::<BTreeMap<_, _>>();
let imported_names = imported_sources_by_name
.keys()
.cloned()
.collect::<BTreeSet<_>>();
let local_decl_names = facts
.css_module_values
.iter()
.filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
.map(|value| value.name.clone())
.filter(|name| !imported_names.contains(name))
.collect::<BTreeSet<_>>();
let mut decl_facts = Vec::new();
for value in &facts.css_module_values {
if value.kind != ParsedCssModuleValueFactKind::Definition
|| !local_decl_names.contains(&value.name)
{
continue;
}
let byte_span = byte_span_for_range(value.range);
let rule_byte_span =
at_rule_statement_byte_span_for_offset(source, byte_span.start, "@value");
decl_facts.push(ParserIndexValueDeclFactV0 {
name: value.name.clone(),
value: css_module_value_definition_text(source, byte_span.start),
source_order: decl_facts.len(),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
rule_byte_span,
rule_range: parser_range_for_byte_span(source, line_index, rule_byte_span),
});
}
decl_facts.sort();
decl_facts.dedup();
let mut import_facts = Vec::new();
for edge in &facts.css_module_value_import_edges {
let byte_span = byte_span_for_range(edge.local_range);
let remote_byte_span = byte_span_for_range(edge.remote_range);
let imported_name_byte_span =
(edge.remote_name != edge.local_name).then_some(remote_byte_span);
let rule_byte_span =
at_rule_statement_byte_span_for_offset(source, byte_span.start, "@value");
import_facts.push(ParserIndexValueImportFactV0 {
name: edge.local_name.clone(),
imported_name: edge.remote_name.clone(),
from: edge.import_source.clone(),
source_order: import_facts.len(),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
imported_name_byte_span,
imported_name_range: imported_name_byte_span
.map(|span| parser_range_for_byte_span(source, line_index, span)),
rule_byte_span,
rule_range: parser_range_for_byte_span(source, line_index, rule_byte_span),
});
}
import_facts.sort();
import_facts.dedup();
let mut ref_facts = Vec::new();
let value_decl_ref_names = facts
.css_module_value_definition_edges
.iter()
.flat_map(|edge| edge.reference_names.iter().cloned())
.collect::<Vec<_>>();
let mut declaration_ref_names = Vec::new();
let mut selectors_with_refs = BTreeSet::new();
let mut selectors_with_local_refs = BTreeSet::new();
let mut selectors_with_imported_refs = BTreeSet::new();
let mut selectors_with_refs_under_media = BTreeSet::new();
let mut selectors_with_refs_under_supports = BTreeSet::new();
let mut selectors_with_refs_under_layer = BTreeSet::new();
let mut selectors_with_local_refs_under_media = BTreeSet::new();
let mut selectors_with_local_refs_under_supports = BTreeSet::new();
let mut selectors_with_local_refs_under_layer = BTreeSet::new();
let mut selectors_with_imported_refs_under_media = BTreeSet::new();
let mut selectors_with_imported_refs_under_supports = BTreeSet::new();
let mut selectors_with_imported_refs_under_layer = BTreeSet::new();
for value in &facts.css_module_values {
if value.kind != ParsedCssModuleValueFactKind::Reference {
continue;
}
if !local_decl_names.contains(&value.name) && !imported_names.contains(&value.name) {
continue;
}
let offset = range_start(value.range);
let selector_names = selector_names_for_offset(blocks, offset);
if !selector_names.is_empty() {
declaration_ref_names.push(value.name.clone());
let byte_span = byte_span_for_range(value.range);
ref_facts.push(ParserIndexValueRefFactV0 {
name: value.name.clone(),
source: "declaration",
source_order: ref_facts.len(),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
});
let wrapper = wrapper_for_offset(blocks, offset);
for selector in selector_names {
selectors_with_refs.insert(selector.clone());
insert_by_wrapper(
&mut selectors_with_refs_under_media,
&mut selectors_with_refs_under_supports,
&mut selectors_with_refs_under_layer,
&selector,
&wrapper,
);
if local_decl_names.contains(&value.name) {
selectors_with_local_refs.insert(selector.clone());
insert_by_wrapper(
&mut selectors_with_local_refs_under_media,
&mut selectors_with_local_refs_under_supports,
&mut selectors_with_local_refs_under_layer,
&selector,
&wrapper,
);
}
if imported_names.contains(&value.name) {
selectors_with_imported_refs.insert(selector.clone());
insert_by_wrapper(
&mut selectors_with_imported_refs_under_media,
&mut selectors_with_imported_refs_under_supports,
&mut selectors_with_imported_refs_under_layer,
&selector,
&wrapper,
);
}
}
} else {
let byte_span = byte_span_for_range(value.range);
ref_facts.push(ParserIndexValueRefFactV0 {
name: value.name.clone(),
source: "valueDecl",
source_order: ref_facts.len(),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
});
}
}
ref_facts.sort();
ref_facts.dedup();
let mut value_decl_imported_ref_sources = Vec::new();
for name in &value_decl_ref_names {
if let Some(source) = imported_sources_by_name.get(name) {
value_decl_imported_ref_sources.push(source.clone());
}
}
let mut declaration_imported_ref_sources = Vec::new();
for name in &declaration_ref_names {
if let Some(source) = imported_sources_by_name.get(name) {
declaration_imported_ref_sources.push(source.clone());
}
}
let semantic_ref_names = declaration_ref_names
.iter()
.chain(value_decl_ref_names.iter())
.cloned()
.collect::<Vec<_>>();
ParserIndexValueFactsV0 {
decl_names: sorted(local_decl_names.clone()),
decl_facts,
decl_names_with_local_refs: facts
.css_module_value_definition_edges
.iter()
.filter(|edge| {
edge.reference_names
.iter()
.any(|name| local_decl_names.contains(name))
})
.map(|edge| edge.definition_name.clone())
.collect::<BTreeSet<_>>()
.into_iter()
.collect(),
decl_names_with_imported_refs: facts
.css_module_value_definition_edges
.iter()
.filter(|edge| {
edge.reference_names
.iter()
.any(|name| imported_names.contains(name))
})
.map(|edge| edge.definition_name.clone())
.collect::<BTreeSet<_>>()
.into_iter()
.collect(),
import_names: facts
.css_module_value_import_edges
.iter()
.map(|edge| edge.local_name.clone())
.collect::<BTreeSet<_>>()
.into_iter()
.collect(),
import_facts,
import_sources: facts
.css_module_value_import_edges
.iter()
.map(|edge| edge.import_source.clone())
.collect::<Vec<_>>()
.tap_sort(),
import_alias_count: facts
.css_module_value_import_edges
.iter()
.filter(|edge| edge.remote_name != edge.local_name)
.count(),
ref_names: semantic_ref_names.clone().tap_sort(),
ref_facts,
local_ref_names: semantic_ref_names
.iter()
.filter(|name| local_decl_names.contains(*name))
.cloned()
.collect::<Vec<_>>()
.tap_sort(),
imported_ref_names: semantic_ref_names
.iter()
.filter(|name| imported_names.contains(*name))
.cloned()
.collect::<Vec<_>>()
.tap_sort(),
imported_ref_sources: semantic_ref_names
.iter()
.filter_map(|name| imported_sources_by_name.get(name).cloned())
.collect::<Vec<_>>()
.tap_sort(),
declaration_ref_names: declaration_ref_names.tap_sort(),
declaration_imported_ref_sources: declaration_imported_ref_sources.tap_sort(),
value_decl_ref_names: value_decl_ref_names.tap_sort(),
value_decl_imported_ref_sources: value_decl_imported_ref_sources.tap_sort(),
selectors_with_refs_names: sorted(selectors_with_refs),
selectors_with_local_refs_names: sorted(selectors_with_local_refs),
selectors_with_imported_refs_names: sorted(selectors_with_imported_refs),
selectors_with_refs_under_media_names: sorted(selectors_with_refs_under_media),
selectors_with_refs_under_supports_names: sorted(selectors_with_refs_under_supports),
selectors_with_refs_under_layer_names: sorted(selectors_with_refs_under_layer),
selectors_with_local_refs_under_media_names: sorted(selectors_with_local_refs_under_media),
selectors_with_local_refs_under_supports_names: sorted(
selectors_with_local_refs_under_supports,
),
selectors_with_local_refs_under_layer_names: sorted(selectors_with_local_refs_under_layer),
selectors_with_imported_refs_under_media_names: sorted(
selectors_with_imported_refs_under_media,
),
selectors_with_imported_refs_under_supports_names: sorted(
selectors_with_imported_refs_under_supports,
),
selectors_with_imported_refs_under_layer_names: sorted(
selectors_with_imported_refs_under_layer,
),
}
}
fn summarize_custom_properties(
source: &str,
line_index: &SourceLineIndex,
facts: &ParsedStyleFacts,
blocks: &[StyleBlock],
) -> ParserIndexCustomPropertyFactsV0 {
let mut decl_facts = Vec::new();
let mut ref_facts = Vec::new();
for variable in &facts.variables {
match variable.kind {
ParsedVariableFactKind::CustomPropertyDeclaration => {
let byte_span = byte_span_for_range(variable.range);
let wrapper = wrapper_for_offset(blocks, byte_span.start);
let rule_byte_span = style_block_for_offset(blocks, byte_span.start)
.map(|block| ParserByteSpanV0 {
start: block.rule_start,
end: block.rule_end,
})
.unwrap_or_else(|| {
declaration_statement_byte_span_for_offset(source, byte_span.start)
});
decl_facts.push(ParserIndexCustomPropertyDeclFactV0 {
name: variable.name.clone(),
value: declaration_value_text(source, byte_span.start),
source_order: decl_facts.len(),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
rule_byte_span,
rule_range: parser_range_for_byte_span(source, line_index, rule_byte_span),
selector_contexts: selector_contexts_for_offset(blocks, byte_span.start),
wrapper_at_rules: wrapper.wrapper_at_rules.clone(),
under_media: wrapper.under_media,
under_supports: wrapper.under_supports,
under_layer: wrapper.under_layer,
});
}
ParsedVariableFactKind::CustomPropertyReference => {
let byte_span = byte_span_for_range(variable.range);
let wrapper = wrapper_for_offset(blocks, byte_span.start);
ref_facts.push(ParserIndexCustomPropertyRefFactV0 {
name: variable.name.clone(),
source_order: ref_facts.len(),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
selector_contexts: selector_contexts_for_offset(blocks, byte_span.start),
wrapper_at_rules: wrapper.wrapper_at_rules.clone(),
under_media: wrapper.under_media,
under_supports: wrapper.under_supports,
under_layer: wrapper.under_layer,
});
}
_ => {}
}
}
decl_facts.sort();
decl_facts.dedup();
ref_facts.sort();
ref_facts.dedup();
ParserIndexCustomPropertyFactsV0 {
decl_names: sorted(decl_facts.iter().map(|fact| fact.name.clone()).collect()),
decl_context_selectors: sorted(
decl_facts
.iter()
.flat_map(|fact| fact.selector_contexts.iter().cloned())
.collect(),
),
decl_names_under_media: sorted(
decl_facts
.iter()
.filter(|fact| fact.under_media)
.map(|fact| fact.name.clone())
.collect(),
),
decl_names_under_supports: sorted(
decl_facts
.iter()
.filter(|fact| fact.under_supports)
.map(|fact| fact.name.clone())
.collect(),
),
decl_names_under_layer: sorted(
decl_facts
.iter()
.filter(|fact| fact.under_layer)
.map(|fact| fact.name.clone())
.collect(),
),
ref_names: sorted(ref_facts.iter().map(|fact| fact.name.clone()).collect()),
selectors_with_refs_names: sorted(
ref_facts
.iter()
.flat_map(|fact| selector_names_from_contexts(&fact.selector_contexts))
.collect(),
),
selectors_with_refs_under_media_names: sorted(
ref_facts
.iter()
.filter(|fact| fact.under_media)
.flat_map(|fact| selector_names_from_contexts(&fact.selector_contexts))
.collect(),
),
selectors_with_refs_under_supports_names: sorted(
ref_facts
.iter()
.filter(|fact| fact.under_supports)
.flat_map(|fact| selector_names_from_contexts(&fact.selector_contexts))
.collect(),
),
selectors_with_refs_under_layer_names: sorted(
ref_facts
.iter()
.filter(|fact| fact.under_layer)
.flat_map(|fact| selector_names_from_contexts(&fact.selector_contexts))
.collect(),
),
decl_facts,
ref_facts,
}
}
fn summarize_sass(
source: &str,
line_index: &SourceLineIndex,
facts: &ParsedStyleFacts,
blocks: &[StyleBlock],
) -> ParserIndexSassFactsV0 {
let mut variable_decl_names = BTreeSet::new();
let mut variable_parameter_names = BTreeSet::new();
let mut variable_ref_names = BTreeSet::new();
let mut mixin_decl_names = BTreeSet::new();
let mut mixin_include_names = BTreeSet::new();
let mut function_decl_names = BTreeSet::new();
let mut function_call_names = BTreeSet::new();
let mut symbol_decl_facts = Vec::new();
let mut selector_symbol_facts = Vec::new();
let mut global_variable_decl_names = BTreeSet::new();
let mut variable_decl_scopes = Vec::new();
for symbol in &facts.sass_symbols {
let byte_span = byte_span_for_range(symbol.range);
let range = parser_range_for_byte_span(source, line_index, byte_span);
match symbol.kind {
ParsedSassSymbolFactKind::VariableDeclaration => {
if symbol.role == "parameter"
|| is_sass_parameter_declaration(source, byte_span.start)
{
variable_parameter_names.insert(symbol.name.clone());
} else {
variable_decl_names.insert(symbol.name.clone());
let selector_names = selector_names_for_offset(blocks, byte_span.start);
if selector_names.is_empty() {
global_variable_decl_names.insert(symbol.name.clone());
}
variable_decl_scopes.push(SassVariableDeclScope {
name: symbol.name.clone(),
selector_names,
});
}
symbol_decl_facts.push(ParserIndexSassSymbolDeclFactV0 {
symbol_kind: symbol.symbol_kind,
name: symbol.name.clone(),
role: symbol.role,
byte_span,
range,
});
}
ParsedSassSymbolFactKind::MixinDeclaration => {
mixin_decl_names.insert(symbol.name.clone());
symbol_decl_facts.push(ParserIndexSassSymbolDeclFactV0 {
symbol_kind: symbol.symbol_kind,
name: symbol.name.clone(),
role: symbol.role,
byte_span,
range,
});
}
ParsedSassSymbolFactKind::FunctionDeclaration => {
function_decl_names.insert(symbol.name.clone());
symbol_decl_facts.push(ParserIndexSassSymbolDeclFactV0 {
symbol_kind: symbol.symbol_kind,
name: symbol.name.clone(),
role: symbol.role,
byte_span,
range,
});
}
ParsedSassSymbolFactKind::VariableReference => {
variable_ref_names.insert(symbol.name.clone());
}
ParsedSassSymbolFactKind::MixinInclude => {
if symbol.namespace.is_none() {
mixin_include_names.insert(symbol.name.clone());
}
}
ParsedSassSymbolFactKind::FunctionCall => {
if symbol.namespace.is_none() {
function_call_names.insert(symbol.name.clone());
}
}
}
}
let mut resolved_variable_ref_names = BTreeSet::new();
let mut unresolved_variable_ref_names = BTreeSet::new();
for symbol in &facts.sass_symbols {
if symbol.kind != ParsedSassSymbolFactKind::VariableReference || symbol.namespace.is_some()
{
continue;
}
if is_sass_variable_reference_resolved(
&symbol.name,
range_start(symbol.range),
blocks,
&global_variable_decl_names,
&variable_parameter_names,
&variable_decl_scopes,
) {
resolved_variable_ref_names.insert(symbol.name.clone());
} else {
unresolved_variable_ref_names.insert(symbol.name.clone());
}
}
let same_file_resolution = ParserIndexSassSameFileResolutionFactsV0 {
resolved_variable_ref_names: sorted(resolved_variable_ref_names),
unresolved_variable_ref_names: sorted(unresolved_variable_ref_names),
resolved_mixin_include_names: sorted(
mixin_include_names
.iter()
.filter(|name| mixin_decl_names.contains(*name))
.cloned()
.collect(),
),
unresolved_mixin_include_names: sorted(
mixin_include_names
.iter()
.filter(|name| !mixin_decl_names.contains(*name))
.cloned()
.collect(),
),
resolved_function_call_names: sorted(
function_call_names
.iter()
.filter(|name| function_decl_names.contains(*name))
.cloned()
.collect(),
),
};
for symbol in &facts.sass_symbols {
if matches!(
symbol.kind,
ParsedSassSymbolFactKind::VariableDeclaration
| ParsedSassSymbolFactKind::MixinDeclaration
| ParsedSassSymbolFactKind::FunctionDeclaration
) {
continue;
}
let offset = range_start(symbol.range);
let byte_span = byte_span_for_range(symbol.range);
for selector_name in selector_names_for_offset(blocks, offset) {
let resolution = match symbol.kind {
ParsedSassSymbolFactKind::VariableReference if symbol.namespace.is_some() => {
"external"
}
ParsedSassSymbolFactKind::VariableReference
if is_sass_variable_reference_resolved(
&symbol.name,
offset,
blocks,
&global_variable_decl_names,
&variable_parameter_names,
&variable_decl_scopes,
) =>
{
"resolved"
}
ParsedSassSymbolFactKind::MixinInclude if symbol.namespace.is_some() => "external",
ParsedSassSymbolFactKind::MixinInclude
if same_file_resolution
.resolved_mixin_include_names
.contains(&symbol.name) =>
{
"resolved"
}
ParsedSassSymbolFactKind::FunctionCall if symbol.namespace.is_some() => "external",
ParsedSassSymbolFactKind::FunctionCall
if same_file_resolution
.resolved_function_call_names
.contains(&symbol.name) =>
{
"resolved"
}
_ => "unresolved",
};
selector_symbol_facts.push(ParserIndexSassSelectorSymbolFactV0 {
selector_name,
symbol_kind: symbol.symbol_kind,
name: symbol.name.clone(),
namespace: symbol.namespace.clone(),
role: symbol.role,
resolution,
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
});
}
}
selector_symbol_facts.sort();
selector_symbol_facts.dedup();
let mut module_use_sources = BTreeSet::new();
let mut module_forward_sources = BTreeSet::new();
let mut module_import_sources = BTreeSet::new();
let mut module_use_edges = Vec::new();
let mut module_forward_edges = Vec::new();
for edge in &facts.sass_module_edges {
match edge.kind {
ParsedSassModuleEdgeFactKind::Use => {
let byte_span = byte_span_for_range(edge.range);
module_use_sources.insert(edge.source.clone());
module_use_edges.push(ParserIndexSassModuleUseFactV0 {
source: edge.source.clone(),
namespace_kind: edge.namespace_kind.unwrap_or("default"),
namespace: edge.namespace.clone(),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
});
}
ParsedSassModuleEdgeFactKind::Forward => {
let byte_span = byte_span_for_range(edge.range);
let rule_byte_span =
at_rule_statement_byte_span_for_offset(source, byte_span.start, "@forward");
module_forward_sources.insert(edge.source.clone());
module_forward_edges.push(ParserIndexSassModuleForwardFactV0 {
source: edge.source.clone(),
prefix: sass_module_forward_prefix_from_statement(source, rule_byte_span),
visibility_kind: edge.visibility_filter_kind.unwrap_or("all"),
visibility_members: edge
.visibility_filter_names
.iter()
.map(|name| ParserIndexSassModuleForwardMemberV0 {
name: name.clone(),
symbol_kind: sass_module_forward_member_symbol_kind(
source,
rule_byte_span,
name,
),
})
.collect(),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
rule_byte_span,
rule_range: parser_range_for_byte_span(source, line_index, rule_byte_span),
});
}
ParsedSassModuleEdgeFactKind::Import => {
let byte_span = byte_span_for_range(edge.range);
module_use_sources.insert(edge.source.clone());
module_import_sources.insert(edge.source.clone());
module_use_edges.push(ParserIndexSassModuleUseFactV0 {
source: edge.source.clone(),
namespace_kind: "wildcard",
namespace: None,
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
});
}
}
}
module_use_edges.sort();
module_use_edges.dedup();
module_forward_edges.sort();
module_forward_edges.dedup();
ParserIndexSassFactsV0 {
variable_decl_names: sorted(variable_decl_names),
symbol_decl_facts,
variable_parameter_names: sorted(variable_parameter_names.clone()),
variable_ref_names: sorted(variable_ref_names),
selectors_with_variable_refs_names: selector_names_for_variable_symbols(
blocks,
facts,
&global_variable_decl_names,
&variable_parameter_names,
&variable_decl_scopes,
None,
),
selectors_with_resolved_variable_refs_names: selector_names_for_variable_symbols(
blocks,
facts,
&global_variable_decl_names,
&variable_parameter_names,
&variable_decl_scopes,
Some(true),
),
selectors_with_unresolved_variable_refs_names: selector_names_for_variable_symbols(
blocks,
facts,
&global_variable_decl_names,
&variable_parameter_names,
&variable_decl_scopes,
Some(false),
),
mixin_decl_names: sorted(mixin_decl_names),
mixin_include_names: sorted(mixin_include_names),
selectors_with_mixin_includes_names: selector_names_for_symbols(
blocks,
facts,
ParsedSassSymbolFactKind::MixinInclude,
None,
),
selectors_with_resolved_mixin_includes_names: selector_names_for_symbols(
blocks,
facts,
ParsedSassSymbolFactKind::MixinInclude,
Some(&same_file_resolution.resolved_mixin_include_names),
),
selectors_with_unresolved_mixin_includes_names: selector_names_for_symbols(
blocks,
facts,
ParsedSassSymbolFactKind::MixinInclude,
Some(&same_file_resolution.unresolved_mixin_include_names),
),
function_decl_names: sorted(function_decl_names),
function_call_names: sorted(function_call_names),
selectors_with_function_calls_names: selector_names_for_symbols(
blocks,
facts,
ParsedSassSymbolFactKind::FunctionCall,
None,
),
selector_symbol_facts,
module_use_sources: sorted(module_use_sources),
module_use_edges,
module_forward_sources: sorted(module_forward_sources),
module_forward_edges,
module_import_sources: sorted(module_import_sources),
same_file_resolution,
}
}
fn summarize_keyframes(
source: &str,
line_index: &SourceLineIndex,
facts: &ParsedStyleFacts,
blocks: &[StyleBlock],
) -> ParserIndexKeyframesFactsV0 {
let mut names = Vec::new();
let mut decl_facts = Vec::new();
let mut names_under_media = BTreeSet::new();
let mut names_under_supports = BTreeSet::new();
let mut names_under_layer = BTreeSet::new();
let mut animation_ref_names = Vec::new();
let mut animation_name_ref_names = Vec::new();
let mut ref_facts = Vec::new();
let mut selectors_with_animation_ref_names = BTreeSet::new();
let mut selectors_with_animation_name_ref_names = BTreeSet::new();
let mut selectors_with_animation_refs_under_media_names = BTreeSet::new();
let mut selectors_with_animation_refs_under_supports_names = BTreeSet::new();
let mut selectors_with_animation_refs_under_layer_names = BTreeSet::new();
let mut selectors_with_animation_name_refs_under_media_names = BTreeSet::new();
let mut selectors_with_animation_name_refs_under_supports_names = BTreeSet::new();
let mut selectors_with_animation_name_refs_under_layer_names = BTreeSet::new();
let declared_keyframes = facts
.animations
.iter()
.filter(|animation| animation.kind == ParsedAnimationFactKind::KeyframesDeclaration)
.map(|animation| animation.name.clone())
.collect::<BTreeSet<_>>();
for animation in &facts.animations {
let offset = range_start(animation.range);
let wrapper = wrapper_for_offset(blocks, offset);
match animation.kind {
ParsedAnimationFactKind::KeyframesDeclaration => {
let byte_span = byte_span_for_range(animation.range);
let rule_byte_span =
at_rule_block_byte_span_for_offset(source, byte_span.start, "@keyframes");
decl_facts.push(ParserIndexKeyframesDeclFactV0 {
name: animation.name.clone(),
source_order: decl_facts.len(),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
rule_byte_span,
rule_range: parser_range_for_byte_span(source, line_index, rule_byte_span),
});
names.push(animation.name.clone());
insert_by_wrapper(
&mut names_under_media,
&mut names_under_supports,
&mut names_under_layer,
&animation.name,
&wrapper,
);
}
ParsedAnimationFactKind::AnimationNameReference => {
let byte_span = byte_span_for_range(animation.range);
let property = if property_name_before_offset(source, offset).as_deref()
== Some("animation-name")
{
"animation-name"
} else {
"animation"
};
ref_facts.push(ParserIndexAnimationNameRefFactV0 {
name: animation.name.clone(),
property,
source_order: ref_facts.len(),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
});
if !declared_keyframes.contains(&animation.name) {
continue;
}
let selectors = selector_names_for_offset(blocks, offset);
if property == "animation-name" {
animation_name_ref_names.push(animation.name.clone());
for selector in selectors {
selectors_with_animation_name_ref_names.insert(selector.clone());
insert_by_wrapper(
&mut selectors_with_animation_name_refs_under_media_names,
&mut selectors_with_animation_name_refs_under_supports_names,
&mut selectors_with_animation_name_refs_under_layer_names,
&selector,
&wrapper,
);
}
} else {
animation_ref_names.push(animation.name.clone());
for selector in selectors {
selectors_with_animation_ref_names.insert(selector.clone());
insert_by_wrapper(
&mut selectors_with_animation_refs_under_media_names,
&mut selectors_with_animation_refs_under_supports_names,
&mut selectors_with_animation_refs_under_layer_names,
&selector,
&wrapper,
);
}
}
}
}
}
decl_facts.sort();
decl_facts.dedup();
ref_facts.sort();
ref_facts.dedup();
ParserIndexKeyframesFactsV0 {
names: names.tap_sort_unique(),
decl_facts,
names_under_media: sorted(names_under_media),
names_under_supports: sorted(names_under_supports),
names_under_layer: sorted(names_under_layer),
animation_ref_names: animation_ref_names.tap_sort_unique(),
animation_name_ref_names: animation_name_ref_names.tap_sort_unique(),
ref_facts,
selectors_with_animation_ref_names: sorted(selectors_with_animation_ref_names),
selectors_with_animation_name_ref_names: sorted(selectors_with_animation_name_ref_names),
selectors_with_animation_refs_under_media_names: sorted(
selectors_with_animation_refs_under_media_names,
),
selectors_with_animation_refs_under_supports_names: sorted(
selectors_with_animation_refs_under_supports_names,
),
selectors_with_animation_refs_under_layer_names: sorted(
selectors_with_animation_refs_under_layer_names,
),
selectors_with_animation_name_refs_under_media_names: sorted(
selectors_with_animation_name_refs_under_media_names,
),
selectors_with_animation_name_refs_under_supports_names: sorted(
selectors_with_animation_name_refs_under_supports_names,
),
selectors_with_animation_name_refs_under_layer_names: sorted(
selectors_with_animation_name_refs_under_layer_names,
),
}
}
fn summarize_composes(
source: &str,
line_index: &SourceLineIndex,
facts: &ParsedStyleFacts,
blocks: &[StyleBlock],
) -> ParserIndexComposesFactsV0 {
let mut summary = ParserIndexComposesFactsV0::default();
for edge in &facts.css_module_composes_edges {
let byte_span = byte_span_for_range(edge.range);
summary.edges.push(ParserIndexComposesEdgeFactV0 {
kind: match edge.kind {
ParsedCssModuleComposesEdgeKind::Local => "local",
ParsedCssModuleComposesEdgeKind::External => "external",
ParsedCssModuleComposesEdgeKind::Global => "global",
},
owner_selector_names: edge.owner_selector_names.clone(),
target_names: edge.target_names.clone(),
import_source: edge.import_source.clone(),
class_tokens: composes_class_tokens_for_edge(source, line_index, facts, edge),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
});
let wrapper = wrapper_for_offset(blocks, range_start(edge.range));
let count = edge.owner_selector_names.len() * edge.target_names.len();
summary.class_name_count += count;
for owner in &edge.owner_selector_names {
summary.selectors_with_composes_names.push(owner.clone());
insert_vec_by_wrapper(
&mut summary.selectors_with_composes_under_media_names,
&mut summary.selectors_with_composes_under_supports_names,
&mut summary.selectors_with_composes_under_layer_names,
owner,
&wrapper,
);
}
match edge.kind {
ParsedCssModuleComposesEdgeKind::Local => {
summary.local_class_name_count += count;
for owner in &edge.owner_selector_names {
summary.local_selector_names.push(owner.clone());
insert_vec_by_wrapper(
&mut summary.local_selector_names_under_media,
&mut summary.local_selector_names_under_supports,
&mut summary.local_selector_names_under_layer,
owner,
&wrapper,
);
}
}
ParsedCssModuleComposesEdgeKind::External => {
summary.imported_class_name_count += count;
for owner in &edge.owner_selector_names {
summary.imported_selector_names.push(owner.clone());
insert_vec_by_wrapper(
&mut summary.imported_selector_names_under_media,
&mut summary.imported_selector_names_under_supports,
&mut summary.imported_selector_names_under_layer,
owner,
&wrapper,
);
if let Some(source) = &edge.import_source {
summary.import_sources.push(source.clone());
if wrapper.under_media {
summary.import_sources_under_media.push(source.clone());
}
if wrapper.under_supports {
summary.import_sources_under_supports.push(source.clone());
}
if wrapper.under_layer {
summary.import_sources_under_layer.push(source.clone());
}
}
}
}
ParsedCssModuleComposesEdgeKind::Global => {
summary.global_class_name_count += count;
for owner in &edge.owner_selector_names {
summary.global_selector_names.push(owner.clone());
insert_vec_by_wrapper(
&mut summary.global_selector_names_under_media,
&mut summary.global_selector_names_under_supports,
&mut summary.global_selector_names_under_layer,
owner,
&wrapper,
);
}
}
}
}
sort_all_composes(&mut summary);
summary.edges.sort();
summary.edges.dedup();
summary
}
fn composes_class_tokens_for_edge(
source: &str,
line_index: &SourceLineIndex,
facts: &ParsedStyleFacts,
edge: &crate::ParsedCssModuleComposesEdgeFact,
) -> Vec<ParserIndexComposesClassTokenV0> {
let target_names = edge
.target_names
.iter()
.map(String::as_str)
.collect::<BTreeSet<_>>();
let edge_start = range_start(edge.range);
let edge_end = u32::from(edge.range.end()) as usize;
let mut class_tokens = facts
.css_module_composes
.iter()
.filter(|fact| fact.kind == ParsedCssModuleComposesFactKind::Target)
.filter(|fact| target_names.contains(fact.name.as_str()))
.filter(|fact| {
let token_start = range_start(fact.range);
let token_end = u32::from(fact.range.end()) as usize;
token_start >= edge_start && token_end <= edge_end
})
.map(|fact| {
let byte_span = byte_span_for_range(fact.range);
ParserIndexComposesClassTokenV0 {
class_name: fact.name.clone(),
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
}
})
.collect::<Vec<_>>();
class_tokens.sort();
class_tokens.dedup();
class_tokens
}
fn summarize_wrappers(blocks: &[StyleBlock]) -> ParserIndexWrapperFactsV0 {
ParserIndexWrapperFactsV0 {
selectors_under_media_names: sorted(
blocks
.iter()
.filter(|block| block.under_media)
.flat_map(|block| {
block
.names
.iter()
.filter(|name| !name.starts_with("__selector_meta:"))
.cloned()
})
.collect(),
),
selectors_under_supports_names: sorted(
blocks
.iter()
.filter(|block| block.under_supports)
.flat_map(|block| {
block
.names
.iter()
.filter(|name| !name.starts_with("__selector_meta:"))
.cloned()
})
.collect(),
),
selectors_under_layer_names: sorted(
blocks
.iter()
.filter(|block| block.under_layer)
.flat_map(|block| {
block
.names
.iter()
.filter(|name| !name.starts_with("__selector_meta:"))
.cloned()
})
.collect(),
),
}
}
fn collect_style_blocks(source: &str, line_index: &SourceLineIndex) -> Vec<StyleBlock> {
let mut blocks = Vec::new();
collect_style_blocks_in_range(
source,
line_index,
0,
source.len(),
&[],
false,
WrapperContext::default(),
&mut blocks,
);
blocks
}
fn at_rule_context_for_block(
source: &str,
line_index: &SourceLineIndex,
header: &str,
start: usize,
end: usize,
) -> ParserIndexAtRuleContextV0 {
let trimmed = header.trim();
let without_at = trimmed.strip_prefix('@').unwrap_or(trimmed);
let split_index = without_at
.find(|character: char| character.is_whitespace())
.unwrap_or(without_at.len());
let name = without_at[..split_index].to_string();
let params = without_at[split_index..].trim().to_string();
let byte_span = ParserByteSpanV0 { start, end };
ParserIndexAtRuleContextV0 {
name,
params,
byte_span,
range: parser_range_for_byte_span(source, line_index, byte_span),
}
}
#[allow(clippy::too_many_arguments)]
fn collect_style_blocks_in_range(
source: &str,
line_index: &SourceLineIndex,
start: usize,
end: usize,
parent_branches: &[SelectorBranch],
parent_is_grouped: bool,
wrapper: WrapperContext,
blocks: &mut Vec<StyleBlock>,
) {
let mut index = start;
while let Some(open) = find_next_byte(source, b'{', index, end) {
let header_start = block_header_start(source, open, start);
let header = source.get(header_start..open).unwrap_or_default().trim();
let Some(close) = matching_brace(source, open, end) else {
break;
};
let mut child_wrapper = wrapper.clone();
if header.starts_with("@media") {
child_wrapper.under_media = true;
child_wrapper
.wrapper_at_rules
.push(at_rule_context_for_block(
source,
line_index,
header,
header_start,
close + 1,
));
blocks.push(StyleBlock {
names: Vec::new(),
context_text: None,
start: open + 1,
end: close,
rule_start: header_start,
rule_end: close + 1,
body_start: open + 1,
body_end: close,
header_text: Some(header.to_string()),
under_media: child_wrapper.under_media,
under_supports: child_wrapper.under_supports,
under_layer: child_wrapper.under_layer,
wrapper_at_rules: child_wrapper.wrapper_at_rules.clone(),
});
collect_style_blocks_in_range(
source,
line_index,
open + 1,
close,
parent_branches,
parent_is_grouped,
child_wrapper,
blocks,
);
} else if header.starts_with("@supports") {
child_wrapper.under_supports = true;
child_wrapper
.wrapper_at_rules
.push(at_rule_context_for_block(
source,
line_index,
header,
header_start,
close + 1,
));
blocks.push(StyleBlock {
names: Vec::new(),
context_text: None,
start: open + 1,
end: close,
rule_start: header_start,
rule_end: close + 1,
body_start: open + 1,
body_end: close,
header_text: Some(header.to_string()),
under_media: child_wrapper.under_media,
under_supports: child_wrapper.under_supports,
under_layer: child_wrapper.under_layer,
wrapper_at_rules: child_wrapper.wrapper_at_rules.clone(),
});
collect_style_blocks_in_range(
source,
line_index,
open + 1,
close,
parent_branches,
parent_is_grouped,
child_wrapper,
blocks,
);
} else if header.starts_with("@layer") {
child_wrapper.under_layer = true;
child_wrapper
.wrapper_at_rules
.push(at_rule_context_for_block(
source,
line_index,
header,
header_start,
close + 1,
));
blocks.push(StyleBlock {
names: Vec::new(),
context_text: None,
start: open + 1,
end: close,
rule_start: header_start,
rule_end: close + 1,
body_start: open + 1,
body_end: close,
header_text: Some(header.to_string()),
under_media: child_wrapper.under_media,
under_supports: child_wrapper.under_supports,
under_layer: child_wrapper.under_layer,
wrapper_at_rules: child_wrapper.wrapper_at_rules.clone(),
});
collect_style_blocks_in_range(
source,
line_index,
open + 1,
close,
parent_branches,
parent_is_grouped,
child_wrapper,
blocks,
);
} else if header.starts_with("@nest") {
let selector_header = header.trim_start_matches("@nest").trim();
let branches = resolve_selector_header_text(source, selector_header, parent_branches);
push_style_block(
source,
header_start,
open,
close,
selector_header,
&branches,
parent_branches,
parent_is_grouped,
wrapper.clone(),
blocks,
);
collect_style_blocks_in_range(
source,
line_index,
open + 1,
close,
&branches,
branches.len() > 1,
wrapper.clone(),
blocks,
);
} else if header.starts_with('@') {
} else {
let branches = resolve_selector_header_text(source, header, parent_branches);
push_style_block(
source,
header_start,
open,
close,
header,
&branches,
parent_branches,
parent_is_grouped,
wrapper.clone(),
blocks,
);
collect_style_blocks_in_range(
source,
line_index,
open + 1,
close,
&branches,
branches.len() > 1,
wrapper.clone(),
blocks,
);
}
index = close + 1;
}
}
#[allow(clippy::too_many_arguments)]
fn push_style_block(
source: &str,
header_start: usize,
open: usize,
close: usize,
header: &str,
branches: &[SelectorBranch],
parent_branches: &[SelectorBranch],
parent_is_grouped: bool,
wrapper: WrapperContext,
blocks: &mut Vec<StyleBlock>,
) {
let context_text = if branches.is_empty() {
let trimmed = header.trim();
(!trimmed.is_empty()).then(|| trimmed.to_string())
} else {
None
};
let names = branches
.iter()
.map(|branch| branch.name.clone())
.collect::<Vec<_>>();
blocks.push(StyleBlock {
names: names.clone(),
context_text,
start: open + 1,
end: close,
rule_start: header_start,
rule_end: close + 1,
body_start: open + 1,
body_end: close,
header_text: Some(header.to_string()),
under_media: wrapper.under_media,
under_supports: wrapper.under_supports,
under_layer: wrapper.under_layer,
wrapper_at_rules: wrapper.wrapper_at_rules.clone(),
});
let nested_safety =
classify_nested_safety(header, branches, parent_branches, parent_is_grouped);
for branch in branches {
blocks.push(StyleBlock {
names: vec![format!("__selector_meta:{}:{nested_safety}", branch.name)],
context_text: Some(source[branch.name_span.start..branch.name_span.end].to_string()),
start: branch.name_span.start,
end: branch.name_span.end,
rule_start: header_start,
rule_end: close + 1,
body_start: open + 1,
body_end: close,
header_text: Some(header.to_string()),
under_media: wrapper.under_media,
under_supports: wrapper.under_supports,
under_layer: wrapper.under_layer,
wrapper_at_rules: wrapper.wrapper_at_rules.clone(),
});
}
}
fn resolve_selector_header_text(
source: &str,
header: &str,
parent_branches: &[SelectorBranch],
) -> Vec<SelectorBranch> {
split_selector_groups_text(header)
.into_iter()
.flat_map(|group| resolve_selector_group_text(source, header, group, parent_branches))
.collect()
}
fn resolve_selector_group_text(
source: &str,
full_header: &str,
group: &str,
parent_branches: &[SelectorBranch],
) -> Vec<SelectorBranch> {
let group = group.trim();
if group.starts_with(":global") && !group.starts_with(":local") {
return Vec::new();
}
let tail = selector_tail(group);
if let Some(suffix) = tail.strip_prefix('&').map(str::trim)
&& is_ampersand_suffix_text(suffix)
{
let span = source_span_for_header_piece(source, full_header, suffix);
return parent_branches
.iter()
.map(|parent| SelectorBranch {
name: format!("{}{}", parent.name, suffix),
name_span: span,
bare_suffix_base: parent.bare_suffix_base,
amp_suffix_depth: parent.amp_suffix_depth + 1,
})
.collect();
}
let names = class_names_in_selector(tail, source, full_header);
let bare_suffix_base = parent_branches.is_empty() && names.len() == 1;
names
.into_iter()
.map(|(name, name_span)| SelectorBranch {
name,
name_span,
bare_suffix_base,
amp_suffix_depth: 0,
})
.collect()
}
fn is_ampersand_suffix_text(suffix: &str) -> bool {
suffix
.chars()
.next()
.is_some_and(|ch| ch == '-' || ch == '_' || ch.is_ascii_alphanumeric())
}
fn classify_nested_safety(
header: &str,
branches: &[SelectorBranch],
parent_branches: &[SelectorBranch],
parent_is_grouped: bool,
) -> &'static str {
if branches.is_empty() {
return "flat";
}
let is_nested = !parent_branches.is_empty() || header.contains('&');
if !is_nested {
return "flat";
}
let header = header.trim();
let bem_suffix_safe = branches.len() == 1
&& parent_branches.len() == 1
&& parent_branches[0].bare_suffix_base
&& !parent_is_grouped
&& header.starts_with('&')
&& (header[1..].trim_start().starts_with("__")
|| header[1..].trim_start().starts_with("--"));
let chained_bem_modifier_safe = header.starts_with('&')
&& header[1..].trim_start().starts_with("--")
&& !parent_branches.is_empty()
&& parent_branches
.iter()
.all(|parent| parent.amp_suffix_depth > 0);
if bem_suffix_safe || chained_bem_modifier_safe {
"bemSuffixSafe"
} else {
"nestedUnsafe"
}
}
fn nested_safety_for_selector(blocks: &[StyleBlock], name: &str) -> Option<&'static str> {
blocks.iter().find_map(|block| {
block.names.iter().find_map(|entry| {
entry
.strip_prefix("__selector_meta:")
.and_then(|rest| rest.rsplit_once(':'))
.and_then(|(entry_name, kind)| {
(entry_name == name).then_some(match kind {
"bemSuffixSafe" => "bemSuffixSafe",
"nestedUnsafe" => "nestedUnsafe",
_ => "flat",
})
})
})
})
}
fn selector_rule_block<'a>(
blocks: &'a [StyleBlock],
name: &str,
selector_offset: usize,
) -> Option<&'a StyleBlock> {
blocks
.iter()
.filter(|block| block.start <= selector_offset && selector_offset < block.end)
.filter(|block| {
block.names.iter().any(|entry| {
entry
.strip_prefix("__selector_meta:")
.and_then(|rest| rest.rsplit_once(':'))
.is_some_and(|(entry_name, _)| entry_name == name)
})
})
.max_by_key(|block| block.rule_start)
}
fn style_block_for_offset(blocks: &[StyleBlock], offset: usize) -> Option<&StyleBlock> {
blocks
.iter()
.filter(|block| !block.names.is_empty())
.filter(|block| block.start <= offset && offset < block.end)
.max_by_key(|block| block.rule_start)
}
fn split_selector_groups_text(header: &str) -> Vec<&str> {
let mut groups = Vec::new();
let mut start = 0usize;
let mut paren_depth = 0usize;
let mut bracket_depth = 0usize;
for (index, byte) in header.bytes().enumerate() {
match byte {
b'(' => paren_depth += 1,
b')' => paren_depth = paren_depth.saturating_sub(1),
b'[' => bracket_depth += 1,
b']' => bracket_depth = bracket_depth.saturating_sub(1),
b',' if paren_depth == 0 && bracket_depth == 0 => {
groups.push(&header[start..index]);
start = index + 1;
}
_ => {}
}
}
groups.push(&header[start..]);
groups
}
fn selector_tail(group: &str) -> &str {
let mut tail_start = 0usize;
let mut paren_depth = 0usize;
let mut bracket_depth = 0usize;
let bytes = group.as_bytes();
let mut index = 0usize;
while index < bytes.len() {
match bytes[index] {
b'(' => paren_depth += 1,
b')' => paren_depth = paren_depth.saturating_sub(1),
b'[' => bracket_depth += 1,
b']' => bracket_depth = bracket_depth.saturating_sub(1),
b'>' | b'+' | b'~' if paren_depth == 0 && bracket_depth == 0 => tail_start = index + 1,
byte if byte.is_ascii_whitespace() && paren_depth == 0 && bracket_depth == 0 => {
let previous = group[..index].trim_end().as_bytes().last().copied();
let next = group[index + 1..].trim_start().as_bytes().first().copied();
if previous.is_some()
&& next.is_some_and(|value| value == b'.' || value == b':' || value == b'&')
{
tail_start = index + 1;
}
}
_ => {}
}
index += 1;
}
group[tail_start..].trim()
}
fn class_names_in_selector(
selector: &str,
source: &str,
full_header: &str,
) -> Vec<(String, ParserByteSpanV0)> {
let mut names = Vec::new();
let mut index = 0usize;
let mut paren_depth = 0usize;
let mut bracket_depth = 0usize;
let bytes = selector.as_bytes();
while index < bytes.len() {
match bytes[index] {
b'(' => paren_depth += 1,
b')' => paren_depth = paren_depth.saturating_sub(1),
b'[' => bracket_depth += 1,
b']' => bracket_depth = bracket_depth.saturating_sub(1),
b'.' if paren_depth == 0 && bracket_depth == 0 => {
let start = index + 1;
let mut end = start;
while end < bytes.len()
&& (bytes[end].is_ascii_alphanumeric() || matches!(bytes[end], b'_' | b'-'))
{
end += 1;
}
if end > start {
let name = selector[start..end].to_string();
names.push((
name.clone(),
source_span_for_header_piece(source, full_header, &name),
));
}
index = end;
continue;
}
_ => {}
}
index += 1;
}
names
}
fn selector_names_for_offset(blocks: &[StyleBlock], offset: usize) -> Vec<String> {
let Some(max_start) = blocks
.iter()
.filter(|block| block.start <= offset && offset < block.end && !block.names.is_empty())
.map(|block| block.start)
.max()
else {
return Vec::new();
};
blocks
.iter()
.filter(|block| block.start == max_start && block.start <= offset && offset < block.end)
.flat_map(|block| {
block
.names
.iter()
.filter(|name| !name.starts_with("__selector_meta:"))
.cloned()
})
.collect::<BTreeSet<_>>()
.into_iter()
.collect()
}
fn selector_contexts_for_offset(blocks: &[StyleBlock], offset: usize) -> Vec<String> {
let Some(max_start) = blocks
.iter()
.filter(|block| block.start <= offset && offset < block.end)
.map(|block| block.start)
.max()
else {
return Vec::new();
};
let mut contexts = BTreeSet::new();
for block in blocks
.iter()
.filter(|block| block.start == max_start && block.start <= offset && offset < block.end)
{
if block.names.is_empty() {
if let Some(context) = &block.context_text {
contexts.insert(context.clone());
}
} else {
for name in &block.names {
if !name.starts_with("__selector_meta:") {
contexts.insert(format!(".{name}"));
}
}
}
}
contexts.into_iter().collect()
}
fn wrapper_for_offset(blocks: &[StyleBlock], offset: usize) -> WrapperContext {
blocks
.iter()
.filter(|block| block.start <= offset && offset < block.end)
.max_by_key(|block| block.start)
.map(|block| WrapperContext {
under_media: block.under_media,
under_supports: block.under_supports,
under_layer: block.under_layer,
wrapper_at_rules: block.wrapper_at_rules.clone(),
})
.unwrap_or_default()
}
fn insert_by_wrapper(
media: &mut BTreeSet<String>,
supports: &mut BTreeSet<String>,
layer: &mut BTreeSet<String>,
value: &str,
wrapper: &WrapperContext,
) {
if wrapper.under_media {
media.insert(value.to_string());
}
if wrapper.under_supports {
supports.insert(value.to_string());
}
if wrapper.under_layer {
layer.insert(value.to_string());
}
}
fn insert_vec_by_wrapper(
media: &mut Vec<String>,
supports: &mut Vec<String>,
layer: &mut Vec<String>,
value: &str,
wrapper: &WrapperContext,
) {
if wrapper.under_media {
media.push(value.to_string());
}
if wrapper.under_supports {
supports.push(value.to_string());
}
if wrapper.under_layer {
layer.push(value.to_string());
}
}
fn selector_names_from_contexts(contexts: &[String]) -> Vec<String> {
contexts
.iter()
.filter_map(|context| context.strip_prefix('.').map(ToString::to_string))
.collect()
}
fn selector_names_for_variable_symbols(
blocks: &[StyleBlock],
facts: &ParsedStyleFacts,
global_variable_decl_names: &BTreeSet<String>,
variable_parameter_names: &BTreeSet<String>,
variable_decl_scopes: &[SassVariableDeclScope],
resolved_filter: Option<bool>,
) -> Vec<String> {
facts
.sass_symbols
.iter()
.filter(|symbol| symbol.kind == ParsedSassSymbolFactKind::VariableReference)
.filter(|symbol| {
let Some(expected_resolved) = resolved_filter else {
return true;
};
if symbol.namespace.is_some() {
return false;
}
is_sass_variable_reference_resolved(
&symbol.name,
range_start(symbol.range),
blocks,
global_variable_decl_names,
variable_parameter_names,
variable_decl_scopes,
) == expected_resolved
})
.flat_map(|symbol| selector_names_for_offset(blocks, range_start(symbol.range)))
.collect::<BTreeSet<_>>()
.into_iter()
.collect()
}
fn is_sass_variable_reference_resolved(
name: &str,
offset: usize,
blocks: &[StyleBlock],
global_variable_decl_names: &BTreeSet<String>,
variable_parameter_names: &BTreeSet<String>,
variable_decl_scopes: &[SassVariableDeclScope],
) -> bool {
if global_variable_decl_names.contains(name) || variable_parameter_names.contains(name) {
return true;
}
let reference_selectors = selector_names_for_offset(blocks, offset);
!reference_selectors.is_empty()
&& variable_decl_scopes.iter().any(|scope| {
scope.name == name
&& !scope.selector_names.is_empty()
&& scope
.selector_names
.iter()
.any(|selector| reference_selectors.contains(selector))
})
}
fn selector_names_for_symbols(
blocks: &[StyleBlock],
facts: &ParsedStyleFacts,
kind: ParsedSassSymbolFactKind,
names_filter: Option<&[String]>,
) -> Vec<String> {
facts
.sass_symbols
.iter()
.filter(|symbol| symbol.kind == kind)
.filter(|symbol| {
names_filter
.map(|names| names.contains(&symbol.name))
.unwrap_or(true)
})
.flat_map(|symbol| selector_names_for_offset(blocks, range_start(symbol.range)))
.collect::<BTreeSet<_>>()
.into_iter()
.collect()
}
fn property_name_before_offset(source: &str, offset: usize) -> Option<String> {
let before = source.get(..offset)?;
let start = before.rfind(['{', ';']).map(|index| index + 1).unwrap_or(0);
let colon = before.rfind(':')?;
if colon < start {
return None;
}
Some(before[start..colon].trim().to_ascii_lowercase())
}
fn at_rule_statement_byte_span_for_offset(
source: &str,
offset: usize,
at_keyword: &str,
) -> ParserByteSpanV0 {
let start = previous_at_keyword_start(source, offset, at_keyword).unwrap_or(offset);
let end = source
.get(offset..)
.and_then(|rest| rest.find(';').map(|index| offset + index + 1))
.or_else(|| {
source
.get(offset..)
.and_then(|rest| rest.find('\n').map(|index| offset + index))
})
.unwrap_or(source.len());
ParserByteSpanV0 { start, end }
}
fn at_rule_block_byte_span_for_offset(
source: &str,
offset: usize,
at_keyword: &str,
) -> ParserByteSpanV0 {
let start = previous_at_keyword_start(source, offset, at_keyword).unwrap_or(offset);
let Some(open) = source.get(offset..).and_then(|rest| rest.find('{')) else {
return ParserByteSpanV0 { start, end: offset };
};
let open = offset + open;
let end = matching_brace(source, open, source.len()).map_or(source.len(), |close| close + 1);
ParserByteSpanV0 { start, end }
}
fn previous_at_keyword_start(source: &str, offset: usize, at_keyword: &str) -> Option<usize> {
source.get(..offset.min(source.len()))?.rfind(at_keyword)
}
fn css_module_value_definition_text(source: &str, offset: usize) -> String {
let span = at_rule_statement_byte_span_for_offset(source, offset, "@value");
let Some(statement) = source.get(span.start..span.end) else {
return String::new();
};
let Some(colon) = statement.find(':') else {
return String::new();
};
statement[colon + 1..]
.trim()
.trim_end_matches(';')
.trim()
.to_string()
}
fn declaration_statement_byte_span_for_offset(source: &str, offset: usize) -> ParserByteSpanV0 {
let start = source
.get(..offset)
.and_then(|before| before.rfind(['{', ';']).map(|index| index + 1))
.unwrap_or(offset);
let end = source
.get(offset..)
.and_then(|rest| {
let semicolon = rest.find(';');
let close = rest.find('}');
match (semicolon, close) {
(Some(semicolon), Some(close)) => Some(offset + semicolon.min(close)),
(Some(semicolon), None) => Some(offset + semicolon + 1),
(None, Some(close)) => Some(offset + close),
(None, None) => None,
}
})
.unwrap_or(source.len());
ParserByteSpanV0 { start, end }
}
fn declaration_value_text(source: &str, offset: usize) -> String {
let span = declaration_statement_byte_span_for_offset(source, offset);
let Some(statement) = source.get(span.start..span.end) else {
return String::new();
};
let Some(colon) = statement.find(':') else {
return String::new();
};
statement[colon + 1..]
.trim()
.trim_end_matches(';')
.trim()
.to_string()
}
fn sass_module_forward_prefix_from_statement(source: &str, span: ParserByteSpanV0) -> String {
let Some(statement) = source.get(span.start..span.end) else {
return String::new();
};
let Some(as_index) = statement.find(" as ") else {
return String::new();
};
let after_as = &statement[as_index + 4..];
let Some(star_index) = after_as.find('*') else {
return String::new();
};
after_as[..star_index].trim().to_string()
}
fn sass_module_forward_member_symbol_kind(
source: &str,
span: ParserByteSpanV0,
name: &str,
) -> Option<&'static str> {
let statement = source.get(span.start..span.end)?;
statement
.contains(&format!("${name}"))
.then_some("variable")
}
fn sort_all_composes(summary: &mut ParserIndexComposesFactsV0) {
sort_unique(&mut summary.selectors_with_composes_names);
sort_unique(&mut summary.selectors_with_composes_under_media_names);
sort_unique(&mut summary.selectors_with_composes_under_supports_names);
sort_unique(&mut summary.selectors_with_composes_under_layer_names);
sort_unique(&mut summary.local_selector_names);
sort_unique(&mut summary.imported_selector_names);
sort_unique(&mut summary.global_selector_names);
sort_unique(&mut summary.local_selector_names_under_media);
sort_unique(&mut summary.local_selector_names_under_supports);
sort_unique(&mut summary.local_selector_names_under_layer);
sort_unique(&mut summary.imported_selector_names_under_media);
sort_unique(&mut summary.imported_selector_names_under_supports);
sort_unique(&mut summary.imported_selector_names_under_layer);
sort_unique(&mut summary.global_selector_names_under_media);
sort_unique(&mut summary.global_selector_names_under_supports);
sort_unique(&mut summary.global_selector_names_under_layer);
summary.import_sources.sort();
summary.import_sources_under_media.sort();
summary.import_sources_under_supports.sort();
summary.import_sources_under_layer.sort();
}
fn sort_unique(values: &mut Vec<String>) {
values.sort();
values.dedup();
}
fn block_header_start(source: &str, open: usize, lower_bound: usize) -> usize {
let bytes = source.as_bytes();
let mut index = open;
while index > lower_bound {
index -= 1;
if matches!(bytes[index], b'{' | b'}' | b';') {
return index + 1;
}
}
lower_bound
}
fn matching_brace(source: &str, open: usize, end: usize) -> Option<usize> {
let bytes = source.as_bytes();
let mut depth = 0usize;
let mut index = open;
while index < end {
match bytes[index] {
b'{' => depth += 1,
b'}' => {
depth = depth.saturating_sub(1);
if depth == 0 {
return Some(index);
}
}
_ => {}
}
index += 1;
}
None
}
fn find_next_byte(source: &str, needle: u8, start: usize, end: usize) -> Option<usize> {
source.as_bytes()[start..end]
.iter()
.position(|byte| *byte == needle)
.map(|index| start + index)
}
fn source_span_for_header_piece(source: &str, full_header: &str, piece: &str) -> ParserByteSpanV0 {
if let Some(header_offset) = source.find(full_header)
&& let Some(piece_offset) = full_header.find(piece)
{
let start = header_offset + piece_offset;
return ParserByteSpanV0 {
start,
end: start + piece.len(),
};
}
ParserByteSpanV0 {
start: 0,
end: piece.len(),
}
}
fn byte_span_for_range(range: TextRange) -> ParserByteSpanV0 {
ParserByteSpanV0 {
start: range_start(range),
end: u32::from(range.end()) as usize,
}
}
fn range_start(range: TextRange) -> usize {
u32::from(range.start()) as usize
}
struct SourceLineIndex {
line_starts: Vec<usize>,
}
impl SourceLineIndex {
fn new(source: &str) -> Self {
let mut line_starts = vec![0];
for (index, byte) in source.as_bytes().iter().enumerate() {
if *byte == b'\n' {
line_starts.push(index + 1);
}
}
Self { line_starts }
}
fn position_for_byte_offset(&self, source: &str, byte_offset: usize) -> ParserPositionV0 {
let offset = byte_offset.min(source.len());
let line = self.line_starts.partition_point(|start| *start <= offset);
let line_index = line.saturating_sub(1);
let line_start = self.line_starts.get(line_index).copied().unwrap_or(0);
ParserPositionV0 {
line: line_index,
character: source
.get(line_start..offset)
.map(|text| text.encode_utf16().count())
.unwrap_or_else(|| offset.saturating_sub(line_start)),
}
}
}
fn parser_range_for_byte_span(
source: &str,
line_index: &SourceLineIndex,
span: ParserByteSpanV0,
) -> ParserRangeV0 {
ParserRangeV0 {
start: line_index.position_for_byte_offset(source, span.start),
end: line_index.position_for_byte_offset(source, span.end),
}
}
fn is_sass_parameter_declaration(source: &str, byte_offset: usize) -> bool {
let offset = byte_offset.min(source.len());
let line_start = source[..offset].rfind('\n').map_or(0, |index| index + 1);
let line_end = source[offset..]
.find(['\n', '{'])
.map_or(source.len(), |index| offset + index);
let header = source.get(line_start..line_end).unwrap_or_default();
let relative_offset = offset.saturating_sub(line_start).min(header.len());
let before = &header[..relative_offset];
let last_open = before.rfind('(');
let last_close = before.rfind(')');
let inside_parameter_list = last_open.is_some() && last_open > last_close;
inside_parameter_list && (header.contains("@mixin") || header.contains("@function"))
}
fn bem_suffix_parent_name(name: &str) -> Option<String> {
let marker = [name.rfind("__"), name.rfind("--")]
.into_iter()
.flatten()
.max()?;
(marker > 0).then(|| name[..marker].to_string())
}
fn sorted(values: BTreeSet<String>) -> Vec<String> {
values.into_iter().collect()
}
trait SortVec {
fn tap_sort(self) -> Self;
fn tap_sort_unique(self) -> Self;
}
impl SortVec for Vec<String> {
fn tap_sort(mut self) -> Self {
self.sort();
self
}
fn tap_sort_unique(mut self) -> Self {
self.sort();
self.dedup();
self
}
}
pub fn dialect_for_path(file_path: &str) -> StyleDialect {
if file_path.ends_with(".sass") || file_path.ends_with(".module.sass") {
StyleDialect::Sass
} else if file_path.ends_with(".scss") || file_path.ends_with(".module.scss") {
StyleDialect::Scss
} else if file_path.ends_with(".less") || file_path.ends_with(".module.less") {
StyleDialect::Less
} else {
StyleDialect::Css
}
}
fn dialect_label(dialect: StyleDialect) -> &'static str {
match dialect {
StyleDialect::Css => "css",
StyleDialect::Scss => "scss",
StyleDialect::Sass => "sass",
StyleDialect::Less => "less",
}
}