@hyperframes/lint 0.8.193 → 0.8.195

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.
Files changed (3) hide show
  1. package/dist/browser.js +270 -113
  2. package/dist/index.js +270 -113
  3. package/package.json +2 -2
package/dist/browser.js CHANGED
@@ -814,6 +814,96 @@ function buildLintContext(html, options = {}) {
814
814
  // src/rules/core.ts
815
815
  import postcss from "postcss";
816
816
  import selectorParser from "postcss-selector-parser";
817
+
818
+ // src/shaderDomains.ts
819
+ import { parse as parse2, parseExpressionAt } from "acorn";
820
+ import { simple as simple2 } from "acorn-walk";
821
+ var GLSL_SCRIPT_TYPES = /* @__PURE__ */ new Set(["x-shader/x-fragment", "x-shader/x-vertex", "text/glsl"]);
822
+ function withoutComments(source) {
823
+ return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*/g, (text) => text.replace(/[^\r\n]/g, " "));
824
+ }
825
+ function staticShaders(source) {
826
+ const shaders = [];
827
+ try {
828
+ const ast = parse2(source, {
829
+ ecmaVersion: "latest",
830
+ sourceType: "module",
831
+ allowReturnOutsideFunction: true
832
+ });
833
+ simple2(ast, {
834
+ Literal(node) {
835
+ if (typeof node.value !== "string") return;
836
+ shaders.push({
837
+ text: node.value,
838
+ start: node.start + 1,
839
+ exactOffsets: source.slice(node.start + 1, node.end - 1) === node.value
840
+ });
841
+ },
842
+ TemplateLiteral(node) {
843
+ if (node.expressions.length !== 0) return;
844
+ const value = node.quasis[0]?.value;
845
+ if (value?.cooked == null) return;
846
+ shaders.push({
847
+ text: value.cooked,
848
+ start: node.start + 1,
849
+ exactOffsets: source.slice(node.start + 1, node.end - 1) === value.cooked
850
+ });
851
+ }
852
+ });
853
+ } catch {
854
+ }
855
+ return shaders.filter(({ text }) => /\bvoid\s+main\s*\(/.test(withoutComments(text)));
856
+ }
857
+ function isNumber(node, value) {
858
+ return node?.type === "Literal" && node.value === value;
859
+ }
860
+ function isCall(node, name) {
861
+ return node?.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === name;
862
+ }
863
+ function unboundedCosine(node) {
864
+ if (node.type !== "CallExpression") return false;
865
+ if (isCall(node, "dot")) return node.arguments.length === 2;
866
+ if (!isCall(node, "max") || node.arguments.length !== 2) return false;
867
+ const a = argumentAt(node, 0);
868
+ const b = argumentAt(node, 1);
869
+ return zeroFlooredDot(a, b) || zeroFlooredDot(b, a);
870
+ }
871
+ function argumentAt(call, index) {
872
+ const node = call.arguments[index];
873
+ return node?.type === "SpreadElement" ? void 0 : node;
874
+ }
875
+ function zeroFlooredDot(a, b) {
876
+ return isCall(a, "dot") && isNumber(b, 0);
877
+ }
878
+ function unsafeCosinePower(call) {
879
+ if (call.arguments.length !== 2) return false;
880
+ const [base2] = call.arguments;
881
+ return base2?.type === "BinaryExpression" && base2.operator === "-" && base2.left.type !== "PrivateIdentifier" && isNumber(base2.left, 1) && unboundedCosine(base2.right);
882
+ }
883
+ function unsafePowOffsets(source) {
884
+ const offsets = [];
885
+ const masked = withoutComments(source);
886
+ for (const match of masked.matchAll(/\bpow\s*\(/g)) {
887
+ try {
888
+ const expression = parseExpressionAt(masked, match.index, { ecmaVersion: "latest" });
889
+ simple2(expression, {
890
+ CallExpression(node) {
891
+ if (node.start === match.index && unsafeCosinePower(node)) offsets.push(node.start);
892
+ }
893
+ });
894
+ } catch {
895
+ }
896
+ }
897
+ return offsets;
898
+ }
899
+ function shaderDomainOffsets(source, glsl = false) {
900
+ const shaders = glsl ? [{ text: source, start: 0, exactOffsets: true }] : staticShaders(source);
901
+ return shaders.flatMap(
902
+ ({ text, start, exactOffsets }) => unsafePowOffsets(text).map((offset) => exactOffsets ? start + offset : void 0)
903
+ );
904
+ }
905
+
906
+ // src/rules/core.ts
817
907
  import {
818
908
  HTML_BODY_CSS_WIDTH_FIRST_RE,
819
909
  HTML_BODY_CSS_HEIGHT_FIRST_RE,
@@ -823,7 +913,8 @@ var SCRIPT_SYNTAX_EXEMPT_TYPES = /* @__PURE__ */ new Set([
823
913
  "application/json",
824
914
  "application/hyperframes-slideshow+json",
825
915
  "importmap",
826
- "module"
916
+ "module",
917
+ ...GLSL_SCRIPT_TYPES
827
918
  ]);
828
919
  function repeatedDescendantId(selector) {
829
920
  let repeated = null;
@@ -2579,20 +2670,20 @@ function findAudioGroupCarveAttrFindings(ctx) {
2579
2670
  }
2580
2671
 
2581
2672
  // src/scriptPositions.ts
2582
- import { parse as parse2 } from "acorn";
2583
- import { simple as simple2 } from "acorn-walk";
2673
+ import { parse as parse3 } from "acorn";
2674
+ import { simple as simple3 } from "acorn-walk";
2584
2675
  var callIndexes = /* @__PURE__ */ new WeakMap();
2585
2676
  function callsFor(script) {
2586
2677
  const cached = callIndexes.get(script);
2587
2678
  if (cached) return cached;
2588
2679
  const calls = [];
2589
2680
  try {
2590
- const ast = parse2(script.content, {
2681
+ const ast = parse3(script.content, {
2591
2682
  ecmaVersion: "latest",
2592
2683
  sourceType: "script",
2593
2684
  allowReturnOutsideFunction: true
2594
2685
  });
2595
- simple2(ast, {
2686
+ simple3(ast, {
2596
2687
  CallExpression: (node) => {
2597
2688
  calls.push(node);
2598
2689
  }
@@ -2618,6 +2709,7 @@ function containingCallOffset(script, tokenOffset) {
2618
2709
  }
2619
2710
 
2620
2711
  // src/rules/composition.ts
2712
+ import selectorParser2 from "postcss-selector-parser";
2621
2713
  import {
2622
2714
  COMPOSITION_VARIABLE_TYPES,
2623
2715
  isCompositionVariable,
@@ -2785,12 +2877,154 @@ function firstRescalingZoom(css) {
2785
2877
  }
2786
2878
  return null;
2787
2879
  }
2788
- function targetsCanvasRoot(selector, rootId, rootClasses) {
2880
+ function canvasScopeCandidate(selector, root) {
2789
2881
  const leftmost = selector.trim().split(/[\s>+~]+/)[0] ?? "";
2790
- const bare = leftmost.toLowerCase();
2791
- if (bare === "html" || bare === "body" || bare === ":root" || bare === "*") return true;
2792
- if (rootId && leftmostCompoundId(selector) === rootId) return true;
2793
- return leftmostCompoundClasses(selector).some((cls) => rootClasses.includes(cls));
2882
+ if (["html", "body", ":root", "*"].includes(leftmost.toLowerCase())) return true;
2883
+ if (root.id && leftmostCompoundId(selector) === root.id) return true;
2884
+ return leftmostCompoundClasses(selector).some((name) => root.classes.includes(name));
2885
+ }
2886
+ function canvasIdentity(tag) {
2887
+ return {
2888
+ name: tag.name,
2889
+ raw: tag.raw,
2890
+ id: readDecodedAttr(tag.raw, "id"),
2891
+ classes: (readDecodedAttr(tag.raw, "class") ?? "").split(/[\t\n\f\r ]+/)
2892
+ };
2893
+ }
2894
+ function anyCanvasMatch(matches) {
2895
+ if (matches.includes(true)) return true;
2896
+ return matches.includes(void 0) ? void 0 : false;
2897
+ }
2898
+ function allCanvasMatch(matches) {
2899
+ if (matches.includes(false)) return false;
2900
+ return matches.includes(void 0) ? void 0 : true;
2901
+ }
2902
+ function canvasTokenMatch(tokens, value) {
2903
+ if (tokens.includes(value)) return true;
2904
+ const fold = (token) => token.replace(/[A-Z]/g, (char) => char.toLowerCase());
2905
+ return tokens.some((token) => fold(token) === fold(value)) ? void 0 : false;
2906
+ }
2907
+ function canvasPseudoMatch(node, element, hasIdentity) {
2908
+ const name = node.value.toLowerCase();
2909
+ if (name.startsWith("::") || [":before", ":after", ":first-letter", ":first-line"].includes(name))
2910
+ return { match: false, hasIdentity: false };
2911
+ if ([":is", ":where"].includes(name)) {
2912
+ const branches = node.nodes.map(
2913
+ (branch) => canvasPathMatch(branch.nodes, element, hasIdentity)
2914
+ );
2915
+ const match = anyCanvasMatch(branches.map((branch) => branch.match));
2916
+ return { match, hasIdentity: match === true || branches.some((branch) => branch.hasIdentity) };
2917
+ }
2918
+ if (name === ":root") {
2919
+ const match = element.name === "html";
2920
+ return { match, hasIdentity: match };
2921
+ }
2922
+ if (name === ":nth-child" && element.name === "body") {
2923
+ const argument = node.nodes.map((branch) => branch.toString()).join(",").trim();
2924
+ if (/^[1-9]\d*$/.test(argument)) {
2925
+ const match = Number(argument) === 2;
2926
+ return { match, hasIdentity: match };
2927
+ }
2928
+ }
2929
+ return { match: void 0, hasIdentity: false };
2930
+ }
2931
+ function canvasTagMatch(node, element) {
2932
+ if (node.namespace !== void 0) return { match: void 0, hasIdentity: false };
2933
+ if (node.value.toLowerCase() !== element.name.toLowerCase())
2934
+ return { match: false, hasIdentity: false };
2935
+ return { match: ["html", "body"].includes(element.name) ? true : void 0, hasIdentity: true };
2936
+ }
2937
+ function canvasAttributeMatch(node, element) {
2938
+ if (node.namespace === void 0 && node.operator === void 0 && /^data-[a-z0-9_-]+$/.test(node.attribute))
2939
+ return readDecodedAttr(element.raw, node.attribute) !== null;
2940
+ return void 0;
2941
+ }
2942
+ function canvasNodeMatch(node, element, hasIdentity) {
2943
+ switch (node.type) {
2944
+ case "id": {
2945
+ const match = canvasTokenMatch(element.id === null ? [] : [element.id], node.value);
2946
+ return { match, hasIdentity: match === true };
2947
+ }
2948
+ case "class": {
2949
+ const match = canvasTokenMatch(element.classes, node.value);
2950
+ return { match, hasIdentity: match === true };
2951
+ }
2952
+ case "tag":
2953
+ return canvasTagMatch(node, element);
2954
+ case "universal":
2955
+ return { match: node.toString().trim() === "*" ? true : void 0, hasIdentity: false };
2956
+ case "attribute":
2957
+ return { match: canvasAttributeMatch(node, element), hasIdentity: false };
2958
+ case "pseudo":
2959
+ return canvasPseudoMatch(node, element, hasIdentity);
2960
+ default:
2961
+ return { match: void 0, hasIdentity: false };
2962
+ }
2963
+ }
2964
+ function canvasAncestorMatch(nodes, relation, element) {
2965
+ if (!["", ">"].includes(relation)) return void 0;
2966
+ const matches = [];
2967
+ let parent = element.parent;
2968
+ while (parent) {
2969
+ matches.push(canvasPathMatch(nodes, parent).match);
2970
+ if (relation === ">") break;
2971
+ parent = parent.parent;
2972
+ }
2973
+ if (parent === void 0) matches.push(void 0);
2974
+ return anyCanvasMatch(matches);
2975
+ }
2976
+ function canvasPathMatch(nodes, element, inheritedIdentity = false) {
2977
+ let split = -1;
2978
+ let relation = "";
2979
+ for (const [index, node] of nodes.entries()) {
2980
+ if (node.type === "combinator") {
2981
+ split = index;
2982
+ relation = node.value.trim();
2983
+ }
2984
+ }
2985
+ const subjectNodes = nodes.slice(split + 1);
2986
+ if (subjectNodes.length === 0) return { match: void 0, hasIdentity: false };
2987
+ const matches = subjectNodes.map((node) => canvasNodeMatch(node, element, inheritedIdentity));
2988
+ const hasIdentity = inheritedIdentity || matches.some((result) => result.hasIdentity);
2989
+ const subject = allCanvasMatch(
2990
+ subjectNodes.map((node, index) => {
2991
+ const match2 = matches[index]?.match;
2992
+ if (!hasIdentity || match2 !== false) return match2;
2993
+ if (["id", "class", "attribute"].includes(node.type)) return void 0;
2994
+ if (node.type === "pseudo" && !inheritedIdentity)
2995
+ return canvasNodeMatch(node, element, true).match;
2996
+ return match2;
2997
+ })
2998
+ );
2999
+ const match = subject === false || split === -1 ? subject : allCanvasMatch([subject, canvasAncestorMatch(nodes.slice(0, split), relation, element)]);
3000
+ return { match, hasIdentity: match !== false && hasIdentity };
3001
+ }
3002
+ function canvasElements(rootTag, tags) {
3003
+ const root = canvasIdentity(rootTag);
3004
+ const html = {
3005
+ ...root.name === "html" ? root : canvasIdentity(findHtmlTag(tags) ?? { name: "html", raw: "<html>" }),
3006
+ parent: null
3007
+ };
3008
+ const body = {
3009
+ ...root.name === "body" ? root : canvasIdentity(tags.find((tag) => tag.name === "body") ?? { name: "body", raw: "<body>" }),
3010
+ parent: html
3011
+ };
3012
+ const elements = [html, body];
3013
+ if (!["html", "body"].includes(root.name))
3014
+ elements.push({ ...root, parent: ["head", "template"].includes(root.name) ? void 0 : body });
3015
+ return { root, elements };
3016
+ }
3017
+ function canvasSelectorMatcher(rootTag, tags) {
3018
+ let canvas;
3019
+ return (selector, allowNewHits) => {
3020
+ const { root, elements } = canvas ??= canvasElements(rootTag, tags);
3021
+ const match = anyCanvasMatch(
3022
+ elements.map((element) => canvasPathMatch(selector.nodes, element).match)
3023
+ );
3024
+ if (match === false) return false;
3025
+ if (match === true && allowNewHits) return true;
3026
+ return selector.toString().split(",").some((part) => canvasScopeCandidate(part, root));
3027
+ };
2794
3028
  }
2795
3029
  function inlineCanvasZoomHits(rootTag, tags) {
2796
3030
  const hits = [];
@@ -2808,19 +3042,23 @@ function inlineCanvasZoomHits(rootTag, tags) {
2808
3042
  }
2809
3043
  return hits;
2810
3044
  }
2811
- function firstCanvasSelector(header, rootId, rootClasses) {
2812
- for (const selector of header.split(",")) {
2813
- const trimmed = selector.trim();
2814
- if (trimmed && targetsCanvasRoot(trimmed, rootId, rootClasses)) return trimmed;
3045
+ function firstCanvasSelector(header, matchesCanvas) {
3046
+ try {
3047
+ for (const selector of selectorParser2().astSync(header).nodes) {
3048
+ const trimmed = selector.toString().trim();
3049
+ if (matchesCanvas(selector)) return trimmed;
3050
+ }
3051
+ } catch {
3052
+ return null;
2815
3053
  }
2816
3054
  return null;
2817
3055
  }
2818
- function canvasZoomInRule(header, body, rootId, rootClasses) {
3056
+ function canvasZoomInRule(header, body, matchesCanvas) {
2819
3057
  const trimmedHeader = header.trim();
2820
3058
  if (!trimmedHeader || trimmedHeader.startsWith("@")) return null;
2821
3059
  const value = firstRescalingZoom(body);
2822
3060
  if (!value) return null;
2823
- const selector = firstCanvasSelector(trimmedHeader, rootId, rootClasses);
3061
+ const selector = firstCanvasSelector(trimmedHeader, matchesCanvas);
2824
3062
  if (!selector) return null;
2825
3063
  return {
2826
3064
  where: `\`${selector}\``,
@@ -2828,23 +3066,28 @@ function canvasZoomInRule(header, body, rootId, rootClasses) {
2828
3066
  snippet: truncateSnippet(`${selector} { zoom: ${value} }`)
2829
3067
  };
2830
3068
  }
2831
- function stylesheetCanvasZoomHits(styles, rootId, rootClasses) {
3069
+ function stylesheetCanvasZoomHits(styles, matchesCanvas) {
2832
3070
  const hits = [];
2833
3071
  for (const style of styles) {
2834
3072
  const ruleWithBody = /([^{}]+)\{([^{}]*)\}/g;
2835
3073
  const content = stripCssComments(style.content);
3074
+ const allowNewHits = style.attrs.trim() === "" && !content.includes("@");
2836
3075
  let match;
2837
3076
  while ((match = ruleWithBody.exec(content)) !== null) {
2838
- const hit = canvasZoomInRule(match[1] ?? "", match[2] ?? "", rootId, rootClasses);
3077
+ const hit = canvasZoomInRule(
3078
+ match[1] ?? "",
3079
+ match[2] ?? "",
3080
+ (selector) => matchesCanvas(selector, allowNewHits)
3081
+ );
2839
3082
  if (hit) hits.push(hit);
2840
3083
  }
2841
3084
  }
2842
3085
  return hits;
2843
3086
  }
2844
- function canvasZoomHits(rootTag, tags, styles, rootId, rootClasses) {
3087
+ function canvasZoomHits(rootTag, tags, styles) {
2845
3088
  return [
2846
3089
  ...inlineCanvasZoomHits(rootTag, tags),
2847
- ...stylesheetCanvasZoomHits(styles, rootId, rootClasses)
3090
+ ...stylesheetCanvasZoomHits(styles, canvasSelectorMatcher(rootTag, tags))
2848
3091
  ];
2849
3092
  }
2850
3093
  function collectDeclaredVariableIds(htmlTagRaw) {
@@ -3742,9 +3985,7 @@ ${allInlineStyles}`.replace(/\/\*[\s\S]*?\*\//g, "");
3742
3985
  // fired on every `zoom` would be flagging correct authoring.
3743
3986
  ({ rootTag, tags, styles }) => {
3744
3987
  if (!rootTag) return [];
3745
- const rootId = readAttr(rootTag.raw, "id");
3746
- const rootClasses = (readAttr(rootTag.raw, "class") || "").split(/\s+/).filter(Boolean);
3747
- const hits = canvasZoomHits(rootTag, tags, styles, rootId, rootClasses);
3988
+ const hits = canvasZoomHits(rootTag, tags, styles);
3748
3989
  const hit = hits[0];
3749
3990
  if (!hit) return [];
3750
3991
  const enlarging = !hit.value.startsWith("-") && parseFloat(hit.value) > (hit.value.includes("%") ? 100 : 1);
@@ -4792,9 +5033,12 @@ ${right.raw}`)
4792
5033
  const usesGsap = allScriptTexts.some(
4793
5034
  (t) => /gsap\.(to|from|fromTo|timeline|set|registerPlugin)\b/.test(t)
4794
5035
  );
4795
- const hasGsapScript = allScriptSrcs.some((src) => /gsap/i.test(src));
5036
+ const isGsapCoreSrc = (src) => /(^|\/)gsap(-\d+(\.\d+)*)?(\.min)?\.js$/i.test(src.split(/[?#]/)[0] ?? "");
5037
+ const hasGsapScript = allScriptSrcs.some(isGsapCoreSrc);
4796
5038
  const hasInlineGsap = allScriptTexts.some(
4797
- (t) => /\/\*\s*inlined:.*gsap/i.test(t) || /\b_gsScope\b/.test(t) || /\bGreenSock\b/.test(t) || /\bgsap\.(config|defaults|version)\b/.test(t) || t.length > 5e3 && /\bgsap\b/i.test(t)
5039
+ (t) => [...t.matchAll(/\/\*\s*inlined:\s*(\S+)\s*\*\//gi)].some(
5040
+ (m) => isGsapCoreSrc(m[1] ?? "")
5041
+ ) || /\/\*!\s*\*\s*GSAP\s+\d/.test(t) || /Missing plugin\? gsap\.registerPlugin\(\)/.test(t) || /\b_gsScope\b/.test(t)
4798
5042
  );
4799
5043
  if (!usesGsap || hasGsapScript || hasInlineGsap || canInheritGsapFromHost) return [];
4800
5044
  return [
@@ -6500,93 +6744,6 @@ var structureRules = [
6500
6744
  }
6501
6745
  ];
6502
6746
 
6503
- // src/shaderDomains.ts
6504
- import { parse as parse3, parseExpressionAt } from "acorn";
6505
- import { simple as simple3 } from "acorn-walk";
6506
- function withoutComments(source) {
6507
- return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*/g, (text) => text.replace(/[^\r\n]/g, " "));
6508
- }
6509
- function staticShaders(source) {
6510
- const shaders = [];
6511
- try {
6512
- const ast = parse3(source, {
6513
- ecmaVersion: "latest",
6514
- sourceType: "module",
6515
- allowReturnOutsideFunction: true
6516
- });
6517
- simple3(ast, {
6518
- Literal(node) {
6519
- if (typeof node.value !== "string") return;
6520
- shaders.push({
6521
- text: node.value,
6522
- start: node.start + 1,
6523
- exactOffsets: source.slice(node.start + 1, node.end - 1) === node.value
6524
- });
6525
- },
6526
- TemplateLiteral(node) {
6527
- if (node.expressions.length !== 0) return;
6528
- const value = node.quasis[0]?.value;
6529
- if (value?.cooked == null) return;
6530
- shaders.push({
6531
- text: value.cooked,
6532
- start: node.start + 1,
6533
- exactOffsets: source.slice(node.start + 1, node.end - 1) === value.cooked
6534
- });
6535
- }
6536
- });
6537
- } catch {
6538
- }
6539
- return shaders.filter(({ text }) => /\bvoid\s+main\s*\(/.test(withoutComments(text)));
6540
- }
6541
- function isNumber(node, value) {
6542
- return node?.type === "Literal" && node.value === value;
6543
- }
6544
- function isCall(node, name) {
6545
- return node?.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === name;
6546
- }
6547
- function unboundedCosine(node) {
6548
- if (node.type !== "CallExpression") return false;
6549
- if (isCall(node, "dot")) return node.arguments.length === 2;
6550
- if (!isCall(node, "max") || node.arguments.length !== 2) return false;
6551
- const a = argumentAt(node, 0);
6552
- const b = argumentAt(node, 1);
6553
- return zeroFlooredDot(a, b) || zeroFlooredDot(b, a);
6554
- }
6555
- function argumentAt(call, index) {
6556
- const node = call.arguments[index];
6557
- return node?.type === "SpreadElement" ? void 0 : node;
6558
- }
6559
- function zeroFlooredDot(a, b) {
6560
- return isCall(a, "dot") && isNumber(b, 0);
6561
- }
6562
- function unsafeCosinePower(call) {
6563
- if (call.arguments.length !== 2) return false;
6564
- const [base2] = call.arguments;
6565
- return base2?.type === "BinaryExpression" && base2.operator === "-" && base2.left.type !== "PrivateIdentifier" && isNumber(base2.left, 1) && unboundedCosine(base2.right);
6566
- }
6567
- function unsafePowOffsets(source) {
6568
- const offsets = [];
6569
- const masked = withoutComments(source);
6570
- for (const match of masked.matchAll(/\bpow\s*\(/g)) {
6571
- try {
6572
- const expression = parseExpressionAt(masked, match.index, { ecmaVersion: "latest" });
6573
- simple3(expression, {
6574
- CallExpression(node) {
6575
- if (node.start === match.index && unsafeCosinePower(node)) offsets.push(node.start);
6576
- }
6577
- });
6578
- } catch {
6579
- }
6580
- }
6581
- return offsets;
6582
- }
6583
- function shaderDomainOffsets(source, glsl = false) {
6584
- const shaders = glsl ? [{ text: source, start: 0, exactOffsets: true }] : staticShaders(source);
6585
- return shaders.flatMap(
6586
- ({ text, start, exactOffsets }) => unsafePowOffsets(text).map((offset) => exactOffsets ? start + offset : void 0)
6587
- );
6588
- }
6589
-
6590
6747
  // src/rules/shaders.ts
6591
6748
  function shaderDomainWarning() {
6592
6749
  return {
@@ -6601,7 +6758,7 @@ var shaderRules = [
6601
6758
  const tag = `<script ${script.attrs}>`;
6602
6759
  if (readAttr(tag, "src") !== null) return [];
6603
6760
  const type = readAttr(tag, "type") ?? "";
6604
- const glsl = /^(?:x-shader\/x-(?:fragment|vertex)|text\/glsl)$/i.test(type);
6761
+ const glsl = GLSL_SCRIPT_TYPES.has(type.toLowerCase());
6605
6762
  if (type && !glsl && !/^(?:module|(?:text|application)\/javascript)$/i.test(type)) return [];
6606
6763
  return shaderDomainOffsets(script.content, glsl).map((offset) => ({
6607
6764
  ...shaderDomainWarning(),
package/dist/index.js CHANGED
@@ -820,6 +820,96 @@ function buildLintContext(html, options = {}) {
820
820
  // src/rules/core.ts
821
821
  import postcss from "postcss";
822
822
  import selectorParser from "postcss-selector-parser";
823
+
824
+ // src/shaderDomains.ts
825
+ import { parse as parse2, parseExpressionAt } from "acorn";
826
+ import { simple as simple2 } from "acorn-walk";
827
+ var GLSL_SCRIPT_TYPES = /* @__PURE__ */ new Set(["x-shader/x-fragment", "x-shader/x-vertex", "text/glsl"]);
828
+ function withoutComments(source) {
829
+ return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*/g, (text) => text.replace(/[^\r\n]/g, " "));
830
+ }
831
+ function staticShaders(source) {
832
+ const shaders = [];
833
+ try {
834
+ const ast = parse2(source, {
835
+ ecmaVersion: "latest",
836
+ sourceType: "module",
837
+ allowReturnOutsideFunction: true
838
+ });
839
+ simple2(ast, {
840
+ Literal(node) {
841
+ if (typeof node.value !== "string") return;
842
+ shaders.push({
843
+ text: node.value,
844
+ start: node.start + 1,
845
+ exactOffsets: source.slice(node.start + 1, node.end - 1) === node.value
846
+ });
847
+ },
848
+ TemplateLiteral(node) {
849
+ if (node.expressions.length !== 0) return;
850
+ const value = node.quasis[0]?.value;
851
+ if (value?.cooked == null) return;
852
+ shaders.push({
853
+ text: value.cooked,
854
+ start: node.start + 1,
855
+ exactOffsets: source.slice(node.start + 1, node.end - 1) === value.cooked
856
+ });
857
+ }
858
+ });
859
+ } catch {
860
+ }
861
+ return shaders.filter(({ text }) => /\bvoid\s+main\s*\(/.test(withoutComments(text)));
862
+ }
863
+ function isNumber(node, value) {
864
+ return node?.type === "Literal" && node.value === value;
865
+ }
866
+ function isCall(node, name) {
867
+ return node?.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === name;
868
+ }
869
+ function unboundedCosine(node) {
870
+ if (node.type !== "CallExpression") return false;
871
+ if (isCall(node, "dot")) return node.arguments.length === 2;
872
+ if (!isCall(node, "max") || node.arguments.length !== 2) return false;
873
+ const a = argumentAt(node, 0);
874
+ const b = argumentAt(node, 1);
875
+ return zeroFlooredDot(a, b) || zeroFlooredDot(b, a);
876
+ }
877
+ function argumentAt(call, index) {
878
+ const node = call.arguments[index];
879
+ return node?.type === "SpreadElement" ? void 0 : node;
880
+ }
881
+ function zeroFlooredDot(a, b) {
882
+ return isCall(a, "dot") && isNumber(b, 0);
883
+ }
884
+ function unsafeCosinePower(call) {
885
+ if (call.arguments.length !== 2) return false;
886
+ const [base2] = call.arguments;
887
+ return base2?.type === "BinaryExpression" && base2.operator === "-" && base2.left.type !== "PrivateIdentifier" && isNumber(base2.left, 1) && unboundedCosine(base2.right);
888
+ }
889
+ function unsafePowOffsets(source) {
890
+ const offsets = [];
891
+ const masked = withoutComments(source);
892
+ for (const match of masked.matchAll(/\bpow\s*\(/g)) {
893
+ try {
894
+ const expression = parseExpressionAt(masked, match.index, { ecmaVersion: "latest" });
895
+ simple2(expression, {
896
+ CallExpression(node) {
897
+ if (node.start === match.index && unsafeCosinePower(node)) offsets.push(node.start);
898
+ }
899
+ });
900
+ } catch {
901
+ }
902
+ }
903
+ return offsets;
904
+ }
905
+ function shaderDomainOffsets(source, glsl = false) {
906
+ const shaders = glsl ? [{ text: source, start: 0, exactOffsets: true }] : staticShaders(source);
907
+ return shaders.flatMap(
908
+ ({ text, start, exactOffsets }) => unsafePowOffsets(text).map((offset) => exactOffsets ? start + offset : void 0)
909
+ );
910
+ }
911
+
912
+ // src/rules/core.ts
823
913
  import {
824
914
  HTML_BODY_CSS_WIDTH_FIRST_RE,
825
915
  HTML_BODY_CSS_HEIGHT_FIRST_RE,
@@ -829,7 +919,8 @@ var SCRIPT_SYNTAX_EXEMPT_TYPES = /* @__PURE__ */ new Set([
829
919
  "application/json",
830
920
  "application/hyperframes-slideshow+json",
831
921
  "importmap",
832
- "module"
922
+ "module",
923
+ ...GLSL_SCRIPT_TYPES
833
924
  ]);
834
925
  function repeatedDescendantId(selector) {
835
926
  let repeated = null;
@@ -2585,20 +2676,20 @@ function findAudioGroupCarveAttrFindings(ctx) {
2585
2676
  }
2586
2677
 
2587
2678
  // src/scriptPositions.ts
2588
- import { parse as parse2 } from "acorn";
2589
- import { simple as simple2 } from "acorn-walk";
2679
+ import { parse as parse3 } from "acorn";
2680
+ import { simple as simple3 } from "acorn-walk";
2590
2681
  var callIndexes = /* @__PURE__ */ new WeakMap();
2591
2682
  function callsFor(script) {
2592
2683
  const cached = callIndexes.get(script);
2593
2684
  if (cached) return cached;
2594
2685
  const calls = [];
2595
2686
  try {
2596
- const ast = parse2(script.content, {
2687
+ const ast = parse3(script.content, {
2597
2688
  ecmaVersion: "latest",
2598
2689
  sourceType: "script",
2599
2690
  allowReturnOutsideFunction: true
2600
2691
  });
2601
- simple2(ast, {
2692
+ simple3(ast, {
2602
2693
  CallExpression: (node) => {
2603
2694
  calls.push(node);
2604
2695
  }
@@ -2624,6 +2715,7 @@ function containingCallOffset(script, tokenOffset) {
2624
2715
  }
2625
2716
 
2626
2717
  // src/rules/composition.ts
2718
+ import selectorParser2 from "postcss-selector-parser";
2627
2719
  import {
2628
2720
  COMPOSITION_VARIABLE_TYPES,
2629
2721
  isCompositionVariable,
@@ -2791,12 +2883,154 @@ function firstRescalingZoom(css) {
2791
2883
  }
2792
2884
  return null;
2793
2885
  }
2794
- function targetsCanvasRoot(selector, rootId, rootClasses) {
2886
+ function canvasScopeCandidate(selector, root) {
2795
2887
  const leftmost = selector.trim().split(/[\s>+~]+/)[0] ?? "";
2796
- const bare = leftmost.toLowerCase();
2797
- if (bare === "html" || bare === "body" || bare === ":root" || bare === "*") return true;
2798
- if (rootId && leftmostCompoundId(selector) === rootId) return true;
2799
- return leftmostCompoundClasses(selector).some((cls) => rootClasses.includes(cls));
2888
+ if (["html", "body", ":root", "*"].includes(leftmost.toLowerCase())) return true;
2889
+ if (root.id && leftmostCompoundId(selector) === root.id) return true;
2890
+ return leftmostCompoundClasses(selector).some((name) => root.classes.includes(name));
2891
+ }
2892
+ function canvasIdentity(tag) {
2893
+ return {
2894
+ name: tag.name,
2895
+ raw: tag.raw,
2896
+ id: readDecodedAttr(tag.raw, "id"),
2897
+ classes: (readDecodedAttr(tag.raw, "class") ?? "").split(/[\t\n\f\r ]+/)
2898
+ };
2899
+ }
2900
+ function anyCanvasMatch(matches) {
2901
+ if (matches.includes(true)) return true;
2902
+ return matches.includes(void 0) ? void 0 : false;
2903
+ }
2904
+ function allCanvasMatch(matches) {
2905
+ if (matches.includes(false)) return false;
2906
+ return matches.includes(void 0) ? void 0 : true;
2907
+ }
2908
+ function canvasTokenMatch(tokens, value) {
2909
+ if (tokens.includes(value)) return true;
2910
+ const fold = (token) => token.replace(/[A-Z]/g, (char) => char.toLowerCase());
2911
+ return tokens.some((token) => fold(token) === fold(value)) ? void 0 : false;
2912
+ }
2913
+ function canvasPseudoMatch(node, element, hasIdentity) {
2914
+ const name = node.value.toLowerCase();
2915
+ if (name.startsWith("::") || [":before", ":after", ":first-letter", ":first-line"].includes(name))
2916
+ return { match: false, hasIdentity: false };
2917
+ if ([":is", ":where"].includes(name)) {
2918
+ const branches = node.nodes.map(
2919
+ (branch) => canvasPathMatch(branch.nodes, element, hasIdentity)
2920
+ );
2921
+ const match = anyCanvasMatch(branches.map((branch) => branch.match));
2922
+ return { match, hasIdentity: match === true || branches.some((branch) => branch.hasIdentity) };
2923
+ }
2924
+ if (name === ":root") {
2925
+ const match = element.name === "html";
2926
+ return { match, hasIdentity: match };
2927
+ }
2928
+ if (name === ":nth-child" && element.name === "body") {
2929
+ const argument = node.nodes.map((branch) => branch.toString()).join(",").trim();
2930
+ if (/^[1-9]\d*$/.test(argument)) {
2931
+ const match = Number(argument) === 2;
2932
+ return { match, hasIdentity: match };
2933
+ }
2934
+ }
2935
+ return { match: void 0, hasIdentity: false };
2936
+ }
2937
+ function canvasTagMatch(node, element) {
2938
+ if (node.namespace !== void 0) return { match: void 0, hasIdentity: false };
2939
+ if (node.value.toLowerCase() !== element.name.toLowerCase())
2940
+ return { match: false, hasIdentity: false };
2941
+ return { match: ["html", "body"].includes(element.name) ? true : void 0, hasIdentity: true };
2942
+ }
2943
+ function canvasAttributeMatch(node, element) {
2944
+ if (node.namespace === void 0 && node.operator === void 0 && /^data-[a-z0-9_-]+$/.test(node.attribute))
2945
+ return readDecodedAttr(element.raw, node.attribute) !== null;
2946
+ return void 0;
2947
+ }
2948
+ function canvasNodeMatch(node, element, hasIdentity) {
2949
+ switch (node.type) {
2950
+ case "id": {
2951
+ const match = canvasTokenMatch(element.id === null ? [] : [element.id], node.value);
2952
+ return { match, hasIdentity: match === true };
2953
+ }
2954
+ case "class": {
2955
+ const match = canvasTokenMatch(element.classes, node.value);
2956
+ return { match, hasIdentity: match === true };
2957
+ }
2958
+ case "tag":
2959
+ return canvasTagMatch(node, element);
2960
+ case "universal":
2961
+ return { match: node.toString().trim() === "*" ? true : void 0, hasIdentity: false };
2962
+ case "attribute":
2963
+ return { match: canvasAttributeMatch(node, element), hasIdentity: false };
2964
+ case "pseudo":
2965
+ return canvasPseudoMatch(node, element, hasIdentity);
2966
+ default:
2967
+ return { match: void 0, hasIdentity: false };
2968
+ }
2969
+ }
2970
+ function canvasAncestorMatch(nodes, relation, element) {
2971
+ if (!["", ">"].includes(relation)) return void 0;
2972
+ const matches = [];
2973
+ let parent = element.parent;
2974
+ while (parent) {
2975
+ matches.push(canvasPathMatch(nodes, parent).match);
2976
+ if (relation === ">") break;
2977
+ parent = parent.parent;
2978
+ }
2979
+ if (parent === void 0) matches.push(void 0);
2980
+ return anyCanvasMatch(matches);
2981
+ }
2982
+ function canvasPathMatch(nodes, element, inheritedIdentity = false) {
2983
+ let split = -1;
2984
+ let relation = "";
2985
+ for (const [index, node] of nodes.entries()) {
2986
+ if (node.type === "combinator") {
2987
+ split = index;
2988
+ relation = node.value.trim();
2989
+ }
2990
+ }
2991
+ const subjectNodes = nodes.slice(split + 1);
2992
+ if (subjectNodes.length === 0) return { match: void 0, hasIdentity: false };
2993
+ const matches = subjectNodes.map((node) => canvasNodeMatch(node, element, inheritedIdentity));
2994
+ const hasIdentity = inheritedIdentity || matches.some((result) => result.hasIdentity);
2995
+ const subject = allCanvasMatch(
2996
+ subjectNodes.map((node, index) => {
2997
+ const match2 = matches[index]?.match;
2998
+ if (!hasIdentity || match2 !== false) return match2;
2999
+ if (["id", "class", "attribute"].includes(node.type)) return void 0;
3000
+ if (node.type === "pseudo" && !inheritedIdentity)
3001
+ return canvasNodeMatch(node, element, true).match;
3002
+ return match2;
3003
+ })
3004
+ );
3005
+ const match = subject === false || split === -1 ? subject : allCanvasMatch([subject, canvasAncestorMatch(nodes.slice(0, split), relation, element)]);
3006
+ return { match, hasIdentity: match !== false && hasIdentity };
3007
+ }
3008
+ function canvasElements(rootTag, tags) {
3009
+ const root = canvasIdentity(rootTag);
3010
+ const html = {
3011
+ ...root.name === "html" ? root : canvasIdentity(findHtmlTag(tags) ?? { name: "html", raw: "<html>" }),
3012
+ parent: null
3013
+ };
3014
+ const body = {
3015
+ ...root.name === "body" ? root : canvasIdentity(tags.find((tag) => tag.name === "body") ?? { name: "body", raw: "<body>" }),
3016
+ parent: html
3017
+ };
3018
+ const elements = [html, body];
3019
+ if (!["html", "body"].includes(root.name))
3020
+ elements.push({ ...root, parent: ["head", "template"].includes(root.name) ? void 0 : body });
3021
+ return { root, elements };
3022
+ }
3023
+ function canvasSelectorMatcher(rootTag, tags) {
3024
+ let canvas;
3025
+ return (selector, allowNewHits) => {
3026
+ const { root, elements } = canvas ??= canvasElements(rootTag, tags);
3027
+ const match = anyCanvasMatch(
3028
+ elements.map((element) => canvasPathMatch(selector.nodes, element).match)
3029
+ );
3030
+ if (match === false) return false;
3031
+ if (match === true && allowNewHits) return true;
3032
+ return selector.toString().split(",").some((part) => canvasScopeCandidate(part, root));
3033
+ };
2800
3034
  }
2801
3035
  function inlineCanvasZoomHits(rootTag, tags) {
2802
3036
  const hits = [];
@@ -2814,19 +3048,23 @@ function inlineCanvasZoomHits(rootTag, tags) {
2814
3048
  }
2815
3049
  return hits;
2816
3050
  }
2817
- function firstCanvasSelector(header, rootId, rootClasses) {
2818
- for (const selector of header.split(",")) {
2819
- const trimmed = selector.trim();
2820
- if (trimmed && targetsCanvasRoot(trimmed, rootId, rootClasses)) return trimmed;
3051
+ function firstCanvasSelector(header, matchesCanvas) {
3052
+ try {
3053
+ for (const selector of selectorParser2().astSync(header).nodes) {
3054
+ const trimmed = selector.toString().trim();
3055
+ if (matchesCanvas(selector)) return trimmed;
3056
+ }
3057
+ } catch {
3058
+ return null;
2821
3059
  }
2822
3060
  return null;
2823
3061
  }
2824
- function canvasZoomInRule(header, body, rootId, rootClasses) {
3062
+ function canvasZoomInRule(header, body, matchesCanvas) {
2825
3063
  const trimmedHeader = header.trim();
2826
3064
  if (!trimmedHeader || trimmedHeader.startsWith("@")) return null;
2827
3065
  const value = firstRescalingZoom(body);
2828
3066
  if (!value) return null;
2829
- const selector = firstCanvasSelector(trimmedHeader, rootId, rootClasses);
3067
+ const selector = firstCanvasSelector(trimmedHeader, matchesCanvas);
2830
3068
  if (!selector) return null;
2831
3069
  return {
2832
3070
  where: `\`${selector}\``,
@@ -2834,23 +3072,28 @@ function canvasZoomInRule(header, body, rootId, rootClasses) {
2834
3072
  snippet: truncateSnippet(`${selector} { zoom: ${value} }`)
2835
3073
  };
2836
3074
  }
2837
- function stylesheetCanvasZoomHits(styles, rootId, rootClasses) {
3075
+ function stylesheetCanvasZoomHits(styles, matchesCanvas) {
2838
3076
  const hits = [];
2839
3077
  for (const style of styles) {
2840
3078
  const ruleWithBody = /([^{}]+)\{([^{}]*)\}/g;
2841
3079
  const content = stripCssComments(style.content);
3080
+ const allowNewHits = style.attrs.trim() === "" && !content.includes("@");
2842
3081
  let match;
2843
3082
  while ((match = ruleWithBody.exec(content)) !== null) {
2844
- const hit = canvasZoomInRule(match[1] ?? "", match[2] ?? "", rootId, rootClasses);
3083
+ const hit = canvasZoomInRule(
3084
+ match[1] ?? "",
3085
+ match[2] ?? "",
3086
+ (selector) => matchesCanvas(selector, allowNewHits)
3087
+ );
2845
3088
  if (hit) hits.push(hit);
2846
3089
  }
2847
3090
  }
2848
3091
  return hits;
2849
3092
  }
2850
- function canvasZoomHits(rootTag, tags, styles, rootId, rootClasses) {
3093
+ function canvasZoomHits(rootTag, tags, styles) {
2851
3094
  return [
2852
3095
  ...inlineCanvasZoomHits(rootTag, tags),
2853
- ...stylesheetCanvasZoomHits(styles, rootId, rootClasses)
3096
+ ...stylesheetCanvasZoomHits(styles, canvasSelectorMatcher(rootTag, tags))
2854
3097
  ];
2855
3098
  }
2856
3099
  function collectDeclaredVariableIds(htmlTagRaw) {
@@ -3748,9 +3991,7 @@ ${allInlineStyles}`.replace(/\/\*[\s\S]*?\*\//g, "");
3748
3991
  // fired on every `zoom` would be flagging correct authoring.
3749
3992
  ({ rootTag, tags, styles }) => {
3750
3993
  if (!rootTag) return [];
3751
- const rootId = readAttr(rootTag.raw, "id");
3752
- const rootClasses = (readAttr(rootTag.raw, "class") || "").split(/\s+/).filter(Boolean);
3753
- const hits = canvasZoomHits(rootTag, tags, styles, rootId, rootClasses);
3994
+ const hits = canvasZoomHits(rootTag, tags, styles);
3754
3995
  const hit = hits[0];
3755
3996
  if (!hit) return [];
3756
3997
  const enlarging = !hit.value.startsWith("-") && parseFloat(hit.value) > (hit.value.includes("%") ? 100 : 1);
@@ -4798,9 +5039,12 @@ ${right.raw}`)
4798
5039
  const usesGsap = allScriptTexts.some(
4799
5040
  (t) => /gsap\.(to|from|fromTo|timeline|set|registerPlugin)\b/.test(t)
4800
5041
  );
4801
- const hasGsapScript = allScriptSrcs.some((src) => /gsap/i.test(src));
5042
+ const isGsapCoreSrc = (src) => /(^|\/)gsap(-\d+(\.\d+)*)?(\.min)?\.js$/i.test(src.split(/[?#]/)[0] ?? "");
5043
+ const hasGsapScript = allScriptSrcs.some(isGsapCoreSrc);
4802
5044
  const hasInlineGsap = allScriptTexts.some(
4803
- (t) => /\/\*\s*inlined:.*gsap/i.test(t) || /\b_gsScope\b/.test(t) || /\bGreenSock\b/.test(t) || /\bgsap\.(config|defaults|version)\b/.test(t) || t.length > 5e3 && /\bgsap\b/i.test(t)
5045
+ (t) => [...t.matchAll(/\/\*\s*inlined:\s*(\S+)\s*\*\//gi)].some(
5046
+ (m) => isGsapCoreSrc(m[1] ?? "")
5047
+ ) || /\/\*!\s*\*\s*GSAP\s+\d/.test(t) || /Missing plugin\? gsap\.registerPlugin\(\)/.test(t) || /\b_gsScope\b/.test(t)
4804
5048
  );
4805
5049
  if (!usesGsap || hasGsapScript || hasInlineGsap || canInheritGsapFromHost) return [];
4806
5050
  return [
@@ -6506,93 +6750,6 @@ var structureRules = [
6506
6750
  }
6507
6751
  ];
6508
6752
 
6509
- // src/shaderDomains.ts
6510
- import { parse as parse3, parseExpressionAt } from "acorn";
6511
- import { simple as simple3 } from "acorn-walk";
6512
- function withoutComments(source) {
6513
- return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*/g, (text) => text.replace(/[^\r\n]/g, " "));
6514
- }
6515
- function staticShaders(source) {
6516
- const shaders = [];
6517
- try {
6518
- const ast = parse3(source, {
6519
- ecmaVersion: "latest",
6520
- sourceType: "module",
6521
- allowReturnOutsideFunction: true
6522
- });
6523
- simple3(ast, {
6524
- Literal(node) {
6525
- if (typeof node.value !== "string") return;
6526
- shaders.push({
6527
- text: node.value,
6528
- start: node.start + 1,
6529
- exactOffsets: source.slice(node.start + 1, node.end - 1) === node.value
6530
- });
6531
- },
6532
- TemplateLiteral(node) {
6533
- if (node.expressions.length !== 0) return;
6534
- const value = node.quasis[0]?.value;
6535
- if (value?.cooked == null) return;
6536
- shaders.push({
6537
- text: value.cooked,
6538
- start: node.start + 1,
6539
- exactOffsets: source.slice(node.start + 1, node.end - 1) === value.cooked
6540
- });
6541
- }
6542
- });
6543
- } catch {
6544
- }
6545
- return shaders.filter(({ text }) => /\bvoid\s+main\s*\(/.test(withoutComments(text)));
6546
- }
6547
- function isNumber(node, value) {
6548
- return node?.type === "Literal" && node.value === value;
6549
- }
6550
- function isCall(node, name) {
6551
- return node?.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === name;
6552
- }
6553
- function unboundedCosine(node) {
6554
- if (node.type !== "CallExpression") return false;
6555
- if (isCall(node, "dot")) return node.arguments.length === 2;
6556
- if (!isCall(node, "max") || node.arguments.length !== 2) return false;
6557
- const a = argumentAt(node, 0);
6558
- const b = argumentAt(node, 1);
6559
- return zeroFlooredDot(a, b) || zeroFlooredDot(b, a);
6560
- }
6561
- function argumentAt(call, index) {
6562
- const node = call.arguments[index];
6563
- return node?.type === "SpreadElement" ? void 0 : node;
6564
- }
6565
- function zeroFlooredDot(a, b) {
6566
- return isCall(a, "dot") && isNumber(b, 0);
6567
- }
6568
- function unsafeCosinePower(call) {
6569
- if (call.arguments.length !== 2) return false;
6570
- const [base2] = call.arguments;
6571
- return base2?.type === "BinaryExpression" && base2.operator === "-" && base2.left.type !== "PrivateIdentifier" && isNumber(base2.left, 1) && unboundedCosine(base2.right);
6572
- }
6573
- function unsafePowOffsets(source) {
6574
- const offsets = [];
6575
- const masked = withoutComments(source);
6576
- for (const match of masked.matchAll(/\bpow\s*\(/g)) {
6577
- try {
6578
- const expression = parseExpressionAt(masked, match.index, { ecmaVersion: "latest" });
6579
- simple3(expression, {
6580
- CallExpression(node) {
6581
- if (node.start === match.index && unsafeCosinePower(node)) offsets.push(node.start);
6582
- }
6583
- });
6584
- } catch {
6585
- }
6586
- }
6587
- return offsets;
6588
- }
6589
- function shaderDomainOffsets(source, glsl = false) {
6590
- const shaders = glsl ? [{ text: source, start: 0, exactOffsets: true }] : staticShaders(source);
6591
- return shaders.flatMap(
6592
- ({ text, start, exactOffsets }) => unsafePowOffsets(text).map((offset) => exactOffsets ? start + offset : void 0)
6593
- );
6594
- }
6595
-
6596
6753
  // src/rules/shaders.ts
6597
6754
  function shaderDomainWarning() {
6598
6755
  return {
@@ -6607,7 +6764,7 @@ var shaderRules = [
6607
6764
  const tag = `<script ${script.attrs}>`;
6608
6765
  if (readAttr(tag, "src") !== null) return [];
6609
6766
  const type = readAttr(tag, "type") ?? "";
6610
- const glsl = /^(?:x-shader\/x-(?:fragment|vertex)|text\/glsl)$/i.test(type);
6767
+ const glsl = GLSL_SCRIPT_TYPES.has(type.toLowerCase());
6611
6768
  if (type && !glsl && !/^(?:module|(?:text|application)\/javascript)$/i.test(type)) return [];
6612
6769
  return shaderDomainOffsets(script.content, glsl).map((offset) => ({
6613
6770
  ...shaderDomainWarning(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/lint",
3
- "version": "0.8.193",
3
+ "version": "0.8.195",
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.193"
38
+ "@hyperframes/parsers": "0.8.195"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^25.0.10",