@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.
- package/dist/browser.js +270 -113
- package/dist/index.js +270 -113
- 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
|
|
2583
|
-
import { simple as
|
|
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 =
|
|
2681
|
+
const ast = parse3(script.content, {
|
|
2591
2682
|
ecmaVersion: "latest",
|
|
2592
2683
|
sourceType: "script",
|
|
2593
2684
|
allowReturnOutsideFunction: true
|
|
2594
2685
|
});
|
|
2595
|
-
|
|
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
|
|
2880
|
+
function canvasScopeCandidate(selector, root) {
|
|
2789
2881
|
const leftmost = selector.trim().split(/[\s>+~]+/)[0] ?? "";
|
|
2790
|
-
|
|
2791
|
-
if (
|
|
2792
|
-
|
|
2793
|
-
|
|
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,
|
|
2812
|
-
|
|
2813
|
-
const
|
|
2814
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
|
3087
|
+
function canvasZoomHits(rootTag, tags, styles) {
|
|
2845
3088
|
return [
|
|
2846
3089
|
...inlineCanvasZoomHits(rootTag, tags),
|
|
2847
|
-
...stylesheetCanvasZoomHits(styles,
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
2589
|
-
import { simple as
|
|
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 =
|
|
2687
|
+
const ast = parse3(script.content, {
|
|
2597
2688
|
ecmaVersion: "latest",
|
|
2598
2689
|
sourceType: "script",
|
|
2599
2690
|
allowReturnOutsideFunction: true
|
|
2600
2691
|
});
|
|
2601
|
-
|
|
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
|
|
2886
|
+
function canvasScopeCandidate(selector, root) {
|
|
2795
2887
|
const leftmost = selector.trim().split(/[\s>+~]+/)[0] ?? "";
|
|
2796
|
-
|
|
2797
|
-
if (
|
|
2798
|
-
|
|
2799
|
-
|
|
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,
|
|
2818
|
-
|
|
2819
|
-
const
|
|
2820
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
|
3093
|
+
function canvasZoomHits(rootTag, tags, styles) {
|
|
2851
3094
|
return [
|
|
2852
3095
|
...inlineCanvasZoomHits(rootTag, tags),
|
|
2853
|
-
...stylesheetCanvasZoomHits(styles,
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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.
|
|
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.
|
|
38
|
+
"@hyperframes/parsers": "0.8.195"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.0.10",
|