"use strict"; const VOID_ELEMENTS = new Set([ "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr", ]); function splitPropDeclarations(value) { const declarations = []; let start = 0; let index = 0; let quote = null; let escaped = false; let square = 0; let brace = 0; let paren = 0; const isIdentifierStart = (character) => /[A-Za-z_]/.test(character || ""); const isIdentifierPart = (character) => /[A-Za-z0-9_]/.test(character || ""); const beginsDeclaration = (position) => { let cursor = position; while (cursor < value.length && /[ \t]/.test(value[cursor])) cursor += 1; if (!isIdentifierStart(value[cursor])) return false; cursor += 1; while (cursor < value.length && isIdentifierPart(value[cursor])) cursor += 1; while (cursor < value.length && /[ \t]/.test(value[cursor])) cursor += 1; return value[cursor] === "=" || value[cursor] === ":"; }; while (index < value.length) { const character = value[index]; if (quote !== null) { if (escaped) escaped = false; else if (character === "\\") escaped = true; else if (character === quote) quote = null; index += 1; continue; } if (character === '"' || character === "'" || character === "`") { quote = character; index += 1; continue; } if (character === "[") square += 1; else if (character === "]" && square > 0) square -= 1; else if (character === "{") brace += 1; else if (character === "}" && brace > 0) brace -= 1; else if (character === "(") paren += 1; else if (character === ")" && paren > 0) paren -= 1; if ( square === 0 && brace === 0 && paren === 0 && /\s/.test(character) && beginsDeclaration(index) ) { const declaration = value.slice(start, index).trim(); if (declaration) declarations.push(declaration); while (index < value.length && /\s/.test(value[index])) index += 1; start = index; continue; } index += 1; } const declaration = value.slice(start).trim(); if (declaration) declarations.push(declaration); return declarations; } function formatInlinePropsBlock(value, unit, depth) { const match = /^props\s*\{([\s\S]*)\}$/.exec(value.trim()); if (!match) return null; const declarations = splitPropDeclarations(match[1].trim()); if (declarations.length === 0) { return [`${unit.repeat(depth)}props {`, `${unit.repeat(depth)}}`]; } return [ `${unit.repeat(depth)}props {`, ...declarations.map((declaration) => `${unit.repeat(depth + 1)}${declaration}`), `${unit.repeat(depth)}}`, ]; } function findOpeningTagEnd(value) { let quote = null; let escaped = false; for (let index = 0; index < value.length; index += 1) { const character = value[index]; if (quote !== null) { if (escaped) { escaped = false; continue; } if (character === "\\") { escaped = true; continue; } if (character === quote) { quote = null; } continue; } if (character === '"' || character === "'") { quote = character; continue; } if (character === ">") { return index; } } return -1; } function parseAttributes(value) { const attributes = []; const pattern = /[^\s"'=<>`]+(?:\s*=\s*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s"'=<>`]+))?/g; let match; while ((match = pattern.exec(value)) !== null) { attributes.push(match[0].trim()); } return attributes; } function parseOpeningTag(value) { const endIndex = findOpeningTagEnd(value); if (endIndex === -1) { return null; } const openingPart = value.slice(0, endIndex + 1); const remainder = value.slice(endIndex + 1).trim(); const match = /^<([A-Za-z][\w$:.-]*)([\s\S]*?)(\/?)>$/.exec(openingPart); if (!match) { return null; } const tagName = match[1]; const attributes = parseAttributes(match[2].trim()); const selfClosing = match[3] === "/"; const inlineClosing = remainder === ``; const closesInRemainder = remainder.startsWith(``); return { tagName, attributes, selfClosing, inlineClosing, closesInRemainder, remainder, }; } function formatOpeningTag(value, unit, depth, printWidth = 100) { const parsed = parseOpeningTag(value); if (!parsed) { return { lines: [`${unit.repeat(depth)}${value.trim()}`], opensElement: false, }; } const baseIndent = unit.repeat(depth); const attributeIndent = unit.repeat(depth + 1); const normalizedOpening = `<${parsed.tagName}${ parsed.attributes.length ? ` ${parsed.attributes.join(" ")}` : "" }${parsed.selfClosing ? " /" : ""}>`; const normalizedSingleLine = `${normalizedOpening}${parsed.remainder}`; const shouldBreak = parsed.attributes.length > 1 || normalizedSingleLine.length > printWidth || value.includes("\n"); const opensElement = !parsed.selfClosing && !parsed.inlineClosing && !parsed.closesInRemainder && !VOID_ELEMENTS.has(parsed.tagName.toLowerCase()); if (!shouldBreak) { return { lines: [`${baseIndent}${normalizedSingleLine}`], opensElement, }; } const lines = [ `${baseIndent}<${parsed.tagName}`, ...parsed.attributes.map((attribute) => `${attributeIndent}${attribute}`), ]; if (parsed.inlineClosing) { lines.push(`${baseIndent}>`); } else if (parsed.selfClosing) { lines.push(`${baseIndent}/>`); } else { lines.push(`${baseIndent}>`); if (parsed.remainder) { lines.push(`${baseIndent}${parsed.remainder}`); } } return { lines, opensElement, }; } function isMultilineOpeningTagStart(value) { if (!value.startsWith("<")) { return false; } if ( value.startsWith("/.test(value); } function isInlineElement(value) { return /^<([A-Za-z][\w$:.-]*)\b[^>]*>[\s\S]*<\/\1\s*>$/.test(value); } function countLeadingClosingBraces(value) { let index = 0; let count = 0; while (index < value.length) { while (index < value.length && /\s/.test(value[index])) { index += 1; } if (value[index] !== "}") { break; } count += 1; index += 1; } return count; } /** * Count braces outside strings and HTML comments. * * This supports WRN blocks, function bodies, lifecycle hooks, * watcher bodies and multiline JavaScript object literals. */ function countStructuralBraces(value) { let openings = 0; let closings = 0; let quote = null; let escaped = false; let htmlComment = false; for (let index = 0; index < value.length; index += 1) { if (!quote && !htmlComment && value.startsWith("", index)) { htmlComment = false; index += 2; continue; } if (htmlComment) { continue; } const character = value[index]; if (quote !== null) { if (escaped) { escaped = false; continue; } if (character === "\\") { escaped = true; continue; } if (character === quote) { quote = null; } continue; } if (character === '"' || character === "'" || character === "`") { quote = character; continue; } if (character === "{") { openings += 1; } else if (character === "}") { closings += 1; } } return { openings, closings, }; } function collectOpeningTag(inputLines, startIndex) { const collected = [inputLines[startIndex].trim()]; let index = startIndex; while (index + 1 < inputLines.length) { const joined = collected.join(" "); if (findOpeningTagEnd(joined) !== -1) { break; } index += 1; collected.push(inputLines[index].trim()); } return { // Preserve the fact that the opening tag was already multiline so a // second formatter pass cannot collapse it back to one line. value: collected.join("\n"), endIndex: index, }; } function formatWrn(source, options = {}) { const unit = options.insertSpaces === false ? "\t" : " ".repeat(options.tabSize || 4); const printWidth = options.printWidth || 100; const inputLines = source.replace(/\r\n/g, "\n").split("\n"); const output = []; let codeDepth = 0; let htmlDepth = 0; let index = 0; let previousWasBlank = false; while (index < inputLines.length) { const originalLine = inputLines[index]; let value = originalLine.trim(); if (value === "") { if (!previousWasBlank && output.length > 0) { output.push(""); } previousWasBlank = true; index += 1; continue; } previousWasBlank = false; if (/^import\b/.test(value)) { const importLines = [value]; while ( !/(?:\bfrom\s+)?["'][^"']+["']\s*;?$/.test(importLines[importLines.length - 1]) && index + 1 < inputLines.length ) { index += 1; importLines.push(inputLines[index].trim()); } output.push(importLines[0], ...importLines.slice(1).map((line) => `${unit}${line}`)); index += 1; continue; } if (isMultilineOpeningTagStart(value)) { const collected = collectOpeningTag(inputLines, index); value = collected.value; index = collected.endIndex; } const inlineProps = formatInlinePropsBlock(value, unit, codeDepth + htmlDepth); if (inlineProps) { output.push(...inlineProps); index += 1; continue; } const leadingClosingBraces = countLeadingClosingBraces(value); const lineCodeDepth = Math.max(0, codeDepth - leadingClosingBraces); let lineHtmlDepth = htmlDepth; if (isClosingTag(value)) { lineHtmlDepth = Math.max(0, htmlDepth - 1); } const depth = lineCodeDepth + lineHtmlDepth; if ( value.startsWith("<") && !value.startsWith(" 0 && output[output.length - 1] === "") { output.pop(); } return `${output.join("\n")}\n`; } module.exports = { countStructuralBraces, formatOpeningTag, formatWrn, parseAttributes, parseOpeningTag, splitPropDeclarations, };