diff --git a/docs/rules/index.md b/docs/rules/index.md
index 2e21edc56..b23f68427 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -253,6 +253,7 @@ For example:
| [vue/no-v-text](./no-v-text.md) | disallow use of v-text | | :hammer: |
| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: | :lipstick: |
| [vue/padding-line-between-tags](./padding-line-between-tags.md) | require or disallow newlines between sibling tags in template | :wrench: | :lipstick: |
+| [vue/padding-lines-in-component-definition](./padding-lines-in-component-definition.md) | require or disallow padding lines in component definition | :wrench: | :lipstick: |
| [vue/prefer-prop-type-boolean-first](./prefer-prop-type-boolean-first.md) | enforce `Boolean` comes first in component prop types | :bulb: | :warning: |
| [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: | :hammer: |
| [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md) | require shorthand form attribute when `v-bind` value is `true` | :bulb: | :hammer: |
diff --git a/docs/rules/padding-lines-in-component-definition.md b/docs/rules/padding-lines-in-component-definition.md
new file mode 100644
index 000000000..ab46a532e
--- /dev/null
+++ b/docs/rules/padding-lines-in-component-definition.md
@@ -0,0 +1,163 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/padding-lines-in-component-definition
+description: require or disallow padding lines in component definition
+---
+# vue/padding-lines-in-component-definition
+
+> require or disallow padding lines in component definition
+
+- :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 in the component definition. Properly blank lines help developers improve code readability and code style flexibility.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/padding-lines-in-component-definition": ["error", {
+ "betweenOptions": "always" | "never",
+
+ "withinOption": {
+ "props": {
+ "betweenItems": "always" | "never" | "ignore",
+ "withinEach": "always" | "never" | "ignore",
+ } | "always" | "never" | "ignore", // shortcut to set both
+
+ "data": {
+ "betweenItems": "always" | "never" | "ignore",
+ "withinEach": "always" | "never" | "ignore",
+ } | "always" | "never" | "ignore" // shortcut to set both
+
+ // ... all options
+ } | "always" | "never" | "ignore",
+
+ "groupSingleLineProperties": true | false
+ }]
+}
+```
+
+- `betweenOptions` ... Setting padding lines between options. default `always`
+- `withinOption` ... Setting padding lines within option
+ - `emits` ... Setting padding between lines between `emits` and `defineEmits`. default `always`
+ - `props` ... Setting padding between lines between `props` and `defineProps`. default `always`
+ - ...
+- `groupSingleLineProperties` ... Setting groupings of multiple consecutive single-line properties (e.g. `name`, `inheritAttrs`), default `true`
+
+### Group single-line properties
+
+
+
+```vue
+
+```
+
+
+
+### With custom options
+
+
+
+```vue
+
+```
+
+
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/padding-lines-in-component-definition.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/padding-lines-in-component-definition.js)
diff --git a/lib/configs/no-layout-rules.js b/lib/configs/no-layout-rules.js
index b1486f157..59880b846 100644
--- a/lib/configs/no-layout-rules.js
+++ b/lib/configs/no-layout-rules.js
@@ -44,6 +44,7 @@ module.exports = {
'vue/operator-linebreak': 'off',
'vue/padding-line-between-blocks': 'off',
'vue/padding-line-between-tags': 'off',
+ 'vue/padding-lines-in-component-definition': '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 f5da9a706..aef60a604 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -163,6 +163,7 @@ module.exports = {
'order-in-components': require('./rules/order-in-components'),
'padding-line-between-blocks': require('./rules/padding-line-between-blocks'),
'padding-line-between-tags': require('./rules/padding-line-between-tags'),
+ 'padding-lines-in-component-definition': require('./rules/padding-lines-in-component-definition'),
'prefer-import-from-vue': require('./rules/prefer-import-from-vue'),
'prefer-prop-type-boolean-first': require('./rules/prefer-prop-type-boolean-first'),
'prefer-separate-static-class': require('./rules/prefer-separate-static-class'),
diff --git a/lib/rules/padding-lines-in-component-definition.js b/lib/rules/padding-lines-in-component-definition.js
new file mode 100644
index 000000000..c15762b34
--- /dev/null
+++ b/lib/rules/padding-lines-in-component-definition.js
@@ -0,0 +1,359 @@
+/**
+ * @author ItMaga
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+/**
+ * @typedef {import('../utils').ComponentProp} ComponentProp
+ * @typedef {import('../utils').GroupName} GroupName
+ */
+
+const utils = require('../utils')
+const { isCommentToken } = require('eslint-utils')
+
+const AvailablePaddingOptions = {
+ Never: 'never',
+ Always: 'always',
+ Ignore: 'ignore'
+}
+const OptionKeys = {
+ BetweenOptions: 'betweenOptions',
+ WithinOption: 'withinOption',
+ BetweenItems: 'betweenItems',
+ WithinEach: 'withinEach',
+ GroupSingleLineProperties: 'groupSingleLineProperties'
+}
+
+/**
+ * @param {Token} node
+ */
+function isComma(node) {
+ return node.type === 'Punctuator' && node.value === ','
+}
+
+/**
+ * @param {string} nodeType
+ */
+function isValidProperties(nodeType) {
+ return ['Property', 'SpreadElement'].includes(nodeType)
+}
+
+/**
+ * Split the source code into multiple lines based on the line delimiters.
+ * @param {string} text Source code as a string.
+ * @returns {string[]} Array of source code lines.
+ */
+function splitLines(text) {
+ return text.split(/\r\n|[\r\n\u2028\u2029]/gu)
+}
+
+/**
+ * @param {any} initialOption
+ * @param {string} optionKey
+ * @private
+ * */
+function parseOption(initialOption, optionKey) {
+ return typeof initialOption === 'string'
+ ? initialOption
+ : initialOption[optionKey]
+}
+
+/**
+ * @param {any} initialOption
+ * @param {string} optionKey
+ * @private
+ * */
+function parseBooleanOption(initialOption, optionKey) {
+ if (typeof initialOption === 'string') {
+ if (initialOption === AvailablePaddingOptions.Always) return true
+ if (initialOption === AvailablePaddingOptions.Never) return false
+ }
+ return initialOption[optionKey]
+}
+
+/**
+ * @param {(Property | SpreadElement)} currentProperty
+ * @param {(Property | SpreadElement)} nextProperty
+ * @param {boolean} option
+ * @returns {boolean}
+ * @private
+ * */
+function needGroupSingleLineProperties(currentProperty, nextProperty, option) {
+ const isSingleCurrentProperty =
+ currentProperty.loc.start.line === currentProperty.loc.end.line
+ const isSingleNextProperty =
+ nextProperty.loc.start.line === nextProperty.loc.end.line
+
+ return isSingleCurrentProperty && isSingleNextProperty && option
+}
+
+module.exports = {
+ meta: {
+ type: 'layout',
+ docs: {
+ description: 'require or disallow padding lines in component definition',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/padding-lines-in-component-definition.html'
+ },
+ fixable: 'whitespace',
+ schema: [
+ {
+ oneOf: [
+ {
+ enum: [
+ AvailablePaddingOptions.Always,
+ AvailablePaddingOptions.Never
+ ]
+ },
+ {
+ type: 'object',
+ additionalProperties: false,
+ properties: {
+ [OptionKeys.BetweenOptions]: {
+ enum: Object.values(AvailablePaddingOptions)
+ },
+ [OptionKeys.WithinOption]: {
+ oneOf: [
+ {
+ enum: Object.values(AvailablePaddingOptions)
+ },
+ {
+ type: 'object',
+ patternProperties: {
+ '^[a-zA-Z]*$': {
+ oneOf: [
+ {
+ enum: Object.values(AvailablePaddingOptions)
+ },
+ {
+ type: 'object',
+ properties: {
+ [OptionKeys.BetweenItems]: {
+ enum: Object.values(AvailablePaddingOptions)
+ },
+ [OptionKeys.WithinEach]: {
+ enum: Object.values(AvailablePaddingOptions)
+ }
+ },
+ additionalProperties: false
+ }
+ ]
+ }
+ },
+ minProperties: 1,
+ additionalProperties: false
+ }
+ ]
+ },
+ [OptionKeys.GroupSingleLineProperties]: {
+ type: 'boolean'
+ }
+ }
+ }
+ ]
+ }
+ ],
+ messages: {
+ never: 'Unexpected blank line before this definition.',
+ always: 'Expected blank line before this definition.',
+ groupSingleLineProperties:
+ 'Unexpected blank line between single line properties.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const options = context.options[0] || AvailablePaddingOptions.Always
+ const sourceCode = context.getSourceCode()
+
+ /**
+ * @param {(Property | SpreadElement)} currentProperty
+ * @param {(Property | SpreadElement | Token)} nextProperty
+ * @param {RuleFixer} fixer
+ * */
+ function replaceLines(currentProperty, nextProperty, fixer) {
+ const commaToken = sourceCode.getTokenAfter(currentProperty, isComma)
+
+ const start = commaToken ? commaToken.range[1] : currentProperty.range[1]
+ const end = nextProperty.range[0]
+
+ const paddingText = sourceCode.text.slice(start, end)
+ const newText = `\n${splitLines(paddingText).pop()}`
+
+ return fixer.replaceTextRange([start, end], newText)
+ }
+
+ /**
+ * @param {(Property | SpreadElement)} currentProperty
+ * @param {(Property | SpreadElement | Token)} nextProperty
+ * @param {RuleFixer} fixer
+ * @param {number} betweenLinesRange
+ * */
+ function insertLines(
+ currentProperty,
+ nextProperty,
+ fixer,
+ betweenLinesRange
+ ) {
+ const commaToken = sourceCode.getTokenAfter(currentProperty, isComma)
+
+ const lineBeforeNextProperty =
+ sourceCode.lines[nextProperty.loc.start.line - 1]
+ const lastSpaces = /** @type {RegExpExecArray} */ (
+ /^\s*/.exec(lineBeforeNextProperty)
+ )[0]
+
+ const newText = betweenLinesRange === 0 ? `\n\n${lastSpaces}` : '\n'
+ return fixer.insertTextAfter(commaToken || currentProperty, newText)
+ }
+
+ /**
+ * @param {(Property | SpreadElement)[]} properties
+ * @param {any} option
+ * @param {any} nextOption
+ * */
+ function verify(properties, option, nextOption) {
+ const groupSingleLineProperties = parseBooleanOption(
+ options,
+ OptionKeys.GroupSingleLineProperties
+ )
+
+ for (const [i, currentProperty] of properties.entries()) {
+ const nextProperty = properties[i + 1]
+
+ if (nextProperty && option !== AvailablePaddingOptions.Ignore) {
+ const tokenBeforeNext = sourceCode.getTokenBefore(nextProperty, {
+ includeComments: true
+ })
+ const isCommentBefore = isCommentToken(tokenBeforeNext)
+ const reportNode = isCommentBefore ? tokenBeforeNext : nextProperty
+
+ const betweenLinesRange =
+ reportNode.loc.start.line - currentProperty.loc.end.line
+
+ if (
+ needGroupSingleLineProperties(
+ currentProperty,
+ nextProperty,
+ groupSingleLineProperties
+ )
+ ) {
+ if (betweenLinesRange > 1) {
+ context.report({
+ node: reportNode,
+ messageId: 'groupSingleLineProperties',
+ loc: reportNode.loc,
+ fix(fixer) {
+ return replaceLines(currentProperty, reportNode, fixer)
+ }
+ })
+ }
+ continue
+ }
+
+ if (
+ betweenLinesRange <= 1 &&
+ option === AvailablePaddingOptions.Always
+ ) {
+ context.report({
+ node: reportNode,
+ messageId: 'always',
+ loc: reportNode.loc,
+ fix(fixer) {
+ return insertLines(
+ currentProperty,
+ reportNode,
+ fixer,
+ betweenLinesRange
+ )
+ }
+ })
+ } else if (
+ betweenLinesRange > 1 &&
+ option === AvailablePaddingOptions.Never
+ ) {
+ context.report({
+ node: reportNode,
+ messageId: 'never',
+ loc: reportNode.loc,
+ fix(fixer) {
+ return replaceLines(currentProperty, reportNode, fixer)
+ }
+ })
+ }
+ }
+
+ if (!nextOption) return
+
+ const name = /** @type {GroupName | null} */ (
+ currentProperty.type === 'Property' &&
+ utils.getStaticPropertyName(currentProperty)
+ )
+ if (!name) continue
+
+ const propertyOption = parseOption(nextOption, name)
+ if (!propertyOption) continue
+
+ const nestedProperties =
+ currentProperty.type === 'Property' &&
+ currentProperty.value.type === 'ObjectExpression' &&
+ currentProperty.value.properties
+ if (!nestedProperties) continue
+
+ verify(
+ nestedProperties,
+ parseOption(propertyOption, OptionKeys.BetweenItems),
+ parseOption(propertyOption, OptionKeys.WithinEach)
+ )
+ }
+ }
+
+ return utils.compositingVisitors(
+ utils.defineVueVisitor(context, {
+ onVueObjectEnter(node) {
+ verify(
+ node.properties,
+ parseOption(options, OptionKeys.BetweenOptions),
+ parseOption(options, OptionKeys.WithinOption)
+ )
+ }
+ }),
+ utils.defineScriptSetupVisitor(context, {
+ onDefinePropsEnter(_, props) {
+ const propNodes = /** @type {(Property | SpreadElement)[]} */ (
+ props
+ .filter((prop) => prop.node && isValidProperties(prop.node.type))
+ .map((prop) => prop.node)
+ )
+
+ const withinOption = parseOption(options, OptionKeys.WithinOption)
+ const propsOption = withinOption && parseOption(withinOption, 'props')
+ if (!propsOption) return
+
+ verify(
+ propNodes,
+ parseOption(propsOption, OptionKeys.BetweenItems),
+ parseOption(propsOption, OptionKeys.WithinEach)
+ )
+ },
+ onDefineEmitsEnter(_, emits) {
+ const emitNodes = /** @type {(Property | SpreadElement)[]} */ (
+ emits
+ .filter((emit) => emit.node && isValidProperties(emit.node.type))
+ .map((emit) => emit.node)
+ )
+
+ const withinOption = parseOption(options, OptionKeys.WithinOption)
+ const emitsOption = withinOption && parseOption(withinOption, 'emits')
+ if (!emitsOption) return
+
+ verify(
+ emitNodes,
+ parseOption(emitsOption, OptionKeys.BetweenItems),
+ parseOption(emitsOption, OptionKeys.WithinEach)
+ )
+ }
+ })
+ )
+ }
+}
diff --git a/tests/lib/rules/padding-lines-in-component-definition.js b/tests/lib/rules/padding-lines-in-component-definition.js
new file mode 100644
index 000000000..a4ed4d3c3
--- /dev/null
+++ b/tests/lib/rules/padding-lines-in-component-definition.js
@@ -0,0 +1,1240 @@
+/**
+ * @author ItMaga
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/padding-lines-in-component-definition')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: 'module'
+ }
+})
+
+tester.run('padding-lines-in-component-definition', rule, {
+ valid: [
+ {
+ filename: 'Never.vue',
+ code: `
+
+ `,
+ options: ['never']
+ },
+ {
+ filename: 'Setup.vue',
+ code: `
+ import { ref, defineComponent } from 'vue';
+
+ `,
+ options: ['never']
+ },
+ {
+ filename: 'BetweenOptionsNever.vue',
+ code: `
+ import { defineComponent } from 'vue';
+
+ `,
+ options: [
+ {
+ betweenOptions: 'never',
+ withinOption: 'never'
+ }
+ ]
+ },
+ {
+ filename: 'GroupSingleLineProperties.vue',
+ code: `
+ import { defineComponent } from 'vue';
+
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: 'never',
+ groupSingleLineProperties: true
+ }
+ ]
+ },
+ {
+ filename: 'WithinOption.vue',
+ code: `
+ import { defineComponent } from 'vue';
+
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: {
+ props: {
+ betweenItems: 'never',
+ withinEach: 'always'
+ },
+ data: {
+ betweenItems: 'always',
+ withinEach: 'never'
+ }
+ }
+ }
+ ]
+ },
+ {
+ filename: 'CustomOptions.vue',
+ code: `
+
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: {
+ props: {
+ betweenItems: 'always',
+ withinEach: 'never'
+ },
+ customOption: {
+ betweenItems: 'always'
+ }
+ }
+ }
+ ]
+ },
+ {
+ filename: 'NewVue.js',
+ code: `
+ new Vue({
+ name: 'NewVue',
+ inheritAttrs: false,
+ props: {
+ foo: {
+ type: String,
+ required: true,
+ },
+ bar: {
+ type: String,
+ required: true,
+ }
+ },
+ customOption: {
+ getString() { return '1' },
+ getNumber: () => 1,
+ },
+ })
+ `,
+ options: ['never']
+ },
+ {
+ filename: 'Mixin.js',
+ code: `
+ Vue.mixin({
+ name: 'Mixin',
+ inheritAttrs: false,
+ props: {
+ foo: {
+ type: String,
+ required: true,
+ },
+ bar: {
+ type: String,
+ required: true,
+ }
+ },
+ customOption: {
+ getString() { return '1' },
+ getNumber: () => 1,
+ },
+ })
+ `,
+ options: [
+ {
+ betweenOptions: 'never',
+ withinOption: 'ignore',
+ groupSingleLineProperties: true
+ }
+ ]
+ },
+ {
+ filename: 'DefineProps.vue',
+ code: `
+ import { defineProps } from 'vue'
+
+ `,
+ options: ['never']
+ },
+ {
+ filename: 'DefineEmits.vue',
+ code: `
+ import { defineEmits } from 'vue'
+
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: {
+ emits: {
+ betweenItems: 'always'
+ }
+ },
+ groupSingleLineProperties: false
+ }
+ ]
+ },
+ {
+ filename: 'Comment.vue',
+ code: `
+
+ `,
+ options: ['always']
+ },
+ {
+ filename: 'Spread.vue',
+ code: `
+
+ `,
+ options: ['never']
+ },
+ {
+ filename: 'SpreadWithComment.vue',
+ code: `
+
+ `,
+ options: ['always']
+ }
+ ],
+ invalid: [
+ {
+ filename: 'Always.vue',
+ code: `
+
+ `,
+ output: `
+
+ `,
+ options: ['always'],
+ errors: [
+ {
+ message: 'Expected blank line before this definition.',
+ line: 5
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 10
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 15
+ }
+ ]
+ },
+ {
+ filename: 'Setup.vue',
+ code: `
+ import { ref, defineComponent } from 'vue';
+
+ `,
+ output: `
+ import { ref, defineComponent } from 'vue';
+
+ `,
+ options: ['never'],
+ errors: [
+ {
+ message: 'Unexpected blank line before this definition.',
+ line: 7
+ }
+ ]
+ },
+ {
+ filename: 'BetweenOptionsAlways.vue',
+ code: `
+ import { defineComponent } from 'vue';
+
+ `,
+ output: `
+ import { defineComponent } from 'vue';
+
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: 'never'
+ }
+ ],
+ errors: [
+ {
+ message: 'Expected blank line before this definition.',
+ line: 6
+ }
+ ]
+ },
+ {
+ filename: 'GroupSingleLineProperties.vue',
+ code: `
+ import { defineComponent } from 'vue';
+
+ `,
+ output: `
+ import { defineComponent } from 'vue';
+
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: 'never',
+ groupSingleLineProperties: true
+ }
+ ],
+ errors: [
+ {
+ message: 'Unexpected blank line between single line properties.',
+ line: 7
+ }
+ ]
+ },
+ {
+ filename: 'WithinOption.vue',
+ code: `
+ import { defineComponent } from 'vue';
+
+ `,
+ output: `
+ import { defineComponent } from 'vue';
+
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: {
+ props: {
+ betweenItems: 'always',
+ withinEach: 'always'
+ },
+ data: {
+ betweenItems: 'always',
+ withinEach: 'never'
+ }
+ }
+ }
+ ],
+ errors: [
+ {
+ message: 'Expected blank line before this definition.',
+ line: 10
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 12
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 14
+ }
+ ]
+ },
+ {
+ filename: 'CustomOptions.vue',
+ code: `
+
+ `,
+ output: `
+
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: {
+ props: {
+ betweenItems: 'always',
+ withinEach: 'never'
+ },
+ customOption: {
+ betweenItems: 'never'
+ }
+ }
+ }
+ ],
+ errors: [
+ {
+ message: 'Unexpected blank line before this definition.',
+ line: 23
+ }
+ ]
+ },
+ {
+ filename: 'NewVue.js',
+ code: `
+ new Vue({
+ name: 'NewVue',
+
+ inheritAttrs: false,
+
+ props: {
+ foo: {
+ type: String,
+ required: true,
+ },
+ bar: {
+ type: String,
+ required: true,
+ }
+ },
+ customOption: {
+ getString() { return '1' },
+ getNumber: () => 1,
+ },
+ })
+ `,
+ output: `
+ new Vue({
+ name: 'NewVue',
+ inheritAttrs: false,
+
+ props: {
+ foo: {
+ type: String,
+ required: true,
+ },
+ bar: {
+ type: String,
+ required: true,
+ }
+ },
+
+ customOption: {
+ getString() { return '1' },
+ getNumber: () => 1,
+ },
+ })
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: 'ignore',
+ groupSingleLineProperties: true
+ }
+ ],
+ errors: [
+ {
+ message: 'Unexpected blank line between single line properties.',
+ line: 5
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 17
+ }
+ ]
+ },
+ {
+ filename: 'Mixin.js',
+ code: `
+ Vue.mixin({
+ name: 'NewVue',
+ inheritAttrs: false,
+ props: {
+ foo: {
+ type: String,
+ required: true,
+ },
+ bar: {
+ type: String,
+ required: true,
+ }
+ },
+ customOption: {
+ getString() { return '1' },
+ getNumber: () => 1,
+ },
+ })
+ `,
+ output: `
+ Vue.mixin({
+ name: 'NewVue',
+
+ inheritAttrs: false,
+
+ props: {
+ foo: {
+ type: String,
+ required: true,
+ },
+ bar: {
+ type: String,
+ required: true,
+ }
+ },
+
+ customOption: {
+ getString() { return '1' },
+ getNumber: () => 1,
+ },
+ })
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: 'ignore',
+ groupSingleLineProperties: false
+ }
+ ],
+ errors: [
+ {
+ message: 'Expected blank line before this definition.',
+ line: 4
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 5
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 15
+ }
+ ]
+ },
+ {
+ filename: 'DefineProps.vue',
+ code: `
+ import { defineProps } from 'vue'
+
+ `,
+ output: `
+ import { defineProps } from 'vue'
+
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: {
+ props: {
+ betweenItems: 'always',
+ withinEach: 'never'
+ }
+ }
+ }
+ ],
+ errors: [
+ {
+ message: 'Unexpected blank line before this definition.',
+ line: 8
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 10
+ }
+ ]
+ },
+ {
+ filename: 'DefineEmits.vue',
+ code: `
+ import { defineEmits } from 'vue'
+
+ `,
+ output: `
+ import { defineEmits } from 'vue'
+
+ `,
+ options: ['never'],
+ errors: [
+ {
+ message: 'Unexpected blank line before this definition.',
+ line: 8
+ }
+ ]
+ },
+ {
+ filename: 'WithinOption.vue',
+ code: `
+ import { defineComponent } from 'vue';
+
+ `,
+ output: `
+ import { defineComponent } from 'vue';
+
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: {
+ props: {
+ betweenItems: 'always',
+ withinEach: 'always'
+ }
+ },
+ groupSingleLineProperties: true
+ }
+ ],
+ errors: [
+ {
+ message: 'Expected blank line before this definition.',
+ line: 5
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 9
+ }
+ ]
+ },
+ {
+ filename: 'Comment.vue',
+ code: `
+
+ `,
+ output: `
+
+ `,
+ options: ['always'],
+ errors: [
+ {
+ message: 'Expected blank line before this definition.',
+ line: 5
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 9
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 13
+ },
+ {
+ message: 'Unexpected blank line between single line properties.',
+ line: 21
+ }
+ ]
+ },
+ {
+ filename: 'Spread.vue',
+ code: `
+
+ `,
+ output: `
+
+ `,
+ options: ['always'],
+ errors: [
+ {
+ message: 'Expected blank line before this definition.',
+ line: 6
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 10
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 11
+ }
+ ]
+ },
+ {
+ filename: 'DefineWithSpreadAndComment.vue',
+ code: `
+ import { defineEmits, defineProps } from 'vue'
+
+ `,
+ output: `
+ import { defineEmits, defineProps } from 'vue'
+
+ `,
+ options: [
+ {
+ betweenOptions: 'always',
+ withinOption: {
+ emits: 'always',
+ props: 'never'
+ },
+ groupSingleLineProperties: false
+ }
+ ],
+ errors: [
+ {
+ message: 'Expected blank line before this definition.',
+ line: 7
+ },
+ {
+ message: 'Expected blank line before this definition.',
+ line: 8
+ },
+ {
+ message: 'Unexpected blank line before this definition.',
+ line: 16
+ }
+ ]
+ }
+ ]
+})