@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 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 { COMPOSITION_VARIABLE_TYPES, isSafeMediaUrl } from "@hyperframes/parsers/composition";
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 document.fonts.ready (or any async callback)
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(/window\s*\.\s*__timelines\s*\[/);
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 document.fonts.ready callback, after the tweens are added. Optionally call window.__hfForceTimelineRebind() right after, to re-nest the populated timeline."
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 stripCssComments2(css) {
6023
- return css.replace(/\/\*[\s\S]*?\*\//g, " ");
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
- const content = stripCssComments2(style.content);
6031
- let match;
6032
- while ((match = fontFaceRe.exec(content)) !== null) {
6033
- const familyMatch = match[0].match(familyRe);
6034
- if (familyMatch?.[2]) {
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 propRe = /font-family\s*:\s*([^;}{]+)/gi;
6096
+ const properties = collectFontCustomProperties(styles);
6050
6097
  for (const style of styles) {
6051
- const withoutFontFace = stripCssComments2(style.content).replace(/@font-face\s*\{[^}]*\}/gi, "");
6052
- let match;
6053
- while ((match = propRe.exec(withoutFontFace)) !== null) {
6054
- for (const part of match[1].split(",")) {
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
- let parent = decl.parent;
6168
- while (parent) {
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 { COMPOSITION_VARIABLE_TYPES, isSafeMediaUrl } from "@hyperframes/parsers/composition";
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 document.fonts.ready (or any async callback)
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(/window\s*\.\s*__timelines\s*\[/);
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 document.fonts.ready callback, after the tweens are added. Optionally call window.__hfForceTimelineRebind() right after, to re-nest the populated timeline."
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 stripCssComments2(css) {
6029
- return css.replace(/\/\*[\s\S]*?\*\//g, " ");
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
- const content = stripCssComments2(style.content);
6037
- let match;
6038
- while ((match = fontFaceRe.exec(content)) !== null) {
6039
- const familyMatch = match[0].match(familyRe);
6040
- if (familyMatch?.[2]) {
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 propRe = /font-family\s*:\s*([^;}{]+)/gi;
6102
+ const properties = collectFontCustomProperties(styles);
6056
6103
  for (const style of styles) {
6057
- const withoutFontFace = stripCssComments2(style.content).replace(/@font-face\s*\{[^}]*\}/gi, "");
6058
- let match;
6059
- while ((match = propRe.exec(withoutFontFace)) !== null) {
6060
- for (const part of match[1].split(",")) {
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
- let parent = decl.parent;
6174
- while (parent) {
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 match;
7365
- const pattern = new RegExp(MASK_IMAGE_URL_RE.source, MASK_IMAGE_URL_RE.flags);
7366
- while ((match = pattern.exec(cssSource.content)) !== null) {
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.190",
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.190"
38
+ "@hyperframes/parsers": "0.8.192"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^25.0.10",