Skip to content

Commit 7170aaf

Browse files
committed
First part of extend chaining
1 parent 99aa363 commit 7170aaf

File tree

9 files changed

+197
-13
lines changed

9 files changed

+197
-13
lines changed

lib/less/extend-visitor.js

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@
4141
return allSelectorsExtend.clone();
4242
});
4343
for(j = 0; j < extendList.length; j++) {
44+
this.foundExtends = true;
4445
extend = extendList[j];
4546
extend.findSelfSelectors(selectorPath);
47+
extend.ruleset = rulesetNode;
4648
this.allExtendsStack[this.allExtendsStack.length-1].push(extend);
4749
}
4850
}
@@ -72,16 +74,69 @@
7274

7375
tree.processExtendsVisitor = function() {
7476
this._visitor = new tree.visitor(this);
75-
this._searches
7677
};
7778

7879
tree.processExtendsVisitor.prototype = {
7980
run: function(root) {
8081
var extendFinder = new tree.extendFinderVisitor();
8182
extendFinder.run(root);
83+
if (!extendFinder.foundExtends) { return root; }
84+
root.allExtends = root.allExtends.concat(this.doExtendChaining(root.allExtends, root.allExtends));
8285
this.allExtendsStack = [root.allExtends];
8386
return this._visitor.visit(root);
8487
},
88+
doExtendChaining: function (extendsList, extendsListTarget, iterationCount) {
89+
var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath;
90+
91+
iterationCount = iterationCount || 0;
92+
93+
for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){
94+
for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){
95+
96+
var extend = extendsList[extendIndex];
97+
var targetExtend = extendsListTarget[targetExtendIndex];
98+
if (this.inInheritanceChain(targetExtend, extend)) { continue; }
99+
100+
selectorPath = [targetExtend.selfSelectors[0]];
101+
matches = extendVisitor.findMatch(extend, selectorPath);
102+
103+
if (matches.length) {
104+
105+
extend.selfSelectors.forEach(function(selfSelector) {
106+
newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector);
107+
newExtend = new(tree.Extend)(targetExtend.selector, targetExtend.option, 0);
108+
newExtend.selfSelectors = newSelector;
109+
newSelector[newSelector.length-1].extendList = [newExtend];
110+
extendsToAdd.push(newExtend);
111+
newExtend.ruleset = targetExtend.ruleset;
112+
newExtend.parent = targetExtend;
113+
targetExtend.ruleset.paths.push(newSelector);
114+
});
115+
}
116+
}
117+
}
118+
119+
if (extendsToAdd.length) {
120+
this.extendChainCount++;
121+
if (iterationCount > 100) {
122+
var selectorOne = "{unable to calculate}";
123+
var selectorTwo = "{unable to calculate}";
124+
try
125+
{
126+
selectorOne = extendsToAdd[0].selfSelectors[0].toCSS();
127+
selectorTwo = extendsToAdd[0].selector.toCSS();
128+
}
129+
catch(e) {}
130+
throw {message: "extend circular reference detected. One of the circular extends is currently:"+selectorOne+":extend(" + selectorTwo+")"};
131+
}
132+
return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1));
133+
} else {
134+
return extendsToAdd;
135+
}
136+
},
137+
inInheritanceChain: function (possibleParent, possibleChild) {
138+
return possibleParent === possibleChild || (possibleChild.parent ? this.inInheritanceChain(possibleParent, possibleChild.parent) : false);
139+
},
85140
visitRule: function (ruleNode, visitArgs) {
86141
visitArgs.visitDeeper = false;
87142
},
@@ -103,12 +158,16 @@
103158
for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
104159

105160
selectorPath = rulesetNode.paths[pathIndex];
161+
162+
// extending extends happens initially, before the main pass
163+
if (selectorPath[selectorPath.length-1].extendList.length) { continue; }
164+
106165
matches = this.findMatch(allExtends[extendIndex], selectorPath);
107166

108167
if (matches.length) {
109168

110169
allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) {
111-
selectorsToAdd.push(extendVisitor.extendSelector(matches, selfSelector));
170+
selectorsToAdd.push(extendVisitor.extendSelector(matches, selectorPath, selfSelector));
112171
});
113172
}
114173
}
@@ -183,7 +242,7 @@
183242
}
184243
return matches;
185244
},
186-
extendSelector:function (matches, replacementSelector) {
245+
extendSelector:function (matches, selectorPath, replacementSelector) {
187246

188247
//for a set of matches, replace each match with the replacement selector
189248

test/css/extend-chaining.css

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
.a,
2+
.b,
3+
.c {
4+
color: black;
5+
}
6+
.f,
7+
.e,
8+
.d {
9+
color: black;
10+
}
11+
.g.h,
12+
.i.j.h,
13+
.k.j.h {
14+
color: black;
15+
}
16+
.i.j,
17+
.k.j {
18+
color: white;
19+
}
20+
.l,
21+
.m,
22+
.n,
23+
.o,
24+
.p,
25+
.q,
26+
.r,
27+
.s,
28+
.t {
29+
color: black;
30+
}
31+
.u,
32+
.v.u.v {
33+
color: black;
34+
}
35+
.w,
36+
.v.w.v {
37+
color: black;
38+
}

test/css/extend-nest.css

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
.sidebar,
22
.sidebar2,
3-
.type1 .sidebar3 {
3+
.type1 .sidebar3,
4+
.type2.sidebar4 {
45
width: 300px;
56
background: red;
67
}
78
.sidebar .box,
89
.sidebar2 .box,
9-
.type1 .sidebar3 .box {
10+
.type1 .sidebar3 .box,
11+
.type2.sidebar4 .box {
1012
background: #FFF;
1113
border: 1px solid #000;
1214
margin: 10px 0;
@@ -17,3 +19,6 @@
1719
.type1 .sidebar3 {
1820
background: green;
1921
}
22+
.type2.sidebar4 {
23+
background: red;
24+
}

test/css/extend.css

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ div.ext7,
3434
width: 100px;
3535
}
3636
.ext8.ext9,
37-
.foo {
37+
.fuu {
3838
result: add-foo;
3939
}
4040
.ext8 .ext9,
4141
.ext8 + .ext9,
4242
.ext8 > .ext9,
43-
.bar,
43+
.buu,
4444
.zap,
4545
.zoo {
4646
result: bar-matched;
@@ -49,15 +49,14 @@ div.ext7,
4949
result: none;
5050
}
5151
.ext8 .ext9,
52-
.bar {
52+
.buu {
5353
result: match-nested-bar;
5454
}
5555
.ext8.ext9,
56-
.foo {
56+
.fuu {
5757
result: match-nested-foo;
5858
}
5959
.aa,
60-
.bb,
6160
.cc {
6261
color: black;
6362
}

test/less/errors/extend-circular.less

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// classic circular reference
2+
3+
.x:extend(.z) {}
4+
.y:extend(.x) {}
5+
.z:extend(.y) {}
6+
7+
//.x:extend(.z) {}
8+
//.y:extend(.x) {}
9+
//.z:extend(.y) {}
10+
//
11+
//.x:extend(.z), <-- target 1
12+
///y:extend(.z) {} <-- new 1
13+
//.y:extend(.x),
14+
//.z:extend(.x) {} <-- new
15+
//.z:extend(.y),
16+
//.x:extend(.y) {} <-- new
17+
//
18+
//.x:extend(.z),
19+
//.z:extend(.z), <--- last iteration 1 - .parent = target 1 - .parent =
20+
///y:extend(.z) {}
21+
//.y:extend(.x),
22+
//.x:extend(.x), <-- last iteration
23+
//.z:extend(.x) {}
24+
//.z:extend(.y), <-- potential match with 1 -> result in .z:extend(.y)
25+
//.y:extend(.y), <-- last iteration
26+
//.x:extend(.y) {}

test/less/errors/extend-circular.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SyntaxError: extend circular reference detected. One of the circular extends is currently: .x:extend( .z) in {path}extend-circular.less on line null, column 0:
2+
1

test/less/extend-chaining.less

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//very simple chaining
2+
.a {
3+
color: black;
4+
}
5+
.b:extend(.a) {}
6+
.c:extend(.b) {}
7+
8+
//very simple chaining, ordering not important
9+
10+
.d:extend(.e) {}
11+
.e:extend(.f) {}
12+
.f {
13+
color: black;
14+
}
15+
16+
//extend with all
17+
18+
.g.h {
19+
color: black;
20+
}
21+
.i.j:extend(.g all) {
22+
color: white;
23+
}
24+
.k:extend(.i all) {}
25+
26+
//extend multi-chaining
27+
28+
.l {
29+
color: black;
30+
}
31+
.m:extend(.l){}
32+
.n:extend(.m){}
33+
.o:extend(.n){}
34+
.p:extend(.o){}
35+
.q:extend(.p){}
36+
.r:extend(.q){}
37+
.s:extend(.r){}
38+
.t:extend(.s){}
39+
40+
// self referencing is ignored
41+
42+
.u {color: black;}
43+
.v.u.v:extend(.u all){}
44+
45+
// circular reference because the new extend product will match the existing extend
46+
47+
.w:extend(.w) {color: black;}
48+
.v.w.v:extend(.w all){}

test/less/extend-nest.less

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,10 @@
2020
background: green;
2121
}
2222
}
23+
24+
.type2 {
25+
&.sidebar4 {
26+
&:extend(.sidebar all);
27+
background: red;
28+
}
29+
}

test/less/extend.less

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ div.ext5,
5959
}
6060
}
6161

62-
.foo:extend(.ext8.ext9 all) {}
63-
.bar:extend(.ext8 .ext9 all) {}
62+
.fuu:extend(.ext8.ext9 all) {}
63+
.buu:extend(.ext8 .ext9 all) {}
6464
.zap:extend(.ext8 + .ext9 all) {}
6565
.zoo:extend(.ext8 > .ext9 all) {}
6666

@@ -70,7 +70,7 @@ div.ext5,
7070
background: red;
7171
}
7272
}
73-
.bb:extend(.aa) {
73+
.bb {
7474
background: red;
7575
.bb {
7676
color: black;

0 commit comments

Comments
 (0)