@monoharada/wcf-mcp 0.9.1 → 0.10.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 +23 -3
- package/core/constants.mjs +1 -1
- package/core/plugins.mjs +184 -3
- package/core/register.mjs +1007 -145
- package/core/tokens.mjs +70 -17
- package/core.mjs +13 -0
- package/data/design-tokens.json +932 -2
- package/data/guidelines-index.json +88 -24
- package/examples/plugins/custom-validation-plugin.mjs +61 -0
- package/package.json +1 -1
- package/runtime-data.mjs +41 -5
- package/server.mjs +5 -0
- package/validator.mjs +209 -31
package/validator.mjs
CHANGED
|
@@ -41,6 +41,42 @@ function isForbiddenAttr(attrName) {
|
|
|
41
41
|
return FORBIDDEN_ATTR_SET.has(attrName.toLowerCase());
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function maskTextPreserveNewlines(fragment) {
|
|
45
|
+
return String(fragment).replace(/[^\n\r]/g, ' ');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function sanitizeMarkupForValidation(text) {
|
|
49
|
+
let out = String(text ?? '');
|
|
50
|
+
const patterns = [
|
|
51
|
+
/^---[\s\S]*?\n---/m, // Astro/frontmatter blocks
|
|
52
|
+
/<!--[\s\S]*?-->/g,
|
|
53
|
+
/<script\b[\s\S]*?<\/script>/gi,
|
|
54
|
+
/<style\b[\s\S]*?<\/style>/gi,
|
|
55
|
+
/{%\s*comment\s*%}[\s\S]*?{%\s*endcomment\s*%}/gi,
|
|
56
|
+
/{{!--[\s\S]*?--}}/g,
|
|
57
|
+
/{#[\s\S]*?#}/g,
|
|
58
|
+
/<%[\s\S]*?%>/g,
|
|
59
|
+
/<\?[\s\S]*?\?>/g,
|
|
60
|
+
];
|
|
61
|
+
for (const pattern of patterns) {
|
|
62
|
+
out = out.replace(pattern, (match) => maskTextPreserveNewlines(match));
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isTemplateValue(value) {
|
|
68
|
+
const raw = String(value ?? '').trim();
|
|
69
|
+
if (!raw) return false;
|
|
70
|
+
return (
|
|
71
|
+
raw.includes('{{') ||
|
|
72
|
+
raw.includes('{%') ||
|
|
73
|
+
raw.includes('<%') ||
|
|
74
|
+
raw.includes('<?') ||
|
|
75
|
+
raw.includes('${') ||
|
|
76
|
+
(/^\{[\s\S]*\}$/.test(raw))
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
44
80
|
function computeLineIndex(text) {
|
|
45
81
|
const out = [0];
|
|
46
82
|
for (let i = 0; i < text.length; i += 1) {
|
|
@@ -170,11 +206,12 @@ export function detectEnumValueMisuse({
|
|
|
170
206
|
const diagnostics = [];
|
|
171
207
|
if (!(enumMap instanceof Map) || enumMap.size === 0) return diagnostics;
|
|
172
208
|
|
|
173
|
-
const
|
|
209
|
+
const sourceText = sanitizeMarkupForValidation(text);
|
|
210
|
+
const lineStarts = computeLineIndex(sourceText);
|
|
174
211
|
const tagRe = /<([a-z][a-z0-9-]*)\b([^<>]*?)>/gi;
|
|
175
212
|
let m;
|
|
176
213
|
|
|
177
|
-
while ((m = tagRe.exec(
|
|
214
|
+
while ((m = tagRe.exec(sourceText))) {
|
|
178
215
|
const tag = String(m[1] ?? '').toLowerCase();
|
|
179
216
|
if (!tag.includes('-')) continue;
|
|
180
217
|
|
|
@@ -191,7 +228,7 @@ export function detectEnumValueMisuse({
|
|
|
191
228
|
if (!validValues) continue;
|
|
192
229
|
|
|
193
230
|
// Skip empty values (boolean-style attributes)
|
|
194
|
-
if (value === undefined || value === '') continue;
|
|
231
|
+
if (value === undefined || value.trim() === '' || isTemplateValue(value)) continue;
|
|
195
232
|
|
|
196
233
|
if (!validValues.has(value)) {
|
|
197
234
|
const startIndex = rawAttrsStart + offset;
|
|
@@ -253,6 +290,43 @@ function parseAttributeNames(rawAttrs) {
|
|
|
253
290
|
return parseAttributes(rawAttrs).map(({ name, offset }) => ({ name, offset }));
|
|
254
291
|
}
|
|
255
292
|
|
|
293
|
+
function readBalancedBraces(text, startIndex) {
|
|
294
|
+
let index = startIndex;
|
|
295
|
+
let depth = 0;
|
|
296
|
+
let quote = null;
|
|
297
|
+
|
|
298
|
+
while (index < text.length) {
|
|
299
|
+
const ch = text[index];
|
|
300
|
+
if (quote) {
|
|
301
|
+
if (ch === '\\') {
|
|
302
|
+
index += 2;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (ch === quote) quote = null;
|
|
306
|
+
index += 1;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
311
|
+
quote = ch;
|
|
312
|
+
index += 1;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (ch === '{') depth += 1;
|
|
317
|
+
if (ch === '}') {
|
|
318
|
+
depth -= 1;
|
|
319
|
+
index += 1;
|
|
320
|
+
if (depth === 0) break;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
index += 1;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return index;
|
|
328
|
+
}
|
|
329
|
+
|
|
256
330
|
function parseAttributes(rawAttrs) {
|
|
257
331
|
/** @type {{ name: string, offset: number }[]} */
|
|
258
332
|
const out = [];
|
|
@@ -269,11 +343,15 @@ function parseAttributes(rawAttrs) {
|
|
|
269
343
|
|
|
270
344
|
const c = rawAttrs[i];
|
|
271
345
|
if (c === '/' || c === '>') break;
|
|
346
|
+
if (c === '{') {
|
|
347
|
+
i = readBalancedBraces(rawAttrs, i);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
272
350
|
|
|
273
351
|
const nameStart = i;
|
|
274
352
|
while (i < len) {
|
|
275
353
|
const ch = rawAttrs[i];
|
|
276
|
-
if (ch === '=' || ch === '>' || ch === '/' || isSpace(rawAttrs.charCodeAt(i))) break;
|
|
354
|
+
if (ch === '=' || ch === '>' || ch === '/' || ch === '{' || isSpace(rawAttrs.charCodeAt(i))) break;
|
|
277
355
|
i += 1;
|
|
278
356
|
}
|
|
279
357
|
|
|
@@ -294,6 +372,10 @@ function parseAttributes(rawAttrs) {
|
|
|
294
372
|
while (i < len && rawAttrs[i] !== quote) i += 1;
|
|
295
373
|
value = rawAttrs.slice(valueStart, i);
|
|
296
374
|
if (i < len) i += 1;
|
|
375
|
+
} else if (quote === '{') {
|
|
376
|
+
const valueStart = i;
|
|
377
|
+
i = readBalancedBraces(rawAttrs, i);
|
|
378
|
+
value = rawAttrs.slice(valueStart, i);
|
|
297
379
|
} else {
|
|
298
380
|
const valueStart = i;
|
|
299
381
|
while (i < len) {
|
|
@@ -330,11 +412,12 @@ export function validateTextAgainstCem({
|
|
|
330
412
|
ignoreTags = new Set(),
|
|
331
413
|
}) {
|
|
332
414
|
const diagnostics = [];
|
|
333
|
-
const
|
|
415
|
+
const sourceText = sanitizeMarkupForValidation(text);
|
|
416
|
+
const lineStarts = computeLineIndex(sourceText);
|
|
334
417
|
|
|
335
418
|
const tagRe = /<([a-z][a-z0-9-]*)\b([^<>]*?)>/gi;
|
|
336
419
|
let m;
|
|
337
|
-
while ((m = tagRe.exec(
|
|
420
|
+
while ((m = tagRe.exec(sourceText))) {
|
|
338
421
|
const tag = String(m[1] ?? '').toLowerCase();
|
|
339
422
|
|
|
340
423
|
const tagOffset = m.index + 1;
|
|
@@ -418,11 +501,12 @@ export function detectTokenMisuseInInlineStyles({
|
|
|
418
501
|
const diagnostics = [];
|
|
419
502
|
if (!(valueToToken instanceof Map) || valueToToken.size === 0) return diagnostics;
|
|
420
503
|
|
|
421
|
-
const
|
|
504
|
+
const sourceText = sanitizeMarkupForValidation(text);
|
|
505
|
+
const lineStarts = computeLineIndex(sourceText);
|
|
422
506
|
const tagRe = /<([a-z][a-z0-9-]*)\b([^<>]*?)>/gi;
|
|
423
507
|
let m;
|
|
424
508
|
|
|
425
|
-
while ((m = tagRe.exec(
|
|
509
|
+
while ((m = tagRe.exec(sourceText))) {
|
|
426
510
|
const tag = String(m[1] ?? '').toLowerCase();
|
|
427
511
|
const attrChunk = String(m[2] ?? '');
|
|
428
512
|
const inlineStyle = parseInlineStyleAttribute(attrChunk);
|
|
@@ -438,7 +522,7 @@ export function detectTokenMisuseInInlineStyles({
|
|
|
438
522
|
if (!TOKEN_MISUSE_STYLE_PROPS.has(prop)) continue;
|
|
439
523
|
|
|
440
524
|
const valueRaw = String(d[2] ?? '').trim();
|
|
441
|
-
if (!valueRaw || /^var\(/i.test(valueRaw)) continue;
|
|
525
|
+
if (!valueRaw || /^var\(/i.test(valueRaw) || isTemplateValue(valueRaw)) continue;
|
|
442
526
|
|
|
443
527
|
const normalizedValue = normalizeStyleValue(valueRaw);
|
|
444
528
|
const cssVariable = valueToToken.get(normalizedValue);
|
|
@@ -481,11 +565,12 @@ export function detectAccessibilityMisuseInMarkup({
|
|
|
481
565
|
cemTagNames,
|
|
482
566
|
}) {
|
|
483
567
|
const diagnostics = [];
|
|
484
|
-
const
|
|
568
|
+
const sourceText = sanitizeMarkupForValidation(text);
|
|
569
|
+
const lineStarts = computeLineIndex(sourceText);
|
|
485
570
|
const tagRe = /<([a-z][a-z0-9-]*)\b([^<>]*?)>/gi;
|
|
486
571
|
let m;
|
|
487
572
|
|
|
488
|
-
while ((m = tagRe.exec(
|
|
573
|
+
while ((m = tagRe.exec(sourceText))) {
|
|
489
574
|
const tag = String(m[1] ?? '').toLowerCase();
|
|
490
575
|
const attrChunk = String(m[2] ?? '');
|
|
491
576
|
const rawAttrsStart = m.index + 1 + tag.length;
|
|
@@ -542,7 +627,7 @@ export function detectAccessibilityMisuseInMarkup({
|
|
|
542
627
|
];
|
|
543
628
|
for (const { name, offset, value } of parsedAttrs) {
|
|
544
629
|
const attrLower = String(name ?? '').toLowerCase();
|
|
545
|
-
if (typeof value !== 'string' || value.trim() !== '') continue;
|
|
630
|
+
if (typeof value !== 'string' || value.trim() !== '' || isTemplateValue(value)) continue;
|
|
546
631
|
const check = EMPTY_LABEL_CHECKS.find((c) => c.attr === attrLower);
|
|
547
632
|
if (!check) continue;
|
|
548
633
|
const startIndex = rawAttrsStart + offset;
|
|
@@ -618,34 +703,66 @@ export function detectInvalidSlotName({
|
|
|
618
703
|
const diagnostics = [];
|
|
619
704
|
if (!(slotMap instanceof Map) || slotMap.size === 0) return diagnostics;
|
|
620
705
|
|
|
621
|
-
|
|
706
|
+
const sourceText = sanitizeMarkupForValidation(text);
|
|
622
707
|
const globalSlotNames = new Set();
|
|
623
708
|
for (const slotNames of slotMap.values()) {
|
|
624
709
|
for (const name of slotNames) globalSlotNames.add(name);
|
|
625
710
|
}
|
|
626
711
|
|
|
627
|
-
const lineStarts = computeLineIndex(
|
|
628
|
-
const
|
|
712
|
+
const lineStarts = computeLineIndex(sourceText);
|
|
713
|
+
const stack = [];
|
|
714
|
+
const tagRe = /<\/?([a-z][a-z0-9-]*)\b([^<>]*?)\/?>/gi;
|
|
629
715
|
let m;
|
|
630
716
|
|
|
631
|
-
while ((m = tagRe.exec(
|
|
717
|
+
while ((m = tagRe.exec(sourceText))) {
|
|
718
|
+
const fullMatch = String(m[0] ?? '');
|
|
632
719
|
const tag = String(m[1] ?? '').toLowerCase();
|
|
633
720
|
const attrChunk = String(m[2] ?? '');
|
|
721
|
+
const isClosing = fullMatch.startsWith('</');
|
|
722
|
+
const isSelfClosing = fullMatch.endsWith('/>');
|
|
634
723
|
const rawAttrsStart = m.index + 1 + tag.length;
|
|
724
|
+
|
|
725
|
+
if (isClosing) {
|
|
726
|
+
for (let index = stack.length - 1; index >= 0; index -= 1) {
|
|
727
|
+
if (stack[index] === tag) {
|
|
728
|
+
stack.splice(index, 1);
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
|
|
635
735
|
const attrs = parseAttributes(attrChunk);
|
|
736
|
+
const nearestComponentParent = [...stack].reverse().find((item) => slotMap.has(item));
|
|
636
737
|
|
|
637
738
|
for (const { name, offset, value } of attrs) {
|
|
638
739
|
const attrName = name.toLowerCase();
|
|
639
740
|
if (attrName !== 'slot') continue;
|
|
640
|
-
if (value === undefined || value === '') continue;
|
|
641
|
-
|
|
642
|
-
// 'default' is always valid (unnamed slot)
|
|
741
|
+
if (value === undefined || value.trim() === '' || isTemplateValue(value)) continue;
|
|
643
742
|
if (value === 'default') continue;
|
|
644
743
|
|
|
744
|
+
const startIndex = rawAttrsStart + offset;
|
|
745
|
+
const endIndex = startIndex + name.length;
|
|
746
|
+
const range = makeRange(lineStarts, startIndex, endIndex);
|
|
747
|
+
|
|
748
|
+
if (nearestComponentParent) {
|
|
749
|
+
const allowedSlots = slotMap.get(nearestComponentParent) ?? new Set();
|
|
750
|
+
if (!allowedSlots.has(value)) {
|
|
751
|
+
diagnostics.push({
|
|
752
|
+
file: filePath,
|
|
753
|
+
range,
|
|
754
|
+
severity,
|
|
755
|
+
code: 'invalidSlotName',
|
|
756
|
+
message: `Unknown slot name "${value}" for parent <${nearestComponentParent}>.`,
|
|
757
|
+
tagName: tag,
|
|
758
|
+
attrName: 'slot',
|
|
759
|
+
hint: `Check <${nearestComponentParent}> for available slot names.`,
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
|
|
645
765
|
if (!globalSlotNames.has(value)) {
|
|
646
|
-
const startIndex = rawAttrsStart + offset;
|
|
647
|
-
const endIndex = startIndex + name.length;
|
|
648
|
-
const range = makeRange(lineStarts, startIndex, endIndex);
|
|
649
766
|
diagnostics.push({
|
|
650
767
|
file: filePath,
|
|
651
768
|
range,
|
|
@@ -658,6 +775,10 @@ export function detectInvalidSlotName({
|
|
|
658
775
|
});
|
|
659
776
|
}
|
|
660
777
|
}
|
|
778
|
+
|
|
779
|
+
if (!isSelfClosing && !HTML_VOID_ELEMENTS.has(tag) && tag.includes('-')) {
|
|
780
|
+
stack.push(tag);
|
|
781
|
+
}
|
|
661
782
|
}
|
|
662
783
|
|
|
663
784
|
return diagnostics;
|
|
@@ -702,7 +823,8 @@ export function detectOrphanedChildComponents({
|
|
|
702
823
|
severity = 'warning',
|
|
703
824
|
}) {
|
|
704
825
|
const diagnostics = [];
|
|
705
|
-
const
|
|
826
|
+
const sourceText = sanitizeMarkupForValidation(text);
|
|
827
|
+
const lineStarts = computeLineIndex(sourceText);
|
|
706
828
|
const p = prefix.toLowerCase();
|
|
707
829
|
const canonicalPrefix = 'dads';
|
|
708
830
|
|
|
@@ -722,7 +844,7 @@ export function detectOrphanedChildComponents({
|
|
|
722
844
|
const tagRe = /<\/?([a-z][a-z0-9-]*)\b[^<>]*?\/?>/gi;
|
|
723
845
|
let m;
|
|
724
846
|
|
|
725
|
-
while ((m = tagRe.exec(
|
|
847
|
+
while ((m = tagRe.exec(sourceText))) {
|
|
726
848
|
const fullMatch = m[0];
|
|
727
849
|
const tag = String(m[1] ?? '').toLowerCase();
|
|
728
850
|
const isClosing = fullMatch.startsWith('</');
|
|
@@ -794,7 +916,8 @@ export function detectEmptyInteractiveElement({
|
|
|
794
916
|
severity = 'warning',
|
|
795
917
|
}) {
|
|
796
918
|
const diagnostics = [];
|
|
797
|
-
const
|
|
919
|
+
const sourceText = sanitizeMarkupForValidation(text);
|
|
920
|
+
const lineStarts = computeLineIndex(sourceText);
|
|
798
921
|
const p = prefix.toLowerCase();
|
|
799
922
|
const canonicalPrefix = 'dads';
|
|
800
923
|
|
|
@@ -807,7 +930,7 @@ export function detectEmptyInteractiveElement({
|
|
|
807
930
|
const selfClosingRe = /<([a-z][a-z0-9-]*)\b([^<>]*?)\/>/gi;
|
|
808
931
|
let m;
|
|
809
932
|
|
|
810
|
-
while ((m = selfClosingRe.exec(
|
|
933
|
+
while ((m = selfClosingRe.exec(sourceText))) {
|
|
811
934
|
const tag = String(m[1] ?? '').toLowerCase();
|
|
812
935
|
if (!elements.has(tag)) continue;
|
|
813
936
|
|
|
@@ -834,7 +957,7 @@ export function detectEmptyInteractiveElement({
|
|
|
834
957
|
for (const tag of elements) {
|
|
835
958
|
const emptyRe = new RegExp(`<(${tag})\\b([^<>]*?)>\\s*</${tag}>`, 'gi');
|
|
836
959
|
let em;
|
|
837
|
-
while ((em = emptyRe.exec(
|
|
960
|
+
while ((em = emptyRe.exec(sourceText))) {
|
|
838
961
|
const matchedTag = String(em[1] ?? '').toLowerCase();
|
|
839
962
|
const attrChunk = String(em[2] ?? '');
|
|
840
963
|
const attrs = parseAttributes(attrChunk);
|
|
@@ -888,7 +1011,8 @@ export function detectMissingRequiredAttributes({
|
|
|
888
1011
|
severity = 'error',
|
|
889
1012
|
}) {
|
|
890
1013
|
const diagnostics = [];
|
|
891
|
-
const
|
|
1014
|
+
const sourceText = sanitizeMarkupForValidation(text);
|
|
1015
|
+
const lineStarts = computeLineIndex(sourceText);
|
|
892
1016
|
const tagRe = /<([a-z][a-z0-9-]*)\b([^<>]*?)>/gi;
|
|
893
1017
|
let m;
|
|
894
1018
|
|
|
@@ -901,7 +1025,7 @@ export function detectMissingRequiredAttributes({
|
|
|
901
1025
|
requiredMap.set(mappedTag, attrs);
|
|
902
1026
|
}
|
|
903
1027
|
|
|
904
|
-
while ((m = tagRe.exec(
|
|
1028
|
+
while ((m = tagRe.exec(sourceText))) {
|
|
905
1029
|
const tag = String(m[1] ?? '').toLowerCase();
|
|
906
1030
|
const requiredAttrs = requiredMap.get(tag);
|
|
907
1031
|
if (!requiredAttrs) continue;
|
|
@@ -931,6 +1055,59 @@ export function detectMissingRequiredAttributes({
|
|
|
931
1055
|
return diagnostics;
|
|
932
1056
|
}
|
|
933
1057
|
|
|
1058
|
+
/**
|
|
1059
|
+
* Detect duplicate id attributes within a single markup document.
|
|
1060
|
+
* @param {{
|
|
1061
|
+
* filePath?: string;
|
|
1062
|
+
* text: string;
|
|
1063
|
+
* severity?: string;
|
|
1064
|
+
* }} params
|
|
1065
|
+
*/
|
|
1066
|
+
export function detectDuplicateIdsInMarkup({
|
|
1067
|
+
filePath = '<input>',
|
|
1068
|
+
text,
|
|
1069
|
+
severity = 'error',
|
|
1070
|
+
}) {
|
|
1071
|
+
const diagnostics = [];
|
|
1072
|
+
const sourceText = sanitizeMarkupForValidation(text);
|
|
1073
|
+
const lineStarts = computeLineIndex(sourceText);
|
|
1074
|
+
const seen = new Map();
|
|
1075
|
+
const tagRe = /<([a-z][a-z0-9-]*)\b([^<>]*?)>/gi;
|
|
1076
|
+
let m;
|
|
1077
|
+
|
|
1078
|
+
while ((m = tagRe.exec(sourceText))) {
|
|
1079
|
+
const tag = String(m[1] ?? '').toLowerCase();
|
|
1080
|
+
const attrChunk = String(m[2] ?? '');
|
|
1081
|
+
const rawAttrsStart = m.index + 1 + tag.length;
|
|
1082
|
+
const attrs = parseAttributes(attrChunk);
|
|
1083
|
+
const idAttr = attrs.find(({ name }) => String(name ?? '').toLowerCase() === 'id');
|
|
1084
|
+
const idValue = String(idAttr?.value ?? '').trim();
|
|
1085
|
+
if (!idAttr || idValue === '' || isTemplateValue(idValue)) continue;
|
|
1086
|
+
|
|
1087
|
+
const startIndex = rawAttrsStart + idAttr.offset;
|
|
1088
|
+
const endIndex = startIndex + idAttr.name.length;
|
|
1089
|
+
const range = makeRange(lineStarts, startIndex, endIndex);
|
|
1090
|
+
const previous = seen.get(idValue);
|
|
1091
|
+
if (previous) {
|
|
1092
|
+
diagnostics.push({
|
|
1093
|
+
file: filePath,
|
|
1094
|
+
range,
|
|
1095
|
+
severity,
|
|
1096
|
+
code: 'duplicateId',
|
|
1097
|
+
message: `Duplicate id "${idValue}" found. IDs must be unique within a document.`,
|
|
1098
|
+
tagName: tag,
|
|
1099
|
+
attrName: 'id',
|
|
1100
|
+
hint: `Rename or remove one of the duplicated id="${idValue}" attributes.`,
|
|
1101
|
+
});
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
seen.set(idValue, { tag, range });
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
return diagnostics;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
934
1111
|
/**
|
|
935
1112
|
* Detect attributes written in non-lowercase on known custom elements.
|
|
936
1113
|
* HTML attributes are case-insensitive, but WCF uses lowercase canonically.
|
|
@@ -950,11 +1127,12 @@ export function detectNonLowercaseAttributes({
|
|
|
950
1127
|
const diagnostics = [];
|
|
951
1128
|
if (!(cem instanceof Map) || cem.size === 0) return diagnostics;
|
|
952
1129
|
|
|
953
|
-
const
|
|
1130
|
+
const sourceText = sanitizeMarkupForValidation(text);
|
|
1131
|
+
const lineStarts = computeLineIndex(sourceText);
|
|
954
1132
|
const tagRe = /<([a-z][a-z0-9-]*)\b([^<>]*?)>/gi;
|
|
955
1133
|
let m;
|
|
956
1134
|
|
|
957
|
-
while ((m = tagRe.exec(
|
|
1135
|
+
while ((m = tagRe.exec(sourceText))) {
|
|
958
1136
|
const tag = String(m[1] ?? '').toLowerCase();
|
|
959
1137
|
if (!tag.includes('-')) continue;
|
|
960
1138
|
|