@hyperframes/lint 0.8.66 → 0.8.67
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/browser.d.ts +3 -0
- package/dist/browser.js +87 -24
- package/dist/browser.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +87 -24
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ type HyperframeLintFinding = {
|
|
|
4
4
|
severity: HyperframeLintSeverity;
|
|
5
5
|
message: string;
|
|
6
6
|
file?: string;
|
|
7
|
+
/** One-based coordinates in the original source; absent when no unique location exists. */
|
|
8
|
+
line?: number;
|
|
9
|
+
column?: number;
|
|
7
10
|
selector?: string;
|
|
8
11
|
elementId?: string;
|
|
9
12
|
fixHint?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/sourceLocations.ts
|
|
2
|
+
import { Parser as Parser2 } from "htmlparser2";
|
|
3
|
+
|
|
1
4
|
// src/utils.ts
|
|
2
5
|
import { Parser } from "htmlparser2";
|
|
3
6
|
var COMPOSITION_ID_IN_CSS_PATTERN = /\[data-composition-id=["']([^"']+)["']\]/g;
|
|
@@ -557,15 +560,67 @@ function hasCaptionStyles(styles) {
|
|
|
557
560
|
return styles.some((s) => /\.caption[-_]?(?:group|word)/i.test(s.content));
|
|
558
561
|
}
|
|
559
562
|
function truncateSnippet(value, maxLength = 220) {
|
|
560
|
-
const
|
|
561
|
-
if (!
|
|
562
|
-
if (
|
|
563
|
-
return `${
|
|
563
|
+
const normalized2 = value.replace(/\s+/g, " ").trim();
|
|
564
|
+
if (!normalized2) return void 0;
|
|
565
|
+
if (normalized2.length <= maxLength) return normalized2;
|
|
566
|
+
return `${normalized2.slice(0, maxLength - 3)}...`;
|
|
564
567
|
}
|
|
565
568
|
function mediaSrcTagRe(tagAlternation) {
|
|
566
569
|
return new RegExp(`<(${tagAlternation})\\b[^>]*\\ssrc\\s*=\\s*["']([^"']+)["'][^>]*>`, "gi");
|
|
567
570
|
}
|
|
568
571
|
|
|
572
|
+
// src/sourceLocations.ts
|
|
573
|
+
function normalized(source) {
|
|
574
|
+
let text = "";
|
|
575
|
+
const offsets = [];
|
|
576
|
+
for (let i = 0; i < source.length; i++) {
|
|
577
|
+
const char = source[i];
|
|
578
|
+
if (/\s/.test(char)) {
|
|
579
|
+
if (text.endsWith(" ")) continue;
|
|
580
|
+
text += " ";
|
|
581
|
+
} else text += char;
|
|
582
|
+
offsets.push(i);
|
|
583
|
+
}
|
|
584
|
+
return { text, offsets };
|
|
585
|
+
}
|
|
586
|
+
function createSourceLocator(source) {
|
|
587
|
+
const tags = parseHtmlStructure(source).tags;
|
|
588
|
+
const searchable = source.split("");
|
|
589
|
+
const parser = new Parser2({
|
|
590
|
+
oncomment() {
|
|
591
|
+
searchable.fill("\0", parser.startIndex, parser.endIndex + 1);
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
parser.write(source);
|
|
595
|
+
parser.end();
|
|
596
|
+
const normalizedSource = normalized(searchable.join(""));
|
|
597
|
+
const at = (offset) => {
|
|
598
|
+
const before = source.slice(0, offset);
|
|
599
|
+
return {
|
|
600
|
+
line: before.split(/\r\n|\r|\n/).length,
|
|
601
|
+
column: offset - Math.max(before.lastIndexOf("\n"), before.lastIndexOf("\r"))
|
|
602
|
+
};
|
|
603
|
+
};
|
|
604
|
+
return (finding) => {
|
|
605
|
+
if (finding.line !== void 0) return { line: finding.line, column: finding.column };
|
|
606
|
+
if (finding.snippet) {
|
|
607
|
+
const offset = snippetOffset(normalizedSource, finding.snippet);
|
|
608
|
+
return offset === void 0 ? {} : at(offset);
|
|
609
|
+
}
|
|
610
|
+
const id = finding.elementId ?? /^#([\w-]+)$/.exec(finding.selector ?? "")?.[1];
|
|
611
|
+
if (!id) return {};
|
|
612
|
+
const matches = tags.filter((tag) => readDecodedAttr(tag.raw, "id") === id);
|
|
613
|
+
return matches.length === 1 ? at(matches[0].index) : {};
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function snippetOffset(source, snippet) {
|
|
617
|
+
const needle = snippet.replace(/\s+/g, " ").trim().replace(/\.\.\.$/, "");
|
|
618
|
+
if (!needle) return void 0;
|
|
619
|
+
const index = source.text.indexOf(needle);
|
|
620
|
+
if (index < 0 || source.text.indexOf(needle, index + 1) >= 0) return void 0;
|
|
621
|
+
return source.offsets[index];
|
|
622
|
+
}
|
|
623
|
+
|
|
569
624
|
// src/context.ts
|
|
570
625
|
function buildLintContext(html, options = {}) {
|
|
571
626
|
const rawSource = html || "";
|
|
@@ -1366,18 +1421,18 @@ function classNamesFromAttr(classAttr) {
|
|
|
1366
1421
|
return classAttr.split(/\s+/).filter(Boolean);
|
|
1367
1422
|
}
|
|
1368
1423
|
function selectorTargetsManagedMedia(selector, mediaIndex) {
|
|
1369
|
-
const
|
|
1370
|
-
if (!
|
|
1371
|
-
if (mediaIndex.hasVideo && /\bvideo\b/i.test(
|
|
1372
|
-
if (mediaIndex.hasAudio && /\baudio\b/i.test(
|
|
1424
|
+
const normalized2 = selector.trim();
|
|
1425
|
+
if (!normalized2) return false;
|
|
1426
|
+
if (mediaIndex.hasVideo && /\bvideo\b/i.test(normalized2)) return true;
|
|
1427
|
+
if (mediaIndex.hasAudio && /\baudio\b/i.test(normalized2)) return true;
|
|
1373
1428
|
for (const mediaId of mediaIndex.ids) {
|
|
1374
1429
|
const escapedId = escapeRegExp(mediaId);
|
|
1375
|
-
if (new RegExp(`#${escapedId}(?![\\w-])`).test(
|
|
1430
|
+
if (new RegExp(`#${escapedId}(?![\\w-])`).test(normalized2) || normalized2.includes(`[id="${mediaId}"]`) || normalized2.includes(`[id='${mediaId}']`)) {
|
|
1376
1431
|
return true;
|
|
1377
1432
|
}
|
|
1378
1433
|
}
|
|
1379
1434
|
for (const className of mediaIndex.classes) {
|
|
1380
|
-
if (new RegExp(`\\.${escapeRegExp(className)}(?![\\w-])`).test(
|
|
1435
|
+
if (new RegExp(`\\.${escapeRegExp(className)}(?![\\w-])`).test(normalized2)) {
|
|
1381
1436
|
return true;
|
|
1382
1437
|
}
|
|
1383
1438
|
}
|
|
@@ -2116,8 +2171,8 @@ function readTagTiming(rawTag) {
|
|
|
2116
2171
|
}
|
|
2117
2172
|
function countPhysicalLines(source) {
|
|
2118
2173
|
if (source.length === 0) return 0;
|
|
2119
|
-
const
|
|
2120
|
-
const withoutFinalNewline =
|
|
2174
|
+
const normalized2 = source.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
2175
|
+
const withoutFinalNewline = normalized2.endsWith("\n") ? normalized2.slice(0, -1) : normalized2;
|
|
2121
2176
|
return withoutFinalNewline.split("\n").length;
|
|
2122
2177
|
}
|
|
2123
2178
|
function countStructuralLines(source) {
|
|
@@ -2130,8 +2185,8 @@ function isCaptionCue(tag) {
|
|
|
2130
2185
|
}
|
|
2131
2186
|
function isRegistrySourceFile(filePath) {
|
|
2132
2187
|
if (!filePath) return false;
|
|
2133
|
-
const
|
|
2134
|
-
return /(?:^|\/)registry\/blocks\/([^/]+)\/\1\.html$/i.test(
|
|
2188
|
+
const normalized2 = filePath.replace(/\\/g, "/");
|
|
2189
|
+
return /(?:^|\/)registry\/blocks\/([^/]+)\/\1\.html$/i.test(normalized2);
|
|
2135
2190
|
}
|
|
2136
2191
|
function isRegistryInstalledFile(rawSource) {
|
|
2137
2192
|
return /^\s*<!--\s*hyperframes-registry-item:[^>]*-->/i.test(rawSource.slice(0, 512));
|
|
@@ -3081,8 +3136,8 @@ function zeroValue(value) {
|
|
|
3081
3136
|
return Number(value.trim()) === 0;
|
|
3082
3137
|
}
|
|
3083
3138
|
function isGsapColorProperty(property) {
|
|
3084
|
-
const
|
|
3085
|
-
return !
|
|
3139
|
+
const normalized2 = property.toLowerCase();
|
|
3140
|
+
return !normalized2.startsWith("--") && (normalized2.endsWith("color") || normalized2 === "fill" || normalized2 === "stroke");
|
|
3086
3141
|
}
|
|
3087
3142
|
function collectStaticCssVariableDefinitions(tags, styles) {
|
|
3088
3143
|
const definitions = /* @__PURE__ */ new Set();
|
|
@@ -3209,10 +3264,10 @@ function styleHasHiddenInitialState(style) {
|
|
|
3209
3264
|
function styleHasOpaqueBackground(style) {
|
|
3210
3265
|
const background = readStyleProperty(style, "background") || readStyleProperty(style, "background-color");
|
|
3211
3266
|
if (!background) return false;
|
|
3212
|
-
const
|
|
3213
|
-
if (
|
|
3214
|
-
if (/rgba?\([^)]*,0(?:\.0+)?\)$/.test(
|
|
3215
|
-
if (/hsla?\([^)]*,0(?:\.0+)?\)$/.test(
|
|
3267
|
+
const normalized2 = background.toLowerCase().replace(/\s+/g, "");
|
|
3268
|
+
if (normalized2 === "transparent" || normalized2 === "none") return false;
|
|
3269
|
+
if (/rgba?\([^)]*,0(?:\.0+)?\)$/.test(normalized2)) return false;
|
|
3270
|
+
if (/hsla?\([^)]*,0(?:\.0+)?\)$/.test(normalized2)) return false;
|
|
3216
3271
|
return true;
|
|
3217
3272
|
}
|
|
3218
3273
|
function styleLooksFullFrameOverlay(style) {
|
|
@@ -3594,9 +3649,9 @@ function elementLevelTokens(tokens, tagsByToken) {
|
|
|
3594
3649
|
return expanded;
|
|
3595
3650
|
}
|
|
3596
3651
|
function isMultiComponentDasharray(value) {
|
|
3597
|
-
const
|
|
3598
|
-
if (!
|
|
3599
|
-
return
|
|
3652
|
+
const normalized2 = value.replace(/!important\s*$/i, "").trim();
|
|
3653
|
+
if (!normalized2 || /^none$/i.test(normalized2)) return false;
|
|
3654
|
+
return normalized2.split(/[\s,]+/).filter(Boolean).length >= 2;
|
|
3600
3655
|
}
|
|
3601
3656
|
function gsapDasharrayValueLooksMultiComponent(valueSource) {
|
|
3602
3657
|
const literal = valueSource.trim().match(/^(["'`])([\s\S]*)\1$/)?.[2];
|
|
@@ -5588,7 +5643,15 @@ function collectFindings(produced, seen, filePath, into) {
|
|
|
5588
5643
|
async function lintHyperframeHtml(html, options = {}) {
|
|
5589
5644
|
const startedAt = performance.now();
|
|
5590
5645
|
const ctx = buildLintContext(html, options);
|
|
5591
|
-
const { findings, timings } = await runRules(ctx, options.filePath);
|
|
5646
|
+
const { findings: rawFindings, timings } = await runRules(ctx, options.filePath);
|
|
5647
|
+
const locate = createSourceLocator(html);
|
|
5648
|
+
const findings = rawFindings.map((finding) => {
|
|
5649
|
+
if (finding.snippet && options.externalStyles?.some(
|
|
5650
|
+
(style) => style.content.replace(/\s+/g, " ").includes(finding.snippet.replace(/\.\.\.$/, ""))
|
|
5651
|
+
))
|
|
5652
|
+
return finding;
|
|
5653
|
+
return { ...finding, ...locate(finding) };
|
|
5654
|
+
});
|
|
5592
5655
|
const errorCount = findings.filter((f) => f.severity === "error").length;
|
|
5593
5656
|
const warningCount = findings.filter((f) => f.severity === "warning").length;
|
|
5594
5657
|
const infoCount = findings.filter((f) => f.severity === "info").length;
|