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 2dc7889

Browse files
waynzhFloEdelmann
andauthored
feat(v-bind-style): add sameNameShorthand option (#2381)
Co-authored-by: Flo Edelmann <git@flo-edelmann.de>
1 parent 9bf52e4 commit 2dc7889

File tree

3 files changed

+320
-44
lines changed

3 files changed

+320
-44
lines changed

‎docs/rules/v-bind-style.md

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,20 @@ This rule enforces `v-bind` directive style which you should use shorthand or lo
3232

3333
## :wrench: Options
3434

35-
Default is set to `shorthand`.
36-
3735
```json
3836
{
39-
"vue/v-bind-style": ["error", "shorthand" | "longform"]
37+
"vue/v-bind-style": ["error", "shorthand" | "longform", {
38+
"sameNameShorthand": "ignore" | "always" | "never"
39+
}]
4040
}
4141
```
4242

4343
- `"shorthand"` (default) ... requires using shorthand.
4444
- `"longform"` ... requires using long form.
45+
- `sameNameShorthand` ... enforce the `v-bind` same-name shorthand style (Vue 3.4+).
46+
- `"ignore"` (default) ... ignores the same-name shorthand style.
47+
- `"always"` ... always enforces same-name shorthand where possible.
48+
- `"never"` ... always disallow same-name shorthand where possible.
4549

4650
### `"longform"`
4751

@@ -59,6 +63,38 @@ Default is set to `shorthand`.
5963

6064
</eslint-code-block>
6165

66+
### `{ "sameNameShorthand": "always" }`
67+
68+
<eslint-code-block fix :rules="{'vue/v-bind-style': ['error', 'shorthand', { 'sameNameShorthand': 'always' }]}">
69+
70+
```vue
71+
<template>
72+
<!-- ✓ GOOD -->
73+
<div :foo />
74+
75+
<!-- ✗ BAD -->
76+
<div :foo="foo" />
77+
</template>
78+
```
79+
80+
</eslint-code-block>
81+
82+
### `{ "sameNameShorthand": "never" }`
83+
84+
<eslint-code-block fix :rules="{'vue/v-bind-style': ['error', 'shorthand', { 'sameNameShorthand': 'never' }]}">
85+
86+
```vue
87+
<template>
88+
<!-- ✓ GOOD -->
89+
<div :foo="foo" />
90+
91+
<!-- ✗ BAD -->
92+
<div :foo />
93+
</template>
94+
```
95+
96+
</eslint-code-block>
97+
6298
## :books: Further Reading
6399

64100
- [Style guide - Directive shorthands](https://vuejs.org/style-guide/rules-strongly-recommended.html#directive-shorthands)

‎lib/rules/v-bind-style.js

Lines changed: 128 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,41 @@
66
'use strict'
77

88
const utils = require('../utils')
9+
const casing = require('../utils/casing')
10+
11+
/**
12+
* @typedef { VDirectiveKey & { name: VIdentifier & { name: 'bind' }, argument: VExpressionContainer | VIdentifier } } VBindDirectiveKey
13+
* @typedef { VDirective & { key: VBindDirectiveKey } } VBindDirective
14+
*/
15+
16+
/**
17+
* @param {VBindDirective} node
18+
* @returns {boolean}
19+
*/
20+
function isSameName(node) {
21+
const attrName =
22+
node.key.argument.type === 'VIdentifier' ? node.key.argument.rawName : null
23+
const valueName =
24+
node.value?.expression?.type === 'Identifier'
25+
? node.value.expression.name
26+
: null
27+
return Boolean(
28+
attrName &&
29+
valueName &&
30+
casing.camelCase(attrName) === casing.camelCase(valueName)
31+
)
32+
}
33+
34+
/**
35+
* @param {VBindDirectiveKey} key
36+
* @returns {number}
37+
*/
38+
function getCutStart(key) {
39+
const modifiers = key.modifiers
40+
return modifiers.length > 0
41+
? modifiers[modifiers.length - 1].range[1]
42+
: key.argument.range[1]
43+
}
944

1045
module.exports = {
1146
meta: {
@@ -16,60 +51,113 @@ module.exports = {
1651
url: 'https://eslint.vuejs.org/rules/v-bind-style.html'
1752
},
1853
fixable: 'code',
19-
schema: [{ enum: ['shorthand', 'longform'] }],
54+
schema: [
55+
{ enum: ['shorthand', 'longform'] },
56+
{
57+
type: 'object',
58+
properties: {
59+
sameNameShorthand: { enum: ['always', 'never', 'ignore'] }
60+
},
61+
additionalProperties: false
62+
}
63+
],
2064
messages: {
2165
expectedLonghand: "Expected 'v-bind' before ':'.",
2266
unexpectedLonghand: "Unexpected 'v-bind' before ':'.",
23-
expectedLonghandForProp: "Expected 'v-bind:' instead of '.'."
67+
expectedLonghandForProp: "Expected 'v-bind:' instead of '.'.",
68+
expectedShorthand: 'Expected same-name shorthand.',
69+
unexpectedShorthand: 'Unexpected same-name shorthand.'
2470
}
2571
},
2672
/** @param {RuleContext} context */
2773
create(context) {
2874
const preferShorthand = context.options[0] !== 'longform'
75+
/** @type {"always" | "never" | "ignore"} */
76+
const sameNameShorthand = context.options[1]?.sameNameShorthand || 'ignore'
2977

30-
return utils.defineTemplateBodyVisitor(context, {
31-
/** @param {VDirective} node */
32-
"VAttribute[directive=true][key.name.name='bind'][key.argument!=null]"(
33-
node
34-
) {
35-
const shorthandProp = node.key.name.rawName === '.'
36-
const shorthand = node.key.name.rawName === ':' || shorthandProp
37-
if (shorthand === preferShorthand) {
38-
return
39-
}
78+
/** @param {VBindDirective} node */
79+
function checkAttributeStyle(node) {
80+
const shorthandProp = node.key.name.rawName === '.'
81+
const shorthand = node.key.name.rawName === ':' || shorthandProp
82+
if (shorthand === preferShorthand) {
83+
return
84+
}
4085

41-
let messageId = 'expectedLonghand'
42-
if (preferShorthand) {
43-
messageId = 'unexpectedLonghand'
44-
} else if (shorthandProp) {
45-
messageId = 'expectedLonghandForProp'
46-
}
86+
let messageId = 'expectedLonghand'
87+
if (preferShorthand) {
88+
messageId = 'unexpectedLonghand'
89+
} else if (shorthandProp) {
90+
messageId = 'expectedLonghandForProp'
91+
}
92+
93+
context.report({
94+
node,
95+
loc: node.loc,
96+
messageId,
97+
*fix(fixer) {
98+
if (preferShorthand) {
99+
yield fixer.remove(node.key.name)
100+
} else {
101+
yield fixer.insertTextBefore(node, 'v-bind')
102+
103+
if (shorthandProp) {
104+
// Replace `.` by `:`.
105+
yield fixer.replaceText(node.key.name, ':')
47106

48-
context.report({
49-
node,
50-
loc: node.loc,
51-
messageId,
52-
*fix(fixer) {
53-
if (preferShorthand) {
54-
yield fixer.remove(node.key.name)
55-
} else {
56-
yield fixer.insertTextBefore(node, 'v-bind')
57-
58-
if (shorthandProp) {
59-
// Replace `.` by `:`.
60-
yield fixer.replaceText(node.key.name, ':')
61-
62-
// Insert `.prop` modifier if it doesn't exist.
63-
const modifier = node.key.modifiers[0]
64-
const isAutoGeneratedPropModifier =
65-
modifier.name === 'prop' && modifier.rawName === ''
66-
if (isAutoGeneratedPropModifier) {
67-
yield fixer.insertTextBefore(modifier, '.prop')
68-
}
107+
// Insert `.prop` modifier if it doesn't exist.
108+
const modifier = node.key.modifiers[0]
109+
const isAutoGeneratedPropModifier =
110+
modifier.name === 'prop' && modifier.rawName === ''
111+
if (isAutoGeneratedPropModifier) {
112+
yield fixer.insertTextBefore(modifier, '.prop')
69113
}
70114
}
71115
}
72-
})
116+
}
117+
})
118+
}
119+
120+
/** @param {VBindDirective} node */
121+
function checkAttributeSameName(node) {
122+
if (sameNameShorthand === 'ignore' || !isSameName(node)) return
123+
124+
const preferShorthand = sameNameShorthand === 'always'
125+
const isShorthand = utils.isVBindSameNameShorthand(node)
126+
if (isShorthand === preferShorthand) {
127+
return
128+
}
129+
130+
const messageId = preferShorthand
131+
? 'expectedShorthand'
132+
: 'unexpectedShorthand'
133+
134+
context.report({
135+
node,
136+
loc: node.loc,
137+
messageId,
138+
*fix(fixer) {
139+
if (preferShorthand) {
140+
/** @type {Range} */
141+
const valueRange = [getCutStart(node.key), node.range[1]]
142+
143+
yield fixer.removeRange(valueRange)
144+
} else if (node.key.argument.type === 'VIdentifier') {
145+
yield fixer.insertTextAfter(
146+
node,
147+
`="${casing.camelCase(node.key.argument.rawName)}"`
148+
)
149+
}
150+
}
151+
})
152+
}
153+
154+
return utils.defineTemplateBodyVisitor(context, {
155+
/** @param {VBindDirective} node */
156+
"VAttribute[directive=true][key.name.name='bind'][key.argument!=null]"(
157+
node
158+
) {
159+
checkAttributeSameName(node)
160+
checkAttributeStyle(node)
73161
}
74162
})
75163
}

0 commit comments

Comments
(0)

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