diff --git a/docs/rules/README.md b/docs/rules/README.md index 30c0f93d1..22617d2e6 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -165,6 +165,7 @@ For example: | [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes | | | [vue/no-unsupported-features](./no-unsupported-features.md) | disallow unsupported Vue.js syntax on the specified version | :wrench: | | [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: | +| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: | | [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | | | [vue/require-name-property](./require-name-property.md) | require a name property in Vue components | | | [vue/script-indent](./script-indent.md) | enforce consistent indentation in ` + + +``` + + + + + +```vue + + + + +``` + + + +## :wrench: Options + +```json +{ + "vue/padding-line-between-blocks": ["error", "always" | "never"] +} +``` + +- `"always"` (default) ... Requires one or more blank lines. Note it does not count lines that comments exist as blank lines. +- `"never"` ... Disallows blank lines. + +### `"always"` (default) + + + +```vue + + + + + + +``` + + + + + +```vue + + + + +``` + + + +### `"never"` + + + +```vue + + + + +``` + + + + + +```vue + + + + + + +``` + + + +## :books: Further reading + +- [padding-line-between-statements] +- [lines-between-class-members] + +[padding-line-between-statements]: https://eslint.org/docs/rules/padding-line-between-statements +[lines-between-class-members]: https://eslint.org/docs/rules/lines-between-class-members + + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/padding-line-between-blocks.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/padding-line-between-blocks.js) diff --git a/lib/configs/no-layout-rules.js b/lib/configs/no-layout-rules.js index 3bf2fec4e..e45c080bc 100644 --- a/lib/configs/no-layout-rules.js +++ b/lib/configs/no-layout-rules.js @@ -25,6 +25,7 @@ module.exports = { 'vue/no-multi-spaces': 'off', 'vue/no-spaces-around-equal-signs-in-attribute': 'off', 'vue/object-curly-spacing': 'off', + 'vue/padding-line-between-blocks': 'off', 'vue/script-indent': 'off', 'vue/singleline-html-element-content-newline': 'off', 'vue/space-infix-ops': 'off', diff --git a/lib/index.js b/lib/index.js index d1b9bd9ef..eda1d2b74 100644 --- a/lib/index.js +++ b/lib/index.js @@ -65,6 +65,7 @@ module.exports = { 'no-v-html': require('./rules/no-v-html'), 'object-curly-spacing': require('./rules/object-curly-spacing'), 'order-in-components': require('./rules/order-in-components'), + 'padding-line-between-blocks': require('./rules/padding-line-between-blocks'), 'prop-name-casing': require('./rules/prop-name-casing'), 'require-component-is': require('./rules/require-component-is'), 'require-default-prop': require('./rules/require-default-prop'), diff --git a/lib/rules/component-tags-order.js b/lib/rules/component-tags-order.js index d6400dec0..7129aa36e 100644 --- a/lib/rules/component-tags-order.js +++ b/lib/rules/component-tags-order.js @@ -45,7 +45,7 @@ module.exports = { function getTopLevelHTMLElements () { if (documentFragment) { - return documentFragment.children + return documentFragment.children.filter(e => e.type === 'VElement') } return [] } diff --git a/lib/rules/padding-line-between-blocks.js b/lib/rules/padding-line-between-blocks.js new file mode 100644 index 000000000..6f08a5d7a --- /dev/null +++ b/lib/rules/padding-line-between-blocks.js @@ -0,0 +1,194 @@ +/** + * @fileoverview Require or disallow padding lines between blocks + * @author Yosuke Ota + */ +'use strict' +const utils = require('../utils') + +/** + * 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) +} + +/** + * Check and report blocks for `never` configuration. + * This autofix removes blank lines between the given 2 blocks. + * @param {RuleContext} context The rule context to report. + * @param {VElement} prevBlock The previous block to check. + * @param {VElement} nextBlock The next block to check. + * @param {Token[]} betweenTokens The array of tokens between blocks. + * @returns {void} + * @private + */ +function verifyForNever (context, prevBlock, nextBlock, betweenTokens) { + if (prevBlock.loc.end.line === nextBlock.loc.start.line) { + // same line + return + } + const tokenOrNodes = [...betweenTokens, nextBlock] + let prev = prevBlock + const paddingLines = [] + for (const tokenOrNode of tokenOrNodes) { + const numOfLineBreaks = tokenOrNode.loc.start.line - prev.loc.end.line + if (numOfLineBreaks > 1) { + paddingLines.push([prev, tokenOrNode]) + } + prev = tokenOrNode + } + if (!paddingLines.length) { + return + } + + context.report({ + node: nextBlock, + messageId: 'never', + fix (fixer) { + return paddingLines.map(([prevToken, nextToken]) => { + const start = prevToken.range[1] + const end = nextToken.range[0] + const paddingText = context.getSourceCode().text + .slice(start, end) + const lastSpaces = splitLines(paddingText).pop() + return fixer.replaceTextRange([start, end], '\n' + lastSpaces) + }) + } + }) +} + +/** + * Check and report blocks for `always` configuration. + * This autofix inserts a blank line between the given 2 blocks. + * @param {RuleContext} context The rule context to report. + * @param {VElement} prevBlock The previous block to check. + * @param {VElement} nextBlock The next block to check. + * @param {Token[]} betweenTokens The array of tokens between blocks. + * @returns {void} + * @private + */ +function verifyForAlways (context, prevBlock, nextBlock, betweenTokens) { + const tokenOrNodes = [...betweenTokens, nextBlock] + let prev = prevBlock + let linebreak + for (const tokenOrNode of tokenOrNodes) { + const numOfLineBreaks = tokenOrNode.loc.start.line - prev.loc.end.line + if (numOfLineBreaks > 1) { + // Already padded. + return + } + if (!linebreak && numOfLineBreaks > 0) { + linebreak = prev + } + prev = tokenOrNode + } + + context.report({ + node: nextBlock, + messageId: 'always', + fix (fixer) { + if (linebreak) { + return fixer.insertTextAfter(linebreak, '\n') + } + return fixer.insertTextAfter(prevBlock, '\n\n') + } + }) +} + +/** + * Types of blank lines. + * `never` and `always` are defined. + * Those have `verify` method to check and report statements. + * @private + */ +const PaddingTypes = { + never: { verify: verifyForNever }, + always: { verify: verifyForAlways } +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'layout', + docs: { + description: 'require or disallow padding lines between blocks', + category: undefined, + url: 'https://eslint.vuejs.org/rules/padding-line-between-blocks.html' + }, + fixable: 'whitespace', + schema: [ + { + enum: Object.keys(PaddingTypes) + } + ], + messages: { + never: 'Unexpected blank line before this block.', + always: 'Expected blank line before this block.' + } + }, + create (context) { + const paddingType = PaddingTypes[context.options[0] || 'always'] + const documentFragment = context.parserServices.getDocumentFragment && context.parserServices.getDocumentFragment() + + let tokens + function getTopLevelHTMLElements () { + if (documentFragment) { + return documentFragment.children.filter(e => e.type === 'VElement') + } + return [] + } + + function getTokenAndCommentsBetween (prev, next) { + // When there is no