@reteps/tree-sitter-htmlmustache 0.6.0 → 0.7.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 +65 -14
  2. package/cli/out/main.js +337 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -228,36 +228,87 @@ Additionally, the following rules are configurable. Set their severities (`"erro
228
228
  {
229
229
  "rules": {
230
230
  "consecutiveDuplicateSections": "off",
231
- "preferMustacheComments": "warning"
232
- }
231
+ "preferMustacheComments": "warning",
232
+ },
233
233
  }
234
234
  ```
235
235
 
236
236
  <!-- RULES_TABLE_START -->
237
237
 
238
- | Rule | Default | Description |
239
- | --- | --- | --- |
240
- | `nestedDuplicateSections` | `error` | Flags `{{#name}}` nested inside another `{{#name}}` with the same name |
241
- | `unquotedMustacheAttributes` | `error` | Requires quotes around mustache expressions used as attribute values |
242
- | `consecutiveDuplicateSections` | `warning` | Warns when adjacent same-name sections can be merged |
243
- | `selfClosingNonVoidTags` | `error` | Disallows self-closing syntax on non-void HTML elements (e.g. `<div/>`) |
244
- | `duplicateAttributes` | `error` | Detects duplicate HTML attributes on the same element |
245
- | `unescapedEntities` | `warning` | Flags unescaped `&` and `>` characters in text content |
246
- | `preferMustacheComments` | `off` | Suggests replacing HTML comments with mustache comments |
247
- | `unrecognizedHtmlTags` | `error` | Flags HTML tags that are not standard HTML elements or valid custom elements |
238
+ | Rule | Default | Description |
239
+ | ------------------------------ | --------- | ---------------------------------------------------------------------------- |
240
+ | `nestedDuplicateSections` | `error` | Flags `{{#name}}` nested inside another `{{#name}}` with the same name |
241
+ | `unquotedMustacheAttributes` | `error` | Requires quotes around mustache expressions used as attribute values |
242
+ | `consecutiveDuplicateSections` | `warning` | Warns when adjacent same-name sections can be merged |
243
+ | `selfClosingNonVoidTags` | `error` | Disallows self-closing syntax on non-void HTML elements (e.g. `<div/>`) |
244
+ | `duplicateAttributes` | `error` | Detects duplicate HTML attributes on the same element |
245
+ | `unescapedEntities` | `warning` | Flags unescaped `&` and `>` characters in text content |
246
+ | `preferMustacheComments` | `off` | Suggests replacing HTML comments with mustache comments |
247
+ | `unrecognizedHtmlTags` | `error` | Flags HTML tags that are not standard HTML elements or valid custom elements |
248
248
 
249
249
  <!-- RULES_TABLE_END -->
250
250
 
251
+ ### Custom Rules
252
+
253
+ Define project-specific lint rules using CSS-like selectors to match HTML elements and Mustache sections:
254
+
255
+ ```jsonc
256
+ {
257
+ "customRules": [
258
+ {
259
+ "id": "no-font",
260
+ "selector": "font",
261
+ "message": "The <font> tag is deprecated. Use CSS instead.",
262
+ },
263
+ {
264
+ "id": "no-inline-styles",
265
+ "selector": "[style]",
266
+ "message": "Avoid inline styles",
267
+ "severity": "warning",
268
+ },
269
+ {
270
+ "id": "images-need-alt",
271
+ "selector": "img:not([alt])",
272
+ "message": "Images must have alt text for accessibility",
273
+ },
274
+ {
275
+ "id": "no-hidden-inputs-in-list",
276
+ "selector": "#items > input[type=hidden]",
277
+ "message": "Hidden inputs inside {{#items}} sections are usually a mistake",
278
+ },
279
+ ],
280
+ }
281
+ ```
282
+
283
+ Each custom rule requires an `id`, `selector`, and `message`. The `severity` defaults to `"error"` but can be set to `"warning"` or `"off"`.
284
+
285
+ **Selector syntax:**
286
+
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.
301
+
251
302
  ### Disabling Lint Rules
252
303
 
253
- Disable a configurable lint rule for an entire file with an inline comment:
304
+ Disable a lint rule for an entire file with an inline comment:
254
305
 
255
306
  ```html
256
307
  <!-- htmlmustache-disable preferMustacheComments -->
257
308
  {{! htmlmustache-disable selfClosingNonVoidTags }}
258
309
  ```
259
310
 
260
- The comment can appear anywhere in the file. Only configurable rules (listed above) can be disabled. Use multiple comments to disable multiple rules.
311
+ 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.
261
312
 
262
313
  ### EditorConfig
263
314
 
package/cli/out/main.js CHANGED
@@ -748,6 +748,22 @@ function validateConfig(raw) {
748
748
  }
749
749
  if (hasRules) config.rules = rules;
750
750
  }
751
+ if (Array.isArray(obj.customRules)) {
752
+ const rules = [];
753
+ for (const entry of obj.customRules) {
754
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
755
+ const e = entry;
756
+ if (typeof e.id !== "string" || e.id.length === 0) continue;
757
+ if (typeof e.selector !== "string" || e.selector.length === 0) continue;
758
+ if (typeof e.message !== "string" || e.message.length === 0) continue;
759
+ const rule = { id: e.id, selector: e.selector, message: e.message };
760
+ if (typeof e.severity === "string" && VALID_RULE_SEVERITIES.has(e.severity)) {
761
+ rule.severity = e.severity;
762
+ }
763
+ rules.push(rule);
764
+ }
765
+ if (rules.length > 0) config.customRules = rules;
766
+ }
751
767
  return config;
752
768
  }
753
769
  function loadConfigFileForPath(filePath) {
@@ -921,18 +937,29 @@ function mergeAdjacentForks(items) {
921
937
  }
922
938
  return result;
923
939
  }
924
- function hasTagEvents(items) {
940
+ function isBranchBalanced(items) {
941
+ const stack = [];
925
942
  for (const item of items) {
926
- if (item.type !== "fork") return true;
927
- if (hasTagEvents(item.truthy) || hasTagEvents(item.falsy)) return true;
943
+ if (item.type === "fork") {
944
+ if (!isBranchBalanced(item.truthy) || !isBranchBalanced(item.falsy)) {
945
+ return false;
946
+ }
947
+ } else if (item.type === "open") {
948
+ stack.push(item.tagName);
949
+ } else {
950
+ if (stack.length === 0 || stack[stack.length - 1] !== item.tagName) {
951
+ return false;
952
+ }
953
+ stack.pop();
954
+ }
928
955
  }
929
- return false;
956
+ return stack.length === 0;
930
957
  }
931
958
  function collectSectionNames(items) {
932
959
  const names = /* @__PURE__ */ new Set();
933
960
  for (const item of items) {
934
961
  if (item.type === "fork") {
935
- if (hasTagEvents(item.truthy) || hasTagEvents(item.falsy)) {
962
+ if (!isBranchBalanced(item.truthy) || !isBranchBalanced(item.falsy)) {
936
963
  names.add(item.sectionName);
937
964
  }
938
965
  for (const name of collectSectionNames(item.truthy)) names.add(name);
@@ -1573,6 +1600,281 @@ function checkDuplicateAttributes(rootNode) {
1573
1600
  return errors;
1574
1601
  }
1575
1602
 
1603
+ // lsp/server/src/selectorMatcher.ts
1604
+ function isNameChar(ch) {
1605
+ return /[a-zA-Z0-9\-_]/.test(ch);
1606
+ }
1607
+ function parseAttributes(raw, pos) {
1608
+ const attrs = [];
1609
+ while (pos.i < raw.length) {
1610
+ if (raw[pos.i] === ":") {
1611
+ if (raw.slice(pos.i, pos.i + 6).toLowerCase() !== ":not([") return attrs;
1612
+ pos.i += 6;
1613
+ const attr = parseOneAttribute(raw, pos, true);
1614
+ if (!attr) return attrs;
1615
+ if (raw[pos.i] !== "]" || raw[pos.i + 1] !== ")") return attrs;
1616
+ pos.i += 2;
1617
+ attrs.push(attr);
1618
+ } else if (raw[pos.i] === "[") {
1619
+ pos.i++;
1620
+ const attr = parseOneAttribute(raw, pos, false);
1621
+ if (!attr) return attrs;
1622
+ if (raw[pos.i] !== "]") return attrs;
1623
+ pos.i++;
1624
+ attrs.push(attr);
1625
+ } else {
1626
+ break;
1627
+ }
1628
+ }
1629
+ return attrs;
1630
+ }
1631
+ function parseOneAttribute(raw, pos, negated) {
1632
+ let name = "";
1633
+ while (pos.i < raw.length && isNameChar(raw[pos.i])) {
1634
+ name += raw[pos.i];
1635
+ pos.i++;
1636
+ }
1637
+ if (name.length === 0) return null;
1638
+ let value;
1639
+ if (raw[pos.i] === "=") {
1640
+ pos.i++;
1641
+ value = "";
1642
+ if (raw[pos.i] === '"' || raw[pos.i] === "'") {
1643
+ const quote = raw[pos.i];
1644
+ pos.i++;
1645
+ while (pos.i < raw.length && raw[pos.i] !== quote) {
1646
+ value += raw[pos.i];
1647
+ pos.i++;
1648
+ }
1649
+ if (pos.i < raw.length) pos.i++;
1650
+ } else {
1651
+ while (pos.i < raw.length && raw[pos.i] !== "]") {
1652
+ value += raw[pos.i];
1653
+ pos.i++;
1654
+ }
1655
+ }
1656
+ }
1657
+ return { name: name.toLowerCase(), value, negated };
1658
+ }
1659
+ function parseSelector(raw) {
1660
+ const trimmed = raw.trim();
1661
+ if (trimmed.length === 0) return null;
1662
+ const parts = trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
1663
+ if (parts.length === 0) return null;
1664
+ const alternatives = [];
1665
+ for (const part of parts) {
1666
+ const alt = parseSingleSelector(part);
1667
+ if (!alt) return null;
1668
+ alternatives.push(alt);
1669
+ }
1670
+ return { alternatives };
1671
+ }
1672
+ function parseSingleSelector(raw) {
1673
+ const segments = [];
1674
+ let i = 0;
1675
+ let nextCombinator = "descendant";
1676
+ while (i < raw.length) {
1677
+ while (i < raw.length && raw[i] === " ") i++;
1678
+ if (i >= raw.length) break;
1679
+ if (raw[i] === ">") {
1680
+ if (segments.length === 0) return null;
1681
+ nextCombinator = "child";
1682
+ i++;
1683
+ while (i < raw.length && raw[i] === " ") i++;
1684
+ if (i >= raw.length) return null;
1685
+ continue;
1686
+ }
1687
+ const pos = { i };
1688
+ const segment = parseOneSegment(raw, pos);
1689
+ if (!segment) return null;
1690
+ i = pos.i;
1691
+ segment.combinator = nextCombinator;
1692
+ nextCombinator = "descendant";
1693
+ segments.push(segment);
1694
+ }
1695
+ if (segments.length === 0) return null;
1696
+ return { segments };
1697
+ }
1698
+ function parseOneSegment(raw, pos) {
1699
+ let kind;
1700
+ let name;
1701
+ if (raw[pos.i] === "#") {
1702
+ kind = "mustache";
1703
+ pos.i++;
1704
+ name = "";
1705
+ while (pos.i < raw.length && isNameChar(raw[pos.i])) {
1706
+ name += raw[pos.i];
1707
+ pos.i++;
1708
+ }
1709
+ if (name.length === 0) name = null;
1710
+ else name = name.toLowerCase();
1711
+ return { kind, name, attributes: [], combinator: "descendant" };
1712
+ }
1713
+ if (raw[pos.i] === "*") {
1714
+ kind = "html";
1715
+ name = null;
1716
+ pos.i++;
1717
+ const attrs2 = parseAttributes(raw, pos);
1718
+ return { kind, name, attributes: attrs2, combinator: "descendant" };
1719
+ }
1720
+ if (raw[pos.i] === "[" || raw[pos.i] === ":") {
1721
+ kind = "html";
1722
+ name = null;
1723
+ const attrs2 = parseAttributes(raw, pos);
1724
+ if (attrs2.length === 0) return null;
1725
+ return { kind, name, attributes: attrs2, combinator: "descendant" };
1726
+ }
1727
+ if (!isNameChar(raw[pos.i])) return null;
1728
+ kind = "html";
1729
+ name = "";
1730
+ while (pos.i < raw.length && isNameChar(raw[pos.i])) {
1731
+ name += raw[pos.i];
1732
+ pos.i++;
1733
+ }
1734
+ name = name.toLowerCase();
1735
+ const attrs = parseAttributes(raw, pos);
1736
+ return { kind, name, attributes: attrs, combinator: "descendant" };
1737
+ }
1738
+ function getNodeAttributes(node) {
1739
+ const startTag = node.children.find(
1740
+ (c) => c.type === "html_start_tag" || c.type === "html_self_closing_tag"
1741
+ );
1742
+ if (!startTag) return [];
1743
+ const attrs = [];
1744
+ for (const child of startTag.children) {
1745
+ if (child.type === "html_attribute") {
1746
+ let attrName = "";
1747
+ let attrValue;
1748
+ for (const part of child.children) {
1749
+ if (part.type === "html_attribute_name") {
1750
+ attrName = part.text.toLowerCase();
1751
+ } else if (part.type === "html_quoted_attribute_value") {
1752
+ attrValue = part.text.replace(/^["']|["']$/g, "");
1753
+ } else if (part.type === "html_attribute_value") {
1754
+ attrValue = part.text;
1755
+ }
1756
+ }
1757
+ if (attrName) attrs.push({ name: attrName, value: attrValue });
1758
+ }
1759
+ }
1760
+ return attrs;
1761
+ }
1762
+ function checkAttributes(node, constraints) {
1763
+ if (constraints.length === 0) return true;
1764
+ const nodeAttrs = getNodeAttributes(node);
1765
+ for (const constraint of constraints) {
1766
+ const found = nodeAttrs.find((a) => a.name === constraint.name);
1767
+ if (constraint.negated) {
1768
+ if (found) return false;
1769
+ } else if (constraint.value !== void 0) {
1770
+ if (!found || found.value !== constraint.value) return false;
1771
+ } else {
1772
+ if (!found) return false;
1773
+ }
1774
+ }
1775
+ return true;
1776
+ }
1777
+ function nodeMatchesSegment(node, segment) {
1778
+ if (segment.kind === "html") {
1779
+ if (!HTML_ELEMENT_TYPES.has(node.type)) return false;
1780
+ if (segment.name !== null) {
1781
+ const tagName = getTagName(node)?.toLowerCase();
1782
+ if (tagName !== segment.name) return false;
1783
+ }
1784
+ return checkAttributes(node, segment.attributes);
1785
+ }
1786
+ if (!MUSTACHE_SECTION_TYPES.has(node.type)) return false;
1787
+ if (segment.name !== null) {
1788
+ const sectionName = getSectionName(node)?.toLowerCase();
1789
+ if (sectionName !== segment.name) return false;
1790
+ }
1791
+ return true;
1792
+ }
1793
+ function checkAncestors(ancestors, segments, segIdx, childCombinator) {
1794
+ if (segIdx < 0) return true;
1795
+ const segment = segments[segIdx];
1796
+ if (childCombinator === "child") {
1797
+ for (let a = ancestors.length - 1; a >= 0; a--) {
1798
+ const entry = ancestors[a];
1799
+ if (entry.kind !== segment.kind) continue;
1800
+ if (segment.name !== null && entry.name !== segment.name) return false;
1801
+ if (segment.kind === "html" && segment.attributes.length > 0) {
1802
+ if (!checkAttributes(entry.node, segment.attributes)) return false;
1803
+ }
1804
+ return checkAncestors(ancestors.slice(0, a), segments, segIdx - 1, segment.combinator);
1805
+ }
1806
+ return false;
1807
+ }
1808
+ for (let a = ancestors.length - 1; a >= 0; a--) {
1809
+ const entry = ancestors[a];
1810
+ if (entry.kind !== segment.kind) continue;
1811
+ if (segment.name !== null && entry.name !== segment.name) continue;
1812
+ if (segment.kind === "html" && segment.attributes.length > 0) {
1813
+ if (!checkAttributes(entry.node, segment.attributes)) continue;
1814
+ }
1815
+ if (checkAncestors(ancestors.slice(0, a), segments, segIdx - 1, segment.combinator)) {
1816
+ return true;
1817
+ }
1818
+ }
1819
+ return false;
1820
+ }
1821
+ function getReportNode(node) {
1822
+ if (HTML_ELEMENT_TYPES.has(node.type)) {
1823
+ const startTag = node.children.find(
1824
+ (c) => c.type === "html_start_tag" || c.type === "html_self_closing_tag"
1825
+ );
1826
+ return startTag ?? node;
1827
+ }
1828
+ if (MUSTACHE_SECTION_TYPES.has(node.type)) {
1829
+ const begin = node.children.find(
1830
+ (c) => c.type === "mustache_section_begin" || c.type === "mustache_inverted_section_begin"
1831
+ );
1832
+ return begin ?? node;
1833
+ }
1834
+ return node;
1835
+ }
1836
+ function matchAlternative(rootNode, alt) {
1837
+ const results = [];
1838
+ const lastSegment = alt.segments[alt.segments.length - 1];
1839
+ function walk(node, ancestors) {
1840
+ if (nodeMatchesSegment(node, lastSegment)) {
1841
+ if (alt.segments.length === 1 || checkAncestors(ancestors, alt.segments, alt.segments.length - 2, lastSegment.combinator)) {
1842
+ results.push(getReportNode(node));
1843
+ }
1844
+ }
1845
+ let newAncestors = ancestors;
1846
+ if (HTML_ELEMENT_TYPES.has(node.type)) {
1847
+ const tagName = getTagName(node)?.toLowerCase();
1848
+ if (tagName) {
1849
+ newAncestors = [...ancestors, { kind: "html", name: tagName, node }];
1850
+ }
1851
+ } else if (MUSTACHE_SECTION_TYPES.has(node.type)) {
1852
+ const sectionName = getSectionName(node)?.toLowerCase();
1853
+ if (sectionName) {
1854
+ newAncestors = [...ancestors, { kind: "mustache", name: sectionName, node }];
1855
+ }
1856
+ }
1857
+ for (const child of node.children) {
1858
+ walk(child, newAncestors);
1859
+ }
1860
+ }
1861
+ walk(rootNode, []);
1862
+ return results;
1863
+ }
1864
+ function matchSelector(rootNode, selector) {
1865
+ const allResults = [];
1866
+ const seen = /* @__PURE__ */ new Set();
1867
+ for (const alt of selector.alternatives) {
1868
+ for (const node of matchAlternative(rootNode, alt)) {
1869
+ if (!seen.has(node)) {
1870
+ seen.add(node);
1871
+ allResults.push(node);
1872
+ }
1873
+ }
1874
+ }
1875
+ return allResults;
1876
+ }
1877
+
1576
1878
  // lsp/server/src/collectErrors.ts
1577
1879
  var ERROR_NODE_TYPES = /* @__PURE__ */ new Set([
1578
1880
  "ERROR",
@@ -1592,7 +1894,7 @@ function errorMessageForNode(nodeType, node) {
1592
1894
  function resolveRuleSeverity(rules, ruleName) {
1593
1895
  return rules?.[ruleName] ?? RULE_DEFAULTS[ruleName] ?? "off";
1594
1896
  }
1595
- function parseDisableDirective(node) {
1897
+ function parseDisableDirective(node, customRuleIds) {
1596
1898
  if (node.type !== "html_comment" && node.type !== "mustache_comment") return null;
1597
1899
  let inner = null;
1598
1900
  if (node.type === "html_comment") {
@@ -1606,12 +1908,14 @@ function parseDisableDirective(node) {
1606
1908
  const prefix = "htmlmustache-disable ";
1607
1909
  if (!inner.startsWith(prefix)) return null;
1608
1910
  const ruleName = inner.slice(prefix.length).trim();
1609
- return KNOWN_RULE_NAMES.has(ruleName) ? ruleName : null;
1911
+ if (KNOWN_RULE_NAMES.has(ruleName)) return ruleName;
1912
+ if (customRuleIds?.has(ruleName)) return ruleName;
1913
+ return null;
1610
1914
  }
1611
- function collectDisabledRules(rootNode) {
1915
+ function collectDisabledRules(rootNode, customRuleIds) {
1612
1916
  const disabled = /* @__PURE__ */ new Set();
1613
1917
  function walk(node) {
1614
- const rule = parseDisableDirective(node);
1918
+ const rule = parseDisableDirective(node, customRuleIds);
1615
1919
  if (rule) {
1616
1920
  disabled.add(rule);
1617
1921
  return;
@@ -1621,7 +1925,7 @@ function collectDisabledRules(rootNode) {
1621
1925
  walk(rootNode);
1622
1926
  return disabled;
1623
1927
  }
1624
- function collectErrors(tree, rules, customTagNames) {
1928
+ function collectErrors(tree, rules, customTagNames, customRules) {
1625
1929
  const errors = [];
1626
1930
  const cursor = tree.walk();
1627
1931
  function visit() {
@@ -1650,7 +1954,8 @@ function collectErrors(tree, rules, customTagNames) {
1650
1954
  for (const error of unclosedErrors) {
1651
1955
  errors.push({ node: error.node, message: error.message });
1652
1956
  }
1653
- const disabledRules = collectDisabledRules(tree.rootNode);
1957
+ const customRuleIds = customRules ? new Set(customRules.map((r) => r.id)) : void 0;
1958
+ const disabledRules = collectDisabledRules(tree.rootNode, customRuleIds);
1654
1959
  const effectiveRules = { ...rules };
1655
1960
  for (const rule of disabledRules) {
1656
1961
  effectiveRules[rule] = "off";
@@ -1675,18 +1980,32 @@ function collectErrors(tree, rules, customTagNames) {
1675
1980
  message: error.message,
1676
1981
  severity,
1677
1982
  fix: error.fix,
1678
- fixDescription: error.fixDescription
1983
+ fixDescription: error.fixDescription,
1984
+ ruleName: rule
1679
1985
  });
1680
1986
  }
1681
1987
  }
1988
+ if (customRules) {
1989
+ for (const rule of customRules) {
1990
+ if (disabledRules.has(rule.id)) continue;
1991
+ const severity = rule.severity ?? "error";
1992
+ if (severity === "off") continue;
1993
+ const parsed = parseSelector(rule.selector);
1994
+ if (!parsed) continue;
1995
+ const matches = matchSelector(tree.rootNode, parsed);
1996
+ for (const node of matches) {
1997
+ errors.push({ node, message: rule.message, severity, ruleName: rule.id });
1998
+ }
1999
+ }
2000
+ }
1682
2001
  return errors.filter(
1683
- (e) => !(e.message.includes("HTML comment found") && parseDisableDirective(e.node) !== null)
2002
+ (e) => !(e.message.includes("HTML comment found") && parseDisableDirective(e.node, customRuleIds) !== null)
1684
2003
  );
1685
2004
  }
1686
2005
 
1687
2006
  // cli/src/check.ts
1688
- function collectErrors2(tree, file, rules, customTagNames) {
1689
- const errors = collectErrors(tree, rules, customTagNames);
2007
+ function collectErrors2(tree, file, rules, customTagNames, customRules) {
2008
+ const errors = collectErrors(tree, rules, customTagNames, customRules);
1690
2009
  return errors.map((error) => ({
1691
2010
  file,
1692
2011
  line: error.node.startPosition.row + 1,
@@ -1871,12 +2190,13 @@ async function run(args) {
1871
2190
  const errorOutput = [];
1872
2191
  const rules = config?.rules;
1873
2192
  const customTagNames = config?.customTags?.map((t) => t.name);
2193
+ const customRules = config?.customRules;
1874
2194
  for (const file of files) {
1875
2195
  const displayPath = import_node_path.default.relative(cwd, file) || file;
1876
2196
  let source = import_node_fs.default.readFileSync(file, "utf-8");
1877
2197
  if (fixMode) {
1878
2198
  const tree2 = parseDocument(source);
1879
- const errors2 = collectErrors2(tree2, displayPath, rules, customTagNames);
2199
+ const errors2 = collectErrors2(tree2, displayPath, rules, customTagNames, customRules);
1880
2200
  const fixed = applyFixes(source, errors2);
1881
2201
  if (fixed !== source) {
1882
2202
  import_node_fs.default.writeFileSync(file, fixed, "utf-8");
@@ -1884,7 +2204,7 @@ async function run(args) {
1884
2204
  }
1885
2205
  }
1886
2206
  const tree = parseDocument(source);
1887
- const errors = collectErrors2(tree, displayPath, rules, customTagNames);
2207
+ const errors = collectErrors2(tree, displayPath, rules, customTagNames, customRules);
1888
2208
  const fileErrors = errors.filter((e) => e.severity !== "warning");
1889
2209
  const fileWarnings = errors.filter((e) => e.severity === "warning");
1890
2210
  if (errors.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reteps/tree-sitter-htmlmustache",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "HTML with Mustache/Handlebars template syntax grammar for tree-sitter",
5
5
  "repository": {
6
6
  "type": "git",