@hyperframes/lint 0.8.220 → 0.8.222
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 +1 -0
- package/dist/browser.js +18 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +118 -7
- package/package.json +2 -2
package/dist/browser.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ type HyperframeLinterOptions = {
|
|
|
51
51
|
* render time — the font will silently fall back to whatever the OS provides.
|
|
52
52
|
*/
|
|
53
53
|
distributed?: boolean;
|
|
54
|
+
projectFontFamilies?: string[];
|
|
54
55
|
/** Who is running the lint: Studio raises the structure rules to errors, the CLI keeps them warnings. */
|
|
55
56
|
host?: "studio" | "cli";
|
|
56
57
|
};
|
package/dist/browser.js
CHANGED
|
@@ -4309,7 +4309,7 @@ function hiddenSetTargetSelectors(target, aliases) {
|
|
|
4309
4309
|
const parts = target.startsWith("[") && target.endsWith("]") ? target.slice(1, -1).split(",") : [target];
|
|
4310
4310
|
return parts.flatMap((part) => {
|
|
4311
4311
|
const trimmed = part.trim();
|
|
4312
|
-
const resolved = /^(["'`])([^"'`]+)\1$/.exec(trimmed)?.[2] ?? aliases.get(trimmed);
|
|
4312
|
+
const resolved = /^(["'`])([^"'`]+)\1$/.exec(trimmed)?.[2] ?? /^[\w$.]+\.querySelector(?:All)?\(\s*(["'`])([^"'`]+)\1\s*\)$/.exec(trimmed)?.[2] ?? aliases.get(trimmed);
|
|
4313
4313
|
return resolved === void 0 ? [] : resolved.split(",");
|
|
4314
4314
|
}).map((selector) => selector.trim()).filter((selector) => selector.length > 0);
|
|
4315
4315
|
}
|
|
@@ -4323,7 +4323,12 @@ function extractStandaloneHiddenSelectors(script) {
|
|
|
4323
4323
|
)) {
|
|
4324
4324
|
aliases.set(match2[1] ?? "", match2[3] ?? "");
|
|
4325
4325
|
}
|
|
4326
|
-
const
|
|
4326
|
+
for (const match2 of source.matchAll(
|
|
4327
|
+
/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*[\w$.]+\.querySelector(?:All)?\(\s*(["'`])([^"'`]+)\2\s*\)/g
|
|
4328
|
+
)) {
|
|
4329
|
+
aliases.set(match2[1] ?? "", match2[3] ?? "");
|
|
4330
|
+
}
|
|
4331
|
+
const pattern = /gsap\.set\s*\(\s*(\[[^[\]]*\]|"[^"]*"|'[^']*'|`[^`]*`|[\w$.]+\.querySelector(?:All)?\(\s*(?:"[^"]*"|'[^']*'|`[^`]*`)\s*\)|[^,()[\]]+?)\s*,\s*\{([\s\S]*?)\}\s*\)/g;
|
|
4327
4332
|
let match;
|
|
4328
4333
|
while ((match = pattern.exec(source)) !== null) {
|
|
4329
4334
|
if (indexInsideNonIifeRange(match.index, source, functionRanges)) continue;
|
|
@@ -4984,7 +4989,12 @@ ${right.raw}`)
|
|
|
4984
4989
|
(win) => win.position <= SCENE_BOUNDARY_EPSILON_SECONDS && (isHiddenGsapState(win.propertyValues) || win.fromPropertyValues !== void 0 && isHiddenGsapState(win.fromPropertyValues))
|
|
4985
4990
|
);
|
|
4986
4991
|
if (startsHiddenAtZero) continue;
|
|
4987
|
-
if (selectors.some(
|
|
4992
|
+
if (selectors.some(
|
|
4993
|
+
(selector2) => [...authoredHiddenSelectors].some(
|
|
4994
|
+
(hidden) => hidden === selector2 || targetedSelectorTokens(hidden).has(selector2)
|
|
4995
|
+
)
|
|
4996
|
+
))
|
|
4997
|
+
continue;
|
|
4988
4998
|
const firstVisible = visibilityWindows.find((win) => makesOverlayVisible(win));
|
|
4989
4999
|
if (!firstVisible) continue;
|
|
4990
5000
|
const selector = selectors.find(
|
|
@@ -6429,6 +6439,9 @@ function extractFontFaceFamilies(styles) {
|
|
|
6429
6439
|
}
|
|
6430
6440
|
return families;
|
|
6431
6441
|
}
|
|
6442
|
+
function declaredFontFamilies(styles, options) {
|
|
6443
|
+
return /* @__PURE__ */ new Set([...extractFontFaceFamilies(styles), ...options.projectFontFamilies ?? []]);
|
|
6444
|
+
}
|
|
6432
6445
|
function normalizeUsedFontName(part) {
|
|
6433
6446
|
const name = part.trim().replace(/\s*!important\s*$/i, "").replace(/^['"]|['"]$/g, "").trim().toLowerCase();
|
|
6434
6447
|
if (!name || name.includes("(") || name.includes(")")) return null;
|
|
@@ -6538,7 +6551,7 @@ var fontRules = [
|
|
|
6538
6551
|
// so there is nothing for the author to act on.
|
|
6539
6552
|
({ styles, options }) => {
|
|
6540
6553
|
if (!options.distributed) return [];
|
|
6541
|
-
const declared =
|
|
6554
|
+
const declared = declaredFontFamilies(styles, options);
|
|
6542
6555
|
const used = extractUsedFontFamilies(styles);
|
|
6543
6556
|
const aliased = collectAliasedFonts(used, declared);
|
|
6544
6557
|
if (aliased.length === 0) return [];
|
|
@@ -6554,7 +6567,7 @@ var fontRules = [
|
|
|
6554
6567
|
({ styles, source, rawSource, options, locate }) => {
|
|
6555
6568
|
if (isRegistrySourceFile(options.filePath) || isRegistryInstalledFile(rawSource)) return [];
|
|
6556
6569
|
const findings = [];
|
|
6557
|
-
const declared =
|
|
6570
|
+
const declared = declaredFontFamilies(styles, options);
|
|
6558
6571
|
const used = extractUsedFontFamilies(styles);
|
|
6559
6572
|
const googleFonts = collectGoogleFontFamilies(source, styles);
|
|
6560
6573
|
const undeclared = used.filter(
|
package/dist/index.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ type HyperframeLinterOptions = {
|
|
|
51
51
|
* render time — the font will silently fall back to whatever the OS provides.
|
|
52
52
|
*/
|
|
53
53
|
distributed?: boolean;
|
|
54
|
+
projectFontFamilies?: string[];
|
|
54
55
|
/** Who is running the lint: Studio raises the structure rules to errors, the CLI keeps them warnings. */
|
|
55
56
|
host?: "studio" | "cli";
|
|
56
57
|
};
|
package/dist/index.js
CHANGED
|
@@ -4315,7 +4315,7 @@ function hiddenSetTargetSelectors(target, aliases) {
|
|
|
4315
4315
|
const parts = target.startsWith("[") && target.endsWith("]") ? target.slice(1, -1).split(",") : [target];
|
|
4316
4316
|
return parts.flatMap((part) => {
|
|
4317
4317
|
const trimmed = part.trim();
|
|
4318
|
-
const resolved = /^(["'`])([^"'`]+)\1$/.exec(trimmed)?.[2] ?? aliases.get(trimmed);
|
|
4318
|
+
const resolved = /^(["'`])([^"'`]+)\1$/.exec(trimmed)?.[2] ?? /^[\w$.]+\.querySelector(?:All)?\(\s*(["'`])([^"'`]+)\1\s*\)$/.exec(trimmed)?.[2] ?? aliases.get(trimmed);
|
|
4319
4319
|
return resolved === void 0 ? [] : resolved.split(",");
|
|
4320
4320
|
}).map((selector) => selector.trim()).filter((selector) => selector.length > 0);
|
|
4321
4321
|
}
|
|
@@ -4329,7 +4329,12 @@ function extractStandaloneHiddenSelectors(script) {
|
|
|
4329
4329
|
)) {
|
|
4330
4330
|
aliases.set(match2[1] ?? "", match2[3] ?? "");
|
|
4331
4331
|
}
|
|
4332
|
-
const
|
|
4332
|
+
for (const match2 of source.matchAll(
|
|
4333
|
+
/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*[\w$.]+\.querySelector(?:All)?\(\s*(["'`])([^"'`]+)\2\s*\)/g
|
|
4334
|
+
)) {
|
|
4335
|
+
aliases.set(match2[1] ?? "", match2[3] ?? "");
|
|
4336
|
+
}
|
|
4337
|
+
const pattern = /gsap\.set\s*\(\s*(\[[^[\]]*\]|"[^"]*"|'[^']*'|`[^`]*`|[\w$.]+\.querySelector(?:All)?\(\s*(?:"[^"]*"|'[^']*'|`[^`]*`)\s*\)|[^,()[\]]+?)\s*,\s*\{([\s\S]*?)\}\s*\)/g;
|
|
4333
4338
|
let match;
|
|
4334
4339
|
while ((match = pattern.exec(source)) !== null) {
|
|
4335
4340
|
if (indexInsideNonIifeRange(match.index, source, functionRanges)) continue;
|
|
@@ -4990,7 +4995,12 @@ ${right.raw}`)
|
|
|
4990
4995
|
(win) => win.position <= SCENE_BOUNDARY_EPSILON_SECONDS && (isHiddenGsapState(win.propertyValues) || win.fromPropertyValues !== void 0 && isHiddenGsapState(win.fromPropertyValues))
|
|
4991
4996
|
);
|
|
4992
4997
|
if (startsHiddenAtZero) continue;
|
|
4993
|
-
if (selectors.some(
|
|
4998
|
+
if (selectors.some(
|
|
4999
|
+
(selector2) => [...authoredHiddenSelectors].some(
|
|
5000
|
+
(hidden) => hidden === selector2 || targetedSelectorTokens(hidden).has(selector2)
|
|
5001
|
+
)
|
|
5002
|
+
))
|
|
5003
|
+
continue;
|
|
4994
5004
|
const firstVisible = visibilityWindows.find((win) => makesOverlayVisible(win));
|
|
4995
5005
|
if (!firstVisible) continue;
|
|
4996
5006
|
const selector = selectors.find(
|
|
@@ -6435,6 +6445,9 @@ function extractFontFaceFamilies(styles) {
|
|
|
6435
6445
|
}
|
|
6436
6446
|
return families;
|
|
6437
6447
|
}
|
|
6448
|
+
function declaredFontFamilies(styles, options) {
|
|
6449
|
+
return /* @__PURE__ */ new Set([...extractFontFaceFamilies(styles), ...options.projectFontFamilies ?? []]);
|
|
6450
|
+
}
|
|
6438
6451
|
function normalizeUsedFontName(part) {
|
|
6439
6452
|
const name = part.trim().replace(/\s*!important\s*$/i, "").replace(/^['"]|['"]$/g, "").trim().toLowerCase();
|
|
6440
6453
|
if (!name || name.includes("(") || name.includes(")")) return null;
|
|
@@ -6544,7 +6557,7 @@ var fontRules = [
|
|
|
6544
6557
|
// so there is nothing for the author to act on.
|
|
6545
6558
|
({ styles, options }) => {
|
|
6546
6559
|
if (!options.distributed) return [];
|
|
6547
|
-
const declared =
|
|
6560
|
+
const declared = declaredFontFamilies(styles, options);
|
|
6548
6561
|
const used = extractUsedFontFamilies(styles);
|
|
6549
6562
|
const aliased = collectAliasedFonts(used, declared);
|
|
6550
6563
|
if (aliased.length === 0) return [];
|
|
@@ -6560,7 +6573,7 @@ var fontRules = [
|
|
|
6560
6573
|
({ styles, source, rawSource, options, locate }) => {
|
|
6561
6574
|
if (isRegistrySourceFile(options.filePath) || isRegistryInstalledFile(rawSource)) return [];
|
|
6562
6575
|
const findings = [];
|
|
6563
|
-
const declared =
|
|
6576
|
+
const declared = declaredFontFamilies(styles, options);
|
|
6564
6577
|
const used = extractUsedFontFamilies(styles);
|
|
6565
6578
|
const googleFonts = collectGoogleFontFamilies(source, styles);
|
|
6566
6579
|
const undeclared = used.filter(
|
|
@@ -7512,6 +7525,42 @@ function resolveCssAssetCandidates(projectDir, url, htmlCompSrcPath, cssRootRela
|
|
|
7512
7525
|
}
|
|
7513
7526
|
return resolveLocalAssetCandidates(projectDir, url);
|
|
7514
7527
|
}
|
|
7528
|
+
function mountedSources(projectDir, rootHtml, rootCompSrcPath) {
|
|
7529
|
+
const sources = [{ html: rootHtml, compSrcPath: rootCompSrcPath }];
|
|
7530
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7531
|
+
for (const source of sources) {
|
|
7532
|
+
const { document } = parseHTML3(source.html);
|
|
7533
|
+
for (const host of querySelectorAllIncludingTemplates(document, "[data-composition-src]")) {
|
|
7534
|
+
const src = host.getAttribute("data-composition-src")?.trim();
|
|
7535
|
+
if (!src || isUnresolvedAssetPlaceholder2(src) || isRemoteOrInlineUrl2(src)) continue;
|
|
7536
|
+
const path = resolve(projectDir, src);
|
|
7537
|
+
if (seen.has(path) || !isWithinProjectRoot2(projectDir, path)) continue;
|
|
7538
|
+
seen.add(path);
|
|
7539
|
+
const read = readProjectFile2(path);
|
|
7540
|
+
if (read.kind !== "file") continue;
|
|
7541
|
+
sources.push({
|
|
7542
|
+
html: read.text,
|
|
7543
|
+
compSrcPath: relative2(resolve(projectDir), path).replace(/\\/g, "/")
|
|
7544
|
+
});
|
|
7545
|
+
}
|
|
7546
|
+
}
|
|
7547
|
+
return sources;
|
|
7548
|
+
}
|
|
7549
|
+
function declaredProjectFontFamilies(projectDir, sources) {
|
|
7550
|
+
const families = /* @__PURE__ */ new Set();
|
|
7551
|
+
for (const { html, compSrcPath } of sources) {
|
|
7552
|
+
const { document } = parseHTML3(html);
|
|
7553
|
+
const styles = [
|
|
7554
|
+
...querySelectorAllIncludingTemplates(document, "style").map((style) => ({
|
|
7555
|
+
content: style.textContent ?? ""
|
|
7556
|
+
})),
|
|
7557
|
+
...collectLocalStylesheets(projectDir, document, compSrcPath)
|
|
7558
|
+
];
|
|
7559
|
+
for (const family of extractFontFaceFamilies(styles)) families.add(family);
|
|
7560
|
+
for (const family of collectGoogleFontFamilies(html, styles)) families.add(family);
|
|
7561
|
+
}
|
|
7562
|
+
return [...families];
|
|
7563
|
+
}
|
|
7515
7564
|
async function lintProject(projectDir, entryFile, hostOptions = {}) {
|
|
7516
7565
|
const indexPath = entryFile ? resolve(entryFile) : resolve(projectDir, "index.html");
|
|
7517
7566
|
if (entryFile && !isWithinProjectRoot2(projectDir, indexPath)) {
|
|
@@ -7524,11 +7573,16 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
|
|
|
7524
7573
|
let totalWarnings = 0;
|
|
7525
7574
|
let totalInfos = 0;
|
|
7526
7575
|
const rootHtml = readFileSync(indexPath, "utf-8");
|
|
7576
|
+
const projectFontFamilies = declaredProjectFontFamilies(
|
|
7577
|
+
projectDir,
|
|
7578
|
+
mountedSources(projectDir, rootHtml, rootCompSrcPath)
|
|
7579
|
+
);
|
|
7527
7580
|
const rootResult = await lintHyperframeHtml(rootHtml, {
|
|
7528
7581
|
...hostOptions,
|
|
7529
7582
|
filePath: indexPath,
|
|
7530
7583
|
compSrcPath: rootFile,
|
|
7531
|
-
externalStyles: collectExternalStyles(projectDir, rootHtml, rootCompSrcPath)
|
|
7584
|
+
externalStyles: collectExternalStyles(projectDir, rootHtml, rootCompSrcPath),
|
|
7585
|
+
projectFontFamilies
|
|
7532
7586
|
});
|
|
7533
7587
|
results.push({ file: rootFile, result: rootResult, contentHash: contentDigest(rootHtml) });
|
|
7534
7588
|
totalErrors += rootResult.errorCount;
|
|
@@ -7562,7 +7616,8 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
|
|
|
7562
7616
|
filePath,
|
|
7563
7617
|
isSubComposition: true,
|
|
7564
7618
|
compSrcPath,
|
|
7565
|
-
externalStyles: collectExternalStyles(projectDir, html, compSrcPath)
|
|
7619
|
+
externalStyles: collectExternalStyles(projectDir, html, compSrcPath),
|
|
7620
|
+
projectFontFamilies
|
|
7566
7621
|
});
|
|
7567
7622
|
results.push({
|
|
7568
7623
|
file: `compositions/${file}`,
|
|
@@ -7580,6 +7635,7 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
|
|
|
7580
7635
|
...lintAudioSrcNotFound(projectDir, allHtmlSources),
|
|
7581
7636
|
...lintMissingLocalAsset(projectDir, allHtmlSources),
|
|
7582
7637
|
...lintTextureMaskAssetNotFound(projectDir, allHtmlSources),
|
|
7638
|
+
...lintLinkedStylesheetAssets(projectDir, allHtmlSources),
|
|
7583
7639
|
...!entryFile ? lintMultipleRootCompositions(projectDir) : [],
|
|
7584
7640
|
...!entryFile ? lintBlankRootWithStandaloneComposition(rootHtml, allHtmlSources) : [],
|
|
7585
7641
|
...lintDuplicateAudioTracks(projectDir, rootHtml),
|
|
@@ -7789,6 +7845,61 @@ function lintTextureMaskAssetNotFound(projectDir, htmlSources) {
|
|
|
7789
7845
|
};
|
|
7790
7846
|
});
|
|
7791
7847
|
}
|
|
7848
|
+
function nearestAncestorMatch(projectDir, cssRootRelativePath, url) {
|
|
7849
|
+
const root = resolve(projectDir);
|
|
7850
|
+
for (let dir = resolve(root, dirname2(cssRootRelativePath), ".."); ; dir = dirname2(dir)) {
|
|
7851
|
+
if (!isWithinProjectRoot2(projectDir, dir)) return null;
|
|
7852
|
+
if (resolveLocalAssetCandidates(projectDir, relative2(root, join3(dir, url))).some(existsSync2)) {
|
|
7853
|
+
return relative2(dirname2(resolve(root, cssRootRelativePath)), join3(dir, url)).replace(
|
|
7854
|
+
/\\/g,
|
|
7855
|
+
"/"
|
|
7856
|
+
);
|
|
7857
|
+
}
|
|
7858
|
+
if (dir === root) return null;
|
|
7859
|
+
}
|
|
7860
|
+
}
|
|
7861
|
+
function lintLinkedStylesheetAssets(projectDir, htmlSources) {
|
|
7862
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7863
|
+
const findings = [];
|
|
7864
|
+
for (const { html, compSrcPath } of htmlSources) {
|
|
7865
|
+
for (const { content, rootRelativePath } of collectLocalStylesheets(
|
|
7866
|
+
projectDir,
|
|
7867
|
+
parseHTML3(html).document,
|
|
7868
|
+
compSrcPath
|
|
7869
|
+
)) {
|
|
7870
|
+
let root;
|
|
7871
|
+
try {
|
|
7872
|
+
root = postcss4.parse(content);
|
|
7873
|
+
} catch (error) {
|
|
7874
|
+
if (!(error instanceof postcss4.CssSyntaxError)) throw error;
|
|
7875
|
+
continue;
|
|
7876
|
+
}
|
|
7877
|
+
root.walkDecls((declaration) => {
|
|
7878
|
+
if (/^(?:-webkit-)?mask-image$/i.test(declaration.prop)) return;
|
|
7879
|
+
for (const match of declaration.value.matchAll(CSS_URL_RE)) {
|
|
7880
|
+
const rawUrl = match[1] ?? match[2] ?? match[3] ?? "";
|
|
7881
|
+
if (isUnresolvedAssetPlaceholder2(rawUrl)) continue;
|
|
7882
|
+
const url = cleanAssetUrl2(rawUrl);
|
|
7883
|
+
if (!url || isRemoteOrInlineUrl2(url) || url.startsWith("/")) continue;
|
|
7884
|
+
if (resolveCssAssetCandidates(projectDir, url, void 0, rootRelativePath).some(existsSync2))
|
|
7885
|
+
continue;
|
|
7886
|
+
const key = `${rootRelativePath}\0${url}`;
|
|
7887
|
+
if (seen.has(key)) continue;
|
|
7888
|
+
seen.add(key);
|
|
7889
|
+
const found = nearestAncestorMatch(projectDir, rootRelativePath, url);
|
|
7890
|
+
findings.push({
|
|
7891
|
+
code: "missing_stylesheet_asset",
|
|
7892
|
+
severity: "warning",
|
|
7893
|
+
file: join3(projectDir, rootRelativePath),
|
|
7894
|
+
message: `${rootRelativePath} references url(${url}), which is not found beside the stylesheet (looked in ${dirname2(rootRelativePath)}/). The browser resolves url() from the stylesheet, so the file will 404.`,
|
|
7895
|
+
fixHint: found ? `A file exists at "${found}" relative to the stylesheet. Change the url() to that path.` : `Add "${url}" beside the stylesheet, or point the url() at an existing file.`
|
|
7896
|
+
});
|
|
7897
|
+
}
|
|
7898
|
+
});
|
|
7899
|
+
}
|
|
7900
|
+
}
|
|
7901
|
+
return findings;
|
|
7902
|
+
}
|
|
7792
7903
|
function lintMultipleRootCompositions(projectDir) {
|
|
7793
7904
|
const findings = [];
|
|
7794
7905
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/lint",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.222",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/heygen-com/hyperframes",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"linkedom": "^0.18.12",
|
|
36
36
|
"postcss": "^8.5.8",
|
|
37
37
|
"postcss-selector-parser": "^7.1.4",
|
|
38
|
-
"@hyperframes/parsers": "0.8.
|
|
38
|
+
"@hyperframes/parsers": "0.8.222"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.0.10",
|