@reteps/tree-sitter-htmlmustache 0.5.1 → 0.6.0
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 +199 -16
- 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));
|
|
@@ -703,9 +708,12 @@ function validateConfig(raw) {
|
|
|
703
708
|
config.mustacheSpaces = obj.mustacheSpaces;
|
|
704
709
|
}
|
|
705
710
|
if (Array.isArray(obj.noBreakDelimiters)) {
|
|
706
|
-
const items =
|
|
707
|
-
|
|
708
|
-
|
|
711
|
+
const items = [];
|
|
712
|
+
for (const entry of obj.noBreakDelimiters) {
|
|
713
|
+
if (entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.start === "string" && entry.start !== "" && typeof entry.end === "string" && entry.end !== "") {
|
|
714
|
+
items.push({ start: entry.start, end: entry.end });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
709
717
|
if (items.length > 0) config.noBreakDelimiters = items;
|
|
710
718
|
}
|
|
711
719
|
if (Array.isArray(obj.include)) {
|
|
@@ -1361,6 +1369,165 @@ function checkHtmlComments(rootNode) {
|
|
|
1361
1369
|
visit(rootNode);
|
|
1362
1370
|
return errors;
|
|
1363
1371
|
}
|
|
1372
|
+
var KNOWN_HTML_TAGS = /* @__PURE__ */ new Set([
|
|
1373
|
+
// Void elements
|
|
1374
|
+
"area",
|
|
1375
|
+
"base",
|
|
1376
|
+
"basefont",
|
|
1377
|
+
"bgsound",
|
|
1378
|
+
"br",
|
|
1379
|
+
"col",
|
|
1380
|
+
"command",
|
|
1381
|
+
"embed",
|
|
1382
|
+
"frame",
|
|
1383
|
+
"hr",
|
|
1384
|
+
"image",
|
|
1385
|
+
"img",
|
|
1386
|
+
"input",
|
|
1387
|
+
"isindex",
|
|
1388
|
+
"keygen",
|
|
1389
|
+
"link",
|
|
1390
|
+
"menuitem",
|
|
1391
|
+
"meta",
|
|
1392
|
+
"nextid",
|
|
1393
|
+
"param",
|
|
1394
|
+
"source",
|
|
1395
|
+
"track",
|
|
1396
|
+
"wbr",
|
|
1397
|
+
// Non-void elements
|
|
1398
|
+
"a",
|
|
1399
|
+
"abbr",
|
|
1400
|
+
"address",
|
|
1401
|
+
"article",
|
|
1402
|
+
"aside",
|
|
1403
|
+
"audio",
|
|
1404
|
+
"b",
|
|
1405
|
+
"bdi",
|
|
1406
|
+
"bdo",
|
|
1407
|
+
"blockquote",
|
|
1408
|
+
"body",
|
|
1409
|
+
"button",
|
|
1410
|
+
"canvas",
|
|
1411
|
+
"caption",
|
|
1412
|
+
"cite",
|
|
1413
|
+
"code",
|
|
1414
|
+
"colgroup",
|
|
1415
|
+
"data",
|
|
1416
|
+
"datalist",
|
|
1417
|
+
"dd",
|
|
1418
|
+
"del",
|
|
1419
|
+
"details",
|
|
1420
|
+
"dfn",
|
|
1421
|
+
"dialog",
|
|
1422
|
+
"div",
|
|
1423
|
+
"dl",
|
|
1424
|
+
"dt",
|
|
1425
|
+
"em",
|
|
1426
|
+
"fieldset",
|
|
1427
|
+
"figcaption",
|
|
1428
|
+
"figure",
|
|
1429
|
+
"footer",
|
|
1430
|
+
"form",
|
|
1431
|
+
"h1",
|
|
1432
|
+
"h2",
|
|
1433
|
+
"h3",
|
|
1434
|
+
"h4",
|
|
1435
|
+
"h5",
|
|
1436
|
+
"h6",
|
|
1437
|
+
"head",
|
|
1438
|
+
"header",
|
|
1439
|
+
"hgroup",
|
|
1440
|
+
"html",
|
|
1441
|
+
"i",
|
|
1442
|
+
"iframe",
|
|
1443
|
+
"ins",
|
|
1444
|
+
"kbd",
|
|
1445
|
+
"label",
|
|
1446
|
+
"legend",
|
|
1447
|
+
"li",
|
|
1448
|
+
"main",
|
|
1449
|
+
"map",
|
|
1450
|
+
"mark",
|
|
1451
|
+
"math",
|
|
1452
|
+
"menu",
|
|
1453
|
+
"meter",
|
|
1454
|
+
"nav",
|
|
1455
|
+
"noscript",
|
|
1456
|
+
"object",
|
|
1457
|
+
"ol",
|
|
1458
|
+
"optgroup",
|
|
1459
|
+
"option",
|
|
1460
|
+
"output",
|
|
1461
|
+
"p",
|
|
1462
|
+
"picture",
|
|
1463
|
+
"pre",
|
|
1464
|
+
"progress",
|
|
1465
|
+
"q",
|
|
1466
|
+
"rb",
|
|
1467
|
+
"rp",
|
|
1468
|
+
"rt",
|
|
1469
|
+
"rtc",
|
|
1470
|
+
"ruby",
|
|
1471
|
+
"s",
|
|
1472
|
+
"samp",
|
|
1473
|
+
"script",
|
|
1474
|
+
"search",
|
|
1475
|
+
"section",
|
|
1476
|
+
"select",
|
|
1477
|
+
"slot",
|
|
1478
|
+
"small",
|
|
1479
|
+
"span",
|
|
1480
|
+
"strong",
|
|
1481
|
+
"style",
|
|
1482
|
+
"sub",
|
|
1483
|
+
"summary",
|
|
1484
|
+
"sup",
|
|
1485
|
+
"svg",
|
|
1486
|
+
"table",
|
|
1487
|
+
"tbody",
|
|
1488
|
+
"td",
|
|
1489
|
+
"template",
|
|
1490
|
+
"textarea",
|
|
1491
|
+
"tfoot",
|
|
1492
|
+
"th",
|
|
1493
|
+
"thead",
|
|
1494
|
+
"time",
|
|
1495
|
+
"title",
|
|
1496
|
+
"tr",
|
|
1497
|
+
"u",
|
|
1498
|
+
"ul",
|
|
1499
|
+
"var",
|
|
1500
|
+
"video"
|
|
1501
|
+
]);
|
|
1502
|
+
function checkUnrecognizedHtmlTags(rootNode, customTagNames) {
|
|
1503
|
+
const errors = [];
|
|
1504
|
+
const customSet = customTagNames ? new Set(customTagNames.map((n) => n.toLowerCase())) : void 0;
|
|
1505
|
+
function visit(node) {
|
|
1506
|
+
if (node.type === "html_element" || node.type === "html_self_closing_tag") {
|
|
1507
|
+
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");
|
|
1508
|
+
const tagName = tagNameNode?.text.toLowerCase();
|
|
1509
|
+
if (tagName === "svg" || tagName === "math") return;
|
|
1510
|
+
}
|
|
1511
|
+
if (node.type === "html_start_tag" || node.type === "html_self_closing_tag") {
|
|
1512
|
+
const tagNameNode = node.children.find((c) => c.type === "html_tag_name");
|
|
1513
|
+
if (tagNameNode) {
|
|
1514
|
+
const tagName = tagNameNode.text.toLowerCase();
|
|
1515
|
+
if (!KNOWN_HTML_TAGS.has(tagName) && !customSet?.has(tagName)) {
|
|
1516
|
+
errors.push({
|
|
1517
|
+
node: tagNameNode,
|
|
1518
|
+
message: `Unrecognized HTML tag: <${tagNameNode.text}>`
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
for (const child of node.children) {
|
|
1525
|
+
visit(child);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
visit(rootNode);
|
|
1529
|
+
return errors;
|
|
1530
|
+
}
|
|
1364
1531
|
function checkDuplicateAttributes(rootNode) {
|
|
1365
1532
|
const errors = [];
|
|
1366
1533
|
function visit(node) {
|
|
@@ -1454,7 +1621,7 @@ function collectDisabledRules(rootNode) {
|
|
|
1454
1621
|
walk(rootNode);
|
|
1455
1622
|
return disabled;
|
|
1456
1623
|
}
|
|
1457
|
-
function collectErrors(tree, rules) {
|
|
1624
|
+
function collectErrors(tree, rules, customTagNames) {
|
|
1458
1625
|
const errors = [];
|
|
1459
1626
|
const cursor = tree.walk();
|
|
1460
1627
|
function visit() {
|
|
@@ -1496,7 +1663,8 @@ function collectErrors(tree, rules) {
|
|
|
1496
1663
|
{ rule: "selfClosingNonVoidTags", errors: () => checkSelfClosingNonVoidTags(tree.rootNode) },
|
|
1497
1664
|
{ rule: "duplicateAttributes", errors: () => checkDuplicateAttributes(tree.rootNode) },
|
|
1498
1665
|
{ rule: "unescapedEntities", errors: () => checkUnescapedEntities(tree.rootNode) },
|
|
1499
|
-
{ rule: "preferMustacheComments", errors: () => checkHtmlComments(tree.rootNode) }
|
|
1666
|
+
{ rule: "preferMustacheComments", errors: () => checkHtmlComments(tree.rootNode) },
|
|
1667
|
+
{ rule: "unrecognizedHtmlTags", errors: () => checkUnrecognizedHtmlTags(tree.rootNode, customTagNames) }
|
|
1500
1668
|
];
|
|
1501
1669
|
for (const { rule, errors: getErrors } of ruleChecks) {
|
|
1502
1670
|
const severity = resolveRuleSeverity(effectiveRules, rule);
|
|
@@ -1517,8 +1685,8 @@ function collectErrors(tree, rules) {
|
|
|
1517
1685
|
}
|
|
1518
1686
|
|
|
1519
1687
|
// cli/src/check.ts
|
|
1520
|
-
function collectErrors2(tree, file, rules) {
|
|
1521
|
-
const errors = collectErrors(tree, rules);
|
|
1688
|
+
function collectErrors2(tree, file, rules, customTagNames) {
|
|
1689
|
+
const errors = collectErrors(tree, rules, customTagNames);
|
|
1522
1690
|
return errors.map((error) => ({
|
|
1523
1691
|
file,
|
|
1524
1692
|
line: error.node.startPosition.row + 1,
|
|
@@ -1702,12 +1870,13 @@ async function run(args) {
|
|
|
1702
1870
|
const cwd = process.cwd();
|
|
1703
1871
|
const errorOutput = [];
|
|
1704
1872
|
const rules = config?.rules;
|
|
1873
|
+
const customTagNames = config?.customTags?.map((t) => t.name);
|
|
1705
1874
|
for (const file of files) {
|
|
1706
1875
|
const displayPath = import_node_path.default.relative(cwd, file) || file;
|
|
1707
1876
|
let source = import_node_fs.default.readFileSync(file, "utf-8");
|
|
1708
1877
|
if (fixMode) {
|
|
1709
1878
|
const tree2 = parseDocument(source);
|
|
1710
|
-
const errors2 = collectErrors2(tree2, displayPath, rules);
|
|
1879
|
+
const errors2 = collectErrors2(tree2, displayPath, rules, customTagNames);
|
|
1711
1880
|
const fixed = applyFixes(source, errors2);
|
|
1712
1881
|
if (fixed !== source) {
|
|
1713
1882
|
import_node_fs.default.writeFileSync(file, fixed, "utf-8");
|
|
@@ -1715,7 +1884,7 @@ async function run(args) {
|
|
|
1715
1884
|
}
|
|
1716
1885
|
}
|
|
1717
1886
|
const tree = parseDocument(source);
|
|
1718
|
-
const errors = collectErrors2(tree, displayPath, rules);
|
|
1887
|
+
const errors = collectErrors2(tree, displayPath, rules, customTagNames);
|
|
1719
1888
|
const fileErrors = errors.filter((e) => e.severity !== "warning");
|
|
1720
1889
|
const fileWarnings = errors.filter((e) => e.severity === "warning");
|
|
1721
1890
|
if (errors.length > 0) {
|
|
@@ -2377,6 +2546,7 @@ function getCSSDisplay(node, customTags = EMPTY_MAP) {
|
|
|
2377
2546
|
if (config) {
|
|
2378
2547
|
if (config.display) return config.display;
|
|
2379
2548
|
if (isCodeTag(config)) return "block";
|
|
2549
|
+
return "inline-block";
|
|
2380
2550
|
}
|
|
2381
2551
|
return CSS_DISPLAY_MAP[lower] ?? "inline";
|
|
2382
2552
|
}
|
|
@@ -2748,7 +2918,7 @@ function formatHtmlElement(node, context, forceInline = false) {
|
|
|
2748
2918
|
parts.push(text(child.text));
|
|
2749
2919
|
}
|
|
2750
2920
|
}
|
|
2751
|
-
} else if (!isBlock && (!hasHtmlElementChildren || forceInline && !contentNodes.some(
|
|
2921
|
+
} else if (!isBlock && (!hasHtmlElementChildren || forceInline && display !== "inline-block" && !contentNodes.some(
|
|
2752
2922
|
(child) => isRawContentElement(child) || isBlockLevel(child, tags)
|
|
2753
2923
|
))) {
|
|
2754
2924
|
if (!forceInline && startTag && startTagHasAttributes(startTag)) {
|
|
@@ -3113,7 +3283,9 @@ function textWords(str) {
|
|
|
3113
3283
|
}
|
|
3114
3284
|
function collapseDelimitedRegions(parts, delimiters) {
|
|
3115
3285
|
if (delimiters.length === 0) return parts;
|
|
3116
|
-
const sorted = [...delimiters].sort(
|
|
3286
|
+
const sorted = [...delimiters].sort(
|
|
3287
|
+
(a, b) => Math.max(b.start.length, b.end.length) - Math.max(a.start.length, a.end.length)
|
|
3288
|
+
);
|
|
3117
3289
|
const result = [...parts];
|
|
3118
3290
|
let activeDelimiter = null;
|
|
3119
3291
|
for (let i = 0; i < result.length; i++) {
|
|
@@ -3121,10 +3293,10 @@ function collapseDelimitedRegions(parts, delimiters) {
|
|
|
3121
3293
|
if (typeof part === "string") {
|
|
3122
3294
|
if (activeDelimiter === null) {
|
|
3123
3295
|
for (const delim of sorted) {
|
|
3124
|
-
const
|
|
3125
|
-
if (
|
|
3126
|
-
const afterOpen =
|
|
3127
|
-
const closeIdx = part.indexOf(delim, afterOpen);
|
|
3296
|
+
const startIdx = part.indexOf(delim.start);
|
|
3297
|
+
if (startIdx >= 0) {
|
|
3298
|
+
const afterOpen = startIdx + delim.start.length;
|
|
3299
|
+
const closeIdx = part.indexOf(delim.end, afterOpen);
|
|
3128
3300
|
if (closeIdx >= 0) {
|
|
3129
3301
|
continue;
|
|
3130
3302
|
}
|
|
@@ -3133,7 +3305,7 @@ function collapseDelimitedRegions(parts, delimiters) {
|
|
|
3133
3305
|
}
|
|
3134
3306
|
}
|
|
3135
3307
|
} else {
|
|
3136
|
-
if (part.includes(activeDelimiter)) {
|
|
3308
|
+
if (part.includes(activeDelimiter.end)) {
|
|
3137
3309
|
activeDelimiter = null;
|
|
3138
3310
|
}
|
|
3139
3311
|
}
|
|
@@ -3393,6 +3565,17 @@ function formatBlockChildren(nodes, context) {
|
|
|
3393
3565
|
}
|
|
3394
3566
|
}
|
|
3395
3567
|
}
|
|
3568
|
+
if (node.type === "html_element" && currentLine.length > 0) {
|
|
3569
|
+
const tagName = getTagName(node);
|
|
3570
|
+
if (tagName?.toLowerCase() === "br") {
|
|
3571
|
+
const lineContent = trimDoc(flushCurrentLine());
|
|
3572
|
+
if (hasDocContent(lineContent)) {
|
|
3573
|
+
lines.push({ doc: lineContent, blankLineBefore: blankLineBeforeCurrentLine });
|
|
3574
|
+
blankLineBeforeCurrentLine = false;
|
|
3575
|
+
}
|
|
3576
|
+
currentLine = [];
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3396
3579
|
lastNodeEnd = node.endIndex;
|
|
3397
3580
|
}
|
|
3398
3581
|
if (inIgnoreRegion && nodes.length > 0) {
|