Extract validator
This commit is contained in:
parent
db0c344965
commit
99e21a1f74
|
@ -1,9 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
/* global hbs */
|
|
||||||
import { Label } from "@budibase/bbui"
|
import { Label } from "@budibase/bbui"
|
||||||
import { onMount, createEventDispatcher, onDestroy } from "svelte"
|
import { onMount, createEventDispatcher, onDestroy } from "svelte"
|
||||||
import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates"
|
import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates"
|
||||||
import Handlebars from "handlebars"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
autocompletion,
|
autocompletion,
|
||||||
|
@ -43,7 +41,6 @@
|
||||||
indentLess,
|
indentLess,
|
||||||
} from "@codemirror/commands"
|
} from "@codemirror/commands"
|
||||||
import { setDiagnostics } from "@codemirror/lint"
|
import { setDiagnostics } from "@codemirror/lint"
|
||||||
import type { Diagnostic } from "@codemirror/lint"
|
|
||||||
import { Compartment, EditorState } from "@codemirror/state"
|
import { Compartment, EditorState } from "@codemirror/state"
|
||||||
import type { Extension } from "@codemirror/state"
|
import type { Extension } from "@codemirror/state"
|
||||||
import { javascript } from "@codemirror/lang-javascript"
|
import { javascript } from "@codemirror/lang-javascript"
|
||||||
|
@ -51,6 +48,7 @@
|
||||||
import { themeStore } from "@/stores/portal"
|
import { themeStore } from "@/stores/portal"
|
||||||
import type { EditorMode } from "@budibase/types"
|
import type { EditorMode } from "@budibase/types"
|
||||||
import type { BindingCompletion, CodeValidator } from "@/types"
|
import type { BindingCompletion, CodeValidator } from "@/types"
|
||||||
|
import { validateHbsTemplate } from "./validator/hbs"
|
||||||
|
|
||||||
export let label: string | undefined = undefined
|
export let label: string | undefined = undefined
|
||||||
export let completions: BindingCompletion[] = []
|
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
|
// None of this is reactive, but it never has been, so we just assume most
|
||||||
// config flags aren't changed at runtime
|
// config flags aren't changed at runtime
|
||||||
// TODO: work out type for base
|
// TODO: work out type for base
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue