@reteps/tree-sitter-htmlmustache 0.5.1 → 0.5.2

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.
package/README.md CHANGED
@@ -244,6 +244,7 @@ Additionally, the following rules are configurable. Set their severities (`"erro
244
244
  | `duplicateAttributes` | `error` | Detects duplicate HTML attributes on the same element |
245
245
  | `unescapedEntities` | `warning` | Flags unescaped `&` and `>` characters in text content |
246
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 |
247
248
 
248
249
  <!-- RULES_TABLE_END -->
249
250
 
package/cli/out/main.js CHANGED
@@ -581,6 +581,11 @@ var RULES = [
581
581
  name: "preferMustacheComments",
582
582
  defaultSeverity: "off",
583
583
  description: "Suggests replacing HTML comments with mustache comments"
584
+ },
585
+ {
586
+ name: "unrecognizedHtmlTags",
587
+ defaultSeverity: "error",
588
+ description: "Flags HTML tags that are not standard HTML elements or valid custom elements"
584
589
  }
585
590
  ];
586
591
  var KNOWN_RULE_NAMES = new Set(RULES.map((r) => r.name));
@@ -1361,6 +1366,165 @@ function checkHtmlComments(rootNode) {
1361
1366
  visit(rootNode);
1362
1367
  return errors;
1363
1368
  }
1369
+ var KNOWN_HTML_TAGS = /* @__PURE__ */ new Set([
1370
+ // Void elements
1371
+ "area",
1372
+ "base",
1373
+ "basefont",
1374
+ "bgsound",
1375
+ "br",
1376
+ "col",
1377
+ "command",
1378
+ "embed",
1379
+ "frame",
1380
+ "hr",
1381
+ "image",
1382
+ "img",
1383
+ "input",
1384
+ "isindex",
1385
+ "keygen",
1386
+ "link",
1387
+ "menuitem",
1388
+ "meta",
1389
+ "nextid",
1390
+ "param",
1391
+ "source",
1392
+ "track",
1393
+ "wbr",
1394
+ // Non-void elements
1395
+ "a",
1396
+ "abbr",
1397
+ "address",
1398
+ "article",
1399
+ "aside",
1400
+ "audio",
1401
+ "b",
1402
+ "bdi",
1403
+ "bdo",
1404
+ "blockquote",
1405
+ "body",
1406
+ "button",
1407
+ "canvas",
1408
+ "caption",
1409
+ "cite",
1410
+ "code",
1411
+ "colgroup",
1412
+ "data",
1413
+ "datalist",
1414
+ "dd",
1415
+ "del",
1416
+ "details",
1417
+ "dfn",
1418
+ "dialog",
1419
+ "div",
1420
+ "dl",
1421
+ "dt",
1422
+ "em",
1423
+ "fieldset",
1424
+ "figcaption",
1425
+ "figure",
1426
+ "footer",
1427
+ "form",
1428
+ "h1",
1429
+ "h2",
1430
+ "h3",
1431
+ "h4",
1432
+ "h5",
1433
+ "h6",
1434
+ "head",
1435
+ "header",
1436
+ "hgroup",
1437
+ "html",
1438
+ "i",
1439
+ "iframe",
1440
+ "ins",
1441
+ "kbd",
1442
+ "label",
1443
+ "legend",
1444
+ "li",
1445
+ "main",
1446
+ "map",
1447
+ "mark",
1448
+ "math",
1449
+ "menu",
1450
+ "meter",
1451
+ "nav",
1452
+ "noscript",
1453
+ "object",
1454
+ "ol",
1455
+ "optgroup",
1456
+ "option",
1457
+ "output",
1458
+ "p",
1459
+ "picture",
1460
+ "pre",
1461
+ "progress",
1462
+ "q",
1463
+ "rb",
1464
+ "rp",
1465
+ "rt",
1466
+ "rtc",
1467
+ "ruby",
1468
+ "s",
1469
+ "samp",
1470
+ "script",
1471
+ "search",
1472
+ "section",
1473
+ "select",
1474
+ "slot",
1475
+ "small",
1476
+ "span",
1477
+ "strong",
1478
+ "style",
1479
+ "sub",
1480
+ "summary",
1481
+ "sup",
1482
+ "svg",
1483
+ "table",
1484
+ "tbody",
1485
+ "td",
1486
+ "template",
1487
+ "textarea",
1488
+ "tfoot",
1489
+ "th",
1490
+ "thead",
1491
+ "time",
1492
+ "title",
1493
+ "tr",
1494
+ "u",
1495
+ "ul",
1496
+ "var",
1497
+ "video"
1498
+ ]);
1499
+ function checkUnrecognizedHtmlTags(rootNode, customTagNames) {
1500
+ const errors = [];
1501
+ const customSet = customTagNames ? new Set(customTagNames.map((n) => n.toLowerCase())) : void 0;
1502
+ function visit(node) {
1503
+ if (node.type === "html_element" || node.type === "html_self_closing_tag") {
1504
+ const tagNameNode = node.type === "html_self_closing_tag" ? node.children.find((c) => c.type === "html_tag_name") : node.children.find((c) => c.type === "html_start_tag")?.children.find((c) => c.type === "html_tag_name");
1505
+ const tagName = tagNameNode?.text.toLowerCase();
1506
+ if (tagName === "svg" || tagName === "math") return;
1507
+ }
1508
+ if (node.type === "html_start_tag" || node.type === "html_self_closing_tag") {
1509
+ const tagNameNode = node.children.find((c) => c.type === "html_tag_name");
1510
+ if (tagNameNode) {
1511
+ const tagName = tagNameNode.text.toLowerCase();
1512
+ if (!KNOWN_HTML_TAGS.has(tagName) && !customSet?.has(tagName)) {
1513
+ errors.push({
1514
+ node: tagNameNode,
1515
+ message: `Unrecognized HTML tag: <${tagNameNode.text}>`
1516
+ });
1517
+ }
1518
+ }
1519
+ return;
1520
+ }
1521
+ for (const child of node.children) {
1522
+ visit(child);
1523
+ }
1524
+ }
1525
+ visit(rootNode);
1526
+ return errors;
1527
+ }
1364
1528
  function checkDuplicateAttributes(rootNode) {
1365
1529
  const errors = [];
1366
1530
  function visit(node) {
@@ -1454,7 +1618,7 @@ function collectDisabledRules(rootNode) {
1454
1618
  walk(rootNode);
1455
1619
  return disabled;
1456
1620
  }
1457
- function collectErrors(tree, rules) {
1621
+ function collectErrors(tree, rules, customTagNames) {
1458
1622
  const errors = [];
1459
1623
  const cursor = tree.walk();
1460
1624
  function visit() {
@@ -1496,7 +1660,8 @@ function collectErrors(tree, rules) {
1496
1660
  { rule: "selfClosingNonVoidTags", errors: () => checkSelfClosingNonVoidTags(tree.rootNode) },
1497
1661
  { rule: "duplicateAttributes", errors: () => checkDuplicateAttributes(tree.rootNode) },
1498
1662
  { rule: "unescapedEntities", errors: () => checkUnescapedEntities(tree.rootNode) },
1499
- { rule: "preferMustacheComments", errors: () => checkHtmlComments(tree.rootNode) }
1663
+ { rule: "preferMustacheComments", errors: () => checkHtmlComments(tree.rootNode) },
1664
+ { rule: "unrecognizedHtmlTags", errors: () => checkUnrecognizedHtmlTags(tree.rootNode, customTagNames) }
1500
1665
  ];
1501
1666
  for (const { rule, errors: getErrors } of ruleChecks) {
1502
1667
  const severity = resolveRuleSeverity(effectiveRules, rule);
@@ -1517,8 +1682,8 @@ function collectErrors(tree, rules) {
1517
1682
  }
1518
1683
 
1519
1684
  // cli/src/check.ts
1520
- function collectErrors2(tree, file, rules) {
1521
- const errors = collectErrors(tree, rules);
1685
+ function collectErrors2(tree, file, rules, customTagNames) {
1686
+ const errors = collectErrors(tree, rules, customTagNames);
1522
1687
  return errors.map((error) => ({
1523
1688
  file,
1524
1689
  line: error.node.startPosition.row + 1,
@@ -1702,12 +1867,13 @@ async function run(args) {
1702
1867
  const cwd = process.cwd();
1703
1868
  const errorOutput = [];
1704
1869
  const rules = config?.rules;
1870
+ const customTagNames = config?.customTags?.map((t) => t.name);
1705
1871
  for (const file of files) {
1706
1872
  const displayPath = import_node_path.default.relative(cwd, file) || file;
1707
1873
  let source = import_node_fs.default.readFileSync(file, "utf-8");
1708
1874
  if (fixMode) {
1709
1875
  const tree2 = parseDocument(source);
1710
- const errors2 = collectErrors2(tree2, displayPath, rules);
1876
+ const errors2 = collectErrors2(tree2, displayPath, rules, customTagNames);
1711
1877
  const fixed = applyFixes(source, errors2);
1712
1878
  if (fixed !== source) {
1713
1879
  import_node_fs.default.writeFileSync(file, fixed, "utf-8");
@@ -1715,7 +1881,7 @@ async function run(args) {
1715
1881
  }
1716
1882
  }
1717
1883
  const tree = parseDocument(source);
1718
- const errors = collectErrors2(tree, displayPath, rules);
1884
+ const errors = collectErrors2(tree, displayPath, rules, customTagNames);
1719
1885
  const fileErrors = errors.filter((e) => e.severity !== "warning");
1720
1886
  const fileWarnings = errors.filter((e) => e.severity === "warning");
1721
1887
  if (errors.length > 0) {
@@ -2377,6 +2543,7 @@ function getCSSDisplay(node, customTags = EMPTY_MAP) {
2377
2543
  if (config) {
2378
2544
  if (config.display) return config.display;
2379
2545
  if (isCodeTag(config)) return "block";
2546
+ return "inline-block";
2380
2547
  }
2381
2548
  return CSS_DISPLAY_MAP[lower] ?? "inline";
2382
2549
  }
@@ -2748,7 +2915,7 @@ function formatHtmlElement(node, context, forceInline = false) {
2748
2915
  parts.push(text(child.text));
2749
2916
  }
2750
2917
  }
2751
- } else if (!isBlock && (!hasHtmlElementChildren || forceInline && !contentNodes.some(
2918
+ } else if (!isBlock && (!hasHtmlElementChildren || forceInline && display !== "inline-block" && !contentNodes.some(
2752
2919
  (child) => isRawContentElement(child) || isBlockLevel(child, tags)
2753
2920
  ))) {
2754
2921
  if (!forceInline && startTag && startTagHasAttributes(startTag)) {
@@ -3393,6 +3560,17 @@ function formatBlockChildren(nodes, context) {
3393
3560
  }
3394
3561
  }
3395
3562
  }
3563
+ if (node.type === "html_element" && currentLine.length > 0) {
3564
+ const tagName = getTagName(node);
3565
+ if (tagName?.toLowerCase() === "br") {
3566
+ const lineContent = trimDoc(flushCurrentLine());
3567
+ if (hasDocContent(lineContent)) {
3568
+ lines.push({ doc: lineContent, blankLineBefore: blankLineBeforeCurrentLine });
3569
+ blankLineBeforeCurrentLine = false;
3570
+ }
3571
+ currentLine = [];
3572
+ }
3573
+ }
3396
3574
  lastNodeEnd = node.endIndex;
3397
3575
  }
3398
3576
  if (inIgnoreRegion && nodes.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reteps/tree-sitter-htmlmustache",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "HTML with Mustache/Handlebars template syntax grammar for tree-sitter",
5
5
  "repository": {
6
6
  "type": "git",