@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 +1 -0
- package/cli/out/main.js +185 -7
- package/package.json +1 -1
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) {
|