Skip to content

Commit

Permalink
Add vue/comma-spacing rule (#1140)
Browse files Browse the repository at this point in the history
* ⭐️New: Add `vue/comma-spacing` rule

* use skipDynamicArguments
  • Loading branch information
ota-meshi committed May 20, 2020
1 parent 6f892ba commit e633212
Show file tree
Hide file tree
Showing 7 changed files with 389 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -271,6 +271,7 @@ For example:
| [vue/brace-style](./brace-style.md) | enforce consistent brace style for blocks | :wrench: |
| [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/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
23 changes: 23 additions & 0 deletions docs/rules/comma-spacing.md
@@ -0,0 +1,23 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/comma-spacing
description: enforce consistent spacing before and after commas
---
# vue/comma-spacing
> enforce consistent spacing before and after commas
- :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-spacing] rule but it applies to the expressions in `<template>`.

## :books: Further reading

- [comma-spacing]

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

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/comma-spacing.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/comma-spacing.js)
1 change: 1 addition & 0 deletions lib/configs/no-layout-rules.js
Expand Up @@ -10,6 +10,7 @@ module.exports = {
'vue/block-spacing': 'off',
'vue/brace-style': 'off',
'vue/comma-dangle': 'off',
'vue/comma-spacing': 'off',
'vue/dot-location': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/html-closing-bracket-spacing': 'off',
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -15,6 +15,7 @@ module.exports = {
'brace-style': require('./rules/brace-style'),
'camelcase': require('./rules/camelcase'),
'comma-dangle': require('./rules/comma-dangle'),
'comma-spacing': require('./rules/comma-spacing'),
'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
12 changes: 12 additions & 0 deletions lib/rules/comma-spacing.js
@@ -0,0 +1,12 @@
/**
* @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-spacing'),
{ skipDynamicArguments: true, skipDynamicArgumentsReport: true }
)
72 changes: 68 additions & 4 deletions lib/utils/index.js
Expand Up @@ -54,9 +54,24 @@ const componentComments = new WeakMap()
* @param {TokenStore} tokenStore The token store object for template.
*/
function wrapContextToOverrideTokenMethods (context, tokenStore) {
const sourceCode = new Proxy(context.getSourceCode(), {
const eslintSourceCode = context.getSourceCode()
let tokensAndComments
function getTokensAndComments () {
if (tokensAndComments) {
return tokensAndComments
}
const templateBody = eslintSourceCode.ast.templateBody
tokensAndComments = templateBody ? tokenStore.getTokens(templateBody, {
includeComments: true
}) : []
return tokensAndComments
}
const sourceCode = new Proxy(Object.assign({}, eslintSourceCode), {
get (object, key) {
return key in tokenStore ? tokenStore[key] : object[key]
if (key === 'tokensAndComments') {
return getTokensAndComments()
}
return key in tokenStore ? tokenStore[key] : eslintSourceCode[key]
}
})

Expand All @@ -68,6 +83,50 @@ function wrapContextToOverrideTokenMethods (context, tokenStore) {
}
}

/**
* Wrap the rule context object to override report method to skip the dynamic argument.
* @param {RuleContext} context The rule context object.
*/
function wrapContextToOverrideReportMethodToSkipDynamicArgument (context) {
const sourceCode = context.getSourceCode()
const templateBody = sourceCode.ast.templateBody
if (!templateBody) {
return context
}
const directiveKeyRanges = []
const traverseNodes = vueEslintParser.AST.traverseNodes
traverseNodes(templateBody, {
enterNode (node, parent) {
if (
parent && parent.type === 'VDirectiveKey' && node.type === 'VExpressionContainer'
) {
directiveKeyRanges.push(node.range)
}
},
leaveNode () {}
})

return {
__proto__: context,
report (descriptor, ...args) {
let range = null
if (descriptor.loc) {
range = [sourceCode.getIndexFromLoc(descriptor.loc.start), sourceCode.getIndexFromLoc(descriptor.loc.end)]
} else if (descriptor.node) {
range = descriptor.node.range
}
if (range) {
for (const directiveKeyRange of directiveKeyRanges) {
if (range[0] < directiveKeyRange[1] && directiveKeyRange[0] < range[1]) {
return
}
}
}
context.report(descriptor, ...args)
}
}
}

// ------------------------------------------------------------------------------
// Exports
// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -97,13 +156,14 @@ module.exports = {
/**
* Wrap a given core rule to apply it to Vue.js template.
* @param {Rule} coreRule The core rule implementation to wrap.
* @param {Object|undefined} options The option of this rule.
* @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.
*/
wrapCoreRule (coreRule, options) {
const { category, skipDynamicArguments } = options || {}
const { category, skipDynamicArguments, skipDynamicArgumentsReport } = options || {}
return {
create (context) {
const tokenStore =
Expand All @@ -116,6 +176,10 @@ module.exports = {
context = wrapContextToOverrideTokenMethods(context, tokenStore)
}

if (skipDynamicArgumentsReport) {
context = wrapContextToOverrideReportMethodToSkipDynamicArgument(context)
}

// Move `Program` handlers to `VElement[parent.type!='VElement']`
const handlers = coreRule.create(context)
if (handlers.Program) {
Expand Down

0 comments on commit e633212

Please sign in to comment.