@reteps/tree-sitter-htmlmustache 0.8.1 → 0.9.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 (67) hide show
  1. package/README.md +1 -1
  2. package/browser/out/browser/index.d.ts +43 -0
  3. package/browser/out/browser/index.d.ts.map +1 -0
  4. package/browser/out/browser/index.mjs +3746 -0
  5. package/browser/out/browser/index.mjs.map +7 -0
  6. package/browser/out/core/collectErrors.d.ts +36 -0
  7. package/browser/out/core/collectErrors.d.ts.map +1 -0
  8. package/browser/out/core/configSchema.d.ts +63 -0
  9. package/browser/out/core/configSchema.d.ts.map +1 -0
  10. package/browser/out/core/customCodeTags.d.ts +34 -0
  11. package/browser/out/core/customCodeTags.d.ts.map +1 -0
  12. package/browser/out/core/diagnostic.d.ts +24 -0
  13. package/browser/out/core/diagnostic.d.ts.map +1 -0
  14. package/browser/out/core/embeddedRegions.d.ts +12 -0
  15. package/browser/out/core/embeddedRegions.d.ts.map +1 -0
  16. package/browser/out/core/formatting/classifier.d.ts +68 -0
  17. package/browser/out/core/formatting/classifier.d.ts.map +1 -0
  18. package/browser/out/core/formatting/embedded.d.ts +19 -0
  19. package/browser/out/core/formatting/embedded.d.ts.map +1 -0
  20. package/browser/out/core/formatting/formatters.d.ts +85 -0
  21. package/browser/out/core/formatting/formatters.d.ts.map +1 -0
  22. package/browser/out/core/formatting/index.d.ts +44 -0
  23. package/browser/out/core/formatting/index.d.ts.map +1 -0
  24. package/browser/out/core/formatting/ir.d.ts +100 -0
  25. package/browser/out/core/formatting/ir.d.ts.map +1 -0
  26. package/browser/out/core/formatting/mergeOptions.d.ts +18 -0
  27. package/browser/out/core/formatting/mergeOptions.d.ts.map +1 -0
  28. package/browser/out/core/formatting/printer.d.ts +18 -0
  29. package/browser/out/core/formatting/printer.d.ts.map +1 -0
  30. package/browser/out/core/formatting/utils.d.ts +39 -0
  31. package/browser/out/core/formatting/utils.d.ts.map +1 -0
  32. package/browser/out/core/grammar.d.ts +3 -0
  33. package/browser/out/core/grammar.d.ts.map +1 -0
  34. package/browser/out/core/htmlBalanceChecker.d.ts +23 -0
  35. package/browser/out/core/htmlBalanceChecker.d.ts.map +1 -0
  36. package/browser/out/core/mustacheChecks.d.ts +24 -0
  37. package/browser/out/core/mustacheChecks.d.ts.map +1 -0
  38. package/browser/out/core/nodeHelpers.d.ts +54 -0
  39. package/browser/out/core/nodeHelpers.d.ts.map +1 -0
  40. package/browser/out/core/ruleMetadata.d.ts +12 -0
  41. package/browser/out/core/ruleMetadata.d.ts.map +1 -0
  42. package/browser/out/core/selectorMatcher.d.ts +87 -0
  43. package/browser/out/core/selectorMatcher.d.ts.map +1 -0
  44. package/cli/out/main.js +333 -181
  45. package/package.json +21 -3
  46. package/src/browser/browser.test.ts +207 -0
  47. package/src/browser/index.ts +128 -0
  48. package/src/browser/tsconfig.json +18 -0
  49. package/src/core/collectErrors.ts +233 -0
  50. package/src/core/configSchema.ts +273 -0
  51. package/src/core/customCodeTags.ts +159 -0
  52. package/src/core/diagnostic.ts +45 -0
  53. package/src/core/embeddedRegions.ts +70 -0
  54. package/src/core/formatting/classifier.ts +549 -0
  55. package/src/core/formatting/embedded.ts +56 -0
  56. package/src/core/formatting/formatters.ts +1272 -0
  57. package/src/core/formatting/index.ts +185 -0
  58. package/src/core/formatting/ir.ts +202 -0
  59. package/src/core/formatting/mergeOptions.ts +34 -0
  60. package/src/core/formatting/printer.ts +242 -0
  61. package/src/core/formatting/utils.ts +193 -0
  62. package/src/core/grammar.ts +2 -0
  63. package/src/core/htmlBalanceChecker.ts +382 -0
  64. package/src/core/mustacheChecks.ts +504 -0
  65. package/src/core/nodeHelpers.ts +126 -0
  66. package/src/core/ruleMetadata.ts +63 -0
  67. package/src/core/selectorMatcher.ts +919 -0
package/cli/out/main.js CHANGED
@@ -525,11 +525,16 @@ var import_node_path = __toESM(require("node:path"));
525
525
  // cli/src/wasm.ts
526
526
  var path = __toESM(require("node:path"));
527
527
  var import_web_tree_sitter = require("web-tree-sitter");
528
+
529
+ // src/core/grammar.ts
530
+ var GRAMMAR_WASM_FILENAME = "tree-sitter-htmlmustache.wasm";
531
+
532
+ // cli/src/wasm.ts
528
533
  var parser;
529
534
  async function initializeParser() {
530
535
  await import_web_tree_sitter.Parser.init();
531
536
  parser = new import_web_tree_sitter.Parser();
532
- const wasmPath = path.resolve(__dirname, "..", "..", "tree-sitter-htmlmustache.wasm");
537
+ const wasmPath = path.resolve(__dirname, "..", "..", GRAMMAR_WASM_FILENAME);
533
538
  const language = await import_web_tree_sitter.Language.load(wasmPath);
534
539
  parser.setLanguage(language);
535
540
  }
@@ -545,7 +550,7 @@ function parseDocument(source) {
545
550
  var fs = __toESM(require("fs"), 1);
546
551
  var path2 = __toESM(require("path"), 1);
547
552
 
548
- // lsp/server/src/ruleMetadata.ts
553
+ // src/core/ruleMetadata.ts
549
554
  var RULES = [
550
555
  {
551
556
  name: "nestedDuplicateSections",
@@ -598,7 +603,7 @@ var RULE_DEFAULTS = Object.fromEntries(
598
603
  RULES.map((r2) => [r2.name, r2.defaultSeverity])
599
604
  );
600
605
 
601
- // lsp/server/src/configFile.ts
606
+ // src/core/configSchema.ts
602
607
  var VALID_CSS_DISPLAY_VALUES = /* @__PURE__ */ new Set([
603
608
  "block",
604
609
  "inline",
@@ -648,7 +653,6 @@ function parseRuleEntry(key, value) {
648
653
  if (!options) return { severity };
649
654
  return { severity, ...options };
650
655
  }
651
- var CONFIG_FILENAME = ".htmlmustache.jsonc";
652
656
  function parseJsonc(text2) {
653
657
  let result = "";
654
658
  let i2 = 0;
@@ -687,21 +691,6 @@ function parseJsonc(text2) {
687
691
  result = result.replace(/,\s*([}\]])/g, "$1");
688
692
  return JSON.parse(result);
689
693
  }
690
- function findConfigFile(startDir) {
691
- let dir = path2.resolve(startDir);
692
- const root = path2.parse(dir).root;
693
- while (true) {
694
- const candidate = path2.join(dir, CONFIG_FILENAME);
695
- try {
696
- fs.accessSync(candidate, fs.constants.R_OK);
697
- return candidate;
698
- } catch {
699
- }
700
- const parent = path2.dirname(dir);
701
- if (parent === dir || dir === root) return null;
702
- dir = parent;
703
- }
704
- }
705
694
  var VALID_INDENT_MODES = /* @__PURE__ */ new Set(["never", "always", "attribute"]);
706
695
  function parseCustomTagArray(arr) {
707
696
  if (!Array.isArray(arr)) return [];
@@ -801,6 +790,24 @@ function validateConfig(raw) {
801
790
  }
802
791
  return config;
803
792
  }
793
+
794
+ // lsp/server/src/configFile.ts
795
+ var CONFIG_FILENAME = ".htmlmustache.jsonc";
796
+ function findConfigFile(startDir) {
797
+ let dir = path2.resolve(startDir);
798
+ const root = path2.parse(dir).root;
799
+ while (true) {
800
+ const candidate = path2.join(dir, CONFIG_FILENAME);
801
+ try {
802
+ fs.accessSync(candidate, fs.constants.R_OK);
803
+ return candidate;
804
+ } catch {
805
+ }
806
+ const parent = path2.dirname(dir);
807
+ if (parent === dir || dir === root) return null;
808
+ dir = parent;
809
+ }
810
+ }
804
811
  function loadConfigFileForPath(filePath) {
805
812
  const dir = path2.dirname(path2.resolve(filePath));
806
813
  const configPath = findConfigFile(dir);
@@ -814,7 +821,7 @@ function loadConfigFileForPath(filePath) {
814
821
  }
815
822
  }
816
823
 
817
- // lsp/server/src/nodeHelpers.ts
824
+ // src/core/nodeHelpers.ts
818
825
  var MUSTACHE_SECTION_TYPES = /* @__PURE__ */ new Set([
819
826
  "mustache_section",
820
827
  "mustache_inverted_section"
@@ -878,7 +885,7 @@ function getPartialName(node) {
878
885
  return child ? child.text.trim() : null;
879
886
  }
880
887
 
881
- // lsp/server/src/htmlBalanceChecker.ts
888
+ // src/core/htmlBalanceChecker.ts
882
889
  function getTagNameLower(element) {
883
890
  return getTagName(element)?.toLowerCase() ?? null;
884
891
  }
@@ -1175,7 +1182,7 @@ function checkHtmlBalance(rootNode) {
1175
1182
  return allErrors;
1176
1183
  }
1177
1184
 
1178
- // lsp/server/src/mustacheChecks.ts
1185
+ // src/core/mustacheChecks.ts
1179
1186
  function checkNestedSameNameSections(rootNode) {
1180
1187
  const errors = [];
1181
1188
  function visit(node, ancestors) {
@@ -1834,7 +1841,7 @@ function m(e2, { recursive: t2 = true, list: s2 = true } = {}) {
1834
1841
  return c2;
1835
1842
  }
1836
1843
 
1837
- // lsp/server/src/selectorMatcher.ts
1844
+ // src/core/selectorMatcher.ts
1838
1845
  var MUSTACHE_KIND_PSEUDO = /* @__PURE__ */ new Set([
1839
1846
  "m-section",
1840
1847
  "m-inverted",
@@ -1932,13 +1939,85 @@ function parseSelector(raw) {
1932
1939
  const tops = ast.type === "list" ? ast.list : [ast];
1933
1940
  const alts = [];
1934
1941
  for (const top of tops) {
1935
- const segments = [];
1936
- if (!collectSegments(top, "descendant", segments)) return null;
1937
- if (segments.length === 0) return null;
1938
- alts.push(segments);
1942
+ const expanded = expandIs(top);
1943
+ if (expanded === null) return null;
1944
+ for (const exp of expanded) {
1945
+ const segments = [];
1946
+ if (!collectSegments(exp, "descendant", segments)) return null;
1947
+ if (segments.length === 0) return null;
1948
+ alts.push(segments);
1949
+ }
1939
1950
  }
1940
1951
  return alts.length > 0 ? alts : null;
1941
1952
  }
1953
+ function expandIs(ast) {
1954
+ switch (ast.type) {
1955
+ case "list": {
1956
+ const out = [];
1957
+ for (const alt of ast.list) {
1958
+ const expanded = expandIs(alt);
1959
+ if (expanded === null) return null;
1960
+ out.push(...expanded);
1961
+ }
1962
+ return out;
1963
+ }
1964
+ case "complex": {
1965
+ const lefts = expandIs(ast.left);
1966
+ if (lefts === null) return null;
1967
+ const rights = expandIs(ast.right);
1968
+ if (rights === null) return null;
1969
+ const out = [];
1970
+ for (const l2 of lefts) for (const r2 of rights) {
1971
+ out.push({ ...ast, left: l2, right: r2 });
1972
+ }
1973
+ return out;
1974
+ }
1975
+ case "compound": {
1976
+ if (ast.list.length === 1) {
1977
+ const tok = ast.list[0];
1978
+ if (tok.type === "pseudo-class" && tok.name === "is") {
1979
+ if (!tok.subtree) return null;
1980
+ return expandIs(tok.subtree);
1981
+ }
1982
+ }
1983
+ return expandCompoundWithIs(ast.list);
1984
+ }
1985
+ default:
1986
+ if (ast.type === "pseudo-class" && ast.name === "is") {
1987
+ if (!ast.subtree) return null;
1988
+ return expandIs(ast.subtree);
1989
+ }
1990
+ return [ast];
1991
+ }
1992
+ }
1993
+ function expandCompoundWithIs(tokens) {
1994
+ let variants = [[]];
1995
+ for (const tok of tokens) {
1996
+ if (tok.type === "pseudo-class" && tok.name === "is") {
1997
+ if (!tok.subtree) return null;
1998
+ const alts = expandIs(tok.subtree);
1999
+ if (alts === null) return null;
2000
+ const next = [];
2001
+ for (const base of variants) {
2002
+ for (const alt of alts) {
2003
+ if (alt.type === "compound") {
2004
+ next.push([...base, ...alt.list]);
2005
+ } else if (alt.type === "complex" || alt.type === "list" || alt.type === "relative") {
2006
+ return null;
2007
+ } else {
2008
+ next.push([...base, alt]);
2009
+ }
2010
+ }
2011
+ }
2012
+ variants = next;
2013
+ } else {
2014
+ variants = variants.map((v) => [...v, tok]);
2015
+ }
2016
+ }
2017
+ return variants.map(
2018
+ (list) => list.length === 1 ? list[0] : { type: "compound", list }
2019
+ );
2020
+ }
1942
2021
  function collectSegments(ast, combinator, out) {
1943
2022
  if (ast.type === "complex") {
1944
2023
  const mapped = mapCombinator(ast.combinator);
@@ -1956,6 +2035,8 @@ function mapCombinator(c2) {
1956
2035
  const trimmed = c2.trim();
1957
2036
  if (trimmed === "") return "descendant";
1958
2037
  if (trimmed === ">") return "child";
2038
+ if (trimmed === "+") return "adjacent-sibling";
2039
+ if (trimmed === "~") return "general-sibling";
1959
2040
  return null;
1960
2041
  }
1961
2042
  function segmentFromCompound(ast) {
@@ -1966,6 +2047,7 @@ function segmentFromCompound(ast) {
1966
2047
  let rootOnly = false;
1967
2048
  const attributes = [];
1968
2049
  const descendantChecks = [];
2050
+ const selfNegations = [];
1969
2051
  const forbidChange = (requested) => {
1970
2052
  if (kind === void 0) return false;
1971
2053
  if (kind === requested) return false;
@@ -2019,7 +2101,7 @@ function segmentFromCompound(ast) {
2019
2101
  break;
2020
2102
  }
2021
2103
  if (token.name === "not") {
2022
- if (!applyNegatedSubtree(token.subtree, attributes, descendantChecks)) return null;
2104
+ if (!applyNegatedSubtree(token.subtree, attributes, descendantChecks, selfNegations)) return null;
2023
2105
  break;
2024
2106
  }
2025
2107
  if (token.name === "root") {
@@ -2041,7 +2123,7 @@ function segmentFromCompound(ast) {
2041
2123
  }
2042
2124
  const isHtml = kind === "html";
2043
2125
  const finalAttrs = isHtml ? attributes : [];
2044
- return { kind, rootOnly, name, pathRegex, attributes: finalAttrs, descendantChecks, combinator: "descendant" };
2126
+ return { kind, rootOnly, name, pathRegex, attributes: finalAttrs, descendantChecks, selfNegations, combinator: "descendant" };
2045
2127
  }
2046
2128
  function mustacheKindFromMarker(name) {
2047
2129
  switch (name) {
@@ -2106,7 +2188,7 @@ function classConstraint(token, negated) {
2106
2188
  function idConstraint(token, negated) {
2107
2189
  return { name: "id", op: "=", value: token.name, negated };
2108
2190
  }
2109
- function applyNegatedSubtree(subtree, attributes, descendantChecks) {
2191
+ function applyNegatedSubtree(subtree, attributes, descendantChecks, selfNegations) {
2110
2192
  if (!subtree) return false;
2111
2193
  if (subtree.type === "attribute") {
2112
2194
  const c2 = attributeConstraint(subtree, true);
@@ -2123,9 +2205,14 @@ function applyNegatedSubtree(subtree, attributes, descendantChecks) {
2123
2205
  return true;
2124
2206
  }
2125
2207
  if (subtree.type === "pseudo-class" && subtree.name === "has") {
2126
- const sel = subtreeToSelector(subtree.subtree);
2127
- if (!sel) return false;
2128
- descendantChecks.push({ selector: sel, negated: true });
2208
+ const sel2 = subtreeToSelector(subtree.subtree);
2209
+ if (!sel2) return false;
2210
+ descendantChecks.push({ selector: sel2, negated: true });
2211
+ return true;
2212
+ }
2213
+ const sel = subtreeToSelector(subtree);
2214
+ if (sel) {
2215
+ selfNegations.push(sel);
2129
2216
  return true;
2130
2217
  }
2131
2218
  return false;
@@ -2225,11 +2312,20 @@ function checkDescendants(node, checks) {
2225
2312
  return true;
2226
2313
  }
2227
2314
  function hasDescendantMatch(node, selector) {
2228
- for (const child of node.children) {
2229
- if (matchSelector(child, selector).length > 0) return true;
2315
+ for (let i2 = 0; i2 < node.children.length; i2++) {
2316
+ if (matchSelector(node.children[i2], selector, node.children, i2).length > 0) return true;
2230
2317
  }
2231
2318
  return false;
2232
2319
  }
2320
+ function checkSelfNegations(node, negations, rootNode) {
2321
+ for (const sel of negations) {
2322
+ for (const alt of sel) {
2323
+ if (alt.length !== 1) continue;
2324
+ if (nodeMatchesSegment(node, alt[0], rootNode)) return false;
2325
+ }
2326
+ }
2327
+ return true;
2328
+ }
2233
2329
  function matchesName(actual, segment) {
2234
2330
  if (segment.name === null) return true;
2235
2331
  if (actual === null) return false;
@@ -2239,51 +2335,73 @@ function matchesName(actual, segment) {
2239
2335
  function nodeMatchesSegment(node, segment, rootNode) {
2240
2336
  if (segment.rootOnly) {
2241
2337
  if (node !== rootNode) return false;
2242
- return checkDescendants(node, segment.descendantChecks);
2243
- }
2244
- switch (segment.kind) {
2245
- case "html": {
2246
- if (!HTML_ELEMENT_TYPES.has(node.type)) return false;
2247
- if (segment.name !== null) {
2248
- const tagName = getTagName(node)?.toLowerCase();
2249
- if (tagName !== segment.name) return false;
2338
+ return checkDescendants(node, segment.descendantChecks) && checkSelfNegations(node, segment.selfNegations, rootNode);
2339
+ }
2340
+ const baseMatches = (() => {
2341
+ switch (segment.kind) {
2342
+ case "html": {
2343
+ if (!HTML_ELEMENT_TYPES.has(node.type)) return false;
2344
+ if (segment.name !== null) {
2345
+ const tagName = getTagName(node)?.toLowerCase();
2346
+ if (tagName !== segment.name) return false;
2347
+ }
2348
+ return checkAttributes(node, segment.attributes) && checkDescendants(node, segment.descendantChecks);
2250
2349
  }
2251
- return checkAttributes(node, segment.attributes) && checkDescendants(node, segment.descendantChecks);
2252
- }
2253
- case "section":
2254
- if (node.type !== "mustache_section") return false;
2255
- if (!matchesName(getSectionName(node)?.toLowerCase() ?? null, segment)) return false;
2256
- return checkDescendants(node, segment.descendantChecks);
2257
- case "inverted":
2258
- if (node.type !== "mustache_inverted_section") return false;
2259
- if (!matchesName(getSectionName(node)?.toLowerCase() ?? null, segment)) return false;
2260
- return checkDescendants(node, segment.descendantChecks);
2261
- case "variable":
2262
- if (node.type !== "mustache_interpolation") return false;
2263
- if (!matchesName(getInterpolationPath(node)?.toLowerCase() ?? null, segment)) return false;
2264
- return checkDescendants(node, segment.descendantChecks);
2265
- case "raw":
2266
- if (node.type !== "mustache_triple") return false;
2267
- if (!matchesName(getInterpolationPath(node)?.toLowerCase() ?? null, segment)) return false;
2268
- return checkDescendants(node, segment.descendantChecks);
2269
- case "comment":
2270
- if (node.type !== "mustache_comment") return false;
2271
- if (!matchesName(getCommentContent(node)?.toLowerCase() ?? null, segment)) return false;
2272
- return checkDescendants(node, segment.descendantChecks);
2273
- case "partial":
2274
- if (node.type !== "mustache_partial") return false;
2275
- if (!matchesName(getPartialName(node)?.toLowerCase() ?? null, segment)) return false;
2276
- return checkDescendants(node, segment.descendantChecks);
2277
- }
2278
- }
2279
- function checkAncestors(ancestors, segments, segIdx, childCombinator) {
2350
+ case "section":
2351
+ if (node.type !== "mustache_section") return false;
2352
+ if (!matchesName(getSectionName(node)?.toLowerCase() ?? null, segment)) return false;
2353
+ return checkDescendants(node, segment.descendantChecks);
2354
+ case "inverted":
2355
+ if (node.type !== "mustache_inverted_section") return false;
2356
+ if (!matchesName(getSectionName(node)?.toLowerCase() ?? null, segment)) return false;
2357
+ return checkDescendants(node, segment.descendantChecks);
2358
+ case "variable":
2359
+ if (node.type !== "mustache_interpolation") return false;
2360
+ if (!matchesName(getInterpolationPath(node)?.toLowerCase() ?? null, segment)) return false;
2361
+ return checkDescendants(node, segment.descendantChecks);
2362
+ case "raw":
2363
+ if (node.type !== "mustache_triple") return false;
2364
+ if (!matchesName(getInterpolationPath(node)?.toLowerCase() ?? null, segment)) return false;
2365
+ return checkDescendants(node, segment.descendantChecks);
2366
+ case "comment":
2367
+ if (node.type !== "mustache_comment") return false;
2368
+ if (!matchesName(getCommentContent(node)?.toLowerCase() ?? null, segment)) return false;
2369
+ return checkDescendants(node, segment.descendantChecks);
2370
+ case "partial":
2371
+ if (node.type !== "mustache_partial") return false;
2372
+ if (!matchesName(getPartialName(node)?.toLowerCase() ?? null, segment)) return false;
2373
+ return checkDescendants(node, segment.descendantChecks);
2374
+ }
2375
+ })();
2376
+ if (!baseMatches) return false;
2377
+ return checkSelfNegations(node, segment.selfNegations, rootNode);
2378
+ }
2379
+ function checkPrefix(cursor, segments, segIdx, stepCombinator, rootNode) {
2280
2380
  if (segIdx < 0) return true;
2281
2381
  const segment = segments[segIdx];
2382
+ if (stepCombinator === "adjacent-sibling" || stepCombinator === "general-sibling") {
2383
+ for (let i2 = cursor.indexInSiblings - 1; i2 >= 0; i2--) {
2384
+ const sib = cursor.siblings[i2];
2385
+ if (!isMatchableNode(sib)) continue;
2386
+ if (!nodeMatchesSegment(sib, segment, rootNode)) {
2387
+ if (stepCombinator === "adjacent-sibling") return false;
2388
+ continue;
2389
+ }
2390
+ const newCursor = {
2391
+ ancestors: cursor.ancestors,
2392
+ siblings: cursor.siblings,
2393
+ indexInSiblings: i2
2394
+ };
2395
+ if (checkPrefix(newCursor, segments, segIdx - 1, segment.combinator, rootNode)) return true;
2396
+ if (stepCombinator === "adjacent-sibling") return false;
2397
+ }
2398
+ return false;
2399
+ }
2282
2400
  const ancestorKind = ancestorKindForSegment(segment);
2283
2401
  if (ancestorKind === null) return false;
2284
- if (childCombinator === "child") {
2285
- for (let a2 = ancestors.length - 1; a2 >= 0; a2--) {
2286
- const entry = ancestors[a2];
2402
+ if (stepCombinator === "child") {
2403
+ for (let a2 = cursor.ancestors.length - 1; a2 >= 0; a2--) {
2404
+ const entry = cursor.ancestors[a2];
2287
2405
  if (entry.kind !== ancestorKind) {
2288
2406
  if (ancestorKind === "root" && entry.kind === "html") return false;
2289
2407
  continue;
@@ -2291,22 +2409,35 @@ function checkAncestors(ancestors, segments, segIdx, childCombinator) {
2291
2409
  if (!matchesName(entry.name, segment)) return false;
2292
2410
  if (segment.kind === "html" && !checkAttributes(entry.node, segment.attributes)) return false;
2293
2411
  if (!checkDescendants(entry.node, segment.descendantChecks)) return false;
2294
- return checkAncestors(ancestors.slice(0, a2), segments, segIdx - 1, segment.combinator);
2412
+ if (!checkSelfNegations(entry.node, segment.selfNegations, rootNode)) return false;
2413
+ const newCursor = {
2414
+ ancestors: cursor.ancestors.slice(0, a2),
2415
+ siblings: entry.siblings,
2416
+ indexInSiblings: entry.indexInSiblings
2417
+ };
2418
+ return checkPrefix(newCursor, segments, segIdx - 1, segment.combinator, rootNode);
2295
2419
  }
2296
2420
  return false;
2297
2421
  }
2298
- for (let a2 = ancestors.length - 1; a2 >= 0; a2--) {
2299
- const entry = ancestors[a2];
2422
+ for (let a2 = cursor.ancestors.length - 1; a2 >= 0; a2--) {
2423
+ const entry = cursor.ancestors[a2];
2300
2424
  if (entry.kind !== ancestorKind) continue;
2301
2425
  if (!matchesName(entry.name, segment)) continue;
2302
2426
  if (segment.kind === "html" && !checkAttributes(entry.node, segment.attributes)) continue;
2303
2427
  if (!checkDescendants(entry.node, segment.descendantChecks)) continue;
2304
- if (checkAncestors(ancestors.slice(0, a2), segments, segIdx - 1, segment.combinator)) {
2305
- return true;
2306
- }
2428
+ if (!checkSelfNegations(entry.node, segment.selfNegations, rootNode)) continue;
2429
+ const newCursor = {
2430
+ ancestors: cursor.ancestors.slice(0, a2),
2431
+ siblings: entry.siblings,
2432
+ indexInSiblings: entry.indexInSiblings
2433
+ };
2434
+ if (checkPrefix(newCursor, segments, segIdx - 1, segment.combinator, rootNode)) return true;
2307
2435
  }
2308
2436
  return false;
2309
2437
  }
2438
+ function isMatchableNode(node) {
2439
+ return HTML_ELEMENT_TYPES.has(node.type) || node.type === "mustache_section" || node.type === "mustache_inverted_section" || node.type === "mustache_interpolation" || node.type === "mustache_triple" || node.type === "mustache_comment" || node.type === "mustache_partial";
2440
+ }
2310
2441
  function ancestorKindForSegment(segment) {
2311
2442
  if (segment.rootOnly) return "root";
2312
2443
  if (segment.kind === "html") return "html";
@@ -2340,12 +2471,13 @@ function getReportNode(node, rootNode) {
2340
2471
  }
2341
2472
  return node;
2342
2473
  }
2343
- function matchAlternative(rootNode, segments) {
2474
+ function matchAlternative(rootNode, segments, rootSiblings, rootIndexInSiblings) {
2344
2475
  const results = [];
2345
2476
  const lastSegment = segments[segments.length - 1];
2346
- function walk(node, ancestors) {
2477
+ function walk(node, ancestors, siblings, indexInSiblings) {
2347
2478
  if (nodeMatchesSegment(node, lastSegment, rootNode)) {
2348
- if (segments.length === 1 || checkAncestors(ancestors, segments, segments.length - 2, lastSegment.combinator)) {
2479
+ const cursor = { ancestors, siblings, indexInSiblings };
2480
+ if (segments.length === 1 || checkPrefix(cursor, segments, segments.length - 2, lastSegment.combinator, rootNode)) {
2349
2481
  results.push(getReportNode(node, rootNode));
2350
2482
  }
2351
2483
  }
@@ -2354,19 +2486,28 @@ function matchAlternative(rootNode, segments) {
2354
2486
  if (ancestorKind !== null) {
2355
2487
  const name = ancestorKind === "html" ? getTagName(node)?.toLowerCase() : getSectionName(node)?.toLowerCase();
2356
2488
  if (name) {
2357
- newAncestors = [...ancestors, { kind: ancestorKind, name, node }];
2489
+ newAncestors = [...ancestors, { kind: ancestorKind, name, node, siblings, indexInSiblings }];
2358
2490
  }
2359
2491
  }
2360
- for (const child of node.children) walk(child, newAncestors);
2492
+ for (let i2 = 0; i2 < node.children.length; i2++) {
2493
+ walk(node.children[i2], newAncestors, node.children, i2);
2494
+ }
2361
2495
  }
2362
- walk(rootNode, [{ kind: "root", name: "", node: rootNode }]);
2496
+ const rootEntry = {
2497
+ kind: "root",
2498
+ name: "",
2499
+ node: rootNode,
2500
+ siblings: rootSiblings,
2501
+ indexInSiblings: rootIndexInSiblings
2502
+ };
2503
+ walk(rootNode, [rootEntry], rootSiblings, rootIndexInSiblings);
2363
2504
  return results;
2364
2505
  }
2365
- function matchSelector(rootNode, selector) {
2506
+ function matchSelector(rootNode, selector, siblings = [], indexInSiblings = 0) {
2366
2507
  const allResults = [];
2367
2508
  const seen = /* @__PURE__ */ new Set();
2368
2509
  for (const alt of selector) {
2369
- for (const node of matchAlternative(rootNode, alt)) {
2510
+ for (const node of matchAlternative(rootNode, alt, siblings, indexInSiblings)) {
2370
2511
  if (!seen.has(node)) {
2371
2512
  seen.add(node);
2372
2513
  allResults.push(node);
@@ -2376,7 +2517,15 @@ function matchSelector(rootNode, selector) {
2376
2517
  return allResults;
2377
2518
  }
2378
2519
 
2379
- // lsp/server/src/collectErrors.ts
2520
+ // src/core/collectErrors.ts
2521
+ var selectorCache = /* @__PURE__ */ new Map();
2522
+ function parseSelectorCached(raw) {
2523
+ const hit = selectorCache.get(raw);
2524
+ if (hit !== void 0) return hit;
2525
+ const parsed = parseSelector(raw);
2526
+ selectorCache.set(raw, parsed);
2527
+ return parsed;
2528
+ }
2380
2529
  var ERROR_NODE_TYPES = /* @__PURE__ */ new Set([
2381
2530
  "ERROR",
2382
2531
  "mustache_erroneous_section_end",
@@ -2507,7 +2656,7 @@ function collectErrors(tree, rules, customTagNames, customRules) {
2507
2656
  if (disabledRules.has(rule.id)) continue;
2508
2657
  const severity = rule.severity ?? "error";
2509
2658
  if (severity === "off") continue;
2510
- const parsed = parseSelector(rule.selector);
2659
+ const parsed = parseSelectorCached(rule.selector);
2511
2660
  if (!parsed) continue;
2512
2661
  const matches = matchSelector(tree.rootNode, parsed);
2513
2662
  for (const node of matches) {
@@ -2520,20 +2669,32 @@ function collectErrors(tree, rules, customTagNames, customRules) {
2520
2669
  );
2521
2670
  }
2522
2671
 
2672
+ // src/core/diagnostic.ts
2673
+ function toFix(r2) {
2674
+ return { range: [r2.startIndex, r2.endIndex], newText: r2.newText };
2675
+ }
2676
+ function toDiagnostic(err) {
2677
+ const { node } = err;
2678
+ return {
2679
+ line: node.startPosition.row + 1,
2680
+ column: node.startPosition.column + 1,
2681
+ endLine: node.endPosition.row + 1,
2682
+ endColumn: node.endPosition.column + 1,
2683
+ message: err.message,
2684
+ severity: err.severity ?? "error",
2685
+ ruleName: err.ruleName,
2686
+ fix: err.fix && err.fix.length > 0 ? err.fix.map(toFix) : void 0,
2687
+ fixDescription: err.fixDescription
2688
+ };
2689
+ }
2690
+
2523
2691
  // cli/src/check.ts
2524
2692
  function collectErrors2(tree, file, rules, customTagNames, customRules) {
2525
2693
  const errors = collectErrors(tree, rules, customTagNames, customRules);
2526
2694
  return errors.map((error) => ({
2527
2695
  file,
2528
- line: error.node.startPosition.row + 1,
2529
- column: error.node.startPosition.column + 1,
2530
- endLine: error.node.endPosition.row + 1,
2531
- endColumn: error.node.endPosition.column + 1,
2532
- message: error.message,
2533
2696
  nodeText: error.node.text,
2534
- severity: error.severity,
2535
- fix: error.fix,
2536
- fixDescription: error.fixDescription
2697
+ ...toDiagnostic(error)
2537
2698
  }));
2538
2699
  }
2539
2700
  function formatError(error, source) {
@@ -2645,8 +2806,9 @@ function resolveFiles(cliPatterns) {
2645
2806
  function applyFixes(source, errors) {
2646
2807
  const replacements = [];
2647
2808
  for (const error of errors) {
2648
- if (error.fix) {
2649
- replacements.push(...error.fix);
2809
+ if (!error.fix) continue;
2810
+ for (const edit of error.fix) {
2811
+ replacements.push({ startIndex: edit.range[0], endIndex: edit.range[1], newText: edit.newText });
2650
2812
  }
2651
2813
  }
2652
2814
  if (replacements.length === 0) return source;
@@ -2974,7 +3136,7 @@ function getWellformedEdit(textEdit) {
2974
3136
  return textEdit;
2975
3137
  }
2976
3138
 
2977
- // lsp/server/src/formatting/printer.ts
3139
+ // src/core/formatting/printer.ts
2978
3140
  function print(doc, options) {
2979
3141
  const output = [];
2980
3142
  const state = { indentLevel: 0, mode: "break", groupModes: /* @__PURE__ */ new Map() };
@@ -3124,7 +3286,7 @@ function makeIndent(level, options) {
3124
3286
  return options.indentUnit.repeat(level);
3125
3287
  }
3126
3288
 
3127
- // lsp/server/src/formatting/ir.ts
3289
+ // src/core/formatting/ir.ts
3128
3290
  var hardline = { type: "hardline" };
3129
3291
  var softline = { type: "softline" };
3130
3292
  var line = { type: "line" };
@@ -3176,7 +3338,7 @@ function isLine(doc) {
3176
3338
  return typeof doc === "object" && doc.type === "line";
3177
3339
  }
3178
3340
 
3179
- // lsp/server/src/formatting/utils.ts
3341
+ // src/core/formatting/utils.ts
3180
3342
  function normalizeText(text2) {
3181
3343
  return text2.split("\n").map((line2) => line2.replace(/[ \t]+/g, " ").trim()).filter((line2, i2, arr) => line2 || i2 > 0 && i2 < arr.length - 1).join("\n");
3182
3344
  }
@@ -3252,7 +3414,7 @@ function getIgnoreDirective(node) {
3252
3414
  return null;
3253
3415
  }
3254
3416
 
3255
- // lsp/server/src/customCodeTags.ts
3417
+ // src/core/customCodeTags.ts
3256
3418
  function isCodeTag(config) {
3257
3419
  return !!(config.languageAttribute || config.languageDefault);
3258
3420
  }
@@ -3281,7 +3443,7 @@ function getAttributeValue(node, attrName) {
3281
3443
  return null;
3282
3444
  }
3283
3445
 
3284
- // lsp/server/src/formatting/classifier.ts
3446
+ // src/core/formatting/classifier.ts
3285
3447
  var EMPTY_MAP = /* @__PURE__ */ new Map();
3286
3448
  var CSS_DISPLAY_MAP = {
3287
3449
  // Block elements
@@ -3583,7 +3745,7 @@ function shouldTreatAsBlock(node, index, nodes, customTags = EMPTY_MAP) {
3583
3745
  return isHtmlEl && !shouldHtmlElementStayInline(node, index, nodes, customTags) || isMustacheSec && !isInTextFlow(node, index, nodes) || isBlockLevel(node, customTags) && !isInTextFlow(node, index, nodes);
3584
3746
  }
3585
3747
 
3586
- // lsp/server/src/formatting/formatters.ts
3748
+ // src/core/formatting/formatters.ts
3587
3749
  function isAttributeTruthy(value) {
3588
3750
  if (value === null || value === "" || value === "false" || value === "0") {
3589
3751
  return false;
@@ -4475,46 +4637,12 @@ function trimDoc(doc) {
4475
4637
  return doc;
4476
4638
  }
4477
4639
 
4478
- // lsp/server/src/formatting/editorconfig.ts
4479
- var import_editorconfig = require("editorconfig");
4480
- var import_url = require("url");
4481
- function getEditorConfigOptions(uri) {
4482
- try {
4483
- if (!uri.startsWith("file://")) {
4484
- return {};
4485
- }
4486
- const filePath = (0, import_url.fileURLToPath)(uri);
4487
- const config = (0, import_editorconfig.parseSync)(filePath);
4488
- const result = {};
4489
- if (config.indent_style === "space") {
4490
- result.insertSpaces = true;
4491
- } else if (config.indent_style === "tab") {
4492
- result.insertSpaces = false;
4493
- }
4494
- if (typeof config.indent_size === "number") {
4495
- result.tabSize = config.indent_size;
4496
- } else if (config.indent_size === "tab" && typeof config.tab_width === "number") {
4497
- result.tabSize = config.tab_width;
4498
- }
4499
- return result;
4500
- } catch {
4501
- return {};
4502
- }
4503
- }
4504
- function mergeOptions(lspOptions, uri, configFile) {
4505
- let tabSize = lspOptions.tabSize;
4506
- let insertSpaces = lspOptions.insertSpaces;
4507
- if (configFile?.indentSize !== void 0) tabSize = configFile.indentSize;
4508
- const ec = getEditorConfigOptions(uri);
4509
- if (ec.tabSize !== void 0) tabSize = ec.tabSize;
4510
- if (ec.insertSpaces !== void 0) insertSpaces = ec.insertSpaces;
4511
- return { tabSize, insertSpaces };
4512
- }
4640
+ // src/core/formatting/mergeOptions.ts
4513
4641
  function createIndentUnit(options) {
4514
4642
  return options.insertSpaces ? " ".repeat(options.tabSize) : " ";
4515
4643
  }
4516
4644
 
4517
- // lsp/server/src/formatting/index.ts
4645
+ // src/core/formatting/index.ts
4518
4646
  function buildCustomTagMap(customTags) {
4519
4647
  if (!customTags || customTags.length === 0) return void 0;
4520
4648
  const map = /* @__PURE__ */ new Map();
@@ -4524,12 +4652,9 @@ function buildCustomTagMap(customTags) {
4524
4652
  return map;
4525
4653
  }
4526
4654
  function formatDocument2(tree, document, options, params = {}) {
4527
- const { printWidth = 80, embeddedFormatted, mustacheSpaces, noBreakDelimiters, configFile } = params;
4528
- const mergedOptions = mergeOptions(options, document.uri, configFile);
4529
- const indentUnit = createIndentUnit(mergedOptions);
4530
- if (tree.rootNode.hasError) {
4531
- return [];
4532
- }
4655
+ const { printWidth = 80, embeddedFormatted, mustacheSpaces, noBreakDelimiters } = params;
4656
+ const indentUnit = createIndentUnit(options);
4657
+ if (tree.rootNode.hasError) return [];
4533
4658
  const customTagMap = buildCustomTagMap(params.customTags);
4534
4659
  const context = {
4535
4660
  document,
@@ -4547,7 +4672,7 @@ function formatDocument2(tree, document, options, params = {}) {
4547
4672
  return [{ range: fullRange, newText: formatted }];
4548
4673
  }
4549
4674
 
4550
- // lsp/server/src/embeddedRegions.ts
4675
+ // src/core/embeddedRegions.ts
4551
4676
  function getEmbeddedLanguageId(node) {
4552
4677
  if (node.type === "html_style_element") {
4553
4678
  return "css";
@@ -4600,6 +4725,62 @@ function collectEmbeddedRegions(rootNode) {
4600
4725
  return regions;
4601
4726
  }
4602
4727
 
4728
+ // src/core/formatting/embedded.ts
4729
+ var LANGUAGE_TO_PRETTIER_PARSER = {
4730
+ javascript: "babel",
4731
+ typescript: "typescript",
4732
+ css: "css"
4733
+ };
4734
+ async function formatEmbeddedRegions(rootNode, options, prettier) {
4735
+ const result = /* @__PURE__ */ new Map();
4736
+ if (!prettier) return result;
4737
+ const regions = collectEmbeddedRegions(rootNode);
4738
+ if (regions.length === 0) return result;
4739
+ await Promise.all(
4740
+ regions.map(async (region) => {
4741
+ const parser2 = LANGUAGE_TO_PRETTIER_PARSER[region.languageId];
4742
+ if (!parser2) return;
4743
+ try {
4744
+ const formatted = await prettier.format(region.content, {
4745
+ parser: parser2,
4746
+ tabWidth: options.tabSize,
4747
+ useTabs: !options.insertSpaces
4748
+ });
4749
+ result.set(region.startIndex, formatted);
4750
+ } catch {
4751
+ }
4752
+ })
4753
+ );
4754
+ return result;
4755
+ }
4756
+
4757
+ // lsp/server/src/formatting/editorconfig.ts
4758
+ var import_editorconfig = require("editorconfig");
4759
+ var import_url = require("url");
4760
+ function getEditorConfigOptions(uri) {
4761
+ try {
4762
+ if (!uri.startsWith("file://")) {
4763
+ return {};
4764
+ }
4765
+ const filePath = (0, import_url.fileURLToPath)(uri);
4766
+ const config = (0, import_editorconfig.parseSync)(filePath);
4767
+ const result = {};
4768
+ if (config.indent_style === "space") {
4769
+ result.insertSpaces = true;
4770
+ } else if (config.indent_style === "tab") {
4771
+ result.insertSpaces = false;
4772
+ }
4773
+ if (typeof config.indent_size === "number") {
4774
+ result.tabSize = config.indent_size;
4775
+ } else if (config.indent_size === "tab" && typeof config.tab_width === "number") {
4776
+ result.tabSize = config.tab_width;
4777
+ }
4778
+ return result;
4779
+ } catch {
4780
+ return {};
4781
+ }
4782
+ }
4783
+
4603
4784
  // cli/src/format.ts
4604
4785
  var USAGE2 = `Usage: htmlmustache format [options] [patterns...]
4605
4786
 
@@ -4703,15 +4884,9 @@ function resolveSettings(flags, filePath) {
4703
4884
  printWidth,
4704
4885
  mustacheSpaces,
4705
4886
  noBreakDelimiters,
4706
- customTags,
4707
- configFile
4887
+ customTags
4708
4888
  };
4709
4889
  }
4710
- var LANGUAGE_TO_PRETTIER_PARSER = {
4711
- javascript: "babel",
4712
- typescript: "typescript",
4713
- css: "css"
4714
- };
4715
4890
  var prettierModule;
4716
4891
  async function getPrettier() {
4717
4892
  if (prettierModule !== void 0) return prettierModule;
@@ -4723,32 +4898,9 @@ async function getPrettier() {
4723
4898
  return null;
4724
4899
  }
4725
4900
  }
4726
- async function formatEmbeddedRegions(tree, options) {
4727
- const result = /* @__PURE__ */ new Map();
4728
- const prettier = await getPrettier();
4729
- if (!prettier) return result;
4730
- const regions = collectEmbeddedRegions(tree.rootNode);
4731
- if (regions.length === 0) return result;
4732
- await Promise.all(
4733
- regions.map(async (region) => {
4734
- const parser2 = LANGUAGE_TO_PRETTIER_PARSER[region.languageId];
4735
- if (!parser2) return;
4736
- try {
4737
- const formatted = await prettier.format(region.content, {
4738
- parser: parser2,
4739
- tabWidth: options.tabSize,
4740
- useTabs: !options.insertSpaces
4741
- });
4742
- result.set(region.startIndex, formatted);
4743
- } catch {
4744
- }
4745
- })
4746
- );
4747
- return result;
4748
- }
4749
4901
  async function formatSource(source, options, params = {}) {
4750
4902
  const tree = parseDocument(source);
4751
- const embeddedFormatted = await formatEmbeddedRegions(tree, options);
4903
+ const embeddedFormatted = await formatEmbeddedRegions(tree.rootNode, options, await getPrettier());
4752
4904
  const document = TextDocument.create("file:///stdin", "htmlmustache", 1, source);
4753
4905
  const edits = formatDocument2(tree, document, options, {
4754
4906
  ...params,