diff --git a/docs/rules/README.md b/docs/rules/README.md
index 3d4a9b2e3..6114049df 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -352,6 +352,7 @@ For example:
| [vue/no-v-text-v-html-on-component](./no-v-text-v-html-on-component.md) | disallow v-text / v-html on component | |
| [vue/no-v-text](./no-v-text.md) | disallow use of v-text | |
| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
+| [vue/padding-line-between-component-options](./padding-line-between-component-options.md) | require or disallow padding lines between top-level component options | :wrench: |
| [vue/prefer-import-from-vue](./prefer-import-from-vue.md) | enforce import from 'vue' instead of import from '@vue/*' | :wrench: |
| [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: |
| [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md) | require shorthand form attribute when `v-bind` value is `true` | :bulb: |
diff --git a/docs/rules/padding-line-between-blocks.md b/docs/rules/padding-line-between-blocks.md
index 7e1e058e1..3c8be3c4b 100644
--- a/docs/rules/padding-line-between-blocks.md
+++ b/docs/rules/padding-line-between-blocks.md
@@ -139,6 +139,10 @@ export default {}
This rule was introduced in eslint-plugin-vue v6.2.0
+## :couple: Related Rules
+
+- [vue/padding-line-between-component-options](./padding-line-between-component-options.md)
+
## :mag: Implementation
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/padding-line-between-blocks.js)
diff --git a/docs/rules/padding-line-between-component-options.md b/docs/rules/padding-line-between-component-options.md
new file mode 100644
index 000000000..ca28084bf
--- /dev/null
+++ b/docs/rules/padding-line-between-component-options.md
@@ -0,0 +1,196 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/padding-line-between-component-options
+description: require or disallow padding lines between top-level component options
+---
+# vue/padding-line-between-component-options
+
+> require or disallow padding lines between top-level component options
+
+- :exclamation: ***This rule has not been released yet.***
+- :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 requires or disallows blank lines between Vue component options.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/padding-line-between-component-options": ["error", "always" | "never"]
+}
+```
+
+- `"always"` (default) ... add an empty line between options.
+- `"never"` ... remove empty lines between options.
+
+### `"always"` (default)
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+### `"never"`
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/padding-line-between-blocks](./padding-line-between-blocks.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/padding-line-between-component-options.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/padding-line-between-component-options.js)
diff --git a/lib/configs/no-layout-rules.js b/lib/configs/no-layout-rules.js
index 4c88287af..c3060179e 100644
--- a/lib/configs/no-layout-rules.js
+++ b/lib/configs/no-layout-rules.js
@@ -40,6 +40,7 @@ module.exports = {
'vue/object-property-newline': 'off',
'vue/operator-linebreak': 'off',
'vue/padding-line-between-blocks': 'off',
+ 'vue/padding-line-between-component-options': 'off',
'vue/script-indent': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/space-in-parens': 'off',
diff --git a/lib/index.js b/lib/index.js
index 97ff805d3..f374864ba 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -157,6 +157,7 @@ module.exports = {
'operator-linebreak': require('./rules/operator-linebreak'),
'order-in-components': require('./rules/order-in-components'),
'padding-line-between-blocks': require('./rules/padding-line-between-blocks'),
+ 'padding-line-between-component-options': require('./rules/padding-line-between-component-options'),
'prefer-import-from-vue': require('./rules/prefer-import-from-vue'),
'prefer-separate-static-class': require('./rules/prefer-separate-static-class'),
'prefer-template': require('./rules/prefer-template'),
diff --git a/lib/rules/padding-line-between-component-options.js b/lib/rules/padding-line-between-component-options.js
new file mode 100644
index 000000000..ce3203ac4
--- /dev/null
+++ b/lib/rules/padding-line-between-component-options.js
@@ -0,0 +1,136 @@
+/**
+ * @author Barthy Bonhomme (https://github.com/barthy-koeln)
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: 'layout',
+ docs: {
+ description:
+ 'require or disallow padding lines between top-level component options',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/padding-line-between-component-options.html'
+ },
+ fixable: 'whitespace',
+ schema: [{ enum: ['always', 'never'] }],
+ messages: {
+ never: 'Unexpected blank line between Vue component options.',
+ always: 'Expected blank line between Vue component options.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const isVueFile = utils.isVueFile(context.getFilename())
+ if (!isVueFile) {
+ return {}
+ }
+
+ const sourceCode = context.getSourceCode()
+ const shouldPad = (context.options[0] || 'always') === 'always'
+
+ const fixFunctions = {
+ /**
+ * Removes newlines between component options
+ *
+ * @param {RuleFixer} fixer
+ * @param {number} endOfCurrent
+ * @param {number} startOfNext
+ * @return {Fix}
+ */
+ never(fixer, endOfCurrent, startOfNext) {
+ return fixer.replaceTextRange([endOfCurrent, startOfNext], '\n')
+ },
+ /**
+ * Add newlines between component options.
+ *
+ * @param {RuleFixer} fixer
+ * @param {number} endOfCurrent
+ * @return {Fix}
+ */
+ always(fixer, endOfCurrent /*, startOfNext*/) {
+ return fixer.insertTextAfterRange([0, endOfCurrent], '\n')
+ }
+ }
+
+ /**
+ * Report error based on configuration.
+ *
+ * @param {ASTNode} node Where to report errors
+ * @param {boolean} isPadded True if the option is followed by an empty line
+ * @param {number} endOfCurrent End of checked token
+ * @param {number} startOfNext Start of next token
+ */
+ function reportError(node, isPadded, endOfCurrent, startOfNext) {
+ const key = isPadded ? 'never' : 'always'
+ const fixFunction = fixFunctions[key]
+
+ context.report({
+ node,
+ messageId: key,
+ fix: (fixer) => fixFunction(fixer, endOfCurrent, startOfNext)
+ })
+ }
+
+ /**
+ * Compares options and decides what to do.
+ * This takes into account comments before options, but not empty lines between multiple comments.
+ *
+ * @param {ASTNode} current current option to check
+ * @param {ASTNode} next next node to check against
+ */
+ function checkOption(current, next) {
+ const endOfCurrent =
+ sourceCode.getIndexFromLoc({
+ line: current.loc.end.line + 1,
+ column: 0
+ }) - 1 /* start of next line, -1 for previous line */
+
+ const comments = sourceCode.getCommentsBefore(next)
+ const nextNode = comments.length ? comments[0] : next
+
+ const startOfNext = sourceCode.getIndexFromLoc({
+ line: nextNode.loc.start.line,
+ column: 0
+ })
+
+ const isPadded = startOfNext !== endOfCurrent + 1
+ if (shouldPad === isPadded) {
+ return
+ }
+
+ reportError(next, isPadded, endOfCurrent, startOfNext)
+ }
+
+ return {
+ /**
+ * @param {import('vue-eslint-parser/ast').Node} node
+ */
+ ObjectExpression(node) {
+ if (node.parent && node.parent.type !== 'ExportDefaultDeclaration') {
+ return
+ }
+
+ const { properties } = node
+
+ for (let i = 0; i < properties.length - 1; i++) {
+ const property = properties[i]
+ const nextProperty = properties[i + 1]
+
+ checkOption(property, nextProperty)
+ }
+ }
+ }
+ }
+}
diff --git a/tests/lib/rules/padding-line-between-component-options.js b/tests/lib/rules/padding-line-between-component-options.js
new file mode 100644
index 000000000..a87539696
--- /dev/null
+++ b/tests/lib/rules/padding-line-between-component-options.js
@@ -0,0 +1,195 @@
+/**
+ * @author Barthy Bonhomme (https://github.com/barthy-koeln)
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/padding-line-between-component-options')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: 'module'
+ }
+})
+
+tester.run('padding-line-between-component-options', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ output: `
+
+
+
+
+
+ `,
+ errors: [
+ {
+ message: rule.meta.messages.always,
+ line: 12,
+ column: 9
+ },
+ {
+ message: rule.meta.messages.always,
+ line: 15,
+ column: 19
+ },
+ {
+ message: rule.meta.messages.always,
+ line: 16,
+ column: 9
+ },
+ {
+ message: rule.meta.messages.always,
+ line: 19,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test-never.vue',
+ code: `
+
+
+
+ `,
+ output: `
+
+
+
+ `,
+ errors: [
+ {
+ message: rule.meta.messages.never,
+ line: 11,
+ column: 9
+ },
+ {
+ message: rule.meta.messages.never,
+ line: 15,
+ column: 19
+ },
+ {
+ message: rule.meta.messages.never,
+ line: 17,
+ column: 9
+ },
+ {
+ message: rule.meta.messages.never,
+ line: 21,
+ column: 9
+ }
+ ],
+ options: ['never']
+ }
+ ]
+})