Skip to content

Commit

Permalink
scope css sibling combinator
Browse files Browse the repository at this point in the history
  • Loading branch information
tanhauhau committed Sep 19, 2020
1 parent b5b02f8 commit cd6a9b3
Show file tree
Hide file tree
Showing 98 changed files with 2,083 additions and 24 deletions.
8 changes: 7 additions & 1 deletion src/compiler/compile/Component.ts
Expand Up @@ -29,6 +29,7 @@ import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, x, b } from 'code-red';
import { is_reserved_keyword } from './utils/reserved_keywords';
import Element from './nodes/Element';

interface ComponentOptions {
namespace?: string;
Expand Down Expand Up @@ -85,6 +86,7 @@ export default class Component {
file: string;
locate: (c: number) => { line: number; column: number };

elements: Element[] = [];
stylesheet: Stylesheet;

aliases: Map<string, Identifier> = new Map();
Expand Down Expand Up @@ -171,8 +173,8 @@ export default class Component {

this.walk_instance_js_post_template();

this.elements.forEach(element => this.stylesheet.apply(element));
if (!compile_options.customElement) this.stylesheet.reify();

this.stylesheet.warn_on_unused_selectors(this);
}

Expand Down Expand Up @@ -221,6 +223,10 @@ export default class Component {
return this.aliases.get(name);
}

apply_stylesheet(element: Element) {
this.elements.push(element);
}

global(name: string) {
const alias = this.alias(name);
this.globals.set(name, alias);
Expand Down
170 changes: 162 additions & 8 deletions src/compiler/compile/css/Selector.ts
Expand Up @@ -4,12 +4,24 @@ import { gather_possible_values, UNKNOWN } from './gather_possible_values';
import { CssNode } from './interfaces';
import Component from '../Component';
import Element from '../nodes/Element';
import { INode } from '../nodes/interfaces';
import EachBlock from '../nodes/EachBlock';
import IfBlock from '../nodes/IfBlock';
import AwaitBlock from '../nodes/AwaitBlock';

enum BlockAppliesToNode {
NotPossible,
Possible,
UnknownSelectorType
}
enum NodeExist {
Probably,
Definitely,
}
interface ElementAndExist {
element: Element;
exist: NodeExist;
}

const whitelist_attribute_selector = new Map([
['details', new Set(['open'])]
Expand Down Expand Up @@ -39,10 +51,10 @@ export default class Selector {
this.used = this.local_blocks.length === 0;
}

apply(node: Element, stack: Element[]) {
apply(node: Element) {
const to_encapsulate: any[] = [];

apply_selector(this.local_blocks.slice(), node, stack.slice(), to_encapsulate);
apply_selector(this.local_blocks.slice(), node, to_encapsulate);

if (to_encapsulate.length > 0) {
to_encapsulate.forEach(({ node, block }) => {
Expand Down Expand Up @@ -149,7 +161,7 @@ export default class Selector {
}
}

function apply_selector(blocks: Block[], node: Element, stack: Element[], to_encapsulate: any[]): boolean {
function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]): boolean {
const block = blocks.pop();
if (!block) return false;

Expand All @@ -162,7 +174,7 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc
return false;

case BlockAppliesToNode.UnknownSelectorType:
// bail. TODO figure out what these could be
// bail. TODO figure out what these could be
to_encapsulate.push({ node, block });
return true;
}
Expand All @@ -174,9 +186,10 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc
continue;
}

for (const stack_node of stack) {
if (block_might_apply_to_node(ancestor_block, stack_node) !== BlockAppliesToNode.NotPossible) {
to_encapsulate.push({ node: stack_node, block: ancestor_block });
let parent = node;
while (parent = get_element_parent(parent)) {
if (block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible) {
to_encapsulate.push({ node: parent, block: ancestor_block });
}
}

Expand All @@ -193,12 +206,32 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc

return false;
} else if (block.combinator.name === '>') {
if (apply_selector(blocks, stack.pop(), stack, to_encapsulate)) {
if (apply_selector(blocks, get_element_parent(node), to_encapsulate)) {
to_encapsulate.push({ node, block });
return true;
}

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);
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;
}

// TODO other combinators
Expand Down Expand Up @@ -376,6 +409,127 @@ function unquote(value: CssNode) {
return str;
}

function get_element_parent(node: Element): Element | null {
let parent: INode = node;
while ((parent = parent.parent) && parent.type !== 'Element');
return parent as Element | null;
}

function get_possible_element_siblings(node: INode, adjacent_only: boolean): ElementAndExist[] {
const result: ElementAndExist[] = [];
let prev: INode = node;
while ((prev = prev.prev) && prev.type !== 'Element') {
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)) {
return result;
}
}
}

if (prev) {
result.push({ element: prev as Element, exist: NodeExist.Definitely });
}

if (!prev || !adjacent_only) {
let parent: INode = node;
let else_block = 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);

if (parent.type === 'EachBlock') {
if (else_block) {
else_block = false;
} else {
for (const each_parent_last_child of get_possible_last_child(parent, adjacent_only)) {
result.push(each_parent_last_child);
}
}
} else if (parent.type === 'ElseBlock') {
else_block = true;
}

if (adjacent_only && possible_siblings.find(sibling => sibling.exist === NodeExist.Definitely)) {
break;
}
}
}

return result;
}

function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjacent_only: boolean): ElementAndExist[] {
const result = [];

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 not_exhaustive =
else_result.length === 0 || !else_result.find(result => result.exist === NodeExist.Definitely);

if (not_exhaustive) {
each_result.forEach(result => result.exist = NodeExist.Probably);
else_result.forEach(result => result.exist = NodeExist.Probably);
}
result.push(...each_result, ...else_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 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);

if (not_exhaustive) {
if_result.forEach(result => result.exist = NodeExist.Probably);
else_result.forEach(result => result.exist = NodeExist.Probably);
}

result.push(...if_result, ...else_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 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);

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);
}
result.push(...pending_result,...then_result,...catch_result);
}

return result;
}

function loop_child(children: INode[], adjacent_only: boolean) {
const result = [];
for (let i = children.length - 1; i >= 0; i--) {
const child = children[i];
if (child.type === 'Element') {
result.push({ element: child, exist: 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)) {
break;
}
}
}
return result;
}

class Block {
global: boolean;
combinator: CssNode;
Expand Down
18 changes: 6 additions & 12 deletions src/compiler/compile/css/Stylesheet.ts
Expand Up @@ -2,7 +2,7 @@ import MagicString from 'magic-string';
import { walk } from 'estree-walker';
import Selector from './Selector';
import Element from '../nodes/Element';
import { Ast, TemplateNode } from '../../interfaces';
import { Ast } from '../../interfaces';
import Component from '../Component';
import { CssNode } from './interfaces';
import hash from "../utils/hash";
Expand Down Expand Up @@ -51,8 +51,8 @@ class Rule {
this.declarations = node.block.children.map((node: CssNode) => new Declaration(node));
}

apply(node: Element, stack: Element[]) {
this.selectors.forEach(selector => selector.apply(node, stack)); // TODO move the logic in here?
apply(node: Element) {
this.selectors.forEach(selector => selector.apply(node)); // TODO move the logic in here?
}

is_used(dev: boolean) {
Expand Down Expand Up @@ -162,10 +162,10 @@ class Atrule {
this.declarations = [];
}

apply(node: Element, stack: Element[]) {
apply(node: Element) {
if (this.node.name === 'media' || this.node.name === 'supports') {
this.children.forEach(child => {
child.apply(node, stack);
child.apply(node);
});
}

Expand Down Expand Up @@ -364,15 +364,9 @@ export default class Stylesheet {
apply(node: Element) {
if (!this.has_styles) return;

const stack: Element[] = [];
let parent: TemplateNode = node;
while (parent = parent.parent) {
if (parent.type === 'Element') stack.unshift(parent as Element);
}

for (let i = 0; i < this.children.length; i += 1) {
const child = this.children[i];
child.apply(node, stack);
child.apply(node);
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/compiler/compile/nodes/Element.ts
Expand Up @@ -16,6 +16,7 @@ import list from '../../utils/list';
import Let from './Let';
import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
import Component from '../Component';

const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/;

Expand Down Expand Up @@ -123,7 +124,7 @@ export default class Element extends Node {
namespace: string;
needs_manual_style_scoping: boolean;

constructor(component, parent, scope, info: any) {
constructor(component: Component, parent, scope, info: any) {
super(component, parent, scope, info);
this.name = info.name;

Expand Down Expand Up @@ -184,7 +185,7 @@ export default class Element extends Node {

case 'Attribute':
case 'Spread':
// special case
// special case
if (node.name === 'xmlns') this.namespace = node.value[0].data;

this.attributes.push(new Attribute(component, this, scope, node));
Expand Down Expand Up @@ -235,7 +236,7 @@ export default class Element extends Node {

this.validate();

component.stylesheet.apply(this);
component.apply_stylesheet(this);
}

validate() {
Expand Down
@@ -0,0 +1,3 @@
export default {
warnings: []
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -0,0 +1,4 @@
<div class="a svelte-xyz"></div>
<div class="d svelte-xyz"></div>
<div class="f svelte-xyz"></div>
<div class="h svelte-xyz"></div>

0 comments on commit cd6a9b3

Please sign in to comment.