9
9
// ------------------------------------------------------------------------------
10
10
11
11
const utils = require ( '../utils' )
12
- const parser = require ( 'postcss-selector-parser' )
12
+ const { parseSelector } = require ( '../utils/selector' )
13
+
14
+ /**
15
+ * @typedef {import('../utils/selector').VElementSelector } VElementSelector
16
+ */
17
+
18
+ // ------------------------------------------------------------------------------
19
+ // Helpers
20
+ // ------------------------------------------------------------------------------
13
21
14
22
const DEFAULT_ORDER = Object . freeze ( [ [ 'script' , 'template' ] , 'style' ] )
15
23
@@ -47,32 +55,46 @@ module.exports = {
47
55
] ,
48
56
messages : {
49
57
unexpected :
50
- '<{{elementName}}{{elementAttributes}}> should be above <{{firstUnorderedName}}{{firstUnorderedAttributes}}> on line {{line}}.'
58
+ " '<{{elementName}}{{elementAttributes}}>' should be above ' <{{firstUnorderedName}}{{firstUnorderedAttributes}}>' on line {{line}}."
51
59
}
52
60
} ,
53
61
/**
54
62
* @param {RuleContext } context - The rule context.
55
63
* @returns {RuleListener } AST event handlers.
56
64
*/
57
65
create ( context ) {
58
- /** @type {Map<string, number> } */
59
- const orderMap = new Map ( )
66
+ /**
67
+ * @typedef {object } OrderElement
68
+ * @property {string } selectorText
69
+ * @property {VElementSelector } selector
70
+ * @property {number } index
71
+ */
72
+ /** @type {OrderElement[] } */
73
+ const orders = [ ]
60
74
/** @type {(string|string[])[] } */
61
75
const orderOptions =
62
76
( context . options [ 0 ] && context . options [ 0 ] . order ) || DEFAULT_ORDER
63
- orderOptions . forEach ( ( nameOrNames , index ) => {
64
- if ( Array . isArray ( nameOrNames ) ) {
65
- for ( const name of nameOrNames ) {
66
- orderMap . set ( name , index )
77
+ orderOptions . forEach ( ( selectorOrSelectors , index ) => {
78
+ if ( Array . isArray ( selectorOrSelectors ) ) {
79
+ for ( const selector of selectorOrSelectors ) {
80
+ orders . push ( {
81
+ selectorText : selector ,
82
+ selector : parseSelector ( selector , context ) ,
83
+ index
84
+ } )
67
85
}
68
86
} else {
69
- orderMap . set ( nameOrNames , index )
87
+ orders . push ( {
88
+ selectorText : selectorOrSelectors ,
89
+ selector : parseSelector ( selectorOrSelectors , context ) ,
90
+ index
91
+ } )
70
92
}
71
93
} )
72
94
73
95
/**
74
96
* @param {VElement } element
75
- * @return {String }
97
+ * @return {string }
76
98
*/
77
99
function getAttributeString ( element ) {
78
100
return element . startTag . attributes
@@ -83,61 +105,18 @@ module.exports = {
83
105
84
106
return `${ attribute . key . name } ${
85
107
attribute . value && attribute . value . value
86
- ? '=' + attribute . value . value
108
+ ? `= ${ attribute . value . value } `
87
109
: ''
88
110
} `
89
111
} )
90
112
. join ( ' ' )
91
113
}
92
114
93
- /**
94
- * @param {String } ordering
95
- * @param {VElement } element
96
- * @return {Boolean } true if the element matches the selector, false otherwise
97
- */
98
- function matches ( ordering , element ) {
99
- let attributeMatches = true
100
- let isNegated = false
101
- let tagMatches = true
102
-
103
- parser ( ( selectors ) => {
104
- selectors . walk ( ( selector ) => {
105
- switch ( selector . type ) {
106
- case 'tag' :
107
- tagMatches = selector . value === element . name
108
- break
109
- case 'pseudo' :
110
- isNegated = selector . value === ':not'
111
- break
112
- case 'attribute' :
113
- attributeMatches = utils . hasAttribute (
114
- element ,
115
- selector . qualifiedAttribute ,
116
- selector . value
117
- )
118
- break
119
- }
120
- } )
121
- } ) . processSync ( ordering )
122
-
123
- if ( isNegated ) {
124
- return tagMatches && ! attributeMatches
125
- } else {
126
- return tagMatches && attributeMatches
127
- }
128
- }
129
-
130
115
/**
131
116
* @param {VElement } element
132
117
*/
133
- function getOrderPosition ( element ) {
134
- for ( const [ ordering , index ] of orderMap . entries ( ) ) {
135
- if ( matches ( ordering , element ) ) {
136
- return index
137
- }
138
- }
139
-
140
- return - 1
118
+ function getOrderElement ( element ) {
119
+ return orders . find ( ( o ) => o . selector . test ( element ) )
141
120
}
142
121
const documentFragment =
143
122
context . parserServices . getDocumentFragment &&
@@ -156,18 +135,34 @@ module.exports = {
156
135
return
157
136
}
158
137
const elements = getTopLevelHTMLElements ( )
138
+
139
+ const elementWithOrders = elements
140
+ . map ( ( element ) => {
141
+ const order = getOrderElement ( element )
142
+ return {
143
+ order,
144
+ element
145
+ }
146
+ } )
147
+ . filter (
148
+ /**
149
+ * @param { {order:OrderElement|undefined, element: VElement} } o
150
+ * @returns {o is {order:OrderElement, element: VElement} }
151
+ */
152
+ ( o ) => Boolean ( o . order )
153
+ )
159
154
const sourceCode = context . getSourceCode ( )
160
- elements . forEach ( ( element , index ) => {
161
- const expectedIndex = getOrderPosition ( element )
162
- if ( expectedIndex < 0 ) {
163
- return
164
- }
165
- const firstUnordered = elements
155
+ elementWithOrders . forEach ( ( { order : expected , element } , index ) => {
156
+ const firstUnordered = elementWithOrders
166
157
. slice ( 0 , index )
167
- . filter ( ( e ) => expectedIndex < getOrderPosition ( e ) )
168
- . sort ( ( e1 , e2 ) => getOrderPosition ( e1 ) - getOrderPosition ( e2 ) ) [ 0 ]
158
+ . filter ( ( { order } ) => {
159
+ return expected . index < order . index
160
+ } )
161
+ . sort ( ( e1 , e2 ) => e1 . order . index - e2 . order . index ) [ 0 ]
169
162
if ( firstUnordered ) {
170
- const firstUnorderedttributes = getAttributeString ( firstUnordered )
163
+ const firstUnorderedAttributes = getAttributeString (
164
+ firstUnordered . element
165
+ )
171
166
const elementAttributes = getAttributeString ( element )
172
167
173
168
context . report ( {
@@ -177,18 +172,18 @@ module.exports = {
177
172
data : {
178
173
elementName : element . name ,
179
174
elementAttributes : elementAttributes
180
- ? ' ' + elementAttributes
175
+ ? ` ${ elementAttributes } `
181
176
: '' ,
182
- firstUnorderedName : firstUnordered . name ,
183
- firstUnorderedAttributes : firstUnorderedttributes
184
- ? ' ' + firstUnorderedttributes
177
+ firstUnorderedName : firstUnordered . element . name ,
178
+ firstUnorderedAttributes : firstUnorderedAttributes
179
+ ? ` ${ firstUnorderedAttributes } `
185
180
: '' ,
186
- line : firstUnordered . loc . start . line
181
+ line : firstUnordered . element . loc . start . line
187
182
} ,
188
183
* fix ( fixer ) {
189
184
// insert element before firstUnordered
190
185
const fixedElements = elements . flatMap ( ( it ) => {
191
- if ( it === firstUnordered ) {
186
+ if ( it === firstUnordered . element ) {
192
187
return [ element , it ]
193
188
} else if ( it === element ) {
194
189
return [ ]
0 commit comments