@hyperframes/lint 0.8.26 → 0.8.28

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
@@ -568,7 +568,12 @@ var coreRules = [
568
568
  let root;
569
569
  try {
570
570
  root = postcss.parse(style.content);
571
- } catch {
571
+ } catch (error) {
572
+ findings.push({
573
+ code: "css_parse_error",
574
+ severity: "error",
575
+ message: `CSS parse error: ${error instanceof Error ? error.message : "unknown"}`
576
+ });
572
577
  continue;
573
578
  }
574
579
  root.walkRules((rule) => {
@@ -709,7 +714,7 @@ var coreRules = [
709
714
  {
710
715
  pattern: /crypto\.getRandomValues\s*\(/,
711
716
  label: "crypto.getRandomValues()",
712
- hint: "Remove time-dependent code. Use a seeded PRNG for deterministic renders."
717
+ hint: "Use a seeded PRNG (e.g. a simple mulberry32) so renders are deterministic across frames."
713
718
  },
714
719
  {
715
720
  pattern: /gsap\.utils\.random\s*\(/,
@@ -773,6 +778,84 @@ function hasAttrName(tagSource, attr) {
773
778
  const attrs = tagSource.replace(/^<\s*[a-z][\w:-]*/i, "");
774
779
  return new RegExp(`(?:^|\\s)${escaped}(?:\\s*=|\\s|/?>)`, "i").test(attrs);
775
780
  }
781
+ var IMAGE_SRC_EXT = /* @__PURE__ */ new Set([
782
+ "jpg",
783
+ "jpeg",
784
+ "png",
785
+ "gif",
786
+ "bmp",
787
+ "webp",
788
+ "svg",
789
+ "heic",
790
+ "heif",
791
+ "tiff",
792
+ "ico"
793
+ ]);
794
+ var VIDEO_SRC_EXT = /* @__PURE__ */ new Set([
795
+ "mp4",
796
+ "mov",
797
+ "avi",
798
+ "webm",
799
+ "mkv",
800
+ "flv",
801
+ "wmv",
802
+ "m4v",
803
+ "mpg",
804
+ "mpeg"
805
+ ]);
806
+ function srcKind(src) {
807
+ const stripped = src.trim();
808
+ if (!stripped) return null;
809
+ const lower = stripped.toLowerCase();
810
+ if (lower.startsWith("data:")) {
811
+ const mime = /^data:([^;,]+)/i.exec(stripped)?.[1]?.toLowerCase();
812
+ if (!mime) return null;
813
+ if (mime.startsWith("image/")) return "image";
814
+ if (mime.startsWith("video/")) return "video";
815
+ return null;
816
+ }
817
+ if (lower.startsWith("blob:")) return null;
818
+ let pathname = stripped;
819
+ try {
820
+ if (/^https?:/i.test(stripped)) {
821
+ pathname = decodeURIComponent(new URL(stripped).pathname);
822
+ } else {
823
+ pathname = stripped.split("?")[0]?.split("#")[0] ?? stripped;
824
+ }
825
+ } catch {
826
+ pathname = stripped.split("?")[0]?.split("#")[0] ?? stripped;
827
+ }
828
+ const base = pathname.split("/").pop() ?? "";
829
+ const dot = base.lastIndexOf(".");
830
+ if (dot < 0) return null;
831
+ const ext = base.slice(dot + 1).toLowerCase();
832
+ if (IMAGE_SRC_EXT.has(ext)) return "image";
833
+ if (VIDEO_SRC_EXT.has(ext)) return "video";
834
+ return null;
835
+ }
836
+ function findMediaSrcKindMismatchFindings(ctx) {
837
+ const findings = [];
838
+ for (const tag of ctx.tags) {
839
+ if (tag.name !== "video" && tag.name !== "img") continue;
840
+ const src = readAttr(tag.raw, "src");
841
+ if (!src) continue;
842
+ const kind = srcKind(src);
843
+ if (kind === null) continue;
844
+ if (tag.name === "video" && kind !== "image") continue;
845
+ if (tag.name === "img" && kind !== "video") continue;
846
+ const elementId = readAttr(tag.raw, "id") || void 0;
847
+ const expected = tag.name === "video" ? "video" : "image";
848
+ findings.push({
849
+ code: "media_src_kind_mismatch",
850
+ severity: "error",
851
+ message: `<${tag.name}${elementId ? ` id="${elementId}"` : ""}> src is a ${kind}, not a ${expected}. The producer fail-closes when the tag and file kind disagree.`,
852
+ elementId,
853
+ fixHint: tag.name === "video" ? "Use <img> for a still, or point <video> at a video URL (mp4/webm/mov/\u2026)." : "Use <video> for a video URL, or point <img> at a still (png/jpg/webp/\u2026).",
854
+ snippet: truncateSnippet(tag.raw)
855
+ });
856
+ }
857
+ return findings;
858
+ }
776
859
  function mediaHasResolvableSrc(tag, tags) {
777
860
  if (readAttr(tag.raw, "src")) return true;
778
861
  const end = tag.closeIndex ?? tag.endIndex;
@@ -1130,6 +1213,8 @@ var mediaRules = [
1130
1213
  }
1131
1214
  return findings;
1132
1215
  },
1216
+ // media_src_kind_mismatch
1217
+ findMediaSrcKindMismatchFindings,
1133
1218
  // placeholder_media_url
1134
1219
  ({ tags }) => {
1135
1220
  const findings = [];