diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts index ab19ebd1e1..d99af7a110 100644 --- a/src/compiler/compile/css/Selector.ts +++ b/src/compiler/compile/css/Selector.ts @@ -3,6 +3,7 @@ import Stylesheet from './Stylesheet'; import { gather_possible_values, UNKNOWN } from './gather_possible_values'; import { CssNode } from './interfaces'; import Component from '../Component'; +import Element from '../nodes/Element'; enum BlockAppliesToNode { NotPossible, @@ -34,8 +35,8 @@ export default class Selector { this.used = this.blocks[0].global; } - apply(node: CssNode, stack: CssNode[]) { - const to_encapsulate: CssNode[] = []; + apply(node: Element, stack: Element[]) { + const to_encapsulate: any[] = []; apply_selector(this.local_blocks.slice(), node, stack.slice(), to_encapsulate); @@ -132,7 +133,7 @@ export default class Selector { } } -function apply_selector(blocks: Block[], node: CssNode, stack: CssNode[], to_encapsulate: any[]): boolean { +function apply_selector(blocks: Block[], node: Element, stack: Element[], to_encapsulate: any[]): boolean { const block = blocks.pop(); if (!block) return false; @@ -259,16 +260,84 @@ function attribute_matches(node: CssNode, name: string, expected_value: string, const attr = node.attributes.find((attr: CssNode) => attr.name === name); if (!attr) return false; if (attr.is_true) return operator === null; - if (attr.chunks.length > 1) return true; if (!expected_value) return true; - const value = attr.chunks[0]; - - if (!value) return false; - if (value.type === 'Text') return test_attribute(operator, expected_value, case_insensitive, value.data); + if (attr.chunks.length === 1) { + const value = attr.chunks[0]; + if (!value) return false; + 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); + + let prev_values = []; + for (const chunk of attr.chunks) { + const current_possible_values = new Set(); + if (chunk.type === 'Text') { + current_possible_values.add(chunk.data); + } else { + gather_possible_values(chunk.node, current_possible_values); + } + + // impossible to find out all combinations + if (current_possible_values.has(UNKNOWN)) return true; + + if (prev_values.length > 0) { + const start_with_space = []; + const remaining = []; + current_possible_values.forEach((current_possible_value: string) => { + if (/^\s/.test(current_possible_value)) { + start_with_space.push(current_possible_value); + } else { + remaining.push(current_possible_value); + } + }); + + if (remaining.length > 0) { + if (start_with_space.length > 0) { + prev_values.forEach(prev_value => possible_values.add(prev_value)); + } + + const combined = []; + prev_values.forEach((prev_value: string) => { + remaining.forEach((value: string) => { + combined.push(prev_value + value); + }); + }); + prev_values = combined; + + start_with_space.forEach((value: string) => { + if (/\s$/.test(value)) { + possible_values.add(value); + } else { + prev_values.push(value); + } + }); + continue; + } else { + prev_values.forEach(prev_value => possible_values.add(prev_value)); + prev_values = []; + } + } + + current_possible_values.forEach((current_possible_value: string) => { + if (/\s$/.test(current_possible_value)) { + possible_values.add(current_possible_value); + } else { + prev_values.push(current_possible_value); + } + }); + if (prev_values.length < current_possible_values.size) { + prev_values.push(' '); + } + + if (prev_values.length > 20) { + // might grow exponentially, bail out + return true; + } + } + prev_values.forEach(prev_value => possible_values.add(prev_value)); + if (possible_values.has(UNKNOWN)) return true; for (const value of possible_values) { diff --git a/test/css/samples/unused-selector-string-concat/_config.js b/test/css/samples/unused-selector-string-concat/_config.js new file mode 100644 index 0000000000..81318fd3ac --- /dev/null +++ b/test/css/samples/unused-selector-string-concat/_config.js @@ -0,0 +1,143 @@ +export default { + warnings: [ + { + code: 'css-unused-selector', + message: 'Unused CSS selector', + frame: + ` 9: `, + start: { line: 28, column: 2, character: 595 }, + end: { line: 28, column: 9, character: 602 }, + pos: 595, + }, + ], +}; diff --git a/test/css/samples/unused-selector-string-concat/expected.css b/test/css/samples/unused-selector-string-concat/expected.css new file mode 100644 index 0000000000..756c2eecae --- /dev/null +++ b/test/css/samples/unused-selector-string-concat/expected.css @@ -0,0 +1 @@ +.foo.svelte-xyz{color:red}.foocc.svelte-xyz{color:red}.aa.svelte-xyz{color:red}.bb.svelte-xyz{color:red}.cc.svelte-xyz{color:red}.dd.svelte-xyz{color:red}.aabar.svelte-xyz{color:red}.fooddbar.svelte-xyz{color:red}.baz.svelte-xyz{color:red} \ No newline at end of file diff --git a/test/css/samples/unused-selector-string-concat/input.svelte b/test/css/samples/unused-selector-string-concat/input.svelte new file mode 100644 index 0000000000..0f69463e78 --- /dev/null +++ b/test/css/samples/unused-selector-string-concat/input.svelte @@ -0,0 +1,29 @@ + + +
+ some stuff +
+ + \ No newline at end of file diff --git a/test/css/samples/unused-selector-ternary-bailed/_config.js b/test/css/samples/unused-selector-ternary-bailed/_config.js new file mode 100644 index 0000000000..e5f82e4a85 --- /dev/null +++ b/test/css/samples/unused-selector-ternary-bailed/_config.js @@ -0,0 +1,3 @@ +export default { + warnings: [], +}; diff --git a/test/css/samples/unused-selector-ternary-bailed/expected.css b/test/css/samples/unused-selector-ternary-bailed/expected.css new file mode 100644 index 0000000000..042d33f3cc --- /dev/null +++ b/test/css/samples/unused-selector-ternary-bailed/expected.css @@ -0,0 +1 @@ +.thing.svelte-xyz{color:blue}.active.svelte-xyz{color:blue}.thing.active.svelte-xyz{color:blue}.hover.svelte-xyz{color:blue}.hover.unused.svelte-xyz{color:blue}.unused.svelte-xyz{color:blue} \ No newline at end of file diff --git a/test/css/samples/unused-selector-ternary-bailed/input.svelte b/test/css/samples/unused-selector-ternary-bailed/input.svelte new file mode 100644 index 0000000000..f9af44ec8b --- /dev/null +++ b/test/css/samples/unused-selector-ternary-bailed/input.svelte @@ -0,0 +1,18 @@ + + +
+ some stuff +
+ + \ No newline at end of file diff --git a/test/css/samples/unused-selector-ternary-concat/_config.js b/test/css/samples/unused-selector-ternary-concat/_config.js new file mode 100644 index 0000000000..5015fccb25 --- /dev/null +++ b/test/css/samples/unused-selector-ternary-concat/_config.js @@ -0,0 +1,25 @@ +export default { + warnings: [ + { + code: 'css-unused-selector', + end: { + character: 205, + column: 9, + line: 14, + }, + frame: ` + 12: .thing.active {color: blue;} + 13: + 14: .unused {color: blue;} + ^ + 15: `, + message: 'Unused CSS selector', + pos: 198, + start: { + character: 198, + column: 2, + line: 14, + }, + }, + ], +}; diff --git a/test/css/samples/unused-selector-ternary-concat/expected.css b/test/css/samples/unused-selector-ternary-concat/expected.css new file mode 100644 index 0000000000..8142a20e83 --- /dev/null +++ b/test/css/samples/unused-selector-ternary-concat/expected.css @@ -0,0 +1 @@ +.thing.svelte-xyz{color:blue}.active.svelte-xyz{color:blue}.thing.active.svelte-xyz{color:blue} \ No newline at end of file diff --git a/test/css/samples/unused-selector-ternary-concat/input.svelte b/test/css/samples/unused-selector-ternary-concat/input.svelte new file mode 100644 index 0000000000..8db417c928 --- /dev/null +++ b/test/css/samples/unused-selector-ternary-concat/input.svelte @@ -0,0 +1,15 @@ + + +
+ some stuff +
+ + \ No newline at end of file diff --git a/test/css/samples/unused-selector-ternary-nested/_config.js b/test/css/samples/unused-selector-ternary-nested/_config.js new file mode 100644 index 0000000000..afee5ac822 --- /dev/null +++ b/test/css/samples/unused-selector-ternary-nested/_config.js @@ -0,0 +1,31 @@ +export default { + warnings: [ + { + code: 'css-unused-selector', + message: 'Unused CSS selector', + frame: ` + 13: .thing.active {color: blue;} + 14: .hover { color: blue; } + 15: .hover.unused { color: blue; } + ^ + 16: + 17: .unused {color: blue;}`, + start: { line: 15, column: 2, character: 261 }, + end: { line: 15, column: 15, character: 274 }, + pos: 261, + }, + { + code: 'css-unused-selector', + message: 'Unused CSS selector', + frame: ` + 15: .hover.unused { color: blue; } + 16: + 17: .unused {color: blue;} + ^ + 18: `, + start: { line: 17, column: 2, character: 295 }, + end: { line: 17, column: 9, character: 302 }, + pos: 295, + }, + ], +}; diff --git a/test/css/samples/unused-selector-ternary-nested/expected.css b/test/css/samples/unused-selector-ternary-nested/expected.css new file mode 100644 index 0000000000..85a95ad23e --- /dev/null +++ b/test/css/samples/unused-selector-ternary-nested/expected.css @@ -0,0 +1 @@ +.thing.svelte-xyz{color:blue}.active.svelte-xyz{color:blue}.thing.active.svelte-xyz{color:blue}.hover.svelte-xyz{color:blue} \ No newline at end of file diff --git a/test/css/samples/unused-selector-ternary-nested/input.svelte b/test/css/samples/unused-selector-ternary-nested/input.svelte new file mode 100644 index 0000000000..a7a8f0e02a --- /dev/null +++ b/test/css/samples/unused-selector-ternary-nested/input.svelte @@ -0,0 +1,18 @@ + + +
+ some stuff +
+ + \ No newline at end of file