@hyperframes/lint 0.8.48 → 0.8.50
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.d.ts +2 -0
- package/dist/browser.js +155 -1
- package/dist/browser.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +158 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/browser.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ type HyperframeLinterOptions = {
|
|
|
45
45
|
* render time — the font will silently fall back to whatever the OS provides.
|
|
46
46
|
*/
|
|
47
47
|
distributed?: boolean;
|
|
48
|
+
/** Who is running the lint: Studio raises the structure rules to errors, the CLI keeps them warnings. */
|
|
49
|
+
host?: "studio" | "cli";
|
|
48
50
|
};
|
|
49
51
|
|
|
50
52
|
declare function lintHyperframeHtml(html: string, options?: HyperframeLinterOptions): Promise<HyperframeLintResult>;
|
package/dist/browser.js
CHANGED
|
@@ -5319,6 +5319,159 @@ var slideshowRules = [
|
|
|
5319
5319
|
}
|
|
5320
5320
|
];
|
|
5321
5321
|
|
|
5322
|
+
// src/rules/structure.ts
|
|
5323
|
+
import {
|
|
5324
|
+
isSubCompositionHost,
|
|
5325
|
+
topLevelElements,
|
|
5326
|
+
trackKindOf
|
|
5327
|
+
} from "@hyperframes/parsers/top-level-elements";
|
|
5328
|
+
var INLINE_TEXT_TAGS = /* @__PURE__ */ new Set([
|
|
5329
|
+
"br",
|
|
5330
|
+
"wbr",
|
|
5331
|
+
"b",
|
|
5332
|
+
"i",
|
|
5333
|
+
"u",
|
|
5334
|
+
"s",
|
|
5335
|
+
"em",
|
|
5336
|
+
"strong",
|
|
5337
|
+
"small",
|
|
5338
|
+
"sub",
|
|
5339
|
+
"sup",
|
|
5340
|
+
"mark",
|
|
5341
|
+
"span",
|
|
5342
|
+
"a",
|
|
5343
|
+
"code",
|
|
5344
|
+
"abbr"
|
|
5345
|
+
]);
|
|
5346
|
+
var OPAQUE_TAGS = /* @__PURE__ */ new Set([
|
|
5347
|
+
"svg",
|
|
5348
|
+
"canvas",
|
|
5349
|
+
"picture",
|
|
5350
|
+
"video",
|
|
5351
|
+
"audio",
|
|
5352
|
+
"select",
|
|
5353
|
+
"textarea",
|
|
5354
|
+
"iframe",
|
|
5355
|
+
"object"
|
|
5356
|
+
]);
|
|
5357
|
+
var NON_LAYOUT_TAGS = /* @__PURE__ */ new Set(["style", "script", "template", "noscript"]);
|
|
5358
|
+
var MEDIA_TAGS = /* @__PURE__ */ new Set(["img", "video", "audio"]);
|
|
5359
|
+
var NODE_ATTRS = [
|
|
5360
|
+
"id",
|
|
5361
|
+
"class",
|
|
5362
|
+
"data-start",
|
|
5363
|
+
"data-duration",
|
|
5364
|
+
"data-end",
|
|
5365
|
+
"data-track",
|
|
5366
|
+
"data-track-index",
|
|
5367
|
+
"data-track-kind",
|
|
5368
|
+
"data-composition-id",
|
|
5369
|
+
"data-composition-src"
|
|
5370
|
+
];
|
|
5371
|
+
function toNode(open) {
|
|
5372
|
+
const attrs = {};
|
|
5373
|
+
for (const name of NODE_ATTRS) attrs[name] = readDecodedAttr(open.raw, name) ?? void 0;
|
|
5374
|
+
return { tag: open.name, attrs, children: [], open };
|
|
5375
|
+
}
|
|
5376
|
+
function enclosing(stack, root, index) {
|
|
5377
|
+
let top = stack.at(-1);
|
|
5378
|
+
while (top && stack.length > 1 && index >= top.end) {
|
|
5379
|
+
stack.pop();
|
|
5380
|
+
top = stack.at(-1);
|
|
5381
|
+
}
|
|
5382
|
+
return top?.node ?? root;
|
|
5383
|
+
}
|
|
5384
|
+
function buildTree(ctx) {
|
|
5385
|
+
const { rootTag } = ctx;
|
|
5386
|
+
const rootEnd = rootTag?.closeIndex;
|
|
5387
|
+
if (!rootTag || rootEnd == null) return null;
|
|
5388
|
+
const root = toNode(rootTag);
|
|
5389
|
+
const stack = [{ node: root, end: rootEnd }];
|
|
5390
|
+
for (const tag of ctx.tags.filter((t) => t.index > rootTag.index && t.index < rootEnd)) {
|
|
5391
|
+
const node = toNode(tag);
|
|
5392
|
+
enclosing(stack, root, tag.index).children.push(node);
|
|
5393
|
+
if (tag.closeIndex != null) stack.push({ node, end: tag.closeIndex });
|
|
5394
|
+
}
|
|
5395
|
+
return root;
|
|
5396
|
+
}
|
|
5397
|
+
function hasNestedStructure(node) {
|
|
5398
|
+
if (OPAQUE_TAGS.has(node.tag)) return null;
|
|
5399
|
+
for (const child of node.children) {
|
|
5400
|
+
if (NON_LAYOUT_TAGS.has(child.tag)) continue;
|
|
5401
|
+
if (!INLINE_TEXT_TAGS.has(child.tag)) return child;
|
|
5402
|
+
const deeper = hasNestedStructure(child);
|
|
5403
|
+
if (deeper) return deeper;
|
|
5404
|
+
}
|
|
5405
|
+
return null;
|
|
5406
|
+
}
|
|
5407
|
+
var describe = (node) => `<${node.tag}${node.attrs.id ? ` id="${node.attrs.id}"` : ""}>`;
|
|
5408
|
+
function nestedStructureFindings(rows, severity) {
|
|
5409
|
+
return rows.flatMap((row) => {
|
|
5410
|
+
const nested = isSubCompositionHost(row) ? null : hasNestedStructure(row);
|
|
5411
|
+
if (!nested) return [];
|
|
5412
|
+
return [
|
|
5413
|
+
{
|
|
5414
|
+
code: "nested_structure_needs_subcomposition",
|
|
5415
|
+
severity,
|
|
5416
|
+
message: `${describe(row)} is a timeline element that contains nested ${describe(nested)}. The timeline shows one row per top-level element, and the root composition is built only from sub-compositions.`,
|
|
5417
|
+
elementId: row.attrs.id,
|
|
5418
|
+
fixHint: `Move ${describe(row)} and its contents into a sub-composition file and mount it with data-composition-src.`
|
|
5419
|
+
}
|
|
5420
|
+
];
|
|
5421
|
+
});
|
|
5422
|
+
}
|
|
5423
|
+
function missingDurationFindings(rows, severity) {
|
|
5424
|
+
const intendedClip = (row) => !MEDIA_TAGS.has(row.tag) || row.attrs["data-start"] !== void 0 || row.attrs["data-track-index"] !== void 0;
|
|
5425
|
+
return rows.filter(
|
|
5426
|
+
(row) => row.attrs["data-duration"] === void 0 && row.attrs["data-end"] === void 0 && !isSubCompositionHost(row) && intendedClip(row)
|
|
5427
|
+
).map((row) => {
|
|
5428
|
+
const isMedia = MEDIA_TAGS.has(row.tag);
|
|
5429
|
+
return {
|
|
5430
|
+
code: isMedia ? "media_missing_duration" : "timeline_element_missing_timing",
|
|
5431
|
+
severity,
|
|
5432
|
+
message: isMedia ? `${describe(row)} is media on the timeline without data-duration, so its clip has no length.` : `${describe(row)} is a timeline element without data-duration, so the timeline cannot draw where it ends.`,
|
|
5433
|
+
elementId: row.attrs.id,
|
|
5434
|
+
fixHint: `Add data-duration (in seconds) to ${describe(row)}.`
|
|
5435
|
+
};
|
|
5436
|
+
});
|
|
5437
|
+
}
|
|
5438
|
+
function captionFindings(rows, severity) {
|
|
5439
|
+
const captionRows = rows.filter((row) => trackKindOf(row).kind === "captions");
|
|
5440
|
+
const findings = captionRows.filter((row) => trackKindOf(row).source === "legacy-captions").map((row) => ({
|
|
5441
|
+
code: "caption_track_kind_missing",
|
|
5442
|
+
severity,
|
|
5443
|
+
message: `${describe(row)} looks like captions but is not marked as such, so the timeline cannot group it on the caption track.`,
|
|
5444
|
+
elementId: row.attrs.id,
|
|
5445
|
+
fixHint: `Add data-track-kind="captions" to ${describe(row)}.`
|
|
5446
|
+
}));
|
|
5447
|
+
const lanes = new Set(
|
|
5448
|
+
captionRows.flatMap((row) => row.attrs["data-track-index"] ?? row.attrs["data-track"] ?? [])
|
|
5449
|
+
);
|
|
5450
|
+
if (lanes.size > 1) {
|
|
5451
|
+
findings.push({
|
|
5452
|
+
code: "multiple_caption_tracks",
|
|
5453
|
+
severity,
|
|
5454
|
+
message: `Captions are spread across ${lanes.size} timeline tracks. Keep all captions on one track.`,
|
|
5455
|
+
fixHint: "Give every caption element the same data-track-index."
|
|
5456
|
+
});
|
|
5457
|
+
}
|
|
5458
|
+
return findings;
|
|
5459
|
+
}
|
|
5460
|
+
var structureRules = [
|
|
5461
|
+
(ctx) => {
|
|
5462
|
+
if (ctx.options.isSubComposition) return [];
|
|
5463
|
+
const root = buildTree(ctx);
|
|
5464
|
+
if (!root) return [];
|
|
5465
|
+
const severity = ctx.options.host === "studio" ? "error" : "warning";
|
|
5466
|
+
const rows = topLevelElements(root);
|
|
5467
|
+
return [
|
|
5468
|
+
...nestedStructureFindings(rows, severity),
|
|
5469
|
+
...missingDurationFindings(rows, severity),
|
|
5470
|
+
...captionFindings(rows, severity)
|
|
5471
|
+
];
|
|
5472
|
+
}
|
|
5473
|
+
];
|
|
5474
|
+
|
|
5322
5475
|
// src/hyperframeLinter.ts
|
|
5323
5476
|
var RULE_GROUPS = [
|
|
5324
5477
|
{ group: "core", rules: coreRules },
|
|
@@ -5329,7 +5482,8 @@ var RULE_GROUPS = [
|
|
|
5329
5482
|
{ group: "adapters", rules: adapterRules },
|
|
5330
5483
|
{ group: "textures", rules: textureRules },
|
|
5331
5484
|
{ group: "fonts", rules: fontRules },
|
|
5332
|
-
{ group: "slideshow", rules: slideshowRules }
|
|
5485
|
+
{ group: "slideshow", rules: slideshowRules },
|
|
5486
|
+
{ group: "structure", rules: structureRules }
|
|
5333
5487
|
];
|
|
5334
5488
|
var LINT_RULE_COUNT = RULE_GROUPS.reduce((n, g) => n + g.rules.length, 0);
|
|
5335
5489
|
var LINT_RULE_GROUP_COUNTS = Object.fromEntries(
|