Repository: vuejs/eslint-plugin-vue
Pull request #1762 (2022-01-19 04:54:38)

Add `vue/object-shorthand` rule
This PR adds `vue/object-shorthand` rule that applies `object-shorthand` rule to expressions in `<template>`.


Patch:
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 49a6c638..127a622d 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -396,6 +396,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 after opening and before closing 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/object-shorthand](./object-shorthand.md) | require or disallow method and property shorthand syntax for object literals | :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: |
diff --git a/docs/rules/object-shorthand.md b/docs/rules/object-shorthand.md
new file mode 100644
index 00000000..e2e9249b
--- /dev/null
+++ b/docs/rules/object-shorthand.md
@@ -0,0 +1,27 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/object-shorthand
+description: require or disallow method and property shorthand syntax for object literals
+---
+# vue/object-shorthand
+
+> require or disallow method and property shorthand syntax for object literals
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+- :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 [object-shorthand] rule but it applies to the expressions in `<template>`.
+
+## :books: Further Reading
+
+- [object-shorthand]
+
+[object-shorthand]: https://eslint.org/docs/rules/object-shorthand
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/object-shorthand.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/object-shorthand.js)
+
+<sup>Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/object-shorthand)</sup>
diff --git a/lib/index.js b/lib/index.js
index 02171098..895c615c 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -151,6 +151,7 @@ module.exports = {
     'object-curly-newline': require('./rules/object-curly-newline'),
     'object-curly-spacing': require('./rules/object-curly-spacing'),
     'object-property-newline': require('./rules/object-property-newline'),
+    'object-shorthand': require('./rules/object-shorthand'),
     'one-component-per-file': require('./rules/one-component-per-file'),
     'operator-linebreak': require('./rules/operator-linebreak'),
     'order-in-components': require('./rules/order-in-components'),
diff --git a/lib/rules/object-shorthand.js b/lib/rules/object-shorthand.js
new file mode 100644
index 00000000..79a3c8a3
--- /dev/null
+++ b/lib/rules/object-shorthand.js
@@ -0,0 +1,12 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const { wrapCoreRule } = require('../utils')
+
+// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+module.exports = wrapCoreRule('object-shorthand', {
+  skipDynamicArguments: true
+})
diff --git a/lib/utils/property-references.js b/lib/utils/property-references.js
index 37709ee0..5170240d 100644
--- a/lib/utils/property-references.js
+++ b/lib/utils/property-references.js
@@ -96,6 +96,12 @@ function definePropertyReferenceExtractor(context) {
   /** @type {{ toRefNodes: Set<ESNode>, toRefsNodes: Set<ESNode>} | null} */
   let toRefSet = null
 
+  let isFunctionalTemplate = false
+  const templateBody = context.getSourceCode().ast.templateBody
+  if (templateBody) {
+    isFunctionalTemplate = utils.hasAttribute(templateBody, 'functional')
+  }
+
   function getToRefSet() {
     if (toRefSet) {
       return toRefSet
@@ -578,6 +584,7 @@ function definePropertyReferenceExtractor(context) {
 
       ignoreRef = (name) => globalScope.set.has(name)
     }
+    /** @type {IPropertyReferences[]} */
     const references = []
     for (const id of node.references
       .filter((ref) => ref.variable == null)
@@ -585,9 +592,15 @@ function definePropertyReferenceExtractor(context) {
       if (ignoreRef(id.name)) {
         continue
       }
-      references.push(
-        extractFromName(id.name, id, () => extractFromExpression(id, true))
-      )
+      if (!isFunctionalTemplate) {
+        references.push(
+          extractFromName(id.name, id, () => extractFromExpression(id, true))
+        )
+      } else {
+        if (id.name === 'props') {
+          references.push(extractFromExpression(id, true))
+        }
+      }
     }
     return mergePropertyReferences(references)
   }


Test patch:
diff --git a/tests/lib/rules/no-undef-properties.js b/tests/lib/rules/no-undef-properties.js
index f432a8d0..8b8ebbb1 100644
--- a/tests/lib/rules/no-undef-properties.js
+++ b/tests/lib/rules/no-undef-properties.js
@@ -517,6 +517,24 @@ tester.run('no-undef-properties', rule, {
         };
       </script>
       `
+    },
+
+    // Vue2 functional component
+    {
+      filename: 'test.vue',
+      code: `
+      <template functional>
+        <div>{{props.a}} {{props.b}}</div>
+      </template>
+
+      <script>
+      export default {
+        props: {
+          a: String,
+          b: String,
+        },
+      };
+      </script>`
     }
   ],
 
@@ -1086,6 +1104,31 @@ tester.run('no-undef-properties', rule, {
           line: 16
         }
       ]
+    },
+
+    // Vue2 functional component
+    {
+      filename: 'test.vue',
+      code: `
+      <template functional>
+        <div>{{props.a}} {{props.b}} {{props.c}}</div>
+      </template>
+
+      <script>
+      export default {
+        props: {
+          a: String,
+          b: String,
+        },
+      };
+      </script>`,
+      errors: [
+        {
+          message: "'c' is not defined.",
+          line: 3,
+          column: 46
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/no-unused-properties.js b/tests/lib/rules/no-unused-properties.js
index ecd41243..0023443c 100644
--- a/tests/lib/rules/no-unused-properties.js
+++ b/tests/lib/rules/no-unused-properties.js
@@ -1678,6 +1678,24 @@ tester.run('no-unused-properties', rule, {
       <style lang="scss" scoped>
       //
       </style>`
+    },
+
+    // Vue2 functional component
+    {
+      filename: 'test.vue',
+      code: `
+      <template functional>
+        <div>{{props.a}} {{props.b}}</div>
+      </template>
+
+      <script>
+      export default {
+        props: {
+          a: String,
+          b: String,
+        },
+      };
+      </script>`
     }
   ],
 
@@ -2756,6 +2774,34 @@ tester.run('no-unused-properties', rule, {
         "'foo.bar.b' of data found, but never used.",
         "'foo.baz' of data found, but never used."
       ]
+    },
+
+    // Vue2 functional component
+    {
+      filename: 'test.vue',
+      code: `
+      <template functional>
+        <div>{{a}} {{b}} {{props.c}}</div>
+      </template>
+
+      <script>
+      export default {
+        props: {
+          a: String,
+          b: String,
+        },
+      };
+      </script>`,
+      errors: [
+        {
+          message: "'a' of property found, but never used.",
+          line: 9
+        },
+        {
+          message: "'b' of property found, but never used.",
+          line: 10
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/object-shorthand.js b/tests/lib/rules/object-shorthand.js
new file mode 100644
index 00000000..62a9a9ef
--- /dev/null
+++ b/tests/lib/rules/object-shorthand.js
@@ -0,0 +1,81 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/object-shorthand')
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: {
+    ecmaVersion: 2020,
+    sourceType: 'module'
+  }
+})
+
+tester.run('object-shorthand', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :style="{height}"></div>
+      </template>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :style="{height: height}"></div>
+      </template>
+      `,
+      options: ['never']
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :style="{height: height}"></div>
+      </template>
+      `,
+      output: `
+      <template>
+        <div :style="{height}"></div>
+      </template>
+      `,
+      errors: [
+        {
+          message: 'Expected property shorthand.',
+          line: 3,
+          column: 23
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :style="{height}"></div>
+      </template>
+      `,
+      output: `
+      <template>
+        <div :style="{height: height}"></div>
+      </template>
+      `,
+      options: ['never'],
+      errors: [
+        {
+          message: 'Expected longform property syntax.',
+          line: 3,
+          column: 23
+        }
+      ]
+    }
+  ]
+})