119 lines
3.6 KiB
TypeScript
119 lines
3.6 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;
|
|
}
|