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'] + } + ] +})