Skip to main content

HTML with Mustache/Handlebars template syntax grammar for tree-sitter

Project description

HTML Mustache Logo

HTML Mustache

Full language support for HTML with Mustache/Handlebars templates

Lint LSP Open VSX


Features

  • Syntax Highlighting — Full semantic highlighting for HTML and Mustache, plus embedded JS/TS in <script> and CSS in <style>
  • Document Formatting — Auto-format with EditorConfig and config file support
  • CLI Linter & Formatter — Check and format templates from the command line
  • Document Symbols — Outline view and breadcrumb navigation
  • Folding — Collapse HTML elements and Mustache sections
  • Hover Information — Tag and attribute documentation

Supported Mustache Syntax

Syntax Description
{{name}} Variable interpolation
{{{html}}} Unescaped HTML
{{#items}}...{{/items}} Sections
{{^items}}...{{/items}} Inverted sections
{{! comment }} Comments
{{> partial}} Partials

VS Code Extension

Install from the VS Code Marketplace or search for "HTML Mustache" in the Extensions view.

What you get out of the box:

  • Syntax highlighting (including embedded JS/TS and CSS)
  • Document formatting (format on save, format selection)
  • Error diagnostics (parse errors, mismatched tags)
  • Document outline and breadcrumbs
  • Hover information for HTML tags and attributes
  • Code folding for HTML elements and Mustache sections

Using with .html Files

By default, the extension activates for .mustache, .hbs, and .handlebars files. To use it with .html files, add this to your VS Code settings:

{
  "files.associations": {
    "*.html": "htmlmustache"
  }
}

You can also change the language mode for a single file by clicking the language indicator in the status bar and selecting "HTML Mustache".

CLI

Install globally or run via npx:

npm install -g @reteps/tree-sitter-htmlmustache

htmlmustache check

Check templates for parse errors:

htmlmustache check '**/*.mustache' '**/*.hbs'

If include is configured in .htmlmustache.jsonc, patterns are optional:

htmlmustache check
file.mustache:3:3 error: Mismatched mustache section: {{/wrong}}
  |
1 | {{#items}}
2 |   <li>{{name}}
3 |   {{/wrong}}
  |   ^^^^^^^^^^ Mismatched mustache section: {{/wrong}}

1 error in 1 file (5 files checked)

Detects parse errors, mismatched Mustache sections, mismatched HTML end tags, and missing tokens.

htmlmustache format

Format templates:

htmlmustache format --write '**/*.mustache'

If include is configured in .htmlmustache.jsonc, patterns are optional:

htmlmustache format --write

Check formatting in CI (exits 1 if any files would change):

htmlmustache format --check 'templates/**/*.hbs'

Read from stdin:

echo '<div><p>hi</p></div>' | htmlmustache format --stdin

Options:

Flag Description
--write Modify files in-place (default: print to stdout)
--check Exit 1 if any files would change (for CI)
--stdin Read from stdin, write to stdout
--indent-size N Spaces per indent level (default: 2)
--print-width N Max line width (default: 80)
--mustache-spaces Add spaces inside mustache delimiters

Python API

The grammar is also published to PyPI as tree-sitter-htmlmustache for use with py-tree-sitter.

pip install tree-sitter tree-sitter-htmlmustache
from tree_sitter import Language, Parser
import tree_sitter_htmlmustache

parser = Parser(Language(tree_sitter_htmlmustache.language()))
tree = parser.parse(b"<div>{{#items}}<li>{{name}}</li>{{/items}}</div>")
print(tree.root_node)

JavaScript API

The package ships three independent subpath exports. Pick the one you need; each pulls in only its own deps.

Subpath What you get Deps
./parser createParser({ locateWasm }) → typed JSON AST + walk helper web-tree-sitter
./linter createLinter({ locateWasm })lint(source, config) → Diagnostic[] web-tree-sitter, ajv
./formatter createFormatter({ locateWasm, prettier })format(source, config) → string web-tree-sitter, peer prettier
./wasm Direct URL of tree-sitter-htmlmustache.wasm

Parser — typed JSON AST

import { createParser, walk } from '@reteps/tree-sitter-htmlmustache/parser';

const parser = await createParser({
  locateWasm: '/static/tree-sitter-htmlmustache.wasm',
});

const { rootNode, hasError } = parser.parse('<p>{{name}}</p>');

walk(rootNode, (node, parents) => {
  switch (node.type) {
    case 'html_element':
      // node.children is narrowed to HtmlStartTagNode | HtmlEndTagNode | ...
      console.log('element:', node.text);
      break;
    case 'mustache_interpolation':
      // node.children: MustacheIdentifierNode | MustachePathExpressionNode
      console.log('interpolation:', node.text);
      break;
  }
});

The SyntaxNode discriminated union is generated from the grammar's node-types.json — switching on node.type narrows node.children to the exact set of allowed children. Build your own validator on top of walk with full autocomplete and exhaustiveness checking.

Linter

import {
  createLinter,
  DEFAULT_CONFIG,
} from '@reteps/tree-sitter-htmlmustache/linter';

const linter = await createLinter({
  locateWasm: '/static/tree-sitter-htmlmustache.wasm',
});
const diagnostics = linter.lint('<a href={{url}}></a>', DEFAULT_CONFIG);
// → [{ line, column, severity: 'error', ruleName: 'unquotedMustacheAttributes', message, ... }]

Custom rules and per-tag JSON-Schema validation are passed through the config argument — see the Configuration section for the shape.

Formatter

import { createFormatter } from '@reteps/tree-sitter-htmlmustache/formatter';
import prettier from 'prettier';

const formatter = await createFormatter({
  locateWasm: '/static/tree-sitter-htmlmustache.wasm',
  prettier, // optional — for embedded <script> / <style>
});

const formatted = await formatter.format('<div><p>hi</p></div>', {
  indentSize: 2,
});

locateWasm

Both a string (URL of the grammar wasm) and a callback are supported:

locateWasm: (name) => {
  if (name === 'tree-sitter-htmlmustache.wasm')
    return '/static/htmlmustache.wasm';
  return `/static/${name}`; // web-tree-sitter resolves tree-sitter.wasm itself
};

In Node, pass absolute file paths. In the browser, pass URLs.

Format Ignore

Skip formatting for specific regions using ignore directives. Both HTML and Mustache comment forms are supported.

Ignore Next Node

Place a comment immediately before the element to preserve its original formatting:

<!-- htmlmustache-ignore -->
<div class="a" id="b">manually formatted</div>
{{! htmlmustache-ignore }}
<table>
  <tr>
    <td>compact</td>
    <td>table</td>
  </tr>
</table>

Only the immediately following sibling node is ignored. Subsequent nodes are formatted normally.

Ignore Region

Wrap a region in start/end comments to preserve everything between them:

<!-- htmlmustache-ignore-start -->
<div class="a">content</div>
<p>kept as-is</p>
<!-- htmlmustache-ignore-end -->
{{! htmlmustache-ignore-start }} {{#items}}
<li>{{name}}</li>
{{/items}} {{! htmlmustache-ignore-end }}

If ignore-start has no matching ignore-end, all remaining siblings in the current scope are preserved as raw text.

Configuration

.htmlmustache.jsonc

Create a .htmlmustache.jsonc file in your project root to configure formatting options. Both the VS Code extension and CLI will pick it up automatically (the file is found by walking up from the formatted file).

{
  "$schema": "https://raw.githubusercontent.com/reteps/tree-sitter-htmlmustache/main/schemas/htmlmustache-config.schema.json",

  // File patterns for CLI commands (used when no patterns are passed as arguments)
  "include": ["**/*.mustache", "**/*.hbs"],

  // Patterns to always exclude (node_modules and .git are excluded by default)
  "exclude": ["**/vendor/**"],

  // Max line width before wrapping (default: 80)
  "printWidth": 100,

  // Spaces per indent level (default: 2)
  "indentSize": 4,

  // Add spaces inside mustache delimiters: {{ foo }} vs {{foo}} (default: false)
  "mustacheSpaces": true,

  // Treat custom tags as raw code blocks (like <script>/<style>), or bind
  // a JSON Schema for attribute/child validation — see Tag Schemas below.
  "customTags": [
    {
      "name": "x-code",
      "languageDefault": "javascript",
    },
  ],
}

The schema URL above tracks main. For release-pinned validation, replace main with a tag such as v1.4.0.

Lint Rules

The following checks are always enabled and report as errors:

  • Syntax errors — invalid or unparseable template syntax
  • Missing tokens — e.g. a missing closing >
  • Mismatched mustache sections{{/wrong}} closing a different section than was opened
  • Mismatched HTML tags — closing tags that don't match their opening tag, including across mustache branches
  • Unclosed HTML tags — non-void elements that are never closed

Additionally, the following rules are configurable. Set their severities ("error", "warning", or "off") in the rules object:

{
  "rules": {
    "consecutiveDuplicateSections": "off",
    "preferMustacheComments": "warning",
  },
}
Rule Default Description
nestedDuplicateSections error Flags {{#name}} nested inside another {{#name}} with the same name
unquotedMustacheAttributes error Requires quotes around mustache expressions used as attribute values
consecutiveDuplicateSections warning Warns when adjacent same-name sections can be merged
selfClosingNonVoidTags error Disallows self-closing syntax on non-void HTML elements (e.g. <div/>)
duplicateAttributes error Detects duplicate HTML attributes on the same element
unescapedEntities warning Flags unescaped &, <, and > characters in text content
preferMustacheComments off Suggests replacing HTML comments with mustache comments
unrecognizedHtmlTags error Flags HTML tags that are not standard HTML elements or valid custom elements
elementContentTooLong off Flags configured elements whose inner content exceeds a byte-length threshold
customTagSchema error Validates configured custom tags against their JSON Schema contracts
customTagDeprecations warning Surfaces deprecated: true annotations in custom tag schemas
pluginModule error Reports plugin module load and export-shape failures

Tag Schemas

Custom tags can declare a JSON Schema draft-06 contract. The schema may be inline or a path resolved relative to .htmlmustache.jsonc:

{
  "customTags": [
    {
      "name": "pl-multiple-choice",
      "schema": "elements/pl-multiple-choice/pl-multiple-choice.schema.json",
    },
  ],
  "rules": {
    "customTagSchema": "error",
  },
}

Schemas validate the tag's flat attribute object. The tag name is implicit from customTags[].name:

{
  "answers-name": "q1",
  "weight": "2",
  "disabled": true,
}

Attribute values are coerced by JSON Schema ("2" can satisfy an integer, boolean attributes become true). Attribute values containing mustache are treated as unknown runtime values, so value-dependent schema errors are waived while presence and unknown-attribute checks still run.

HTML boolean attributes are allowed on custom tags by default, so <pl-answer correct> validates as { "correct": true }. To require explicit values for custom tag attributes by default, set customTagDefaults.allowBooleanAttributes to false; individual custom tag entries can opt back in:

{
  "customTagDefaults": { "allowBooleanAttributes": false },
  "customTags": [{ "name": "pl-answer", "allowBooleanAttributes": true }],
}

When boolean attributes are disabled for a custom tag, <pl-answer correct> reports Attribute "correct" on <pl-answer> must have a value. Ordinary HTML boolean attributes such as <input disabled> are not affected by this custom tag setting.

Custom tags can also declare parent-owned child schemas. Child schemas validate a direct child tag's flat attribute object only in the context of that parent:

{
  "customTags": [
    {
      "name": "pl-multiple-choice",
      "schema": "elements/pl-multiple-choice/pl-multiple-choice.schema.json",
      "children": [
        {
          "name": "pl-answer",
          "schema": "elements/pl-multiple-choice/pl-answer.schema.json",
        },
      ],
    },
    { "name": "pl-answer" },
  ],
}

By default, the example above allows only direct <pl-answer> HTML elements under <pl-multiple-choice>. Set "allowAdditionalChildren": true on the parent tag to keep unlisted direct child elements allowed while still schema-validating listed child tags when they appear. Mustache sections are transparent for this check: <pl-answer> inside {{#cond}}...{{/cond}} still counts as a direct child of the surrounding parent element.

Parent-owned child schemas do not create a global schema for the child tag. A bare <pl-answer> outside <pl-multiple-choice> is recognized by { "name": "pl-answer" }, but it does not use the multiple-choice-specific child schema.

If a child tag is declared only inside children and is not listed as a top-level customTags entry, it is recognized only as a child-owned tag and may appear only as a direct child of the parent tags that declared it. Listing the same tag at the top level means it can also appear in any other context.

children can be nested recursively. Each level still validates only direct children, so this keeps <pl-answer> scoped to <pl-multiple-choice> while giving <pl-answer> its own allowed direct child tags:

{
  "customTags": [
    {
      "name": "pl-multiple-choice",
      "children": [
        {
          "name": "pl-answer",
          "schema": "elements/pl-multiple-choice/pl-answer.schema.json",
          "children": [{ "name": "pl-answer-feedback" }],
        },
      ],
    },
  ],
}

Same-name child entries are self-references. This child-only recursive tag allows <pl-node> to nest under itself at any depth while still rejecting other direct children:

{
  "customTags": [
    {
      "name": "pl-tree",
      "children": [
        {
          "name": "pl-node",
          "children": [{ "name": "pl-node" }],
        },
      ],
    },
  ],
}

Example schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "answers-name": { "type": "string" },
    "weight": { "type": "number", "minimum": 0 },
    "disabled": { "type": "boolean" },
  },
  "required": ["answers-name"],
  "additionalProperties": false,
}

Schemas must declare draft-06 using http://json-schema.org/draft-06/schema# (the https form and missing trailing # are also accepted). htmlGlobalAttributes, custom AJV keywords, and schema access to tag, text, innerHtml, or children are intentionally not supported; use tag validators for content or relationship checks.

Diagnostics

Schema diagnostics are phrased in HTML/element terms rather than JSON-Schema vocabulary, so template authors aren't asked to translate instancePath and additionalProperty back into the markup they wrote. Examples:

Schema constraint Diagnostic
required: ["answers-name"] <pl-multiple-choice> is missing required attribute "answers-name".
additionalProperties: false Unknown attribute "extra" on <pl-multiple-choice>.
strict unlisted child <pl-multiple-choice> only allows these child elements: <pl-answer>.
child-only tag outside its parent <pl-answer> may only appear as a direct child of these parent elements: <pl-multiple-choice>.
child additionalProperties Unknown attribute "ranking" on <pl-answer> inside <pl-multiple-choice>.
custom tag boolean attribute Attribute "correct" on <pl-answer> must have a value.
properties.display.enum Attribute "display" on <pl-multiple-choice> must be one of: "block", "inline".
properties.size.type: "integer" Attribute "size" on <pl-multiple-choice> must be integer.
properties.weight.minimum: 0 Attribute "weight" on <pl-multiple-choice> must be >= 0.

Constraints without a rewriter fall through to ajv's localized text. Every diagnostic carries ruleName: "customTagSchema" and points at the element or attribute — see Disabling Lint Rules to silence them per-region.

Custom error messages

Override individual diagnostics with JSON Schema's errorMessage keyword (enabled via ajv-errors). Authored strings flow through unchanged — the HTML-shaped rewriter only kicks in when the schema doesn't provide one.

{
  "type": "object",
  "required": ["answers-name"],
  "properties": {
    "size": { "type": "integer", "minimum": 1 },
    "display": { "enum": ["block", "inline", "dropdown"] },
  },
  "errorMessage": {
    "required": {
      // Per-property override for `required` failures
      "answers-name": "Add `answers-name=\"...\"` so this question can be graded.",
    },
    "properties": {
      // Per-property override for *value-level* failures (type, minimum, enum, ...)
      "size": "`size` must be a positive whole number — got ${0}.",
      "display": "`display` must be one of: block, inline, dropdown.",
    },
    // Catch-all for the whole object
    "_": "<pl-multiple-choice> failed its schema.",
  },
}

ajv-errors substitutes the original ajv error with one whose message is your string; the rewriter sees keyword: "errorMessage", doesn't recognise it, and passes the string through. Use this when the default phrasing doesn't say enough about your domain (e.g. linking authors to a runbook URL, naming a specific config the attribute drives).

Tag validators

Use synchronous JavaScript validators for checks that need children, content, ordering, or cross-element relationships. Validators run only for tags that are also declared in customTags[]; the plugin does not make undeclared tags known by itself.

// scripts/htmlmustache-plugin.mjs
import {
  attr,
  defineTagValidators,
  validateAttributes,
  validateElement,
} from '@reteps/tree-sitter-htmlmustache/linter';

export const validators = defineTagValidators('pl-order-blocks', {
  'pl/order-blocks-children'(element, context) {
    for (const child of element.childrenWithoutTag('pl-answer')) {
      context.reportElement(
        child,
        '<pl-order-blocks> only allows <pl-answer> children.',
      );
    }
  },
  'pl/order-blocks-attributes'(element, context) {
    const gradingMethod =
      attr(element, 'grading-method').literalMap((value) =>
        typeof value === 'string' ? value : undefined,
      ) ?? 'ordered';
    if (!['ordered', 'unordered'].includes(gradingMethod)) {
      context.reportAttribute(
        element,
        'grading-method',
        'grading-method must be ordered or unordered.',
      );
    }

    validateElement(context, element, {
      reportAttribute: 'code-language',
      invalid(target) {
        return (
          attr(target, 'code-language').present() &&
          attr(target, 'format').literal() !== 'code'
        );
      },
      message: 'code-language is only valid when format is code.',
    });

    validateAttributes(context, element, ['min-incorrect'], {
      invalid(_target, attribute) {
        return (
          attribute.present() &&
          attribute.literal() !== undefined &&
          attribute.literalMap((value) => {
            if (typeof value !== 'string') return undefined;
            const trimmed = value.trim();
            if (!/^[+-]?\d+$/.test(trimmed)) return undefined;
            return Number(trimmed);
          }) === undefined
        );
      },
      message(_target, attribute) {
        return `${attribute.name} must be an integer.`;
      },
    });
  },
});

element.children contains direct child HTML elements. Mustache sections are transparent, so children inside {{#section}}...{{/section}} are included. Child facades are populated recursively, so validators can inspect nested direct children from each child facade. element.innerHtml is present only when the validator opts into options.includeInnerHtml.

defineTagValidators(tagOrTags, rules) is optional sugar for plugin authors. It lowercases the target tag or tags and expands each rule-map entry into an independent validator; rule-map keys are the exact rule ids used by rules config and inline disable comments. A rule can be a bare validation function or an object with validate, severity, and options.

Attribute helper names are case-insensitive. Use attr(element, name).present() for presence and attr(element, name).literal() when dynamic Mustache values should be treated as unknown. Use attr(element, name).literalMap(mapper) to convert literal values into project-specific enums, booleans, numbers, or other validated values. literalMap(mapper) runs only for literal values and returns undefined when the attribute is missing, dynamic, or rejected by the mapper.

Use validateElement(context, element, options) and validateAttributes(context, element, names, options) for common report-if-invalid checks. validateElement reports on the element unless options.reportAttribute is set. validateAttributes passes an attribute helper to invalid(element, attribute) and reports against the matching attribute name.

Use childrenWithTag(tag) and childrenWithoutTag(tag) for direct child tag checks. ValidatorContext also exposes reportElement(element, message) and reportAttribute(element, name, message) as shorthand for report(...).

Validator ids are rule ids. Configure severity and inline disables the same way as built-in rules:

{
  "rules": {
    "pl/order-blocks-children": "warning",
  },
}

Custom formats

Schemas can use the format keyword for value rules that don't fit into enum/pattern/type — for example, the case-insensitive truthy-string set PrairieLearn accepts for boolean attributes (true/t/1/yes/y/on and their negatives, any case). Register the format implementation programmatically when constructing the linter:

import {
  createLinter,
  type SchemaFormat,
} from '@reteps/tree-sitter-htmlmustache/linter';

const BOOLEAN_STRINGS = new Set([
  'true',
  't',
  '1',
  'yes',
  'y',
  'on',
  'false',
  'f',
  '0',
  'no',
  'n',
  'off',
]);
const plBoolean: SchemaFormat = (value) =>
  typeof value === 'string' && BOOLEAN_STRINGS.has(value.toLowerCase());

const linter = await createLinter({
  locateWasm,
  formats: { 'pl-boolean': plBoolean },
});

A schema then references it like any built-in format:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "fixed-order": { "type": "string", "format": "pl-boolean" },
  },
}

Formats are functions (or RegExp, or ajv FormatDefinition objects), so they can't live directly in .htmlmustache.jsonc. For the CLI and the VS Code extension (which both load .htmlmustache.jsonc), supply them through a pluginModule field pointing at a relative JS/TS file that exports a formats record:

// .htmlmustache.jsonc
{
  "customTags": [
    {
      "name": "pl-multiple-choice",
      "schema": "elements/pl-multiple-choice/pl-multiple-choice.schema.json",
    },
  ],
  "pluginModule": "./scripts/htmlmustache-plugin.mjs",
  "rules": { "customTagSchema": "error" },
}
// scripts/htmlmustache-plugin.mjs
const BOOLEAN_STRINGS = new Set([
  'true',
  't',
  '1',
  'yes',
  'y',
  'on',
  'false',
  'f',
  '0',
  'no',
  'n',
  'off',
]);

export const formats = {
  'pl-boolean': (value) =>
    typeof value === 'string' && BOOLEAN_STRINGS.has(value.toLowerCase()),
};

The CLI and the language server dynamically import this file once per process (cached by absolute path) and register the exports before linting. Note the trust implication: pointing pluginModule at a path means running that code in-process when the CLI lints or the VS Code extension opens a document — treat it like a build script in your repo.

If the file can't be loaded, or its export shape is wrong, you'll see a pluginModule-rule diagnostic naming the path in the failure message; tag schemas that reference an unregistered format will then fail at compile time with the standard ajv "unknown format" error.

Custom Rules

Define project-specific lint rules using CSS-like selectors. Mustache constructs are written literally — {{foo}}, {{{foo}}}, {{#section}}, {{^inverted}}, {{!comment}}, {{>partial}}:

{
  "customRules": [
    {
      "id": "no-font",
      "selector": "font",
      "message": "The <font> tag is deprecated. Use CSS instead.",
    },
    {
      "id": "images-need-alt",
      "selector": "img:not([alt])",
      "message": "Images must have alt text for accessibility",
    },
    {
      "id": "no-hidden-inputs-in-list",
      "selector": "{{#items}} > input[type=hidden]",
      "message": "Hidden inputs inside {{#items}} sections are usually a mistake",
    },
    {
      "id": "no-relative-client-files-question",
      "selector": "[src^=\"clientFilesQuestion/\"], [href^=\"clientFilesQuestion/\"]",
      "message": "Use {{options.client_files_question_url}}/... instead of a relative clientFilesQuestion/... path.",
      "severity": "warning",
    },
    {
      "id": "no-deprecated-param",
      "selector": "{{data.deprecated_param}}",
      "message": "data.deprecated_param was removed.",
    },
    {
      "id": "no-raw-user-input",
      "selector": "{{{user_input}}}",
      "message": "Never emit user text unescaped.",
    },
  ],
}

Each custom rule requires an id, selector, and message. The severity defaults to "error" but can be set to "warning" or "off".

Selector syntax:

Selector Matches
div HTML elements by tag name
* Any HTML element
#main ID (shorthand for [id="main"])
.panel Class (shorthand for [class~="panel"])
div span Descendant (span anywhere inside div)
div > span Direct child
[style] Attribute presence
input[type=hidden] Attribute value (exact)
[src^="prefix/"] Attribute starts with
[href*="substring"] Attribute contains
[src$=".png"] Attribute ends with
[class~="warning"] Attribute contains whitespace-token
img:not([alt]) Negated attribute / class / id
{{foo}} Escaped variable {{foo}}
{{data.foo}} Variable with a dotted path
{{{foo}}} Triple / unescaped variable
{{options.*}} Variable path prefix match
{{*.deprecated}} Variable path suffix match
{{*}} Any escaped variable
{{{*}}} Any triple
{{#items}} Section {{#items}}...{{/items}}
{{^items}} Inverted section {{^items}}...{{/items}}
{{#items}} > li Direct child inside a section
{{!TODO}} Comment with exact content
{{!*TODO*}} Comment containing "TODO"
{{>header}} Partial invocation
{{>legacy_*}} Partial name prefix
pl-multiple-choice:has({{foo}}) Element containing a given variable
pl-multiple-choice:not(:has(pl-answer)) Element missing a required descendant
div, span Comma-separated alternatives
:root The document root (the whole parse tree)
:root:has(pl-answer-panel) Document contains a descendant anywhere
:root:not(:has(pl-answer-panel)) Document is missing a descendant anywhere
:root > section Top-level element (direct child of root)

The > (child) combinator is kind-transparent: div > span matches even if a Mustache section sits between them (e.g. <div>{{#show}}<span>{{/show}}</div>), and {{#a}} > {{#b}} matches across intervening HTML elements. {{#foo}} matches only positive sections — to target inverted sections use {{^foo}}.

Document-scoped conditional rules. Use :root with :has(...) / :not(:has(...)) to express rules that depend on the overall document. Chained :has(...) acts as AND, so you can combine "contains X" and "missing Y" in one selector:

{
  "id": "question-needs-answer-panel",
  "selector": ":root:has(pl-question-panel):not(:has(pl-answer-panel))",
  "message": "A question-panel document must also declare a pl-answer-panel.",
}

When :root matches, the diagnostic is reported at the start of the document (row 0, column 0) so the squiggle doesn't span the whole file. :root here is the tree-sitter fragment root, so it works on partial templates and fragments — unlike browser CSS, which anchors :root on <html>. Inside :has(...), :root refers to the element being has-checked, not the document.

Disabling Lint Rules

Disable a lint rule for an entire file with an inline comment:

<!-- htmlmustache-disable preferMustacheComments -->
{{! htmlmustache-disable selfClosingNonVoidTags }}

The comment can appear anywhere in the file. Both built-in and custom rules can be disabled by name/id. Use multiple comments to disable multiple rules.

EditorConfig

Both the CLI and VS Code extension respect your .editorconfig file for indentation settings (indent_style, indent_size). EditorConfig values override .htmlmustache.jsonc for indentation, and CLI flags override everything.

Priority order: defaults < .htmlmustache.jsonc < .editorconfig (indent only) < CLI flags

Acknowledgments

This project is based on tree-sitter-html by Max Brunsfeld and Amaan Qureshi.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tree_sitter_htmlmustache-1.4.1.tar.gz (72.4 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

tree_sitter_htmlmustache-1.4.1-cp310-abi3-win_amd64.whl (37.9 kB view details)

Uploaded CPython 3.10+Windows x86-64

tree_sitter_htmlmustache-1.4.1-cp310-abi3-musllinux_1_2_x86_64.whl (69.0 kB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ x86-64

tree_sitter_htmlmustache-1.4.1-cp310-abi3-musllinux_1_2_aarch64.whl (68.8 kB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

tree_sitter_htmlmustache-1.4.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl (70.8 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64manylinux: glibc 2.28+ ARM64

tree_sitter_htmlmustache-1.4.1-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (69.9 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ x86-64manylinux: glibc 2.5+ x86-64

tree_sitter_htmlmustache-1.4.1-cp310-abi3-macosx_11_0_arm64.whl (35.5 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

Details for the file tree_sitter_htmlmustache-1.4.1.tar.gz.

File metadata

  • Download URL: tree_sitter_htmlmustache-1.4.1.tar.gz
  • Upload date:
  • Size: 72.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tree_sitter_htmlmustache-1.4.1.tar.gz
Algorithm Hash digest
SHA256 3ab96a2a76bf69d8d13877489d5e13271a4d11f6837983e6e9564fe6f540c11e
MD5 e0c24d55780854174cfa6d20a575f64e
BLAKE2b-256 5c7d563ccc9676ebf1bf6ef38b56096d2106cd1f9b0a7e9433435bc2c77080e2

See more details on using hashes here.

Provenance

The following attestation bundles were made for tree_sitter_htmlmustache-1.4.1.tar.gz:

Publisher: publish-pypi.yml on reteps/tree-sitter-htmlmustache

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tree_sitter_htmlmustache-1.4.1-cp310-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for tree_sitter_htmlmustache-1.4.1-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 ffcd0bb85d9b5cb9960b35e0389d56691255f8a4205f1a789722e5450c36c5cb
MD5 53a7ba65c6bf9599a9f37e61b3a977a7
BLAKE2b-256 f36b5abd58af3d5106cb6cf009660ebf868ffdf18832b29389c46d3fbdc2f246

See more details on using hashes here.

Provenance

The following attestation bundles were made for tree_sitter_htmlmustache-1.4.1-cp310-abi3-win_amd64.whl:

Publisher: publish-pypi.yml on reteps/tree-sitter-htmlmustache

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tree_sitter_htmlmustache-1.4.1-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for tree_sitter_htmlmustache-1.4.1-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 60d5b387c7a0d929817a97bfb35b09ab5d1a1dfd52b237e6a3357195ba177f3b
MD5 430183d76d724c586c37874a4e40693f
BLAKE2b-256 95e52806d5f0e48a2390b5fc8897549be2df65022568acb6e0d4ec52fcf47d3b

See more details on using hashes here.

Provenance

The following attestation bundles were made for tree_sitter_htmlmustache-1.4.1-cp310-abi3-musllinux_1_2_x86_64.whl:

Publisher: publish-pypi.yml on reteps/tree-sitter-htmlmustache

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tree_sitter_htmlmustache-1.4.1-cp310-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for tree_sitter_htmlmustache-1.4.1-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 86fba2497d2b07cc965d7666a511d3ec195e0be7280c9562e2dea21a0c42670f
MD5 ffee8282f84168c1de813c77d72dc6b1
BLAKE2b-256 28e3ba4f772d6c1e2717c0d38de93c00981ae6e6c84660cc26f46568c5a6e0c5

See more details on using hashes here.

Provenance

The following attestation bundles were made for tree_sitter_htmlmustache-1.4.1-cp310-abi3-musllinux_1_2_aarch64.whl:

Publisher: publish-pypi.yml on reteps/tree-sitter-htmlmustache

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tree_sitter_htmlmustache-1.4.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for tree_sitter_htmlmustache-1.4.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 9821af209649bc25c6d00a317fa2c5590e5fc56e2f42a27376fd1f7a4bb8a2ca
MD5 1ccf6b83115838775bd6964564f3b64f
BLAKE2b-256 d33c34caa631d1391df53bb57997170f0056f99e1accb7f1ace13f22278830f8

See more details on using hashes here.

Provenance

The following attestation bundles were made for tree_sitter_htmlmustache-1.4.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl:

Publisher: publish-pypi.yml on reteps/tree-sitter-htmlmustache

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tree_sitter_htmlmustache-1.4.1-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.

File metadata

File hashes

Hashes for tree_sitter_htmlmustache-1.4.1-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
Algorithm Hash digest
SHA256 ee6864992c44b665a4d28b685abf7e387ae5e449b724f9c89deee90bd35f5632
MD5 ec19fe3b5f9207b525cdc697da958588
BLAKE2b-256 41d1609db097da660cb621d6e6bfcaff9a4654f31beb8a477d97f9e329f37e68

See more details on using hashes here.

Provenance

The following attestation bundles were made for tree_sitter_htmlmustache-1.4.1-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl:

Publisher: publish-pypi.yml on reteps/tree-sitter-htmlmustache

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tree_sitter_htmlmustache-1.4.1-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for tree_sitter_htmlmustache-1.4.1-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c8f42f724d150d784d08605769735d0012917883c23074ec69f9faeaf0ff3359
MD5 606a227d807f4e8bad302a763fbba142
BLAKE2b-256 c22478fd40545cc76a168e5da40ec52207ff29269cdcbb5797ef07d08fba42a8

See more details on using hashes here.

Provenance

The following attestation bundles were made for tree_sitter_htmlmustache-1.4.1-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: publish-pypi.yml on reteps/tree-sitter-htmlmustache

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page