pull/7738/head
Rich Harris 8 years ago
parent 2b15bbae91
commit 27050dfd2e

@ -2,10 +2,16 @@ import MagicString from 'magic-string';
import { Validator } from '../validate/index'; import { Validator } from '../validate/index';
import { Node } from '../interfaces'; import { Node } from '../interfaces';
interface Block {
global: boolean;
combinator: Node;
selectors: Node[]
}
export default class Selector { export default class Selector {
node: Node; node: Node;
blocks: any; // TODO blocks: Block[];
parts: Node[]; localBlocks: Block[];
used: boolean; used: boolean;
constructor(node: Node) { constructor(node: Node) {
@ -14,25 +20,18 @@ export default class Selector {
this.blocks = groupSelectors(node); this.blocks = groupSelectors(node);
// take trailing :global(...) selectors out of consideration // take trailing :global(...) selectors out of consideration
let i = node.children.length; let i = this.blocks.length;
while (i > 2) { while (i > 0) {
const last = node.children[i-1]; if (!this.blocks[i - 1].global) break;
const penultimate = node.children[i-2]; i -= 1;
if (last.type === 'PseudoClassSelector' && last.name === 'global') {
i -= 2;
} else {
break;
}
} }
this.parts = node.children.slice(0, i); this.localBlocks = this.blocks.slice(0, i);
this.used = this.blocks[0].global; this.used = this.blocks[0].global;
} }
apply(node: Node, stack: Node[]) { apply(node: Node, stack: Node[]) {
const applies = selectorAppliesTo(this.parts, node, stack.slice()); const applies = selectorAppliesTo(this.localBlocks.slice(), node, stack.slice());
if (applies) { if (applies) {
this.used = true; this.used = true;
@ -45,7 +44,7 @@ export default class Selector {
} }
transform(code: MagicString, attr: string) { transform(code: MagicString, attr: string) {
function encapsulateBlock(block) { function encapsulateBlock(block: Block) {
let i = block.selectors.length; let i = block.selectors.length;
while (i--) { while (i--) {
const selector = block.selectors[i]; const selector = block.selectors[i];
@ -77,9 +76,9 @@ export default class Selector {
this.blocks.forEach((block) => { this.blocks.forEach((block) => {
let i = block.selectors.length; let i = block.selectors.length;
while (i-- > 1) { while (i-- > 1) {
const part = block.selectors[i]; const selector = block.selectors[i];
if (part.type === 'PseudoClassSelector' && part.name === 'global') { if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
validator.error(`:global(...) must be the first element in a compound selector`, part.start); validator.error(`:global(...) must be the first element in a compound selector`, selector.start);
} }
} }
}); });
@ -107,71 +106,67 @@ function isDescendantSelector(selector: Node) {
return selector.type === 'WhiteSpace' || selector.type === 'Combinator'; return selector.type === 'WhiteSpace' || selector.type === 'Combinator';
} }
function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]): boolean { function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[]): boolean {
let i = parts.length; const block = blocks.pop();
if (!block) return false;
if (!node) {
return blocks.every(block => block.global);
}
let i = block.selectors.length;
let j = stack.length; let j = stack.length;
while (i--) { while (i--) {
if (!node) { const selector = block.selectors[i];
return parts.every((part: Node) => {
return part.type === 'Combinator' || (part.type === 'PseudoClassSelector' && part.name === 'global');
});
}
const part = parts[i]; if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
if (part.type === 'PseudoClassSelector' && part.name === 'global') {
// TODO shouldn't see this here... maybe we should enforce that :global(...) // TODO shouldn't see this here... maybe we should enforce that :global(...)
// cannot be sandwiched between non-global selectors? // cannot be sandwiched between non-global selectors?
return false; return false;
} }
if (part.type === 'PseudoClassSelector' || part.type === 'PseudoElementSelector') { if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') {
continue; continue;
} }
if (part.type === 'ClassSelector') { if (selector.type === 'ClassSelector') {
if (!attributeMatches(node, 'class', part.name, '~=', false)) return false; if (!attributeMatches(node, 'class', selector.name, '~=', false)) return false;
} }
else if (part.type === 'IdSelector') { else if (selector.type === 'IdSelector') {
if (!attributeMatches(node, 'id', part.name, '=', false)) return false; if (!attributeMatches(node, 'id', selector.name, '=', false)) return false;
} }
else if (part.type === 'AttributeSelector') { else if (selector.type === 'AttributeSelector') {
if (!attributeMatches(node, part.name.name, part.value && unquote(part.value.value), part.operator, part.flags)) return false; if (!attributeMatches(node, selector.name.name, selector.value && unquote(selector.value.value), selector.operator, selector.flags)) return false;
} }
else if (part.type === 'TypeSelector') { else if (selector.type === 'TypeSelector') {
if (part.name === '*') return true; if (node.name !== selector.name && selector.name !== '*') return false;
if (node.name !== part.name) return false;
} }
else if (part.type === 'WhiteSpace') { else {
parts = parts.slice(0, i); // bail. TODO figure out what these could be
return true;
}
}
if (block.combinator) {
if (block.combinator.type === 'WhiteSpace') {
while (stack.length) { while (stack.length) {
if (selectorAppliesTo(parts, stack.pop(), stack)) { if (selectorAppliesTo(blocks.slice(), stack.pop(), stack)) {
return true; return true;
} }
} }
return false; return false;
} else if (block.combinator.name === '>') {
return selectorAppliesTo(blocks, stack.pop(), stack);
} }
else if (part.type === 'Combinator') { // TODO other combinators
if (part.name === '>') { return true;
return selectorAppliesTo(parts.slice(0, i), stack.pop(), stack);
}
// TODO other combinators
return true;
}
else {
// bail. TODO figure out what these could be
return true;
}
} }
return true; return true;
@ -209,7 +204,7 @@ function unquote(str: string) {
} }
function groupSelectors(selector: Node) { function groupSelectors(selector: Node) {
let block = { let block: Block = {
global: selector.children[0].type === 'PseudoClassSelector' && selector.children[0].name === 'global', global: selector.children[0].type === 'PseudoClassSelector' && selector.children[0].name === 'global',
selectors: [], selectors: [],
combinator: null combinator: null

@ -4,7 +4,7 @@ import { getLocator } from 'locate-character';
import Selector from './Selector'; import Selector from './Selector';
import getCodeFrame from '../utils/getCodeFrame'; import getCodeFrame from '../utils/getCodeFrame';
import { Validator } from '../validate/index'; import { Validator } from '../validate/index';
import { Node, Parsed } from '../interfaces'; import { Node, Parsed, Warning } from '../interfaces';
class Rule { class Rule {
selectors: Selector[]; selectors: Selector[];
@ -166,7 +166,7 @@ export default class Stylesheet {
} }
} }
render(cssOutputFilename) { render(cssOutputFilename: string) {
if (!this.hasStyles) { if (!this.hasStyles) {
return { css: null, cssMap: null }; return { css: null, cssMap: null };
} }
@ -202,7 +202,7 @@ export default class Stylesheet {
}); });
} }
warnOnUnusedSelectors(onwarn) { warnOnUnusedSelectors(onwarn: (warning: Warning) => void) {
if (this.cascade) return; if (this.cascade) return;
let locator; let locator;

Loading…
Cancel
Save