@reteps/tree-sitter-htmlmustache 0.7.3 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +68 -22
  2. package/cli/out/main.js +946 -429
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -250,7 +250,7 @@ Additionally, the following rules are configurable. Set their severities (`"erro
250
250
 
251
251
  ### Custom Rules
252
252
 
253
- Define project-specific lint rules using CSS-like selectors to match HTML elements and Mustache sections:
253
+ Define project-specific lint rules using CSS-like selectors. Mustache constructs are written literally `{{foo}}`, `{{{foo}}}`, `{{#section}}`, `{{^inverted}}`, `{{!comment}}`, `{{>partial}}`:
254
254
 
255
255
  ```jsonc
256
256
  {
@@ -260,12 +260,6 @@ Define project-specific lint rules using CSS-like selectors to match HTML elemen
260
260
  "selector": "font",
261
261
  "message": "The <font> tag is deprecated. Use CSS instead.",
262
262
  },
263
- {
264
- "id": "no-inline-styles",
265
- "selector": "[style]",
266
- "message": "Avoid inline styles",
267
- "severity": "warning",
268
- },
269
263
  {
270
264
  "id": "images-need-alt",
271
265
  "selector": "img:not([alt])",
@@ -273,9 +267,25 @@ Define project-specific lint rules using CSS-like selectors to match HTML elemen
273
267
  },
274
268
  {
275
269
  "id": "no-hidden-inputs-in-list",
276
- "selector": "#items > input[type=hidden]",
270
+ "selector": "{{#items}} > input[type=hidden]",
277
271
  "message": "Hidden inputs inside {{#items}} sections are usually a mistake",
278
272
  },
273
+ {
274
+ "id": "no-relative-client-files-question",
275
+ "selector": "[src^=\"clientFilesQuestion/\"], [href^=\"clientFilesQuestion/\"]",
276
+ "message": "Use {{options.client_files_question_url}}/... instead of a relative clientFilesQuestion/... path.",
277
+ "severity": "warning",
278
+ },
279
+ {
280
+ "id": "no-deprecated-param",
281
+ "selector": "{{data.deprecated_param}}",
282
+ "message": "data.deprecated_param was removed.",
283
+ },
284
+ {
285
+ "id": "no-raw-user-input",
286
+ "selector": "{{{user_input}}}",
287
+ "message": "Never emit user text unescaped.",
288
+ },
279
289
  ],
280
290
  }
281
291
  ```
@@ -284,20 +294,56 @@ Each custom rule requires an `id`, `selector`, and `message`. The `severity` def
284
294
 
285
295
  **Selector syntax:**
286
296
 
287
- | Selector | Matches |
288
- | -------------------- | ---------------------------------------- |
289
- | `div` | HTML elements by tag name |
290
- | `#items` | Mustache sections by name (`{{#items}}`) |
291
- | `*` | Any HTML element |
292
- | `#` | Any Mustache section |
293
- | `div span` | Descendant (span anywhere inside div) |
294
- | `div > span` | Direct child (span directly inside div) |
295
- | `[style]` | Attribute presence |
296
- | `input[type=hidden]` | Attribute value |
297
- | `img:not([alt])` | Negated attribute |
298
- | `div, span` | Comma-separated alternatives |
299
-
300
- 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.
297
+ | Selector | Matches |
298
+ | ----------------------------------------- | ------------------------------------------ |
299
+ | `div` | HTML elements by tag name |
300
+ | `*` | Any HTML element |
301
+ | `#main` | ID (shorthand for `[id="main"]`) |
302
+ | `.panel` | Class (shorthand for `[class~="panel"]`) |
303
+ | `div span` | Descendant (span anywhere inside div) |
304
+ | `div > span` | Direct child |
305
+ | `[style]` | Attribute presence |
306
+ | `input[type=hidden]` | Attribute value (exact) |
307
+ | `[src^="prefix/"]` | Attribute starts with |
308
+ | `[href*="substring"]` | Attribute contains |
309
+ | `[src$=".png"]` | Attribute ends with |
310
+ | `[class~="warning"]` | Attribute contains whitespace-token |
311
+ | `img:not([alt])` | Negated attribute / class / id |
312
+ | `{{foo}}` | Escaped variable `{{foo}}` |
313
+ | `{{data.foo}}` | Variable with a dotted path |
314
+ | `{{{foo}}}` | Triple / unescaped variable |
315
+ | `{{options.*}}` | Variable path prefix match |
316
+ | `{{*.deprecated}}` | Variable path suffix match |
317
+ | `{{*}}` | Any escaped variable |
318
+ | `{{{*}}}` | Any triple |
319
+ | `{{#items}}` | Section `{{#items}}...{{/items}}` |
320
+ | `{{^items}}` | Inverted section `{{^items}}...{{/items}}` |
321
+ | `{{#items}} > li` | Direct child inside a section |
322
+ | `{{!TODO}}` | Comment with exact content |
323
+ | `{{!*TODO*}}` | Comment containing "TODO" |
324
+ | `{{>header}}` | Partial invocation |
325
+ | `{{>legacy_*}}` | Partial name prefix |
326
+ | `pl-multiple-choice:has({{foo}})` | Element containing a given variable |
327
+ | `pl-multiple-choice:not(:has(pl-answer))` | Element missing a required descendant |
328
+ | `div, span` | Comma-separated alternatives |
329
+ | `:root` | The document root (the whole parse tree) |
330
+ | `:root:has(pl-answer-panel)` | Document contains a descendant anywhere |
331
+ | `:root:not(:has(pl-answer-panel))` | Document is missing a descendant anywhere |
332
+ | `:root > section` | Top-level element (direct child of root) |
333
+
334
+ 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}}`.
335
+
336
+ **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:
337
+
338
+ ```jsonc
339
+ {
340
+ "id": "question-needs-answer-panel",
341
+ "selector": ":root:has(pl-question-panel):not(:has(pl-answer-panel))",
342
+ "message": "A question-panel document must also declare a pl-answer-panel.",
343
+ }
344
+ ```
345
+
346
+ 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.
301
347
 
302
348
  ### Disabling Lint Rules
303
349