UNPKG

eslint-plugin-vue

Version:

Official ESLint plugin for Vue.js

193 lines (190 loc) 6.4 kB
'use strict'; 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(); } });