@hyperframes/lint 0.8.194 → 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 +173 -20
  2. package/dist/index.js +173 -20
  3. package/package.json +2 -2
package/dist/browser.js CHANGED
@@ -2709,6 +2709,7 @@ function containingCallOffset(script, tokenOffset) {
2709
2709
  }
2710
2710
 
2711
2711
  // src/rules/composition.ts
2712
+ import selectorParser2 from "postcss-selector-parser";
2712
2713
  import {
2713
2714
  COMPOSITION_VARIABLE_TYPES,
2714
2715
  isCompositionVariable,
@@ -2876,12 +2877,154 @@ function firstRescalingZoom(css) {
2876
2877
  }
2877
2878
  return null;
2878
2879
  }
2879
- function targetsCanvasRoot(selector, rootId, rootClasses) {
2880
+ function canvasScopeCandidate(selector, root) {
2880
2881
  const leftmost = selector.trim().split(/[\s>+~]+/)[0] ?? "";
2881
- const bare = leftmost.toLowerCase();
2882
- if (bare === "html" || bare === "body" || bare === ":root" || bare === "*") return true;
2883
- if (rootId && leftmostCompoundId(selector) === rootId) return true;
2884
- 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
+ };
2885
3028
  }
2886
3029
  function inlineCanvasZoomHits(rootTag, tags) {
2887
3030
  const hits = [];
@@ -2899,19 +3042,23 @@ function inlineCanvasZoomHits(rootTag, tags) {
2899
3042
  }
2900
3043
  return hits;
2901
3044
  }
2902
- function firstCanvasSelector(header, rootId, rootClasses) {
2903
- for (const selector of header.split(",")) {
2904
- const trimmed = selector.trim();
2905
- 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;
2906
3053
  }
2907
3054
  return null;
2908
3055
  }
2909
- function canvasZoomInRule(header, body, rootId, rootClasses) {
3056
+ function canvasZoomInRule(header, body, matchesCanvas) {
2910
3057
  const trimmedHeader = header.trim();
2911
3058
  if (!trimmedHeader || trimmedHeader.startsWith("@")) return null;
2912
3059
  const value = firstRescalingZoom(body);
2913
3060
  if (!value) return null;
2914
- const selector = firstCanvasSelector(trimmedHeader, rootId, rootClasses);
3061
+ const selector = firstCanvasSelector(trimmedHeader, matchesCanvas);
2915
3062
  if (!selector) return null;
2916
3063
  return {
2917
3064
  where: `\`${selector}\``,
@@ -2919,23 +3066,28 @@ function canvasZoomInRule(header, body, rootId, rootClasses) {
2919
3066
  snippet: truncateSnippet(`${selector} { zoom: ${value} }`)
2920
3067
  };
2921
3068
  }
2922
- function stylesheetCanvasZoomHits(styles, rootId, rootClasses) {
3069
+ function stylesheetCanvasZoomHits(styles, matchesCanvas) {
2923
3070
  const hits = [];
2924
3071
  for (const style of styles) {
2925
3072
  const ruleWithBody = /([^{}]+)\{([^{}]*)\}/g;
2926
3073
  const content = stripCssComments(style.content);
3074
+ const allowNewHits = style.attrs.trim() === "" && !content.includes("@");
2927
3075
  let match;
2928
3076
  while ((match = ruleWithBody.exec(content)) !== null) {
2929
- 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
+ );
2930
3082
  if (hit) hits.push(hit);
2931
3083
  }
2932
3084
  }
2933
3085
  return hits;
2934
3086
  }
2935
- function canvasZoomHits(rootTag, tags, styles, rootId, rootClasses) {
3087
+ function canvasZoomHits(rootTag, tags, styles) {
2936
3088
  return [
2937
3089
  ...inlineCanvasZoomHits(rootTag, tags),
2938
- ...stylesheetCanvasZoomHits(styles, rootId, rootClasses)
3090
+ ...stylesheetCanvasZoomHits(styles, canvasSelectorMatcher(rootTag, tags))
2939
3091
  ];
2940
3092
  }
2941
3093
  function collectDeclaredVariableIds(htmlTagRaw) {
@@ -3833,9 +3985,7 @@ ${allInlineStyles}`.replace(/\/\*[\s\S]*?\*\//g, "");
3833
3985
  // fired on every `zoom` would be flagging correct authoring.
3834
3986
  ({ rootTag, tags, styles }) => {
3835
3987
  if (!rootTag) return [];
3836
- const rootId = readAttr(rootTag.raw, "id");
3837
- const rootClasses = (readAttr(rootTag.raw, "class") || "").split(/\s+/).filter(Boolean);
3838
- const hits = canvasZoomHits(rootTag, tags, styles, rootId, rootClasses);
3988
+ const hits = canvasZoomHits(rootTag, tags, styles);
3839
3989
  const hit = hits[0];
3840
3990
  if (!hit) return [];
3841
3991
  const enlarging = !hit.value.startsWith("-") && parseFloat(hit.value) > (hit.value.includes("%") ? 100 : 1);
@@ -4883,9 +5033,12 @@ ${right.raw}`)
4883
5033
  const usesGsap = allScriptTexts.some(
4884
5034
  (t) => /gsap\.(to|from|fromTo|timeline|set|registerPlugin)\b/.test(t)
4885
5035
  );
4886
- 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);
4887
5038
  const hasInlineGsap = allScriptTexts.some(
4888
- (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)
4889
5042
  );
4890
5043
  if (!usesGsap || hasGsapScript || hasInlineGsap || canInheritGsapFromHost) return [];
4891
5044
  return [
package/dist/index.js CHANGED
@@ -2715,6 +2715,7 @@ function containingCallOffset(script, tokenOffset) {
2715
2715
  }
2716
2716
 
2717
2717
  // src/rules/composition.ts
2718
+ import selectorParser2 from "postcss-selector-parser";
2718
2719
  import {
2719
2720
  COMPOSITION_VARIABLE_TYPES,
2720
2721
  isCompositionVariable,
@@ -2882,12 +2883,154 @@ function firstRescalingZoom(css) {
2882
2883
  }
2883
2884
  return null;
2884
2885
  }
2885
- function targetsCanvasRoot(selector, rootId, rootClasses) {
2886
+ function canvasScopeCandidate(selector, root) {
2886
2887
  const leftmost = selector.trim().split(/[\s>+~]+/)[0] ?? "";
2887
- const bare = leftmost.toLowerCase();
2888
- if (bare === "html" || bare === "body" || bare === ":root" || bare === "*") return true;
2889
- if (rootId && leftmostCompoundId(selector) === rootId) return true;
2890
- 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
+ };
2891
3034
  }
2892
3035
  function inlineCanvasZoomHits(rootTag, tags) {
2893
3036
  const hits = [];
@@ -2905,19 +3048,23 @@ function inlineCanvasZoomHits(rootTag, tags) {
2905
3048
  }
2906
3049
  return hits;
2907
3050
  }
2908
- function firstCanvasSelector(header, rootId, rootClasses) {
2909
- for (const selector of header.split(",")) {
2910
- const trimmed = selector.trim();
2911
- 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;
2912
3059
  }
2913
3060
  return null;
2914
3061
  }
2915
- function canvasZoomInRule(header, body, rootId, rootClasses) {
3062
+ function canvasZoomInRule(header, body, matchesCanvas) {
2916
3063
  const trimmedHeader = header.trim();
2917
3064
  if (!trimmedHeader || trimmedHeader.startsWith("@")) return null;
2918
3065
  const value = firstRescalingZoom(body);
2919
3066
  if (!value) return null;
2920
- const selector = firstCanvasSelector(trimmedHeader, rootId, rootClasses);
3067
+ const selector = firstCanvasSelector(trimmedHeader, matchesCanvas);
2921
3068
  if (!selector) return null;
2922
3069
  return {
2923
3070
  where: `\`${selector}\``,
@@ -2925,23 +3072,28 @@ function canvasZoomInRule(header, body, rootId, rootClasses) {
2925
3072
  snippet: truncateSnippet(`${selector} { zoom: ${value} }`)
2926
3073
  };
2927
3074
  }
2928
- function stylesheetCanvasZoomHits(styles, rootId, rootClasses) {
3075
+ function stylesheetCanvasZoomHits(styles, matchesCanvas) {
2929
3076
  const hits = [];
2930
3077
  for (const style of styles) {
2931
3078
  const ruleWithBody = /([^{}]+)\{([^{}]*)\}/g;
2932
3079
  const content = stripCssComments(style.content);
3080
+ const allowNewHits = style.attrs.trim() === "" && !content.includes("@");
2933
3081
  let match;
2934
3082
  while ((match = ruleWithBody.exec(content)) !== null) {
2935
- 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
+ );
2936
3088
  if (hit) hits.push(hit);
2937
3089
  }
2938
3090
  }
2939
3091
  return hits;
2940
3092
  }
2941
- function canvasZoomHits(rootTag, tags, styles, rootId, rootClasses) {
3093
+ function canvasZoomHits(rootTag, tags, styles) {
2942
3094
  return [
2943
3095
  ...inlineCanvasZoomHits(rootTag, tags),
2944
- ...stylesheetCanvasZoomHits(styles, rootId, rootClasses)
3096
+ ...stylesheetCanvasZoomHits(styles, canvasSelectorMatcher(rootTag, tags))
2945
3097
  ];
2946
3098
  }
2947
3099
  function collectDeclaredVariableIds(htmlTagRaw) {
@@ -3839,9 +3991,7 @@ ${allInlineStyles}`.replace(/\/\*[\s\S]*?\*\//g, "");
3839
3991
  // fired on every `zoom` would be flagging correct authoring.
3840
3992
  ({ rootTag, tags, styles }) => {
3841
3993
  if (!rootTag) return [];
3842
- const rootId = readAttr(rootTag.raw, "id");
3843
- const rootClasses = (readAttr(rootTag.raw, "class") || "").split(/\s+/).filter(Boolean);
3844
- const hits = canvasZoomHits(rootTag, tags, styles, rootId, rootClasses);
3994
+ const hits = canvasZoomHits(rootTag, tags, styles);
3845
3995
  const hit = hits[0];
3846
3996
  if (!hit) return [];
3847
3997
  const enlarging = !hit.value.startsWith("-") && parseFloat(hit.value) > (hit.value.includes("%") ? 100 : 1);
@@ -4889,9 +5039,12 @@ ${right.raw}`)
4889
5039
  const usesGsap = allScriptTexts.some(
4890
5040
  (t) => /gsap\.(to|from|fromTo|timeline|set|registerPlugin)\b/.test(t)
4891
5041
  );
4892
- 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);
4893
5044
  const hasInlineGsap = allScriptTexts.some(
4894
- (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)
4895
5048
  );
4896
5049
  if (!usesGsap || hasGsapScript || hasInlineGsap || canInheritGsapFromHost) return [];
4897
5050
  return [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/lint",
3
- "version": "0.8.194",
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.194"
38
+ "@hyperframes/parsers": "0.8.195"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^25.0.10",