feat: restore gateway HMR and add WRN formatting
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"use strict";
|
||||
|
||||
const VOID_ELEMENTS = new Set([
|
||||
"area",
|
||||
"base",
|
||||
"br",
|
||||
"col",
|
||||
"embed",
|
||||
"hr",
|
||||
"img",
|
||||
"input",
|
||||
"link",
|
||||
"meta",
|
||||
"param",
|
||||
"source",
|
||||
"track",
|
||||
"wbr",
|
||||
]);
|
||||
|
||||
/** Count structural curly braces while ignoring quoted strings and line comments. */
|
||||
function curlyDelta(line, state) {
|
||||
let delta = 0;
|
||||
let quote = null;
|
||||
let escaped = false;
|
||||
|
||||
for (let index = 0; index < line.length; index++) {
|
||||
const char = line[index];
|
||||
const next = line[index + 1];
|
||||
|
||||
if (state.blockComment) {
|
||||
if (char === "*" && next === "/") {
|
||||
state.blockComment = false;
|
||||
index++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (state.template) {
|
||||
if (!escaped && char === "`") state.template = false;
|
||||
escaped = !escaped && char === "\\";
|
||||
if (char !== "\\") escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (quote) {
|
||||
if (!escaped && char === quote) quote = null;
|
||||
escaped = !escaped && char === "\\";
|
||||
if (char !== "\\") escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (char === "/" && next === "*") {
|
||||
state.blockComment = true;
|
||||
index++;
|
||||
} else if (char === "/" && next === "/") {
|
||||
break;
|
||||
} else if (char === "`") {
|
||||
state.template = true;
|
||||
} else if (char === '"' || char === "'") {
|
||||
quote = char;
|
||||
} else if (char === "{") {
|
||||
delta++;
|
||||
} else if (char === "}") {
|
||||
delta--;
|
||||
}
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
/** Return the net nesting introduced by HTML-like tags on this line. */
|
||||
function htmlDelta(line) {
|
||||
let delta = 0;
|
||||
const tags = line.matchAll(/<\s*(\/?)\s*([A-Za-z][\w:-]*)\b[^>]*>/g);
|
||||
for (const match of tags) {
|
||||
const full = match[0];
|
||||
const closing = match[1] === "/";
|
||||
const name = match[2].toLowerCase();
|
||||
if (closing) delta--;
|
||||
else if (!VOID_ELEMENTS.has(name) && !/\/\s*>$/.test(full)) delta++;
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
function leadingClosers(line) {
|
||||
let count = /^\s*}/.test(line) ? 1 : 0;
|
||||
const tags = line.match(/^\s*((?:<\/\s*[A-Za-z][\w:-]*\s*>\s*)+)/);
|
||||
if (tags) count += [...tags[1].matchAll(/<\//g)].length;
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format WRN source conservatively: normalize structural indentation and
|
||||
* trailing whitespace without rewriting expressions, HTML, CSS, or JS.
|
||||
*/
|
||||
function formatWrn(text, options = {}) {
|
||||
const tabSize = Math.max(1, Number(options.tabSize) || 2);
|
||||
const unit = options.insertSpaces === false ? "\t" : " ".repeat(tabSize);
|
||||
const hadFinalNewline = /\r?\n$/.test(text);
|
||||
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
||||
if (hadFinalNewline) lines.pop();
|
||||
|
||||
let depth = 0;
|
||||
const state = { blockComment: false, template: false };
|
||||
const formatted = lines.map((original) => {
|
||||
const line = original.trimEnd();
|
||||
if (!line.trim()) return "";
|
||||
|
||||
// Preserve multiline template-string content because its whitespace can be data.
|
||||
const wasTemplate = state.template;
|
||||
const trimmed = line.trimStart();
|
||||
const indent = Math.max(0, depth - leadingClosers(trimmed));
|
||||
const output = wasTemplate ? line : unit.repeat(indent) + trimmed;
|
||||
depth = Math.max(0, depth + curlyDelta(trimmed, state) + htmlDelta(trimmed));
|
||||
return output;
|
||||
});
|
||||
|
||||
return formatted.join("\n") + (hadFinalNewline ? "\n" : "");
|
||||
}
|
||||
|
||||
module.exports = { formatWrn };
|
||||
Reference in New Issue
Block a user