@hyperframes/lint 0.8.67 → 0.8.69

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.d.ts CHANGED
@@ -39,6 +39,7 @@ type HyperframeLinterOptions = {
39
39
  externalStyles?: Array<{
40
40
  href: string;
41
41
  content: string;
42
+ file?: string;
42
43
  }>;
43
44
  /**
44
45
  * Set to `true` when linting compositions destined for distributed / Lambda
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { Parser as Parser2 } from "htmlparser2";
3
3
 
4
4
  // src/utils.ts
5
5
  import { Parser } from "htmlparser2";
6
+ import { parse } from "acorn";
6
7
  var COMPOSITION_ID_IN_CSS_PATTERN = /\[data-composition-id=["']([^"']+)["']\]/g;
7
8
  var TIMELINE_REGISTRY_INIT_PATTERN = /window\.__timelines\s*=\s*window\.__timelines\s*\|\|\s*\{\}|window\.__timelines\s*=\s*\{\}|window\.__timelines\s*\?\?=\s*\{\}/i;
8
9
  var TIMELINE_REGISTRY_OBJECT_LITERAL_PATTERN = /window\.__timelines\s*=\s*\{\s*(?:["'][^"']+["']|[A-Za-z_$][\w$]*)\s*:/i;
@@ -56,6 +57,7 @@ function parseHtmlStructure(source) {
56
57
  blocks[name].push({
57
58
  attrs: block.attrs,
58
59
  content: source.slice(block.contentStart, parser.startIndex),
60
+ contentStart: block.contentStart,
59
61
  raw: source.slice(block.index, parser.endIndex + 1),
60
62
  index: block.index
61
63
  });
@@ -205,11 +207,17 @@ function readTimelineRegistryTopLevelKeys(source) {
205
207
  function getInlineScriptSyntaxError(source) {
206
208
  if (!source.trim()) return null;
207
209
  try {
208
- new Function(source);
210
+ parse(source, {
211
+ ecmaVersion: "latest",
212
+ sourceType: "script",
213
+ allowReturnOutsideFunction: true
214
+ });
209
215
  return null;
210
216
  } catch (error) {
211
- if (error instanceof Error) return error.message;
212
- return String(error);
217
+ return {
218
+ message: error instanceof Error ? error.message : String(error),
219
+ offset: error instanceof SyntaxError && "pos" in error && typeof error.pos === "number" ? error.pos : void 0
220
+ };
213
221
  }
214
222
  }
215
223
  function stripStringLiterals(source) {
@@ -528,7 +536,7 @@ function stripCssComments(source) {
528
536
  }
529
537
  return out;
530
538
  }
531
- function stripHtmlCommentsOnce(source) {
539
+ function stripHtmlCommentsOnce(source, removed) {
532
540
  let out = "";
533
541
  let i = 0;
534
542
  for (; ; ) {
@@ -537,14 +545,17 @@ function stripHtmlCommentsOnce(source) {
537
545
  const end = source.indexOf("-->", start + 4);
538
546
  if (end < 0) return out + source.slice(i);
539
547
  out += source.slice(i, start);
548
+ removed?.(start, end + 3);
540
549
  i = end + 3;
541
550
  }
542
551
  }
543
- function stripHtmlComments(source) {
552
+ function stripHtmlComments(source, pass) {
544
553
  let out = source;
545
554
  for (let prev = ""; prev !== out; ) {
546
555
  prev = out;
547
- out = stripHtmlCommentsOnce(out);
556
+ const ranges = [];
557
+ out = stripHtmlCommentsOnce(out, pass ? (start, end) => ranges.push([start, end]) : void 0);
558
+ if (ranges.length) pass?.(ranges);
548
559
  }
549
560
  return out;
550
561
  }
@@ -571,17 +582,17 @@ function mediaSrcTagRe(tagAlternation) {
571
582
 
572
583
  // src/sourceLocations.ts
573
584
  function normalized(source) {
574
- let text = "";
585
+ const chars = [];
575
586
  const offsets = [];
587
+ let lastWasSpace = false;
576
588
  for (let i = 0; i < source.length; i++) {
577
- const char = source[i];
578
- if (/\s/.test(char)) {
579
- if (text.endsWith(" ")) continue;
580
- text += " ";
581
- } else text += char;
589
+ const isSpace = /\s/.test(source[i]);
590
+ if (isSpace && lastWasSpace) continue;
591
+ chars.push(isSpace ? " " : source[i]);
582
592
  offsets.push(i);
593
+ lastWasSpace = isSpace;
583
594
  }
584
- return { text, offsets };
595
+ return { text: chars.join(""), offsets };
585
596
  }
586
597
  function createSourceLocator(source) {
587
598
  const tags = parseHtmlStructure(source).tags;
@@ -621,10 +632,42 @@ function snippetOffset(source, snippet) {
621
632
  return source.offsets[index];
622
633
  }
623
634
 
635
+ // src/sourceCoordinates.ts
636
+ function mappedHtmlSource(original) {
637
+ let offsets;
638
+ const source = stripHtmlComments(original, (ranges) => {
639
+ const previous = offsets;
640
+ const length = (previous?.length ?? original.length + 1) - ranges.reduce((n, [a, b]) => n + b - a, 0);
641
+ offsets = new Uint32Array(length);
642
+ let target = 0;
643
+ let start = 0;
644
+ const spans = [
645
+ ...ranges,
646
+ [previous ? previous.length : original.length + 1, 0]
647
+ ];
648
+ for (const [a, b] of spans) {
649
+ for (let i = start; i < a; i++) offsets[target++] = previous ? previous[i] : i;
650
+ start = b;
651
+ }
652
+ });
653
+ return { source, originalOffset: (index) => offsets ? offsets[index] : index };
654
+ }
655
+ function sourcePosition(source, offset, file) {
656
+ if (!Number.isInteger(offset) || offset < 0 || offset > source.length) return {};
657
+ const before = source.slice(0, offset);
658
+ return {
659
+ ...file ? { file } : {},
660
+ line: before.split(/\r\n|\r|\n/).length,
661
+ column: offset - Math.max(before.lastIndexOf("\n"), before.lastIndexOf("\r"))
662
+ };
663
+ }
664
+
624
665
  // src/context.ts
625
666
  function buildLintContext(html, options = {}) {
626
667
  const rawSource = html || "";
627
- let source = stripHtmlComments(rawSource);
668
+ const mapped = mappedHtmlSource(rawSource);
669
+ let source = mapped.source;
670
+ let sourceStart = 0;
628
671
  const initialStructure = parseHtmlStructure(source);
629
672
  const templateTags = initialStructure.tags.filter(
630
673
  (tag) => tag.name === "template" && tag.closeIndex != null
@@ -637,7 +680,8 @@ function buildLintContext(html, options = {}) {
637
680
  const template = templateTags[0];
638
681
  let structure = initialStructure;
639
682
  if (template && !findRootTag(sourceWithoutTemplates)) {
640
- source = source.slice(template.index + template.raw.length, template.closeIndex);
683
+ sourceStart = template.index + template.raw.length;
684
+ source = source.slice(sourceStart, template.closeIndex);
641
685
  structure = parseHtmlStructure(source);
642
686
  }
643
687
  const tags = structure.tags;
@@ -647,14 +691,23 @@ function buildLintContext(html, options = {}) {
647
691
  attrs: `href="${style.href}"`,
648
692
  content: style.content,
649
693
  raw: style.content,
650
- index: -1
694
+ index: -1,
695
+ file: style.file ?? style.href
651
696
  }))
652
697
  ];
653
698
  const scripts = structure.scripts;
654
699
  const compositionIds = collectCompositionIds(tags);
655
700
  const rootTag = findRootTag(source, tags);
656
701
  const rootCompositionId = readDecodedAttr(rootTag?.raw || "", "data-composition-id");
702
+ const locate = (block, offset) => {
703
+ if (offset === void 0 || offset < 0 || offset > block.content.length) return {};
704
+ if (block.index === -1) return sourcePosition(block.content, offset, block.file);
705
+ if (block.contentStart === void 0) return {};
706
+ const originalOffset = mapped.originalOffset(sourceStart + block.contentStart + offset);
707
+ return originalOffset === void 0 ? {} : sourcePosition(rawSource, originalOffset, options.filePath);
708
+ };
657
709
  return {
710
+ locate,
658
711
  source,
659
712
  rawSource,
660
713
  tags,
@@ -674,7 +727,7 @@ import {
674
727
  HTML_BODY_CSS_WIDTH_FIRST_RE,
675
728
  HTML_BODY_CSS_HEIGHT_FIRST_RE,
676
729
  VIEWPORT_META_SIZE_RE
677
- } from "@hyperframes/parsers";
730
+ } from "@hyperframes/parsers/composition";
678
731
  function repeatedDescendantId(selector) {
679
732
  let repeated = null;
680
733
  const requiredPseudoIds = (pseudo) => {
@@ -1052,7 +1105,7 @@ var coreRules = [
1052
1105
  return findings;
1053
1106
  },
1054
1107
  // CSS selector safety
1055
- ({ styles }) => {
1108
+ ({ styles, locate }) => {
1056
1109
  const findings = [];
1057
1110
  const reportedRepeatedIds = /* @__PURE__ */ new Set();
1058
1111
  const reportedHiddenStyleSelectors = /* @__PURE__ */ new Set();
@@ -1062,6 +1115,10 @@ var coreRules = [
1062
1115
  root = postcss.parse(style.content);
1063
1116
  } catch (error) {
1064
1117
  findings.push({
1118
+ ...locate(
1119
+ style,
1120
+ error instanceof postcss.CssSyntaxError ? cssErrorOffset(style.content, error) : void 0
1121
+ ),
1065
1122
  code: "css_parse_error",
1066
1123
  severity: "error",
1067
1124
  message: `CSS parse error: ${error instanceof Error ? error.message : "unknown"}`
@@ -1127,7 +1184,7 @@ var coreRules = [
1127
1184
  ];
1128
1185
  },
1129
1186
  // invalid_inline_script_syntax (JS parse error)
1130
- ({ scripts }) => {
1187
+ ({ scripts, locate }) => {
1131
1188
  const findings = [];
1132
1189
  for (const script of scripts) {
1133
1190
  const attrs = script.attrs || "";
@@ -1138,9 +1195,10 @@ var coreRules = [
1138
1195
  const syntaxError = getInlineScriptSyntaxError(script.content);
1139
1196
  if (!syntaxError) continue;
1140
1197
  findings.push({
1198
+ ...locate(script, syntaxError.offset),
1141
1199
  code: "invalid_inline_script_syntax",
1142
1200
  severity: "error",
1143
- message: `Inline script has invalid syntax: ${syntaxError}`,
1201
+ message: `Inline script has invalid syntax: ${syntaxError.message}`,
1144
1202
  fixHint: "Fix the inline script syntax before render verification.",
1145
1203
  snippet: truncateSnippet(script.content)
1146
1204
  });
@@ -1206,7 +1264,7 @@ var coreRules = [
1206
1264
  return findings;
1207
1265
  },
1208
1266
  // non_deterministic_code
1209
- ({ scripts }) => {
1267
+ ({ scripts, locate }) => {
1210
1268
  const findings = [];
1211
1269
  const patterns = [
1212
1270
  {
@@ -1257,8 +1315,10 @@ var coreRules = [
1257
1315
  const withoutComments = stripJsComments(script.content);
1258
1316
  const executable = stripStringLiterals(withoutComments);
1259
1317
  for (const { pattern, label, hint, scansStrings } of patterns) {
1260
- if (pattern.test(scansStrings ? withoutComments : executable)) {
1318
+ const match = pattern.exec(scansStrings ? withoutComments : executable);
1319
+ if (match) {
1261
1320
  findings.push({
1321
+ ...locate(script, match.index),
1262
1322
  code: "non_deterministic_code",
1263
1323
  severity: "error",
1264
1324
  message: `Script contains \`${label}\` which produces non-deterministic output. Renders may differ between frames or runs.`,
@@ -1271,10 +1331,24 @@ var coreRules = [
1271
1331
  return findings;
1272
1332
  }
1273
1333
  ];
1334
+ function cssErrorOffset(source, error) {
1335
+ if (error.input && "offset" in error.input && typeof error.input.offset === "number")
1336
+ return error.input.offset;
1337
+ const { line, column } = error;
1338
+ if (line === void 0 || column === void 0) return void 0;
1339
+ let offset = 0;
1340
+ let currentLine = 1;
1341
+ for (const match of source.matchAll(/\n/g)) {
1342
+ if (currentLine >= line) break;
1343
+ offset = match.index + 1;
1344
+ currentLine++;
1345
+ }
1346
+ return currentLine === line ? offset + column - 1 : void 0;
1347
+ }
1274
1348
 
1275
1349
  // src/rules/media.ts
1276
1350
  import { validateColorGradingContract } from "@hyperframes/parsers/color-grading-contract";
1277
- import { extractMediaSrcMutations } from "@hyperframes/parsers";
1351
+ import { extractMediaSrcMutations } from "@hyperframes/parsers/composition";
1278
1352
  import { parseHTML } from "linkedom";
1279
1353
  function tweensVolumeInSameCall(script, id) {
1280
1354
  const selector = new RegExp(`#${escapeRegExp(id)}(?![\\w-])`, "g");
@@ -2133,6 +2207,45 @@ function findAudioGroupCarveAttrFindings(ctx) {
2133
2207
  return findings;
2134
2208
  }
2135
2209
 
2210
+ // src/scriptPositions.ts
2211
+ import { parse as parse2 } from "acorn";
2212
+ import { simple } from "acorn-walk";
2213
+ var callIndexes = /* @__PURE__ */ new WeakMap();
2214
+ function callsFor(script) {
2215
+ const cached = callIndexes.get(script);
2216
+ if (cached) return cached;
2217
+ const calls = [];
2218
+ try {
2219
+ const ast = parse2(script.content, {
2220
+ ecmaVersion: "latest",
2221
+ sourceType: "script",
2222
+ allowReturnOutsideFunction: true
2223
+ });
2224
+ simple(ast, {
2225
+ CallExpression: (node) => {
2226
+ calls.push(node);
2227
+ }
2228
+ });
2229
+ } catch {
2230
+ }
2231
+ calls.sort((a, b) => a.start - b.start);
2232
+ callIndexes.set(script, calls);
2233
+ return calls;
2234
+ }
2235
+ function gsapCallOffset(script, method) {
2236
+ return callsFor(script).find(
2237
+ ({ callee }) => callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "gsap" && callee.property.type === "Identifier" && callee.property.name === method
2238
+ )?.start;
2239
+ }
2240
+ function containingCallOffset(script, tokenOffset) {
2241
+ let start;
2242
+ for (const call of callsFor(script)) {
2243
+ if (call.start > tokenOffset) break;
2244
+ if (call.end > tokenOffset) start = call.start;
2245
+ }
2246
+ return start;
2247
+ }
2248
+
2136
2249
  // src/rules/composition.ts
2137
2250
  import { COMPOSITION_VARIABLE_TYPES, isSafeMediaUrl } from "@hyperframes/parsers/composition";
2138
2251
  import { COMPOSITION_ATTRIBUTES, readClipTiming } from "@hyperframes/parsers/composition-contract";
@@ -3342,8 +3455,8 @@ function targetedSelectorTokens(selector) {
3342
3455
  const compounds = group.trim().split(/[\s>+~]+/).filter(Boolean);
3343
3456
  const last = compounds[compounds.length - 1];
3344
3457
  if (!last) continue;
3345
- const simple = last.match(/[#.][A-Za-z0-9_-]+/g);
3346
- if (simple) for (const token of simple) tokens.add(token);
3458
+ const simple2 = last.match(/[#.][A-Za-z0-9_-]+/g);
3459
+ if (simple2) for (const token of simple2) tokens.add(token);
3347
3460
  }
3348
3461
  return tokens;
3349
3462
  }
@@ -3382,7 +3495,7 @@ function scanScriptsForRegexMatches(scripts, pattern, options) {
3382
3495
  content.length,
3383
3496
  match.index + match[0].length + options.contextAfter
3384
3497
  );
3385
- hits.push({ match, snippet: content.slice(contextStart, contextEnd) });
3498
+ hits.push({ match, snippet: content.slice(contextStart, contextEnd), script });
3386
3499
  }
3387
3500
  }
3388
3501
  return hits;
@@ -4088,19 +4201,20 @@ ${right.raw}`)
4088
4201
  return findings;
4089
4202
  },
4090
4203
  // gsap_infinite_repeat
4091
- ({ scripts, rootTag }) => {
4204
+ ({ scripts, rootTag, locate }) => {
4092
4205
  const findings = [];
4093
4206
  const declaredDuration = Number.parseFloat(
4094
4207
  rootTag ? readAttr(rootTag.raw, "data-duration") ?? "" : ""
4095
4208
  );
4096
4209
  const hasFiniteCompositionWindow = Number.isFinite(declaredDuration) && declaredDuration > 0;
4097
4210
  const pattern = /repeat\s*:\s*-1(?!\d)/g;
4098
- for (const { snippet } of scanScriptsForRegexMatches(scripts, pattern, {
4211
+ for (const { snippet, match, script } of scanScriptsForRegexMatches(scripts, pattern, {
4099
4212
  stripComments: true,
4100
4213
  contextBefore: 60,
4101
4214
  contextAfter: 60
4102
4215
  })) {
4103
4216
  findings.push({
4217
+ ...locate(script, containingCallOffset(script, match.index)),
4104
4218
  code: "gsap_infinite_repeat",
4105
4219
  severity: hasFiniteCompositionWindow ? "warning" : "error",
4106
4220
  message: hasFiniteCompositionWindow ? `GSAP tween uses \`repeat: -1\` (infinite), but the composition declares a finite ${declaredDuration}s window. HyperFrames clips deterministic seeking and export to that explicit duration.` : "GSAP tween uses `repeat: -1` (infinite) without a finite composition `data-duration`. The timeline can report an unbounded duration and make render planning fail.",
@@ -4111,15 +4225,16 @@ ${right.raw}`)
4111
4225
  return findings;
4112
4226
  },
4113
4227
  // gsap_repeat_ceil_overshoot
4114
- ({ scripts }) => {
4228
+ ({ scripts, locate }) => {
4115
4229
  const findings = [];
4116
4230
  const pattern = /repeat\s*:\s*Math\.ceil\s*\([^)]+\)\s*-\s*1/g;
4117
- for (const { snippet } of scanScriptsForRegexMatches(scripts, pattern, {
4231
+ for (const { snippet, match, script } of scanScriptsForRegexMatches(scripts, pattern, {
4118
4232
  stripComments: false,
4119
4233
  contextBefore: 40,
4120
4234
  contextAfter: 40
4121
4235
  })) {
4122
4236
  findings.push({
4237
+ ...locate(script, containingCallOffset(script, match.index)),
4123
4238
  code: "gsap_repeat_ceil_overshoot",
4124
4239
  severity: "warning",
4125
4240
  message: "GSAP repeat calculation uses `Math.ceil` which can overshoot the composition duration. For example, Math.ceil(10.5 / 2) - 1 = 5 repeats \u2192 6 cycles \xD7 2s = 12s, exceeding 10.5s.",
@@ -4130,15 +4245,16 @@ ${right.raw}`)
4130
4245
  return findings;
4131
4246
  },
4132
4247
  // gsap_repeat_floor_unclamped
4133
- ({ scripts }) => {
4248
+ ({ scripts, locate }) => {
4134
4249
  const findings = [];
4135
4250
  const pattern = /repeat\s*:\s*Math\.floor\s*\([^)]+\)\s*-\s*1/g;
4136
- for (const { snippet } of scanScriptsForRegexMatches(scripts, pattern, {
4251
+ for (const { snippet, match, script } of scanScriptsForRegexMatches(scripts, pattern, {
4137
4252
  stripComments: false,
4138
4253
  contextBefore: 40,
4139
4254
  contextAfter: 40
4140
4255
  })) {
4141
4256
  findings.push({
4257
+ ...locate(script, containingCallOffset(script, match.index)),
4142
4258
  code: "gsap_repeat_floor_unclamped",
4143
4259
  severity: "warning",
4144
4260
  message: "GSAP repeat calculation can evaluate to -1 when the composition is shorter than one cycle, which GSAP interprets as an infinite repeat.",
@@ -4149,7 +4265,7 @@ ${right.raw}`)
4149
4265
  return findings;
4150
4266
  },
4151
4267
  // gsap_timeline_not_registered
4152
- ({ scripts, rawSource, options }) => {
4268
+ ({ scripts, rawSource, options, locate }) => {
4153
4269
  const findings = [];
4154
4270
  const canInheritFromHost = options.isSubComposition || rawSource.trimStart().toLowerCase().startsWith("<template");
4155
4271
  for (const script of scripts) {
@@ -4158,6 +4274,7 @@ ${right.raw}`)
4158
4274
  const hasRegistration = WINDOW_TIMELINE_ASSIGN_PATTERN.test(content) || TIMELINE_REGISTRY_OBJECT_LITERAL_PATTERN.test(content);
4159
4275
  if (hasRegistration || canInheritFromHost) continue;
4160
4276
  findings.push({
4277
+ ...locate(script, gsapCallOffset(script, "timeline")),
4161
4278
  code: "gsap_timeline_not_registered",
4162
4279
  severity: "error",
4163
4280
  message: "GSAP timeline is created but never registered in window.__timelines. The runtime discovers timelines from this registry \u2014 without registration, animations will not play during preview or render.",
@@ -5200,6 +5317,7 @@ var textureRules = [
5200
5317
  ];
5201
5318
 
5202
5319
  // src/rules/fonts.ts
5320
+ import postcss3 from "postcss";
5203
5321
  import { FONT_ALIAS_KEYS, resolveAliasDisplayName } from "@hyperframes/parsers/composition";
5204
5322
  var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
5205
5323
  "serif",
@@ -5346,7 +5464,7 @@ var fontRules = [
5346
5464
  ];
5347
5465
  },
5348
5466
  // font_family_without_font_face
5349
- ({ styles, source, rawSource, options }) => {
5467
+ ({ styles, source, rawSource, options, locate }) => {
5350
5468
  if (isRegistrySourceFile(options.filePath) || isRegistryInstalledFile(rawSource)) return [];
5351
5469
  const findings = [];
5352
5470
  const declared = extractFontFaceFamilies(styles);
@@ -5357,6 +5475,7 @@ var fontRules = [
5357
5475
  );
5358
5476
  if (undeclared.length === 0) return findings;
5359
5477
  findings.push({
5478
+ ...uniqueFontLocation(styles, undeclared, locate),
5360
5479
  code: "font_family_without_font_face",
5361
5480
  severity: "error",
5362
5481
  message: `Font ${undeclared.length === 1 ? "family" : "families"} used without @font-face declaration: ${undeclared.join(", ")}. These are not in the auto-resolved font list, so the renderer cannot supply them automatically. Text will fall back to a generic font, producing incorrect typography in the video.`,
@@ -5365,6 +5484,26 @@ var fontRules = [
5365
5484
  return findings;
5366
5485
  }
5367
5486
  ];
5487
+ function uniqueFontLocation(styles, families, locate) {
5488
+ const locations = [];
5489
+ for (const style of styles) {
5490
+ try {
5491
+ postcss3.parse(style.content).walkDecls(/^font-family$/i, (decl) => {
5492
+ let parent = decl.parent;
5493
+ while (parent) {
5494
+ if (parent.type === "atrule" && parent.name.toLowerCase() === "font-face") return;
5495
+ parent = parent.parent;
5496
+ }
5497
+ if (!decl.value.split(",").some((part) => families.includes(normalizeUsedFontName(part) ?? "")))
5498
+ return;
5499
+ locations.push(locate(style, decl.source?.start?.offset));
5500
+ });
5501
+ } catch {
5502
+ return {};
5503
+ }
5504
+ }
5505
+ return locations.length === 1 ? locations[0] : {};
5506
+ }
5368
5507
 
5369
5508
  // src/rules/slideshow.ts
5370
5509
  import {
@@ -5637,7 +5776,7 @@ function collectFindings(produced, seen, filePath, into) {
5637
5776
  const dedupeKey = dedupeKeyFor(finding);
5638
5777
  if (seen.has(dedupeKey)) continue;
5639
5778
  seen.add(dedupeKey);
5640
- into.push(filePath ? { ...finding, file: filePath } : finding);
5779
+ into.push(filePath ? { ...finding, file: finding.file ?? filePath } : finding);
5641
5780
  }
5642
5781
  }
5643
5782
  async function lintHyperframeHtml(html, options = {}) {
@@ -5646,6 +5785,12 @@ async function lintHyperframeHtml(html, options = {}) {
5646
5785
  const { findings: rawFindings, timings } = await runRules(ctx, options.filePath);
5647
5786
  const locate = createSourceLocator(html);
5648
5787
  const findings = rawFindings.map((finding) => {
5788
+ if (finding.line !== void 0 || finding.code === "invalid_inline_script_syntax" || finding.code === "non_deterministic_code" || [
5789
+ "gsap_infinite_repeat",
5790
+ "gsap_repeat_ceil_overshoot",
5791
+ "gsap_repeat_floor_unclamped"
5792
+ ].includes(finding.code))
5793
+ return finding;
5649
5794
  if (finding.snippet && options.externalStyles?.some(
5650
5795
  (style) => style.content.replace(/\s+/g, " ").includes(finding.snippet.replace(/\.\.\.$/, ""))
5651
5796
  ))
@@ -6029,8 +6174,12 @@ function collectLocalStylesheets(projectDir, document, compSrcPath) {
6029
6174
  function collectExternalStyles(projectDir, html, compSrcPath) {
6030
6175
  const styles = [];
6031
6176
  const { document } = parseHTML3(html);
6032
- for (const { href, content } of collectLocalStylesheets(projectDir, document, compSrcPath)) {
6033
- styles.push({ href, content });
6177
+ for (const { href, content, rootRelativePath } of collectLocalStylesheets(
6178
+ projectDir,
6179
+ document,
6180
+ compSrcPath
6181
+ )) {
6182
+ styles.push({ href, content, file: join2(projectDir, rootRelativePath) });
6034
6183
  }
6035
6184
  return styles;
6036
6185
  }