Skip to content

Commit

Permalink
rework attribute selector matching to not use regexes (sveltejs#1710)
Browse files Browse the repository at this point in the history
  • Loading branch information
Conduitry committed Aug 9, 2019
1 parent 2ef004e commit b584142
Showing 1 changed file with 18 additions and 12 deletions.
30 changes: 18 additions & 12 deletions src/compiler/compile/css/Selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,21 @@ function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: Node, sta
return true;
}

const operators = {
'=' : (value: string, flags: string) => new RegExp(`^${value}$`, flags),
'~=': (value: string, flags: string) => new RegExp(`\\b${value}\\b`, flags),
'|=': (value: string, flags: string) => new RegExp(`^${value}(-.+)?$`, flags),
'^=': (value: string, flags: string) => new RegExp(`^${value}`, flags),
'$=': (value: string, flags: string) => new RegExp(`${value}$`, flags),
'*=': (value: string, flags: string) => new RegExp(value, flags)
};
function test_attribute(operator, expected_value, case_insensitive, value) {
if (case_insensitive) {
expected_value = expected_value.toLowerCase();
value = value.toLowerCase();
}
switch (operator) {
case '=': return value === expected_value;
case '~=': return ` ${value} `.includes(` ${expected_value} `);
case '|=': return `${value}-`.startsWith(`${expected_value}-`);
case '^=': return value.startsWith(expected_value);
case '$=': return value.endsWith(expected_value);
case '*=': return value.includes(expected_value);
default: throw new Error(`this shouldn't happen`);
}
}

function attribute_matches(node: Node, name: string, expected_value: string, operator: string, case_insensitive: boolean) {
const spread = node.attributes.find(attr => attr.type === 'Spread');
Expand All @@ -227,18 +234,17 @@ function attribute_matches(node: Node, name: string, expected_value: string, ope
if (attr.chunks.length > 1) return true;
if (!expected_value) return true;

const pattern = operators[operator](expected_value, case_insensitive ? 'i' : '');
const value = attr.chunks[0];

if (!value) return false;
if (value.type === 'Text') return pattern.test(value.data);
if (value.type === 'Text') return test_attribute(operator, expected_value, case_insensitive, value.data);

const possible_values = new Set();
gather_possible_values(value.node, possible_values);
if (possible_values.has(UNKNOWN)) return true;

for (const x of Array.from(possible_values)) { // TypeScript for-of is slightly unlike JS
if (pattern.test(x)) return true;
for (const value of possible_values) {
if (test_attribute(operator, expected_value, case_insensitive, value)) return true;
}

return false;
Expand Down

0 comments on commit b584142

Please sign in to comment.