Skip to content

Commit 7217cb5

Browse files
committed
refactor extend visitor to be more readable and maintainable
1 parent cf9496e commit 7217cb5

File tree

1 file changed

+104
-70
lines changed

1 file changed

+104
-70
lines changed

lib/less/extend-visitor.js

Lines changed: 104 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -95,112 +95,146 @@
9595
if (rulesetNode.root) {
9696
return;
9797
}
98-
var i, j, k, selector, element, allExtends = this.allExtendsStack[this.allExtendsStack.length-1], selectorsToAdd = [];
98+
var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length-1], selectorsToAdd = [], extendVisitor = this;
9999

100-
for(k = 0; k < allExtends.length; k++) {
101-
for(i = 0; i < rulesetNode.paths.length; i++) {
102-
selectorPath = rulesetNode.paths[i];
103-
var matches = this.findMatch(allExtends[k], selectorPath);
104-
if (matches.length) {
105-
allExtends[k].selfSelectors.forEach(function(selfSelector) {
106-
var currentSelectorPathIndex = 0,
107-
currentSelectorPathElementIndex = 0,
108-
path = [];
109-
for(j = 0; j < matches.length; j++) {
110-
match = matches[j];
111-
var selector = selectorPath[match.pathIndex],
112-
firstElement = new tree.Element(
113-
match.initialCombinator,
114-
selfSelector.elements[0].value,
115-
selfSelector.elements[0].index
116-
);
117-
118-
if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) {
119-
path[path.length-1].elements = path[path.length-1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
120-
currentSelectorPathElementIndex = 0;
121-
currentSelectorPathIndex++;
122-
}
123-
124-
path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex));
125-
126-
path.push(new tree.Selector(
127-
selector.elements
128-
.slice(currentSelectorPathElementIndex, match.index)
129-
.concat([firstElement])
130-
.concat(selfSelector.elements.slice(1))
131-
));
132-
currentSelectorPathIndex = match.endPathIndex;
133-
currentSelectorPathElementIndex = match.endPathElementIndex;
134-
if (currentSelectorPathElementIndex >= selector.elements.length) {
135-
currentSelectorPathElementIndex = 0;
136-
currentSelectorPathIndex++;
137-
}
138-
}
100+
// look at each selector path in the ruleset, find any extend matches and then copy, find and replace
139101

140-
if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
141-
path[path.length-1].elements = path[path.length-1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
142-
currentSelectorPathElementIndex = 0;
143-
currentSelectorPathIndex++;
144-
}
102+
for(extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
103+
for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
145104

146-
path = path.concat(selectorPath.slice(currentSelectorPathIndex, selectorPath.length));
105+
selectorPath = rulesetNode.paths[pathIndex];
106+
matches = this.findMatch(allExtends[extendIndex], selectorPath);
107+
108+
if (matches.length) {
147109

148-
selectorsToAdd.push(path);
110+
allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) {
111+
selectorsToAdd.push(extendVisitor.extendSelector(matches, selfSelector));
149112
});
150113
}
151114
}
152115
}
153116
rulesetNode.paths = rulesetNode.paths.concat(selectorsToAdd);
154117
},
155-
findMatch: function (extend, selectorPath) {
156-
var i, k, l, element, hasMatch, potentialMatches = [], potentialMatch, matches = [];
157-
for(k = 0; k < selectorPath.length; k++) {
158-
selector = selectorPath[k];
159-
for(i = 0; i < selector.elements.length; i++) {
160-
if (extend.allowBefore || (k == 0 && i == 0)) {
161-
potentialMatches.push({pathIndex: k, index: i, matched: 0, initialCombinator: selector.elements[i].combinator});
118+
findMatch: function (extend, haystackSelectorPath) {
119+
//
120+
// look through the haystack selector path to try and find the needle - extend.selector
121+
// returns an array of selector matches that can then be replaced
122+
//
123+
var haystackSelectorIndex, hackstackSelector, hackstackElementIndex, haystackElement,
124+
targetCombinator, i,
125+
needleElements = extend.selector.elements,
126+
potentialMatches = [], potentialMatch, matches = [];
127+
128+
// loop through the haystack elements
129+
for(haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
130+
hackstackSelector = haystackSelectorPath[haystackSelectorIndex];
131+
132+
for(hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
133+
134+
haystackElement = hackstackSelector.elements[hackstackElementIndex];
135+
136+
// if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
137+
if (extend.allowBefore || (haystackSelectorIndex == 0 && hackstackElementIndex == 0)) {
138+
potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator});
162139
}
163140

164-
for(l = 0; l < potentialMatches.length; l++) {
165-
potentialMatch = potentialMatches[l];
141+
for(i = 0; i < potentialMatches.length; i++) {
142+
potentialMatch = potentialMatches[i];
166143

167-
var targetCombinator = selector.elements[i].combinator.value;
168-
if (targetCombinator == '' && i === 0) {
144+
// selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
145+
// then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
146+
// what the resulting combinator will be
147+
targetCombinator = haystackElement.combinator.value;
148+
if (targetCombinator == '' && hackstackElementIndex === 0) {
169149
targetCombinator = ' ';
170150
}
171-
if (extend.selector.elements[potentialMatch.matched].value !== selector.elements[i].value ||
172-
(potentialMatch.matched > 0 && extend.selector.elements[potentialMatch.matched].combinator.value !== targetCombinator)) {
151+
152+
// if we don't match, null our match to indicate failure
153+
if (needleElements[potentialMatch.matched].value !== haystackElement.value ||
154+
(potentialMatch.matched > 0 && needleElements[potentialMatch.matched].combinator.value !== targetCombinator)) {
173155
potentialMatch = null;
174156
} else {
175157
potentialMatch.matched++;
176158
}
177159

160+
// if we are still valid and have finished, test whether we have elements after and whether these are allowed
178161
if (potentialMatch) {
179-
potentialMatch.finished = potentialMatch.matched === extend.selector.elements.length;
162+
potentialMatch.finished = potentialMatch.matched === needleElements.length;
180163
if (potentialMatch.finished &&
181-
(!extend.allowAfter && (i+1 < selector.elements.length ||
182-
k+1 < selectorPath.length))) {
164+
(!extend.allowAfter && (hackstackElementIndex+1 < hackstackSelector.elements.length || haystackSelectorIndex+1 < haystackSelectorPath.length))) {
183165
potentialMatch = null;
184166
}
185167
}
168+
// if null we remove, if not, we are still valid, so either push as a valid match or continue
186169
if (potentialMatch) {
187170
if (potentialMatch.finished) {
188-
potentialMatch.length = extend.selector.elements.length;
189-
potentialMatch.endPathIndex = k;
190-
potentialMatch.endPathElementIndex = i+1; // index after end of match
191-
potentialMatches.length = 0;
171+
potentialMatch.length = needleElements.length;
172+
potentialMatch.endPathIndex = haystackSelectorIndex;
173+
potentialMatch.endPathElementIndex = hackstackElementIndex + 1; // index after end of match
174+
potentialMatches.length = 0; // we don't allow matches to overlap, so start matching again
192175
matches.push(potentialMatch);
193-
break;
194176
}
195177
} else {
196-
potentialMatches.splice(l, 1);
197-
l--;
178+
potentialMatches.splice(i, 1);
179+
i--;
198180
}
199181
}
200182
}
201183
}
202184
return matches;
203185
},
186+
extendSelector:function (matches, replacementSelector) {
187+
188+
//for a set of matches, replace each match with the replacement selector
189+
190+
var currentSelectorPathIndex = 0,
191+
currentSelectorPathElementIndex = 0,
192+
path = [],
193+
matchIndex,
194+
selector,
195+
firstElement;
196+
197+
for (matchIndex = 0; matchIndex < matches.length; matchIndex++) {
198+
match = matches[matchIndex];
199+
selector = selectorPath[match.pathIndex];
200+
firstElement = new tree.Element(
201+
match.initialCombinator,
202+
replacementSelector.elements[0].value,
203+
replacementSelector.elements[0].index
204+
);
205+
206+
if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) {
207+
path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
208+
currentSelectorPathElementIndex = 0;
209+
currentSelectorPathIndex++;
210+
}
211+
212+
path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex));
213+
214+
path.push(new tree.Selector(
215+
selector.elements
216+
.slice(currentSelectorPathElementIndex, match.index)
217+
.concat([firstElement])
218+
.concat(replacementSelector.elements.slice(1))
219+
));
220+
currentSelectorPathIndex = match.endPathIndex;
221+
currentSelectorPathElementIndex = match.endPathElementIndex;
222+
if (currentSelectorPathElementIndex >= selector.elements.length) {
223+
currentSelectorPathElementIndex = 0;
224+
currentSelectorPathIndex++;
225+
}
226+
}
227+
228+
if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
229+
path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
230+
currentSelectorPathElementIndex = 0;
231+
currentSelectorPathIndex++;
232+
}
233+
234+
path = path.concat(selectorPath.slice(currentSelectorPathIndex, selectorPath.length));
235+
236+
return path;
237+
},
204238
visitRulesetOut: function (rulesetNode) {
205239
},
206240
visitMedia: function (mediaNode, visitArgs) {

0 commit comments

Comments
 (0)