eslint-plugin-vue
Version:
Official ESLint plugin for Vue.js
205 lines (186 loc) • 5.59 kB
JavaScript
/**
* @author dev1437
* See LICENSE file in root directory for full license.
*/
const utils = require('../utils')
/**
* Split the source code into multiple lines based on the line delimiters.
* Copied from padding-line-between-blocks
* @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 {RuleContext} context
* @param {VElement} tag
* @param {VElement} sibling
* @param {number} lineDifference
*/
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, 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}`)
}
})
}
}
}
/**
* @param {RuleContext} context
*/
function checkNewline(context) {
/** @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<VElement, "always" | "never">}
*/
const firstConsistentBlankLines = new Map()
/**
* @param {VElement} block
*/
return (block) => {
if (!block.parent.parent) {
return
}
const endTag = block.endTag || block.startTag
const lowerSiblings = block.parent.children
.filter(
(element) =>
element.type === 'VElement' && element.range !== block.range
)
.filter((sibling) => sibling.range[0] - endTag.range[1] >= 0)
if (lowerSiblings.length === 0) {
return
}
const closestSibling = /** @type {VElement} */ (lowerSiblings[0])
const configure = reverseConfigureList.find(
(configure) =>
(configure.prev === '*' || block.name === configure.prev) &&
(configure.next === '*' || closestSibling.name === configure.next)
)
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)
}
}
}
module.exports = {
meta: {
type: 'layout',
docs: {
description:
'require or disallow newlines between sibling tags in template',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/padding-line-between-tags.html'
},
// eslint-disable-next-line eslint-plugin/require-meta-fixable -- fixer is not recognized
fixable: 'whitespace',
schema: [
{
type: 'array',
items: {
type: 'object',
properties: {
blankLine: { enum: ['always', 'never', 'consistent'] },
prev: { type: 'string' },
next: { type: 'string' }
},
additionalProperties: false,
required: ['blankLine', 'prev', 'next']
}
}
],
messages: {
never: 'Unexpected blank line before this tag.',
always: 'Expected blank line before this tag.'
}
},
/** @param {RuleContext} context */
create(context) {
return utils.defineTemplateBodyVisitor(context, {
VElement: checkNewline(context)
})
}
}