|
|
|
@ -15,12 +15,8 @@ enum BlockAppliesToNode {
|
|
|
|
|
UnknownSelectorType
|
|
|
|
|
}
|
|
|
|
|
enum NodeExist {
|
|
|
|
|
Probably,
|
|
|
|
|
Definitely,
|
|
|
|
|
}
|
|
|
|
|
interface ElementAndExist {
|
|
|
|
|
element: Element;
|
|
|
|
|
exist: NodeExist;
|
|
|
|
|
Probably = 1,
|
|
|
|
|
Definitely = 2,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const whitelist_attribute_selector = new Map([
|
|
|
|
@ -212,21 +208,11 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]):
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
} else if (block.combinator.name === '+') {
|
|
|
|
|
const siblings = get_possible_element_siblings(node, true);
|
|
|
|
|
let has_match = false;
|
|
|
|
|
for (const possible_sibling of siblings) {
|
|
|
|
|
if (apply_selector(blocks.slice(), possible_sibling.element, to_encapsulate)) {
|
|
|
|
|
to_encapsulate.push({ node, block });
|
|
|
|
|
has_match = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return has_match;
|
|
|
|
|
} else if (block.combinator.name === '~') {
|
|
|
|
|
const siblings = get_possible_element_siblings(node, false);
|
|
|
|
|
} else if (block.combinator.name === '+' || block.combinator.name === '~') {
|
|
|
|
|
const siblings = get_possible_element_siblings(node, block.combinator.name === '+');
|
|
|
|
|
let has_match = false;
|
|
|
|
|
for (const possible_sibling of siblings) {
|
|
|
|
|
if (apply_selector(blocks.slice(), possible_sibling.element, to_encapsulate)) {
|
|
|
|
|
for (const possible_sibling of siblings.keys()) {
|
|
|
|
|
if (apply_selector(blocks.slice(), possible_sibling, to_encapsulate)) {
|
|
|
|
|
to_encapsulate.push({ node, block });
|
|
|
|
|
has_match = true;
|
|
|
|
|
}
|
|
|
|
@ -415,13 +401,13 @@ function get_element_parent(node: Element): Element | null {
|
|
|
|
|
return parent as Element | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function get_possible_element_siblings(node: INode, adjacent_only: boolean): ElementAndExist[] {
|
|
|
|
|
const result: ElementAndExist[] = [];
|
|
|
|
|
function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map<Element, NodeExist> {
|
|
|
|
|
const result: Map<Element, NodeExist> = new Map();
|
|
|
|
|
let prev: INode = node;
|
|
|
|
|
while (prev = prev.prev) {
|
|
|
|
|
if (prev.type === 'Element') {
|
|
|
|
|
if (!prev.attributes.find(attr => attr.name.toLowerCase() === 'slot')) {
|
|
|
|
|
result.push({ element: prev as Element, exist: NodeExist.Definitely });
|
|
|
|
|
result.set(prev, NodeExist.Definitely);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (adjacent_only) {
|
|
|
|
@ -429,8 +415,9 @@ function get_possible_element_siblings(node: INode, adjacent_only: boolean): Ele
|
|
|
|
|
}
|
|
|
|
|
} else if (prev.type === 'EachBlock' || prev.type === 'IfBlock' || prev.type === 'AwaitBlock') {
|
|
|
|
|
const possible_last_child = get_possible_last_child(prev, adjacent_only);
|
|
|
|
|
result.push(...possible_last_child);
|
|
|
|
|
if (adjacent_only && possible_last_child.find(child => child.exist === NodeExist.Definitely)) {
|
|
|
|
|
|
|
|
|
|
add_to_map(possible_last_child, result);
|
|
|
|
|
if (adjacent_only && has_definite_elements(possible_last_child)) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -438,23 +425,24 @@ function get_possible_element_siblings(node: INode, adjacent_only: boolean): Ele
|
|
|
|
|
|
|
|
|
|
if (!prev || !adjacent_only) {
|
|
|
|
|
let parent: INode = node;
|
|
|
|
|
if (node.type === 'ElseBlock') {
|
|
|
|
|
parent = parent.parent;
|
|
|
|
|
}
|
|
|
|
|
let skip_each_for_last_child = node.type === 'ElseBlock';
|
|
|
|
|
while ((parent = parent.parent) && (parent.type === 'EachBlock' || parent.type === 'IfBlock' || parent.type === 'ElseBlock' || parent.type === 'AwaitBlock')) {
|
|
|
|
|
const possible_siblings = get_possible_element_siblings(parent, adjacent_only);
|
|
|
|
|
result.push(...possible_siblings);
|
|
|
|
|
add_to_map(possible_siblings, result);
|
|
|
|
|
|
|
|
|
|
if (parent.type === 'EachBlock') {
|
|
|
|
|
// first child of each block can select the last child of each block as previous sibling
|
|
|
|
|
for (const each_parent_last_child of get_possible_last_child(parent, adjacent_only)) {
|
|
|
|
|
result.push(each_parent_last_child);
|
|
|
|
|
if (skip_each_for_last_child) {
|
|
|
|
|
skip_each_for_last_child = false;
|
|
|
|
|
} else {
|
|
|
|
|
add_to_map(get_possible_last_child(parent, adjacent_only), result);
|
|
|
|
|
}
|
|
|
|
|
} else if (parent.type === 'ElseBlock') {
|
|
|
|
|
skip_each_for_last_child = true;
|
|
|
|
|
parent = parent.parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (adjacent_only && possible_siblings.find(sibling => sibling.exist === NodeExist.Definitely)) {
|
|
|
|
|
if (adjacent_only && has_definite_elements(possible_siblings)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -463,69 +451,95 @@ function get_possible_element_siblings(node: INode, adjacent_only: boolean): Ele
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjacent_only: boolean): ElementAndExist[] {
|
|
|
|
|
const result = [];
|
|
|
|
|
function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjacent_only: boolean): Map<Element, NodeExist> {
|
|
|
|
|
const result: Map<Element, NodeExist> = new Map();
|
|
|
|
|
|
|
|
|
|
if (block.type === 'EachBlock') {
|
|
|
|
|
const each_result: ElementAndExist[] = loop_child(block.children, adjacent_only);
|
|
|
|
|
const else_result: ElementAndExist[] = block.else ? loop_child(block.else.children, adjacent_only) : [];
|
|
|
|
|
const each_result: Map<Element, NodeExist> = loop_child(block.children, adjacent_only);
|
|
|
|
|
const else_result: Map<Element, NodeExist> = block.else ? loop_child(block.else.children, adjacent_only) : new Map();
|
|
|
|
|
|
|
|
|
|
const not_exhaustive =
|
|
|
|
|
else_result.length === 0 || !else_result.find(result => result.exist === NodeExist.Definitely);
|
|
|
|
|
const not_exhaustive = !has_definite_elements(else_result);
|
|
|
|
|
|
|
|
|
|
if (not_exhaustive) {
|
|
|
|
|
each_result.forEach(result => result.exist = NodeExist.Probably);
|
|
|
|
|
else_result.forEach(result => result.exist = NodeExist.Probably);
|
|
|
|
|
mark_as_probably(each_result);
|
|
|
|
|
mark_as_probably(else_result);
|
|
|
|
|
}
|
|
|
|
|
result.push(...each_result, ...else_result);
|
|
|
|
|
add_to_map(each_result, result);
|
|
|
|
|
add_to_map(else_result, result);
|
|
|
|
|
} else if (block.type === 'IfBlock') {
|
|
|
|
|
const if_result: ElementAndExist[] = loop_child(block.children, adjacent_only);
|
|
|
|
|
const else_result: ElementAndExist[] = block.else ? loop_child(block.else.children, adjacent_only) : [];
|
|
|
|
|
const if_result: Map<Element, NodeExist> = loop_child(block.children, adjacent_only);
|
|
|
|
|
const else_result: Map<Element, NodeExist> = block.else ? loop_child(block.else.children, adjacent_only) : new Map();
|
|
|
|
|
|
|
|
|
|
const not_exhaustive =
|
|
|
|
|
if_result.length === 0 || !if_result.find(result => result.exist === NodeExist.Definitely) ||
|
|
|
|
|
else_result.length === 0 || !else_result.find(result => result.exist === NodeExist.Definitely);
|
|
|
|
|
const not_exhaustive = !has_definite_elements(if_result) || !has_definite_elements(else_result);
|
|
|
|
|
|
|
|
|
|
if (not_exhaustive) {
|
|
|
|
|
if_result.forEach(result => result.exist = NodeExist.Probably);
|
|
|
|
|
else_result.forEach(result => result.exist = NodeExist.Probably);
|
|
|
|
|
mark_as_probably(if_result);
|
|
|
|
|
mark_as_probably(else_result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.push(...if_result, ...else_result);
|
|
|
|
|
add_to_map(if_result, result);
|
|
|
|
|
add_to_map(else_result, result);
|
|
|
|
|
} else if (block.type === 'AwaitBlock') {
|
|
|
|
|
const pending_result: ElementAndExist[] = block.pending ? loop_child(block.pending.children, adjacent_only) : [];
|
|
|
|
|
const then_result: ElementAndExist[] = block.then ? loop_child(block.then.children, adjacent_only) : [];
|
|
|
|
|
const catch_result: ElementAndExist[] = block.catch ? loop_child(block.catch.children, adjacent_only) : [];
|
|
|
|
|
const pending_result: Map<Element, NodeExist> = block.pending ? loop_child(block.pending.children, adjacent_only) : new Map();
|
|
|
|
|
const then_result: Map<Element, NodeExist> = block.then ? loop_child(block.then.children, adjacent_only) : new Map();
|
|
|
|
|
const catch_result: Map<Element, NodeExist> = block.catch ? loop_child(block.catch.children, adjacent_only) : new Map();
|
|
|
|
|
|
|
|
|
|
const not_exhaustive =
|
|
|
|
|
pending_result.length === 0 || !pending_result.find(result => result.exist === NodeExist.Definitely) ||
|
|
|
|
|
then_result.length === 0 || !then_result.find(result => result.exist === NodeExist.Definitely) ||
|
|
|
|
|
catch_result.length === 0 || !catch_result.find(result => result.exist === NodeExist.Definitely);
|
|
|
|
|
const not_exhaustive = !has_definite_elements(pending_result) || !has_definite_elements(then_result) || !has_definite_elements(catch_result);
|
|
|
|
|
|
|
|
|
|
if (not_exhaustive) {
|
|
|
|
|
pending_result.forEach(result => result.exist = NodeExist.Probably);
|
|
|
|
|
then_result.forEach(result => result.exist = NodeExist.Probably);
|
|
|
|
|
catch_result.forEach(result => result.exist = NodeExist.Probably);
|
|
|
|
|
mark_as_probably(pending_result);
|
|
|
|
|
mark_as_probably(then_result);
|
|
|
|
|
mark_as_probably(catch_result);
|
|
|
|
|
}
|
|
|
|
|
result.push(...pending_result,...then_result,...catch_result);
|
|
|
|
|
|
|
|
|
|
add_to_map(pending_result, result);
|
|
|
|
|
add_to_map(then_result, result);
|
|
|
|
|
add_to_map(catch_result, result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function has_definite_elements(result: Map<Element, NodeExist>): boolean {
|
|
|
|
|
if (result.size === 0) return false;
|
|
|
|
|
for (const exist of result.values()) {
|
|
|
|
|
if (exist === NodeExist.Definitely) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function add_to_map(from: Map<Element, NodeExist>, to: Map<Element, NodeExist>) {
|
|
|
|
|
from.forEach((exist, element) => {
|
|
|
|
|
to.set(element, higher_existance(exist, to.get(element)));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function higher_existance(exist1: NodeExist | null, exist2: NodeExist | null): NodeExist {
|
|
|
|
|
if (exist1 === undefined || exist2 === undefined) return exist1 || exist2;
|
|
|
|
|
return exist1 > exist2 ? exist1 : exist2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mark_as_probably(result: Map<Element, NodeExist>) {
|
|
|
|
|
for (const key of result.keys()) {
|
|
|
|
|
result.set(key, NodeExist.Probably);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function loop_child(children: INode[], adjacent_only: boolean) {
|
|
|
|
|
const result = [];
|
|
|
|
|
const result: Map<Element, NodeExist> = new Map();
|
|
|
|
|
for (let i = children.length - 1; i >= 0; i--) {
|
|
|
|
|
const child = children[i];
|
|
|
|
|
if (child.type === 'Element') {
|
|
|
|
|
result.push({ element: child, exist: NodeExist.Definitely });
|
|
|
|
|
result.set(child, NodeExist.Definitely);
|
|
|
|
|
if (adjacent_only) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else if (child.type === 'EachBlock' || child.type === 'IfBlock' || child.type === 'AwaitBlock') {
|
|
|
|
|
const child_result = get_possible_last_child(child, adjacent_only);
|
|
|
|
|
result.push(...child_result);
|
|
|
|
|
if (adjacent_only && child_result.find(child => child.exist === NodeExist.Definitely)) {
|
|
|
|
|
add_to_map(child_result, result);
|
|
|
|
|
if (adjacent_only && has_definite_elements(child_result)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|