@hyperframes/lint 0.8.190 → 0.8.192
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.js +77 -35
- package/dist/index.d.ts +1 -1
- package/dist/index.js +89 -38
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -1725,7 +1725,8 @@ var IMAGE_SRC_EXT = /* @__PURE__ */ new Set([
|
|
|
1725
1725
|
"heic",
|
|
1726
1726
|
"heif",
|
|
1727
1727
|
"tiff",
|
|
1728
|
-
"ico"
|
|
1728
|
+
"ico",
|
|
1729
|
+
"avif"
|
|
1729
1730
|
]);
|
|
1730
1731
|
var VIDEO_SRC_EXT = /* @__PURE__ */ new Set([
|
|
1731
1732
|
"mp4",
|
|
@@ -1739,7 +1740,7 @@ var VIDEO_SRC_EXT = /* @__PURE__ */ new Set([
|
|
|
1739
1740
|
"mpg",
|
|
1740
1741
|
"mpeg"
|
|
1741
1742
|
]);
|
|
1742
|
-
var AUDIO_SRC_EXT = /* @__PURE__ */ new Set(["mp3", "wav", "aac", "flac", "opus", "aiff", "wma"]);
|
|
1743
|
+
var AUDIO_SRC_EXT = /* @__PURE__ */ new Set(["mp3", "wav", "aac", "flac", "opus", "aiff", "wma", "m4a"]);
|
|
1743
1744
|
var SRC_KIND_NOUN = {
|
|
1744
1745
|
image: "an image",
|
|
1745
1746
|
video: "a video",
|
|
@@ -2617,7 +2618,11 @@ function containingCallOffset(script, tokenOffset) {
|
|
|
2617
2618
|
}
|
|
2618
2619
|
|
|
2619
2620
|
// src/rules/composition.ts
|
|
2620
|
-
import {
|
|
2621
|
+
import {
|
|
2622
|
+
COMPOSITION_VARIABLE_TYPES,
|
|
2623
|
+
isCompositionVariable,
|
|
2624
|
+
isSafeMediaUrl
|
|
2625
|
+
} from "@hyperframes/parsers/composition";
|
|
2621
2626
|
import { COMPOSITION_ATTRIBUTES, readClipTiming } from "@hyperframes/parsers/composition-contract";
|
|
2622
2627
|
import { resolveCompositionDuration } from "@hyperframes/parsers/composition-duration";
|
|
2623
2628
|
import {
|
|
@@ -2921,6 +2926,16 @@ function variablesDeclarationFindings(tag, tags) {
|
|
|
2921
2926
|
});
|
|
2922
2927
|
continue;
|
|
2923
2928
|
}
|
|
2929
|
+
if (!isCompositionVariable(e)) {
|
|
2930
|
+
findings.push({
|
|
2931
|
+
code: "invalid_composition_variables_declaration",
|
|
2932
|
+
severity: "error",
|
|
2933
|
+
message: `data-composition-variables entry [${i}] has a default that does not match its type, or an enum without an options array.`,
|
|
2934
|
+
fixHint: "Use number defaults for number variables, boolean defaults for boolean variables, and string defaults for other types. Enum variables also require an options array.",
|
|
2935
|
+
snippet: truncateSnippet(tag.raw)
|
|
2936
|
+
});
|
|
2937
|
+
continue;
|
|
2938
|
+
}
|
|
2924
2939
|
const id = String(e.id);
|
|
2925
2940
|
if ((e.type === "image" || varSrcIds.has(id)) && typeof e.default === "string" && e.default.length > 0 && !isSafeMediaUrl(e.default)) {
|
|
2926
2941
|
findings.push({
|
|
@@ -4959,7 +4974,7 @@ ${right.raw}`)
|
|
|
4959
4974
|
return findings;
|
|
4960
4975
|
},
|
|
4961
4976
|
// gsap_timeline_registered_before_async_build — registering window.__timelines[id]
|
|
4962
|
-
// BEFORE the timeline is built inside
|
|
4977
|
+
// BEFORE the timeline is built inside a font-ready callback
|
|
4963
4978
|
// leaves an EMPTY timeline registered. The runtime's sub-composition readiness gate
|
|
4964
4979
|
// treats "key present" as "ready" and nests the child ONCE, while still empty — so the
|
|
4965
4980
|
// animation never renders when this composition is mounted as a sub-composition.
|
|
@@ -4968,9 +4983,11 @@ ${right.raw}`)
|
|
|
4968
4983
|
const findings = [];
|
|
4969
4984
|
for (const script of scripts) {
|
|
4970
4985
|
const content = stripJsComments(script.content);
|
|
4971
|
-
const regIdx = content.search(
|
|
4986
|
+
const regIdx = content.search(
|
|
4987
|
+
/window\s*\.\s*__timelines\s*(?:\[[^\]]+\]|\.\s*[A-Za-z_$][\w$]*)\s*=\s*(?!=)/
|
|
4988
|
+
);
|
|
4972
4989
|
if (regIdx < 0) continue;
|
|
4973
|
-
const fontsReadyIdx = content.search(/document\s*\.\s*fonts\s*\.\s*ready/);
|
|
4990
|
+
const fontsReadyIdx = content.search(/document\s*\.\s*fonts\s*\.\s*(?:ready\b|load\s*\()/);
|
|
4974
4991
|
if (fontsReadyIdx < 0) continue;
|
|
4975
4992
|
if (regIdx >= fontsReadyIdx) continue;
|
|
4976
4993
|
const tail = content.slice(fontsReadyIdx);
|
|
@@ -4978,8 +4995,8 @@ ${right.raw}`)
|
|
|
4978
4995
|
findings.push({
|
|
4979
4996
|
code: "gsap_timeline_registered_before_async_build",
|
|
4980
4997
|
severity: "error",
|
|
4981
|
-
message: 'window.__timelines is assigned BEFORE the timeline is built inside document.fonts.ready. An empty timeline registered early gets nested empty when this composition is used as a sub-composition (the readiness gate treats "key present" as "ready" and never re-nests), so the animation renders blank.',
|
|
4982
|
-
fixHint: "Move the `window.__timelines[id] = tl;` assignment to the END of the
|
|
4998
|
+
message: 'window.__timelines is assigned BEFORE the timeline is built inside document.fonts.ready or document.fonts.load(). An empty timeline registered early gets nested empty when this composition is used as a sub-composition (the readiness gate treats "key present" as "ready" and never re-nests), so the animation renders blank.',
|
|
4999
|
+
fixHint: "Move the `window.__timelines[id] = tl;` assignment to the END of the font-ready callback, after the tweens are added. Optionally call window.__hfForceTimelineRebind() right after, to re-nest the populated timeline."
|
|
4983
5000
|
});
|
|
4984
5001
|
}
|
|
4985
5002
|
return findings;
|
|
@@ -6019,22 +6036,29 @@ var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
|
6019
6036
|
"unset",
|
|
6020
6037
|
"revert"
|
|
6021
6038
|
]);
|
|
6022
|
-
function
|
|
6023
|
-
|
|
6039
|
+
function parseFontCss(css) {
|
|
6040
|
+
try {
|
|
6041
|
+
return postcss3.parse(css);
|
|
6042
|
+
} catch {
|
|
6043
|
+
return null;
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
function isFontFaceDeclaration(decl) {
|
|
6047
|
+
let parent = decl.parent;
|
|
6048
|
+
while (parent) {
|
|
6049
|
+
if (parent.type === "atrule" && parent.name.toLowerCase() === "font-face") return true;
|
|
6050
|
+
parent = parent.parent;
|
|
6051
|
+
}
|
|
6052
|
+
return false;
|
|
6024
6053
|
}
|
|
6025
6054
|
function extractFontFaceFamilies(styles) {
|
|
6026
6055
|
const families = /* @__PURE__ */ new Set();
|
|
6027
|
-
const fontFaceRe = /@font-face\s*\{[^}]*\}/gi;
|
|
6028
|
-
const familyRe = /font-family\s*:\s*(['"]?)([^;'"]+)\1/i;
|
|
6029
6056
|
for (const style of styles) {
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
families.add(familyMatch[2].trim().toLowerCase());
|
|
6036
|
-
}
|
|
6037
|
-
}
|
|
6057
|
+
parseFontCss(style.content)?.walkDecls(/^font-family$/i, (decl) => {
|
|
6058
|
+
if (!isFontFaceDeclaration(decl)) return;
|
|
6059
|
+
const name = normalizeUsedFontName(decl.value);
|
|
6060
|
+
if (name) families.add(name);
|
|
6061
|
+
});
|
|
6038
6062
|
}
|
|
6039
6063
|
return families;
|
|
6040
6064
|
}
|
|
@@ -6043,22 +6067,43 @@ function normalizeUsedFontName(part) {
|
|
|
6043
6067
|
if (!name || name.includes("(") || name.includes(")")) return null;
|
|
6044
6068
|
return name;
|
|
6045
6069
|
}
|
|
6070
|
+
function collectFontCustomProperties(styles) {
|
|
6071
|
+
const properties = /* @__PURE__ */ new Map();
|
|
6072
|
+
for (const style of styles) {
|
|
6073
|
+
parseFontCss(style.content)?.walkDecls(/^--/, (decl) => {
|
|
6074
|
+
if (!isFontFaceDeclaration(decl)) properties.set(decl.prop, decl.value);
|
|
6075
|
+
});
|
|
6076
|
+
}
|
|
6077
|
+
return properties;
|
|
6078
|
+
}
|
|
6079
|
+
function resolveUsedFontNames(value, properties, resolving = /* @__PURE__ */ new Set()) {
|
|
6080
|
+
return postcss3.list.comma(value).flatMap((part) => {
|
|
6081
|
+
const variable = /^var\(\s*(--[^\s,()]+)\s*(?:,[\s\S]*)?\)$/i.exec(part.trim());
|
|
6082
|
+
if (!variable) {
|
|
6083
|
+
const name = normalizeUsedFontName(part);
|
|
6084
|
+
return name ? [name] : [];
|
|
6085
|
+
}
|
|
6086
|
+
const property = variable[1];
|
|
6087
|
+
if (!property || resolving.has(property) || resolving.size >= 8) return [];
|
|
6088
|
+
const referenced = properties.get(property);
|
|
6089
|
+
if (referenced === void 0) return [];
|
|
6090
|
+
return resolveUsedFontNames(referenced, properties, new Set(resolving).add(property));
|
|
6091
|
+
});
|
|
6092
|
+
}
|
|
6046
6093
|
function extractUsedFontFamilies(styles) {
|
|
6047
6094
|
const used = [];
|
|
6048
6095
|
const seen = /* @__PURE__ */ new Set();
|
|
6049
|
-
const
|
|
6096
|
+
const properties = collectFontCustomProperties(styles);
|
|
6050
6097
|
for (const style of styles) {
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
const name = normalizeUsedFontName(part);
|
|
6056
|
-
if (name && !GENERIC_FAMILIES.has(name) && !seen.has(name)) {
|
|
6098
|
+
parseFontCss(style.content)?.walkDecls(/^font-family$/i, (decl) => {
|
|
6099
|
+
if (isFontFaceDeclaration(decl)) return;
|
|
6100
|
+
for (const name of resolveUsedFontNames(decl.value, properties)) {
|
|
6101
|
+
if (!GENERIC_FAMILIES.has(name) && !seen.has(name)) {
|
|
6057
6102
|
seen.add(name);
|
|
6058
6103
|
used.push(name);
|
|
6059
6104
|
}
|
|
6060
6105
|
}
|
|
6061
|
-
}
|
|
6106
|
+
});
|
|
6062
6107
|
}
|
|
6063
6108
|
return used;
|
|
6064
6109
|
}
|
|
@@ -6161,15 +6206,12 @@ var fontRules = [
|
|
|
6161
6206
|
];
|
|
6162
6207
|
function uniqueFontLocation(styles, families, locate) {
|
|
6163
6208
|
const locations = [];
|
|
6209
|
+
const properties = collectFontCustomProperties(styles);
|
|
6164
6210
|
for (const style of styles) {
|
|
6165
6211
|
try {
|
|
6166
6212
|
postcss3.parse(style.content).walkDecls(/^font-family$/i, (decl) => {
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
if (parent.type === "atrule" && parent.name.toLowerCase() === "font-face") return;
|
|
6170
|
-
parent = parent.parent;
|
|
6171
|
-
}
|
|
6172
|
-
if (!decl.value.split(",").some((part) => families.includes(normalizeUsedFontName(part) ?? "")))
|
|
6213
|
+
if (isFontFaceDeclaration(decl)) return;
|
|
6214
|
+
if (!resolveUsedFontNames(decl.value, properties).some((name) => families.includes(name)))
|
|
6173
6215
|
return;
|
|
6174
6216
|
locations.push(locate(style, decl.source?.start?.offset));
|
|
6175
6217
|
});
|
|
@@ -6177,7 +6219,7 @@ function uniqueFontLocation(styles, families, locate) {
|
|
|
6177
6219
|
return {};
|
|
6178
6220
|
}
|
|
6179
6221
|
}
|
|
6180
|
-
return locations.length === 1 ? locations[0] : {};
|
|
6222
|
+
return locations.length === 1 ? locations[0] ?? {} : {};
|
|
6181
6223
|
}
|
|
6182
6224
|
|
|
6183
6225
|
// src/rules/slideshow.ts
|
package/dist/index.d.ts
CHANGED
|
@@ -98,6 +98,6 @@ interface ProjectLintResult {
|
|
|
98
98
|
totalWarnings: number;
|
|
99
99
|
totalInfos: number;
|
|
100
100
|
}
|
|
101
|
-
declare function lintProject(projectDir: string, entryFile?: string, hostOptions?: Pick<HyperframeLinterOptions, "host">): Promise<ProjectLintResult>;
|
|
101
|
+
declare function lintProject(projectDir: string, entryFile?: string, hostOptions?: Pick<HyperframeLinterOptions, "host" | "isSubComposition">): Promise<ProjectLintResult>;
|
|
102
102
|
|
|
103
103
|
export { type HyperframeLintFinding, type HyperframeLintResult, type HyperframeLintSeverity, type HyperframeLinterOptions, LINT_RULE_COUNT, LINT_RULE_GROUP_COUNTS, type LintTimings, type ProjectLintResult, lintHyperframeHtml, lintMediaUrls, lintProject, shouldBlockRender };
|
package/dist/index.js
CHANGED
|
@@ -1731,7 +1731,8 @@ var IMAGE_SRC_EXT = /* @__PURE__ */ new Set([
|
|
|
1731
1731
|
"heic",
|
|
1732
1732
|
"heif",
|
|
1733
1733
|
"tiff",
|
|
1734
|
-
"ico"
|
|
1734
|
+
"ico",
|
|
1735
|
+
"avif"
|
|
1735
1736
|
]);
|
|
1736
1737
|
var VIDEO_SRC_EXT = /* @__PURE__ */ new Set([
|
|
1737
1738
|
"mp4",
|
|
@@ -1745,7 +1746,7 @@ var VIDEO_SRC_EXT = /* @__PURE__ */ new Set([
|
|
|
1745
1746
|
"mpg",
|
|
1746
1747
|
"mpeg"
|
|
1747
1748
|
]);
|
|
1748
|
-
var AUDIO_SRC_EXT = /* @__PURE__ */ new Set(["mp3", "wav", "aac", "flac", "opus", "aiff", "wma"]);
|
|
1749
|
+
var AUDIO_SRC_EXT = /* @__PURE__ */ new Set(["mp3", "wav", "aac", "flac", "opus", "aiff", "wma", "m4a"]);
|
|
1749
1750
|
var SRC_KIND_NOUN = {
|
|
1750
1751
|
image: "an image",
|
|
1751
1752
|
video: "a video",
|
|
@@ -2623,7 +2624,11 @@ function containingCallOffset(script, tokenOffset) {
|
|
|
2623
2624
|
}
|
|
2624
2625
|
|
|
2625
2626
|
// src/rules/composition.ts
|
|
2626
|
-
import {
|
|
2627
|
+
import {
|
|
2628
|
+
COMPOSITION_VARIABLE_TYPES,
|
|
2629
|
+
isCompositionVariable,
|
|
2630
|
+
isSafeMediaUrl
|
|
2631
|
+
} from "@hyperframes/parsers/composition";
|
|
2627
2632
|
import { COMPOSITION_ATTRIBUTES, readClipTiming } from "@hyperframes/parsers/composition-contract";
|
|
2628
2633
|
import { resolveCompositionDuration } from "@hyperframes/parsers/composition-duration";
|
|
2629
2634
|
import {
|
|
@@ -2927,6 +2932,16 @@ function variablesDeclarationFindings(tag, tags) {
|
|
|
2927
2932
|
});
|
|
2928
2933
|
continue;
|
|
2929
2934
|
}
|
|
2935
|
+
if (!isCompositionVariable(e)) {
|
|
2936
|
+
findings.push({
|
|
2937
|
+
code: "invalid_composition_variables_declaration",
|
|
2938
|
+
severity: "error",
|
|
2939
|
+
message: `data-composition-variables entry [${i}] has a default that does not match its type, or an enum without an options array.`,
|
|
2940
|
+
fixHint: "Use number defaults for number variables, boolean defaults for boolean variables, and string defaults for other types. Enum variables also require an options array.",
|
|
2941
|
+
snippet: truncateSnippet(tag.raw)
|
|
2942
|
+
});
|
|
2943
|
+
continue;
|
|
2944
|
+
}
|
|
2930
2945
|
const id = String(e.id);
|
|
2931
2946
|
if ((e.type === "image" || varSrcIds.has(id)) && typeof e.default === "string" && e.default.length > 0 && !isSafeMediaUrl(e.default)) {
|
|
2932
2947
|
findings.push({
|
|
@@ -4965,7 +4980,7 @@ ${right.raw}`)
|
|
|
4965
4980
|
return findings;
|
|
4966
4981
|
},
|
|
4967
4982
|
// gsap_timeline_registered_before_async_build — registering window.__timelines[id]
|
|
4968
|
-
// BEFORE the timeline is built inside
|
|
4983
|
+
// BEFORE the timeline is built inside a font-ready callback
|
|
4969
4984
|
// leaves an EMPTY timeline registered. The runtime's sub-composition readiness gate
|
|
4970
4985
|
// treats "key present" as "ready" and nests the child ONCE, while still empty — so the
|
|
4971
4986
|
// animation never renders when this composition is mounted as a sub-composition.
|
|
@@ -4974,9 +4989,11 @@ ${right.raw}`)
|
|
|
4974
4989
|
const findings = [];
|
|
4975
4990
|
for (const script of scripts) {
|
|
4976
4991
|
const content = stripJsComments(script.content);
|
|
4977
|
-
const regIdx = content.search(
|
|
4992
|
+
const regIdx = content.search(
|
|
4993
|
+
/window\s*\.\s*__timelines\s*(?:\[[^\]]+\]|\.\s*[A-Za-z_$][\w$]*)\s*=\s*(?!=)/
|
|
4994
|
+
);
|
|
4978
4995
|
if (regIdx < 0) continue;
|
|
4979
|
-
const fontsReadyIdx = content.search(/document\s*\.\s*fonts\s*\.\s*ready/);
|
|
4996
|
+
const fontsReadyIdx = content.search(/document\s*\.\s*fonts\s*\.\s*(?:ready\b|load\s*\()/);
|
|
4980
4997
|
if (fontsReadyIdx < 0) continue;
|
|
4981
4998
|
if (regIdx >= fontsReadyIdx) continue;
|
|
4982
4999
|
const tail = content.slice(fontsReadyIdx);
|
|
@@ -4984,8 +5001,8 @@ ${right.raw}`)
|
|
|
4984
5001
|
findings.push({
|
|
4985
5002
|
code: "gsap_timeline_registered_before_async_build",
|
|
4986
5003
|
severity: "error",
|
|
4987
|
-
message: 'window.__timelines is assigned BEFORE the timeline is built inside document.fonts.ready. An empty timeline registered early gets nested empty when this composition is used as a sub-composition (the readiness gate treats "key present" as "ready" and never re-nests), so the animation renders blank.',
|
|
4988
|
-
fixHint: "Move the `window.__timelines[id] = tl;` assignment to the END of the
|
|
5004
|
+
message: 'window.__timelines is assigned BEFORE the timeline is built inside document.fonts.ready or document.fonts.load(). An empty timeline registered early gets nested empty when this composition is used as a sub-composition (the readiness gate treats "key present" as "ready" and never re-nests), so the animation renders blank.',
|
|
5005
|
+
fixHint: "Move the `window.__timelines[id] = tl;` assignment to the END of the font-ready callback, after the tweens are added. Optionally call window.__hfForceTimelineRebind() right after, to re-nest the populated timeline."
|
|
4989
5006
|
});
|
|
4990
5007
|
}
|
|
4991
5008
|
return findings;
|
|
@@ -6025,22 +6042,29 @@ var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
|
6025
6042
|
"unset",
|
|
6026
6043
|
"revert"
|
|
6027
6044
|
]);
|
|
6028
|
-
function
|
|
6029
|
-
|
|
6045
|
+
function parseFontCss(css) {
|
|
6046
|
+
try {
|
|
6047
|
+
return postcss3.parse(css);
|
|
6048
|
+
} catch {
|
|
6049
|
+
return null;
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
function isFontFaceDeclaration(decl) {
|
|
6053
|
+
let parent = decl.parent;
|
|
6054
|
+
while (parent) {
|
|
6055
|
+
if (parent.type === "atrule" && parent.name.toLowerCase() === "font-face") return true;
|
|
6056
|
+
parent = parent.parent;
|
|
6057
|
+
}
|
|
6058
|
+
return false;
|
|
6030
6059
|
}
|
|
6031
6060
|
function extractFontFaceFamilies(styles) {
|
|
6032
6061
|
const families = /* @__PURE__ */ new Set();
|
|
6033
|
-
const fontFaceRe = /@font-face\s*\{[^}]*\}/gi;
|
|
6034
|
-
const familyRe = /font-family\s*:\s*(['"]?)([^;'"]+)\1/i;
|
|
6035
6062
|
for (const style of styles) {
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
families.add(familyMatch[2].trim().toLowerCase());
|
|
6042
|
-
}
|
|
6043
|
-
}
|
|
6063
|
+
parseFontCss(style.content)?.walkDecls(/^font-family$/i, (decl) => {
|
|
6064
|
+
if (!isFontFaceDeclaration(decl)) return;
|
|
6065
|
+
const name = normalizeUsedFontName(decl.value);
|
|
6066
|
+
if (name) families.add(name);
|
|
6067
|
+
});
|
|
6044
6068
|
}
|
|
6045
6069
|
return families;
|
|
6046
6070
|
}
|
|
@@ -6049,22 +6073,43 @@ function normalizeUsedFontName(part) {
|
|
|
6049
6073
|
if (!name || name.includes("(") || name.includes(")")) return null;
|
|
6050
6074
|
return name;
|
|
6051
6075
|
}
|
|
6076
|
+
function collectFontCustomProperties(styles) {
|
|
6077
|
+
const properties = /* @__PURE__ */ new Map();
|
|
6078
|
+
for (const style of styles) {
|
|
6079
|
+
parseFontCss(style.content)?.walkDecls(/^--/, (decl) => {
|
|
6080
|
+
if (!isFontFaceDeclaration(decl)) properties.set(decl.prop, decl.value);
|
|
6081
|
+
});
|
|
6082
|
+
}
|
|
6083
|
+
return properties;
|
|
6084
|
+
}
|
|
6085
|
+
function resolveUsedFontNames(value, properties, resolving = /* @__PURE__ */ new Set()) {
|
|
6086
|
+
return postcss3.list.comma(value).flatMap((part) => {
|
|
6087
|
+
const variable = /^var\(\s*(--[^\s,()]+)\s*(?:,[\s\S]*)?\)$/i.exec(part.trim());
|
|
6088
|
+
if (!variable) {
|
|
6089
|
+
const name = normalizeUsedFontName(part);
|
|
6090
|
+
return name ? [name] : [];
|
|
6091
|
+
}
|
|
6092
|
+
const property = variable[1];
|
|
6093
|
+
if (!property || resolving.has(property) || resolving.size >= 8) return [];
|
|
6094
|
+
const referenced = properties.get(property);
|
|
6095
|
+
if (referenced === void 0) return [];
|
|
6096
|
+
return resolveUsedFontNames(referenced, properties, new Set(resolving).add(property));
|
|
6097
|
+
});
|
|
6098
|
+
}
|
|
6052
6099
|
function extractUsedFontFamilies(styles) {
|
|
6053
6100
|
const used = [];
|
|
6054
6101
|
const seen = /* @__PURE__ */ new Set();
|
|
6055
|
-
const
|
|
6102
|
+
const properties = collectFontCustomProperties(styles);
|
|
6056
6103
|
for (const style of styles) {
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
const name = normalizeUsedFontName(part);
|
|
6062
|
-
if (name && !GENERIC_FAMILIES.has(name) && !seen.has(name)) {
|
|
6104
|
+
parseFontCss(style.content)?.walkDecls(/^font-family$/i, (decl) => {
|
|
6105
|
+
if (isFontFaceDeclaration(decl)) return;
|
|
6106
|
+
for (const name of resolveUsedFontNames(decl.value, properties)) {
|
|
6107
|
+
if (!GENERIC_FAMILIES.has(name) && !seen.has(name)) {
|
|
6063
6108
|
seen.add(name);
|
|
6064
6109
|
used.push(name);
|
|
6065
6110
|
}
|
|
6066
6111
|
}
|
|
6067
|
-
}
|
|
6112
|
+
});
|
|
6068
6113
|
}
|
|
6069
6114
|
return used;
|
|
6070
6115
|
}
|
|
@@ -6167,15 +6212,12 @@ var fontRules = [
|
|
|
6167
6212
|
];
|
|
6168
6213
|
function uniqueFontLocation(styles, families, locate) {
|
|
6169
6214
|
const locations = [];
|
|
6215
|
+
const properties = collectFontCustomProperties(styles);
|
|
6170
6216
|
for (const style of styles) {
|
|
6171
6217
|
try {
|
|
6172
6218
|
postcss3.parse(style.content).walkDecls(/^font-family$/i, (decl) => {
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
if (parent.type === "atrule" && parent.name.toLowerCase() === "font-face") return;
|
|
6176
|
-
parent = parent.parent;
|
|
6177
|
-
}
|
|
6178
|
-
if (!decl.value.split(",").some((part) => families.includes(normalizeUsedFontName(part) ?? "")))
|
|
6219
|
+
if (isFontFaceDeclaration(decl)) return;
|
|
6220
|
+
if (!resolveUsedFontNames(decl.value, properties).some((name) => families.includes(name)))
|
|
6179
6221
|
return;
|
|
6180
6222
|
locations.push(locate(style, decl.source?.start?.offset));
|
|
6181
6223
|
});
|
|
@@ -6183,7 +6225,7 @@ function uniqueFontLocation(styles, families, locate) {
|
|
|
6183
6225
|
return {};
|
|
6184
6226
|
}
|
|
6185
6227
|
}
|
|
6186
|
-
return locations.length === 1 ? locations[0] : {};
|
|
6228
|
+
return locations.length === 1 ? locations[0] ?? {} : {};
|
|
6187
6229
|
}
|
|
6188
6230
|
|
|
6189
6231
|
// src/rules/slideshow.ts
|
|
@@ -6749,6 +6791,7 @@ import {
|
|
|
6749
6791
|
} from "@hyperframes/parsers/reference-resolver";
|
|
6750
6792
|
import { checkSubCompositionUsability } from "@hyperframes/parsers/sub-composition-validity";
|
|
6751
6793
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
6794
|
+
import postcss4 from "postcss";
|
|
6752
6795
|
import {
|
|
6753
6796
|
cleanAssetUrl as cleanAssetUrl2,
|
|
6754
6797
|
collectSubCompositionSrcs,
|
|
@@ -7069,6 +7112,7 @@ function contentDigest(html) {
|
|
|
7069
7112
|
return createHash("sha256").update(html).digest("hex").slice(0, 16);
|
|
7070
7113
|
}
|
|
7071
7114
|
var AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([".mp3", ".wav", ".aac", ".ogg", ".m4a", ".flac", ".opus"]);
|
|
7115
|
+
var CSS_URL_RE = /\burl\(\s*(?:"([^"]+)"|'([^']+)'|([^"')\s]+))\s*\)/gi;
|
|
7072
7116
|
var MASK_IMAGE_URL_RE = /\b(?:-webkit-)?mask-image\s*:\s*[^;{}]*url\(\s*(?:"([^"]+)"|'([^']+)'|([^"')\s]+))\s*\)/gi;
|
|
7073
7117
|
function isLocalStylesheetHref(href) {
|
|
7074
7118
|
return !!href && !/^(https?:|data:|blob:|\/\/)/i.test(href);
|
|
@@ -7361,9 +7405,16 @@ function lintTextureMaskAssetNotFound(projectDir, htmlSources) {
|
|
|
7361
7405
|
for (const { html, compSrcPath } of htmlSources) {
|
|
7362
7406
|
const file = sourceFile(projectDir, compSrcPath);
|
|
7363
7407
|
for (const cssSource of collectCssSources(projectDir, html, compSrcPath)) {
|
|
7364
|
-
let
|
|
7365
|
-
|
|
7366
|
-
|
|
7408
|
+
let matches = [];
|
|
7409
|
+
try {
|
|
7410
|
+
postcss4.parse(cssSource.content).walkDecls(/^(?:-webkit-)?mask-image$/i, (declaration) => {
|
|
7411
|
+
matches.push(...declaration.value.matchAll(CSS_URL_RE));
|
|
7412
|
+
});
|
|
7413
|
+
} catch (error) {
|
|
7414
|
+
if (!(error instanceof postcss4.CssSyntaxError)) throw error;
|
|
7415
|
+
matches = [...cssSource.content.matchAll(MASK_IMAGE_URL_RE)];
|
|
7416
|
+
}
|
|
7417
|
+
for (const match of matches) {
|
|
7367
7418
|
const rawUrl = match[1] ?? match[2] ?? match[3] ?? "";
|
|
7368
7419
|
if (isUnresolvedAssetPlaceholder2(rawUrl)) continue;
|
|
7369
7420
|
const url = cleanAssetUrl2(rawUrl);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/lint",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.192",
|
|
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.192"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.0.10",
|