Repository: vuejs/eslint-plugin-vue
Pull request #1201 (2020-06-07 09:03:26)

Add `vue/func-call-spacing` rule
This PR adds the wrapper rule of the [func-call-spacing](https://eslint.org/docs/rules/func-call-spacing) core rule to apply to the expressions in `<template>`.


Patch:
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 636d6768..1fe65868 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -322,6 +322,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
 | [vue/dot-location](./dot-location.md) | enforce consistent newlines before and after dots | :wrench: |
 | [vue/dot-notation](./dot-notation.md) | enforce dot notation whenever possible | :wrench: |
 | [vue/eqeqeq](./eqeqeq.md) | require the use of `===` and `!==` | :wrench: |
+| [vue/func-call-spacing](./func-call-spacing.md) | require or disallow spacing between function identifiers and their invocations | :wrench: |
 | [vue/key-spacing](./key-spacing.md) | enforce consistent spacing between keys and values in object literal properties | :wrench: |
 | [vue/keyword-spacing](./keyword-spacing.md) | enforce consistent spacing before and after keywords | :wrench: |
 | [vue/max-len](./max-len.md) | enforce a maximum line length |  |
@@ -333,6 +334,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
 | [vue/object-curly-newline](./object-curly-newline.md) | enforce consistent line breaks inside braces | :wrench: |
 | [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: |
 | [vue/object-property-newline](./object-property-newline.md) | enforce placing object properties on separate lines | :wrench: |
+| [vue/operator-linebreak](./operator-linebreak.md) | enforce consistent linebreak style for operators | :wrench: |
 | [vue/prefer-template](./prefer-template.md) | require template literals instead of string concatenation | :wrench: |
 | [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: |
diff --git a/docs/rules/attribute-hyphenation.md b/docs/rules/attribute-hyphenation.md
index 5a1ff415..b6a4915c 100644
--- a/docs/rules/attribute-hyphenation.md
+++ b/docs/rules/attribute-hyphenation.md
@@ -18,10 +18,10 @@ This rule enforces using hyphenated attribute names on custom components in Vue
 
 ```vue
 <template>
-  <!-- ✔ GOOD -->
+  <!-- ✓ GOOD -->
   <MyComponent my-prop="prop" />
 
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <MyComponent myProp="prop" />
 </template>
 ```
@@ -51,10 +51,10 @@ It errors on upper case letters.
 
 ```vue
 <template>
-  <!-- ✔ GOOD -->
+  <!-- ✓ GOOD -->
   <MyComponent my-prop="prop" />
 
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <MyComponent myProp="prop" />
 </template>
 ```
@@ -68,34 +68,34 @@ It errors on hyphens except `data-`, `aria-` and `slot-scope`.
 
 ```vue
 <template>
-  <!-- ✔ GOOD -->
+  <!-- ✓ GOOD -->
   <MyComponent myProp="prop" />
   <MyComponent data-id="prop" />
   <MyComponent aria-role="button" />
   <MyComponent slot-scope="prop" />
 
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <MyComponent my-prop="prop" />
 </template>
 ```
 
 </eslint-code-block>
 
-### `"never", { "ignore": ["custom-prop"] }` 
+### `"never", { "ignore": ["custom-prop"] }`
 Don't use hyphenated name but allow custom attributes
 
 <eslint-code-block fix :rules="{'vue/attribute-hyphenation': ['error', 'never', { ignore: ['custom-prop']}]}">
 
 ```vue
 <template>
-  <!-- ✔ GOOD -->
+  <!-- ✓ GOOD -->
   <MyComponent myProp="prop" />
   <MyComponent custom-prop="prop" />
   <MyComponent data-id="prop" />
   <MyComponent aria-role="button" />
   <MyComponent slot-scope="prop" />
 
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <MyComponent my-prop="prop" />
 </template>
 ```
diff --git a/docs/rules/custom-event-name-casing.md b/docs/rules/custom-event-name-casing.md
index cc0153c8..52ddc7b5 100644
--- a/docs/rules/custom-event-name-casing.md
+++ b/docs/rules/custom-event-name-casing.md
@@ -23,21 +23,21 @@ See [Guide - Custom Events] for more details.
 
 ```vue
 <template>
-  <!-- ✔ GOOD -->
+  <!-- ✓ GOOD -->
   <button @click="$emit('my-event')" />
 
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <button @click="$emit('myEvent')" />
 </template>
 <script>
 export default {
   methods: {
     onClick () {
-      /* ✔ GOOD */
+      /* ✓ GOOD */
       this.$emit('my-event')
       this.$emit('update:myProp', myProp)
 
-      /* ✘ BAD */
+      /* ✗ BAD */
       this.$emit('myEvent')
     }
   }
diff --git a/docs/rules/func-call-spacing.md b/docs/rules/func-call-spacing.md
new file mode 100644
index 00000000..41af5a45
--- /dev/null
+++ b/docs/rules/func-call-spacing.md
@@ -0,0 +1,25 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/func-call-spacing
+description: require or disallow spacing between function identifiers and their invocations
+---
+# vue/func-call-spacing
+> require or disallow spacing between function identifiers and their invocations
+
+- :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 [func-call-spacing] rule but it applies to the expressions in `<template>`.
+
+## :books: Further reading
+
+- [func-call-spacing]
+
+[func-call-spacing]: https://eslint.org/docs/rules/func-call-spacing
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/func-call-spacing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/func-call-spacing.js)
+
+<sup>Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/func-call-spacing)</sup>
diff --git a/docs/rules/no-potential-component-option-typo.md b/docs/rules/no-potential-component-option-typo.md
index e347676d..6474adda 100644
--- a/docs/rules/no-potential-component-option-typo.md
+++ b/docs/rules/no-potential-component-option-typo.md
@@ -18,8 +18,7 @@ This rule disallow a potential typo in your component options
   "vue/no-potential-component-option-typo": ["error", {
     "presets": ["all"],
     "custom": ["test"]
-    }
-  ]
+  }]
 }
 ```
 
@@ -30,21 +29,21 @@ This rule disallow a potential typo in your component options
 export default {
   /* ✓ GOOD */
   props: {
-    
+
   },
-  /* × BAD */
+  /* ✗ BAD */
   method: {
 
   },
   /* ✓ GOOD */
   data: {
-    
+
   },
-  /* × BAD */
+  /* ✗ BAD */
   beforeRouteEnteR() {
 
   },
-  /* × BAD due to custom option 'test'*/
+  /* ✗ BAD due to custom option 'test' */
   testt: {
 
   }
@@ -72,19 +71,19 @@ export default {
 ```vue
 <script>
 export default {
-  /* ✓ BAD, due to threshold is 5 */
+  /* ✓ GOOD, due to threshold is 5 */
   props: {
-    
+
   },
-  /* ✓ BAD, due to threshold is 5 */
+  /* ✓ GOOD, due to threshold is 5 */
   method: {
 
   },
-  /* ✓ BAD, due to threshold is 5 */
+  /* ✓ GOOD, due to threshold is 5 */
   data: {
-    
+
   },
-  /* × GOOD, due to we don't choose vue-router preset or add a custom option */
+  /* ✗ BAD, due to we don't choose vue-router preset or add a custom option */
   beforeRouteEnteR() {
 
   }
diff --git a/docs/rules/no-restricted-static-attribute.md b/docs/rules/no-restricted-static-attribute.md
index a2009d7a..f0453e6e 100644
--- a/docs/rules/no-restricted-static-attribute.md
+++ b/docs/rules/no-restricted-static-attribute.md
@@ -25,7 +25,7 @@ This rule takes a list of strings, where each string is a attribute name or patt
 
 ```vue
 <template>
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <div foo="x" />
   <div bar />
 </template>
@@ -62,7 +62,7 @@ The following properties can be specified for the object.
   <!-- ✓ GOOD -->
   <div foo="foo" />
 
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <div foo="bar" />
 </template>
 ```
@@ -78,7 +78,7 @@ The following properties can be specified for the object.
   <!-- ✓ GOOD -->
   <CoolButton foo="x" />
 
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <MyButton foo="x" />
 </template>
 ```
diff --git a/docs/rules/no-restricted-syntax.md b/docs/rules/no-restricted-syntax.md
index 04dcd404..ddd7d044 100644
--- a/docs/rules/no-restricted-syntax.md
+++ b/docs/rules/no-restricted-syntax.md
@@ -25,11 +25,11 @@ Forbids call expressions inside mustache interpolation.
 
 ```vue
 <template>
-  <!-- ✔ GOOD -->
+  <!-- ✓ GOOD -->
   <div> {{ foo }} </div>
   <div> {{ foo.bar }} </div>
 
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <div> {{ foo() }} </div>
   <div> {{ foo.bar() }} </div>
   <div> {{ foo().bar }} </div>
diff --git a/docs/rules/no-restricted-v-bind.md b/docs/rules/no-restricted-v-bind.md
index cc141f1b..26f37646 100644
--- a/docs/rules/no-restricted-v-bind.md
+++ b/docs/rules/no-restricted-v-bind.md
@@ -25,7 +25,7 @@ This rule takes a list of strings, where each string is a argument name or patte
 
 ```vue
 <template>
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <div v-bind:foo="x" />
   <div :bar="x" />
 </template>
@@ -39,7 +39,7 @@ By default, `'/^v-/'` is set. This prevents mistakes intended to be directives.
 
 ```vue
 <template>
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <MyInput :v-model="x" />
   <div :v-if="x" />
 </template>
@@ -84,7 +84,7 @@ The following properties can be specified for the object.
   <!-- ✓ GOOD -->
   <div :foo="x" />
 
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <div :foo.prop="x" />
 </template>
 ```
@@ -100,7 +100,7 @@ The following properties can be specified for the object.
   <!-- ✓ GOOD -->
   <CoolButton :foo="x" />
 
-  <!-- ✘ BAD -->
+  <!-- ✗ BAD -->
   <MyButton :foo="x" />
 </template>
 ```
diff --git a/docs/rules/no-unused-properties.md b/docs/rules/no-unused-properties.md
index 43818706..bcf1a56e 100644
--- a/docs/rules/no-unused-properties.md
+++ b/docs/rules/no-unused-properties.md
@@ -89,7 +89,7 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property
 <eslint-code-block :rules="{'vue/no-unused-properties': ['error', {groups: ['props', 'data']}]}">
 
 ```vue
-<!-- ✓ BAD (`count` data not used) -->
+<!-- ✗ BAD (`count` data not used) -->
 <script>
   export default {
     data() {
@@ -136,7 +136,7 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property
 <eslint-code-block :rules="{'vue/no-unused-properties': ['error', {groups: ['props', 'computed']}]}">
 
 ```vue
-<!-- ✓ BAD (`reversedMessage` computed property not used) -->
+<!-- ✗ BAD (`reversedMessage` computed property not used) -->
 <template>
   <p>{{ message }}</p>
 </template>
diff --git a/docs/rules/operator-linebreak.md b/docs/rules/operator-linebreak.md
new file mode 100644
index 00000000..080e7e77
--- /dev/null
+++ b/docs/rules/operator-linebreak.md
@@ -0,0 +1,25 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/operator-linebreak
+description: enforce consistent linebreak style for operators
+---
+# vue/operator-linebreak
+> enforce consistent linebreak style for operators
+
+- :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 [operator-linebreak] rule but it applies to the expressions in `<template>`.
+
+## :books: Further reading
+
+- [operator-linebreak]
+
+[operator-linebreak]: https://eslint.org/docs/rules/operator-linebreak
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/operator-linebreak.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/operator-linebreak.js)
+
+<sup>Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/operator-linebreak)</sup>
diff --git a/lib/configs/no-layout-rules.js b/lib/configs/no-layout-rules.js
index d8ba1837..3d2cb5d3 100644
--- a/lib/configs/no-layout-rules.js
+++ b/lib/configs/no-layout-rules.js
@@ -13,6 +13,7 @@ module.exports = {
     'vue/comma-spacing': 'off',
     'vue/comma-style': 'off',
     'vue/dot-location': 'off',
+    'vue/func-call-spacing': 'off',
     'vue/html-closing-bracket-newline': 'off',
     'vue/html-closing-bracket-spacing': 'off',
     'vue/html-comment-content-newline': 'off',
@@ -33,6 +34,7 @@ module.exports = {
     'vue/object-curly-newline': 'off',
     'vue/object-curly-spacing': 'off',
     'vue/object-property-newline': 'off',
+    'vue/operator-linebreak': 'off',
     'vue/padding-line-between-blocks': 'off',
     'vue/script-indent': 'off',
     'vue/singleline-html-element-content-newline': 'off',
diff --git a/lib/index.js b/lib/index.js
index c8b463f6..f962015c 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -25,6 +25,7 @@ module.exports = {
     'dot-location': require('./rules/dot-location'),
     'dot-notation': require('./rules/dot-notation'),
     eqeqeq: require('./rules/eqeqeq'),
+    'func-call-spacing': require('./rules/func-call-spacing'),
     'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'),
     'html-closing-bracket-spacing': require('./rules/html-closing-bracket-spacing'),
     'html-comment-content-newline': require('./rules/html-comment-content-newline'),
@@ -108,6 +109,7 @@ module.exports = {
     'object-curly-spacing': require('./rules/object-curly-spacing'),
     'object-property-newline': require('./rules/object-property-newline'),
     'one-component-per-file': require('./rules/one-component-per-file'),
+    'operator-linebreak': require('./rules/operator-linebreak'),
     'order-in-components': require('./rules/order-in-components'),
     'padding-line-between-blocks': require('./rules/padding-line-between-blocks'),
     'prefer-template': require('./rules/prefer-template'),
diff --git a/lib/rules/func-call-spacing.js b/lib/rules/func-call-spacing.js
new file mode 100644
index 00000000..da076f9c
--- /dev/null
+++ b/lib/rules/func-call-spacing.js
@@ -0,0 +1,11 @@
+/**
+ * @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/func-call-spacing'), {
+  skipDynamicArguments: true
+})
diff --git a/lib/rules/operator-linebreak.js b/lib/rules/operator-linebreak.js
new file mode 100644
index 00000000..2546693d
--- /dev/null
+++ b/lib/rules/operator-linebreak.js
@@ -0,0 +1,9 @@
+/**
+ * @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/operator-linebreak'))


Test patch:
diff --git a/tests/lib/rules/func-call-spacing.js b/tests/lib/rules/func-call-spacing.js
new file mode 100644
index 00000000..93df1577
--- /dev/null
+++ b/tests/lib/rules/func-call-spacing.js
@@ -0,0 +1,85 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const { RuleTester, CLIEngine } = require('eslint')
+const semver = require('semver')
+const rule = require('../../../lib/rules/func-call-spacing')
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2020 }
+})
+
+tester.run('func-call-spacing', rule, {
+  valid: [
+    `
+    <template>
+      <div :foo="foo()" />
+    </template>
+    `,
+    {
+      code: `
+      <template>
+        <div :foo="foo ()" />
+      </template>
+      `,
+      options: ['always']
+    },
+    `
+    <template>
+      <div :[foo()]="value" />
+    </template>
+    `,
+    {
+      code: `
+      <template>
+        <div :[foo()]="value" />
+      </template>
+      `,
+      options: ['always']
+    }
+  ],
+  invalid: [
+    {
+      code: `
+      <template>
+        <div :foo="foo ()" />
+      </template>
+      `,
+      output: `
+      <template>
+        <div :foo="foo()" />
+      </template>
+      `,
+      errors: [
+        {
+          message: semver.lt(CLIEngine.version, '7.0.0')
+            ? 'Unexpected newline between function name and paren.'
+            : 'Unexpected whitespace between function name and paren.',
+          line: 3
+        }
+      ]
+    },
+    {
+      code: `
+      <template>
+        <div :foo="foo()" />
+      </template>
+      `,
+      options: ['always'],
+      output: `
+      <template>
+        <div :foo="foo ()" />
+      </template>
+      `,
+      errors: [
+        {
+          message: 'Missing space between function name and paren.',
+          line: 3
+        }
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/operator-linebreak.js b/tests/lib/rules/operator-linebreak.js
new file mode 100644
index 00000000..8b0c731e
--- /dev/null
+++ b/tests/lib/rules/operator-linebreak.js
@@ -0,0 +1,129 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/operator-linebreak')
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2020 }
+})
+
+tester.run('operator-linebreak', rule, {
+  valid: [
+    `
+    <template>
+      <div :foo="1 + 2" />
+    </template>
+    `,
+    {
+      code: `
+      <template>
+        <div :foo="1 + 2" />
+      </template>
+      `,
+      options: ['before']
+    },
+    {
+      code: `
+      <template>
+        <div :foo="1 + 2" />
+      </template>
+      `,
+      options: ['none']
+    },
+    `
+    <template>
+      <div :[foo+bar]="value" />
+    </template>
+    `,
+    {
+      code: `
+      <template>
+        <div :[foo+bar]="value" />
+      </template>
+      `,
+      options: ['before']
+    },
+    {
+      code: `
+      <template>
+        <div :[foo+bar]="value" />
+      </template>
+      `,
+      options: ['none']
+    }
+  ],
+  invalid: [
+    {
+      code: `
+      <template>
+        <div :foo="1
+          + 2" />
+      </template>
+      `,
+      output: `
+      <template>
+        <div :foo="1 +
+          2" />
+      </template>
+      `,
+      errors: [
+        {
+          message: "'+' should be placed at the end of the line.",
+          line: 4
+        }
+      ]
+    },
+    {
+      code: `
+      <template>
+        <div :foo="1 +
+          2" />
+      </template>
+      `,
+      output: `
+      <template>
+        <div :foo="1
+          + 2" />
+      </template>
+      `,
+      options: ['before'],
+      errors: [
+        {
+          message: "'+' should be placed at the beginning of the line.",
+          line: 3
+        }
+      ]
+    },
+    {
+      code: `
+      <template>
+        <div :foo="1 +
+          2" />
+        <div :foo="1
+          + 2" />
+      </template>
+      `,
+      output: `
+      <template>
+        <div :foo="1 +          2" />
+        <div :foo="1          + 2" />
+      </template>
+      `,
+      options: ['none'],
+      errors: [
+        {
+          message: "There should be no line break before or after '+'.",
+          line: 3
+        },
+        {
+          message: "There should be no line break before or after '+'.",
+          line: 6
+        }
+      ]
+    }
+  ]
+})