99// ------------------------------------------------------------------------------
1010
1111const 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+ // ------------------------------------------------------------------------------
1321
1422const DEFAULT_ORDER = Object . freeze ( [ [ 'script' , 'template' ] , 'style' ] )
1523
@@ -47,32 +55,46 @@ module.exports = {
4755 ] ,
4856 messages : {
4957 unexpected :
50- '<{{elementName}}{{elementAttributes}}> should be above <{{firstUnorderedName}}{{firstUnorderedAttributes}}> on line {{line}}.'
58+ " '<{{elementName}}{{elementAttributes}}>' should be above ' <{{firstUnorderedName}}{{firstUnorderedAttributes}}>' on line {{line}}."
5159 }
5260 } ,
5361 /**
5462 * @param {RuleContext } context - The rule context.
5563 * @returns {RuleListener } AST event handlers.
5664 */
5765 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 = [ ]
6074 /** @type {(string|string[])[] } */
6175 const orderOptions =
6276 ( 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+ } )
6785 }
6886 } else {
69- orderMap . set ( nameOrNames , index )
87+ orders . push ( {
88+ selectorText : selectorOrSelectors ,
89+ selector : parseSelector ( selectorOrSelectors , context ) ,
90+ index
91+ } )
7092 }
7193 } )
7294
7395 /**
7496 * @param {VElement } element
75- * @return {String }
97+ * @return {string }
7698 */
7799 function getAttributeString ( element ) {
78100 return element . startTag . attributes
@@ -83,61 +105,18 @@ module.exports = {
83105
84106 return `${ attribute . key . name } ${
85107 attribute . value && attribute . value . value
86- ? '=' + attribute . value . value
108+ ? `= ${ attribute . value . value } `
87109 : ''
88110 } `
89111 } )
90112 . join ( ' ' )
91113 }
92114
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-
130115 /**
131116 * @param {VElement } element
132117 */
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 ) )
141120 }
142121 const documentFragment =
143122 context . parserServices . getDocumentFragment &&
@@ -156,18 +135,34 @@ module.exports = {
156135 return
157136 }
158137 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+ )
159154 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
166157 . 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 ]
169162 if ( firstUnordered ) {
170- const firstUnorderedttributes = getAttributeString ( firstUnordered )
163+ const firstUnorderedAttributes = getAttributeString (
164+ firstUnordered . element
165+ )
171166 const elementAttributes = getAttributeString ( element )
172167
173168 context . report ( {
@@ -177,18 +172,18 @@ module.exports = {
177172 data : {
178173 elementName : element . name ,
179174 elementAttributes : elementAttributes
180- ? ' ' + elementAttributes
175+ ? ` ${ elementAttributes } `
181176 : '' ,
182- firstUnorderedName : firstUnordered . name ,
183- firstUnorderedAttributes : firstUnorderedttributes
184- ? ' ' + firstUnorderedttributes
177+ firstUnorderedName : firstUnordered . element . name ,
178+ firstUnorderedAttributes : firstUnorderedAttributes
179+ ? ` ${ firstUnorderedAttributes } `
185180 : '' ,
186- line : firstUnordered . loc . start . line
181+ line : firstUnordered . element . loc . start . line
187182 } ,
188183 * fix ( fixer ) {
189184 // insert element before firstUnordered
190185 const fixedElements = elements . flatMap ( ( it ) => {
191- if ( it === firstUnordered ) {
186+ if ( it === firstUnordered . element ) {
192187 return [ element , it ]
193188 } else if ( it === element ) {
194189 return [ ]
0 commit comments