/** * @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] /** @type {ASTNode | Token} */ let prev = prevBlock /** @type {[ASTNode | Token, ASTNode | Token][]} */ 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) { for (const [prevToken, nextToken] of paddingLines) { const start = prevToken.range[1] const end = nextToken.range[0] const paddingText = context.getSourceCode().text.slice(start, end) const lastSpaces = splitLines(paddingText).pop() yield 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] /** @type {ASTNode | Token} */ let prev = prevBlock /** @type {ASTNode | Token | undefined} */ 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', categories: 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.' } }, /** @param {RuleContext} context */ create(context) { if (!context.parserServices.getDocumentFragment) { return {} } const df = context.parserServices.getDocumentFragment() if (!df) { return {} } const documentFragment = df /** @type {'always' | 'never'} */ const option = context.options[0] || 'always' const paddingType = PaddingTypes[option] /** @type {Token[]} */ let tokens /** * @returns {VElement[]} */ function getTopLevelHTMLElements() { return documentFragment.children.filter(utils.isVElement) } /** * @param {VElement} prev * @param {VElement} next */ function getTokenAndCommentsBetween(prev, next) { // When there is no