@@ -2,6 +2,7 @@ import Node from './node';
22import Selector from './selector' ;
33import Ruleset from './ruleset' ;
44import Anonymous from './anonymous' ;
5+ import NestableAtRulePrototype from './nested-at-rule' ;
56
67const AtRule = function (
78 name ,
@@ -14,19 +15,45 @@ const AtRule = function(
1415 visibilityInfo
1516) {
1617 let i ;
18+ var selectors = ( new Selector ( [ ] , null , null , this . _index , this . _fileInfo ) ) . createEmptySelectors ( ) ;
1719
1820 this . name = name ;
1921 this . value = ( value instanceof Node ) ? value : ( value ? new Anonymous ( value ) : value ) ;
2022 if ( rules ) {
2123 if ( Array . isArray ( rules ) ) {
22- this . rules = rules ;
24+ const allDeclarations = this . declarationsBlock ( rules ) ;
25+
26+ let allRulesetDeclarations = true ;
27+ rules . forEach ( rule => {
28+ if ( rule . type === 'Ruleset' && rule . rules ) allRulesetDeclarations = allRulesetDeclarations && this . declarationsBlock ( rule . rules , true ) ;
29+ } ) ;
30+
31+ if ( allDeclarations && ! isRooted ) {
32+ this . simpleBlock = true ;
33+ this . declarations = rules ;
34+ } else if ( allRulesetDeclarations && rules . length === 1 && ! isRooted && ! value ) {
35+ this . simpleBlock = true ;
36+ this . declarations = rules [ 0 ] . rules ? rules [ 0 ] . rules : rules ;
37+ } else {
38+ this . rules = rules ;
39+ }
2340 } else {
24- this . rules = [ rules ] ;
25- this . rules [ 0 ] . selectors = ( new Selector ( [ ] , null , null , index , currentFileInfo ) ) . createEmptySelectors ( ) ;
41+ const allDeclarations = this . declarationsBlock ( rules . rules ) ;
42+
43+ if ( allDeclarations && ! isRooted && ! value ) {
44+ this . simpleBlock = true ;
45+ this . declarations = rules . rules ;
46+ } else {
47+ this . rules = [ rules ] ;
48+ this . rules [ 0 ] . selectors = ( new Selector ( [ ] , null , null , index , currentFileInfo ) ) . createEmptySelectors ( ) ;
49+ }
2650 }
27- for ( i = 0 ; i < this . rules . length ; i ++ ) {
28- this . rules [ i ] . allowImports = true ;
51+ if ( ! this . simpleBlock ) {
52+ for ( i = 0 ; i < this . rules . length ; i ++ ) {
53+ this . rules [ i ] . allowImports = true ;
54+ }
2955 }
56+ this . setParent ( selectors , this ) ;
3057 this . setParent ( this . rules , this ) ;
3158 }
3259 this . _index = index ;
@@ -39,10 +66,24 @@ const AtRule = function(
3966
4067AtRule . prototype = Object . assign ( new Node ( ) , {
4168 type : 'AtRule' ,
69+
70+ ...NestableAtRulePrototype ,
71+
72+ declarationsBlock ( rules , mergeable = false ) {
73+ if ( ! mergeable ) {
74+ return rules . filter ( function ( node ) { return ( node . type === 'Declaration' || node . type === 'Comment' ) && ! node . merge } ) . length === rules . length ;
75+ } else {
76+ return rules . filter ( function ( node ) { return ( node . type === 'Declaration' || node . type === 'Comment' ) ; } ) . length === rules . length ;
77+ }
78+ } ,
79+
4280 accept ( visitor ) {
43- const value = this . value , rules = this . rules ;
81+ const value = this . value , rules = this . rules , declarations = this . declarations ;
82+
4483 if ( rules ) {
4584 this . rules = visitor . visitArray ( rules ) ;
85+ } else if ( declarations ) {
86+ this . declarations = visitor . visitArray ( declarations ) ;
4687 }
4788 if ( value ) {
4889 this . value = visitor . visit ( value ) ;
@@ -58,22 +99,24 @@ AtRule.prototype = Object.assign(new Node(), {
5899 } ,
59100
60101 genCSS ( context , output ) {
61- const value = this . value , rules = this . rules ;
102+ const value = this . value , rules = this . rules || this . declarations ;
62103 output . add ( this . name , this . fileInfo ( ) , this . getIndex ( ) ) ;
63104 if ( value ) {
64105 output . add ( ' ' ) ;
65106 value . genCSS ( context , output ) ;
66107 }
67- if ( rules ) {
108+ if ( this . simpleBlock ) {
109+ this . outputRuleset ( context , output , this . declarations ) ;
110+ } else if ( rules ) {
68111 this . outputRuleset ( context , output , rules ) ;
69112 } else {
70113 output . add ( ';' ) ;
71114 }
72115 } ,
73116
74117 eval ( context ) {
75- let mediaPathBackup , mediaBlocksBackup , value = this . value , rules = this . rules ;
76-
118+ let mediaPathBackup , mediaBlocksBackup , value = this . value , rules = this . rules || this . declarations ;
119+
77120 // media stored inside other atrule should not bubble over it
78121 // backpup media bubbling information
79122 mediaPathBackup = context . mediaPath ;
@@ -85,17 +128,78 @@ AtRule.prototype = Object.assign(new Node(), {
85128 if ( value ) {
86129 value = value . eval ( context ) ;
87130 }
131+
88132 if ( rules ) {
89- // assuming that there is only one rule at this point - that is how parser constructs the rule
90- rules = [ rules [ 0 ] . eval ( context ) ] ;
91- rules [ 0 ] . root = true ;
133+ rules = this . evalRoot ( context , rules ) ;
134+ }
135+ if ( Array . isArray ( rules ) && rules [ 0 ] . rules && Array . isArray ( rules [ 0 ] . rules ) && rules [ 0 ] . rules . length ) {
136+ const allMergeableDeclarations = this . declarationsBlock ( rules [ 0 ] . rules , true ) ;
137+ if ( allMergeableDeclarations && ! this . isRooted && ! value ) {
138+ var mergeRules = context . pluginManager . less . visitors . ToCSSVisitor . prototype . _mergeRules ;
139+ mergeRules ( rules [ 0 ] . rules ) ;
140+ rules = rules [ 0 ] . rules ;
141+ rules . forEach ( rule => rule . merge = false ) ;
142+ }
143+ }
144+ if ( this . simpleBlock && rules ) {
145+ rules [ 0 ] . functionRegistry = context . frames [ 0 ] . functionRegistry . inherit ( ) ;
146+ rules = rules . map ( function ( rule ) { return rule . eval ( context ) ; } ) ;
92147 }
148+
93149 // restore media bubbling information
94150 context . mediaPath = mediaPathBackup ;
95151 context . mediaBlocks = mediaBlocksBackup ;
152+ return new AtRule ( this . name , value , rules , this . getIndex ( ) , this . fileInfo ( ) , this . debugInfo , this . isRooted , this . visibilityInfo ( ) ) ;
153+ } ,
96154
97- return new AtRule ( this . name , value , rules ,
98- this . getIndex ( ) , this . fileInfo ( ) , this . debugInfo , this . isRooted , this . visibilityInfo ( ) ) ;
155+ evalRoot ( context , rules ) {
156+ let ampersandCount = 0 ;
157+ let noAmpersandCount = 0 ;
158+ let noAmpersands = true ;
159+ let allAmpersands = false ;
160+
161+ if ( ! this . simpleBlock ) {
162+ rules = [ rules [ 0 ] . eval ( context ) ] ;
163+ }
164+
165+ let precedingSelectors = [ ] ;
166+ if ( context . frames . length > 0 ) {
167+ for ( let index = 0 ; index < context . frames . length ; index ++ ) {
168+ const frame = context . frames [ index ] ;
169+ if (
170+ frame . type === 'Ruleset' &&
171+ frame . rules &&
172+ frame . rules . length > 0
173+ ) {
174+ if ( frame && ! frame . root && frame . selectors && frame . selectors . length > 0 ) {
175+ precedingSelectors = precedingSelectors . concat ( frame . selectors ) ;
176+ }
177+ }
178+ if ( precedingSelectors . length > 0 ) {
179+ let value = '' ;
180+ const output = { add : function ( s ) { value += s ; } } ;
181+ for ( let i = 0 ; i < precedingSelectors . length ; i ++ ) {
182+ precedingSelectors [ i ] . genCSS ( context , output ) ;
183+ }
184+ if ( / ^ & + $ / . test ( value . replace ( / \s + / g, '' ) ) ) {
185+ noAmpersands = false ;
186+ noAmpersandCount ++ ;
187+ } else {
188+ allAmpersands = false ;
189+ ampersandCount ++ ;
190+ }
191+ }
192+ }
193+ }
194+
195+ const mixedAmpersands = ampersandCount > 0 && noAmpersandCount > 0 && ! allAmpersands && ! noAmpersands ;
196+ if (
197+ ( this . isRooted && ampersandCount > 0 && noAmpersandCount === 0 && ! allAmpersands && noAmpersands )
198+ || ! mixedAmpersands
199+ ) {
200+ rules [ 0 ] . root = true ;
201+ }
202+ return rules ;
99203 } ,
100204
101205 variable ( name ) {
0 commit comments