Skip to content

Commit 1e375bc

Browse files
Improve usage of postcss-selector-parser (#1842)
* Add CSS selector parser * Change to use selector parser in `component-tags-order` rule. * Update tests/lib/utils/selector.js Co-authored-by: Flo Edelmann <[email protected]> * Update fixtures * Update lib/rules/component-tags-order.js Co-authored-by: Flo Edelmann <[email protected]> * Update lib/rules/component-tags-order.js Co-authored-by: Flo Edelmann <[email protected]> * Update lib/utils/selector.js Co-authored-by: Flo Edelmann <[email protected]> * Update tests/lib/utils/selector.js Co-authored-by: Flo Edelmann <[email protected]> * format and update fixtures * Refactor * rename var Co-authored-by: Flo Edelmann <[email protected]>
1 parent 124cc37 commit 1e375bc

File tree

136 files changed

+2365
-82
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

136 files changed

+2365
-82
lines changed

docs/rules/component-tags-order.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ This rule warns about the order of the top-level tags, such as `<script>`, `<tem
2626
}
2727
```
2828

29-
- `order` (`(string|string[])[]`) ... The order of top-level element names. default `[ [ "script", "template" ], "style" ]`. May also be CSS selectors, such as `script[setup]` and `i18n:not([lang=en])`.
29+
- `order` (`(string|string[])[]`) ... The order of top-level element names. default `[ [ "script", "template" ], "style" ]`. May also be CSS selectors, such as `script[setup]` and `i18n:not([locale=en])`.
3030

3131
### `{ "order": [ [ "script", "template" ], "style" ] }` (default)
3232

@@ -161,26 +161,26 @@ This rule warns about the order of the top-level tags, such as `<script>`, `<tem
161161

162162
</eslint-code-block>
163163

164-
### `{ 'order': ['template', 'i18n:not([lang=en])', 'i18n[lang=en]'] }`
164+
### `{ 'order': ['template', 'i18n:not([locale=en])', 'i18n[locale=en]'] }`
165165

166-
<eslint-code-block fix :rules="{'vue/component-tags-order': ['error', { 'order': ['template', 'i18n:not([lang=en])', 'i18n[lang=en]'] }]}">
166+
<eslint-code-block fix :rules="{'vue/component-tags-order': ['error', { 'order': ['template', 'i18n:not([locale=en])', 'i18n[locale=en]'] }]}">
167167

168168
```vue
169169
<!-- ✓ GOOD -->
170170
<template>...</template>
171-
<i18n lang="ja">/* ... */</i18n>
172-
<i18n lang="en">/* ... */</i18n>
171+
<i18n locale="ja">/* ... */</i18n>
172+
<i18n locale="en">/* ... */</i18n>
173173
```
174174

175175
</eslint-code-block>
176176

177-
<eslint-code-block fix :rules="{'vue/component-tags-order': ['error', { 'order': ['template', 'i18n:not([lang=en])', 'i18n[lang=en]'] }]}">
177+
<eslint-code-block fix :rules="{'vue/component-tags-order': ['error', { 'order': ['template', 'i18n:not([locale=en])', 'i18n[locale=en]'] }]}">
178178

179179
```vue
180180
<!-- ✗ BAD -->
181181
<template>...</template>
182-
<i18n lang="en">/* ... */</i18n>
183-
<i18n lang="ja">/* ... */</i18n>
182+
<i18n locale="en">/* ... */</i18n>
183+
<i18n locale="ja">/* ... */</i18n>
184184
```
185185

186186
</eslint-code-block>

lib/rules/component-tags-order.js

+50-68
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@
99
// ------------------------------------------------------------------------------
1010

1111
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+
// ------------------------------------------------------------------------------
1321

1422
const DEFAULT_ORDER = Object.freeze([['script', 'template'], 'style'])
1523

@@ -55,24 +63,38 @@ module.exports = {
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
@@ -90,54 +112,11 @@ module.exports = {
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,21 @@ module.exports = {
156135
return
157136
}
158137
const elements = getTopLevelHTMLElements()
138+
139+
const elementWithOrders = elements.flatMap((element) => {
140+
const order = getOrderElement(element)
141+
return order ? [{ order, element }] : []
142+
})
159143
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
144+
elementWithOrders.forEach(({ order: expected, element }, index) => {
145+
const firstUnordered = elementWithOrders
166146
.slice(0, index)
167-
.filter((e) => expectedIndex < getOrderPosition(e))
168-
.sort((e1, e2) => getOrderPosition(e1) - getOrderPosition(e2))[0]
147+
.filter(({ order }) => expected.index < order.index)
148+
.sort((e1, e2) => e1.order.index - e2.order.index)[0]
169149
if (firstUnordered) {
170-
const firstUnorderedttributes = getAttributeString(firstUnordered)
150+
const firstUnorderedAttributes = getAttributeString(
151+
firstUnordered.element
152+
)
171153
const elementAttributes = getAttributeString(element)
172154

173155
context.report({
@@ -179,16 +161,16 @@ module.exports = {
179161
elementAttributes: elementAttributes
180162
? ` ${elementAttributes}`
181163
: '',
182-
firstUnorderedName: firstUnordered.name,
183-
firstUnorderedAttributes: firstUnorderedttributes
184-
? ` ${firstUnorderedttributes}`
164+
firstUnorderedName: firstUnordered.element.name,
165+
firstUnorderedAttributes: firstUnorderedAttributes
166+
? ` ${firstUnorderedAttributes}`
185167
: '',
186-
line: firstUnordered.loc.start.line
168+
line: firstUnordered.element.loc.start.line
187169
},
188170
*fix(fixer) {
189171
// insert element before firstUnordered
190172
const fixedElements = elements.flatMap((it) => {
191-
if (it === firstUnordered) {
173+
if (it === firstUnordered.element) {
192174
return [element, it]
193175
} else if (it === element) {
194176
return []

0 commit comments

Comments
 (0)