diff --git a/lib/rules/padding-line-between-tags.js b/lib/rules/padding-line-between-tags.js index 3e0994a12..a6144d9a5 100644 --- a/lib/rules/padding-line-between-tags.js +++ b/lib/rules/padding-line-between-tags.js @@ -24,44 +24,75 @@ function splitLines(text) { * @param {RuleContext} context * @param {VElement} tag * @param {VElement} sibling + * @param {number} lineDifference */ -function insertNewLine(context, tag, sibling) { - context.report({ - messageId: 'always', - loc: sibling.loc, - // @ts-ignore - fix(fixer) { - return fixer.insertTextAfter(tag, '\n') - } - }) +function insertNewLine(context, tag, sibling, lineDifference) { + const endTag = tag.endTag || tag.startTag + + if (lineDifference === 1) { + context.report({ + messageId: 'always', + loc: sibling.loc, + // @ts-ignore + fix(fixer) { + return fixer.insertTextAfter(tag, '\n') + } + }) + } else if (lineDifference === 0) { + context.report({ + messageId: 'always', + loc: sibling.loc, + // @ts-ignore + fix(fixer) { + const lastSpaces = /** @type {RegExpExecArray} */ ( + /^\s*/.exec(context.getSourceCode().lines[endTag.loc.start.line - 1]) + )[0] + + return fixer.insertTextAfter(endTag, `\n\n${lastSpaces}`) + } + }) + } } /** * @param {RuleContext} context * @param {VEndTag | VStartTag} endTag * @param {VElement} sibling + * @param {number} lineDifference */ -function removeExcessLines(context, endTag, sibling) { - context.report({ - messageId: 'never', - loc: sibling.loc, - // @ts-ignore - fix(fixer) { - const start = endTag.range[1] - const end = sibling.range[0] - const paddingText = context.getSourceCode().text.slice(start, end) - const textBetween = splitLines(paddingText) - let newTextBetween = `\n${textBetween.pop()}` - for (let i = textBetween.length - 1; i >= 0; i--) { - if (!/^\s*$/.test(textBetween[i])) { - newTextBetween = `${i === 0 ? '' : '\n'}${ - textBetween[i] - }${newTextBetween}` +function removeExcessLines(context, endTag, sibling, lineDifference) { + if (lineDifference > 1) { + let hasOnlyTextBetween = true + for ( + let i = endTag.loc.start.line; + i < sibling.loc.start.line - 1 && hasOnlyTextBetween; + i++ + ) { + hasOnlyTextBetween = !/^\s*$/.test(context.getSourceCode().lines[i]) + } + if (!hasOnlyTextBetween) { + context.report({ + messageId: 'never', + loc: sibling.loc, + // @ts-ignore + fix(fixer) { + const start = endTag.range[1] + const end = sibling.range[0] + const paddingText = context.getSourceCode().text.slice(start, end) + const textBetween = splitLines(paddingText) + let newTextBetween = `\n${textBetween.pop()}` + for (let i = textBetween.length - 1; i >= 0; i--) { + if (!/^\s*$/.test(textBetween[i])) { + newTextBetween = `${i === 0 ? '' : '\n'}${ + textBetween[i] + }${newTextBetween}` + } + } + return fixer.replaceTextRange([start, end], `${newTextBetween}`) } - } - return fixer.replaceTextRange([start, end], `${newTextBetween}`) + }) } - }) + } } // ------------------------------------------------------------------------------ @@ -72,11 +103,19 @@ function removeExcessLines(context, endTag, sibling) { * @param {RuleContext} context */ function checkNewline(context) { - /** @type {Array<{blankLine: "always" | "never", prev: string, next: string}>} */ + /** @type {Array<{blankLine: "always" | "never" | "consistent", prev: string, next: string}>} */ const configureList = context.options[0] || [ { blankLine: 'always', prev: '*', next: '*' } ] + const reverseConfigureList = [...configureList].reverse() + + /** + * It has the style of the first `blankLine="consistent"`. + * @type {Map} + */ + const firstConsistentBlankLines = new Map() + /** * @param {VElement} block */ @@ -99,53 +138,36 @@ function checkNewline(context) { const closestSibling = /** @type {VElement} */ (lowerSiblings[0]) - for (let i = configureList.length - 1; i >= 0; --i) { - const configure = configureList[i] - const matched = + const configure = reverseConfigureList.find( + (configure) => (configure.prev === '*' || block.name === configure.prev) && (configure.next === '*' || closestSibling.name === configure.next) + ) - if (matched) { - const lineDifference = - closestSibling.loc.start.line - endTag.loc.end.line - if (configure.blankLine === 'always') { - if (lineDifference === 1) { - insertNewLine(context, block, closestSibling) - } else if (lineDifference === 0) { - context.report({ - messageId: 'always', - loc: closestSibling.loc, - // @ts-ignore - fix(fixer) { - const lastSpaces = /** @type {RegExpExecArray} */ ( - /^\s*/.exec( - context.getSourceCode().lines[endTag.loc.start.line - 1] - ) - )[0] - - return fixer.insertTextAfter(endTag, `\n\n${lastSpaces}`) - } - }) - } - } else { - if (lineDifference > 1) { - let hasOnlyTextBetween = true - for ( - let i = endTag.loc.start.line; - i < closestSibling.loc.start.line - 1 && hasOnlyTextBetween; - i++ - ) { - hasOnlyTextBetween = !/^\s*$/.test( - context.getSourceCode().lines[i] - ) - } - if (!hasOnlyTextBetween) { - removeExcessLines(context, endTag, closestSibling) - } - } - } - break + if (!configure) { + return + } + const lineDifference = closestSibling.loc.start.line - endTag.loc.end.line + + let blankLine = configure.blankLine + if (blankLine === 'consistent') { + const firstConsistentBlankLine = firstConsistentBlankLines.get( + block.parent + ) + if (firstConsistentBlankLine == null) { + firstConsistentBlankLines.set( + block.parent, + lineDifference > 1 ? 'always' : 'never' + ) + return } + blankLine = firstConsistentBlankLine + } + + if (blankLine === 'always') { + insertNewLine(context, block, closestSibling, lineDifference) + } else { + removeExcessLines(context, endTag, closestSibling, lineDifference) } } } @@ -166,7 +188,7 @@ module.exports = { items: { type: 'object', properties: { - blankLine: { enum: ['always', 'never'] }, + blankLine: { enum: ['always', 'never', 'consistent'] }, prev: { type: 'string' }, next: { type: 'string' } }, diff --git a/tests/lib/rules/padding-line-between-tags.js b/tests/lib/rules/padding-line-between-tags.js index 18d6743a0..67aa6b228 100644 --- a/tests/lib/rules/padding-line-between-tags.js +++ b/tests/lib/rules/padding-line-between-tags.js @@ -17,6 +17,96 @@ const tester = new RuleTester({ tester.run('padding-line-between-tags', rule, { valid: [ + { + filename: 'test.vue', + code: ` + + `, + options: [ + [ + { blankLine: 'consistent', prev: '*', next: '*' }, + { blankLine: 'never', prev: 'br', next: 'br' } + ] + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]] + }, { filename: 'test.vue', code: ` @@ -1027,6 +1117,185 @@ tester.run('padding-line-between-tags', rule, { } ], options: [[{ blankLine: 'never', prev: '*', next: '*' }]] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Expected blank line before this tag.', + line: 7, + column: 11 + } + ], + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Expected blank line before this tag.', + line: 7, + column: 11 + }, + { + message: 'Expected blank line before this tag.', + line: 8, + column: 11 + }, + { + message: 'Expected blank line before this tag.', + line: 9, + column: 11 + } + ], + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Unexpected blank line before this tag.', + line: 7, + column: 11 + }, + { + message: 'Unexpected blank line before this tag.', + line: 10, + column: 11 + }, + { + message: 'Unexpected blank line before this tag.', + line: 13, + column: 11 + } + ], + options: [ + [ + { blankLine: 'consistent', prev: '*', next: '*' }, + { blankLine: 'never', prev: 'br', next: 'br' } + ] + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Unexpected blank line before this tag.', + line: 7, + column: 11 + } + ], + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]] } ] })