validate_template
Check NoJS HTML templates for syntax errors, unknown directives, and adherence to best practices to ensure proper functionality.
Instructions
Validate a NoJS HTML template for syntax errors, unknown directives, and best practices
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| html | Yes | The HTML template to validate |
Implementation Reference
- src/tools/index.ts:25-159 (handler)The handler function that validates the NoJS template for syntax, common errors, and best practices.
async ({ html }) => { const errors: string[] = []; const warnings: string[] = []; const knownDirectives = getDirectiveNames(); // Extract all attributes from HTML const attrRegex = /\s([a-z][a-z0-9\-:]*?)(?:=|[\s>])/gi; const attrs = new Set<string>(); let match: RegExpExecArray | null; while ((match = attrRegex.exec(html)) !== null) { attrs.add(match[1].toLowerCase()); } // Check for potential NoJS directive typos const possibleTypos: Record<string, string> = { bnd: "bind", bing: "bind", binde: "bind", "bind-htm": "bind-html", iff: "if", els: "else", "else-iff": "else-if", shw: "show", hid: "hide", ech: "each", forech: "foreach", modle: "model", stat: "state", stor: "store", rout: "route", "route-vew": "route-view", validat: "validate", animat: "animate", "on-click": "on:click", "on-submit": "on:submit", "on-input": "on:input", }; for (const attr of attrs) { if (possibleTypos[attr]) { errors.push( `Unknown attribute "${attr}" — did you mean "${possibleTypos[attr]}"?` ); } } // Check for each without "in" keyword const eachRegex = /each="([^"]+)"/g; while ((match = eachRegex.exec(html)) !== null) { if (!match[1].includes(" in ")) { errors.push( `Invalid "each" syntax: "${match[1]}" — expected format: "item in items"` ); } } // Check foreach without from if (html.includes("foreach=") && !html.includes("from=")) { errors.push( `"foreach" directive requires a "from" attribute to specify the source array` ); } // Check for deprecated syntax if (html.includes('mode="hash"') || html.includes("mode=\"history\"")) { warnings.push( `Deprecated: router "mode" option. Use "useHash: true" instead of "mode: 'hash'"` ); } // Check model on non-input elements const modelOnDivRegex = /<(div|span|p|h[1-6]|section|article|main|header|footer)\s[^>]*model=/gi; while ((match = modelOnDivRegex.exec(html)) !== null) { warnings.push( `"model" directive on <${match[1]}> — "model" is designed for form inputs (<input>, <select>, <textarea>)` ); } // Check bind-html without sanitization warning if (html.includes("bind-html=")) { warnings.push( `"bind-html" renders raw HTML. Ensure the content is sanitized to prevent XSS.` ); } // Check for on: events without correct syntax const onRegex = /on:([a-z]+)\.([a-z.]+)/gi; const validModifiers = new Set([ "prevent", "stop", "once", "self", "enter", "escape", "tab", "space", "up", "down", "left", "right", "ctrl", "alt", "shift", "meta", ]); while ((match = onRegex.exec(html)) !== null) { const mods = match[2].split("."); for (const mod of mods) { if (!validModifiers.has(mod)) { warnings.push( `Unknown event modifier ".${mod}" on "on:${match[1]}" — valid modifiers: ${[...validModifiers].join(", ")}` ); } } } const valid = errors.length === 0; let summary = valid ? "✅ Template is valid." : `❌ Found ${errors.length} error(s).`; if (warnings.length > 0) { summary += ` ${warnings.length} warning(s).`; } return { content: [ { type: "text" as const, text: JSON.stringify({ valid, errors, warnings, summary }, null, 2), }, ], }; } - src/tools/index.ts:21-24 (registration)Tool registration for validate_template in src/tools/index.ts
server.tool( "validate_template", "Validate a NoJS HTML template for syntax errors, unknown directives, and best practices", { html: z.string().describe("The HTML template to validate") },