@hyperframes/lint 0.8.143 → 0.8.145
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 +43 -33
- package/dist/browser.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +46 -34
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -36,10 +36,12 @@ type HyperframeLintResult = {
|
|
|
36
36
|
type HyperframeLinterOptions = {
|
|
37
37
|
filePath?: string;
|
|
38
38
|
isSubComposition?: boolean;
|
|
39
|
+
compSrcPath?: string;
|
|
39
40
|
externalStyles?: Array<{
|
|
40
41
|
href: string;
|
|
41
42
|
content: string;
|
|
42
43
|
file?: string;
|
|
44
|
+
rootRelativePath?: string;
|
|
43
45
|
}>;
|
|
44
46
|
/**
|
|
45
47
|
* Set to `true` when linting compositions destined for distributed / Lambda
|
package/dist/index.js
CHANGED
|
@@ -71,12 +71,13 @@ function parseHtmlStructure(source) {
|
|
|
71
71
|
function findHtmlTag(tags) {
|
|
72
72
|
return tags.find((tag) => tag.name === "html") ?? null;
|
|
73
73
|
}
|
|
74
|
+
var hasCompositionMarker = (tag) => Boolean(
|
|
75
|
+
readDecodedAttr(tag.raw, "data-composition-id") || readAttr(tag.raw, "data-width") || readAttr(tag.raw, "data-height")
|
|
76
|
+
);
|
|
74
77
|
function findRootTag(source, parsedTags) {
|
|
75
78
|
const tags = parsedTags ?? parseHtmlStructure(source).tags;
|
|
76
79
|
const bodyTag = tags.find((tag) => tag.name === "body");
|
|
77
|
-
if (bodyTag && (
|
|
78
|
-
return bodyTag;
|
|
79
|
-
}
|
|
80
|
+
if (bodyTag && hasCompositionMarker(bodyTag)) return bodyTag;
|
|
80
81
|
const bodyStart = bodyTag ? bodyTag.index + bodyTag.raw.length : 0;
|
|
81
82
|
const bodyEnd = bodyTag?.closeIndex ?? source.length;
|
|
82
83
|
const bodyTags = tags.filter((tag) => tag.index >= bodyStart && tag.index < bodyEnd);
|
|
@@ -84,7 +85,8 @@ function findRootTag(source, parsedTags) {
|
|
|
84
85
|
for (const tag of bodyTags) {
|
|
85
86
|
if (tag.index < skipBefore) continue;
|
|
86
87
|
if (["script", "style", "meta", "link", "title"].includes(tag.name)) continue;
|
|
87
|
-
if (tag.name === "
|
|
88
|
+
if ((tag.name === "html" || tag.name === "head") && !hasCompositionMarker(tag)) continue;
|
|
89
|
+
if (tag.name === "svg" && !hasCompositionMarker(tag)) {
|
|
88
90
|
skipBefore = tag.endIndex ?? Infinity;
|
|
89
91
|
continue;
|
|
90
92
|
}
|
|
@@ -712,7 +714,8 @@ function buildLintContext(html, options = {}) {
|
|
|
712
714
|
content: style.content,
|
|
713
715
|
raw: style.content,
|
|
714
716
|
index: -1,
|
|
715
|
-
file: style.file ?? style.href
|
|
717
|
+
file: style.file ?? style.href,
|
|
718
|
+
rootRelativePath: style.rootRelativePath
|
|
716
719
|
}))
|
|
717
720
|
];
|
|
718
721
|
const scripts = structure.scripts;
|
|
@@ -1110,7 +1113,7 @@ var coreRules = [
|
|
|
1110
1113
|
},
|
|
1111
1114
|
// root_missing_composition_id + root_missing_dimensions
|
|
1112
1115
|
// fallow-ignore-next-line complexity
|
|
1113
|
-
({ rootTag }) => {
|
|
1116
|
+
({ rootTag, options }) => {
|
|
1114
1117
|
const findings = [];
|
|
1115
1118
|
if (!rootTag || !readDecodedAttr(rootTag.raw, "data-composition-id")) {
|
|
1116
1119
|
findings.push({
|
|
@@ -1122,7 +1125,7 @@ var coreRules = [
|
|
|
1122
1125
|
snippet: truncateSnippet(rootTag?.raw || "")
|
|
1123
1126
|
});
|
|
1124
1127
|
}
|
|
1125
|
-
if (!rootTag || !readAttr(rootTag.raw, "data-width") || !readAttr(rootTag.raw, "data-height")) {
|
|
1128
|
+
if (!options.isSubComposition && (!rootTag || !readAttr(rootTag.raw, "data-width") || !readAttr(rootTag.raw, "data-height"))) {
|
|
1126
1129
|
findings.push({
|
|
1127
1130
|
code: "root_missing_dimensions",
|
|
1128
1131
|
severity: "error",
|
|
@@ -2605,6 +2608,24 @@ function extractCssUrlReferences(css) {
|
|
|
2605
2608
|
}
|
|
2606
2609
|
return out;
|
|
2607
2610
|
}
|
|
2611
|
+
function folderDepth(filePath) {
|
|
2612
|
+
if (/^(?:[a-z]:)?[\\/]/i.test(filePath)) return 0;
|
|
2613
|
+
let depth = 0;
|
|
2614
|
+
for (const segment of filePath.split(/[\\/]/).slice(0, -1)) {
|
|
2615
|
+
if (segment === "..") depth = Math.max(0, depth - 1);
|
|
2616
|
+
else if (segment && segment !== ".") depth += 1;
|
|
2617
|
+
}
|
|
2618
|
+
return depth;
|
|
2619
|
+
}
|
|
2620
|
+
function climbsAboveRoot(fileDepth, path) {
|
|
2621
|
+
let depth = fileDepth;
|
|
2622
|
+
for (const segment of path.split("/")) {
|
|
2623
|
+
if (segment === "..") depth -= 1;
|
|
2624
|
+
else if (segment && segment !== ".") depth += 1;
|
|
2625
|
+
if (depth < 0) return true;
|
|
2626
|
+
}
|
|
2627
|
+
return false;
|
|
2628
|
+
}
|
|
2608
2629
|
function extractCssSelectors(css) {
|
|
2609
2630
|
const out = [];
|
|
2610
2631
|
const noComments = css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
@@ -2918,42 +2939,31 @@ var compositionRules = [
|
|
|
2918
2939
|
}
|
|
2919
2940
|
return findings;
|
|
2920
2941
|
},
|
|
2921
|
-
// invalid_parent_traversal_in_asset_path —
|
|
2922
|
-
//
|
|
2923
|
-
//
|
|
2924
|
-
// with the project root as their base URL, so any `../`-traversing path
|
|
2925
|
-
// climbs above the project root and 404s in Studio preview. Renders
|
|
2926
|
-
// tolerate it because the server-side bundler rewrites `../foo` against
|
|
2927
|
-
// each sub-composition's source path; the runtime now mirrors that fallback
|
|
2928
|
-
// (see rewriteSubCompositionAssetPaths in runtime/compositionLoader.ts), but
|
|
2929
|
-
// the authoring-time signal is still wrong — flag it at lint time so the
|
|
2930
|
-
// baked path is plain root-relative and matches what the bundler emits.
|
|
2931
|
-
//
|
|
2932
|
-
// Mirrors the runtime fallback's surface: `[src]` / `[href]` attribute
|
|
2933
|
-
// values, `[style]` inline url(), and `<style>` block url() references.
|
|
2934
|
-
// Skips absolute URLs (http(s)://, //, data:, /-prefixed root-relative),
|
|
2935
|
-
// hash anchors, and plain relative paths (`assets/x.mp4`) — only `../`
|
|
2936
|
-
// traversal is flagged. Subsumes the older `../capture/`-specific rule.
|
|
2942
|
+
// invalid_parent_traversal_in_asset_path — a `../` path in src, href or a CSS url() that leaves
|
|
2943
|
+
// the project once resolved from the file's own folder, as the bundler, producer and runtime
|
|
2944
|
+
// resolve it. "../assets/x" from compositions/ stays inside and passes.
|
|
2937
2945
|
// fallow-ignore-next-line complexity
|
|
2938
2946
|
({ tags, styles, rawSource, options }) => {
|
|
2939
2947
|
if (isRegistrySourceFile(options.filePath) || isRegistryInstalledFile(rawSource)) return [];
|
|
2940
2948
|
const offenders = [];
|
|
2941
|
-
const
|
|
2942
|
-
|
|
2943
|
-
const trimmed = value
|
|
2944
|
-
if (
|
|
2945
|
-
|
|
2949
|
+
const fileDepth = folderDepth(options.compSrcPath ?? "index.html");
|
|
2950
|
+
const collect = (value, depth = fileDepth) => {
|
|
2951
|
+
const trimmed = value?.trim() ?? "";
|
|
2952
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|[\\/#])/i.test(trimmed)) return;
|
|
2953
|
+
const path = (trimmed.split(/[?#]/, 1)[0] ?? "").replace(/\\|%2f/gi, "/").replace(/%2e/gi, ".");
|
|
2954
|
+
if (climbsAboveRoot(depth, path)) offenders.push(trimmed);
|
|
2946
2955
|
};
|
|
2947
2956
|
for (const tag of tags) {
|
|
2948
|
-
collect(
|
|
2949
|
-
collect(
|
|
2957
|
+
collect(readDecodedAttr(tag.raw, "src"));
|
|
2958
|
+
collect(readDecodedAttr(tag.raw, "href"));
|
|
2950
2959
|
const styleAttr = readJsonAttr(tag.raw, "style");
|
|
2951
2960
|
if (styleAttr) {
|
|
2952
2961
|
for (const url of extractCssUrlReferences(styleAttr)) collect(url);
|
|
2953
2962
|
}
|
|
2954
2963
|
}
|
|
2955
2964
|
for (const style of styles) {
|
|
2956
|
-
|
|
2965
|
+
const depth = style.rootRelativePath ? folderDepth(style.rootRelativePath) : fileDepth;
|
|
2966
|
+
for (const url of extractCssUrlReferences(style.content)) collect(url, depth);
|
|
2957
2967
|
}
|
|
2958
2968
|
if (offenders.length === 0) return [];
|
|
2959
2969
|
const prefixCounts = /* @__PURE__ */ new Map();
|
|
@@ -2966,8 +2976,8 @@ var compositionRules = [
|
|
|
2966
2976
|
{
|
|
2967
2977
|
code: "invalid_parent_traversal_in_asset_path",
|
|
2968
2978
|
severity: "error",
|
|
2969
|
-
message: `Found ${offenders.length} asset path(s) traversing above the project root with "../" (${prefixSummary})
|
|
2970
|
-
fixHint:
|
|
2979
|
+
message: `Found ${offenders.length} asset path(s) traversing above the project root with "../" (${prefixSummary}), resolved from the folder of the file that holds them. Render and preview clamp them to the project root or resolve them outside it, so they load a different file than written, or none.`,
|
|
2980
|
+
fixHint: `Point the path at a file inside the project: from compositions/scene.html, "../assets/x.png" and "assets/x.png" both reach the project's assets folder.`
|
|
2971
2981
|
}
|
|
2972
2982
|
];
|
|
2973
2983
|
},
|
|
@@ -6749,7 +6759,7 @@ function collectExternalStyles(projectDir, html, compSrcPath) {
|
|
|
6749
6759
|
document,
|
|
6750
6760
|
compSrcPath
|
|
6751
6761
|
)) {
|
|
6752
|
-
styles.push({ href, content, file: join2(projectDir, rootRelativePath) });
|
|
6762
|
+
styles.push({ href, content, file: join2(projectDir, rootRelativePath), rootRelativePath });
|
|
6753
6763
|
}
|
|
6754
6764
|
return styles;
|
|
6755
6765
|
}
|
|
@@ -6801,6 +6811,7 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
|
|
|
6801
6811
|
const rootResult = await lintHyperframeHtml(rootHtml, {
|
|
6802
6812
|
...hostOptions,
|
|
6803
6813
|
filePath: indexPath,
|
|
6814
|
+
compSrcPath: rootFile,
|
|
6804
6815
|
externalStyles: collectExternalStyles(projectDir, rootHtml, rootCompSrcPath)
|
|
6805
6816
|
});
|
|
6806
6817
|
results.push({ file: rootFile, result: rootResult, contentHash: contentDigest(rootHtml) });
|
|
@@ -6834,6 +6845,7 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
|
|
|
6834
6845
|
...hostOptions,
|
|
6835
6846
|
filePath,
|
|
6836
6847
|
isSubComposition: true,
|
|
6848
|
+
compSrcPath,
|
|
6837
6849
|
externalStyles: collectExternalStyles(projectDir, html, compSrcPath)
|
|
6838
6850
|
});
|
|
6839
6851
|
results.push({
|