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