@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/browser.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/browser.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,10 +560,62 @@ 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)}...`;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/sourceLocations.ts
|
|
570
|
+
function normalized(source) {
|
|
571
|
+
let text = "";
|
|
572
|
+
const offsets = [];
|
|
573
|
+
for (let i = 0; i < source.length; i++) {
|
|
574
|
+
const char = source[i];
|
|
575
|
+
if (/\s/.test(char)) {
|
|
576
|
+
if (text.endsWith(" ")) continue;
|
|
577
|
+
text += " ";
|
|
578
|
+
} else text += char;
|
|
579
|
+
offsets.push(i);
|
|
580
|
+
}
|
|
581
|
+
return { text, offsets };
|
|
582
|
+
}
|
|
583
|
+
function createSourceLocator(source) {
|
|
584
|
+
const tags = parseHtmlStructure(source).tags;
|
|
585
|
+
const searchable = source.split("");
|
|
586
|
+
const parser = new Parser2({
|
|
587
|
+
oncomment() {
|
|
588
|
+
searchable.fill("\0", parser.startIndex, parser.endIndex + 1);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
parser.write(source);
|
|
592
|
+
parser.end();
|
|
593
|
+
const normalizedSource = normalized(searchable.join(""));
|
|
594
|
+
const at = (offset) => {
|
|
595
|
+
const before = source.slice(0, offset);
|
|
596
|
+
return {
|
|
597
|
+
line: before.split(/\r\n|\r|\n/).length,
|
|
598
|
+
column: offset - Math.max(before.lastIndexOf("\n"), before.lastIndexOf("\r"))
|
|
599
|
+
};
|
|
600
|
+
};
|
|
601
|
+
return (finding) => {
|
|
602
|
+
if (finding.line !== void 0) return { line: finding.line, column: finding.column };
|
|
603
|
+
if (finding.snippet) {
|
|
604
|
+
const offset = snippetOffset(normalizedSource, finding.snippet);
|
|
605
|
+
return offset === void 0 ? {} : at(offset);
|
|
606
|
+
}
|
|
607
|
+
const id = finding.elementId ?? /^#([\w-]+)$/.exec(finding.selector ?? "")?.[1];
|
|
608
|
+
if (!id) return {};
|
|
609
|
+
const matches = tags.filter((tag) => readDecodedAttr(tag.raw, "id") === id);
|
|
610
|
+
return matches.length === 1 ? at(matches[0].index) : {};
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function snippetOffset(source, snippet) {
|
|
614
|
+
const needle = snippet.replace(/\s+/g, " ").trim().replace(/\.\.\.$/, "");
|
|
615
|
+
if (!needle) return void 0;
|
|
616
|
+
const index = source.text.indexOf(needle);
|
|
617
|
+
if (index < 0 || source.text.indexOf(needle, index + 1) >= 0) return void 0;
|
|
618
|
+
return source.offsets[index];
|
|
564
619
|
}
|
|
565
620
|
|
|
566
621
|
// src/context.ts
|
|
@@ -1363,18 +1418,18 @@ function classNamesFromAttr(classAttr) {
|
|
|
1363
1418
|
return classAttr.split(/\s+/).filter(Boolean);
|
|
1364
1419
|
}
|
|
1365
1420
|
function selectorTargetsManagedMedia(selector, mediaIndex) {
|
|
1366
|
-
const
|
|
1367
|
-
if (!
|
|
1368
|
-
if (mediaIndex.hasVideo && /\bvideo\b/i.test(
|
|
1369
|
-
if (mediaIndex.hasAudio && /\baudio\b/i.test(
|
|
1421
|
+
const normalized2 = selector.trim();
|
|
1422
|
+
if (!normalized2) return false;
|
|
1423
|
+
if (mediaIndex.hasVideo && /\bvideo\b/i.test(normalized2)) return true;
|
|
1424
|
+
if (mediaIndex.hasAudio && /\baudio\b/i.test(normalized2)) return true;
|
|
1370
1425
|
for (const mediaId of mediaIndex.ids) {
|
|
1371
1426
|
const escapedId = escapeRegExp(mediaId);
|
|
1372
|
-
if (new RegExp(`#${escapedId}(?![\\w-])`).test(
|
|
1427
|
+
if (new RegExp(`#${escapedId}(?![\\w-])`).test(normalized2) || normalized2.includes(`[id="${mediaId}"]`) || normalized2.includes(`[id='${mediaId}']`)) {
|
|
1373
1428
|
return true;
|
|
1374
1429
|
}
|
|
1375
1430
|
}
|
|
1376
1431
|
for (const className of mediaIndex.classes) {
|
|
1377
|
-
if (new RegExp(`\\.${escapeRegExp(className)}(?![\\w-])`).test(
|
|
1432
|
+
if (new RegExp(`\\.${escapeRegExp(className)}(?![\\w-])`).test(normalized2)) {
|
|
1378
1433
|
return true;
|
|
1379
1434
|
}
|
|
1380
1435
|
}
|
|
@@ -2113,8 +2168,8 @@ function readTagTiming(rawTag) {
|
|
|
2113
2168
|
}
|
|
2114
2169
|
function countPhysicalLines(source) {
|
|
2115
2170
|
if (source.length === 0) return 0;
|
|
2116
|
-
const
|
|
2117
|
-
const withoutFinalNewline =
|
|
2171
|
+
const normalized2 = source.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
2172
|
+
const withoutFinalNewline = normalized2.endsWith("\n") ? normalized2.slice(0, -1) : normalized2;
|
|
2118
2173
|
return withoutFinalNewline.split("\n").length;
|
|
2119
2174
|
}
|
|
2120
2175
|
function countStructuralLines(source) {
|
|
@@ -2127,8 +2182,8 @@ function isCaptionCue(tag) {
|
|
|
2127
2182
|
}
|
|
2128
2183
|
function isRegistrySourceFile(filePath) {
|
|
2129
2184
|
if (!filePath) return false;
|
|
2130
|
-
const
|
|
2131
|
-
return /(?:^|\/)registry\/blocks\/([^/]+)\/\1\.html$/i.test(
|
|
2185
|
+
const normalized2 = filePath.replace(/\\/g, "/");
|
|
2186
|
+
return /(?:^|\/)registry\/blocks\/([^/]+)\/\1\.html$/i.test(normalized2);
|
|
2132
2187
|
}
|
|
2133
2188
|
function isRegistryInstalledFile(rawSource) {
|
|
2134
2189
|
return /^\s*<!--\s*hyperframes-registry-item:[^>]*-->/i.test(rawSource.slice(0, 512));
|
|
@@ -3078,8 +3133,8 @@ function zeroValue(value) {
|
|
|
3078
3133
|
return Number(value.trim()) === 0;
|
|
3079
3134
|
}
|
|
3080
3135
|
function isGsapColorProperty(property) {
|
|
3081
|
-
const
|
|
3082
|
-
return !
|
|
3136
|
+
const normalized2 = property.toLowerCase();
|
|
3137
|
+
return !normalized2.startsWith("--") && (normalized2.endsWith("color") || normalized2 === "fill" || normalized2 === "stroke");
|
|
3083
3138
|
}
|
|
3084
3139
|
function collectStaticCssVariableDefinitions(tags, styles) {
|
|
3085
3140
|
const definitions = /* @__PURE__ */ new Set();
|
|
@@ -3206,10 +3261,10 @@ function styleHasHiddenInitialState(style) {
|
|
|
3206
3261
|
function styleHasOpaqueBackground(style) {
|
|
3207
3262
|
const background = readStyleProperty(style, "background") || readStyleProperty(style, "background-color");
|
|
3208
3263
|
if (!background) return false;
|
|
3209
|
-
const
|
|
3210
|
-
if (
|
|
3211
|
-
if (/rgba?\([^)]*,0(?:\.0+)?\)$/.test(
|
|
3212
|
-
if (/hsla?\([^)]*,0(?:\.0+)?\)$/.test(
|
|
3264
|
+
const normalized2 = background.toLowerCase().replace(/\s+/g, "");
|
|
3265
|
+
if (normalized2 === "transparent" || normalized2 === "none") return false;
|
|
3266
|
+
if (/rgba?\([^)]*,0(?:\.0+)?\)$/.test(normalized2)) return false;
|
|
3267
|
+
if (/hsla?\([^)]*,0(?:\.0+)?\)$/.test(normalized2)) return false;
|
|
3213
3268
|
return true;
|
|
3214
3269
|
}
|
|
3215
3270
|
function styleLooksFullFrameOverlay(style) {
|
|
@@ -3591,9 +3646,9 @@ function elementLevelTokens(tokens, tagsByToken) {
|
|
|
3591
3646
|
return expanded;
|
|
3592
3647
|
}
|
|
3593
3648
|
function isMultiComponentDasharray(value) {
|
|
3594
|
-
const
|
|
3595
|
-
if (!
|
|
3596
|
-
return
|
|
3649
|
+
const normalized2 = value.replace(/!important\s*$/i, "").trim();
|
|
3650
|
+
if (!normalized2 || /^none$/i.test(normalized2)) return false;
|
|
3651
|
+
return normalized2.split(/[\s,]+/).filter(Boolean).length >= 2;
|
|
3597
3652
|
}
|
|
3598
3653
|
function gsapDasharrayValueLooksMultiComponent(valueSource) {
|
|
3599
3654
|
const literal = valueSource.trim().match(/^(["'`])([\s\S]*)\1$/)?.[2];
|
|
@@ -5585,7 +5640,15 @@ function collectFindings(produced, seen, filePath, into) {
|
|
|
5585
5640
|
async function lintHyperframeHtml(html, options = {}) {
|
|
5586
5641
|
const startedAt = performance.now();
|
|
5587
5642
|
const ctx = buildLintContext(html, options);
|
|
5588
|
-
const { findings, timings } = await runRules(ctx, options.filePath);
|
|
5643
|
+
const { findings: rawFindings, timings } = await runRules(ctx, options.filePath);
|
|
5644
|
+
const locate = createSourceLocator(html);
|
|
5645
|
+
const findings = rawFindings.map((finding) => {
|
|
5646
|
+
if (finding.snippet && options.externalStyles?.some(
|
|
5647
|
+
(style) => style.content.replace(/\s+/g, " ").includes(finding.snippet.replace(/\.\.\.$/, ""))
|
|
5648
|
+
))
|
|
5649
|
+
return finding;
|
|
5650
|
+
return { ...finding, ...locate(finding) };
|
|
5651
|
+
});
|
|
5589
5652
|
const errorCount = findings.filter((f) => f.severity === "error").length;
|
|
5590
5653
|
const warningCount = findings.filter((f) => f.severity === "warning").length;
|
|
5591
5654
|
const infoCount = findings.filter((f) => f.severity === "info").length;
|