Skip to content

Commit

Permalink
Add vue/comma-style rule (#1159)
Browse files Browse the repository at this point in the history
* Add `vue/comma-style` rule

* update
  • Loading branch information
ota-meshi committed May 23, 2020
1 parent 625e271 commit f537354
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 20 deletions.
2 changes: 2 additions & 0 deletions docs/rules/README.md
Expand Up @@ -278,6 +278,7 @@ For example:
| [vue/camelcase](./camelcase.md) | enforce camelcase naming convention | |
| [vue/comma-dangle](./comma-dangle.md) | require or disallow trailing commas | :wrench: |
| [vue/comma-spacing](./comma-spacing.md) | enforce consistent spacing before and after commas | :wrench: |
| [vue/comma-style](./comma-style.md) | enforce consistent comma style | :wrench: |
| [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: |
| [vue/dot-location](./dot-location.md) | enforce consistent newlines before and after dots | :wrench: |
| [vue/eqeqeq](./eqeqeq.md) | require the use of `===` and `!==` | :wrench: |
Expand Down Expand Up @@ -308,6 +309,7 @@ For example:
| [vue/require-name-property](./require-name-property.md) | require a name property in Vue components | |
| [vue/script-indent](./script-indent.md) | enforce consistent indentation in `<script>` | :wrench: |
| [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components | |
| [vue/space-in-parens](./space-in-parens.md) | enforce consistent spacing inside parentheses | :wrench: |
| [vue/space-infix-ops](./space-infix-ops.md) | require spacing around infix operators | :wrench: |
| [vue/space-unary-ops](./space-unary-ops.md) | enforce consistent spacing before or after unary operators | :wrench: |
| [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: |
Expand Down
23 changes: 23 additions & 0 deletions docs/rules/comma-style.md
@@ -0,0 +1,23 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/comma-style
description: enforce consistent comma style
---
# vue/comma-style
> enforce consistent comma style
- :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.

This rule is the same rule as core [comma-style] rule but it applies to the expressions in `<template>`.

## :books: Further reading

- [comma-style]

[comma-style]: https://eslint.org/docs/rules/comma-style

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/comma-style.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/comma-style.js)
1 change: 1 addition & 0 deletions lib/configs/no-layout-rules.js
Expand Up @@ -11,6 +11,7 @@ module.exports = {
'vue/brace-style': 'off',
'vue/comma-dangle': 'off',
'vue/comma-spacing': 'off',
'vue/comma-style': 'off',
'vue/dot-location': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/html-closing-bracket-spacing': 'off',
Expand Down
2 changes: 2 additions & 0 deletions lib/index.js
Expand Up @@ -16,6 +16,7 @@ module.exports = {
'camelcase': require('./rules/camelcase'),
'comma-dangle': require('./rules/comma-dangle'),
'comma-spacing': require('./rules/comma-spacing'),
'comma-style': require('./rules/comma-style'),
'comment-directive': require('./rules/comment-directive'),
'component-definition-name-casing': require('./rules/component-definition-name-casing'),
'component-name-in-template-casing': require('./rules/component-name-in-template-casing'),
Expand Down Expand Up @@ -114,6 +115,7 @@ module.exports = {
'script-indent': require('./rules/script-indent'),
'singleline-html-element-content-newline': require('./rules/singleline-html-element-content-newline'),
'sort-keys': require('./rules/sort-keys'),
'space-in-parens': require('./rules/space-in-parens'),
'space-infix-ops': require('./rules/space-infix-ops'),
'space-unary-ops': require('./rules/space-unary-ops'),
'static-class-names-order': require('./rules/static-class-names-order'),
Expand Down
20 changes: 20 additions & 0 deletions lib/rules/comma-style.js
@@ -0,0 +1,20 @@
/**
* @author Yosuke Ota
*/
'use strict'

const { wrapCoreRule } = require('../utils')

// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
module.exports = wrapCoreRule(
require('eslint/lib/rules/comma-style'),
{
create (_context, { coreHandlers }) {
return {
VSlotScopeExpression (node) {
coreHandlers.FunctionExpression(node)
}
}
}
}
)
58 changes: 38 additions & 20 deletions lib/utils/index.js
Expand Up @@ -22,6 +22,7 @@

/**
* @typedef {import('eslint').Rule.RuleContext} RuleContext
* @typedef {import('eslint').Rule.RuleModule} RuleModule
* @typedef {import('vue-eslint-parser').AST.Token} Token
*/

Expand Down Expand Up @@ -154,28 +155,20 @@ module.exports = {
* @param {Object} [scriptVisitor] The visitor to traverse the script.
* @returns {Object} The merged visitor.
*/
defineTemplateBodyVisitor (context, templateBodyVisitor, scriptVisitor) {
if (context.parserServices.defineTemplateBodyVisitor == null) {
context.report({
loc: { line: 1, column: 0 },
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'
})
return {}
}
return context.parserServices.defineTemplateBodyVisitor(templateBodyVisitor, scriptVisitor)
},
defineTemplateBodyVisitor,

/**
* Wrap a given core rule to apply it to Vue.js template.
* @param {Rule} coreRule The core rule implementation to wrap.
* @param {RuleModule} coreRule The core rule implementation to wrap.
* @param {Object} [options] The option of this rule.
* @param {string|undefined} options.category The category of this rule.
* @param {boolean|undefined} options.skipDynamicArguments If `true`, skip validation within dynamic arguments.
* @param {boolean|undefined} options.skipDynamicArgumentsReport If `true`, skip report within dynamic arguments.
* @returns {Rule} The wrapped rule implementation.
* @param {string} [options.category] The category of this rule.
* @param {boolean} [options.skipDynamicArguments] If `true`, skip validation within dynamic arguments.
* @param {boolean} [options.skipDynamicArgumentsReport] If `true`, skip report within dynamic arguments.
* @param {RuleModule["create"]} [options.create] If define, extend core rule.
* @returns {RuleModule} The wrapped rule implementation.
*/
wrapCoreRule (coreRule, options) {
const { category, skipDynamicArguments, skipDynamicArgumentsReport } = options || {}
const { category, skipDynamicArguments, skipDynamicArgumentsReport, create } = options || {}
return {
create (context) {
const tokenStore =
Expand All @@ -193,7 +186,8 @@ module.exports = {
}

// Move `Program` handlers to `VElement[parent.type!='VElement']`
const handlers = coreRule.create(context)
const coreHandlers = coreRule.create(context)
const handlers = Object.assign({}, coreHandlers)
if (handlers.Program) {
handlers["VElement[parent.type!='VElement']"] = handlers.Program
delete handlers.Program
Expand All @@ -216,8 +210,12 @@ module.exports = {
handlers['VDirectiveKey > VExpressionContainer:exit'] = () => { withinDynamicArguments = false }
}

if (create) {
compositingVisitors(handlers, create(context, { coreHandlers }))
}

// Apply the handlers to templates.
return module.exports.defineTemplateBodyVisitor(context, handlers)
return defineTemplateBodyVisitor(context, handlers)
},

meta: Object.assign({}, coreRule.meta, {
Expand Down Expand Up @@ -1089,6 +1087,27 @@ module.exports = {
}
}

/**
* Register the given visitor to parser services.
* If the parser service of `vue-eslint-parser` was not found,
* this generates a warning.
*
* @param {RuleContext} context The rule context to use parser services.
* @param {Object} templateBodyVisitor The visitor to traverse the template body.
* @param {Object} [scriptVisitor] The visitor to traverse the script.
* @returns {Object} The merged visitor.
*/
function defineTemplateBodyVisitor (context, templateBodyVisitor, scriptVisitor) {
if (context.parserServices.defineTemplateBodyVisitor == null) {
context.report({
loc: { line: 1, column: 0 },
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'
})
return {}
}
return context.parserServices.defineTemplateBodyVisitor(templateBodyVisitor, scriptVisitor)
}

/**
* Unwrap typescript types like "X as F"
* @template T
Expand Down Expand Up @@ -1294,8 +1313,7 @@ function getComponentComments (context) {
return tokens
}

function compositingVisitors (...visitors) {
const visitor = {}
function compositingVisitors (visitor, ...visitors) {
for (const v of visitors) {
for (const key in v) {
if (visitor[key]) {
Expand Down
124 changes: 124 additions & 0 deletions tests/lib/rules/comma-style.js
@@ -0,0 +1,124 @@
/**
* @author Yosuke Ota
*/
'use strict'

const { RuleTester } = require('eslint')
const rule = require('../../../lib/rules/comma-style')

const tester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: { ecmaVersion: 2018 }
})

tester.run('comma-style', rule, {
valid: [
`<template>
<CustomButton @click="() => fn({
a,
b
})" />
</template>`,
{
code: `
<template>
<CustomButton @click="($event,
data) => fn()" />
</template>`,
options: ['last', { exceptions: { ArrowFunctionExpression: false }}]
},
{
code: `
<template>
<CustomButton @click="($event
, data) => fn()" />
</template>`,
options: ['first', { exceptions: { ArrowFunctionExpression: false }}]
}
],
invalid: [
{
code: `
<template>
<CustomButton @click="() => fn({
a
, b
})" />
</template>`,
output: `
<template>
<CustomButton @click="() => fn({
a,
b
})" />
</template>`,
errors: [
{
message: "',' should be placed last.",
line: 5
}
]
},
{
code: `
<template>
<CustomButton @click="($event
, data) => fn()" />
</template>`,
options: ['last', { exceptions: { ArrowFunctionExpression: false }}],
output: `
<template>
<CustomButton @click="($event,
data) => fn()" />
</template>`,
errors: [
{
message: "',' should be placed last.",
line: 4
}
]
},
{
code: `
<template>
<CustomButton @click="($event,
data) => fn()" />
</template>`,
options: ['first', { exceptions: { ArrowFunctionExpression: false }}],
output: `
<template>
<CustomButton @click="($event
,data) => fn()" />
</template>`,
errors: [
{
message: "',' should be placed first."
// line: 3 // eslint v7.0
}
]
},
{
code: `
<template>
<CustomButton v-slot="foo,
bar" >
<div/>
</CustomButton>
</template>`,
options: ['first', { exceptions: { FunctionExpression: false }}],
output: `
<template>
<CustomButton v-slot="foo
,bar" >
<div/>
</CustomButton>
</template>`,
errors: [
{
message: "',' should be placed first."
// line: 3 // eslint v7.0
}
]
}
]
})

0 comments on commit f537354

Please sign in to comment.