@reteps/tree-sitter-htmlmustache 0.0.39 → 0.0.40

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 +12 -9
  2. package/cli/out/main.js +246 -25
  3. package/package.json +10 -4
package/README.md CHANGED
@@ -145,14 +145,17 @@ Place a comment immediately before the element to preserve its original formatti
145
145
 
146
146
  ```html
147
147
  <!-- htmlmustache-ignore -->
148
- <div class="a" id="b" >
149
- manually formatted
150
- </div>
148
+ <div class="a" id="b">manually formatted</div>
151
149
  ```
152
150
 
153
151
  ```html
154
152
  {{! htmlmustache-ignore }}
155
- <table><tr><td>compact</td><td>table</td></tr></table>
153
+ <table>
154
+ <tr>
155
+ <td>compact</td>
156
+ <td>table</td>
157
+ </tr>
158
+ </table>
156
159
  ```
157
160
 
158
161
  Only the immediately following sibling node is ignored. Subsequent nodes are formatted normally.
@@ -163,15 +166,15 @@ Wrap a region in start/end comments to preserve everything between them:
163
166
 
164
167
  ```html
165
168
  <!-- htmlmustache-ignore-start -->
166
- <div class="a" >content</div>
167
- <p> kept as-is </p>
169
+ <div class="a">content</div>
170
+ <p>kept as-is</p>
168
171
  <!-- htmlmustache-ignore-end -->
169
172
  ```
170
173
 
171
174
  ```html
172
- {{! htmlmustache-ignore-start }}
173
- {{#items}}<li>{{name}}</li>{{/items}}
174
- {{! htmlmustache-ignore-end }}
175
+ {{! htmlmustache-ignore-start }} {{#items}}
176
+ <li>{{name}}</li>
177
+ {{/items}} {{! htmlmustache-ignore-end }}
175
178
  ```
176
179
 
177
180
  If `ignore-start` has no matching `ignore-end`, all remaining siblings in the current scope are preserved as raw text.
package/cli/out/main.js CHANGED
@@ -1715,16 +1715,229 @@ function loadConfigFileForPath(filePath) {
1715
1715
  }
1716
1716
  }
1717
1717
 
1718
+ // lsp/server/src/htmlBalanceChecker.ts
1719
+ function getTagName(element) {
1720
+ const startTag = element.children.find((c) => c.type === "html_start_tag");
1721
+ if (!startTag) return null;
1722
+ const tagNameNode = startTag.children.find((c) => c.type === "html_tag_name");
1723
+ return tagNameNode?.text?.toLowerCase() ?? null;
1724
+ }
1725
+ function getErroneousEndTagName(node) {
1726
+ const nameNode = node.children.find((c) => c.type === "html_erroneous_end_tag_name");
1727
+ return nameNode?.text?.toLowerCase() ?? null;
1728
+ }
1729
+ function getSectionName(node) {
1730
+ const beginNode = node.children.find(
1731
+ (c) => c.type === "mustache_section_begin" || c.type === "mustache_inverted_section_begin"
1732
+ );
1733
+ if (!beginNode) return null;
1734
+ const tagNameNode = beginNode.children.find((c) => c.type === "mustache_tag_name");
1735
+ return tagNameNode?.text ?? null;
1736
+ }
1737
+ function hasForcedEndTag(element) {
1738
+ return element.children.some((c) => c.type === "html_forced_end_tag");
1739
+ }
1740
+ function extractFromNodes(nodes) {
1741
+ const items = [];
1742
+ for (const node of nodes) {
1743
+ items.push(...extractFromNode(node));
1744
+ }
1745
+ return items;
1746
+ }
1747
+ function extractFromNode(node) {
1748
+ if (node.type === "html_element") {
1749
+ const contentChildren = node.children.filter(
1750
+ (c) => c.type !== "html_start_tag" && c.type !== "html_end_tag" && c.type !== "html_forced_end_tag"
1751
+ );
1752
+ if (hasForcedEndTag(node)) {
1753
+ const tagName = getTagName(node);
1754
+ const items = [];
1755
+ if (tagName) {
1756
+ const startTag = node.children.find((c) => c.type === "html_start_tag");
1757
+ items.push({ type: "open", tagName, node: startTag ?? node });
1758
+ }
1759
+ items.push(...extractFromNodes(contentChildren));
1760
+ return items;
1761
+ }
1762
+ return extractFromNodes(contentChildren);
1763
+ }
1764
+ if (node.type === "html_self_closing_tag") {
1765
+ return [];
1766
+ }
1767
+ if (node.type === "html_erroneous_end_tag") {
1768
+ const tagName = getErroneousEndTagName(node);
1769
+ if (tagName) {
1770
+ return [{ type: "close", tagName, node }];
1771
+ }
1772
+ return [];
1773
+ }
1774
+ if (node.type === "mustache_section") {
1775
+ const sectionName = getSectionName(node);
1776
+ if (sectionName) {
1777
+ const contentChildren = node.children.filter(
1778
+ (c) => c.type !== "mustache_section_begin" && c.type !== "mustache_section_end" && c.type !== "mustache_erroneous_section_end"
1779
+ );
1780
+ return [
1781
+ {
1782
+ type: "fork",
1783
+ sectionName,
1784
+ truthy: extractFromNodes(contentChildren),
1785
+ falsy: []
1786
+ }
1787
+ ];
1788
+ }
1789
+ return [];
1790
+ }
1791
+ if (node.type === "mustache_inverted_section") {
1792
+ const sectionName = getSectionName(node);
1793
+ if (sectionName) {
1794
+ const contentChildren = node.children.filter(
1795
+ (c) => c.type !== "mustache_inverted_section_begin" && c.type !== "mustache_inverted_section_end" && c.type !== "mustache_erroneous_inverted_section_end"
1796
+ );
1797
+ return [
1798
+ {
1799
+ type: "fork",
1800
+ sectionName,
1801
+ truthy: [],
1802
+ falsy: extractFromNodes(contentChildren)
1803
+ }
1804
+ ];
1805
+ }
1806
+ return [];
1807
+ }
1808
+ return extractFromNodes(node.children);
1809
+ }
1810
+ function mergeAdjacentForks(items) {
1811
+ if (items.length === 0) return items;
1812
+ const result = [];
1813
+ let i = 0;
1814
+ while (i < items.length) {
1815
+ const item = items[i];
1816
+ if (item.type !== "fork") {
1817
+ result.push(item);
1818
+ i++;
1819
+ continue;
1820
+ }
1821
+ const truthy = [...item.truthy];
1822
+ const falsy = [...item.falsy];
1823
+ let j = i + 1;
1824
+ while (j < items.length) {
1825
+ const next = items[j];
1826
+ if (next.type !== "fork" || next.sectionName !== item.sectionName) break;
1827
+ truthy.push(...next.truthy);
1828
+ falsy.push(...next.falsy);
1829
+ j++;
1830
+ }
1831
+ result.push({
1832
+ type: "fork",
1833
+ sectionName: item.sectionName,
1834
+ truthy: mergeAdjacentForks(truthy),
1835
+ falsy: mergeAdjacentForks(falsy)
1836
+ });
1837
+ i = j;
1838
+ }
1839
+ return result;
1840
+ }
1841
+ function collectSectionNames(items) {
1842
+ const names = /* @__PURE__ */ new Set();
1843
+ for (const item of items) {
1844
+ if (item.type === "fork") {
1845
+ names.add(item.sectionName);
1846
+ for (const name of collectSectionNames(item.truthy)) names.add(name);
1847
+ for (const name of collectSectionNames(item.falsy)) names.add(name);
1848
+ }
1849
+ }
1850
+ return names;
1851
+ }
1852
+ function flattenPath(items, assignment) {
1853
+ const events = [];
1854
+ for (const item of items) {
1855
+ if (item.type === "fork") {
1856
+ const value = assignment.get(item.sectionName) ?? true;
1857
+ const branch = value ? item.truthy : item.falsy;
1858
+ events.push(...flattenPath(branch, assignment));
1859
+ } else {
1860
+ events.push(item);
1861
+ }
1862
+ }
1863
+ return events;
1864
+ }
1865
+ function formatCondition(assignment) {
1866
+ if (assignment.size === 0) return "";
1867
+ const parts = [];
1868
+ for (const [name, value] of assignment) {
1869
+ parts.push(`${name} is ${value ? "truthy" : "falsy"}`);
1870
+ }
1871
+ return ` (when ${parts.join(", ")})`;
1872
+ }
1873
+ function validateBalance(events, condition) {
1874
+ const errors = [];
1875
+ const stack = [];
1876
+ for (const event of events) {
1877
+ if (event.type === "open") {
1878
+ stack.push(event);
1879
+ } else {
1880
+ if (stack.length === 0) {
1881
+ errors.push({
1882
+ node: event.node,
1883
+ message: `Mismatched HTML end tag: </${event.tagName}>${condition}`
1884
+ });
1885
+ } else {
1886
+ const top = stack[stack.length - 1];
1887
+ if (top.tagName !== event.tagName) {
1888
+ errors.push({
1889
+ node: event.node,
1890
+ message: `Mismatched HTML end tag: </${event.tagName}>${condition}`
1891
+ });
1892
+ } else {
1893
+ stack.pop();
1894
+ }
1895
+ }
1896
+ }
1897
+ }
1898
+ for (const event of stack) {
1899
+ errors.push({
1900
+ node: event.node,
1901
+ message: `Unclosed HTML tag: <${event.tagName}>${condition}`
1902
+ });
1903
+ }
1904
+ return errors;
1905
+ }
1906
+ var MAX_SECTION_NAMES = 15;
1907
+ function checkHtmlBalance(rootNode) {
1908
+ const rawItems = extractFromNode(rootNode);
1909
+ const items = mergeAdjacentForks(rawItems);
1910
+ const sectionNames = [...collectSectionNames(items)];
1911
+ if (sectionNames.length > MAX_SECTION_NAMES) {
1912
+ return [];
1913
+ }
1914
+ const allErrors = [];
1915
+ const errorNodes = /* @__PURE__ */ new Set();
1916
+ const totalPaths = 1 << sectionNames.length;
1917
+ for (let mask = 0; mask < totalPaths; mask++) {
1918
+ const assignment = /* @__PURE__ */ new Map();
1919
+ for (let i = 0; i < sectionNames.length; i++) {
1920
+ assignment.set(sectionNames[i], (mask & 1 << i) !== 0);
1921
+ }
1922
+ const events = flattenPath(items, assignment);
1923
+ const condition = formatCondition(assignment);
1924
+ const pathErrors = validateBalance(events, condition);
1925
+ for (const error of pathErrors) {
1926
+ if (!errorNodes.has(error.node)) {
1927
+ errorNodes.add(error.node);
1928
+ allErrors.push(error);
1929
+ }
1930
+ }
1931
+ }
1932
+ return allErrors;
1933
+ }
1934
+
1718
1935
  // cli/src/check.ts
1719
1936
  function errorMessageForNode(nodeType, node) {
1720
1937
  if (nodeType === "mustache_erroneous_section_end" || nodeType === "mustache_erroneous_inverted_section_end") {
1721
1938
  const tagNameNode = node.children.find((c) => c.type === "mustache_erroneous_tag_name");
1722
1939
  return `Mismatched mustache section: {{/${tagNameNode?.text || "?"}}}`;
1723
1940
  }
1724
- if (nodeType === "html_erroneous_end_tag") {
1725
- const tagNameNode = node.children.find((c) => c.type === "html_erroneous_end_tag_name");
1726
- return `Mismatched HTML end tag: </${tagNameNode?.text || "?"}>`;
1727
- }
1728
1941
  if (nodeType === "ERROR") {
1729
1942
  return "Syntax error";
1730
1943
  }
@@ -1733,39 +1946,47 @@ function errorMessageForNode(nodeType, node) {
1733
1946
  var ERROR_NODE_TYPES = /* @__PURE__ */ new Set([
1734
1947
  "ERROR",
1735
1948
  "mustache_erroneous_section_end",
1736
- "mustache_erroneous_inverted_section_end",
1737
- "html_erroneous_end_tag"
1949
+ "mustache_erroneous_inverted_section_end"
1738
1950
  ]);
1739
1951
  function collectErrors(tree, file) {
1740
1952
  const errors = [];
1741
1953
  const cursor = tree.walk();
1742
- function visit(insideMustacheSection) {
1954
+ function visit() {
1743
1955
  const node = cursor.currentNode;
1744
1956
  const nodeType = cursor.nodeType;
1745
1957
  if (ERROR_NODE_TYPES.has(nodeType) || cursor.nodeIsMissing) {
1746
- const skip = insideMustacheSection && nodeType === "html_erroneous_end_tag";
1747
- if (!skip) {
1748
- errors.push({
1749
- file,
1750
- line: node.startPosition.row + 1,
1751
- column: node.startPosition.column + 1,
1752
- endLine: node.endPosition.row + 1,
1753
- endColumn: node.endPosition.column + 1,
1754
- message: errorMessageForNode(nodeType, node),
1755
- nodeText: node.text
1756
- });
1757
- }
1958
+ errors.push({
1959
+ file,
1960
+ line: node.startPosition.row + 1,
1961
+ column: node.startPosition.column + 1,
1962
+ endLine: node.endPosition.row + 1,
1963
+ endColumn: node.endPosition.column + 1,
1964
+ message: errorMessageForNode(nodeType, node),
1965
+ nodeText: node.text
1966
+ });
1758
1967
  if (nodeType === "ERROR") return;
1759
1968
  }
1760
- const enteringMustacheSection = nodeType === "mustache_section" || nodeType === "mustache_inverted_section";
1761
1969
  if (cursor.gotoFirstChild()) {
1762
1970
  do {
1763
- visit(insideMustacheSection || enteringMustacheSection);
1971
+ visit();
1764
1972
  } while (cursor.gotoNextSibling());
1765
1973
  cursor.gotoParent();
1766
1974
  }
1767
1975
  }
1768
- visit(false);
1976
+ visit();
1977
+ const rootNode = tree.rootNode;
1978
+ const balanceErrors = checkHtmlBalance(rootNode);
1979
+ for (const error of balanceErrors) {
1980
+ errors.push({
1981
+ file,
1982
+ line: error.node.startPosition.row + 1,
1983
+ column: error.node.startPosition.column + 1,
1984
+ endLine: error.node.endPosition.row + 1,
1985
+ endColumn: error.node.endPosition.column + 1,
1986
+ message: error.message,
1987
+ nodeText: error.node.text
1988
+ });
1989
+ }
1769
1990
  return errors;
1770
1991
  }
1771
1992
  function formatError(error, source) {
@@ -2347,7 +2568,7 @@ function isLine(doc) {
2347
2568
  }
2348
2569
 
2349
2570
  // lsp/server/src/formatting/utils.ts
2350
- function getTagName(node) {
2571
+ function getTagName2(node) {
2351
2572
  for (let i = 0; i < node.childCount; i++) {
2352
2573
  const child = node.child(i);
2353
2574
  if (!child) continue;
@@ -2535,7 +2756,7 @@ var PRESERVE_CONTENT_ELEMENTS = /* @__PURE__ */ new Set([
2535
2756
  function getCSSDisplay(node) {
2536
2757
  const type = node.type;
2537
2758
  if (type === "html_element") {
2538
- const tagName = getTagName(node);
2759
+ const tagName = getTagName2(node);
2539
2760
  if (tagName) {
2540
2761
  if (customCodeTags.has(tagName.toLowerCase())) {
2541
2762
  return "block";
@@ -2591,7 +2812,7 @@ function shouldPreserveContent(node) {
2591
2812
  return true;
2592
2813
  }
2593
2814
  if (type === "html_element") {
2594
- const tagName = getTagName(node);
2815
+ const tagName = getTagName2(node);
2595
2816
  if (!tagName) return false;
2596
2817
  const lower = tagName.toLowerCase();
2597
2818
  return PRESERVE_CONTENT_ELEMENTS.has(lower) || customCodeTags.has(lower);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reteps/tree-sitter-htmlmustache",
3
- "version": "0.0.39",
3
+ "version": "0.0.40",
4
4
  "description": "HTML with Mustache/Handlebars template syntax grammar for tree-sitter",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,9 +46,15 @@
46
46
  "tree-sitter": "^0.25.0"
47
47
  },
48
48
  "peerDependenciesMeta": {
49
- "node-addon-api": { "optional": true },
50
- "node-gyp-build": { "optional": true },
51
- "tree-sitter": { "optional": true }
49
+ "node-addon-api": {
50
+ "optional": true
51
+ },
52
+ "node-gyp-build": {
53
+ "optional": true
54
+ },
55
+ "tree-sitter": {
56
+ "optional": true
57
+ }
52
58
  },
53
59
  "devDependencies": {
54
60
  "@eslint/js": "^9.39.2",