@wcstack/lint 1.25.0 → 1.26.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/dist/cli.cjs +187 -72
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -418,15 +418,17 @@ function extractAttribute(tagContent, attrName) {
|
|
|
418
418
|
var RESERVED_STREAMS_KEY = "$streams";
|
|
419
419
|
var RESERVED_COMMAND_TOKENS_KEY = "$commandTokens";
|
|
420
420
|
var RESERVED_EVENT_TOKENS_KEY = "$eventTokens";
|
|
421
|
+
var RESERVED_LIST_KEYS_KEY = "$listKeys";
|
|
421
422
|
function analyzeStatePaths(scriptContent, stateName = "default") {
|
|
422
423
|
const objectContent = extractDefaultExportObject(scriptContent);
|
|
423
424
|
if (!objectContent) return [];
|
|
424
425
|
const paths = [];
|
|
425
426
|
const topLevelProps = parseTopLevelProperties(objectContent);
|
|
426
427
|
const pendingStreamValues = [];
|
|
428
|
+
const pendingListKeys = [];
|
|
427
429
|
for (const prop of topLevelProps) {
|
|
428
430
|
if (prop.name.startsWith("$")) {
|
|
429
|
-
collectReservedKeyPaths(prop, paths, pendingStreamValues, stateName);
|
|
431
|
+
collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys, stateName);
|
|
430
432
|
continue;
|
|
431
433
|
}
|
|
432
434
|
if (prop.kind === "method") {
|
|
@@ -434,7 +436,9 @@ function analyzeStatePaths(scriptContent, stateName = "default") {
|
|
|
434
436
|
continue;
|
|
435
437
|
}
|
|
436
438
|
if (prop.kind === "getter") {
|
|
437
|
-
paths.
|
|
439
|
+
if (!paths.some((p) => p.stateName === stateName && p.path === prop.name)) {
|
|
440
|
+
paths.push({ path: prop.name, kind: "computed", stateName });
|
|
441
|
+
}
|
|
438
442
|
continue;
|
|
439
443
|
}
|
|
440
444
|
pushDataPropertyPaths(prop, paths, stateName);
|
|
@@ -443,9 +447,12 @@ function analyzeStatePaths(scriptContent, stateName = "default") {
|
|
|
443
447
|
if (paths.some((p) => p.stateName === stateName && p.path === streamValue.name)) continue;
|
|
444
448
|
pushDataPropertyPaths(streamValue, paths, stateName);
|
|
445
449
|
}
|
|
450
|
+
for (const listKeyEntry of pendingListKeys) {
|
|
451
|
+
pushListKeyPaths(listKeyEntry, paths, stateName);
|
|
452
|
+
}
|
|
446
453
|
return paths;
|
|
447
454
|
}
|
|
448
|
-
function collectReservedKeyPaths(prop, paths, pendingStreamValues, stateName) {
|
|
455
|
+
function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys, stateName) {
|
|
449
456
|
if (prop.name === RESERVED_STREAMS_KEY && prop.kind === "data" && prop.value && isObjectLiteral(prop.value)) {
|
|
450
457
|
const entries = parseTopLevelProperties(extractObjectContent(prop.value));
|
|
451
458
|
for (const entry of entries) {
|
|
@@ -474,6 +481,36 @@ function collectReservedKeyPaths(prop, paths, pendingStreamValues, stateName) {
|
|
|
474
481
|
}
|
|
475
482
|
return;
|
|
476
483
|
}
|
|
484
|
+
if (prop.name === RESERVED_LIST_KEYS_KEY && prop.kind === "data" && prop.value && isObjectLiteral(prop.value)) {
|
|
485
|
+
for (const entry of parseTopLevelProperties(extractObjectContent(prop.value))) {
|
|
486
|
+
if (entry.kind !== "data") continue;
|
|
487
|
+
pendingListKeys.push(entry);
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function pushListKeyPaths(entry, paths, stateName) {
|
|
493
|
+
const listPath = entry.name;
|
|
494
|
+
const segments = listPath.split(".");
|
|
495
|
+
if (listPath.length === 0 || segments.some((s) => s.length === 0) || segments[segments.length - 1] === "*") {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const has = (path) => paths.some((p) => p.stateName === stateName && p.path === path);
|
|
499
|
+
if (!has(listPath)) paths.push({ path: listPath, kind: "data", typeHint: "array", stateName });
|
|
500
|
+
if (!has(`${listPath}.*`)) paths.push({ path: `${listPath}.*`, kind: "list", stateName });
|
|
501
|
+
if (!has(`${listPath}.length`)) {
|
|
502
|
+
paths.push({ path: `${listPath}.length`, kind: "data", typeHint: "number", stateName });
|
|
503
|
+
}
|
|
504
|
+
const keyField = extractStringLiteralValue(entry.value);
|
|
505
|
+
if (keyField === null || keyField.includes(".") || keyField.includes("*")) return;
|
|
506
|
+
if (!has(`${listPath}.*.${keyField}`)) {
|
|
507
|
+
paths.push({ path: `${listPath}.*.${keyField}`, kind: "data", stateName });
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function extractStringLiteralValue(value) {
|
|
511
|
+
if (!value) return null;
|
|
512
|
+
const match = value.trim().match(/^["']([^"'\\]*)["']$/);
|
|
513
|
+
return match && match[1].length > 0 ? match[1] : null;
|
|
477
514
|
}
|
|
478
515
|
function findStreamInitialProperty(entryValue) {
|
|
479
516
|
const defProps = parseTopLevelProperties(extractObjectContent(entryValue));
|
|
@@ -489,46 +526,32 @@ function extractStringArrayItems(value) {
|
|
|
489
526
|
}
|
|
490
527
|
return items;
|
|
491
528
|
}
|
|
529
|
+
var MAX_OBJECT_NEST_DEPTH = 5;
|
|
492
530
|
function pushDataPropertyPaths(prop, paths, stateName) {
|
|
493
|
-
|
|
531
|
+
pushDataPropertyPathsAt(prop.name, prop, paths, stateName, 0);
|
|
532
|
+
}
|
|
533
|
+
function pushDataPropertyPathsAt(path, prop, paths, stateName, depth) {
|
|
534
|
+
paths.push({ path, kind: "data", typeHint: prop.typeHint, rawInitial: prop.value?.trim(), stateName });
|
|
494
535
|
if (prop.value && isArrayLiteral(prop.value)) {
|
|
495
|
-
paths.push({ path: `${
|
|
496
|
-
paths.push({ path: `${
|
|
536
|
+
paths.push({ path: `${path}.*`, kind: "list", stateName });
|
|
537
|
+
paths.push({ path: `${path}.length`, kind: "data", typeHint: "number", stateName });
|
|
497
538
|
const elementProps = extractArrayElementProperties(prop.value);
|
|
498
539
|
for (const childProp of elementProps) {
|
|
499
540
|
paths.push({
|
|
500
|
-
path: `${
|
|
541
|
+
path: `${path}.*.${childProp.name}`,
|
|
501
542
|
kind: "data",
|
|
502
543
|
typeHint: childProp.typeHint,
|
|
503
544
|
stateName
|
|
504
545
|
});
|
|
505
546
|
}
|
|
547
|
+
return;
|
|
506
548
|
}
|
|
507
549
|
if (prop.value && isObjectLiteral(prop.value)) {
|
|
550
|
+
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
508
551
|
const childProps = parseTopLevelProperties(extractObjectContent(prop.value));
|
|
509
552
|
for (const childProp of childProps) {
|
|
510
|
-
if (childProp.kind
|
|
511
|
-
|
|
512
|
-
path: `${prop.name}.${childProp.name}`,
|
|
513
|
-
kind: "data",
|
|
514
|
-
typeHint: childProp.typeHint,
|
|
515
|
-
rawInitial: childProp.value?.trim(),
|
|
516
|
-
stateName
|
|
517
|
-
});
|
|
518
|
-
if (childProp.value && isArrayLiteral(childProp.value)) {
|
|
519
|
-
paths.push({ path: `${prop.name}.${childProp.name}.*`, kind: "list", stateName });
|
|
520
|
-
paths.push({ path: `${prop.name}.${childProp.name}.length`, kind: "data", typeHint: "number", stateName });
|
|
521
|
-
const grandchildProps = extractArrayElementProperties(childProp.value);
|
|
522
|
-
for (const gc of grandchildProps) {
|
|
523
|
-
paths.push({
|
|
524
|
-
path: `${prop.name}.${childProp.name}.*.${gc.name}`,
|
|
525
|
-
kind: "data",
|
|
526
|
-
typeHint: gc.typeHint,
|
|
527
|
-
stateName
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
}
|
|
553
|
+
if (childProp.kind !== "data") continue;
|
|
554
|
+
pushDataPropertyPathsAt(`${path}.${childProp.name}`, childProp, paths, stateName, depth + 1);
|
|
532
555
|
}
|
|
533
556
|
}
|
|
534
557
|
}
|
|
@@ -576,35 +599,44 @@ function inferJsonTypeHint(value) {
|
|
|
576
599
|
return void 0;
|
|
577
600
|
}
|
|
578
601
|
function extractDefaultExportObject(script) {
|
|
579
|
-
const
|
|
602
|
+
const scan = maskCommentsAndStrings(script);
|
|
603
|
+
const match = scan.match(/export\s+default\s+(?:defineState\s*\(\s*)?(\{)/);
|
|
580
604
|
if (!match) return null;
|
|
581
|
-
const startIndex =
|
|
582
|
-
return extractBracedContent(script, startIndex);
|
|
605
|
+
const startIndex = scan.indexOf(match[1], match.index);
|
|
606
|
+
return extractBracedContent(script, scan, startIndex);
|
|
583
607
|
}
|
|
584
608
|
function parseTopLevelProperties(objectContent) {
|
|
585
609
|
const props = [];
|
|
586
|
-
const
|
|
610
|
+
const scan = maskCommentsAndStrings(objectContent);
|
|
611
|
+
const regex = /(?:(?:get|set)\s+(?:"([^"]+)"|'([^']+)'|([$\w]+))\s*\([^)]*\)\s*\{)|(?:(?:async\s+)?([$\w]+)\s*\([^)]*\)\s*\{)|(?:(?:"([^"]+)"|'([^']+)'|([$\w]+))\s*:\s*)/gd;
|
|
587
612
|
let match;
|
|
588
|
-
while ((match = regex.exec(
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
613
|
+
while ((match = regex.exec(scan)) !== null) {
|
|
614
|
+
const indices = match.indices;
|
|
615
|
+
const nameAt = (group) => {
|
|
616
|
+
const span = indices[group];
|
|
617
|
+
return span ? objectContent.slice(span[0], span[1]) : void 0;
|
|
618
|
+
};
|
|
619
|
+
const skipBody = () => {
|
|
620
|
+
const braceStart = match.index + match[0].length - 1;
|
|
621
|
+
const body = extractBracedContent(objectContent, scan, braceStart);
|
|
622
|
+
regex.lastIndex = braceStart + body.length + 2;
|
|
623
|
+
};
|
|
624
|
+
const accessorName = nameAt(1) ?? nameAt(2) ?? nameAt(3);
|
|
625
|
+
if (accessorName) {
|
|
626
|
+
props.push({ name: accessorName, kind: "getter" });
|
|
627
|
+
skipBody();
|
|
592
628
|
continue;
|
|
593
629
|
}
|
|
594
|
-
const methodName =
|
|
630
|
+
const methodName = nameAt(4);
|
|
595
631
|
if (methodName) {
|
|
596
632
|
props.push({ name: methodName, kind: "method" });
|
|
597
|
-
|
|
598
|
-
if (braceStart !== -1) {
|
|
599
|
-
const body = extractBracedContent(objectContent, braceStart);
|
|
600
|
-
regex.lastIndex = braceStart + body.length + 2;
|
|
601
|
-
}
|
|
633
|
+
skipBody();
|
|
602
634
|
continue;
|
|
603
635
|
}
|
|
604
|
-
const propName =
|
|
636
|
+
const propName = nameAt(5) ?? nameAt(6) ?? nameAt(7);
|
|
605
637
|
if (propName) {
|
|
606
638
|
const valueStartIndex = match.index + match[0].length;
|
|
607
|
-
const value = extractFullValue(objectContent, valueStartIndex);
|
|
639
|
+
const value = extractFullValue(objectContent, scan, valueStartIndex);
|
|
608
640
|
const jsdocType = extractJsDocType(objectContent, match.index);
|
|
609
641
|
const typeHint = jsdocType ?? inferTypeHint(value);
|
|
610
642
|
props.push({ name: propName, kind: "data", value, typeHint });
|
|
@@ -613,15 +645,48 @@ function parseTopLevelProperties(objectContent) {
|
|
|
613
645
|
}
|
|
614
646
|
return props;
|
|
615
647
|
}
|
|
616
|
-
function
|
|
648
|
+
function maskCommentsAndStrings(source) {
|
|
649
|
+
const out = source.split("");
|
|
650
|
+
const len = source.length;
|
|
651
|
+
const blank = (i2) => {
|
|
652
|
+
if (source[i2] !== "\n" && source[i2] !== "\r") out[i2] = " ";
|
|
653
|
+
};
|
|
654
|
+
let i = 0;
|
|
655
|
+
while (i < len) {
|
|
656
|
+
const ch = source[i];
|
|
657
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
658
|
+
i += 2;
|
|
659
|
+
while (i < len && source[i] !== "\n") blank(i++);
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
663
|
+
i += 2;
|
|
664
|
+
while (i < len && !(source[i] === "*" && source[i + 1] === "/")) blank(i++);
|
|
665
|
+
i += 2;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
669
|
+
i++;
|
|
670
|
+
while (i < len && source[i] !== ch) {
|
|
671
|
+
if (source[i] === "\\") blank(i++);
|
|
672
|
+
if (i < len) blank(i++);
|
|
673
|
+
}
|
|
674
|
+
i++;
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
i++;
|
|
678
|
+
}
|
|
679
|
+
return out.join("");
|
|
680
|
+
}
|
|
681
|
+
function extractFullValue(content, scan, startIndex) {
|
|
617
682
|
let depth = 0;
|
|
618
683
|
let i = startIndex;
|
|
619
|
-
const len =
|
|
684
|
+
const len = scan.length;
|
|
620
685
|
let inString = null;
|
|
621
686
|
while (i < len) {
|
|
622
|
-
const ch =
|
|
687
|
+
const ch = scan[i];
|
|
623
688
|
if (inString) {
|
|
624
|
-
if (ch === inString && !isEscaped(
|
|
689
|
+
if (ch === inString && !isEscaped(scan, i)) {
|
|
625
690
|
inString = null;
|
|
626
691
|
}
|
|
627
692
|
i++;
|
|
@@ -641,13 +706,13 @@ function extractFullValue(content, startIndex) {
|
|
|
641
706
|
}
|
|
642
707
|
return content.slice(startIndex, i).trim();
|
|
643
708
|
}
|
|
644
|
-
function extractBracedContent(text, openBraceIndex) {
|
|
709
|
+
function extractBracedContent(text, scan, openBraceIndex) {
|
|
645
710
|
let depth = 0;
|
|
646
711
|
let inString = null;
|
|
647
|
-
for (let i = openBraceIndex; i <
|
|
648
|
-
const ch =
|
|
712
|
+
for (let i = openBraceIndex; i < scan.length; i++) {
|
|
713
|
+
const ch = scan[i];
|
|
649
714
|
if (inString) {
|
|
650
|
-
if (ch === inString && !isEscaped(
|
|
715
|
+
if (ch === inString && !isEscaped(scan, i)) {
|
|
651
716
|
inString = null;
|
|
652
717
|
}
|
|
653
718
|
continue;
|
|
@@ -673,16 +738,18 @@ function isObjectLiteral(value) {
|
|
|
673
738
|
}
|
|
674
739
|
function extractObjectContent(value) {
|
|
675
740
|
const trimmed = value.trim();
|
|
676
|
-
const
|
|
741
|
+
const scan = maskCommentsAndStrings(trimmed);
|
|
742
|
+
const start = scan.indexOf("{");
|
|
677
743
|
if (start === -1) return "";
|
|
678
|
-
return extractBracedContent(trimmed, start);
|
|
744
|
+
return extractBracedContent(trimmed, scan, start);
|
|
679
745
|
}
|
|
680
746
|
function extractArrayElementProperties(value) {
|
|
681
747
|
const trimmed = value.trim();
|
|
682
748
|
if (!trimmed.startsWith("[")) return [];
|
|
683
|
-
const
|
|
749
|
+
const scan = maskCommentsAndStrings(trimmed);
|
|
750
|
+
const objectStart = scan.indexOf("{");
|
|
684
751
|
if (objectStart === -1) return [];
|
|
685
|
-
const objectContent = extractBracedContent(trimmed, objectStart);
|
|
752
|
+
const objectContent = extractBracedContent(trimmed, scan, objectStart);
|
|
686
753
|
const props = [];
|
|
687
754
|
const allProps = parseTopLevelProperties(objectContent);
|
|
688
755
|
for (const prop of allProps) {
|
|
@@ -978,6 +1045,11 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale) {
|
|
|
978
1045
|
pathsByState.set(p.stateName, list);
|
|
979
1046
|
}
|
|
980
1047
|
const attrs = findAllBindAttributes(html, attrName);
|
|
1048
|
+
let structuralTemplates = null;
|
|
1049
|
+
const getStructuralTemplates = () => {
|
|
1050
|
+
structuralTemplates ??= collectStructuralTemplates(html, attrName);
|
|
1051
|
+
return structuralTemplates;
|
|
1052
|
+
};
|
|
981
1053
|
const filterNameSet = new Set(BUILTIN_FILTERS.map((f) => f.name));
|
|
982
1054
|
for (const attr of attrs) {
|
|
983
1055
|
const bindings = splitBindingExpressions(attr.value);
|
|
@@ -1063,7 +1135,7 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale) {
|
|
|
1063
1135
|
if (pathTrimmed.startsWith(".")) {
|
|
1064
1136
|
const forPath = getInnermostForPath(html, attr.valueStart, attrName);
|
|
1065
1137
|
if (forPath && !forPath.startsWith(".")) {
|
|
1066
|
-
checkPath = `${forPath}.*.${pathTrimmed.slice(1)}`;
|
|
1138
|
+
checkPath = pathTrimmed === "." ? `${forPath}.*` : `${forPath}.*.${pathTrimmed.slice(1)}`;
|
|
1067
1139
|
} else {
|
|
1068
1140
|
checkPath = "";
|
|
1069
1141
|
}
|
|
@@ -1172,7 +1244,10 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale) {
|
|
|
1172
1244
|
if (pathTrimmed && !pathTrimmed.startsWith(".") && !isLiteral(pathTrimmed)) {
|
|
1173
1245
|
const resultType = resolveResultType(pathTrimmed, parsed.filters, scopedPaths);
|
|
1174
1246
|
if (resultType !== null) {
|
|
1175
|
-
const typeReq = getExpectedType(
|
|
1247
|
+
const typeReq = getExpectedType(
|
|
1248
|
+
parsed.property,
|
|
1249
|
+
() => isNegatedByElseChain(getStructuralTemplates(), attr.valueStart)
|
|
1250
|
+
);
|
|
1176
1251
|
if (typeReq && resultType !== typeReq.expected) {
|
|
1177
1252
|
const pathOffset = binding.indexOf(parsed.path);
|
|
1178
1253
|
const pathStart = bindingStart + pathOffset;
|
|
@@ -1352,12 +1427,54 @@ function validatePathExistence(checkPath, displayPath, scopedPaths, scopedPathSe
|
|
|
1352
1427
|
}
|
|
1353
1428
|
return null;
|
|
1354
1429
|
}
|
|
1355
|
-
function
|
|
1430
|
+
function collectStructuralTemplates(html, attrName) {
|
|
1431
|
+
const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1432
|
+
const attrRegex = new RegExp(`${escaped}\\s*=\\s*(["'])`, "i");
|
|
1433
|
+
const tagRegex = /<template(?:\s[^>]*)?>|<\/template\s*>/gi;
|
|
1434
|
+
const templates = [];
|
|
1435
|
+
let depth = 0;
|
|
1436
|
+
let match;
|
|
1437
|
+
while ((match = tagRegex.exec(html)) !== null) {
|
|
1438
|
+
if (match[0].startsWith("</")) {
|
|
1439
|
+
depth = Math.max(0, depth - 1);
|
|
1440
|
+
continue;
|
|
1441
|
+
}
|
|
1442
|
+
const attrMatch = attrRegex.exec(match[0]);
|
|
1443
|
+
if (attrMatch) {
|
|
1444
|
+
const quote = attrMatch[1];
|
|
1445
|
+
const valueStart = match.index + attrMatch.index + attrMatch[0].length;
|
|
1446
|
+
const valueEnd = html.indexOf(quote, valueStart);
|
|
1447
|
+
if (valueEnd !== -1) {
|
|
1448
|
+
const first = splitBindingExpressions(html.slice(valueStart, valueEnd))[0] ?? "";
|
|
1449
|
+
const prop = first.split(":")[0].replace(/#.*$/, "").trim();
|
|
1450
|
+
const type = prop === "if" || prop === "elseif" || prop === "else" ? prop : "other";
|
|
1451
|
+
templates.push({ valueStart, depth, type });
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
depth++;
|
|
1455
|
+
}
|
|
1456
|
+
return templates;
|
|
1457
|
+
}
|
|
1458
|
+
function isNegatedByElseChain(templates, valueStart) {
|
|
1459
|
+
const index = templates.findIndex((t) => t.valueStart === valueStart);
|
|
1460
|
+
if (index === -1) return false;
|
|
1461
|
+
const selfDepth = templates[index].depth;
|
|
1462
|
+
for (let i = index + 1; i < templates.length; i++) {
|
|
1463
|
+
const next = templates[i];
|
|
1464
|
+
if (next.depth > selfDepth) continue;
|
|
1465
|
+
if (next.depth < selfDepth) return false;
|
|
1466
|
+
if (next.type === "elseif" || next.type === "else") return true;
|
|
1467
|
+
if (next.type === "if") return false;
|
|
1468
|
+
}
|
|
1469
|
+
return false;
|
|
1470
|
+
}
|
|
1471
|
+
function getExpectedType(property, isNegatedIf) {
|
|
1356
1472
|
const prop = property.replace(/#.*$/, "");
|
|
1357
1473
|
if (prop === "for") {
|
|
1358
1474
|
return { label: "for", expected: "array", severity: "error" };
|
|
1359
1475
|
}
|
|
1360
1476
|
if (prop === "if" || prop === "elseif") {
|
|
1477
|
+
if (!isNegatedIf()) return null;
|
|
1361
1478
|
return { label: prop, expected: "boolean", severity: "warning" };
|
|
1362
1479
|
}
|
|
1363
1480
|
if (prop.startsWith("class.")) {
|
|
@@ -1696,20 +1813,18 @@ function findAllCommentBindings(html, commentTextPrefix = "wcs-text") {
|
|
|
1696
1813
|
return results;
|
|
1697
1814
|
}
|
|
1698
1815
|
function isInsideTag(html, offset, tagName) {
|
|
1699
|
-
const
|
|
1700
|
-
|
|
1701
|
-
let lastOpenEnd = -1;
|
|
1702
|
-
let lastCloseEnd = -1;
|
|
1816
|
+
const tagRegex = new RegExp(`<(/?)${tagName}[\\s>]`, "gi");
|
|
1817
|
+
let depth = 0;
|
|
1703
1818
|
let match;
|
|
1704
|
-
while ((match =
|
|
1705
|
-
if (match.index > offset) break;
|
|
1706
|
-
lastOpenEnd = match.index;
|
|
1707
|
-
}
|
|
1708
|
-
while ((match = closeRegex.exec(html)) !== null) {
|
|
1819
|
+
while ((match = tagRegex.exec(html)) !== null) {
|
|
1709
1820
|
if (match.index > offset) break;
|
|
1710
|
-
|
|
1821
|
+
if (match[1]) {
|
|
1822
|
+
depth = Math.max(0, depth - 1);
|
|
1823
|
+
} else {
|
|
1824
|
+
depth++;
|
|
1825
|
+
}
|
|
1711
1826
|
}
|
|
1712
|
-
return
|
|
1827
|
+
return depth > 0;
|
|
1713
1828
|
}
|
|
1714
1829
|
|
|
1715
1830
|
// src/service/templateSyntaxValidator.ts
|
|
@@ -1779,7 +1894,7 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
|
|
|
1779
1894
|
if (pathPart.startsWith(".")) {
|
|
1780
1895
|
const forPath = insideFor ? getInnermostForPath(html, item.matchStart, bindAttrName) : null;
|
|
1781
1896
|
if (forPath && !forPath.startsWith(".")) {
|
|
1782
|
-
const expandedPath = `${forPath}.*.${pathPart.slice(1)}`;
|
|
1897
|
+
const expandedPath = pathPart === "." ? `${forPath}.*` : `${forPath}.*.${pathPart.slice(1)}`;
|
|
1783
1898
|
if (!isValidTemplatePath(expandedPath, pathSet, defaultPaths)) {
|
|
1784
1899
|
diagnostics.push({
|
|
1785
1900
|
code: WcsDiagnosticCode.BindingPathMissing,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wcstack/lint",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"description": "Static-contract validator CLI (wcs-validate) for wcstack data-wcs bindings and wcstack.manifest.json sidecars. Thin npm wrapper around the wcstack-intellisense validator core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|