Skip to content

Commit

Permalink
Merge pull request #698 from sveltejs/gh-691
Browse files Browse the repository at this point in the history
CSS sourcemaps
  • Loading branch information
Rich-Harris committed Jul 10, 2017
2 parents 6a9e1d5 + 02ea3e7 commit f0fddaa
Show file tree
Hide file tree
Showing 24 changed files with 471 additions and 360 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@
"eslint": "^3.12.2",
"eslint-plugin-html": "^3.0.0",
"eslint-plugin-import": "^2.2.0",
"estree-walker": "^0.3.0",
"estree-walker": "^0.5.0",
"fuzzyset.js": "0.0.1",
"glob": "^7.1.1",
"jsdom": "^9.9.1",
"locate-character": "^2.0.0",
"magic-string": "^0.21.1",
"magic-string": "^0.22.1",
"mocha": "^3.2.0",
"node-resolve": "^1.3.3",
"nyc": "^10.0.0",
Expand Down
160 changes: 106 additions & 54 deletions src/generators/Selector.ts → src/css/Selector.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
import MagicString from 'magic-string';
import { groupSelectors, isGlobalSelector, walkRules } from '../utils/css';
import { Validator } from '../validate/index';
import { Node } from '../interfaces';

interface Block {
global: boolean;
combinator: Node;
selectors: Node[]
}

export default class Selector {
node: Node;
blocks: any; // TODO
parts: Node[];
blocks: Block[];
localBlocks: Block[];
used: boolean;

constructor(node: Node) {
this.node = node;

this.blocks = groupSelectors(this.node);
this.blocks = groupSelectors(node);

// take trailing :global(...) selectors out of consideration
let i = node.children.length;
while (i > 2) {
const last = node.children[i-1];
const penultimate = node.children[i-2];

if (last.type === 'PseudoClassSelector' && last.name === 'global') {
i -= 2;
} else {
break;
}
let i = this.blocks.length;
while (i > 0) {
if (!this.blocks[i - 1].global) break;
i -= 1;
}

this.parts = node.children.slice(0, i);

this.localBlocks = this.blocks.slice(0, i);
this.used = this.blocks[0].global;
}

apply(node: Node, stack: Node[]) {
const applies = selectorAppliesTo(this.parts, node, stack.slice());
const applies = selectorAppliesTo(this.localBlocks.slice(), node, stack.slice());

if (applies) {
this.used = true;
Expand All @@ -45,7 +44,7 @@ export default class Selector {
}

transform(code: MagicString, attr: string) {
function encapsulateBlock(block) {
function encapsulateBlock(block: Block) {
let i = block.selectors.length;
while (i--) {
const selector = block.selectors[i];
Expand All @@ -72,77 +71,102 @@ export default class Selector {
}
});
}

validate(validator: Validator) {
this.blocks.forEach((block) => {
let i = block.selectors.length;
while (i-- > 1) {
const selector = block.selectors[i];
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
validator.error(`:global(...) must be the first element in a compound selector`, selector.start);
}
}
});

let start = 0;
let end = this.blocks.length;

for (; start < end; start += 1) {
if (!this.blocks[start].global) break;
}

for (; end > start; end -= 1) {
if (!this.blocks[end - 1].global) break;
}

for (let i = start; i < end; i += 1) {
if (this.blocks[i].global) {
validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, this.blocks[i].selectors[0].start);
}
}
}
}

function isDescendantSelector(selector: Node) {
return selector.type === 'WhiteSpace' || selector.type === 'Combinator';
}

function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]): boolean {
let i = parts.length;
function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[]): boolean {
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;

while (i--) {
if (!node) {
return parts.every((part: Node) => {
return part.type === 'Combinator' || (part.type === 'PseudoClassSelector' && part.name === 'global');
});
}
const selector = block.selectors[i];

const part = parts[i];

if (part.type === 'PseudoClassSelector' && part.name === 'global') {
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
// TODO shouldn't see this here... maybe we should enforce that :global(...)
// cannot be sandwiched between non-global selectors?
return false;
}

if (part.type === 'PseudoClassSelector' || part.type === 'PseudoElementSelector') {
if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') {
continue;
}

if (part.type === 'ClassSelector') {
if (!attributeMatches(node, 'class', part.name, '~=', false)) return false;
if (selector.type === 'ClassSelector') {
if (!attributeMatches(node, 'class', selector.name, '~=', false)) return false;
}

else if (part.type === 'IdSelector') {
if (!attributeMatches(node, 'id', part.name, '=', false)) return false;
else if (selector.type === 'IdSelector') {
if (!attributeMatches(node, 'id', selector.name, '=', false)) return false;
}

else if (part.type === 'AttributeSelector') {
if (!attributeMatches(node, part.name.name, part.value && unquote(part.value.value), part.operator, part.flags)) return false;
else if (selector.type === 'AttributeSelector') {
if (!attributeMatches(node, selector.name.name, selector.value && unquote(selector.value.value), selector.operator, selector.flags)) return false;
}

else if (part.type === 'TypeSelector') {
if (part.name === '*') return true;
if (node.name !== part.name) return false;
else if (selector.type === 'TypeSelector') {
if (node.name !== selector.name && selector.name !== '*') return false;
}

else if (part.type === 'WhiteSpace') {
parts = parts.slice(0, i);
else {
// bail. TODO figure out what these could be
return true;
}
}

if (block.combinator) {
if (block.combinator.type === 'WhiteSpace') {
while (stack.length) {
if (selectorAppliesTo(parts, stack.pop(), stack)) {
if (selectorAppliesTo(blocks.slice(), stack.pop(), stack)) {
return true;
}
}

return false;
} else if (block.combinator.name === '>') {
return selectorAppliesTo(blocks, stack.pop(), stack);
}

else if (part.type === 'Combinator') {
if (part.name === '>') {
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;
}
// TODO other combinators
return true;
}

return true;
Expand Down Expand Up @@ -177,4 +201,32 @@ function unquote(str: string) {
if (str[0] === str[str.length - 1] && str[0] === "'" || str[0] === '"') {
return str.slice(1, str.length - 1);
}
}

function groupSelectors(selector: Node) {
let block: Block = {
global: selector.children[0].type === 'PseudoClassSelector' && selector.children[0].name === 'global',
selectors: [],
combinator: null
};

const blocks = [block];

selector.children.forEach((child: Node, i: number) => {
if (child.type === 'WhiteSpace' || child.type === 'Combinator') {
const next = selector.children[i + 1];

block = {
global: next.type === 'PseudoClassSelector' && next.name === 'global',
selectors: [],
combinator: child
};

blocks.push(block);
} else {
block.selectors.push(child);
}
});

return blocks;
}
Loading

0 comments on commit f0fddaa

Please sign in to comment.