Allow fields to be used in property sheets
This commit is contained in:
parent
65d1ce8593
commit
9f3134dace
4 changed files with 401 additions and 122 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -420,7 +420,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rsass"
|
||||
version = "0.9.6"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
@ -586,7 +586,7 @@ dependencies = [
|
|||
"rand 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rsass 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rsass 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
@ -697,7 +697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum redox_users 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "214a97e49be64fd2c86f568dd0cb2c757d2cc53de95b273b6ad0a1c908482f26"
|
||||
"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f"
|
||||
"checksum regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4e47a2ed29da7a9e1960e1639e7a982e6edc6d49be308a3b02daf511504a16d1"
|
||||
"checksum rsass 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7a5dde55023a6c19470f7aeb59f75f897d8b80cbe00d61dfcaf7bbbe3de4c0a6"
|
||||
"checksum rsass 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4534cc03040beacd2668621815f26fe57e5b7cfe085790f98e5e87c1612316"
|
||||
"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ serde = "1.0"
|
|||
serde_derive = "1.0"
|
||||
regex-syntax = "0.6.4"
|
||||
regex = "1"
|
||||
rsass = "0.9"
|
||||
rsass = "^0.9.8"
|
||||
|
||||
[dependencies.tree-sitter]
|
||||
version = ">= 0.3.7"
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use crate::error::{Error, Result};
|
|||
use log::info;
|
||||
use rsass;
|
||||
use rsass::sass::Value;
|
||||
use rsass::selectors::SelectorPart;
|
||||
use serde_derive::Serialize;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
|
||||
use std::fmt::{self, Write};
|
||||
|
|
@ -27,11 +27,12 @@ type PropertySetId = usize;
|
|||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
struct SelectorStep {
|
||||
kind: String,
|
||||
is_named: bool,
|
||||
is_immediate: bool,
|
||||
kind: Option<String>,
|
||||
field: Option<String>,
|
||||
child_index: Option<usize>,
|
||||
text_pattern: Option<String>,
|
||||
is_named: Option<bool>,
|
||||
is_immediate: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
|
|
@ -175,6 +176,7 @@ impl Builder {
|
|||
transition_map.insert((
|
||||
PropertyTransitionJSON {
|
||||
kind: step.kind.clone(),
|
||||
field: step.field.clone(),
|
||||
named: step.is_named,
|
||||
index: step.child_index,
|
||||
text: step.text_pattern.clone(),
|
||||
|
|
@ -235,19 +237,11 @@ impl Builder {
|
|||
// first, and in the event of a tie, transitions corresponding to later rules
|
||||
// in the cascade are tried first.
|
||||
transition_list.sort_by(|a, b| {
|
||||
let result = a.0.kind.cmp(&b.0.kind);
|
||||
if result != Ordering::Equal {
|
||||
return result;
|
||||
}
|
||||
let result = a.0.named.cmp(&b.0.named);
|
||||
if result != Ordering::Equal {
|
||||
return result;
|
||||
}
|
||||
let result = transition_specificity(&b.0).cmp(&transition_specificity(&a.0));
|
||||
if result != Ordering::Equal {
|
||||
return result;
|
||||
}
|
||||
b.1.cmp(&a.1)
|
||||
(transition_specificity(&b.0).cmp(&transition_specificity(&a.0)))
|
||||
.then_with(|| b.1.cmp(&a.1))
|
||||
.then_with(|| a.0.kind.cmp(&b.0.kind))
|
||||
.then_with(|| a.0.named.cmp(&b.0.named))
|
||||
.then_with(|| a.0.field.cmp(&b.0.field))
|
||||
});
|
||||
|
||||
// Compute the merged properties that apply in the current state.
|
||||
|
|
@ -256,11 +250,7 @@ impl Builder {
|
|||
// rules will override less specific selectors and earlier rules.
|
||||
let mut properties = PropertySet::new();
|
||||
selector_matches.sort_unstable_by(|a, b| {
|
||||
let result = a.specificity.cmp(&b.specificity);
|
||||
if result != Ordering::Equal {
|
||||
return result;
|
||||
}
|
||||
a.rule_id.cmp(&b.rule_id)
|
||||
(a.specificity.cmp(&b.specificity)).then_with(|| a.rule_id.cmp(&b.rule_id))
|
||||
});
|
||||
selector_matches.dedup();
|
||||
for selector_match in selector_matches {
|
||||
|
|
@ -322,6 +312,7 @@ impl Builder {
|
|||
transition.state_id = *replacement;
|
||||
}
|
||||
}
|
||||
state.transitions.dedup();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -356,8 +347,14 @@ impl Builder {
|
|||
}
|
||||
|
||||
fn selector_specificity(selector: &Selector) -> u32 {
|
||||
let mut result = selector.0.len() as u32;
|
||||
let mut result = 0;
|
||||
for step in &selector.0 {
|
||||
if step.kind.is_some() {
|
||||
result += 1;
|
||||
}
|
||||
if step.field.is_some() {
|
||||
result += 1;
|
||||
}
|
||||
if step.child_index.is_some() {
|
||||
result += 1;
|
||||
}
|
||||
|
|
@ -370,6 +367,12 @@ fn selector_specificity(selector: &Selector) -> u32 {
|
|||
|
||||
fn transition_specificity(transition: &PropertyTransitionJSON) -> u32 {
|
||||
let mut result = 0;
|
||||
if transition.kind.is_some() {
|
||||
result += 1;
|
||||
}
|
||||
if transition.field.is_some() {
|
||||
result += 1;
|
||||
}
|
||||
if transition.index.is_some() {
|
||||
result += 1;
|
||||
}
|
||||
|
|
@ -380,19 +383,37 @@ fn transition_specificity(transition: &PropertyTransitionJSON) -> u32 {
|
|||
}
|
||||
|
||||
fn step_matches_transition(step: &SelectorStep, transition: &PropertyTransitionJSON) -> bool {
|
||||
step.kind == transition.kind
|
||||
&& step.is_named == transition.named
|
||||
&& (step.child_index == transition.index || step.child_index.is_none())
|
||||
&& (step.text_pattern == transition.text || step.text_pattern.is_none())
|
||||
step.kind
|
||||
.as_ref()
|
||||
.map_or(true, |kind| transition.kind.as_ref() == Some(kind))
|
||||
&& step
|
||||
.is_named
|
||||
.map_or(true, |named| transition.named == Some(named))
|
||||
&& step
|
||||
.field
|
||||
.as_ref()
|
||||
.map_or(true, |field| transition.field.as_ref() == Some(field))
|
||||
&& step
|
||||
.child_index
|
||||
.map_or(true, |index| transition.index == Some(index))
|
||||
&& step
|
||||
.text_pattern
|
||||
.as_ref()
|
||||
.map_or(true, |text| transition.text.as_ref() == Some(text))
|
||||
}
|
||||
|
||||
impl fmt::Debug for SelectorStep {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "(")?;
|
||||
if self.is_named {
|
||||
write!(f, "{}", self.kind)?;
|
||||
} else {
|
||||
write!(f, "\"{}\"", self.kind)?;
|
||||
if let Some(kind) = &self.kind {
|
||||
if self.is_named.unwrap() {
|
||||
write!(f, "{}", kind)?;
|
||||
} else {
|
||||
write!(f, "[token='{}']", kind)?;
|
||||
}
|
||||
}
|
||||
if let Some(field) = &self.field {
|
||||
write!(f, ".{}", field)?;
|
||||
}
|
||||
if let Some(n) = self.child_index {
|
||||
write!(f, ":nth-child({})", n)?;
|
||||
|
|
@ -416,7 +437,7 @@ impl fmt::Debug for Selector {
|
|||
}
|
||||
write!(f, "{:?}", step)?;
|
||||
}
|
||||
write!(f, "]")?;
|
||||
write!(f, " (specificity: {})]", selector_specificity(self))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -522,52 +543,134 @@ fn parse_sass_items(
|
|||
rsass::Item::Rule(selectors, items) => {
|
||||
let mut full_selectors = Vec::new();
|
||||
for prefix in selector_prefixes {
|
||||
let mut part_string = String::new();
|
||||
let mut next_step_is_immediate = false;
|
||||
for selector in &selectors.s {
|
||||
let mut prefix = prefix.clone();
|
||||
let mut operator_was_immediate: Option<bool> = Some(false);
|
||||
for part in &selector.0 {
|
||||
part_string.clear();
|
||||
write!(&mut part_string, "{}", part).unwrap();
|
||||
let part_string = part_string.trim();
|
||||
if !part_string.is_empty() {
|
||||
if part_string == "&" {
|
||||
continue;
|
||||
} else if part_string.starts_with(":nth-child(") {
|
||||
if let Some(last_step) = prefix.last_mut() {
|
||||
if let Ok(index) = usize::from_str_radix(
|
||||
&part_string[11..(part_string.len() - 1)],
|
||||
10,
|
||||
) {
|
||||
last_step.child_index = Some(index);
|
||||
match part {
|
||||
SelectorPart::BackRef => {
|
||||
operator_was_immediate = None;
|
||||
}
|
||||
SelectorPart::Simple(value) => {
|
||||
if let Some(value) = value.single_raw() {
|
||||
for (i, value) in value.split('.').enumerate() {
|
||||
if value.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let value = value.to_string();
|
||||
check_node_kind(&value)?;
|
||||
if i > 0 {
|
||||
if let Some(immediate) = operator_was_immediate {
|
||||
prefix.push(SelectorStep {
|
||||
kind: None,
|
||||
field: Some(value),
|
||||
is_named: None,
|
||||
child_index: None,
|
||||
text_pattern: None,
|
||||
is_immediate: immediate,
|
||||
})
|
||||
} else {
|
||||
prefix.last_mut().unwrap().field = Some(value);
|
||||
}
|
||||
} else {
|
||||
if let Some(immediate) = operator_was_immediate {
|
||||
prefix.push(SelectorStep {
|
||||
kind: Some(value.to_string()),
|
||||
field: None,
|
||||
child_index: None,
|
||||
text_pattern: None,
|
||||
is_named: Some(true),
|
||||
is_immediate: immediate,
|
||||
});
|
||||
} else {
|
||||
return Err(Error(format!("Node type {} must be separated by whitespace or the `>` operator", value)));
|
||||
}
|
||||
}
|
||||
operator_was_immediate = None;
|
||||
}
|
||||
} else {
|
||||
return Err(interpolation_error());
|
||||
}
|
||||
operator_was_immediate = None;
|
||||
}
|
||||
SelectorPart::Attribute { name, val, .. } => {
|
||||
match name.single_raw() {
|
||||
None => return Err(interpolation_error()),
|
||||
Some("text") => {
|
||||
if operator_was_immediate.is_some() {
|
||||
return Err(Error("The `text` attribute must be used in combination with a node type or field".to_string()));
|
||||
}
|
||||
if let Some(last_step) = prefix.last_mut() {
|
||||
last_step.text_pattern =
|
||||
Some(get_string_value(val.to_string())?)
|
||||
}
|
||||
}
|
||||
Some("token") => {
|
||||
if let Some(immediate) = operator_was_immediate {
|
||||
prefix.push(SelectorStep {
|
||||
kind: Some(get_string_value(val.to_string())?),
|
||||
field: None,
|
||||
is_named: Some(false),
|
||||
child_index: None,
|
||||
text_pattern: None,
|
||||
is_immediate: immediate,
|
||||
});
|
||||
operator_was_immediate = None;
|
||||
} else {
|
||||
return Err(Error("The `token` attribute canot be used in combination with a node type".to_string()));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(Error(format!(
|
||||
"Unsupported attribute {}",
|
||||
part
|
||||
)));
|
||||
}
|
||||
}
|
||||
} else if part_string.starts_with("[text=") {
|
||||
if let Some(last_step) = prefix.last_mut() {
|
||||
last_step.text_pattern = Some(
|
||||
part_string[7..(part_string.len() - 2)].to_string(),
|
||||
)
|
||||
}
|
||||
SelectorPart::PseudoElement { .. } => {
|
||||
return Err(Error(
|
||||
"Pseudo elements are not supported".to_string(),
|
||||
));
|
||||
}
|
||||
SelectorPart::Pseudo { name, arg } => match name.single_raw() {
|
||||
None => return Err(interpolation_error()),
|
||||
Some("nth-child") => {
|
||||
if let Some(arg) = arg {
|
||||
let mut arg_str = String::new();
|
||||
write!(&mut arg_str, "{}", arg).unwrap();
|
||||
if let Some(last_step) = prefix.last_mut() {
|
||||
if let Ok(i) = usize::from_str_radix(&arg_str, 10) {
|
||||
last_step.child_index = Some(i);
|
||||
} else {
|
||||
return Err(Error(format!(
|
||||
"Invalid child index {}",
|
||||
arg
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(Error(format!(
|
||||
"Unsupported pseudo-class {}",
|
||||
part
|
||||
)));
|
||||
}
|
||||
},
|
||||
SelectorPart::Descendant => {
|
||||
operator_was_immediate = Some(false);
|
||||
}
|
||||
SelectorPart::RelOp(operator) => {
|
||||
let operator = *operator as char;
|
||||
if operator == '>' {
|
||||
operator_was_immediate = Some(true);
|
||||
} else {
|
||||
return Err(Error(format!(
|
||||
"Unsupported operator {}",
|
||||
operator
|
||||
)));
|
||||
}
|
||||
} else if part_string == ">" {
|
||||
next_step_is_immediate = true;
|
||||
} else if part_string.starts_with("[token=") {
|
||||
prefix.push(SelectorStep {
|
||||
kind: part_string[8..(part_string.len() - 2)].to_string(),
|
||||
is_named: false,
|
||||
child_index: None,
|
||||
text_pattern: None,
|
||||
is_immediate: next_step_is_immediate,
|
||||
});
|
||||
next_step_is_immediate = false;
|
||||
} else {
|
||||
prefix.push(SelectorStep {
|
||||
kind: part_string.to_string(),
|
||||
is_named: true,
|
||||
child_index: None,
|
||||
text_pattern: None,
|
||||
is_immediate: next_step_is_immediate,
|
||||
});
|
||||
next_step_is_immediate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -596,7 +699,7 @@ fn parse_sass_value(value: &Value) -> Result<PropertyValue> {
|
|||
if let Some(s) = s.single_raw() {
|
||||
Ok(PropertyValue::String(s.to_string()))
|
||||
} else {
|
||||
Err(Error("String interpolation is not supported".to_string()))
|
||||
Err(interpolation_error())
|
||||
}
|
||||
}
|
||||
Value::Call(name, raw_args) => {
|
||||
|
|
@ -665,13 +768,36 @@ fn resolve_path(base: &Path, p: &str) -> Result<PathBuf> {
|
|||
Err(Error(format!("Could not resolve import path `{}`", p)))
|
||||
}
|
||||
|
||||
fn check_node_kind(name: &String) -> Result<()> {
|
||||
for c in name.chars() {
|
||||
if !c.is_alphanumeric() && c != '_' {
|
||||
return Err(Error(format!("Invalid identifier '{}'", name)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_string_value(mut s: String) -> Result<String> {
|
||||
if s.starts_with("'") && s.ends_with("'") || s.starts_with('"') && s.ends_with('"') {
|
||||
s.pop();
|
||||
s.remove(0);
|
||||
Ok(s)
|
||||
} else {
|
||||
Err(Error(format!("Unsupported string literal {}", s)))
|
||||
}
|
||||
}
|
||||
|
||||
fn interpolation_error() -> Error {
|
||||
Error("String interpolation is not supported".to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use regex::Regex;
|
||||
|
||||
#[test]
|
||||
fn test_immediate_child_and_descendant_selectors() {
|
||||
fn test_properties_immediate_child_and_descendant_selectors() {
|
||||
let sheet = generate_property_sheet(
|
||||
"foo.css",
|
||||
"
|
||||
|
|
@ -776,7 +902,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_attribute() {
|
||||
fn test_properties_text_attribute() {
|
||||
let sheet = generate_property_sheet(
|
||||
"foo.css",
|
||||
"
|
||||
|
|
@ -800,26 +926,93 @@ mod tests {
|
|||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("f1", true, 0)], "abc"),
|
||||
*query(&sheet, vec![("f1", None, true, 0)], "abc"),
|
||||
props(&[("color", "red")])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("f1", true, 0)], "Abc"),
|
||||
*query(&sheet, vec![("f1", None, true, 0)], "Abc"),
|
||||
props(&[("color", "green")])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("f1", true, 0)], "AB_CD"),
|
||||
*query(&sheet, vec![("f1", None, true, 0)], "AB_CD"),
|
||||
props(&[("color", "blue")])
|
||||
);
|
||||
assert_eq!(*query(&sheet, vec![("f2", true, 0)], "Abc"), props(&[]));
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("f2", true, 0)], "ABC"),
|
||||
*query(&sheet, vec![("f2", None, true, 0)], "Abc"),
|
||||
props(&[])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("f2", None, true, 0)], "ABC"),
|
||||
props(&[("color", "purple")])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cascade_ordering_as_tie_breaker() {
|
||||
fn test_properties_with_fields() {
|
||||
let sheet = generate_property_sheet(
|
||||
"foo.css",
|
||||
"
|
||||
a {
|
||||
color: red;
|
||||
&.x {
|
||||
color: green;
|
||||
b {
|
||||
color: blue;
|
||||
&.y { color: yellow; }
|
||||
}
|
||||
}
|
||||
b { color: orange; }
|
||||
b.y { color: indigo; }
|
||||
}
|
||||
.x { color: violet; }
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("a", None, true, 0)], ""),
|
||||
props(&[("color", "red")])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("a", Some("x"), true, 0)], ""),
|
||||
props(&[("color", "green")])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(
|
||||
&sheet,
|
||||
vec![("a", Some("x"), true, 0), ("b", None, true, 0)],
|
||||
""
|
||||
),
|
||||
props(&[("color", "blue")])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(
|
||||
&sheet,
|
||||
vec![("a", Some("x"), true, 0), ("b", Some("y"), true, 0)],
|
||||
""
|
||||
),
|
||||
props(&[("color", "yellow")])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("b", Some("x"), true, 0)], ""),
|
||||
props(&[("color", "violet")])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("a", None, true, 0), ("b", None, true, 0)], ""),
|
||||
props(&[("color", "orange")])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(
|
||||
&sheet,
|
||||
vec![("a", None, true, 0), ("b", Some("y"), true, 0)],
|
||||
""
|
||||
),
|
||||
props(&[("color", "indigo")])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_properties_cascade_ordering_as_tie_breaker() {
|
||||
let sheet = generate_property_sheet(
|
||||
"foo.css",
|
||||
"
|
||||
|
|
@ -832,29 +1025,49 @@ mod tests {
|
|||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("f1", true, 0), ("f2", true, 0)], "x"),
|
||||
*query(
|
||||
&sheet,
|
||||
vec![("f1", None, true, 0), ("f2", None, true, 0)],
|
||||
"x"
|
||||
),
|
||||
props(&[])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("f1", true, 0), ("f2", true, 1)], "x"),
|
||||
*query(
|
||||
&sheet,
|
||||
vec![("f1", None, true, 0), ("f2", None, true, 1)],
|
||||
"x"
|
||||
),
|
||||
props(&[("color", "red")])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("f1", true, 1), ("f2", true, 1)], "x"),
|
||||
*query(
|
||||
&sheet,
|
||||
vec![("f1", None, true, 1), ("f2", None, true, 1)],
|
||||
"x"
|
||||
),
|
||||
props(&[("color", "green")])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("f1", true, 1), ("f2", true, 1)], "a"),
|
||||
*query(
|
||||
&sheet,
|
||||
vec![("f1", None, true, 1), ("f2", None, true, 1)],
|
||||
"a"
|
||||
),
|
||||
props(&[("color", "blue")])
|
||||
);
|
||||
assert_eq!(
|
||||
*query(&sheet, vec![("f1", true, 1), ("f2", true, 1)], "ab"),
|
||||
*query(
|
||||
&sheet,
|
||||
vec![("f1", None, true, 1), ("f2", None, true, 1)],
|
||||
"ab"
|
||||
),
|
||||
props(&[("color", "violet")])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_css_function_calls() {
|
||||
fn test_properties_css_function_calls() {
|
||||
let sheet = generate_property_sheet(
|
||||
"foo.css",
|
||||
"
|
||||
|
|
@ -891,7 +1104,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_array_by_declaring_property_multiple_times() {
|
||||
fn test_properties_array_by_declaring_property_multiple_times() {
|
||||
let sheet = generate_property_sheet(
|
||||
"foo.css",
|
||||
"
|
||||
|
|
@ -937,25 +1150,26 @@ mod tests {
|
|||
) -> &'a PropertySet {
|
||||
query(
|
||||
sheet,
|
||||
node_stack.into_iter().map(|s| (s, true, 0)).collect(),
|
||||
node_stack.into_iter().map(|s| (s, None, true, 0)).collect(),
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
fn query<'a>(
|
||||
sheet: &'a PropertySheetJSON,
|
||||
node_stack: Vec<(&'static str, bool, usize)>,
|
||||
node_stack: Vec<(&'static str, Option<&'static str>, bool, usize)>,
|
||||
leaf_text: &str,
|
||||
) -> &'a PropertySet {
|
||||
let mut state_id = 0;
|
||||
for (kind, is_named, child_index) in node_stack {
|
||||
for (kind, field, is_named, child_index) in node_stack {
|
||||
let state = &sheet.states[state_id];
|
||||
state_id = state
|
||||
.transitions
|
||||
.iter()
|
||||
.find(|transition| {
|
||||
transition.kind == kind
|
||||
&& transition.named == is_named
|
||||
transition.kind.as_ref().map_or(true, |k| k == kind)
|
||||
&& transition.named.map_or(true, |n| n == is_named)
|
||||
&& transition.field.as_ref().map_or(true, |f| field == Some(f))
|
||||
&& transition.index.map_or(true, |index| index == child_index)
|
||||
&& (transition
|
||||
.text
|
||||
|
|
|
|||
|
|
@ -62,8 +62,15 @@ struct PropertyTransition {
|
|||
text_regex_index: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
enum NodeId {
|
||||
Kind(u16),
|
||||
Field(u16),
|
||||
KindAndField(u16, u16),
|
||||
}
|
||||
|
||||
struct PropertyState {
|
||||
transitions: HashMap<u16, Vec<PropertyTransition>>,
|
||||
transitions: HashMap<NodeId, Vec<PropertyTransition>>,
|
||||
property_set_id: usize,
|
||||
default_next_state_id: usize,
|
||||
}
|
||||
|
|
@ -83,11 +90,15 @@ pub struct PropertySheet<P = HashMap<String, String>> {
|
|||
#[derive(Debug, Deserialize, Serialize, Hash, PartialEq, Eq)]
|
||||
pub struct PropertyTransitionJSON {
|
||||
#[serde(rename = "type")]
|
||||
pub kind: String,
|
||||
pub named: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub kind: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub named: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub index: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub field: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub text: Option<String>,
|
||||
pub state_id: usize,
|
||||
}
|
||||
|
|
@ -137,6 +148,22 @@ impl Language {
|
|||
pub fn node_kind_is_named(&self, id: u16) -> bool {
|
||||
unsafe { ffi::ts_language_symbol_type(self.0, id) == ffi::TSSymbolType_TSSymbolTypeRegular }
|
||||
}
|
||||
|
||||
pub fn field_id_for_name(&self, field_name: impl AsRef<[u8]>) -> Option<u16> {
|
||||
let field_name = field_name.as_ref();
|
||||
let id = unsafe {
|
||||
ffi::ts_language_field_id_for_name(
|
||||
self.0,
|
||||
field_name.as_ptr() as *const c_char,
|
||||
field_name.len() as u32,
|
||||
)
|
||||
};
|
||||
if id == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Language {}
|
||||
|
|
@ -657,7 +684,9 @@ impl<'a, P> TreePropertyCursor<'a, P> {
|
|||
property_sheet,
|
||||
source,
|
||||
};
|
||||
let state = result.next_state(&result.current_state(), result.cursor.node().kind_id(), 0);
|
||||
let kind_id = result.cursor.node().kind_id();
|
||||
let field_id = result.cursor.field_id();
|
||||
let state = result.next_state(&result.current_state(), kind_id, field_id, 0);
|
||||
result.state_stack.push(state);
|
||||
result
|
||||
}
|
||||
|
|
@ -676,7 +705,8 @@ impl<'a, P> TreePropertyCursor<'a, P> {
|
|||
let next_state_id = {
|
||||
let state = &self.current_state();
|
||||
let kind_id = self.cursor.node().kind_id();
|
||||
self.next_state(state, kind_id, child_index)
|
||||
let field_id = self.cursor.field_id();
|
||||
self.next_state(state, kind_id, field_id, child_index)
|
||||
};
|
||||
self.state_stack.push(next_state_id);
|
||||
self.child_index_stack.push(child_index);
|
||||
|
|
@ -693,7 +723,8 @@ impl<'a, P> TreePropertyCursor<'a, P> {
|
|||
let next_state_id = {
|
||||
let state = &self.current_state();
|
||||
let kind_id = self.cursor.node().kind_id();
|
||||
self.next_state(state, kind_id, child_index)
|
||||
let field_id = self.cursor.field_id();
|
||||
self.next_state(state, kind_id, field_id, child_index)
|
||||
};
|
||||
self.state_stack.push(next_state_id);
|
||||
self.child_index_stack.push(child_index);
|
||||
|
|
@ -717,12 +748,25 @@ impl<'a, P> TreePropertyCursor<'a, P> {
|
|||
&self,
|
||||
state: &PropertyState,
|
||||
node_kind_id: u16,
|
||||
node_field_id: Option<u16>,
|
||||
node_child_index: usize,
|
||||
) -> usize {
|
||||
state
|
||||
.transitions
|
||||
.get(&node_kind_id)
|
||||
.and_then(|transitions| {
|
||||
let keys;
|
||||
let key_count;
|
||||
if let Some(node_field_id) = node_field_id {
|
||||
key_count = 3;
|
||||
keys = [
|
||||
NodeId::KindAndField(node_kind_id, node_field_id),
|
||||
NodeId::Field(node_field_id),
|
||||
NodeId::Kind(node_kind_id),
|
||||
];
|
||||
} else {
|
||||
key_count = 1;
|
||||
keys = [NodeId::Kind(node_kind_id); 3];
|
||||
}
|
||||
|
||||
for key in &keys[0..key_count] {
|
||||
if let Some(transitions) = state.transitions.get(key) {
|
||||
for transition in transitions.iter() {
|
||||
if let Some(text_regex_index) = transition.text_regex_index {
|
||||
let node = self.cursor.node();
|
||||
|
|
@ -740,11 +784,12 @@ impl<'a, P> TreePropertyCursor<'a, P> {
|
|||
}
|
||||
}
|
||||
|
||||
return Some(transition.state_id);
|
||||
return transition.state_id;
|
||||
}
|
||||
None
|
||||
})
|
||||
.unwrap_or(state.default_next_state_id)
|
||||
}
|
||||
}
|
||||
|
||||
state.default_next_state_id
|
||||
}
|
||||
|
||||
fn current_state(&self) -> &PropertyState {
|
||||
|
|
@ -848,18 +893,38 @@ impl<P> PropertySheet<P> {
|
|||
None
|
||||
};
|
||||
|
||||
for i in 0..(node_kind_count as u16) {
|
||||
if transition.kind == language.node_kind_for_id(i)
|
||||
&& transition.named == language.node_kind_is_named(i)
|
||||
{
|
||||
let entry = transitions.entry(i).or_insert(Vec::new());
|
||||
entry.push(PropertyTransition {
|
||||
child_index: transition.index,
|
||||
state_id: transition.state_id,
|
||||
text_regex_index,
|
||||
});
|
||||
let kind_id = transition.kind.as_ref().and_then(|kind| {
|
||||
let named = transition.named.unwrap();
|
||||
for i in 0..(node_kind_count as u16) {
|
||||
if kind == language.node_kind_for_id(i)
|
||||
&& named == language.node_kind_is_named(i)
|
||||
{
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
|
||||
let field_id = transition
|
||||
.field
|
||||
.as_ref()
|
||||
.and_then(|field| language.field_id_for_name(&field));
|
||||
|
||||
let key = match (kind_id, field_id) {
|
||||
(Some(kind_id), None) => NodeId::Kind(kind_id),
|
||||
(None, Some(field_id)) => NodeId::Field(field_id),
|
||||
(Some(kind_id), Some(field_id)) => NodeId::KindAndField(kind_id, field_id),
|
||||
(None, None) => continue,
|
||||
};
|
||||
|
||||
transitions
|
||||
.entry(key)
|
||||
.or_insert(Vec::new())
|
||||
.push(PropertyTransition {
|
||||
child_index: transition.index,
|
||||
state_id: transition.state_id,
|
||||
text_regex_index,
|
||||
});
|
||||
}
|
||||
states.push(PropertyState {
|
||||
transitions,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue