Repository: vuejs/eslint-plugin-vue
Pull request #838 (2019-02-28 01:16:15)

⭐️New: Add vue/no-deprecated-scope-attribute rule
`vue/no-deprecated-scope-attribute` rule reports deprecated `scope` attribute in Vue.js v2.5.0+.


```vue
<template>
  <ListComponent>
    <!-- ✓ GOOD -->
    <template v-slot:name="props">
      {{ props.title }}
    </template>
    <template slot="name" slot-scope="props">
      {{ props.title }}
    </template>
  </ListComponent>
  <ListComponent>
    <!-- ✗ BAD -->
    <template slot="name" scope="props">
      {{ props.title }}
    </template>
  </ListComponent>
</template>
```

---

refs https://github.com/vuejs/eslint-plugin-vue/issues/800#issuecomment-460646676


Patch:
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 82baedd0..97117319 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -149,8 +149,10 @@ For example:
 | [vue/dot-location](./dot-location.md) | enforce consistent newlines before and after dots | :wrench: |
 | [vue/eqeqeq](./eqeqeq.md) | require the use of `===` and `!==` | :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/match-component-file-name](./match-component-file-name.md) | require component name property to match its file name |  |
 | [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: |
+| [vue/no-deprecated-scope-attribute](./no-deprecated-scope-attribute.md) | disallow deprecated `scope` attribute (in Vue.js 2.5.0+) | :wrench: |
 | [vue/no-empty-pattern](./no-empty-pattern.md) | disallow empty destructuring patterns |  |
 | [vue/no-restricted-syntax](./no-restricted-syntax.md) | disallow specified syntax |  |
 | [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: |
diff --git a/docs/rules/keyword-spacing.md b/docs/rules/keyword-spacing.md
new file mode 100644
index 00000000..f053a90b
--- /dev/null
+++ b/docs/rules/keyword-spacing.md
@@ -0,0 +1,23 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/keyword-spacing
+description: enforce consistent spacing before and after keywords
+---
+# vue/keyword-spacing
+> enforce consistent spacing before and after keywords
+
+- :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 [keyword-spacing] rule but it applies to the expressions in `<template>`.
+
+## :books: Further reading
+
+- [keyword-spacing]
+
+[keyword-spacing]: https://eslint.org/docs/rules/keyword-spacing
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/keyword-spacing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/keyword-spacing.js)
diff --git a/docs/rules/no-deprecated-scope-attribute.md b/docs/rules/no-deprecated-scope-attribute.md
new file mode 100644
index 00000000..d7ac6ef9
--- /dev/null
+++ b/docs/rules/no-deprecated-scope-attribute.md
@@ -0,0 +1,47 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-scope-attribute
+description: disallow deprecated `scope` attribute (in Vue.js 2.5.0+)
+---
+# vue/no-deprecated-scope-attribute
+> disallow deprecated `scope` attribute (in Vue.js 2.5.0+)
+
+- :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.
+
+## :book: Rule Details
+
+This rule reports deprecated `scope` attribute in Vue.js v2.5.0+.
+
+<eslint-code-block fix :rules="{'vue/no-deprecated-scope-attribute': ['error']}">
+
+```vue
+<template>
+  <ListComponent>
+    <!-- ✓ GOOD -->
+    <template v-slot:name="props">
+      {{ props.title }}
+    </template>
+    <template slot="name" slot-scope="props">
+      {{ props.title }}
+    </template>
+  </ListComponent>
+  <ListComponent>
+    <!-- ✗ BAD -->
+    <template slot="name" scope="props">
+      {{ props.title }}
+    </template>
+  </ListComponent>
+</template>
+```
+
+</eslint-code-block>
+
+## :books: Further reading
+
+- [API - scope](https://vuejs.org/v2/api/#scope-removed)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-scope-attribute.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-scope-attribute.js)
diff --git a/docs/rules/require-direct-export.md b/docs/rules/require-direct-export.md
index 778c6630..63d9fb19 100644
--- a/docs/rules/require-direct-export.md
+++ b/docs/rules/require-direct-export.md
@@ -7,37 +7,51 @@ description: require the component to be directly exported
 # vue/require-direct-export
 > require the component to be directly exported
 
-## Rule Details
+## :book: Rule Details
 
 This rule aims to require that the component object be directly exported.
 
-:-1: Examples of **incorrect** code:
+<eslint-code-block :rules="{'vue/require-direct-export': ['error']}">
 
-```js
+```vue
+<script>
+/* ✓ GOOD */
+export default {
+  name: 'ComponentA',
+  data() {
+    return {
+      state: 1
+    }
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/require-direct-export': ['error']}">
+
+```vue
+<script>
 const ComponentA = {
-	name: 'ComponentA',
-	data() {
-		return {
-			state: 1
-		}
-	}
+  name: 'ComponentA',
+  data() {
+    return {
+      state: 1
+    }
+  }
 }
 
+/* ✗ BAD */
 export default ComponentA
+</script>
 ```
 
-:+1: Examples of **correct** code:
+</eslint-code-block>
 
-```js
-export default {
-	name: 'ComponentA',
-	data() {
-		return {
-			state: 1
-		}
-	}
-}
-```
+## :wrench: Options
+
+Nothing.
 
 ## :mag: Implementation
 
diff --git a/docs/rules/v-on-function-call.md b/docs/rules/v-on-function-call.md
index e684f08f..7e858eb7 100644
--- a/docs/rules/v-on-function-call.md
+++ b/docs/rules/v-on-function-call.md
@@ -11,66 +11,80 @@ description: enforce or forbid parentheses after method calls without arguments
 
 ## :book: Rule Details
 
-:-1: Example of **incorrect** code for this rule:
-
-```html
-<button v-on:click="closeModal()">
-  Close
-</button>
+This rule aims to enforce to bind methods to `v-on` or call methods on `v-on` when without arguments.
+
+<eslint-code-block fix :rules="{'vue/v-on-function-call': ['error']}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <button v-on:click="closeModal">
+    Close
+  </button>
+
+  <!-- ✗ BAD -->
+  <button v-on:click="closeModal()">
+    Close
+  </button>
+</template>
 ```
 
-:+1: Example of **correct** code for this rule:
-
-```html
-<button v-on:click="closeModal">
-  Close
-</button>
-```
+</eslint-code-block>
 
 ## :wrench: Options
 
 Default is set to `never`.
 
-```
-'vue/v-on-function-call': [2, 'always'|'never']
+```json
+{
+  "vue/v-on-function-call": ["error", "always"|"never"]
+}
 ```
 
 ### `"always"` - Always use parentheses in `v-on` directives
 
-:-1: Example of **incorrect** code:
+<eslint-code-block fix :rules="{'vue/v-on-function-call': ['error', 'always']}">
 
-```html
-<button v-on:click="closeModal">
-  Close
-</button>
-```
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <button v-on:click="closeModal()">
+    Close
+  </button>
 
-:+1: Example of **correct** code:
-
-```html
-<button v-on:click="closeModal()">
-  Close
-</button>
+  <!-- ✗ BAD -->
+  <button v-on:click="closeModal">
+    Close
+  </button>
+</template>
 ```
 
+</eslint-code-block>
+
 ### `"never"` - Never use parentheses in `v-on` directives for method calls without arguments
 
-:-1: Example of **incorrect** code:
 
-```html
-<button v-on:click="closeModal()">
-  Close
-</button>
-```
+<eslint-code-block fix :rules="{'vue/v-on-function-call': ['error', 'never']}">
 
-:+1: Example of **correct** code:
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <button v-on:click="closeModal">
+    Close
+  </button>
+  <button v-on:click="closeModal(arg)">
+    Close
+  </button>
 
-```html
-<button v-on:click="closeModal">
-  Close
-</button>
+  <!-- ✗ BAD -->
+  <button v-on:click="closeModal()">
+    Close
+  </button>
+</template>
 ```
 
+</eslint-code-block>
+
 ## :mag: Implementation
 
 - [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-on-function-call.js)
diff --git a/lib/configs/no-layout-rules.js b/lib/configs/no-layout-rules.js
index b50aaddb..1dcab231 100644
--- a/lib/configs/no-layout-rules.js
+++ b/lib/configs/no-layout-rules.js
@@ -17,6 +17,7 @@ module.exports = {
     'vue/html-quotes': 'off',
     'vue/html-self-closing': 'off',
     'vue/key-spacing': 'off',
+    'vue/keyword-spacing': 'off',
     'vue/max-attributes-per-line': 'off',
     'vue/multiline-html-element-content-newline': 'off',
     'vue/mustache-interpolation-spacing': 'off',
diff --git a/lib/index.js b/lib/index.js
index 8dbae682..6c727f24 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -27,6 +27,7 @@ module.exports = {
     'html-self-closing': require('./rules/html-self-closing'),
     'jsx-uses-vars': require('./rules/jsx-uses-vars'),
     'key-spacing': require('./rules/key-spacing'),
+    'keyword-spacing': require('./rules/keyword-spacing'),
     'match-component-file-name': require('./rules/match-component-file-name'),
     'max-attributes-per-line': require('./rules/max-attributes-per-line'),
     'multiline-html-element-content-newline': require('./rules/multiline-html-element-content-newline'),
@@ -35,6 +36,7 @@ module.exports = {
     'no-async-in-computed-properties': require('./rules/no-async-in-computed-properties'),
     'no-boolean-default': require('./rules/no-boolean-default'),
     'no-confusing-v-for-v-if': require('./rules/no-confusing-v-for-v-if'),
+    'no-deprecated-scope-attribute': require('./rules/no-deprecated-scope-attribute'),
     'no-dupe-keys': require('./rules/no-dupe-keys'),
     'no-duplicate-attributes': require('./rules/no-duplicate-attributes'),
     'no-empty-pattern': require('./rules/no-empty-pattern'),
diff --git a/lib/rules/keyword-spacing.js b/lib/rules/keyword-spacing.js
new file mode 100644
index 00000000..548c39ef
--- /dev/null
+++ b/lib/rules/keyword-spacing.js
@@ -0,0 +1,12 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const { wrapCoreRule } = require('../utils')
+
+// eslint-disable-next-line no-invalid-meta
+module.exports = wrapCoreRule(
+  require('eslint/lib/rules/keyword-spacing'),
+  { skipDynamicArguments: true }
+)
diff --git a/lib/rules/no-deprecated-scope-attribute.js b/lib/rules/no-deprecated-scope-attribute.js
new file mode 100644
index 00000000..facac6cd
--- /dev/null
+++ b/lib/rules/no-deprecated-scope-attribute.js
@@ -0,0 +1,28 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const scopeAttribute = require('./syntaxes/scope-attribute')
+
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description: 'disallow deprecated `scope` attribute (in Vue.js 2.5.0+)',
+      category: undefined,
+      url: 'https://eslint.vuejs.org/rules/no-deprecated-scope-attribute.html'
+    },
+    fixable: 'code',
+    schema: [],
+    messages: {
+      forbiddenScopeAttribute: '`scope` attributes are deprecated.'
+    }
+  },
+  create (context) {
+    const templateBodyVisitor = scopeAttribute.createTemplateBodyVisitor(context)
+    return utils.defineTemplateBodyVisitor(context, templateBodyVisitor)
+  }
+}
diff --git a/lib/rules/syntaxes/scope-attribute.js b/lib/rules/syntaxes/scope-attribute.js
new file mode 100644
index 00000000..faa9da2e
--- /dev/null
+++ b/lib/rules/syntaxes/scope-attribute.js
@@ -0,0 +1,27 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+module.exports = {
+  deprecated: '2.5.0',
+  createTemplateBodyVisitor (context) {
+    /**
+     * Reports `scope` node
+     * @param {VDirectiveKey} scopeKey node of `scope`
+     * @returns {void}
+     */
+    function reportScope (scopeKey) {
+      context.report({
+        node: scopeKey,
+        messageId: 'forbiddenScopeAttribute',
+        // fix to use `slot-scope`
+        fix: fixer => fixer.replaceText(scopeKey, 'slot-scope')
+      })
+    }
+
+    return {
+      "VAttribute[directive=true] > VDirectiveKey[name.name='scope']": reportScope
+    }
+  }
+}


Test patch:
diff --git a/tests/lib/rules/keyword-spacing.js b/tests/lib/rules/keyword-spacing.js
new file mode 100644
index 00000000..f20814ab
--- /dev/null
+++ b/tests/lib/rules/keyword-spacing.js
@@ -0,0 +1,160 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/keyword-spacing')
+
+const tester = new RuleTester({
+  parser: 'vue-eslint-parser',
+  parserOptions: { ecmaVersion: 2015 }
+})
+
+tester.run('keyword-spacing', rule, {
+  valid: [
+    `<template>
+      <div @event="
+        if (foo) {
+          //...
+        } else if (bar) {
+          //...
+        } else {
+          //...
+        }
+      " />
+    </template>`,
+    {
+      code:
+      `<template>
+        <div @event="
+          if(foo) {
+            //...
+          }else if(bar) {
+            //...
+          }else{
+            //...
+          }
+        " />
+      </template>`,
+      options: [{ before: false, after: false }]
+    },
+    `<template>
+      <div :[(function(){return(1)})()]="val" />
+    </template>`
+  ],
+  invalid: [
+    {
+      code:
+      `<template>
+        <div @event="
+          if(foo) {
+            //...
+          }else if(bar) {
+            //...
+          }else{
+            //...
+          }
+        " />
+      </template>`,
+      output:
+      `<template>
+        <div @event="
+          if (foo) {
+            //...
+          } else if (bar) {
+            //...
+          } else {
+            //...
+          }
+        " />
+      </template>`,
+      errors: [
+        {
+          message: 'Expected space(s) after "if".',
+          line: 3
+        },
+        {
+          message: 'Expected space(s) before "else".',
+          line: 5
+        },
+        {
+          message: 'Expected space(s) after "if".',
+          line: 5
+        },
+        {
+          message: 'Expected space(s) before "else".',
+          line: 7
+        },
+        {
+          message: 'Expected space(s) after "else".',
+          line: 7
+        }
+      ]
+    },
+    {
+      code:
+      `<template>
+        <div @event="
+          if (foo) {
+            //...
+          } else if (bar) {
+            //...
+          } else {
+            //...
+          }
+        " />
+      </template>`,
+      options: [{ before: false, after: false }],
+      output:
+      `<template>
+        <div @event="
+          if(foo) {
+            //...
+          }else if(bar) {
+            //...
+          }else{
+            //...
+          }
+        " />
+      </template>`,
+      errors: [
+        {
+          message: 'Unexpected space(s) after "if".',
+          line: 3
+        },
+        {
+          message: 'Unexpected space(s) before "else".',
+          line: 5
+        },
+        {
+          message: 'Unexpected space(s) after "if".',
+          line: 5
+        },
+        {
+          message: 'Unexpected space(s) before "else".',
+          line: 7
+        },
+        {
+          message: 'Unexpected space(s) after "else".',
+          line: 7
+        }
+      ]
+    },
+    {
+      code:
+      `<template>
+        <div :[(function(){return(1)})()]="(function(){return(1)})()" />
+      </template>`,
+      output:
+      `<template>
+        <div :[(function(){return(1)})()]="(function(){return (1)})()" />
+      </template>`,
+      errors: [
+        {
+          message: 'Expected space(s) after "return".',
+          line: 2
+        }]
+    }
+  ]
+})
diff --git a/tests/lib/rules/no-deprecated-scope-attribute.js b/tests/lib/rules/no-deprecated-scope-attribute.js
new file mode 100644
index 00000000..be4d9f6b
--- /dev/null
+++ b/tests/lib/rules/no-deprecated-scope-attribute.js
@@ -0,0 +1,101 @@
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/no-deprecated-scope-attribute')
+
+const tester = new RuleTester({
+  parser: 'vue-eslint-parser',
+  parserOptions: {
+    ecmaVersion: 2015
+  }
+})
+
+tester.run('no-deprecated-scope-attribute', rule, {
+  valid: [
+    `<template>
+      <LinkList>
+        <a v-slot:name />
+      </LinkList>
+    </template>`,
+    `<template>
+      <LinkList>
+        <a #name />
+      </LinkList>
+    </template>`,
+    `<template>
+      <LinkList>
+        <a v-slot="{a}" />
+      </LinkList>
+    </template>`,
+    `<template>
+      <LinkList>
+        <a #default="{a}" />
+      </LinkList>
+    </template>`,
+    `<template>
+      <LinkList>
+        <a slot="name" />
+      </LinkList>
+    </template>`,
+    `<template>
+      <LinkList>
+        <a slot-scope="{a}" />
+      </LinkList>
+    </template>`,
+    `<template>
+      <LinkList>
+        <a />
+      </LinkList>
+    </template>`
+  ],
+  invalid: [
+    {
+      code: `
+      <template>
+        <LinkList>
+          <template scope="{a}">
+            <a />
+          </template>
+        </LinkList>
+      </template>`,
+      output: `
+      <template>
+        <LinkList>
+          <template slot-scope="{a}">
+            <a />
+          </template>
+        </LinkList>
+      </template>`,
+      errors: [
+        {
+          message: '`scope` attributes are deprecated.',
+          line: 4
+        }
+      ]
+    },
+    {
+      code: `
+      <template>
+        <LinkList>
+          <template slot="name" scope="{a}">
+            <a />
+          </template>
+        </LinkList>
+      </template>`,
+      output: `
+      <template>
+        <LinkList>
+          <template slot="name" slot-scope="{a}">
+            <a />
+          </template>
+        </LinkList>
+      </template>`,
+      errors: [
+        {
+          message: '`scope` attributes are deprecated.',
+          line: 4
+        }
+      ]
+    }
+  ]
+})