Skip to content

Commit e633212

Browse files
authored
Add vue/comma-spacing rule (#1140)
* ⭐️New: Add `vue/comma-spacing` rule * use skipDynamicArguments
1 parent 6f892ba commit e633212

File tree

7 files changed

+389
-4
lines changed

7 files changed

+389
-4
lines changed

docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ For example:
271271
| [vue/brace-style](./brace-style.md) | enforce consistent brace style for blocks | :wrench: |
272272
| [vue/camelcase](./camelcase.md) | enforce camelcase naming convention | |
273273
| [vue/comma-dangle](./comma-dangle.md) | require or disallow trailing commas | :wrench: |
274+
| [vue/comma-spacing](./comma-spacing.md) | enforce consistent spacing before and after commas | :wrench: |
274275
| [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: |
275276
| [vue/dot-location](./dot-location.md) | enforce consistent newlines before and after dots | :wrench: |
276277
| [vue/eqeqeq](./eqeqeq.md) | require the use of `===` and `!==` | :wrench: |

docs/rules/comma-spacing.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-spacing
5+
description: enforce consistent spacing before and after commas
6+
---
7+
# vue/comma-spacing
8+
> enforce consistent spacing before and after commas
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-spacing] rule but it applies to the expressions in `<template>`.
13+
14+
## :books: Further reading
15+
16+
- [comma-spacing]
17+
18+
[comma-spacing]: https://eslint.org/docs/rules/comma-spacing
19+
20+
## :mag: Implementation
21+
22+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/comma-spacing.js)
23+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/comma-spacing.js)

lib/configs/no-layout-rules.js

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

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = {
1515
'brace-style': require('./rules/brace-style'),
1616
'camelcase': require('./rules/camelcase'),
1717
'comma-dangle': require('./rules/comma-dangle'),
18+
'comma-spacing': require('./rules/comma-spacing'),
1819
'comment-directive': require('./rules/comment-directive'),
1920
'component-definition-name-casing': require('./rules/component-definition-name-casing'),
2021
'component-name-in-template-casing': require('./rules/component-name-in-template-casing'),

lib/rules/comma-spacing.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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-spacing'),
11+
{ skipDynamicArguments: true, skipDynamicArgumentsReport: true }
12+
)

lib/utils/index.js

+68-4
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,24 @@ const componentComments = new WeakMap()
5454
* @param {TokenStore} tokenStore The token store object for template.
5555
*/
5656
function wrapContextToOverrideTokenMethods (context, tokenStore) {
57-
const sourceCode = new Proxy(context.getSourceCode(), {
57+
const eslintSourceCode = context.getSourceCode()
58+
let tokensAndComments
59+
function getTokensAndComments () {
60+
if (tokensAndComments) {
61+
return tokensAndComments
62+
}
63+
const templateBody = eslintSourceCode.ast.templateBody
64+
tokensAndComments = templateBody ? tokenStore.getTokens(templateBody, {
65+
includeComments: true
66+
}) : []
67+
return tokensAndComments
68+
}
69+
const sourceCode = new Proxy(Object.assign({}, eslintSourceCode), {
5870
get (object, key) {
59-
return key in tokenStore ? tokenStore[key] : object[key]
71+
if (key === 'tokensAndComments') {
72+
return getTokensAndComments()
73+
}
74+
return key in tokenStore ? tokenStore[key] : eslintSourceCode[key]
6075
}
6176
})
6277

@@ -68,6 +83,50 @@ function wrapContextToOverrideTokenMethods (context, tokenStore) {
6883
}
6984
}
7085

86+
/**
87+
* Wrap the rule context object to override report method to skip the dynamic argument.
88+
* @param {RuleContext} context The rule context object.
89+
*/
90+
function wrapContextToOverrideReportMethodToSkipDynamicArgument (context) {
91+
const sourceCode = context.getSourceCode()
92+
const templateBody = sourceCode.ast.templateBody
93+
if (!templateBody) {
94+
return context
95+
}
96+
const directiveKeyRanges = []
97+
const traverseNodes = vueEslintParser.AST.traverseNodes
98+
traverseNodes(templateBody, {
99+
enterNode (node, parent) {
100+
if (
101+
parent && parent.type === 'VDirectiveKey' && node.type === 'VExpressionContainer'
102+
) {
103+
directiveKeyRanges.push(node.range)
104+
}
105+
},
106+
leaveNode () {}
107+
})
108+
109+
return {
110+
__proto__: context,
111+
report (descriptor, ...args) {
112+
let range = null
113+
if (descriptor.loc) {
114+
range = [sourceCode.getIndexFromLoc(descriptor.loc.start), sourceCode.getIndexFromLoc(descriptor.loc.end)]
115+
} else if (descriptor.node) {
116+
range = descriptor.node.range
117+
}
118+
if (range) {
119+
for (const directiveKeyRange of directiveKeyRanges) {
120+
if (range[0] < directiveKeyRange[1] && directiveKeyRange[0] < range[1]) {
121+
return
122+
}
123+
}
124+
}
125+
context.report(descriptor, ...args)
126+
}
127+
}
128+
}
129+
71130
// ------------------------------------------------------------------------------
72131
// Exports
73132
// ------------------------------------------------------------------------------
@@ -97,13 +156,14 @@ module.exports = {
97156
/**
98157
* Wrap a given core rule to apply it to Vue.js template.
99158
* @param {Rule} coreRule The core rule implementation to wrap.
100-
* @param {Object|undefined} options The option of this rule.
159+
* @param {Object} [options] The option of this rule.
101160
* @param {string|undefined} options.category The category of this rule.
102161
* @param {boolean|undefined} options.skipDynamicArguments If `true`, skip validation within dynamic arguments.
162+
* @param {boolean|undefined} options.skipDynamicArgumentsReport If `true`, skip report within dynamic arguments.
103163
* @returns {Rule} The wrapped rule implementation.
104164
*/
105165
wrapCoreRule (coreRule, options) {
106-
const { category, skipDynamicArguments } = options || {}
166+
const { category, skipDynamicArguments, skipDynamicArgumentsReport } = options || {}
107167
return {
108168
create (context) {
109169
const tokenStore =
@@ -116,6 +176,10 @@ module.exports = {
116176
context = wrapContextToOverrideTokenMethods(context, tokenStore)
117177
}
118178

179+
if (skipDynamicArgumentsReport) {
180+
context = wrapContextToOverrideReportMethodToSkipDynamicArgument(context)
181+
}
182+
119183
// Move `Program` handlers to `VElement[parent.type!='VElement']`
120184
const handlers = coreRule.create(context)
121185
if (handlers.Program) {

0 commit comments

Comments
 (0)