Fix breaking of property selector specificity ties
This commit is contained in:
parent
91fb64984f
commit
2b6857bb45
2 changed files with 49 additions and 23 deletions
|
|
@ -74,6 +74,43 @@ struct PropertySelectorMatch {
|
|||
}
|
||||
};
|
||||
|
||||
struct PropertyTransitionEntry {
|
||||
PropertyTransition transition;
|
||||
unsigned latest_matching_rule_id;
|
||||
|
||||
unsigned specificity() const {
|
||||
return
|
||||
(transition.index == -1 ? 0 : 1) +
|
||||
(transition.text_pattern.empty() ? 0 : 1);
|
||||
}
|
||||
|
||||
// When using the final state machine, the runtime library computes
|
||||
// a node's property by descending from the root of the syntax
|
||||
// tree to that node. For each ancestor node on the way, it should
|
||||
// update its state using the *first* matching entry of the
|
||||
// `transitions` list. Therefore, the order of the transitions
|
||||
// must match the normal tie-breaking rules of CSS.
|
||||
bool operator<(const PropertyTransitionEntry &other) const {
|
||||
// If two transitions match different node types, they can't
|
||||
// both match a given node, so their order is arbitrary.
|
||||
if (transition.type < other.transition.type) return true;
|
||||
if (transition.type > other.transition.type) return false;
|
||||
if (transition.named && !other.transition.named) return true;
|
||||
if (!transition.named && other.transition.named) return false;
|
||||
|
||||
// More specific transitions should be considered before less
|
||||
// specific ones.
|
||||
if (specificity() > other.specificity()) return true;
|
||||
if (specificity() < other.specificity()) return false;
|
||||
|
||||
// If there are two transitions with a specificity tie (e.g. one
|
||||
// with an `:nth-child` pseudo-class and a one with a `:text`
|
||||
// pseudo-class), then the one whose matching properties appeared
|
||||
// later in the cascade should be considered first.
|
||||
return latest_matching_rule_id > other.latest_matching_rule_id;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace build_tables
|
||||
} // namespace tree_sitter
|
||||
|
||||
|
|
@ -303,9 +340,11 @@ struct PropertyTableBuilder {
|
|||
|
||||
// For each element that follows an item in this set,
|
||||
// compute the next item set after descending through that element.
|
||||
vector<PropertyTransitionEntry> transition_list;
|
||||
for (auto &pair : transitions) {
|
||||
PropertyTransition transition = pair.first;
|
||||
PropertyItemSet &next_item_set = pair.second;
|
||||
unsigned latest_matching_rule_id = 0;
|
||||
|
||||
for (const PropertyItem &item : item_set.entries) {
|
||||
const PropertySelectorStep *next_step = next_step_for_item(item);
|
||||
|
|
@ -318,6 +357,12 @@ struct PropertyTableBuilder {
|
|||
PropertyItem next_item = item;
|
||||
next_item.step_id++;
|
||||
next_item_set.entries.insert(next_item);
|
||||
|
||||
// If the item is at the end of its selector, record its rule id
|
||||
// so that it can be used when sorting the transitions.
|
||||
if (!next_step_for_item(next_item) && next_item.rule_id > latest_matching_rule_id) {
|
||||
latest_matching_rule_id = item.rule_id;
|
||||
}
|
||||
}
|
||||
|
||||
// If the element does not match, and the item is in the middle
|
||||
|
|
@ -330,11 +375,13 @@ struct PropertyTableBuilder {
|
|||
}
|
||||
|
||||
transition.state_id = add_state(next_item_set);
|
||||
result.states[state_id].transitions.push_back(transition);
|
||||
transition_list.push_back(PropertyTransitionEntry {transition, latest_matching_rule_id});
|
||||
}
|
||||
|
||||
auto &transition_list = result.states[state_id].transitions;
|
||||
std::sort(transition_list.begin(), transition_list.end());
|
||||
for (auto &entry : transition_list) {
|
||||
result.states[state_id].transitions.push_back(entry.transition);
|
||||
}
|
||||
|
||||
// Compute the default successor item set - the item set that
|
||||
// we should advance to if the next element doesn't match any
|
||||
|
|
|
|||
|
|
@ -23,27 +23,6 @@ struct PropertyTransition {
|
|||
text_pattern == other.text_pattern &&
|
||||
state_id == other.state_id;
|
||||
}
|
||||
|
||||
bool operator<(const PropertyTransition &other) const {
|
||||
if (type < other.type) return true;
|
||||
if (type > other.type) return false;
|
||||
if (named && !other.named) return true;
|
||||
if (!named && other.named) return false;
|
||||
|
||||
// The lack of a specific child index is represented as -1.
|
||||
// It should be sorted *after* transitions with a specific
|
||||
// child index.
|
||||
if (index > other.index) return true;
|
||||
if (index < other.index) return false;
|
||||
|
||||
// The lack of a text pattern is represented as the empty string.
|
||||
// This should be sorted *after* transitions with a specific
|
||||
// text pattern.
|
||||
if (text_pattern.size() > other.text_pattern.size()) return true;
|
||||
if (text_pattern.size() < other.text_pattern.size()) return false;
|
||||
|
||||
return state_id < other.state_id;
|
||||
}
|
||||
};
|
||||
|
||||
struct PropertyState {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue