Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 31666df

Browse files
committed
Add block-attributes-order rule
1 parent 6173b91 commit 31666df

File tree

4 files changed

+556
-4
lines changed

4 files changed

+556
-4
lines changed

‎.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ yarn.lock
99
yarn-error.log
1010
docs/.vuepress/dist
1111
typings/eslint/lib/rules
12+
.DS_Store

‎lib/rules/attributes-order.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -382,10 +382,10 @@ module.exports = {
382382
{
383383
type: 'array',
384384
items: {
385-
enum: Object.values(ATTRS),
386-
uniqueItems: true,
387-
additionalItems: false
388-
}
385+
enum: Object.values(ATTRS)
386+
},
387+
uniqueItems: true,
388+
additionalItems: false
389389
}
390390
]
391391
},

‎lib/rules/block-attributes-order.js

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
/**
2+
* @fileoverview enforce ordering of block attributes
3+
* @author Wenlu Wang
4+
*/
5+
'use strict'
6+
const utils = require('../utils')
7+
8+
/**
9+
* @enum {string}
10+
*/
11+
const WELL_KNOWN_TEMPLATE_ATTRS = {
12+
functional: 'functional',
13+
lang: 'lang',
14+
src: 'src'
15+
}
16+
17+
/**
18+
* @enum {string}
19+
*/
20+
const WELL_KNOWN_SCRIPT_ATTRS = {
21+
lang: 'lang',
22+
setup: 'setup',
23+
src: 'src'
24+
}
25+
26+
/**
27+
* @enum {string}
28+
*/
29+
const WELL_KNOWN_STYLE_ATTRS = {
30+
scoped: 'scoped',
31+
module: 'module',
32+
lang: 'lang',
33+
src: 'src'
34+
}
35+
36+
/**
37+
* @template {string} T
38+
* @typedef {T | T[]} OrderItem
39+
*/
40+
41+
/**
42+
* @typedef WellKnownOrders
43+
* @property { OrderItem<WELL_KNOWN_TEMPLATE_ATTRS>[] } [template]
44+
* @property { OrderItem<WELL_KNOWN_SCRIPT_ATTRS>[] } [script]
45+
* @property { OrderItem<WELL_KNOWN_STYLE_ATTRS>[] } [style]
46+
*/
47+
48+
/**
49+
* @typedef UserOptions
50+
* @property {WellKnownOrders & Record<string, OrderItem<string>[]>} [order]
51+
*/
52+
53+
/**
54+
* Normalizes a given options.
55+
* @param {UserOptions?} options An option to parse.
56+
* @return {WellKnownOrders & Record<string, OrderItem<string>[]>}
57+
*/
58+
function normalizeOptions(options) {
59+
if (!options || !options.order) {
60+
return {
61+
template: [
62+
WELL_KNOWN_TEMPLATE_ATTRS.functional,
63+
WELL_KNOWN_TEMPLATE_ATTRS.lang,
64+
WELL_KNOWN_TEMPLATE_ATTRS.src
65+
],
66+
script: [
67+
WELL_KNOWN_SCRIPT_ATTRS.lang,
68+
WELL_KNOWN_SCRIPT_ATTRS.setup,
69+
WELL_KNOWN_SCRIPT_ATTRS.src
70+
],
71+
style: [
72+
WELL_KNOWN_STYLE_ATTRS.lang,
73+
WELL_KNOWN_STYLE_ATTRS.module,
74+
WELL_KNOWN_STYLE_ATTRS.scoped,
75+
WELL_KNOWN_STYLE_ATTRS.src
76+
]
77+
}
78+
}
79+
return options.order
80+
}
81+
82+
/**
83+
* @param {WellKnownOrders & Record<string, OrderItem<string>[]>} order
84+
*/
85+
function normalizeAttributePositions(order) {
86+
/**
87+
* @type { Record<string, Record<string, number>> }
88+
*/
89+
const attributePositions = {}
90+
for (const [blockName, blockOrder] of Object.entries(order)) {
91+
/**
92+
* @type { Record<string, number> }
93+
*/
94+
const attributePosition = {}
95+
for (const [i, o] of blockOrder.entries()) {
96+
if (Array.isArray(o)) {
97+
for (const attr of o) {
98+
attributePosition[attr] = i
99+
}
100+
} else {
101+
attributePosition[o] = i
102+
}
103+
}
104+
attributePositions[blockName] = attributePosition
105+
}
106+
return attributePositions
107+
}
108+
109+
/**
110+
* @param {VAttribute | VDirective} attribute
111+
* @param { Record<string, number> } attributePosition
112+
* @returns {number | null}
113+
*/
114+
function getPosition(attribute, attributePosition) {
115+
if (attribute.directive) {
116+
return null
117+
}
118+
119+
return attributePosition[attribute.key.name]
120+
}
121+
122+
/**
123+
* @param {VStartTag} node
124+
* @param {Record<string, number>} attributePosition
125+
*/
126+
function getAttributeAndPositionList(node, attributePosition) {
127+
/**
128+
* @type {{ attr: (VAttribute | VDirective), position: number }[]}
129+
*/
130+
const results = []
131+
for (const attr of node.attributes) {
132+
const position = getPosition(attr, attributePosition)
133+
if (position == null) {
134+
continue
135+
}
136+
results.push({ attr, position })
137+
}
138+
return results
139+
}
140+
141+
/**
142+
* @param {RuleContext} context - The rule context.
143+
* @returns {RuleListener} AST event handlers.
144+
*/
145+
function create(context) {
146+
const sourceCode = context.getSourceCode()
147+
const order = normalizeOptions(context.options[0])
148+
const attributeAndPositions = normalizeAttributePositions(order)
149+
150+
/**
151+
* @param {VAttribute | VDirective} node
152+
* @param {VAttribute | VDirective} previousNode
153+
*/
154+
function reportIssue(node, previousNode) {
155+
const currentNodeText = sourceCode.getText(node.key)
156+
const prevNode = sourceCode.getText(previousNode.key)
157+
158+
/**
159+
* @param {RuleFixer} fixer
160+
*/
161+
function fix(fixer) {
162+
const attributes = node.parent.attributes
163+
164+
const previousNodes = attributes.slice(
165+
attributes.indexOf(previousNode),
166+
attributes.indexOf(node)
167+
)
168+
const moveNodes = [node]
169+
for (const n of previousNodes) {
170+
moveNodes.push(n)
171+
}
172+
173+
return moveNodes.map((moveNode, index) => {
174+
const text = sourceCode.getText(moveNode)
175+
return fixer.replaceText(previousNodes[index] || node, text)
176+
})
177+
}
178+
179+
context.report({
180+
node,
181+
message: `Attribute "${currentNodeText}" should go before "${prevNode}".`,
182+
data: {
183+
currentNodeText
184+
},
185+
fix
186+
})
187+
}
188+
189+
/**
190+
* @param {VElement} element
191+
* @returns {void}
192+
*/
193+
function verify(element) {
194+
const tag = element.name
195+
const attributePosition = attributeAndPositions[tag]
196+
if (!attributePosition) {
197+
return
198+
}
199+
200+
const attributeAndPositionList = getAttributeAndPositionList(
201+
element.startTag,
202+
attributePosition
203+
)
204+
if (attributeAndPositionList.length <= 1) {
205+
return
206+
}
207+
208+
let { attr: previousNode, position: previousPosition } =
209+
attributeAndPositionList[0]
210+
for (let index = 1; index < attributeAndPositionList.length; index++) {
211+
const { attr, position } = attributeAndPositionList[index]
212+
if (previousPosition <= position) {
213+
previousNode = attr
214+
previousPosition = position
215+
} else {
216+
reportIssue(attr, previousNode)
217+
}
218+
}
219+
}
220+
221+
return utils.defineDocumentVisitor(context, {
222+
'VDocumentFragment > VElement': verify
223+
})
224+
}
225+
226+
module.exports = {
227+
meta: {
228+
type: 'suggestion',
229+
docs: {
230+
description: 'enforce order of block attributes',
231+
categories: undefined,
232+
url: 'https://eslint.vuejs.org/rules/block-attributes-order.html'
233+
},
234+
fixable: 'code',
235+
schema: [
236+
{
237+
type: 'object',
238+
properties: {
239+
order: {
240+
type: 'object',
241+
properties: {
242+
template: {
243+
type: 'array',
244+
items: {
245+
anyOf: [
246+
{ enum: Object.values(WELL_KNOWN_TEMPLATE_ATTRS) },
247+
{ type: 'string' },
248+
{
249+
type: 'array',
250+
items: {
251+
anyOf: [
252+
{ enum: Object.values(WELL_KNOWN_TEMPLATE_ATTRS) },
253+
{ type: 'string' }
254+
]
255+
},
256+
uniqueItems: true
257+
}
258+
]
259+
},
260+
uniqueItems: true
261+
},
262+
script: {
263+
type: 'array',
264+
items: {
265+
anyOf: [
266+
{ enum: Object.values(WELL_KNOWN_SCRIPT_ATTRS) },
267+
{ type: 'string' },
268+
{
269+
type: 'array',
270+
items: {
271+
anyOf: [
272+
{ enum: Object.values(WELL_KNOWN_SCRIPT_ATTRS) },
273+
{ type: 'string' }
274+
]
275+
},
276+
uniqueItems: true
277+
}
278+
]
279+
},
280+
uniqueItems: true
281+
},
282+
style: {
283+
type: 'array',
284+
items: {
285+
anyOf: [
286+
{ enum: Object.values(WELL_KNOWN_STYLE_ATTRS) },
287+
{ type: 'string' },
288+
{
289+
type: 'array',
290+
items: {
291+
anyOf: [
292+
{ enum: Object.values(WELL_KNOWN_STYLE_ATTRS) },
293+
{ type: 'string' }
294+
]
295+
},
296+
uniqueItems: true
297+
}
298+
]
299+
},
300+
uniqueItems: true
301+
}
302+
},
303+
additionalProperties: {
304+
type: 'array',
305+
items: {
306+
type: 'string'
307+
},
308+
uniqueItems: true
309+
}
310+
}
311+
},
312+
additionalProperties: false
313+
}
314+
]
315+
},
316+
create
317+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /