@@ -1334,29 +1334,38 @@ less.Parser = function Parser(env) {
1334
1334
restore ( ) ;
1335
1335
}
1336
1336
} ,
1337
- rule : function ( ) {
1337
+ rule : function ( tryAnonymous ) {
1338
1338
var name , value , c = input . charAt ( i ) , important , match ;
1339
1339
save ( ) ;
1340
1340
1341
1341
if ( c === '.' || c === '#' || c === '&' ) { return }
1342
1342
1343
1343
if ( name = $ ( this . variable ) || $ ( this . property ) ) {
1344
- if ( ! env . compress && ( name . charAt ( 0 ) != '@' ) && ( match = / ^ ( [ ^ @ + \/ ' " * ` ( ; { } - ] * ) ; / . exec ( chunks [ j ] ) ) ) {
1345
- i += match [ 0 ] . length - 1 ;
1346
- value = new ( tree . Anonymous ) ( match [ 1 ] ) ;
1347
- } else {
1348
- value = $ ( this . value ) ;
1349
- }
1344
+ // prefer to try to parse first if its a variable or we are compressing
1345
+ // but always fallback on the other one
1346
+ value = ! tryAnonymous && ( env . compress || ( name . charAt ( 0 ) === '@' ) ) ?
1347
+ ( $ ( this . value ) || $ ( this . anonymousValue ) ) :
1348
+ ( $ ( this . anonymousValue ) || $ ( this . value ) ) ;
1349
+
1350
1350
important = $ ( this . important ) ;
1351
1351
1352
1352
if ( value && $ ( this . end ) ) {
1353
1353
return new ( tree . Rule ) ( name , value , important , memo , env . currentFileInfo ) ;
1354
1354
} else {
1355
1355
furthest = i ;
1356
1356
restore ( ) ;
1357
+ if ( value && ! tryAnonymous ) {
1358
+ return this . rule ( true ) ;
1359
+ }
1357
1360
}
1358
1361
}
1359
1362
} ,
1363
+ anonymousValue : function ( ) {
1364
+ if ( match = / ^ ( [ ^ @ + \/ ' " * ` ( ; { } - ] * ) ; / . exec ( chunks [ j ] ) ) {
1365
+ i += match [ 0 ] . length - 1 ;
1366
+ return new ( tree . Anonymous ) ( match [ 1 ] ) ;
1367
+ }
1368
+ } ,
1360
1369
1361
1370
//
1362
1371
// An @import directive
@@ -4982,8 +4991,11 @@ tree.jsify = function (obj) {
4982
4991
return allSelectorsExtend . clone ( ) ;
4983
4992
} ) ;
4984
4993
for ( j = 0 ; j < extendList . length ; j ++ ) {
4994
+ this . foundExtends = true ;
4985
4995
extend = extendList [ j ] ;
4986
4996
extend . findSelfSelectors ( selectorPath ) ;
4997
+ extend . ruleset = rulesetNode ;
4998
+ if ( j === 0 ) { extend . firstExtendOnThisSelectorPath = true ; }
4987
4999
this . allExtendsStack [ this . allExtendsStack . length - 1 ] . push ( extend ) ;
4988
5000
}
4989
5001
}
@@ -5013,16 +5025,120 @@ tree.jsify = function (obj) {
5013
5025
5014
5026
tree . processExtendsVisitor = function ( ) {
5015
5027
this . _visitor = new tree . visitor ( this ) ;
5016
- this . _searches
5017
5028
} ;
5018
5029
5019
5030
tree . processExtendsVisitor . prototype = {
5020
5031
run : function ( root ) {
5021
5032
var extendFinder = new tree . extendFinderVisitor ( ) ;
5022
5033
extendFinder . run ( root ) ;
5034
+ if ( ! extendFinder . foundExtends ) { return root ; }
5035
+ root . allExtends = root . allExtends . concat ( this . doExtendChaining ( root . allExtends , root . allExtends ) ) ;
5023
5036
this . allExtendsStack = [ root . allExtends ] ;
5024
5037
return this . _visitor . visit ( root ) ;
5025
5038
} ,
5039
+ doExtendChaining : function ( extendsList , extendsListTarget , iterationCount ) {
5040
+ //
5041
+ // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
5042
+ // the selector we would do normally, but we are also adding an extend with the same target selector
5043
+ // this means this new extend can then go and alter other extends
5044
+ //
5045
+ // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
5046
+ // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
5047
+ // we look at each selector at a time, as is done in visitRuleset
5048
+
5049
+ var extendIndex , targetExtendIndex , matches , extendsToAdd = [ ] , newSelector , extendVisitor = this , selectorPath , extend , targetExtend ;
5050
+
5051
+ iterationCount = iterationCount || 0 ;
5052
+
5053
+ //loop through comparing every extend with every target extend.
5054
+ // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
5055
+ // e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
5056
+ // and the second is the target.
5057
+ // the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
5058
+ // case when processing media queries
5059
+ for ( extendIndex = 0 ; extendIndex < extendsList . length ; extendIndex ++ ) {
5060
+ for ( targetExtendIndex = 0 ; targetExtendIndex < extendsListTarget . length ; targetExtendIndex ++ ) {
5061
+
5062
+ extend = extendsList [ extendIndex ] ;
5063
+ targetExtend = extendsListTarget [ targetExtendIndex ] ;
5064
+
5065
+ // look for circular references
5066
+ if ( this . inInheritanceChain ( targetExtend , extend ) ) { continue ; }
5067
+
5068
+ // find a match in the target extends self selector (the bit before :extend)
5069
+ selectorPath = [ targetExtend . selfSelectors [ 0 ] ] ;
5070
+ matches = extendVisitor . findMatch ( extend , selectorPath ) ;
5071
+
5072
+ if ( matches . length ) {
5073
+
5074
+ // we found a match, so for each self selector..
5075
+ extend . selfSelectors . forEach ( function ( selfSelector ) {
5076
+
5077
+ // process the extend as usual
5078
+ newSelector = extendVisitor . extendSelector ( matches , selectorPath , selfSelector ) ;
5079
+
5080
+ // but now we create a new extend from it
5081
+ newExtend = new ( tree . Extend ) ( targetExtend . selector , targetExtend . option , 0 ) ;
5082
+ newExtend . selfSelectors = newSelector ;
5083
+
5084
+ // add the extend onto the list of extends for that selector
5085
+ newSelector [ newSelector . length - 1 ] . extendList = [ newExtend ] ;
5086
+
5087
+ // record that we need to add it.
5088
+ extendsToAdd . push ( newExtend ) ;
5089
+ newExtend . ruleset = targetExtend . ruleset ;
5090
+
5091
+ //remember its parents for circular references
5092
+ newExtend . parents = [ targetExtend , extend ] ;
5093
+
5094
+ // only process the selector once.. if we have :extend(.a,.b) then multiple
5095
+ // extends will look at the same selector path, so when extending
5096
+ // we know that any others will be duplicates in terms of what is added to the css
5097
+ if ( targetExtend . firstExtendOnThisSelectorPath ) {
5098
+ newExtend . firstExtendOnThisSelectorPath = true ;
5099
+ targetExtend . ruleset . paths . push ( newSelector ) ;
5100
+ }
5101
+ } ) ;
5102
+ }
5103
+ }
5104
+ }
5105
+
5106
+ if ( extendsToAdd . length ) {
5107
+ // try to detect circular references to stop a stack overflow.
5108
+ // may no longer be needed.
5109
+ this . extendChainCount ++ ;
5110
+ if ( iterationCount > 100 ) {
5111
+ var selectorOne = "{unable to calculate}" ;
5112
+ var selectorTwo = "{unable to calculate}" ;
5113
+ try
5114
+ {
5115
+ selectorOne = extendsToAdd [ 0 ] . selfSelectors [ 0 ] . toCSS ( ) ;
5116
+ selectorTwo = extendsToAdd [ 0 ] . selector . toCSS ( ) ;
5117
+ }
5118
+ catch ( e ) { }
5119
+ throw { message : "extend circular reference detected. One of the circular extends is currently:" + selectorOne + ":extend(" + selectorTwo + ")" } ;
5120
+ }
5121
+
5122
+ // now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
5123
+ return extendsToAdd . concat ( extendVisitor . doExtendChaining ( extendsToAdd , extendsListTarget , iterationCount + 1 ) ) ;
5124
+ } else {
5125
+ return extendsToAdd ;
5126
+ }
5127
+ } ,
5128
+ inInheritanceChain : function ( possibleParent , possibleChild ) {
5129
+ if ( possibleParent === possibleChild ) {
5130
+ return true ;
5131
+ }
5132
+ if ( possibleChild . parents ) {
5133
+ if ( this . inInheritanceChain ( possibleParent , possibleChild . parents [ 0 ] ) ) {
5134
+ return true ;
5135
+ }
5136
+ if ( this . inInheritanceChain ( possibleParent , possibleChild . parents [ 1 ] ) ) {
5137
+ return true ;
5138
+ }
5139
+ }
5140
+ return false ;
5141
+ } ,
5026
5142
visitRule : function ( ruleNode , visitArgs ) {
5027
5143
visitArgs . visitDeeper = false ;
5028
5144
} ,
@@ -5044,12 +5160,16 @@ tree.jsify = function (obj) {
5044
5160
for ( pathIndex = 0 ; pathIndex < rulesetNode . paths . length ; pathIndex ++ ) {
5045
5161
5046
5162
selectorPath = rulesetNode . paths [ pathIndex ] ;
5163
+
5164
+ // extending extends happens initially, before the main pass
5165
+ if ( selectorPath [ selectorPath . length - 1 ] . extendList . length ) { continue ; }
5166
+
5047
5167
matches = this . findMatch ( allExtends [ extendIndex ] , selectorPath ) ;
5048
5168
5049
5169
if ( matches . length ) {
5050
5170
5051
5171
allExtends [ extendIndex ] . selfSelectors . forEach ( function ( selfSelector ) {
5052
- selectorsToAdd . push ( extendVisitor . extendSelector ( matches , selfSelector ) ) ;
5172
+ selectorsToAdd . push ( extendVisitor . extendSelector ( matches , selectorPath , selfSelector ) ) ;
5053
5173
} ) ;
5054
5174
}
5055
5175
}
@@ -5124,7 +5244,7 @@ tree.jsify = function (obj) {
5124
5244
}
5125
5245
return matches ;
5126
5246
} ,
5127
- extendSelector :function ( matches , replacementSelector ) {
5247
+ extendSelector :function ( matches , selectorPath , replacementSelector ) {
5128
5248
5129
5249
//for a set of matches, replace each match with the replacement selector
5130
5250
@@ -5179,13 +5299,17 @@ tree.jsify = function (obj) {
5179
5299
visitRulesetOut : function ( rulesetNode ) {
5180
5300
} ,
5181
5301
visitMedia : function ( mediaNode , visitArgs ) {
5182
- this . allExtendsStack . push ( mediaNode . allExtends . concat ( this . allExtendsStack [ this . allExtendsStack . length - 1 ] ) ) ;
5302
+ var newAllExtends = mediaNode . allExtends . concat ( this . allExtendsStack [ this . allExtendsStack . length - 1 ] ) ;
5303
+ newAllExtends = newAllExtends . concat ( this . doExtendChaining ( newAllExtends , mediaNode . allExtends ) ) ;
5304
+ this . allExtendsStack . push ( newAllExtends ) ;
5183
5305
} ,
5184
5306
visitMediaOut : function ( mediaNode ) {
5185
5307
this . allExtendsStack . length = this . allExtendsStack . length - 1 ;
5186
5308
} ,
5187
5309
visitDirective : function ( directiveNode , visitArgs ) {
5188
- this . allExtendsStack . push ( directiveNode . allExtends . concat ( this . allExtendsStack [ this . allExtendsStack . length - 1 ] ) ) ;
5310
+ var newAllExtends = directiveNode . allExtends . concat ( this . allExtendsStack [ this . allExtendsStack . length - 1 ] ) ;
5311
+ newAllExtends = newAllExtends . concat ( this . doExtendChaining ( newAllExtends , directiveNode . allExtends ) ) ;
5312
+ this . allExtendsStack . push ( newAllExtends ) ;
5189
5313
} ,
5190
5314
visitDirectiveOut : function ( directiveNode ) {
5191
5315
this . allExtendsStack . length = this . allExtendsStack . length - 1 ;
0 commit comments