Skip to content

Commit 94b6915

Browse files
authored
Merge branch 'master' into prefer-define-component
2 parents 58343cb + f8d0757 commit 94b6915

12 files changed

+828
-98
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/define-props-destructuring
5+
description: enforce consistent style for props destructuring
6+
---
7+
8+
# vue/define-props-destructuring
9+
10+
> enforce consistent style for props destructuring
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 consistent style for handling Vue 3 Composition API props, allowing you to choose between requiring destructuring or prohibiting it.
17+
18+
By default, the rule requires you to use destructuring syntax when using `defineProps` instead of storing props in a variable and warns against combining `withDefaults` with destructuring.
19+
20+
<eslint-code-block :rules="{'vue/define-props-destructuring': ['error']}">
21+
22+
```vue
23+
<script setup>
24+
// ✓ GOOD
25+
const { foo } = defineProps(['foo'])
26+
const { bar = 'default' } = defineProps(['bar'])
27+
28+
// ✗ BAD
29+
const props = defineProps(['foo'])
30+
const propsWithDefaults = withDefaults(defineProps(['foo']), { foo: 'default' })
31+
32+
// ✗ BAD
33+
const { baz } = withDefaults(defineProps(['baz']), { baz: 'default' })
34+
</script>
35+
```
36+
37+
</eslint-code-block>
38+
39+
The rule applies to both JavaScript and TypeScript props:
40+
41+
<eslint-code-block :rules="{'vue/define-props-destructuring': ['error']}">
42+
43+
```vue
44+
<script setup lang="ts">
45+
// ✓ GOOD
46+
const { foo } = defineProps<{ foo?: string }>()
47+
const { bar = 'default' } = defineProps<{ bar?: string }>()
48+
49+
// ✗ BAD
50+
const props = defineProps<{ foo?: string }>()
51+
const propsWithDefaults = withDefaults(defineProps<{ foo?: string }>(), { foo: 'default' })
52+
</script>
53+
```
54+
55+
</eslint-code-block>
56+
57+
## :wrench: Options
58+
59+
```js
60+
{
61+
"vue/define-props-destructuring": ["error", {
62+
"destructure": "always" | "never"
63+
}]
64+
}
65+
```
66+
67+
- `destructure` - Sets the destructuring preference for props
68+
- `"always"` (default) - Requires destructuring when using `defineProps` and warns against using `withDefaults` with destructuring
69+
- `"never"` - Requires using a variable to store props and prohibits destructuring
70+
71+
### `"destructure": "never"`
72+
73+
<eslint-code-block :rules="{'vue/define-props-destructuring': ['error', { destructure: 'never' }]}">
74+
75+
```vue
76+
<script setup>
77+
// ✓ GOOD
78+
const props = defineProps(['foo'])
79+
const propsWithDefaults = withDefaults(defineProps(['foo']), { foo: 'default' })
80+
81+
// ✗ BAD
82+
const { foo } = defineProps(['foo'])
83+
</script>
84+
```
85+
86+
</eslint-code-block>
87+
88+
## :books: Further Reading
89+
90+
- [Reactive Props Destructure](https://vuejs.org/guide/components/props.html#reactive-props-destructure)
91+
92+
## :mag: Implementation
93+
94+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/define-props-destructuring.js)
95+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/define-props-destructuring.js)

docs/rules/index.md

Lines changed: 85 additions & 83 deletions
Large diffs are not rendered by default.

docs/rules/no-bare-strings-in-template.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ since: v7.0.0
1212
1313
## :book: Rule Details
1414

15-
This rule disallows the use of bare strings in `<template>`.
15+
This rule disallows the use of bare strings in `<template>`.
1616
In order to be able to internationalize your application, you will need to avoid using plain strings in your templates. Instead, you would need to use a template helper specializing in translation.
1717

1818
This rule was inspired by [no-bare-strings rule in ember-template-lint](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-bare-strings.md).
@@ -50,7 +50,7 @@ This rule was inspired by [no-bare-strings rule in ember-template-lint](https://
5050
</eslint-code-block>
5151

5252
:::tip
53-
This rule does not check for string literals, in bindings and mustaches interpolation. This is because it looks like a conscious decision.
53+
This rule does not check for string literals, in bindings and mustaches interpolation. This is because it looks like a conscious decision.
5454
If you want to report these string literals, enable the [vue/no-useless-v-bind] and [vue/no-useless-mustaches] rules and fix the useless string literals.
5555
:::
5656

@@ -72,7 +72,7 @@ If you want to report these string literals, enable the [vue/no-useless-v-bind]
7272
}
7373
```
7474

75-
- `allowlist` ... An array of allowed strings.
75+
- `allowlist` ... An array of allowed strings or regular expression patterns (e.g. `/\d+/` to allow numbers).
7676
- `attributes` ... An object whose keys are tag name or patterns and value is an array of attributes to check for that tag name.
7777
- `directives` ... An array of directive names to check literal value.
7878

docs/rules/no-multiple-template-root.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,32 @@ This rule checks whether template contains single root element valid for Vue 2.
6161

6262
## :wrench: Options
6363

64-
Nothing.
64+
```json
65+
{
66+
"vue/no-multiple-template-root": ["error", {
67+
"disallowComments": false
68+
}]
69+
}
70+
```
71+
72+
- "disallowComments" (`boolean`) Enables there should not be any comments in the template root. Default is `false`.
73+
74+
### "disallowComments": true
75+
76+
<eslint-code-block :rules="{'vue/no-multiple-template-root': ['error', {disallowComments: true}]}">
77+
78+
```vue
79+
/* ✗ BAD */
80+
<template>
81+
<!-- root comment -->
82+
<div>
83+
vue eslint plugin
84+
</div>
85+
<!-- root comment -->
86+
</template>
87+
```
88+
89+
</eslint-code-block>
6590

6691
## :rocket: Version
6792

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const plugin = {
5858
'define-emits-declaration': require('./rules/define-emits-declaration'),
5959
'define-macros-order': require('./rules/define-macros-order'),
6060
'define-props-declaration': require('./rules/define-props-declaration'),
61+
'define-props-destructuring': require('./rules/define-props-destructuring'),
6162
'dot-location': require('./rules/dot-location'),
6263
'dot-notation': require('./rules/dot-notation'),
6364
'enforce-style-attribute': require('./rules/enforce-style-attribute'),
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* @author Wayne Zhang
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
module.exports = {
10+
meta: {
11+
type: 'suggestion',
12+
docs: {
13+
description: 'enforce consistent style for props destructuring',
14+
categories: undefined,
15+
url: 'https://eslint.vuejs.org/rules/define-props-destructuring.html'
16+
},
17+
fixable: null,
18+
schema: [
19+
{
20+
type: 'object',
21+
properties: {
22+
destructure: {
23+
enum: ['always', 'never']
24+
}
25+
},
26+
additionalProperties: false
27+
}
28+
],
29+
messages: {
30+
preferDestructuring: 'Prefer destructuring from `defineProps` directly.',
31+
avoidDestructuring: 'Avoid destructuring from `defineProps`.',
32+
avoidWithDefaults: 'Avoid using `withDefaults` with destructuring.'
33+
}
34+
},
35+
/** @param {RuleContext} context */
36+
create(context) {
37+
const options = context.options[0] || {}
38+
const destructurePreference = options.destructure || 'always'
39+
40+
return utils.compositingVisitors(
41+
utils.defineScriptSetupVisitor(context, {
42+
onDefinePropsEnter(node, props) {
43+
const hasNoArgs = props.filter((prop) => prop.propName).length === 0
44+
if (hasNoArgs) {
45+
return
46+
}
47+
48+
const hasDestructure = utils.isUsingPropsDestructure(node)
49+
const hasWithDefaults = utils.hasWithDefaults(node)
50+
51+
if (destructurePreference === 'never') {
52+
if (hasDestructure) {
53+
context.report({
54+
node,
55+
messageId: 'avoidDestructuring'
56+
})
57+
}
58+
return
59+
}
60+
61+
if (!hasDestructure) {
62+
context.report({
63+
node,
64+
messageId: 'preferDestructuring'
65+
})
66+
return
67+
}
68+
69+
if (hasWithDefaults) {
70+
context.report({
71+
node: node.parent.callee,
72+
messageId: 'avoidWithDefaults'
73+
})
74+
}
75+
}
76+
})
77+
)
78+
}
79+
}

lib/rules/no-bare-strings-in-template.js

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,33 @@ module.exports = {
149149
*/
150150
const opts = context.options[0] || {}
151151
/** @type {string[]} */
152-
const allowlist = opts.allowlist || DEFAULT_ALLOWLIST
152+
const rawAllowlist = opts.allowlist || DEFAULT_ALLOWLIST
153153
const attributes = parseTargetAttrs(opts.attributes || DEFAULT_ATTRIBUTES)
154154
const directives = opts.directives || DEFAULT_DIRECTIVES
155155

156-
const allowlistRe = new RegExp(
157-
allowlist
158-
.map((w) => regexp.escape(w))
159-
.sort((a, b) => b.length - a.length)
160-
.join('|'),
161-
'gu'
162-
)
156+
/** @type {string[]} */
157+
const stringAllowlist = []
158+
/** @type {RegExp[]} */
159+
const regexAllowlist = []
160+
161+
for (const item of rawAllowlist) {
162+
if (regexp.isRegExp(item)) {
163+
regexAllowlist.push(regexp.toRegExp(item))
164+
} else {
165+
stringAllowlist.push(item)
166+
}
167+
}
168+
169+
const allowlistRe =
170+
stringAllowlist.length > 0
171+
? new RegExp(
172+
stringAllowlist
173+
.map((w) => regexp.escape(w))
174+
.sort((a, b) => b.length - a.length)
175+
.join('|'),
176+
'gu'
177+
)
178+
: null
163179

164180
/** @type {ElementStack | null} */
165181
let elementStack = null
@@ -168,7 +184,21 @@ module.exports = {
168184
* @param {string} str
169185
*/
170186
function getBareString(str) {
171-
return str.trim().replace(allowlistRe, '').trim()
187+
let result = str.trim()
188+
189+
if (allowlistRe) {
190+
result = result.replace(allowlistRe, '')
191+
}
192+
193+
for (const regex of regexAllowlist) {
194+
const flags = regex.flags.includes('g')
195+
? regex.flags
196+
: `${regex.flags}g`
197+
const globalRegex = new RegExp(regex.source, flags)
198+
result = result.replace(globalRegex, '')
199+
}
200+
201+
return result.trim()
172202
}
173203

174204
/**

lib/rules/no-multiple-template-root.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@
66

77
const utils = require('../utils')
88

9+
/**
10+
* Get all comments that need to be reported
11+
* @param {(HTMLComment | HTMLBogusComment | Comment)[]} comments
12+
* @param {Range[]} elementRanges
13+
* @returns {(HTMLComment | HTMLBogusComment | Comment)[]}
14+
*/
15+
function getReportComments(comments, elementRanges) {
16+
return comments.filter(
17+
(comment) =>
18+
!elementRanges.some(
19+
(range) => range[0] <= comment.range[0] && comment.range[1] <= range[1]
20+
)
21+
)
22+
}
23+
924
module.exports = {
1025
meta: {
1126
type: 'problem',
@@ -15,8 +30,19 @@ module.exports = {
1530
url: 'https://eslint.vuejs.org/rules/no-multiple-template-root.html'
1631
},
1732
fixable: null,
18-
schema: [],
33+
schema: [
34+
{
35+
type: 'object',
36+
properties: {
37+
disallowComments: {
38+
type: 'boolean'
39+
}
40+
},
41+
additionalProperties: false
42+
}
43+
],
1944
messages: {
45+
commentRoot: 'The template root disallows comments.',
2046
multipleRoot: 'The template root requires exactly one element.',
2147
textRoot: 'The template root requires an element rather than texts.',
2248
disallowedElement: "The template root disallows '<{{name}}>' elements.",
@@ -28,6 +54,8 @@ module.exports = {
2854
* @returns {RuleListener} AST event handlers.
2955
*/
3056
create(context) {
57+
const options = context.options[0] || {}
58+
const disallowComments = options.disallowComments
3159
const sourceCode = context.getSourceCode()
3260

3361
return {
@@ -37,6 +65,18 @@ module.exports = {
3765
return
3866
}
3967

68+
const comments = element.comments
69+
const elementRanges = element.children.map((child) => child.range)
70+
if (disallowComments && comments.length > 0) {
71+
for (const comment of getReportComments(comments, elementRanges)) {
72+
context.report({
73+
node: comment,
74+
loc: comment.loc,
75+
messageId: 'commentRoot'
76+
})
77+
}
78+
}
79+
4080
const rootElements = []
4181
let extraText = null
4282
let extraElement = null

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-plugin-vue",
3-
"version": "10.0.0",
3+
"version": "10.0.1",
44
"description": "Official ESLint plugin for Vue.js",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",

0 commit comments

Comments
 (0)