@hyperframes/lint 0.8.191 → 0.8.193
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 +74 -33
- package/dist/index.d.ts +1 -1
- package/dist/index.js +86 -36
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -2618,7 +2618,11 @@ function containingCallOffset(script, tokenOffset) {
|
|
|
2618
2618
|
}
|
|
2619
2619
|
|
|
2620
2620
|
// src/rules/composition.ts
|
|
2621
|
-
import {
|
|
2621
|
+
import {
|
|
2622
|
+
COMPOSITION_VARIABLE_TYPES,
|
|
2623
|
+
isCompositionVariable,
|
|
2624
|
+
isSafeMediaUrl
|
|
2625
|
+
} from "@hyperframes/parsers/composition";
|
|
2622
2626
|
import { COMPOSITION_ATTRIBUTES, readClipTiming } from "@hyperframes/parsers/composition-contract";
|
|
2623
2627
|
import { resolveCompositionDuration } from "@hyperframes/parsers/composition-duration";
|
|
2624
2628
|
import {
|
|
@@ -2922,6 +2926,16 @@ function variablesDeclarationFindings(tag, tags) {
|
|
|
2922
2926
|
});
|
|
2923
2927
|
continue;
|
|
2924
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
|
+
}
|
|
2925
2939
|
const id = String(e.id);
|
|
2926
2940
|
if ((e.type === "image" || varSrcIds.has(id)) && typeof e.default === "string" && e.default.length > 0 && !isSafeMediaUrl(e.default)) {
|
|
2927
2941
|
findings.push({
|
|
@@ -4960,7 +4974,7 @@ ${right.raw}`)
|
|
|
4960
4974
|
return findings;
|
|
4961
4975
|
},
|
|
4962
4976
|
// gsap_timeline_registered_before_async_build — registering window.__timelines[id]
|
|
4963
|
-
// BEFORE the timeline is built inside
|
|
4977
|
+
// BEFORE the timeline is built inside a font-ready callback
|
|
4964
4978
|
// leaves an EMPTY timeline registered. The runtime's sub-composition readiness gate
|
|
4965
4979
|
// treats "key present" as "ready" and nests the child ONCE, while still empty — so the
|
|
4966
4980
|
// animation never renders when this composition is mounted as a sub-composition.
|
|
@@ -4969,9 +4983,11 @@ ${right.raw}`)
|
|
|
4969
4983
|
const findings = [];
|
|
4970
4984
|
for (const script of scripts) {
|
|
4971
4985
|
const content = stripJsComments(script.content);
|
|
4972
|
-
const regIdx = content.search(
|
|
4986
|
+
const regIdx = content.search(
|
|
4987
|
+
/window\s*\.\s*__timelines\s*(?:\[[^\]]+\]|\.\s*[A-Za-z_$][\w$]*)\s*=\s*(?!=)/
|
|
4988
|
+
);
|
|
4973
4989
|
if (regIdx < 0) continue;
|
|
4974
|
-
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*\()/);
|
|
4975
4991
|
if (fontsReadyIdx < 0) continue;
|
|
4976
4992
|
if (regIdx >= fontsReadyIdx) continue;
|
|
4977
4993
|
const tail = content.slice(fontsReadyIdx);
|
|
@@ -4979,8 +4995,8 @@ ${right.raw}`)
|
|
|
4979
4995
|
findings.push({
|
|
4980
4996
|
code: "gsap_timeline_registered_before_async_build",
|
|
4981
4997
|
severity: "error",
|
|
4982
|
-
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.',
|
|
4983
|
-
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."
|
|
4984
5000
|
});
|
|
4985
5001
|
}
|
|
4986
5002
|
return findings;
|
|
@@ -6020,22 +6036,29 @@ var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
|
6020
6036
|
"unset",
|
|
6021
6037
|
"revert"
|
|
6022
6038
|
]);
|
|
6023
|
-
function
|
|
6024
|
-
|
|
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;
|
|
6025
6053
|
}
|
|
6026
6054
|
function extractFontFaceFamilies(styles) {
|
|
6027
6055
|
const families = /* @__PURE__ */ new Set();
|
|
6028
|
-
const fontFaceRe = /@font-face\s*\{[^}]*\}/gi;
|
|
6029
|
-
const familyRe = /font-family\s*:\s*(['"]?)([^;'"]+)\1/i;
|
|
6030
6056
|
for (const style of styles) {
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
families.add(familyMatch[2].trim().toLowerCase());
|
|
6037
|
-
}
|
|
6038
|
-
}
|
|
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
|
+
});
|
|
6039
6062
|
}
|
|
6040
6063
|
return families;
|
|
6041
6064
|
}
|
|
@@ -6044,22 +6067,43 @@ function normalizeUsedFontName(part) {
|
|
|
6044
6067
|
if (!name || name.includes("(") || name.includes(")")) return null;
|
|
6045
6068
|
return name;
|
|
6046
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
|
+
}
|
|
6047
6093
|
function extractUsedFontFamilies(styles) {
|
|
6048
6094
|
const used = [];
|
|
6049
6095
|
const seen = /* @__PURE__ */ new Set();
|
|
6050
|
-
const
|
|
6096
|
+
const properties = collectFontCustomProperties(styles);
|
|
6051
6097
|
for (const style of styles) {
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
const name = normalizeUsedFontName(part);
|
|
6057
|
-
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)) {
|
|
6058
6102
|
seen.add(name);
|
|
6059
6103
|
used.push(name);
|
|
6060
6104
|
}
|
|
6061
6105
|
}
|
|
6062
|
-
}
|
|
6106
|
+
});
|
|
6063
6107
|
}
|
|
6064
6108
|
return used;
|
|
6065
6109
|
}
|
|
@@ -6162,15 +6206,12 @@ var fontRules = [
|
|
|
6162
6206
|
];
|
|
6163
6207
|
function uniqueFontLocation(styles, families, locate) {
|
|
6164
6208
|
const locations = [];
|
|
6209
|
+
const properties = collectFontCustomProperties(styles);
|
|
6165
6210
|
for (const style of styles) {
|
|
6166
6211
|
try {
|
|
6167
6212
|
postcss3.parse(style.content).walkDecls(/^font-family$/i, (decl) => {
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
if (parent.type === "atrule" && parent.name.toLowerCase() === "font-face") return;
|
|
6171
|
-
parent = parent.parent;
|
|
6172
|
-
}
|
|
6173
|
-
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)))
|
|
6174
6215
|
return;
|
|
6175
6216
|
locations.push(locate(style, decl.source?.start?.offset));
|
|
6176
6217
|
});
|
|
@@ -6178,7 +6219,7 @@ function uniqueFontLocation(styles, families, locate) {
|
|
|
6178
6219
|
return {};
|
|
6179
6220
|
}
|
|
6180
6221
|
}
|
|
6181
|
-
return locations.length === 1 ? locations[0] : {};
|
|
6222
|
+
return locations.length === 1 ? locations[0] ?? {} : {};
|
|
6182
6223
|
}
|
|
6183
6224
|
|
|
6184
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
|
@@ -2624,7 +2624,11 @@ function containingCallOffset(script, tokenOffset) {
|
|
|
2624
2624
|
}
|
|
2625
2625
|
|
|
2626
2626
|
// src/rules/composition.ts
|
|
2627
|
-
import {
|
|
2627
|
+
import {
|
|
2628
|
+
COMPOSITION_VARIABLE_TYPES,
|
|
2629
|
+
isCompositionVariable,
|
|
2630
|
+
isSafeMediaUrl
|
|
2631
|
+
} from "@hyperframes/parsers/composition";
|
|
2628
2632
|
import { COMPOSITION_ATTRIBUTES, readClipTiming } from "@hyperframes/parsers/composition-contract";
|
|
2629
2633
|
import { resolveCompositionDuration } from "@hyperframes/parsers/composition-duration";
|
|
2630
2634
|
import {
|
|
@@ -2928,6 +2932,16 @@ function variablesDeclarationFindings(tag, tags) {
|
|
|
2928
2932
|
});
|
|
2929
2933
|
continue;
|
|
2930
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
|
+
}
|
|
2931
2945
|
const id = String(e.id);
|
|
2932
2946
|
if ((e.type === "image" || varSrcIds.has(id)) && typeof e.default === "string" && e.default.length > 0 && !isSafeMediaUrl(e.default)) {
|
|
2933
2947
|
findings.push({
|
|
@@ -4966,7 +4980,7 @@ ${right.raw}`)
|
|
|
4966
4980
|
return findings;
|
|
4967
4981
|
},
|
|
4968
4982
|
// gsap_timeline_registered_before_async_build — registering window.__timelines[id]
|
|
4969
|
-
// BEFORE the timeline is built inside
|
|
4983
|
+
// BEFORE the timeline is built inside a font-ready callback
|
|
4970
4984
|
// leaves an EMPTY timeline registered. The runtime's sub-composition readiness gate
|
|
4971
4985
|
// treats "key present" as "ready" and nests the child ONCE, while still empty — so the
|
|
4972
4986
|
// animation never renders when this composition is mounted as a sub-composition.
|
|
@@ -4975,9 +4989,11 @@ ${right.raw}`)
|
|
|
4975
4989
|
const findings = [];
|
|
4976
4990
|
for (const script of scripts) {
|
|
4977
4991
|
const content = stripJsComments(script.content);
|
|
4978
|
-
const regIdx = content.search(
|
|
4992
|
+
const regIdx = content.search(
|
|
4993
|
+
/window\s*\.\s*__timelines\s*(?:\[[^\]]+\]|\.\s*[A-Za-z_$][\w$]*)\s*=\s*(?!=)/
|
|
4994
|
+
);
|
|
4979
4995
|
if (regIdx < 0) continue;
|
|
4980
|
-
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*\()/);
|
|
4981
4997
|
if (fontsReadyIdx < 0) continue;
|
|
4982
4998
|
if (regIdx >= fontsReadyIdx) continue;
|
|
4983
4999
|
const tail = content.slice(fontsReadyIdx);
|
|
@@ -4985,8 +5001,8 @@ ${right.raw}`)
|
|
|
4985
5001
|
findings.push({
|
|
4986
5002
|
code: "gsap_timeline_registered_before_async_build",
|
|
4987
5003
|
severity: "error",
|
|
4988
|
-
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.',
|
|
4989
|
-
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."
|
|
4990
5006
|
});
|
|
4991
5007
|
}
|
|
4992
5008
|
return findings;
|
|
@@ -6026,22 +6042,29 @@ var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
|
6026
6042
|
"unset",
|
|
6027
6043
|
"revert"
|
|
6028
6044
|
]);
|
|
6029
|
-
function
|
|
6030
|
-
|
|
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;
|
|
6031
6059
|
}
|
|
6032
6060
|
function extractFontFaceFamilies(styles) {
|
|
6033
6061
|
const families = /* @__PURE__ */ new Set();
|
|
6034
|
-
const fontFaceRe = /@font-face\s*\{[^}]*\}/gi;
|
|
6035
|
-
const familyRe = /font-family\s*:\s*(['"]?)([^;'"]+)\1/i;
|
|
6036
6062
|
for (const style of styles) {
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
families.add(familyMatch[2].trim().toLowerCase());
|
|
6043
|
-
}
|
|
6044
|
-
}
|
|
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
|
+
});
|
|
6045
6068
|
}
|
|
6046
6069
|
return families;
|
|
6047
6070
|
}
|
|
@@ -6050,22 +6073,43 @@ function normalizeUsedFontName(part) {
|
|
|
6050
6073
|
if (!name || name.includes("(") || name.includes(")")) return null;
|
|
6051
6074
|
return name;
|
|
6052
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
|
+
}
|
|
6053
6099
|
function extractUsedFontFamilies(styles) {
|
|
6054
6100
|
const used = [];
|
|
6055
6101
|
const seen = /* @__PURE__ */ new Set();
|
|
6056
|
-
const
|
|
6102
|
+
const properties = collectFontCustomProperties(styles);
|
|
6057
6103
|
for (const style of styles) {
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
const name = normalizeUsedFontName(part);
|
|
6063
|
-
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)) {
|
|
6064
6108
|
seen.add(name);
|
|
6065
6109
|
used.push(name);
|
|
6066
6110
|
}
|
|
6067
6111
|
}
|
|
6068
|
-
}
|
|
6112
|
+
});
|
|
6069
6113
|
}
|
|
6070
6114
|
return used;
|
|
6071
6115
|
}
|
|
@@ -6168,15 +6212,12 @@ var fontRules = [
|
|
|
6168
6212
|
];
|
|
6169
6213
|
function uniqueFontLocation(styles, families, locate) {
|
|
6170
6214
|
const locations = [];
|
|
6215
|
+
const properties = collectFontCustomProperties(styles);
|
|
6171
6216
|
for (const style of styles) {
|
|
6172
6217
|
try {
|
|
6173
6218
|
postcss3.parse(style.content).walkDecls(/^font-family$/i, (decl) => {
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
if (parent.type === "atrule" && parent.name.toLowerCase() === "font-face") return;
|
|
6177
|
-
parent = parent.parent;
|
|
6178
|
-
}
|
|
6179
|
-
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)))
|
|
6180
6221
|
return;
|
|
6181
6222
|
locations.push(locate(style, decl.source?.start?.offset));
|
|
6182
6223
|
});
|
|
@@ -6184,7 +6225,7 @@ function uniqueFontLocation(styles, families, locate) {
|
|
|
6184
6225
|
return {};
|
|
6185
6226
|
}
|
|
6186
6227
|
}
|
|
6187
|
-
return locations.length === 1 ? locations[0] : {};
|
|
6228
|
+
return locations.length === 1 ? locations[0] ?? {} : {};
|
|
6188
6229
|
}
|
|
6189
6230
|
|
|
6190
6231
|
// src/rules/slideshow.ts
|
|
@@ -6750,6 +6791,7 @@ import {
|
|
|
6750
6791
|
} from "@hyperframes/parsers/reference-resolver";
|
|
6751
6792
|
import { checkSubCompositionUsability } from "@hyperframes/parsers/sub-composition-validity";
|
|
6752
6793
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
6794
|
+
import postcss4 from "postcss";
|
|
6753
6795
|
import {
|
|
6754
6796
|
cleanAssetUrl as cleanAssetUrl2,
|
|
6755
6797
|
collectSubCompositionSrcs,
|
|
@@ -7070,6 +7112,7 @@ function contentDigest(html) {
|
|
|
7070
7112
|
return createHash("sha256").update(html).digest("hex").slice(0, 16);
|
|
7071
7113
|
}
|
|
7072
7114
|
var AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([".mp3", ".wav", ".aac", ".ogg", ".m4a", ".flac", ".opus"]);
|
|
7115
|
+
var CSS_URL_RE = /\burl\(\s*(?:"([^"]+)"|'([^']+)'|([^"')\s]+))\s*\)/gi;
|
|
7073
7116
|
var MASK_IMAGE_URL_RE = /\b(?:-webkit-)?mask-image\s*:\s*[^;{}]*url\(\s*(?:"([^"]+)"|'([^']+)'|([^"')\s]+))\s*\)/gi;
|
|
7074
7117
|
function isLocalStylesheetHref(href) {
|
|
7075
7118
|
return !!href && !/^(https?:|data:|blob:|\/\/)/i.test(href);
|
|
@@ -7362,9 +7405,16 @@ function lintTextureMaskAssetNotFound(projectDir, htmlSources) {
|
|
|
7362
7405
|
for (const { html, compSrcPath } of htmlSources) {
|
|
7363
7406
|
const file = sourceFile(projectDir, compSrcPath);
|
|
7364
7407
|
for (const cssSource of collectCssSources(projectDir, html, compSrcPath)) {
|
|
7365
|
-
let
|
|
7366
|
-
|
|
7367
|
-
|
|
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) {
|
|
7368
7418
|
const rawUrl = match[1] ?? match[2] ?? match[3] ?? "";
|
|
7369
7419
|
if (isUnresolvedAssetPlaceholder2(rawUrl)) continue;
|
|
7370
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.193",
|
|
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.193"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.0.10",
|