Skip to content

Commit cd6a9b3

Browse files
committed
scope css sibling combinator
1 parent b5b02f8 commit cd6a9b3

File tree

98 files changed

+2083
-24
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+2083
-24
lines changed

src/compiler/compile/Component.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import add_to_set from './utils/add_to_set';
2929
import check_graph_for_cycles from './utils/check_graph_for_cycles';
3030
import { print, x, b } from 'code-red';
3131
import { is_reserved_keyword } from './utils/reserved_keywords';
32+
import Element from './nodes/Element';
3233

3334
interface ComponentOptions {
3435
namespace?: string;
@@ -85,6 +86,7 @@ export default class Component {
8586
file: string;
8687
locate: (c: number) => { line: number; column: number };
8788

89+
elements: Element[] = [];
8890
stylesheet: Stylesheet;
8991

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

172174
this.walk_instance_js_post_template();
173175

176+
this.elements.forEach(element => this.stylesheet.apply(element));
174177
if (!compile_options.customElement) this.stylesheet.reify();
175-
176178
this.stylesheet.warn_on_unused_selectors(this);
177179
}
178180

@@ -221,6 +223,10 @@ export default class Component {
221223
return this.aliases.get(name);
222224
}
223225

226+
apply_stylesheet(element: Element) {
227+
this.elements.push(element);
228+
}
229+
224230
global(name: string) {
225231
const alias = this.alias(name);
226232
this.globals.set(name, alias);

src/compiler/compile/css/Selector.ts

+162-8
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,24 @@ import { gather_possible_values, UNKNOWN } from './gather_possible_values';
44
import { CssNode } from './interfaces';
55
import Component from '../Component';
66
import Element from '../nodes/Element';
7+
import { INode } from '../nodes/interfaces';
8+
import EachBlock from '../nodes/EachBlock';
9+
import IfBlock from '../nodes/IfBlock';
10+
import AwaitBlock from '../nodes/AwaitBlock';
711

812
enum BlockAppliesToNode {
913
NotPossible,
1014
Possible,
1115
UnknownSelectorType
1216
}
17+
enum NodeExist {
18+
Probably,
19+
Definitely,
20+
}
21+
interface ElementAndExist {
22+
element: Element;
23+
exist: NodeExist;
24+
}
1325

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

42-
apply(node: Element, stack: Element[]) {
54+
apply(node: Element) {
4355
const to_encapsulate: any[] = [];
4456

45-
apply_selector(this.local_blocks.slice(), node, stack.slice(), to_encapsulate);
57+
apply_selector(this.local_blocks.slice(), node, to_encapsulate);
4658

4759
if (to_encapsulate.length > 0) {
4860
to_encapsulate.forEach(({ node, block }) => {
@@ -149,7 +161,7 @@ export default class Selector {
149161
}
150162
}
151163

152-
function apply_selector(blocks: Block[], node: Element, stack: Element[], to_encapsulate: any[]): boolean {
164+
function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]): boolean {
153165
const block = blocks.pop();
154166
if (!block) return false;
155167

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

164176
case BlockAppliesToNode.UnknownSelectorType:
165-
// bail. TODO figure out what these could be
177+
// bail. TODO figure out what these could be
166178
to_encapsulate.push({ node, block });
167179
return true;
168180
}
@@ -174,9 +186,10 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc
174186
continue;
175187
}
176188

177-
for (const stack_node of stack) {
178-
if (block_might_apply_to_node(ancestor_block, stack_node) !== BlockAppliesToNode.NotPossible) {
179-
to_encapsulate.push({ node: stack_node, block: ancestor_block });
189+
let parent = node;
190+
while (parent = get_element_parent(parent)) {
191+
if (block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible) {
192+
to_encapsulate.push({ node: parent, block: ancestor_block });
180193
}
181194
}
182195

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

194207
return false;
195208
} else if (block.combinator.name === '>') {
196-
if (apply_selector(blocks, stack.pop(), stack, to_encapsulate)) {
209+
if (apply_selector(blocks, get_element_parent(node), to_encapsulate)) {
197210
to_encapsulate.push({ node, block });
198211
return true;
199212
}
200213

201214
return false;
215+
} else if (block.combinator.name === '+') {
216+
const siblings = get_possible_element_siblings(node, true);
217+
let has_match = false;
218+
for (const possible_sibling of siblings) {
219+
if (apply_selector(blocks.slice(), possible_sibling.element, to_encapsulate)) {
220+
to_encapsulate.push({ node, block });
221+
has_match = true;
222+
}
223+
}
224+
return has_match;
225+
} else if (block.combinator.name === '~') {
226+
const siblings = get_possible_element_siblings(node, false);
227+
let has_match = false;
228+
for (const possible_sibling of siblings) {
229+
if (apply_selector(blocks.slice(), possible_sibling.element, to_encapsulate)) {
230+
to_encapsulate.push({ node, block });
231+
has_match = true;
232+
}
233+
}
234+
return has_match;
202235
}
203236

204237
// TODO other combinators
@@ -376,6 +409,127 @@ function unquote(value: CssNode) {
376409
return str;
377410
}
378411

412+
function get_element_parent(node: Element): Element | null {
413+
let parent: INode = node;
414+
while ((parent = parent.parent) && parent.type !== 'Element');
415+
return parent as Element | null;
416+
}
417+
418+
function get_possible_element_siblings(node: INode, adjacent_only: boolean): ElementAndExist[] {
419+
const result: ElementAndExist[] = [];
420+
let prev: INode = node;
421+
while ((prev = prev.prev) && prev.type !== 'Element') {
422+
if (prev.type === 'EachBlock' || prev.type === 'IfBlock' || prev.type === 'AwaitBlock') {
423+
const possible_last_child = get_possible_last_child(prev, adjacent_only);
424+
result.push(...possible_last_child);
425+
if (adjacent_only && possible_last_child.find(child => child.exist === NodeExist.Definitely)) {
426+
return result;
427+
}
428+
}
429+
}
430+
431+
if (prev) {
432+
result.push({ element: prev as Element, exist: NodeExist.Definitely });
433+
}
434+
435+
if (!prev || !adjacent_only) {
436+
let parent: INode = node;
437+
let else_block = node.type === 'ElseBlock';
438+
while ((parent = parent.parent) && (parent.type === 'EachBlock' || parent.type === 'IfBlock' || parent.type === 'ElseBlock' || parent.type === 'AwaitBlock')) {
439+
const possible_siblings = get_possible_element_siblings(parent, adjacent_only);
440+
result.push(...possible_siblings);
441+
442+
if (parent.type === 'EachBlock') {
443+
if (else_block) {
444+
else_block = false;
445+
} else {
446+
for (const each_parent_last_child of get_possible_last_child(parent, adjacent_only)) {
447+
result.push(each_parent_last_child);
448+
}
449+
}
450+
} else if (parent.type === 'ElseBlock') {
451+
else_block = true;
452+
}
453+
454+
if (adjacent_only && possible_siblings.find(sibling => sibling.exist === NodeExist.Definitely)) {
455+
break;
456+
}
457+
}
458+
}
459+
460+
return result;
461+
}
462+
463+
function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjacent_only: boolean): ElementAndExist[] {
464+
const result = [];
465+
466+
if (block.type === 'EachBlock') {
467+
const each_result: ElementAndExist[] = loop_child(block.children, adjacent_only);
468+
const else_result: ElementAndExist[] = block.else ? loop_child(block.else.children, adjacent_only) : [];
469+
470+
const not_exhaustive =
471+
else_result.length === 0 || !else_result.find(result => result.exist === NodeExist.Definitely);
472+
473+
if (not_exhaustive) {
474+
each_result.forEach(result => result.exist = NodeExist.Probably);
475+
else_result.forEach(result => result.exist = NodeExist.Probably);
476+
}
477+
result.push(...each_result, ...else_result);
478+
} else if (block.type === 'IfBlock') {
479+
const if_result: ElementAndExist[] = loop_child(block.children, adjacent_only);
480+
const else_result: ElementAndExist[] = block.else ? loop_child(block.else.children, adjacent_only) : [];
481+
482+
const not_exhaustive =
483+
if_result.length === 0 || !if_result.find(result => result.exist === NodeExist.Definitely) ||
484+
else_result.length === 0 || !else_result.find(result => result.exist === NodeExist.Definitely);
485+
486+
if (not_exhaustive) {
487+
if_result.forEach(result => result.exist = NodeExist.Probably);
488+
else_result.forEach(result => result.exist = NodeExist.Probably);
489+
}
490+
491+
result.push(...if_result, ...else_result);
492+
} else if (block.type === 'AwaitBlock') {
493+
const pending_result: ElementAndExist[] = block.pending ? loop_child(block.pending.children, adjacent_only) : [];
494+
const then_result: ElementAndExist[] = block.then ? loop_child(block.then.children, adjacent_only) : [];
495+
const catch_result: ElementAndExist[] = block.catch ? loop_child(block.catch.children, adjacent_only) : [];
496+
497+
const not_exhaustive =
498+
pending_result.length === 0 || !pending_result.find(result => result.exist === NodeExist.Definitely) ||
499+
then_result.length === 0 || !then_result.find(result => result.exist === NodeExist.Definitely) ||
500+
catch_result.length === 0 || !catch_result.find(result => result.exist === NodeExist.Definitely);
501+
502+
if (not_exhaustive) {
503+
pending_result.forEach(result => result.exist = NodeExist.Probably);
504+
then_result.forEach(result => result.exist = NodeExist.Probably);
505+
catch_result.forEach(result => result.exist = NodeExist.Probably);
506+
}
507+
result.push(...pending_result,...then_result,...catch_result);
508+
}
509+
510+
return result;
511+
}
512+
513+
function loop_child(children: INode[], adjacent_only: boolean) {
514+
const result = [];
515+
for (let i = children.length - 1; i >= 0; i--) {
516+
const child = children[i];
517+
if (child.type === 'Element') {
518+
result.push({ element: child, exist: NodeExist.Definitely });
519+
if (adjacent_only) {
520+
break;
521+
}
522+
} else if (child.type === 'EachBlock' || child.type === 'IfBlock' || child.type === 'AwaitBlock') {
523+
const child_result = get_possible_last_child(child, adjacent_only);
524+
result.push(...child_result);
525+
if (adjacent_only && child_result.find(child => child.exist === NodeExist.Definitely)) {
526+
break;
527+
}
528+
}
529+
}
530+
return result;
531+
}
532+
379533
class Block {
380534
global: boolean;
381535
combinator: CssNode;

src/compiler/compile/css/Stylesheet.ts

+6-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import MagicString from 'magic-string';
22
import { walk } from 'estree-walker';
33
import Selector from './Selector';
44
import Element from '../nodes/Element';
5-
import { Ast, TemplateNode } from '../../interfaces';
5+
import { Ast } from '../../interfaces';
66
import Component from '../Component';
77
import { CssNode } from './interfaces';
88
import hash from "../utils/hash";
@@ -51,8 +51,8 @@ class Rule {
5151
this.declarations = node.block.children.map((node: CssNode) => new Declaration(node));
5252
}
5353

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

5858
is_used(dev: boolean) {
@@ -162,10 +162,10 @@ class Atrule {
162162
this.declarations = [];
163163
}
164164

165-
apply(node: Element, stack: Element[]) {
165+
apply(node: Element) {
166166
if (this.node.name === 'media' || this.node.name === 'supports') {
167167
this.children.forEach(child => {
168-
child.apply(node, stack);
168+
child.apply(node);
169169
});
170170
}
171171

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

367-
const stack: Element[] = [];
368-
let parent: TemplateNode = node;
369-
while (parent = parent.parent) {
370-
if (parent.type === 'Element') stack.unshift(parent as Element);
371-
}
372-
373367
for (let i = 0; i < this.children.length; i += 1) {
374368
const child = this.children[i];
375-
child.apply(node, stack);
369+
child.apply(node);
376370
}
377371
}
378372

src/compiler/compile/nodes/Element.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import list from '../../utils/list';
1616
import Let from './Let';
1717
import TemplateScope from './shared/TemplateScope';
1818
import { INode } from './interfaces';
19+
import Component from '../Component';
1920

2021
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)$/;
2122

@@ -123,7 +124,7 @@ export default class Element extends Node {
123124
namespace: string;
124125
needs_manual_style_scoping: boolean;
125126

126-
constructor(component, parent, scope, info: any) {
127+
constructor(component: Component, parent, scope, info: any) {
127128
super(component, parent, scope, info);
128129
this.name = info.name;
129130

@@ -184,7 +185,7 @@ export default class Element extends Node {
184185

185186
case 'Attribute':
186187
case 'Spread':
187-
// special case
188+
// special case
188189
if (node.name === 'xmlns') this.namespace = node.value[0].data;
189190

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

236237
this.validate();
237238

238-
component.stylesheet.apply(this);
239+
component.apply_stylesheet(this);
239240
}
240241

241242
validate() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
warnings: []
3+
};

test/css/samples/general-siblings-combinator-await-not-exhaustive/expected.css

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<div class="a svelte-xyz"></div>
2+
<div class="d svelte-xyz"></div>
3+
<div class="f svelte-xyz"></div>
4+
<div class="h svelte-xyz"></div>

0 commit comments

Comments
 (0)