Skip to content

Commit f537354

Browse files
authored
Add vue/comma-style rule (#1159)
* Add `vue/comma-style` rule * update
1 parent 625e271 commit f537354

File tree

7 files changed

+210
-20
lines changed

7 files changed

+210
-20
lines changed

docs/rules/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ For example:
278278
| [vue/camelcase](./camelcase.md) | enforce camelcase naming convention | |
279279
| [vue/comma-dangle](./comma-dangle.md) | require or disallow trailing commas | :wrench: |
280280
| [vue/comma-spacing](./comma-spacing.md) | enforce consistent spacing before and after commas | :wrench: |
281+
| [vue/comma-style](./comma-style.md) | enforce consistent comma style | :wrench: |
281282
| [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: |
282283
| [vue/dot-location](./dot-location.md) | enforce consistent newlines before and after dots | :wrench: |
283284
| [vue/eqeqeq](./eqeqeq.md) | require the use of `===` and `!==` | :wrench: |
@@ -308,6 +309,7 @@ For example:
308309
| [vue/require-name-property](./require-name-property.md) | require a name property in Vue components | |
309310
| [vue/script-indent](./script-indent.md) | enforce consistent indentation in `<script>` | :wrench: |
310311
| [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components | |
312+
| [vue/space-in-parens](./space-in-parens.md) | enforce consistent spacing inside parentheses | :wrench: |
311313
| [vue/space-infix-ops](./space-infix-ops.md) | require spacing around infix operators | :wrench: |
312314
| [vue/space-unary-ops](./space-unary-ops.md) | enforce consistent spacing before or after unary operators | :wrench: |
313315
| [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: |

docs/rules/comma-style.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/comma-style
5+
description: enforce consistent comma style
6+
---
7+
# vue/comma-style
8+
> enforce consistent comma style
9+
10+
- :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.
11+
12+
This rule is the same rule as core [comma-style] rule but it applies to the expressions in `<template>`.
13+
14+
## :books: Further reading
15+
16+
- [comma-style]
17+
18+
[comma-style]: https://eslint.org/docs/rules/comma-style
19+
20+
## :mag: Implementation
21+
22+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/comma-style.js)
23+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/comma-style.js)

lib/configs/no-layout-rules.js

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = {
1111
'vue/brace-style': 'off',
1212
'vue/comma-dangle': 'off',
1313
'vue/comma-spacing': 'off',
14+
'vue/comma-style': 'off',
1415
'vue/dot-location': 'off',
1516
'vue/html-closing-bracket-newline': 'off',
1617
'vue/html-closing-bracket-spacing': 'off',

lib/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module.exports = {
1616
'camelcase': require('./rules/camelcase'),
1717
'comma-dangle': require('./rules/comma-dangle'),
1818
'comma-spacing': require('./rules/comma-spacing'),
19+
'comma-style': require('./rules/comma-style'),
1920
'comment-directive': require('./rules/comment-directive'),
2021
'component-definition-name-casing': require('./rules/component-definition-name-casing'),
2122
'component-name-in-template-casing': require('./rules/component-name-in-template-casing'),
@@ -114,6 +115,7 @@ module.exports = {
114115
'script-indent': require('./rules/script-indent'),
115116
'singleline-html-element-content-newline': require('./rules/singleline-html-element-content-newline'),
116117
'sort-keys': require('./rules/sort-keys'),
118+
'space-in-parens': require('./rules/space-in-parens'),
117119
'space-infix-ops': require('./rules/space-infix-ops'),
118120
'space-unary-ops': require('./rules/space-unary-ops'),
119121
'static-class-names-order': require('./rules/static-class-names-order'),

lib/rules/comma-style.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @author Yosuke Ota
3+
*/
4+
'use strict'
5+
6+
const { wrapCoreRule } = require('../utils')
7+
8+
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
9+
module.exports = wrapCoreRule(
10+
require('eslint/lib/rules/comma-style'),
11+
{
12+
create (_context, { coreHandlers }) {
13+
return {
14+
VSlotScopeExpression (node) {
15+
coreHandlers.FunctionExpression(node)
16+
}
17+
}
18+
}
19+
}
20+
)

lib/utils/index.js

+38-20
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
/**
2424
* @typedef {import('eslint').Rule.RuleContext} RuleContext
25+
* @typedef {import('eslint').Rule.RuleModule} RuleModule
2526
* @typedef {import('vue-eslint-parser').AST.Token} Token
2627
*/
2728

@@ -154,28 +155,20 @@ module.exports = {
154155
* @param {Object} [scriptVisitor] The visitor to traverse the script.
155156
* @returns {Object} The merged visitor.
156157
*/
157-
defineTemplateBodyVisitor (context, templateBodyVisitor, scriptVisitor) {
158-
if (context.parserServices.defineTemplateBodyVisitor == null) {
159-
context.report({
160-
loc: { line: 1, column: 0 },
161-
message: 'Use the latest vue-eslint-parser. See also https://eslint.vuejs.org/user-guide/#what-is-the-use-the-latest-vue-eslint-parser-error'
162-
})
163-
return {}
164-
}
165-
return context.parserServices.defineTemplateBodyVisitor(templateBodyVisitor, scriptVisitor)
166-
},
158+
defineTemplateBodyVisitor,
167159

168160
/**
169161
* Wrap a given core rule to apply it to Vue.js template.
170-
* @param {Rule} coreRule The core rule implementation to wrap.
162+
* @param {RuleModule} coreRule The core rule implementation to wrap.
171163
* @param {Object} [options] The option of this rule.
172-
* @param {string|undefined} options.category The category of this rule.
173-
* @param {boolean|undefined} options.skipDynamicArguments If `true`, skip validation within dynamic arguments.
174-
* @param {boolean|undefined} options.skipDynamicArgumentsReport If `true`, skip report within dynamic arguments.
175-
* @returns {Rule} The wrapped rule implementation.
164+
* @param {string} [options.category] The category of this rule.
165+
* @param {boolean} [options.skipDynamicArguments] If `true`, skip validation within dynamic arguments.
166+
* @param {boolean} [options.skipDynamicArgumentsReport] If `true`, skip report within dynamic arguments.
167+
* @param {RuleModule["create"]} [options.create] If define, extend core rule.
168+
* @returns {RuleModule} The wrapped rule implementation.
176169
*/
177170
wrapCoreRule (coreRule, options) {
178-
const { category, skipDynamicArguments, skipDynamicArgumentsReport } = options || {}
171+
const { category, skipDynamicArguments, skipDynamicArgumentsReport, create } = options || {}
179172
return {
180173
create (context) {
181174
const tokenStore =
@@ -193,7 +186,8 @@ module.exports = {
193186
}
194187

195188
// Move `Program` handlers to `VElement[parent.type!='VElement']`
196-
const handlers = coreRule.create(context)
189+
const coreHandlers = coreRule.create(context)
190+
const handlers = Object.assign({}, coreHandlers)
197191
if (handlers.Program) {
198192
handlers["VElement[parent.type!='VElement']"] = handlers.Program
199193
delete handlers.Program
@@ -216,8 +210,12 @@ module.exports = {
216210
handlers['VDirectiveKey > VExpressionContainer:exit'] = () => { withinDynamicArguments = false }
217211
}
218212

213+
if (create) {
214+
compositingVisitors(handlers, create(context, { coreHandlers }))
215+
}
216+
219217
// Apply the handlers to templates.
220-
return module.exports.defineTemplateBodyVisitor(context, handlers)
218+
return defineTemplateBodyVisitor(context, handlers)
221219
},
222220

223221
meta: Object.assign({}, coreRule.meta, {
@@ -1089,6 +1087,27 @@ module.exports = {
10891087
}
10901088
}
10911089

1090+
/**
1091+
* Register the given visitor to parser services.
1092+
* If the parser service of `vue-eslint-parser` was not found,
1093+
* this generates a warning.
1094+
*
1095+
* @param {RuleContext} context The rule context to use parser services.
1096+
* @param {Object} templateBodyVisitor The visitor to traverse the template body.
1097+
* @param {Object} [scriptVisitor] The visitor to traverse the script.
1098+
* @returns {Object} The merged visitor.
1099+
*/
1100+
function defineTemplateBodyVisitor (context, templateBodyVisitor, scriptVisitor) {
1101+
if (context.parserServices.defineTemplateBodyVisitor == null) {
1102+
context.report({
1103+
loc: { line: 1, column: 0 },
1104+
message: 'Use the latest vue-eslint-parser. See also https://eslint.vuejs.org/user-guide/#what-is-the-use-the-latest-vue-eslint-parser-error'
1105+
})
1106+
return {}
1107+
}
1108+
return context.parserServices.defineTemplateBodyVisitor(templateBodyVisitor, scriptVisitor)
1109+
}
1110+
10921111
/**
10931112
* Unwrap typescript types like "X as F"
10941113
* @template T
@@ -1294,8 +1313,7 @@ function getComponentComments (context) {
12941313
return tokens
12951314
}
12961315

1297-
function compositingVisitors (...visitors) {
1298-
const visitor = {}
1316+
function compositingVisitors (visitor, ...visitors) {
12991317
for (const v of visitors) {
13001318
for (const key in v) {
13011319
if (visitor[key]) {

tests/lib/rules/comma-style.js

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* @author Yosuke Ota
3+
*/
4+
'use strict'
5+
6+
const { RuleTester } = require('eslint')
7+
const rule = require('../../../lib/rules/comma-style')
8+
9+
const tester = new RuleTester({
10+
parser: require.resolve('vue-eslint-parser'),
11+
parserOptions: { ecmaVersion: 2018 }
12+
})
13+
14+
tester.run('comma-style', rule, {
15+
valid: [
16+
`<template>
17+
<CustomButton @click="() => fn({
18+
a,
19+
b
20+
})" />
21+
</template>`,
22+
{
23+
code: `
24+
<template>
25+
<CustomButton @click="($event,
26+
data) => fn()" />
27+
</template>`,
28+
options: ['last', { exceptions: { ArrowFunctionExpression: false }}]
29+
},
30+
{
31+
code: `
32+
<template>
33+
<CustomButton @click="($event
34+
, data) => fn()" />
35+
</template>`,
36+
options: ['first', { exceptions: { ArrowFunctionExpression: false }}]
37+
}
38+
],
39+
invalid: [
40+
{
41+
code: `
42+
<template>
43+
<CustomButton @click="() => fn({
44+
a
45+
, b
46+
})" />
47+
</template>`,
48+
output: `
49+
<template>
50+
<CustomButton @click="() => fn({
51+
a,
52+
b
53+
})" />
54+
</template>`,
55+
errors: [
56+
{
57+
message: "',' should be placed last.",
58+
line: 5
59+
}
60+
]
61+
},
62+
{
63+
code: `
64+
<template>
65+
<CustomButton @click="($event
66+
, data) => fn()" />
67+
</template>`,
68+
options: ['last', { exceptions: { ArrowFunctionExpression: false }}],
69+
output: `
70+
<template>
71+
<CustomButton @click="($event,
72+
data) => fn()" />
73+
</template>`,
74+
errors: [
75+
{
76+
message: "',' should be placed last.",
77+
line: 4
78+
}
79+
]
80+
},
81+
{
82+
code: `
83+
<template>
84+
<CustomButton @click="($event,
85+
data) => fn()" />
86+
</template>`,
87+
options: ['first', { exceptions: { ArrowFunctionExpression: false }}],
88+
output: `
89+
<template>
90+
<CustomButton @click="($event
91+
,data) => fn()" />
92+
</template>`,
93+
errors: [
94+
{
95+
message: "',' should be placed first."
96+
// line: 3 // eslint v7.0
97+
}
98+
]
99+
},
100+
{
101+
code: `
102+
<template>
103+
<CustomButton v-slot="foo,
104+
bar" >
105+
<div/>
106+
</CustomButton>
107+
</template>`,
108+
options: ['first', { exceptions: { FunctionExpression: false }}],
109+
output: `
110+
<template>
111+
<CustomButton v-slot="foo
112+
,bar" >
113+
<div/>
114+
</CustomButton>
115+
</template>`,
116+
errors: [
117+
{
118+
message: "',' should be placed first."
119+
// line: 3 // eslint v7.0
120+
}
121+
]
122+
}
123+
]
124+
})

0 commit comments

Comments
 (0)