Skip to content

Commit b06212d

Browse files
authored
Merge branch 'master' into slot-attribute-dynamic-component
2 parents f35bf94 + b08273c commit b06212d

12 files changed

+701
-14
lines changed

docs/rules/define-macros-order.md

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22
pageClass: rule-details
33
sidebarDepth: 0
44
title: vue/define-macros-order
5-
description: enforce order of `defineEmits` and `defineProps` compiler macros
5+
description: enforce order of compiler macros (`defineProps`, `defineEmits`, etc.)
66
since: v8.7.0
77
---
88

99
# vue/define-macros-order
1010

11-
> enforce order of `defineEmits` and `defineProps` compiler macros
11+
> enforce order of compiler macros (`defineProps`, `defineEmits`, etc.)
1212
1313
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
1414
- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
1515

1616
## :book: Rule Details
1717

18-
This rule reports the `defineProps` and `defineEmits` compiler macros when they are not the first statements in `<script setup>` (after any potential import statements or type definitions) or when they are not in the correct order.
18+
This rule reports compiler macros (like `defineProps` or `defineEmits` but also custom ones) when they are not the first statements in `<script setup>` (after any potential import statements or type definitions) or when they are not in the correct order.
1919

2020
## :wrench: Options
2121

@@ -28,7 +28,7 @@ This rule reports the `defineProps` and `defineEmits` compiler macros when they
2828
}
2929
```
3030

31-
- `order` (`string[]`) ... The order of defineEmits and defineProps macros. You can also add `"defineOptions"`, `"defineSlots"`, and `"defineModel"`.
31+
- `order` (`string[]`) ... The order in which the macros should appear. The default is `["defineProps", "defineEmits"]`.
3232
- `defineExposeLast` (`boolean`) ... Force `defineExpose` at the end.
3333

3434
### `{ "order": ["defineProps", "defineEmits"] }` (default)
@@ -118,6 +118,39 @@ const slots = defineSlots()
118118

119119
</eslint-code-block>
120120

121+
### `{ "order": ["definePage", "defineModel", "defineCustom", "defineEmits", "defineSlots"] }`
122+
123+
<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['definePage', 'defineModel', 'defineCustom', 'defineEmits', 'defineSlots']}]}">
124+
125+
```vue
126+
<!-- ✓ GOOD -->
127+
<script setup>
128+
definePage()
129+
const model = defineModel()
130+
defineCustom()
131+
defineEmits(/* ... */)
132+
const slots = defineSlots()
133+
</script>
134+
```
135+
136+
</eslint-code-block>
137+
138+
<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['definePage', 'defineModel', 'defineCustom', 'defineEmits', 'defineSlots']}]}">
139+
140+
```vue
141+
<!-- ✗ BAD -->
142+
<script setup>
143+
defineEmits(/* ... */)
144+
const slots = defineSlots()
145+
defineProps(/* ... */)
146+
defineCustom({/* ... */})
147+
const model = defineModel()
148+
definePage()
149+
</script>
150+
```
151+
152+
</eslint-code-block>
153+
121154
### `{ "defineExposeLast": true }`
122155

123156
<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {defineExposeLast: true}]}">

docs/rules/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ For example:
213213
| [vue/component-options-name-casing](./component-options-name-casing.md) | enforce the casing of component name in `components` options | :wrench::bulb: | :hammer: |
214214
| [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce specific casing for custom event name | | :hammer: |
215215
| [vue/define-emits-declaration](./define-emits-declaration.md) | enforce declaration style of `defineEmits` | | :hammer: |
216-
| [vue/define-macros-order](./define-macros-order.md) | enforce order of `defineEmits` and `defineProps` compiler macros | :wrench::bulb: | :lipstick: |
216+
| [vue/define-macros-order](./define-macros-order.md) | enforce order of compiler macros (`defineProps`, `defineEmits`, etc.) | :wrench::bulb: | :lipstick: |
217217
| [vue/define-props-declaration](./define-props-declaration.md) | enforce declaration style of `defineProps` | | :hammer: |
218218
| [vue/enforce-style-attribute](./enforce-style-attribute.md) | enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags | | :hammer: |
219219
| [vue/html-button-has-type](./html-button-has-type.md) | disallow usage of button without an explicit type attribute | | :hammer: |
@@ -224,6 +224,7 @@ For example:
224224
| [vue/match-component-import-name](./match-component-import-name.md) | require the registered component name to match the imported component name | | :warning: |
225225
| [vue/max-lines-per-block](./max-lines-per-block.md) | enforce maximum number of lines in Vue SFC blocks | | :warning: |
226226
| [vue/max-props](./max-props.md) | enforce maximum number of props in Vue component | | :warning: |
227+
| [vue/max-template-depth](./max-template-depth.md) | enforce maximum depth of template | | :warning: |
227228
| [vue/new-line-between-multi-line-property](./new-line-between-multi-line-property.md) | enforce new lines between multi-line properties in Vue components | :wrench: | :lipstick: |
228229
| [vue/next-tick-style](./next-tick-style.md) | enforce Promise or callback style in `nextTick` | :wrench: | :hammer: |
229230
| [vue/no-bare-strings-in-template](./no-bare-strings-in-template.md) | disallow the use of bare strings in `<template>` | | :hammer: |

docs/rules/max-template-depth.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/max-template-depth
5+
description: enforce maximum depth of template
6+
---
7+
8+
# vue/max-template-depth
9+
10+
> enforce maximum depth of template
11+
12+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>
13+
14+
## :book: Rule Details
15+
16+
This rule enforces a maximum depth of the template in a Vue SFC, in order to aid in maintainability and reduce complexity.
17+
18+
## :wrench: Options
19+
20+
This rule takes an object, where you can specify the maximum depth allowed in a Vue SFC template block.
21+
There is one property that can be specified for the object.
22+
23+
- `maxDepth` ... Specify the maximum template depth `template` block.
24+
25+
### `{ maxDepth: 3 }`
26+
27+
<eslint-code-block :rules="{'vue/max-template-depth': ['error', { maxDepth: 3 }]}">
28+
29+
```vue
30+
<!-- ✗ BAD -->
31+
<template>
32+
<main-container>
33+
<child-component>
34+
<div />
35+
</child-component>
36+
<child-component>
37+
<nested-component>
38+
<div>
39+
<div />
40+
</div>
41+
</nested-component>
42+
</child-component>
43+
</main-container>
44+
</template>
45+
```
46+
47+
</eslint-code-block>
48+
49+
<eslint-code-block :rules="{'vue/max-template-depth': ['error', { maxDepth: 3 }]}">
50+
51+
```vue
52+
<!-- ✓ GOOD -->
53+
<template>
54+
<main-container>
55+
<child-component>
56+
<div />
57+
</child-component>
58+
</main-container>
59+
</template>
60+
```
61+
62+
</eslint-code-block>
63+
64+
## :mag: Implementation
65+
66+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/max-template-depth.js)
67+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/max-template-depth.js)

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ const plugin = {
8080
'max-len': require('./rules/max-len'),
8181
'max-lines-per-block': require('./rules/max-lines-per-block'),
8282
'max-props': require('./rules/max-props'),
83+
'max-template-depth': require('./rules/max-template-depth'),
8384
'multi-word-component-names': require('./rules/multi-word-component-names'),
8485
'multiline-html-element-content-newline': require('./rules/multiline-html-element-content-newline'),
8586
'multiline-ternary': require('./rules/multiline-ternary'),

lib/rules/attribute-hyphenation.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ module.exports = {
101101
return null
102102
}
103103

104+
if (text.endsWith('.sync')) {
105+
return null
106+
}
107+
104108
if (/^[A-Z]/.test(name)) {
105109
return null
106110
}

lib/rules/define-macros-order.js

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ const MACROS_PROPS = 'defineProps'
1111
const MACROS_OPTIONS = 'defineOptions'
1212
const MACROS_SLOTS = 'defineSlots'
1313
const MACROS_MODEL = 'defineModel'
14-
const ORDER_SCHEMA = [
14+
const MACROS_EXPOSE = 'defineExpose'
15+
const KNOWN_MACROS = new Set([
1516
MACROS_EMITS,
1617
MACROS_PROPS,
1718
MACROS_OPTIONS,
1819
MACROS_SLOTS,
19-
MACROS_MODEL
20-
]
20+
MACROS_MODEL,
21+
MACROS_EXPOSE
22+
])
2123
const DEFAULT_ORDER = [MACROS_PROPS, MACROS_EMITS]
2224

2325
/**
@@ -109,6 +111,12 @@ function create(context) {
109111
/** @type {ASTNode} */
110112
let defineExposeNode
111113

114+
if (order.includes(MACROS_EXPOSE) && defineExposeLast) {
115+
throw new Error(
116+
"`defineExpose` macro can't be in the `order` array if `defineExposeLast` is true."
117+
)
118+
}
119+
112120
return utils.compositingVisitors(
113121
utils.defineScriptSetupVisitor(context, {
114122
onDefinePropsExit(node) {
@@ -130,6 +138,20 @@ function create(context) {
130138
},
131139
onDefineExposeExit(node) {
132140
defineExposeNode = getDefineMacrosStatement(node)
141+
},
142+
143+
/** @param {CallExpression} node */
144+
'Program > ExpressionStatement > CallExpression, Program > VariableDeclaration > VariableDeclarator > CallExpression'(
145+
node
146+
) {
147+
if (
148+
node.callee &&
149+
node.callee.type === 'Identifier' &&
150+
order.includes(node.callee.name) &&
151+
!KNOWN_MACROS.has(node.callee.name)
152+
) {
153+
macrosNodes.set(node.callee.name, [getDefineMacrosStatement(node)])
154+
}
133155
}
134156
}),
135157
{
@@ -333,7 +355,7 @@ module.exports = {
333355
type: 'layout',
334356
docs: {
335357
description:
336-
'enforce order of `defineEmits` and `defineProps` compiler macros',
358+
'enforce order of compiler macros (`defineProps`, `defineEmits`, etc.)',
337359
categories: undefined,
338360
url: 'https://eslint.vuejs.org/rules/define-macros-order.html'
339361
},
@@ -346,7 +368,8 @@ module.exports = {
346368
order: {
347369
type: 'array',
348370
items: {
349-
enum: ORDER_SCHEMA
371+
type: 'string',
372+
minLength: 1
350373
},
351374
uniqueItems: true,
352375
additionalItems: false

lib/rules/max-props.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ module.exports = {
4343
function checkMaxNumberOfProps(props) {
4444
if (props.length > option.maxProps && props[0].node) {
4545
context.report({
46-
node: props[0].node,
46+
node: props[0].node.parent,
4747
messageId: 'tooManyProps',
4848
data: {
4949
propCount: props.length,

lib/rules/max-template-depth.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* @author kevsommer Kevin Sommer
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
module.exports = {
8+
meta: {
9+
type: 'problem',
10+
docs: {
11+
description: 'enforce maximum depth of template',
12+
categories: undefined,
13+
url: 'https://eslint.vuejs.org/rules/max-template-depth.html'
14+
},
15+
fixable: null,
16+
schema: [
17+
{
18+
type: 'object',
19+
properties: {
20+
maxDepth: {
21+
type: 'integer',
22+
minimum: 1
23+
}
24+
},
25+
additionalProperties: false,
26+
minProperties: 1
27+
}
28+
],
29+
messages: {
30+
templateTooDeep:
31+
'Element is nested too deeply (depth of {{depth}}, maximum allowed is {{limit}}).'
32+
}
33+
},
34+
/** @param {RuleContext} context */
35+
create(context) {
36+
const option = context.options[0] || {}
37+
38+
/**
39+
* @param {VElement} element
40+
* @param {number} curDepth
41+
*/
42+
function checkMaxDepth(element, curDepth) {
43+
if (curDepth > option.maxDepth) {
44+
context.report({
45+
node: element,
46+
messageId: 'templateTooDeep',
47+
data: {
48+
depth: curDepth,
49+
limit: option.maxDepth
50+
}
51+
})
52+
}
53+
54+
if (!element.children) {
55+
return
56+
}
57+
58+
for (const child of element.children) {
59+
if (child.type === 'VElement') {
60+
checkMaxDepth(child, curDepth + 1)
61+
}
62+
}
63+
}
64+
65+
return {
66+
/** @param {Program} program */
67+
Program(program) {
68+
const element = program.templateBody
69+
70+
if (element == null) {
71+
return
72+
}
73+
74+
if (element.type !== 'VElement') {
75+
return
76+
}
77+
78+
checkMaxDepth(element, 0)
79+
}
80+
}
81+
}
82+
}

tests/lib/rules/attribute-hyphenation.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ ruleTester.run('attribute-hyphenation', rule, {
7070
filename: 'test.vue',
7171
code: '<template><div><custom :attr_ff="prop"></custom></div></template>',
7272
options: ['never']
73+
},
74+
{
75+
filename: 'test.vue',
76+
code: '<template><div><custom :my-name.sync="prop"></custom></div></template>',
77+
options: ['always']
78+
},
79+
{
80+
filename: 'test.vue',
81+
code: '<template><div><custom :myName.sync="prop"></custom></div></template>',
82+
options: ['never']
7383
}
7484
],
7585

@@ -367,6 +377,32 @@ ruleTester.run('attribute-hyphenation', rule, {
367377
line: 1
368378
}
369379
]
380+
},
381+
{
382+
filename: 'test.vue',
383+
code: '<template><div><custom :myAge.sync="prop"></custom></div></template>',
384+
output: null,
385+
options: ['always'],
386+
errors: [
387+
{
388+
message: "Attribute ':myAge.sync' must be hyphenated.",
389+
type: 'VDirectiveKey',
390+
line: 1
391+
}
392+
]
393+
},
394+
{
395+
filename: 'test.vue',
396+
code: '<template><div><custom :my-age.sync="prop"></custom></div></template>',
397+
output: null,
398+
options: ['never'],
399+
errors: [
400+
{
401+
message: "Attribute ':my-age.sync' can't be hyphenated.",
402+
type: 'VDirectiveKey',
403+
line: 1
404+
}
405+
]
370406
}
371407
]
372408
})

0 commit comments

Comments
 (0)