Extract validator

This commit is contained in:
Adria Navarro 2025-02-17 09:56:12 +01:00
parent db0c344965
commit 99e21a1f74
2 changed files with 111 additions and 106 deletions

View File

@ -1,9 +1,7 @@
<script lang="ts">
/* global hbs */
import { Label } from "@budibase/bbui"
import { onMount, createEventDispatcher, onDestroy } from "svelte"
import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates"
import Handlebars from "handlebars"
import {
autocompletion,
@ -43,7 +41,6 @@
indentLess,
} from "@codemirror/commands"
import { setDiagnostics } from "@codemirror/lint"
import type { Diagnostic } from "@codemirror/lint"
import { Compartment, EditorState } from "@codemirror/state"
import type { Extension } from "@codemirror/state"
import { javascript } from "@codemirror/lang-javascript"
@ -51,6 +48,7 @@
import { themeStore } from "@/stores/portal"
import type { EditorMode } from "@budibase/types"
import type { BindingCompletion, CodeValidator } from "@/types"
import { validateHbsTemplate } from "./validator/hbs"
export let label: string | undefined = undefined
export let completions: BindingCompletion[] = []
@ -250,109 +248,6 @@
]
}
function isMustacheStatement(
node: hbs.AST.Statement
): node is hbs.AST.MustacheStatement {
return node.type === "MustacheStatement"
}
function isBlockStatement(
node: hbs.AST.Statement
): node is hbs.AST.BlockStatement {
return node.type === "BlockStatement"
}
async function validateHbsTemplate(
editor: EditorView,
template: string,
validations: CodeValidator
): Promise<Diagnostic[]> {
const diagnostics: Diagnostic[] = []
try {
const ast = Handlebars.parse(template)
function traverseNodes(
nodes: hbs.AST.Statement[],
options?: {
ignoreMissing?: boolean
}
) {
const ignoreMissing = options?.ignoreMissing || false
nodes.forEach(node => {
if (
isMustacheStatement(node) &&
node.path.type === "PathExpression"
) {
const helperName = (node.path as hbs.AST.PathExpression).original
const from =
editor.state.doc.line(node.loc.start.line).from +
node.loc.start.column
const to =
editor.state.doc.line(node.loc.end.line).from +
node.loc.end.column
if (!(helperName in validations)) {
if (!ignoreMissing) {
diagnostics.push({
from,
to,
severity: "warning",
message: `"${helperName}" handler does not exist.`,
})
}
return
}
const { arguments: expectedArguments = [], requiresBlock } =
validations[helperName]
if (requiresBlock && !isBlockStatement(node)) {
diagnostics.push({
from,
to,
severity: "error",
message: `Helper "${helperName}" requires a body:\n{{#${helperName} ...}} [body] {{/${helperName}}}`,
})
return
}
const providedParams = node.params
if (providedParams.length !== expectedArguments.length) {
diagnostics.push({
from,
to,
severity: "error",
message: `Helper "${helperName}" expects ${
expectedArguments.length
} parameters (${expectedArguments.join(", ")}), but got ${
providedParams.length
}.`,
})
}
}
if (isBlockStatement(node)) {
traverseNodes(node.program.body, { ignoreMissing: true })
}
})
}
traverseNodes(ast.body)
} catch (e: any) {
diagnostics.push({
from: 0,
to: template.length,
severity: "error",
message: `Syntax error: ${e.message}`,
})
}
return diagnostics
}
// None of this is reactive, but it never has been, so we just assume most
// config flags aren't changed at runtime
// TODO: work out type for base

View File

@ -0,0 +1,110 @@
/* global hbs */
import Handlebars from "handlebars"
import { EditorView } from "@codemirror/view"
import type { Diagnostic } from "@codemirror/lint"
import { CodeValidator } from "@/types"
function isMustacheStatement(
node: hbs.AST.Statement
): node is hbs.AST.MustacheStatement {
return node.type === "MustacheStatement"
}
function isBlockStatement(
node: hbs.AST.Statement
): node is hbs.AST.BlockStatement {
return node.type === "BlockStatement"
}
function isPathExpression(
node: hbs.AST.Statement
): node is hbs.AST.PathExpression {
return node.type === "PathExpression"
}
export async function validateHbsTemplate(
editor: EditorView,
template: string,
validations: CodeValidator
): Promise<Diagnostic[]> {
const diagnostics: Diagnostic[] = []
try {
const ast = Handlebars.parse(template, {})
function traverseNodes(
nodes: hbs.AST.Statement[],
options?: {
ignoreMissing?: boolean
}
) {
const ignoreMissing = options?.ignoreMissing || false
nodes.forEach(node => {
if (isMustacheStatement(node) && isPathExpression(node.path)) {
const helperName = node.path.original
const from =
editor.state.doc.line(node.loc.start.line).from +
node.loc.start.column
const to =
editor.state.doc.line(node.loc.end.line).from + node.loc.end.column
if (!(helperName in validations)) {
if (!ignoreMissing) {
diagnostics.push({
from,
to,
severity: "warning",
message: `"${helperName}" handler does not exist.`,
})
}
return
}
const { arguments: expectedArguments = [], requiresBlock } =
validations[helperName]
if (requiresBlock && !isBlockStatement(node)) {
diagnostics.push({
from,
to,
severity: "error",
message: `Helper "${helperName}" requires a body:\n{{#${helperName} ...}} [body] {{/${helperName}}}`,
})
return
}
const providedParams = node.params
if (providedParams.length !== expectedArguments.length) {
diagnostics.push({
from,
to,
severity: "error",
message: `Helper "${helperName}" expects ${
expectedArguments.length
} parameters (${expectedArguments.join(", ")}), but got ${
providedParams.length
}.`,
})
}
}
if (isBlockStatement(node)) {
traverseNodes(node.program.body, { ignoreMissing: true })
}
})
}
traverseNodes(ast.body)
} catch (e: any) {
diagnostics.push({
from: 0,
to: template.length,
severity: "error",
message: `Syntax error: ${e.message}`,
})
}
return diagnostics
}