silverbullet/plugs/template/lint.ts

115 lines
3.1 KiB
TypeScript

import type { LintDiagnostic, LintEvent } from "../../plug-api/types.ts";
import {
findNodeOfType,
renderToText,
traverseTreeAsync,
} from "@silverbulletmd/silverbullet/lib/tree";
import { extractFrontmatter } from "@silverbulletmd/silverbullet/lib/frontmatter";
import {
jsonschema,
template,
YAML,
} from "@silverbulletmd/silverbullet/syscalls";
import { TemplateObjectSchema } from "./types.ts";
export async function lintTemplateFrontmatter(
{ tree }: LintEvent,
): Promise<LintDiagnostic[]> {
const diagnostics: LintDiagnostic[] = [];
const frontmatter = await extractFrontmatter(tree);
// Just looking this up again for the purposes of error reporting
const frontmatterNode = findNodeOfType(tree, "FrontMatterCode")!;
if (!frontmatter.tags?.includes("template")) {
return [];
}
// TODO: Replace with JSON schema validation
const validationResults = await jsonschema.validateObject(
TemplateObjectSchema,
frontmatter,
);
if (validationResults) {
diagnostics.push({
from: frontmatterNode.from!,
to: frontmatterNode.to!,
message: validationResults,
severity: "error",
});
}
return diagnostics;
}
export async function lintTemplateBlocks(
{ tree }: LintEvent,
): Promise<LintDiagnostic[]> {
const frontmatter = await extractFrontmatter(tree);
const diagnostics: LintDiagnostic[] = [];
if (frontmatter.tags?.includes("template")) {
// Parse the whole page as a template to check for errors if this is a template
try {
await template.parseTemplate(renderToText(tree));
} catch (e: any) {
diagnostics.push({
from: tree.from!,
to: tree.to!,
message: e.message,
severity: "error",
});
}
}
await traverseTreeAsync(tree, async (node) => {
if (node.type === "FencedCode") {
const codeInfo = findNodeOfType(node, "CodeInfo")!;
if (!codeInfo) {
return true;
}
const codeLang = codeInfo.children![0].text!;
if (codeLang !== "template") {
return true;
}
const codeText = findNodeOfType(node, "CodeText");
if (!codeText) {
return true;
}
const bodyText = renderToText(codeText);
// See if it parses as YAML, then issue a warning
try {
const parsedYaml = await YAML.parse(bodyText);
if (
typeof parsedYaml === "object" &&
(parsedYaml.template || parsedYaml.page || parsedYaml.raw)
) {
diagnostics.push({
from: codeText.from!,
to: codeText.to!,
message:
"Legacy template syntax detected, please replace ```template with ```include to fix.",
severity: "warning",
});
}
} catch {
// Ignore
}
// Ok, now parse it as a template and report any parse errors
try {
await template.parseTemplate(bodyText);
} catch (e: any) {
diagnostics.push({
from: codeText.from!,
to: codeText.to!,
message: e.message,
severity: "error",
});
}
}
return false;
});
return diagnostics;
}