@@ -2,6 +2,7 @@ import Node from './node';
2
2
import Selector from './selector' ;
3
3
import Ruleset from './ruleset' ;
4
4
import Anonymous from './anonymous' ;
5
+ import NestableAtRulePrototype from './nested-at-rule' ;
5
6
6
7
const AtRule = function (
7
8
name ,
@@ -14,19 +15,45 @@ const AtRule = function(
14
15
visibilityInfo
15
16
) {
16
17
let i ;
18
+ var selectors = ( new Selector ( [ ] , null , null , this . _index , this . _fileInfo ) ) . createEmptySelectors ( ) ;
17
19
18
20
this . name = name ;
19
21
this . value = ( value instanceof Node ) ? value : ( value ? new Anonymous ( value ) : value ) ;
20
22
if ( rules ) {
21
23
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
+ }
23
40
} 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
+ }
26
50
}
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
+ }
29
55
}
56
+ this . setParent ( selectors , this ) ;
30
57
this . setParent ( this . rules , this ) ;
31
58
}
32
59
this . _index = index ;
@@ -39,10 +66,24 @@ const AtRule = function(
39
66
40
67
AtRule . prototype = Object . assign ( new Node ( ) , {
41
68
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
+
42
80
accept ( visitor ) {
43
- const value = this . value , rules = this . rules ;
81
+ const value = this . value , rules = this . rules , declarations = this . declarations ;
82
+
44
83
if ( rules ) {
45
84
this . rules = visitor . visitArray ( rules ) ;
85
+ } else if ( declarations ) {
86
+ this . declarations = visitor . visitArray ( declarations ) ;
46
87
}
47
88
if ( value ) {
48
89
this . value = visitor . visit ( value ) ;
@@ -58,22 +99,24 @@ AtRule.prototype = Object.assign(new Node(), {
58
99
} ,
59
100
60
101
genCSS ( context , output ) {
61
- const value = this . value , rules = this . rules ;
102
+ const value = this . value , rules = this . rules || this . declarations ;
62
103
output . add ( this . name , this . fileInfo ( ) , this . getIndex ( ) ) ;
63
104
if ( value ) {
64
105
output . add ( ' ' ) ;
65
106
value . genCSS ( context , output ) ;
66
107
}
67
- if ( rules ) {
108
+ if ( this . simpleBlock ) {
109
+ this . outputRuleset ( context , output , this . declarations ) ;
110
+ } else if ( rules ) {
68
111
this . outputRuleset ( context , output , rules ) ;
69
112
} else {
70
113
output . add ( ';' ) ;
71
114
}
72
115
} ,
73
116
74
117
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
+
77
120
// media stored inside other atrule should not bubble over it
78
121
// backpup media bubbling information
79
122
mediaPathBackup = context . mediaPath ;
@@ -85,17 +128,78 @@ AtRule.prototype = Object.assign(new Node(), {
85
128
if ( value ) {
86
129
value = value . eval ( context ) ;
87
130
}
131
+
88
132
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 ) ; } ) ;
92
147
}
148
+
93
149
// restore media bubbling information
94
150
context . mediaPath = mediaPathBackup ;
95
151
context . mediaBlocks = mediaBlocksBackup ;
152
+ return new AtRule ( this . name , value , rules , this . getIndex ( ) , this . fileInfo ( ) , this . debugInfo , this . isRooted , this . visibilityInfo ( ) ) ;
153
+ } ,
96
154
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 ;
99
203
} ,
100
204
101
205
variable ( name ) {
0 commit comments