Skip to content

Commit 012e2df

Browse files
authored
Add support for defineOptions and defineSlots to vue/define-macros-order rule (#2154)
1 parent 7bc4a9e commit 012e2df

File tree

3 files changed

+189
-68
lines changed

3 files changed

+189
-68
lines changed

Diff for: docs/rules/define-macros-order.md

+46-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ This rule reports the `defineProps` and `defineEmits` compiler macros when they
2525
}
2626
```
2727

28-
- `order` (`string[]`) ... The order of defineEmits and defineProps macros
28+
- `order` (`string[]`) ... The order of defineEmits and defineProps macros. You can also add `"defineOptions"` and `"defineSlots"`.
2929

3030
### `{ "order": ["defineProps", "defineEmits"] }` (default)
3131

@@ -66,6 +66,51 @@ defineEmits(/* ... */)
6666

6767
</eslint-code-block>
6868

69+
### `{ "order": ["defineOptions", "defineProps", "defineEmits", "defineSlots"] }` (default)
70+
71+
<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots']}]}">
72+
73+
```vue
74+
<!-- ✓ GOOD -->
75+
<script setup>
76+
defineOptions({/* ... */})
77+
defineProps(/* ... */)
78+
defineEmits(/* ... */)
79+
const slots = defineSlots()
80+
</script>
81+
```
82+
83+
</eslint-code-block>
84+
85+
<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots']}]}">
86+
87+
```vue
88+
<!-- ✗ BAD -->
89+
<script setup>
90+
defineEmits(/* ... */)
91+
const slots = defineSlots()
92+
defineProps(/* ... */)
93+
defineOptions({/* ... */})
94+
</script>
95+
```
96+
97+
</eslint-code-block>
98+
99+
<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots']}]}">
100+
101+
```vue
102+
<!-- ✗ BAD -->
103+
<script setup>
104+
const bar = ref()
105+
defineOptions({/* ... */})
106+
defineProps(/* ... */)
107+
defineEmits(/* ... */)
108+
const slots = defineSlots()
109+
</script>
110+
```
111+
112+
</eslint-code-block>
113+
69114
## :rocket: Version
70115

71116
This rule was introduced in eslint-plugin-vue v8.7.0

Diff for: lib/rules/define-macros-order.js

+41-67
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ const utils = require('../utils')
88

99
const MACROS_EMITS = 'defineEmits'
1010
const MACROS_PROPS = 'defineProps'
11-
const ORDER = [MACROS_EMITS, MACROS_PROPS]
11+
const MACROS_OPTIONS = 'defineOptions'
12+
const MACROS_SLOTS = 'defineSlots'
13+
const ORDER_SCHEMA = [MACROS_EMITS, MACROS_PROPS, MACROS_OPTIONS, MACROS_SLOTS]
1214
const DEFAULT_ORDER = [MACROS_PROPS, MACROS_EMITS]
1315

1416
/**
@@ -103,97 +105,69 @@ function create(context) {
103105
},
104106
onDefineEmitsExit(node) {
105107
macrosNodes.set(MACROS_EMITS, getDefineMacrosStatement(node))
108+
},
109+
onDefineOptionsExit(node) {
110+
macrosNodes.set(MACROS_OPTIONS, getDefineMacrosStatement(node))
111+
},
112+
onDefineSlotsExit(node) {
113+
macrosNodes.set(MACROS_SLOTS, getDefineMacrosStatement(node))
106114
}
107115
}),
108116
{
109117
'Program:exit'(program) {
110-
const shouldFirstNode = macrosNodes.get(order[0])
111-
const shouldSecondNode = macrosNodes.get(order[1])
118+
/**
119+
* @typedef {object} OrderedData
120+
* @property {string} name
121+
* @property {ASTNode} node
122+
*/
112123
const firstStatementIndex = getTargetStatementPosition(
113124
scriptSetup,
114125
program
115126
)
116-
const firstStatement = program.body[firstStatementIndex]
127+
const orderedList = order
128+
.map((name) => ({ name, node: macrosNodes.get(name) }))
129+
.filter(
130+
/** @returns {data is OrderedData} */
131+
(data) => utils.isDef(data.node)
132+
)
117133

118-
// have both defineEmits and defineProps
119-
if (shouldFirstNode && shouldSecondNode) {
120-
const secondStatement = program.body[firstStatementIndex + 1]
134+
for (const [index, should] of orderedList.entries()) {
135+
const targetStatement = program.body[firstStatementIndex + index]
121136

122-
// need move only first
123-
if (firstStatement === shouldSecondNode) {
124-
reportNotOnTop(order[0], shouldFirstNode, firstStatement)
137+
if (should.node !== targetStatement) {
138+
let moveTargetNodes = orderedList
139+
.slice(index)
140+
.map(({ node }) => node)
141+
const targetStatementIndex =
142+
moveTargetNodes.indexOf(targetStatement)
143+
if (targetStatementIndex >= 0) {
144+
moveTargetNodes = moveTargetNodes.slice(0, targetStatementIndex)
145+
}
146+
reportNotOnTop(should.name, moveTargetNodes, targetStatement)
125147
return
126148
}
127-
128-
// need move both defineEmits and defineProps
129-
if (firstStatement !== shouldFirstNode) {
130-
reportBothNotOnTop(
131-
shouldFirstNode,
132-
shouldSecondNode,
133-
firstStatement
134-
)
135-
return
136-
}
137-
138-
// need move only second
139-
if (secondStatement !== shouldSecondNode) {
140-
reportNotOnTop(order[1], shouldSecondNode, secondStatement)
141-
}
142-
143-
return
144-
}
145-
146-
// have only first and need to move it
147-
if (shouldFirstNode && firstStatement !== shouldFirstNode) {
148-
reportNotOnTop(order[0], shouldFirstNode, firstStatement)
149-
return
150-
}
151-
152-
// have only second and need to move it
153-
if (shouldSecondNode && firstStatement !== shouldSecondNode) {
154-
reportNotOnTop(order[1], shouldSecondNode, firstStatement)
155149
}
156150
}
157151
}
158152
)
159153

160-
/**
161-
* @param {ASTNode} shouldFirstNode
162-
* @param {ASTNode} shouldSecondNode
163-
* @param {ASTNode} before
164-
*/
165-
function reportBothNotOnTop(shouldFirstNode, shouldSecondNode, before) {
166-
context.report({
167-
node: shouldFirstNode,
168-
loc: shouldFirstNode.loc,
169-
messageId: 'macrosNotOnTop',
170-
data: {
171-
macro: order[0]
172-
},
173-
fix(fixer) {
174-
return [
175-
...moveNodeBefore(fixer, shouldFirstNode, before),
176-
...moveNodeBefore(fixer, shouldSecondNode, before)
177-
]
178-
}
179-
})
180-
}
181-
182154
/**
183155
* @param {string} macro
184-
* @param {ASTNode} node
156+
* @param {ASTNode[]} nodes
185157
* @param {ASTNode} before
186158
*/
187-
function reportNotOnTop(macro, node, before) {
159+
function reportNotOnTop(macro, nodes, before) {
188160
context.report({
189-
node,
190-
loc: node.loc,
161+
node: nodes[0],
162+
loc: nodes[0].loc,
191163
messageId: 'macrosNotOnTop',
192164
data: {
193165
macro
194166
},
195-
fix(fixer) {
196-
return moveNodeBefore(fixer, node, before)
167+
*fix(fixer) {
168+
for (const node of nodes) {
169+
yield* moveNodeBefore(fixer, node, before)
170+
}
197171
}
198172
})
199173
}
@@ -288,7 +262,7 @@ module.exports = {
288262
order: {
289263
type: 'array',
290264
items: {
291-
enum: Object.values(ORDER)
265+
enum: ORDER_SCHEMA
292266
},
293267
uniqueItems: true,
294268
additionalItems: false

Diff for: tests/lib/rules/define-macros-order.js

+102
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,28 @@ tester.run('define-macros-order', rule, {
148148
<script setup>
149149
</script>
150150
`
151+
},
152+
{
153+
filename: 'test.vue',
154+
code: `
155+
<script setup>
156+
import Foo from 'foo'
157+
/** options */
158+
defineOptions({})
159+
/** emits */
160+
defineEmits(['update:foo'])
161+
/** props */
162+
const props = defineProps(['foo'])
163+
/** slots */
164+
const slots = defineSlots()
165+
console.log('test1')
166+
</script>
167+
`,
168+
options: [
169+
{
170+
order: ['defineOptions', 'defineEmits', 'defineProps', 'defineSlots']
171+
}
172+
]
151173
}
152174
],
153175
invalid: [
@@ -520,6 +542,86 @@ tester.run('define-macros-order', rule, {
520542
line: 5
521543
}
522544
]
545+
},
546+
{
547+
filename: 'test.vue',
548+
code: `
549+
<script setup>
550+
import Foo from 'foo'
551+
console.log('test1')
552+
/** emits */
553+
defineEmits(['update:foo'])
554+
/** props */
555+
const props = defineProps(['foo'])
556+
/** slots */
557+
const slots = defineSlots()
558+
/** options */
559+
defineOptions({})
560+
</script>
561+
`,
562+
output: `
563+
<script setup>
564+
import Foo from 'foo'
565+
/** options */
566+
defineOptions({})
567+
/** emits */
568+
defineEmits(['update:foo'])
569+
/** props */
570+
const props = defineProps(['foo'])
571+
/** slots */
572+
const slots = defineSlots()
573+
console.log('test1')
574+
</script>
575+
`,
576+
options: [
577+
{
578+
order: ['defineOptions', 'defineEmits', 'defineProps', 'defineSlots']
579+
}
580+
],
581+
errors: [
582+
{
583+
message: message('defineOptions'),
584+
line: 12
585+
}
586+
]
587+
},
588+
{
589+
filename: 'test.vue',
590+
code: `
591+
<script setup>
592+
/** slots */
593+
const slots = defineSlots()
594+
/** options */
595+
defineOptions({})
596+
/** emits */
597+
defineEmits(['update:foo'])
598+
/** props */
599+
const props = defineProps(['foo'])
600+
</script>
601+
`,
602+
output: `
603+
<script setup>
604+
/** options */
605+
defineOptions({})
606+
/** emits */
607+
defineEmits(['update:foo'])
608+
/** props */
609+
const props = defineProps(['foo'])
610+
/** slots */
611+
const slots = defineSlots()
612+
</script>
613+
`,
614+
options: [
615+
{
616+
order: ['defineOptions', 'defineEmits', 'defineProps', 'defineSlots']
617+
}
618+
],
619+
errors: [
620+
{
621+
message: message('defineOptions'),
622+
line: 6
623+
}
624+
]
523625
}
524626
]
525627
})

0 commit comments

Comments
 (0)