@hyperframes/lint 0.8.158 → 0.8.160
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 +124 -12
- package/dist/index.js +198 -31
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -1495,10 +1495,10 @@ var coreRules = [
|
|
|
1495
1495
|
}
|
|
1496
1496
|
];
|
|
1497
1497
|
for (const script of scripts) {
|
|
1498
|
-
const
|
|
1499
|
-
const executable = stripStringLiterals(
|
|
1498
|
+
const withoutComments2 = stripJsComments(script.content);
|
|
1499
|
+
const executable = stripStringLiterals(withoutComments2);
|
|
1500
1500
|
for (const { pattern, label, hint, scansStrings } of patterns) {
|
|
1501
|
-
const match = pattern.exec(scansStrings ?
|
|
1501
|
+
const match = pattern.exec(scansStrings ? withoutComments2 : executable);
|
|
1502
1502
|
if (match) {
|
|
1503
1503
|
findings.push({
|
|
1504
1504
|
...locate(script, match.index),
|
|
@@ -3878,8 +3878,8 @@ function collectStaticCssVariableDefinitions(tags, styles) {
|
|
|
3878
3878
|
];
|
|
3879
3879
|
const definitionPattern = /(?:^|[;{])\s*(--[A-Za-z0-9_-]+)\s*:/gm;
|
|
3880
3880
|
for (const source of sources) {
|
|
3881
|
-
const
|
|
3882
|
-
for (const match of
|
|
3881
|
+
const withoutComments2 = source.replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
3882
|
+
for (const match of withoutComments2.matchAll(definitionPattern)) {
|
|
3883
3883
|
if (match[1]) definitions.add(match[1]);
|
|
3884
3884
|
}
|
|
3885
3885
|
}
|
|
@@ -4073,8 +4073,8 @@ function targetedSelectorTokens(selector) {
|
|
|
4073
4073
|
const compounds = group.trim().split(/[\s>+~]+/).filter(Boolean);
|
|
4074
4074
|
const last = compounds[compounds.length - 1];
|
|
4075
4075
|
if (!last) continue;
|
|
4076
|
-
const
|
|
4077
|
-
if (
|
|
4076
|
+
const simple3 = last.match(/[#.][A-Za-z0-9_-]+/g);
|
|
4077
|
+
if (simple3) for (const token of simple3) tokens.add(token);
|
|
4078
4078
|
}
|
|
4079
4079
|
return tokens;
|
|
4080
4080
|
}
|
|
@@ -4276,8 +4276,8 @@ function firstParamMemberAccessHazard(fn) {
|
|
|
4276
4276
|
while ((match = pattern.exec(fn.body)) !== null) {
|
|
4277
4277
|
const member = match[1] ?? "";
|
|
4278
4278
|
const after = fn.body.slice(match.index + match[0].length);
|
|
4279
|
-
const
|
|
4280
|
-
if (
|
|
4279
|
+
const isCall2 = /^\s*\(/.test(after);
|
|
4280
|
+
if (isCall2 && NUMBER_METHODS.has(member)) continue;
|
|
4281
4281
|
return member;
|
|
4282
4282
|
}
|
|
4283
4283
|
return null;
|
|
@@ -6367,6 +6367,116 @@ var structureRules = [
|
|
|
6367
6367
|
}
|
|
6368
6368
|
];
|
|
6369
6369
|
|
|
6370
|
+
// src/shaderDomains.ts
|
|
6371
|
+
import { parse as parse3, parseExpressionAt } from "acorn";
|
|
6372
|
+
import { simple as simple2 } from "acorn-walk";
|
|
6373
|
+
function withoutComments(source) {
|
|
6374
|
+
return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*/g, (text) => text.replace(/[^\r\n]/g, " "));
|
|
6375
|
+
}
|
|
6376
|
+
function staticShaders(source) {
|
|
6377
|
+
const shaders = [];
|
|
6378
|
+
try {
|
|
6379
|
+
const ast = parse3(source, {
|
|
6380
|
+
ecmaVersion: "latest",
|
|
6381
|
+
sourceType: "module",
|
|
6382
|
+
allowReturnOutsideFunction: true
|
|
6383
|
+
});
|
|
6384
|
+
simple2(ast, {
|
|
6385
|
+
Literal(node) {
|
|
6386
|
+
if (typeof node.value !== "string") return;
|
|
6387
|
+
shaders.push({
|
|
6388
|
+
text: node.value,
|
|
6389
|
+
start: node.start + 1,
|
|
6390
|
+
exactOffsets: source.slice(node.start + 1, node.end - 1) === node.value
|
|
6391
|
+
});
|
|
6392
|
+
},
|
|
6393
|
+
TemplateLiteral(node) {
|
|
6394
|
+
if (node.expressions.length !== 0) return;
|
|
6395
|
+
const value = node.quasis[0]?.value;
|
|
6396
|
+
if (value?.cooked == null) return;
|
|
6397
|
+
shaders.push({
|
|
6398
|
+
text: value.cooked,
|
|
6399
|
+
start: node.start + 1,
|
|
6400
|
+
exactOffsets: source.slice(node.start + 1, node.end - 1) === value.cooked
|
|
6401
|
+
});
|
|
6402
|
+
}
|
|
6403
|
+
});
|
|
6404
|
+
} catch {
|
|
6405
|
+
}
|
|
6406
|
+
return shaders.filter(({ text }) => /\bvoid\s+main\s*\(/.test(withoutComments(text)));
|
|
6407
|
+
}
|
|
6408
|
+
function isNumber(node, value) {
|
|
6409
|
+
return node?.type === "Literal" && node.value === value;
|
|
6410
|
+
}
|
|
6411
|
+
function isCall(node, name) {
|
|
6412
|
+
return node?.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === name;
|
|
6413
|
+
}
|
|
6414
|
+
function unboundedCosine(node) {
|
|
6415
|
+
if (node.type !== "CallExpression") return false;
|
|
6416
|
+
if (isCall(node, "dot")) return node.arguments.length === 2;
|
|
6417
|
+
if (!isCall(node, "max") || node.arguments.length !== 2) return false;
|
|
6418
|
+
const a = argumentAt(node, 0);
|
|
6419
|
+
const b = argumentAt(node, 1);
|
|
6420
|
+
return zeroFlooredDot(a, b) || zeroFlooredDot(b, a);
|
|
6421
|
+
}
|
|
6422
|
+
function argumentAt(call, index) {
|
|
6423
|
+
const node = call.arguments[index];
|
|
6424
|
+
return node?.type === "SpreadElement" ? void 0 : node;
|
|
6425
|
+
}
|
|
6426
|
+
function zeroFlooredDot(a, b) {
|
|
6427
|
+
return isCall(a, "dot") && isNumber(b, 0);
|
|
6428
|
+
}
|
|
6429
|
+
function unsafeCosinePower(call) {
|
|
6430
|
+
if (call.arguments.length !== 2) return false;
|
|
6431
|
+
const [base] = call.arguments;
|
|
6432
|
+
return base?.type === "BinaryExpression" && base.operator === "-" && base.left.type !== "PrivateIdentifier" && isNumber(base.left, 1) && unboundedCosine(base.right);
|
|
6433
|
+
}
|
|
6434
|
+
function unsafePowOffsets(source) {
|
|
6435
|
+
const offsets = [];
|
|
6436
|
+
const masked = withoutComments(source);
|
|
6437
|
+
for (const match of masked.matchAll(/\bpow\s*\(/g)) {
|
|
6438
|
+
try {
|
|
6439
|
+
const expression = parseExpressionAt(masked, match.index, { ecmaVersion: "latest" });
|
|
6440
|
+
simple2(expression, {
|
|
6441
|
+
CallExpression(node) {
|
|
6442
|
+
if (node.start === match.index && unsafeCosinePower(node)) offsets.push(node.start);
|
|
6443
|
+
}
|
|
6444
|
+
});
|
|
6445
|
+
} catch {
|
|
6446
|
+
}
|
|
6447
|
+
}
|
|
6448
|
+
return offsets;
|
|
6449
|
+
}
|
|
6450
|
+
function shaderDomainOffsets(source, glsl = false) {
|
|
6451
|
+
const shaders = glsl ? [{ text: source, start: 0, exactOffsets: true }] : staticShaders(source);
|
|
6452
|
+
return shaders.flatMap(
|
|
6453
|
+
({ text, start, exactOffsets }) => unsafePowOffsets(text).map((offset) => exactOffsets ? start + offset : void 0)
|
|
6454
|
+
);
|
|
6455
|
+
}
|
|
6456
|
+
|
|
6457
|
+
// src/rules/shaders.ts
|
|
6458
|
+
function shaderDomainWarning() {
|
|
6459
|
+
return {
|
|
6460
|
+
code: "shader_unbounded_cosine_pow",
|
|
6461
|
+
severity: "warning",
|
|
6462
|
+
message: "Shader pow(1 - dot(...), ...) can receive a negative base when the cosine exceeds 1, including through GPU rounding. Undefined GLSL results can spread through glow or transmission textures as black artifacts.",
|
|
6463
|
+
fixHint: "Bound the cosine on both sides: pow(1.0 - clamp(dot(normal, view), 0.0, 1.0), exponent). A lower-only max(dot(...), 0.0) does not bound the upper end. Inspect the affected frames on the target GPU."
|
|
6464
|
+
};
|
|
6465
|
+
}
|
|
6466
|
+
var shaderRules = [
|
|
6467
|
+
({ scripts, locate }) => scripts.flatMap((script) => {
|
|
6468
|
+
const tag = `<script ${script.attrs}>`;
|
|
6469
|
+
if (readAttr(tag, "src") !== null) return [];
|
|
6470
|
+
const type = readAttr(tag, "type") ?? "";
|
|
6471
|
+
const glsl = /^(?:x-shader\/x-(?:fragment|vertex)|text\/glsl)$/i.test(type);
|
|
6472
|
+
if (type && !glsl && !/^(?:module|(?:text|application)\/javascript)$/i.test(type)) return [];
|
|
6473
|
+
return shaderDomainOffsets(script.content, glsl).map((offset) => ({
|
|
6474
|
+
...shaderDomainWarning(),
|
|
6475
|
+
...locate(script, offset)
|
|
6476
|
+
}));
|
|
6477
|
+
})
|
|
6478
|
+
];
|
|
6479
|
+
|
|
6370
6480
|
// src/hyperframeLinter.ts
|
|
6371
6481
|
var RULE_GROUPS = [
|
|
6372
6482
|
{ group: "core", rules: coreRules },
|
|
@@ -6378,7 +6488,8 @@ var RULE_GROUPS = [
|
|
|
6378
6488
|
{ group: "textures", rules: textureRules },
|
|
6379
6489
|
{ group: "fonts", rules: fontRules },
|
|
6380
6490
|
{ group: "slideshow", rules: slideshowRules },
|
|
6381
|
-
{ group: "structure", rules: structureRules }
|
|
6491
|
+
{ group: "structure", rules: structureRules },
|
|
6492
|
+
{ group: "shaders", rules: shaderRules }
|
|
6382
6493
|
];
|
|
6383
6494
|
var LINT_RULE_COUNT = RULE_GROUPS.reduce((n, g) => n + g.rules.length, 0);
|
|
6384
6495
|
var LINT_RULE_GROUP_COUNTS = Object.fromEntries(
|
|
@@ -6390,7 +6501,8 @@ function dedupeKeyFor(finding) {
|
|
|
6390
6501
|
finding.severity,
|
|
6391
6502
|
finding.selector || "",
|
|
6392
6503
|
finding.elementId || "",
|
|
6393
|
-
finding.message
|
|
6504
|
+
finding.message,
|
|
6505
|
+
...finding.code === "shader_unbounded_cosine_pow" ? [finding.line, finding.column] : []
|
|
6394
6506
|
].join("|");
|
|
6395
6507
|
}
|
|
6396
6508
|
async function runRules(ctx, filePath) {
|
|
@@ -6428,7 +6540,7 @@ async function lintHyperframeHtml(html, options = {}) {
|
|
|
6428
6540
|
const { findings: rawFindings, timings } = await runRules(ctx, options.filePath);
|
|
6429
6541
|
const locate = createSourceLocator(html);
|
|
6430
6542
|
const findings = rawFindings.map((finding) => {
|
|
6431
|
-
if (finding.line !== void 0 || finding.code === "invalid_inline_script_syntax" || finding.code === "non_deterministic_code" || [
|
|
6543
|
+
if (finding.line !== void 0 || finding.code === "shader_unbounded_cosine_pow" || finding.code === "invalid_inline_script_syntax" || finding.code === "non_deterministic_code" || [
|
|
6432
6544
|
"gsap_infinite_repeat",
|
|
6433
6545
|
"gsap_repeat_ceil_overshoot",
|
|
6434
6546
|
"gsap_repeat_floor_unclamped"
|
package/dist/index.js
CHANGED
|
@@ -1501,10 +1501,10 @@ var coreRules = [
|
|
|
1501
1501
|
}
|
|
1502
1502
|
];
|
|
1503
1503
|
for (const script of scripts) {
|
|
1504
|
-
const
|
|
1505
|
-
const executable = stripStringLiterals(
|
|
1504
|
+
const withoutComments2 = stripJsComments(script.content);
|
|
1505
|
+
const executable = stripStringLiterals(withoutComments2);
|
|
1506
1506
|
for (const { pattern, label, hint, scansStrings } of patterns) {
|
|
1507
|
-
const match = pattern.exec(scansStrings ?
|
|
1507
|
+
const match = pattern.exec(scansStrings ? withoutComments2 : executable);
|
|
1508
1508
|
if (match) {
|
|
1509
1509
|
findings.push({
|
|
1510
1510
|
...locate(script, match.index),
|
|
@@ -3884,8 +3884,8 @@ function collectStaticCssVariableDefinitions(tags, styles) {
|
|
|
3884
3884
|
];
|
|
3885
3885
|
const definitionPattern = /(?:^|[;{])\s*(--[A-Za-z0-9_-]+)\s*:/gm;
|
|
3886
3886
|
for (const source of sources) {
|
|
3887
|
-
const
|
|
3888
|
-
for (const match of
|
|
3887
|
+
const withoutComments2 = source.replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
3888
|
+
for (const match of withoutComments2.matchAll(definitionPattern)) {
|
|
3889
3889
|
if (match[1]) definitions.add(match[1]);
|
|
3890
3890
|
}
|
|
3891
3891
|
}
|
|
@@ -4079,8 +4079,8 @@ function targetedSelectorTokens(selector) {
|
|
|
4079
4079
|
const compounds = group.trim().split(/[\s>+~]+/).filter(Boolean);
|
|
4080
4080
|
const last = compounds[compounds.length - 1];
|
|
4081
4081
|
if (!last) continue;
|
|
4082
|
-
const
|
|
4083
|
-
if (
|
|
4082
|
+
const simple3 = last.match(/[#.][A-Za-z0-9_-]+/g);
|
|
4083
|
+
if (simple3) for (const token of simple3) tokens.add(token);
|
|
4084
4084
|
}
|
|
4085
4085
|
return tokens;
|
|
4086
4086
|
}
|
|
@@ -4282,8 +4282,8 @@ function firstParamMemberAccessHazard(fn) {
|
|
|
4282
4282
|
while ((match = pattern.exec(fn.body)) !== null) {
|
|
4283
4283
|
const member = match[1] ?? "";
|
|
4284
4284
|
const after = fn.body.slice(match.index + match[0].length);
|
|
4285
|
-
const
|
|
4286
|
-
if (
|
|
4285
|
+
const isCall2 = /^\s*\(/.test(after);
|
|
4286
|
+
if (isCall2 && NUMBER_METHODS.has(member)) continue;
|
|
4287
4287
|
return member;
|
|
4288
4288
|
}
|
|
4289
4289
|
return null;
|
|
@@ -6373,6 +6373,116 @@ var structureRules = [
|
|
|
6373
6373
|
}
|
|
6374
6374
|
];
|
|
6375
6375
|
|
|
6376
|
+
// src/shaderDomains.ts
|
|
6377
|
+
import { parse as parse3, parseExpressionAt } from "acorn";
|
|
6378
|
+
import { simple as simple2 } from "acorn-walk";
|
|
6379
|
+
function withoutComments(source) {
|
|
6380
|
+
return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*/g, (text) => text.replace(/[^\r\n]/g, " "));
|
|
6381
|
+
}
|
|
6382
|
+
function staticShaders(source) {
|
|
6383
|
+
const shaders = [];
|
|
6384
|
+
try {
|
|
6385
|
+
const ast = parse3(source, {
|
|
6386
|
+
ecmaVersion: "latest",
|
|
6387
|
+
sourceType: "module",
|
|
6388
|
+
allowReturnOutsideFunction: true
|
|
6389
|
+
});
|
|
6390
|
+
simple2(ast, {
|
|
6391
|
+
Literal(node) {
|
|
6392
|
+
if (typeof node.value !== "string") return;
|
|
6393
|
+
shaders.push({
|
|
6394
|
+
text: node.value,
|
|
6395
|
+
start: node.start + 1,
|
|
6396
|
+
exactOffsets: source.slice(node.start + 1, node.end - 1) === node.value
|
|
6397
|
+
});
|
|
6398
|
+
},
|
|
6399
|
+
TemplateLiteral(node) {
|
|
6400
|
+
if (node.expressions.length !== 0) return;
|
|
6401
|
+
const value = node.quasis[0]?.value;
|
|
6402
|
+
if (value?.cooked == null) return;
|
|
6403
|
+
shaders.push({
|
|
6404
|
+
text: value.cooked,
|
|
6405
|
+
start: node.start + 1,
|
|
6406
|
+
exactOffsets: source.slice(node.start + 1, node.end - 1) === value.cooked
|
|
6407
|
+
});
|
|
6408
|
+
}
|
|
6409
|
+
});
|
|
6410
|
+
} catch {
|
|
6411
|
+
}
|
|
6412
|
+
return shaders.filter(({ text }) => /\bvoid\s+main\s*\(/.test(withoutComments(text)));
|
|
6413
|
+
}
|
|
6414
|
+
function isNumber(node, value) {
|
|
6415
|
+
return node?.type === "Literal" && node.value === value;
|
|
6416
|
+
}
|
|
6417
|
+
function isCall(node, name) {
|
|
6418
|
+
return node?.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === name;
|
|
6419
|
+
}
|
|
6420
|
+
function unboundedCosine(node) {
|
|
6421
|
+
if (node.type !== "CallExpression") return false;
|
|
6422
|
+
if (isCall(node, "dot")) return node.arguments.length === 2;
|
|
6423
|
+
if (!isCall(node, "max") || node.arguments.length !== 2) return false;
|
|
6424
|
+
const a = argumentAt(node, 0);
|
|
6425
|
+
const b = argumentAt(node, 1);
|
|
6426
|
+
return zeroFlooredDot(a, b) || zeroFlooredDot(b, a);
|
|
6427
|
+
}
|
|
6428
|
+
function argumentAt(call, index) {
|
|
6429
|
+
const node = call.arguments[index];
|
|
6430
|
+
return node?.type === "SpreadElement" ? void 0 : node;
|
|
6431
|
+
}
|
|
6432
|
+
function zeroFlooredDot(a, b) {
|
|
6433
|
+
return isCall(a, "dot") && isNumber(b, 0);
|
|
6434
|
+
}
|
|
6435
|
+
function unsafeCosinePower(call) {
|
|
6436
|
+
if (call.arguments.length !== 2) return false;
|
|
6437
|
+
const [base] = call.arguments;
|
|
6438
|
+
return base?.type === "BinaryExpression" && base.operator === "-" && base.left.type !== "PrivateIdentifier" && isNumber(base.left, 1) && unboundedCosine(base.right);
|
|
6439
|
+
}
|
|
6440
|
+
function unsafePowOffsets(source) {
|
|
6441
|
+
const offsets = [];
|
|
6442
|
+
const masked = withoutComments(source);
|
|
6443
|
+
for (const match of masked.matchAll(/\bpow\s*\(/g)) {
|
|
6444
|
+
try {
|
|
6445
|
+
const expression = parseExpressionAt(masked, match.index, { ecmaVersion: "latest" });
|
|
6446
|
+
simple2(expression, {
|
|
6447
|
+
CallExpression(node) {
|
|
6448
|
+
if (node.start === match.index && unsafeCosinePower(node)) offsets.push(node.start);
|
|
6449
|
+
}
|
|
6450
|
+
});
|
|
6451
|
+
} catch {
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
return offsets;
|
|
6455
|
+
}
|
|
6456
|
+
function shaderDomainOffsets(source, glsl = false) {
|
|
6457
|
+
const shaders = glsl ? [{ text: source, start: 0, exactOffsets: true }] : staticShaders(source);
|
|
6458
|
+
return shaders.flatMap(
|
|
6459
|
+
({ text, start, exactOffsets }) => unsafePowOffsets(text).map((offset) => exactOffsets ? start + offset : void 0)
|
|
6460
|
+
);
|
|
6461
|
+
}
|
|
6462
|
+
|
|
6463
|
+
// src/rules/shaders.ts
|
|
6464
|
+
function shaderDomainWarning() {
|
|
6465
|
+
return {
|
|
6466
|
+
code: "shader_unbounded_cosine_pow",
|
|
6467
|
+
severity: "warning",
|
|
6468
|
+
message: "Shader pow(1 - dot(...), ...) can receive a negative base when the cosine exceeds 1, including through GPU rounding. Undefined GLSL results can spread through glow or transmission textures as black artifacts.",
|
|
6469
|
+
fixHint: "Bound the cosine on both sides: pow(1.0 - clamp(dot(normal, view), 0.0, 1.0), exponent). A lower-only max(dot(...), 0.0) does not bound the upper end. Inspect the affected frames on the target GPU."
|
|
6470
|
+
};
|
|
6471
|
+
}
|
|
6472
|
+
var shaderRules = [
|
|
6473
|
+
({ scripts, locate }) => scripts.flatMap((script) => {
|
|
6474
|
+
const tag = `<script ${script.attrs}>`;
|
|
6475
|
+
if (readAttr(tag, "src") !== null) return [];
|
|
6476
|
+
const type = readAttr(tag, "type") ?? "";
|
|
6477
|
+
const glsl = /^(?:x-shader\/x-(?:fragment|vertex)|text\/glsl)$/i.test(type);
|
|
6478
|
+
if (type && !glsl && !/^(?:module|(?:text|application)\/javascript)$/i.test(type)) return [];
|
|
6479
|
+
return shaderDomainOffsets(script.content, glsl).map((offset) => ({
|
|
6480
|
+
...shaderDomainWarning(),
|
|
6481
|
+
...locate(script, offset)
|
|
6482
|
+
}));
|
|
6483
|
+
})
|
|
6484
|
+
];
|
|
6485
|
+
|
|
6376
6486
|
// src/hyperframeLinter.ts
|
|
6377
6487
|
var RULE_GROUPS = [
|
|
6378
6488
|
{ group: "core", rules: coreRules },
|
|
@@ -6384,7 +6494,8 @@ var RULE_GROUPS = [
|
|
|
6384
6494
|
{ group: "textures", rules: textureRules },
|
|
6385
6495
|
{ group: "fonts", rules: fontRules },
|
|
6386
6496
|
{ group: "slideshow", rules: slideshowRules },
|
|
6387
|
-
{ group: "structure", rules: structureRules }
|
|
6497
|
+
{ group: "structure", rules: structureRules },
|
|
6498
|
+
{ group: "shaders", rules: shaderRules }
|
|
6388
6499
|
];
|
|
6389
6500
|
var LINT_RULE_COUNT = RULE_GROUPS.reduce((n, g) => n + g.rules.length, 0);
|
|
6390
6501
|
var LINT_RULE_GROUP_COUNTS = Object.fromEntries(
|
|
@@ -6396,7 +6507,8 @@ function dedupeKeyFor(finding) {
|
|
|
6396
6507
|
finding.severity,
|
|
6397
6508
|
finding.selector || "",
|
|
6398
6509
|
finding.elementId || "",
|
|
6399
|
-
finding.message
|
|
6510
|
+
finding.message,
|
|
6511
|
+
...finding.code === "shader_unbounded_cosine_pow" ? [finding.line, finding.column] : []
|
|
6400
6512
|
].join("|");
|
|
6401
6513
|
}
|
|
6402
6514
|
async function runRules(ctx, filePath) {
|
|
@@ -6434,7 +6546,7 @@ async function lintHyperframeHtml(html, options = {}) {
|
|
|
6434
6546
|
const { findings: rawFindings, timings } = await runRules(ctx, options.filePath);
|
|
6435
6547
|
const locate = createSourceLocator(html);
|
|
6436
6548
|
const findings = rawFindings.map((finding) => {
|
|
6437
|
-
if (finding.line !== void 0 || finding.code === "invalid_inline_script_syntax" || finding.code === "non_deterministic_code" || [
|
|
6549
|
+
if (finding.line !== void 0 || finding.code === "shader_unbounded_cosine_pow" || finding.code === "invalid_inline_script_syntax" || finding.code === "non_deterministic_code" || [
|
|
6438
6550
|
"gsap_infinite_repeat",
|
|
6439
6551
|
"gsap_repeat_ceil_overshoot",
|
|
6440
6552
|
"gsap_repeat_floor_unclamped"
|
|
@@ -6530,7 +6642,7 @@ function shouldBlockRender(strictErrors, strictAll, totalErrors, totalWarnings)
|
|
|
6530
6642
|
// src/project.ts
|
|
6531
6643
|
import { createHash } from "crypto";
|
|
6532
6644
|
import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
|
|
6533
|
-
import { dirname, extname, join as
|
|
6645
|
+
import { dirname as dirname2, extname, join as join3, relative as relative2, resolve } from "path";
|
|
6534
6646
|
import { rewriteAssetPath as rewriteAssetPath2 } from "@hyperframes/parsers/asset-paths";
|
|
6535
6647
|
import { checkSubCompositionUsability } from "@hyperframes/parsers/sub-composition-validity";
|
|
6536
6648
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
@@ -6539,10 +6651,10 @@ import {
|
|
|
6539
6651
|
collectSubCompositionSrcs,
|
|
6540
6652
|
isRemoteOrInlineUrl as isRemoteOrInlineUrl2,
|
|
6541
6653
|
isUnresolvedAssetPlaceholder as isUnresolvedAssetPlaceholder2,
|
|
6542
|
-
isWithinProjectRoot,
|
|
6654
|
+
isWithinProjectRoot as isWithinProjectRoot2,
|
|
6543
6655
|
maskNonScannableRanges as maskNonScannableRanges2,
|
|
6544
|
-
readProjectFile,
|
|
6545
|
-
resolveExistingLocalAsset as
|
|
6656
|
+
readProjectFile as readProjectFile2,
|
|
6657
|
+
resolveExistingLocalAsset as resolveExistingLocalAsset3,
|
|
6546
6658
|
resolveLocalAssetCandidates,
|
|
6547
6659
|
resolveProjectRelativeSrc
|
|
6548
6660
|
} from "@hyperframes/parsers/asset-resolution";
|
|
@@ -6783,6 +6895,60 @@ async function lintHevcPreviewCodec(candidates) {
|
|
|
6783
6895
|
];
|
|
6784
6896
|
}
|
|
6785
6897
|
|
|
6898
|
+
// src/projectShaders.ts
|
|
6899
|
+
import { realpathSync } from "fs";
|
|
6900
|
+
import { dirname, join as join2, relative } from "path";
|
|
6901
|
+
import {
|
|
6902
|
+
isWithinProjectRoot,
|
|
6903
|
+
readProjectFile,
|
|
6904
|
+
resolveExistingLocalAsset as resolveExistingLocalAsset2
|
|
6905
|
+
} from "@hyperframes/parsers/asset-resolution";
|
|
6906
|
+
function isAuthoredScript(path) {
|
|
6907
|
+
return /\.(?:js|mjs|cjs)$/i.test(path) && !/(?:\.min\.js$|(?:^|[/\\])(?:vendor|node_modules)[/\\])/i.test(path);
|
|
6908
|
+
}
|
|
6909
|
+
function localScript(projectDir, src, compSrcPath) {
|
|
6910
|
+
if (/^(?:[a-z][a-z\d+.-]*:|\/\/|#)/i.test(src)) return;
|
|
6911
|
+
const besideFile = src.startsWith("/") ? src : join2(dirname(compSrcPath ?? "index.html"), src);
|
|
6912
|
+
const asset = resolveExistingLocalAsset2(projectDir, besideFile);
|
|
6913
|
+
if (!asset) return;
|
|
6914
|
+
try {
|
|
6915
|
+
const file = realpathSync(asset.resolved);
|
|
6916
|
+
if (!isWithinProjectRoot(realpathSync(projectDir), file)) return;
|
|
6917
|
+
if (!isAuthoredScript(relative(projectDir, file))) return;
|
|
6918
|
+
return file;
|
|
6919
|
+
} catch {
|
|
6920
|
+
return;
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
function linkedScripts(projectDir, { html, compSrcPath }) {
|
|
6924
|
+
const files = [];
|
|
6925
|
+
for (const script of parseHtmlStructure(html).scripts) {
|
|
6926
|
+
const tag = `<script ${script.attrs}>`;
|
|
6927
|
+
const type = readDecodedAttr(tag, "type") ?? "";
|
|
6928
|
+
if (type && !/^(?:module|(?:text|application)\/javascript)$/i.test(type)) continue;
|
|
6929
|
+
const src = readDecodedAttr(tag, "src");
|
|
6930
|
+
if (!src) continue;
|
|
6931
|
+
const file = localScript(projectDir, src, compSrcPath);
|
|
6932
|
+
if (file) files.push(file);
|
|
6933
|
+
}
|
|
6934
|
+
return files;
|
|
6935
|
+
}
|
|
6936
|
+
function lintProjectShaderScripts(projectDir, sources) {
|
|
6937
|
+
const files = new Set(sources.flatMap((source) => linkedScripts(projectDir, source)));
|
|
6938
|
+
const findings = [];
|
|
6939
|
+
for (const file of files) {
|
|
6940
|
+
const read = readProjectFile(file);
|
|
6941
|
+
if (read.kind !== "file") continue;
|
|
6942
|
+
for (const offset of shaderDomainOffsets(read.text)) {
|
|
6943
|
+
findings.push({
|
|
6944
|
+
...shaderDomainWarning(),
|
|
6945
|
+
...offset === void 0 ? { file } : sourcePosition(read.text, offset, file)
|
|
6946
|
+
});
|
|
6947
|
+
}
|
|
6948
|
+
}
|
|
6949
|
+
return findings;
|
|
6950
|
+
}
|
|
6951
|
+
|
|
6786
6952
|
// src/project.ts
|
|
6787
6953
|
function parseSubCompHtml(html) {
|
|
6788
6954
|
return parseHTML3(html).document;
|
|
@@ -6814,10 +6980,10 @@ function collectLocalStylesheets(projectDir, document, compSrcPath) {
|
|
|
6814
6980
|
if (!rel.split(/\s+/).some((part) => part.toLowerCase() === "stylesheet")) continue;
|
|
6815
6981
|
const href = link.getAttribute("href") ?? "";
|
|
6816
6982
|
if (!isLocalStylesheetHref(href)) continue;
|
|
6817
|
-
const besideFile = compSrcPath ?
|
|
6818
|
-
const stylesheet =
|
|
6983
|
+
const besideFile = compSrcPath ? join3(dirname2(compSrcPath), href) : href;
|
|
6984
|
+
const stylesheet = resolveExistingLocalAsset3(projectDir, besideFile) ?? (compSrcPath && staysInsideProjectRoot(href) ? resolveExistingLocalAsset3(projectDir, href) : null);
|
|
6819
6985
|
if (!stylesheet) continue;
|
|
6820
|
-
const read =
|
|
6986
|
+
const read = readProjectFile2(stylesheet.resolved);
|
|
6821
6987
|
if (read.kind !== "file") continue;
|
|
6822
6988
|
styles.push({ href, content: read.text, rootRelativePath: stylesheet.rootRelativePath });
|
|
6823
6989
|
}
|
|
@@ -6831,7 +6997,7 @@ function collectExternalStyles(projectDir, html, compSrcPath) {
|
|
|
6831
6997
|
document,
|
|
6832
6998
|
compSrcPath
|
|
6833
6999
|
)) {
|
|
6834
|
-
styles.push({ href, content, file:
|
|
7000
|
+
styles.push({ href, content, file: join3(projectDir, rootRelativePath), rootRelativePath });
|
|
6835
7001
|
}
|
|
6836
7002
|
return styles;
|
|
6837
7003
|
}
|
|
@@ -6858,22 +7024,22 @@ function collectCssSources(projectDir, html, compSrcPath) {
|
|
|
6858
7024
|
function resolveCssAssetCandidates(projectDir, url, htmlCompSrcPath, cssRootRelativePath) {
|
|
6859
7025
|
if (url.startsWith("/")) return resolveLocalAssetCandidates(projectDir, url);
|
|
6860
7026
|
if (cssRootRelativePath) {
|
|
6861
|
-
return resolveLocalAssetCandidates(projectDir,
|
|
7027
|
+
return resolveLocalAssetCandidates(projectDir, join3(dirname2(cssRootRelativePath), url));
|
|
6862
7028
|
}
|
|
6863
7029
|
if (htmlCompSrcPath) {
|
|
6864
7030
|
return resolveLocalAssetCandidates(
|
|
6865
7031
|
projectDir,
|
|
6866
|
-
rewriteAssetPath2(htmlCompSrcPath, url, (path) => existsSync2(
|
|
7032
|
+
rewriteAssetPath2(htmlCompSrcPath, url, (path) => existsSync2(join3(projectDir, path)))
|
|
6867
7033
|
);
|
|
6868
7034
|
}
|
|
6869
7035
|
return resolveLocalAssetCandidates(projectDir, url);
|
|
6870
7036
|
}
|
|
6871
7037
|
async function lintProject(projectDir, entryFile, hostOptions = {}) {
|
|
6872
7038
|
const indexPath = entryFile ? resolve(entryFile) : resolve(projectDir, "index.html");
|
|
6873
|
-
if (entryFile && !
|
|
7039
|
+
if (entryFile && !isWithinProjectRoot2(projectDir, indexPath)) {
|
|
6874
7040
|
throw new Error(`Explicit lint entry is outside the project directory: ${entryFile}`);
|
|
6875
7041
|
}
|
|
6876
|
-
const rootFile =
|
|
7042
|
+
const rootFile = relative2(resolve(projectDir), indexPath).replace(/\\/g, "/") || "index.html";
|
|
6877
7043
|
const rootCompSrcPath = rootFile === "index.html" ? void 0 : rootFile;
|
|
6878
7044
|
const results = [];
|
|
6879
7045
|
let totalErrors = 0;
|
|
@@ -6899,7 +7065,7 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
|
|
|
6899
7065
|
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
6900
7066
|
if (entry.isDirectory()) {
|
|
6901
7067
|
if (!rel && entry.name === "components") continue;
|
|
6902
|
-
out.push(...collectHtmlFiles(
|
|
7068
|
+
out.push(...collectHtmlFiles(join3(dir, entry.name), relPath));
|
|
6903
7069
|
} else if (entry.isFile() && entry.name.endsWith(".html") && !entry.name.startsWith("._")) {
|
|
6904
7070
|
out.push(relPath);
|
|
6905
7071
|
}
|
|
@@ -6908,7 +7074,7 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
|
|
|
6908
7074
|
};
|
|
6909
7075
|
const files = collectHtmlFiles(compositionsDir, "").sort();
|
|
6910
7076
|
for (const file of files) {
|
|
6911
|
-
const filePath =
|
|
7077
|
+
const filePath = join3(compositionsDir, file);
|
|
6912
7078
|
const html = readFileSync(filePath, "utf-8");
|
|
6913
7079
|
const compSrcPath = `compositions/${file}`;
|
|
6914
7080
|
allHtmlSources.push({ html, compSrcPath });
|
|
@@ -6931,6 +7097,7 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
|
|
|
6931
7097
|
}
|
|
6932
7098
|
}
|
|
6933
7099
|
const projectFindings = [
|
|
7100
|
+
...lintProjectShaderScripts(projectDir, allHtmlSources),
|
|
6934
7101
|
...lintProjectAudioFiles(projectDir, allHtmlSources),
|
|
6935
7102
|
...lintAudioSrcNotFound(projectDir, allHtmlSources),
|
|
6936
7103
|
...lintMissingLocalAsset(projectDir, allHtmlSources),
|
|
@@ -7026,7 +7193,7 @@ function lintAudioSrcNotFound(projectDir, htmlSources) {
|
|
|
7026
7193
|
const src = mediaSrcOf(match);
|
|
7027
7194
|
if (/^(https?:|data:|blob:)/i.test(src)) continue;
|
|
7028
7195
|
if (isUnresolvedAssetPlaceholder2(src)) continue;
|
|
7029
|
-
const rootRelative = compSrcPath ? rewriteAssetPath2(compSrcPath, src, (path) => existsSync2(
|
|
7196
|
+
const rootRelative = compSrcPath ? rewriteAssetPath2(compSrcPath, src, (path) => existsSync2(join3(projectDir, path))) : src;
|
|
7030
7197
|
if (!existsSync2(resolveProjectRelativeSrc(rootRelative, projectDir))) {
|
|
7031
7198
|
const file = sourceFile(projectDir, compSrcPath);
|
|
7032
7199
|
missingByFile.set(file, (missingByFile.get(file) ?? /* @__PURE__ */ new Set()).add(src));
|
|
@@ -7060,8 +7227,8 @@ function lintMissingLocalAsset(projectDir, htmlSources) {
|
|
|
7060
7227
|
const src = cleanAssetUrl2(rawSrc);
|
|
7061
7228
|
if (!src) continue;
|
|
7062
7229
|
if (isRemoteOrInlineUrl2(src)) continue;
|
|
7063
|
-
const rootRelative = compSrcPath ? rewriteAssetPath2(compSrcPath, src, (path) => existsSync2(
|
|
7064
|
-
const resolvedAsset =
|
|
7230
|
+
const rootRelative = compSrcPath ? rewriteAssetPath2(compSrcPath, src, (path) => existsSync2(join3(projectDir, path))) : src;
|
|
7231
|
+
const resolvedAsset = resolveExistingLocalAsset3(projectDir, rootRelative);
|
|
7065
7232
|
if (resolvedAsset) continue;
|
|
7066
7233
|
const resolvedKey = resolve(projectDir, rootRelative);
|
|
7067
7234
|
const file = sourceFile(projectDir, compSrcPath);
|
|
@@ -7129,7 +7296,7 @@ function lintMultipleRootCompositions(projectDir) {
|
|
|
7129
7296
|
const rootCompositions = [];
|
|
7130
7297
|
for (const file of rootHtmlFiles) {
|
|
7131
7298
|
if (file === "caption-skin.html") continue;
|
|
7132
|
-
const content = readFileSync(
|
|
7299
|
+
const content = readFileSync(join3(projectDir, file), "utf-8");
|
|
7133
7300
|
if (/data-composition-id/i.test(content)) {
|
|
7134
7301
|
rootCompositions.push(file);
|
|
7135
7302
|
}
|
|
@@ -7195,7 +7362,7 @@ function lintMissingOrEmptySubComposition(projectDir, rootHtml) {
|
|
|
7195
7362
|
const filePath = resolve(projectDir, srcPath);
|
|
7196
7363
|
if (visited.has(filePath)) continue;
|
|
7197
7364
|
visited.add(filePath);
|
|
7198
|
-
const read =
|
|
7365
|
+
const read = readProjectFile2(filePath);
|
|
7199
7366
|
if (read.kind === "missing") {
|
|
7200
7367
|
if (!checked.has(srcPath)) {
|
|
7201
7368
|
checked.set(srcPath, { srcPath, problem: "the file does not exist" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/lint",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.160",
|
|
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.160"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.0.10",
|