63 lines
3.5 KiB
TypeScript
63 lines
3.5 KiB
TypeScript
import { foldLines } from "../views/engine/lexer.ts";
|
|
|
|
export interface FormatOptions { tabWidth?: number; useTabs?: boolean; printWidth?: number }
|
|
export type FormatLanguage = "spug" | "stpug" | "stug" | "stcss";
|
|
|
|
/** Shared by the CLI and Prettier plugin, with no subprocesses or write side effects. */
|
|
export async function formatSource(source: string, language: FormatLanguage, options: FormatOptions = {}): Promise<string> {
|
|
const prettier = await import("prettier");
|
|
const scripts: string[] = [];
|
|
let protectedSource = source.replace(/(^[\t ]*)(javascript!|typescript!|---)\s*\n([\s\S]*?)^\s*(end|---)\s*$/gm, (_match, indentation: string, directive: string, code: string, ending: string) => {
|
|
const index = scripts.length;
|
|
scripts.push(JSON.stringify({ directive, code, ending }));
|
|
return language === "stcss" ? `/* MUDDLE_SCRIPT_${index} */` : `${indentation}MUDDLE_SCRIPT_${index}`;
|
|
});
|
|
const rawBlocks: { marker: string; body: string[]; prefix: string }[] = [];
|
|
if (language !== "stcss") {
|
|
const sourceLines = protectedSource.split("\n");
|
|
const output: string[] = [];
|
|
for (let index = 0; index < sourceLines.length; index++) {
|
|
const line = sourceLines[index]!;
|
|
const match = /^(\s*)(?:[\w.#-]+(?:\([^]*\))?\.|\/\/-?|\$\$\$)\s*$/.exec(line);
|
|
if (!match) { output.push(line); continue; }
|
|
const prefix = match[1]!;
|
|
const body: string[] = [line.slice(prefix.length)];
|
|
while (index + 1 < sourceLines.length) {
|
|
const next = sourceLines[index + 1]!;
|
|
if (next.trim() && (next.match(/^\s*/)?.[0].length ?? 0) <= prefix.length) break;
|
|
body.push(next.startsWith(prefix) ? next.slice(prefix.length) : next);
|
|
index++;
|
|
}
|
|
const marker = `MUDDLE_RAW_${rawBlocks.length}`;
|
|
rawBlocks.push({ marker, body, prefix });
|
|
output.push(prefix + marker);
|
|
}
|
|
protectedSource = output.join("\n");
|
|
}
|
|
const unit = options.useTabs ? "\t" : " ".repeat(options.tabWidth ?? 2);
|
|
let formatted: string;
|
|
if (language === "stcss") {
|
|
formatted = await prettier.format(protectedSource, { parser: "scss", plugins: [], tabWidth: options.tabWidth ?? 2, useTabs: options.useTabs ?? false, printWidth: options.printWidth ?? 80 });
|
|
} else {
|
|
const stack = [0];
|
|
const lines: string[] = [];
|
|
for (const line of foldLines(protectedSource)) {
|
|
while (stack.length > 1 && line.indent < stack[stack.length - 1]!) stack.pop();
|
|
if (line.indent > stack[stack.length - 1]!) stack.push(line.indent);
|
|
const indent = unit.repeat(stack.length - 1);
|
|
lines.push(`${indent}${line.text.trimEnd()}`);
|
|
}
|
|
formatted = `${lines.join("\n")}\n`;
|
|
}
|
|
for (let index = 0; index < scripts.length; index++) {
|
|
const script = JSON.parse(scripts[index]!) as { directive: string; code: string; ending: string };
|
|
const code = await prettier.format(script.code, { parser: "typescript", plugins: [], tabWidth: options.tabWidth ?? 2, useTabs: options.useTabs ?? false });
|
|
const marker = language === "stcss" ? new RegExp(`^[\\t ]*/\\* MUDDLE_SCRIPT_${index} \\*/`, "m") : new RegExp(`^([\\t ]*)MUDDLE_SCRIPT_${index}`, "m");
|
|
formatted = formatted.replace(marker, (match) => {
|
|
const indentation = match.match(/^[\t ]*/)?.[0] ?? "";
|
|
return `${indentation}${script.directive}\n${code.trimEnd().split("\n").map((line) => `${indentation}${line}`).join("\n")}\n${indentation}${script.ending}`;
|
|
});
|
|
}
|
|
for (const block of rawBlocks) formatted = formatted.replace(new RegExp(`^([\\t ]*)${block.marker}$`, "m"), (_match, prefix: string) => block.body.map((line) => prefix + line).join("\n"));
|
|
return formatted;
|
|
}
|