eslint-plugin-vue
Version:
Official ESLint plugin for Vue.js
193 lines (190 loc) • 6.4 kB
JavaScript
;
const require_runtime = require('../_virtual/_rolldown/runtime.js');
const require_index = require('../utils/index.js');
//#region lib/rules/padding-line-between-tags.js
/**
* @author dev1437
* See LICENSE file in root directory for full license.
*/
var require_padding_line_between_tags = /* @__PURE__ */ require_runtime.__commonJSMin(((exports, module) => {
const utils = require_index.default;
/**
* 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,
fix(fixer) {
return fixer.insertTextAfter(tag, "\n");
}
});
else if (lineDifference === 0) context.report({
messageId: "always",
loc: sibling.loc,
fix(fixer) {
const lastSpaces = /^\s*/.exec(context.sourceCode.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.sourceCode.lines[i]);
if (!hasOnlyTextBetween) context.report({
messageId: "never",
loc: sibling.loc,
fix(fixer) {
const start = endTag.range[1];
const end = sibling.range[0];
const textBetween = splitLines(context.sourceCode.text.slice(start, end));
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}`);
}
});
}
}
/**
* Check if a tag is single-line (opening and closing tags on the same line)
* @param {VElement} element
* @returns {boolean}
*/
function isSingleLine(element) {
const endTag = element.endTag || element.startTag;
return element.startTag.loc.start.line === endTag.loc.end.line;
}
/**
* Match a selector string against an element.
* Supports pseudo-classes `:single-line` and `:multi-line`.
* @param {string} selector
* @param {VElement} element
* @returns {boolean}
*/
function matchesTag(selector, element) {
const colonIndex = selector.lastIndexOf(":");
if (colonIndex > 0) {
const pseudo = selector.slice(colonIndex);
const tagPart = selector.slice(0, colonIndex);
const tagMatches = tagPart === "*" || element.name === tagPart;
if (pseudo === ":single-line") return tagMatches && isSingleLine(element);
if (pseudo === ":multi-line") return tagMatches && !isSingleLine(element);
}
return selector === "*" || element.name === selector;
}
/**
* Check if the configuration matches the given elements
* @param {{blankLine: string, prev: string, next: string}} configure
* @param {VElement} prevElement
* @param {VElement} nextElement
* @returns {boolean}
*/
function matchesConfiguration(configure, prevElement, nextElement) {
return matchesTag(configure.prev, prevElement) && matchesTag(configure.next, nextElement);
}
/**
* @param {RuleContext} context
*/
function checkNewline(context) {
const reverseConfigureList = [...context.options[0] || [{
blankLine: "always",
prev: "*",
next: "*"
}]].reverse();
/**
* It has the style of the first `blankLine="consistent"`.
* @type {Map<VElement, "always" | "never">}
*/
const firstConsistentBlankLines = /* @__PURE__ */ 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 = lowerSiblings[0];
const configure = reverseConfigureList.find((configure) => matchesConfiguration(configure, block, closestSibling));
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: void 0,
url: "https://eslint.vuejs.org/rules/padding-line-between-tags.html"
},
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."
}
},
create(context) {
return utils.defineTemplateBodyVisitor(context, { VElement: checkNewline(context) });
}
};
}));
//#endregion
Object.defineProperty(exports, 'default', {
enumerable: true,
get: function () {
return require_padding_line_between_tags();
}
});