@hyperframes/lint 0.7.51 → 0.7.53
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 +106 -87
- package/dist/browser.js.map +1 -1
- package/dist/index.js +148 -128
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// src/utils.ts
|
|
2
|
-
|
|
3
|
-
var STYLE_BLOCK_PATTERN = /<style\b([^>]*)>([\s\S]*?)<\/style>/gi;
|
|
4
|
-
var SCRIPT_BLOCK_PATTERN = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
2
|
+
import { Parser } from "htmlparser2";
|
|
5
3
|
var COMPOSITION_ID_IN_CSS_PATTERN = /\[data-composition-id=["']([^"']+)["']\]/g;
|
|
6
4
|
var TIMELINE_REGISTRY_INIT_PATTERN = /window\.__timelines\s*=\s*window\.__timelines\s*\|\|\s*\{\}|window\.__timelines\s*=\s*\{\}|window\.__timelines\s*\?\?=\s*\{\}/i;
|
|
7
5
|
var TIMELINE_REGISTRY_OBJECT_LITERAL_PATTERN = /window\.__timelines\s*=\s*\{\s*(?:["'][^"']+["']|[A-Za-z_$][\w$]*)\s*:/i;
|
|
@@ -11,71 +9,69 @@ var INVALID_SCRIPT_CLOSE_PATTERN = /<script[^>]*>[\s\S]*?<\s*\/\s*script(?!>)/i;
|
|
|
11
9
|
var TIMELINE_REGISTRY_KEY_PATTERN = /window\.__timelines(?:\[\s*["']([^"']+)["']\s*\]|\.\s*([A-Za-z_$][\w$]*))\s*=/g;
|
|
12
10
|
var TIMELINE_REGISTRY_OBJECT_BODY_PATTERN = /window\.__timelines\s*=\s*\{([\s\S]*?)\}/i;
|
|
13
11
|
var TIMELINE_REGISTRY_OBJECT_ENTRY_PATTERN = /(?:["']([^"']+)["']|([A-Za-z_$][\w$]*))\s*:\s*[A-Za-z_$][\w$]*/g;
|
|
14
|
-
function
|
|
12
|
+
function parseHtmlStructure(source) {
|
|
15
13
|
const tags = [];
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
const blocks = { script: [], style: [] };
|
|
15
|
+
const openTagsByName = /* @__PURE__ */ new Map();
|
|
16
|
+
const openBlocks = [];
|
|
17
|
+
const parser = new Parser(
|
|
18
|
+
{
|
|
19
|
+
onopentag(name) {
|
|
20
|
+
const index = parser.startIndex;
|
|
21
|
+
const raw = source.slice(index, parser.endIndex + 1);
|
|
22
|
+
const attrs = raw.slice(name.length + 1, -1).replace(/\s*\/$/, "");
|
|
23
|
+
const tag = { raw, name, attrs, index };
|
|
24
|
+
tags.push(tag);
|
|
25
|
+
const sameNameStack = openTagsByName.get(name) ?? [];
|
|
26
|
+
sameNameStack.push(tag);
|
|
27
|
+
openTagsByName.set(name, sameNameStack);
|
|
28
|
+
if (name === "script" || name === "style") {
|
|
29
|
+
openBlocks.push({ name, attrs, contentStart: parser.endIndex + 1, index });
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
onclosetag(name) {
|
|
33
|
+
const tag = openTagsByName.get(name)?.pop();
|
|
34
|
+
if (tag) {
|
|
35
|
+
tag.closeIndex = parser.startIndex;
|
|
36
|
+
tag.endIndex = parser.endIndex + 1;
|
|
37
|
+
}
|
|
38
|
+
if (name !== "script" && name !== "style") return;
|
|
39
|
+
const block = openBlocks.pop();
|
|
40
|
+
if (!block || block.name !== name) return;
|
|
41
|
+
blocks[name].push({
|
|
42
|
+
attrs: block.attrs,
|
|
43
|
+
content: source.slice(block.contentStart, parser.startIndex),
|
|
44
|
+
raw: source.slice(block.index, parser.endIndex + 1),
|
|
45
|
+
index: block.index
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{ decodeEntities: false, lowerCaseAttributeNames: false, lowerCaseTags: true }
|
|
50
|
+
);
|
|
51
|
+
parser.end(source);
|
|
52
|
+
return { tags, scripts: blocks.script, styles: blocks.style };
|
|
29
53
|
}
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
let match;
|
|
33
|
-
const p = new RegExp(pattern.source, pattern.flags);
|
|
34
|
-
while ((match = p.exec(source)) !== null) {
|
|
35
|
-
blocks.push({
|
|
36
|
-
attrs: match[1] || "",
|
|
37
|
-
content: match[2] || "",
|
|
38
|
-
raw: match[0],
|
|
39
|
-
index: match.index
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
return blocks;
|
|
54
|
+
function findHtmlTag(tags) {
|
|
55
|
+
return tags.find((tag) => tag.name === "html") ?? null;
|
|
43
56
|
}
|
|
44
|
-
function
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
name: "html",
|
|
50
|
-
attrs: match[1] ?? "",
|
|
51
|
-
index: match.index
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function findRootTag(source) {
|
|
55
|
-
const bodyOpenMatch = /<body\b([^>]*)>/i.exec(source);
|
|
56
|
-
const bodyCloseMatch = /<\/body>/i.exec(source);
|
|
57
|
-
if (bodyOpenMatch && (readAttr(bodyOpenMatch[0], "data-composition-id") || readAttr(bodyOpenMatch[0], "data-width") || readAttr(bodyOpenMatch[0], "data-height"))) {
|
|
58
|
-
return {
|
|
59
|
-
raw: bodyOpenMatch[0],
|
|
60
|
-
name: "body",
|
|
61
|
-
attrs: bodyOpenMatch[1] ?? "",
|
|
62
|
-
index: bodyOpenMatch.index
|
|
63
|
-
};
|
|
57
|
+
function findRootTag(source, parsedTags) {
|
|
58
|
+
const tags = parsedTags ?? parseHtmlStructure(source).tags;
|
|
59
|
+
const bodyTag = tags.find((tag) => tag.name === "body");
|
|
60
|
+
if (bodyTag && (readAttr(bodyTag.raw, "data-composition-id") || readAttr(bodyTag.raw, "data-width") || readAttr(bodyTag.raw, "data-height"))) {
|
|
61
|
+
return bodyTag;
|
|
64
62
|
}
|
|
65
|
-
const bodyStart =
|
|
66
|
-
const bodyEnd =
|
|
67
|
-
const
|
|
68
|
-
const bodyTags = extractOpenTags(bodyContent);
|
|
63
|
+
const bodyStart = bodyTag ? bodyTag.index + bodyTag.raw.length : 0;
|
|
64
|
+
const bodyEnd = bodyTag?.closeIndex ?? source.length;
|
|
65
|
+
const bodyTags = tags.filter((tag) => tag.index >= bodyStart && tag.index < bodyEnd);
|
|
69
66
|
let skipBefore = -1;
|
|
70
67
|
for (const tag of bodyTags) {
|
|
71
68
|
if (tag.index < skipBefore) continue;
|
|
72
69
|
if (["script", "style", "meta", "link", "title"].includes(tag.name)) continue;
|
|
73
70
|
if (tag.name === "svg" && !readAttr(tag.raw, "data-composition-id") && !readAttr(tag.raw, "data-width") && !readAttr(tag.raw, "data-height")) {
|
|
74
|
-
|
|
75
|
-
skipBefore = closeMatch ? tag.index + closeMatch.index + closeMatch[0].length : Infinity;
|
|
71
|
+
skipBefore = tag.endIndex ?? Infinity;
|
|
76
72
|
continue;
|
|
77
73
|
}
|
|
78
|
-
return
|
|
74
|
+
return tag;
|
|
79
75
|
}
|
|
80
76
|
return null;
|
|
81
77
|
}
|
|
@@ -246,15 +242,24 @@ function truncateSnippet(value, maxLength = 220) {
|
|
|
246
242
|
function buildLintContext(html, options = {}) {
|
|
247
243
|
const rawSource = html || "";
|
|
248
244
|
let source = stripHtmlComments(rawSource);
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
"
|
|
245
|
+
const initialStructure = parseHtmlStructure(source);
|
|
246
|
+
const templateTags = initialStructure.tags.filter(
|
|
247
|
+
(tag) => tag.name === "template" && tag.closeIndex != null
|
|
252
248
|
);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
249
|
+
let sourceWithoutTemplates = source;
|
|
250
|
+
for (const template2 of [...templateTags].reverse()) {
|
|
251
|
+
const end = template2.endIndex ?? template2.index;
|
|
252
|
+
sourceWithoutTemplates = sourceWithoutTemplates.slice(0, template2.index) + " ".repeat(end - template2.index) + sourceWithoutTemplates.slice(end);
|
|
253
|
+
}
|
|
254
|
+
const template = templateTags[0];
|
|
255
|
+
let structure = initialStructure;
|
|
256
|
+
if (template && !findRootTag(sourceWithoutTemplates)) {
|
|
257
|
+
source = source.slice(template.index + template.raw.length, template.closeIndex);
|
|
258
|
+
structure = parseHtmlStructure(source);
|
|
259
|
+
}
|
|
260
|
+
const tags = structure.tags;
|
|
256
261
|
const styles = [
|
|
257
|
-
...
|
|
262
|
+
...structure.styles,
|
|
258
263
|
...(options.externalStyles ?? []).map((style) => ({
|
|
259
264
|
attrs: `href="${style.href}"`,
|
|
260
265
|
content: style.content,
|
|
@@ -262,9 +267,9 @@ function buildLintContext(html, options = {}) {
|
|
|
262
267
|
index: -1
|
|
263
268
|
}))
|
|
264
269
|
];
|
|
265
|
-
const scripts =
|
|
270
|
+
const scripts = structure.scripts;
|
|
266
271
|
const compositionIds = collectCompositionIds(tags);
|
|
267
|
-
const rootTag = findRootTag(source);
|
|
272
|
+
const rootTag = findRootTag(source, tags);
|
|
268
273
|
const rootCompositionId = readAttr(rootTag?.raw || "", "data-composition-id");
|
|
269
274
|
return {
|
|
270
275
|
source,
|
|
@@ -403,6 +408,23 @@ function findVisibleMarkupCommentLeak(source) {
|
|
|
403
408
|
return null;
|
|
404
409
|
}
|
|
405
410
|
var coreRules = [
|
|
411
|
+
// id_requires_css_escape
|
|
412
|
+
({ tags }) => {
|
|
413
|
+
const findings = [];
|
|
414
|
+
for (const tag of tags) {
|
|
415
|
+
const id = readAttr(tag.raw, "id");
|
|
416
|
+
if (!id || !/^\d/.test(id)) continue;
|
|
417
|
+
findings.push({
|
|
418
|
+
code: "id_requires_css_escape",
|
|
419
|
+
severity: "warning",
|
|
420
|
+
message: `id="${id}" starts with a digit, so the common selector \`#${id}\` throws a SyntaxError in querySelector().`,
|
|
421
|
+
elementId: id,
|
|
422
|
+
fixHint: "Rename the id to start with a letter (recommended), or build selectors with `#${CSS.escape(id)}` at runtime.",
|
|
423
|
+
snippet: truncateSnippet(tag.raw)
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return findings;
|
|
427
|
+
},
|
|
406
428
|
// root_missing_composition_id + root_missing_dimensions
|
|
407
429
|
({ rootTag }) => {
|
|
408
430
|
const findings = [];
|
|
@@ -1981,18 +2003,19 @@ ${right.raw}`)
|
|
|
1981
2003
|
async ({ styles, scripts, tags }) => {
|
|
1982
2004
|
const findings = [];
|
|
1983
2005
|
const cssOpacityZeroSelectors = /* @__PURE__ */ new Set();
|
|
2006
|
+
const opacityExactlyZero = /opacity\s*:\s*0(?:\.0+)?\s*(?:;|$)/;
|
|
1984
2007
|
for (const style of styles) {
|
|
1985
2008
|
for (const [, selector, body] of style.content.matchAll(
|
|
1986
2009
|
/([#.][a-zA-Z0-9_-]+)\s*\{([^}]+)\}/g
|
|
1987
2010
|
)) {
|
|
1988
|
-
if (body &&
|
|
2011
|
+
if (body && opacityExactlyZero.test(body)) {
|
|
1989
2012
|
cssOpacityZeroSelectors.add((selector ?? "").trim());
|
|
1990
2013
|
}
|
|
1991
2014
|
}
|
|
1992
2015
|
}
|
|
1993
2016
|
for (const tag of tags) {
|
|
1994
2017
|
const inlineStyle = readAttr(tag.raw, "style");
|
|
1995
|
-
if (!inlineStyle ||
|
|
2018
|
+
if (!inlineStyle || !opacityExactlyZero.test(inlineStyle)) continue;
|
|
1996
2019
|
const id = readAttr(tag.raw, "id");
|
|
1997
2020
|
const classes = readAttr(tag.raw, "class")?.split(/\s+/).filter(Boolean) ?? [];
|
|
1998
2021
|
if (id) cssOpacityZeroSelectors.add(`#${id}`);
|
|
@@ -2460,21 +2483,20 @@ function collectDeclaredVariableIds(htmlTagRaw) {
|
|
|
2460
2483
|
}
|
|
2461
2484
|
return declared;
|
|
2462
2485
|
}
|
|
2463
|
-
function collectAllDeclaredVariableIds(
|
|
2486
|
+
function collectAllDeclaredVariableIds(tags) {
|
|
2464
2487
|
const all = /* @__PURE__ */ new Set();
|
|
2465
|
-
const
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
const ids = collectDeclaredVariableIds(match[0]);
|
|
2488
|
+
for (const tag of tags) {
|
|
2489
|
+
if (!readAttr(tag.raw, "data-composition-variables")) continue;
|
|
2490
|
+
const ids = collectDeclaredVariableIds(tag.raw);
|
|
2469
2491
|
if (ids === null) return null;
|
|
2470
2492
|
for (const id of ids) all.add(id);
|
|
2471
2493
|
}
|
|
2472
2494
|
return all;
|
|
2473
2495
|
}
|
|
2474
|
-
function declaredIdsForBindingCheck(
|
|
2475
|
-
const declared = collectAllDeclaredVariableIds(
|
|
2496
|
+
function declaredIdsForBindingCheck(tags) {
|
|
2497
|
+
const declared = collectAllDeclaredVariableIds(tags);
|
|
2476
2498
|
if (declared === null) return null;
|
|
2477
|
-
if (declared.size === 0 && !findHtmlTag(
|
|
2499
|
+
if (declared.size === 0 && !findHtmlTag(tags)) return null;
|
|
2478
2500
|
return declared;
|
|
2479
2501
|
}
|
|
2480
2502
|
var compositionRules = [
|
|
@@ -2878,8 +2900,8 @@ var compositionRules = [
|
|
|
2878
2900
|
// nothing, so a typo'd binding is invisible until a customer's override
|
|
2879
2901
|
// does nothing. Skipped for fragment files (no <html>): their values come
|
|
2880
2902
|
// from a host's data-variable-values, which this file can't see.
|
|
2881
|
-
({
|
|
2882
|
-
const declared = declaredIdsForBindingCheck(
|
|
2903
|
+
({ tags }) => {
|
|
2904
|
+
const declared = declaredIdsForBindingCheck(tags);
|
|
2883
2905
|
if (!declared) return [];
|
|
2884
2906
|
const findings = [];
|
|
2885
2907
|
for (const tag of tags) {
|
|
@@ -2904,8 +2926,8 @@ var compositionRules = [
|
|
|
2904
2926
|
// catch them at lint time rather than wondering why their `getVariables()`
|
|
2905
2927
|
// defaults aren't applied.
|
|
2906
2928
|
// fallow-ignore-next-line complexity
|
|
2907
|
-
({
|
|
2908
|
-
const htmlTag = findHtmlTag(
|
|
2929
|
+
({ tags }) => {
|
|
2930
|
+
const htmlTag = findHtmlTag(tags);
|
|
2909
2931
|
if (!htmlTag) return [];
|
|
2910
2932
|
const raw = readJsonAttr(htmlTag.raw, "data-composition-variables");
|
|
2911
2933
|
if (!raw) return [];
|
|
@@ -2978,8 +3000,8 @@ var compositionRules = [
|
|
|
2978
3000
|
// fixed top-left-origin screenshot region, which RTL layout can shift the
|
|
2979
3001
|
// actual content away from), only surfaces the already-confirmed footgun
|
|
2980
3002
|
// before someone hits it blind.
|
|
2981
|
-
({
|
|
2982
|
-
const htmlTag = findHtmlTag(
|
|
3003
|
+
({ tags }) => {
|
|
3004
|
+
const htmlTag = findHtmlTag(tags);
|
|
2983
3005
|
if (!htmlTag) return [];
|
|
2984
3006
|
const dir = readAttr(htmlTag.raw, "dir");
|
|
2985
3007
|
if (!dir) return [];
|
|
@@ -3671,11 +3693,8 @@ async function lintHyperframeHtml(html, options = {}) {
|
|
|
3671
3693
|
}
|
|
3672
3694
|
function extractMediaUrls(html) {
|
|
3673
3695
|
const results = [];
|
|
3674
|
-
const
|
|
3675
|
-
|
|
3676
|
-
while ((match = tagRe.exec(html)) !== null) {
|
|
3677
|
-
const tagName = (match[1] ?? "").toLowerCase();
|
|
3678
|
-
const raw = match[0];
|
|
3696
|
+
for (const { name: tagName, raw } of parseHtmlStructure(html).tags) {
|
|
3697
|
+
if (!/^(?:video|audio|img|source)$/.test(tagName)) continue;
|
|
3679
3698
|
const src = readAttr(raw, "src");
|
|
3680
3699
|
if (!src) continue;
|
|
3681
3700
|
if (/^https?:\/\//i.test(src)) {
|
|
@@ -3751,63 +3770,60 @@ import { parseHTML } from "linkedom";
|
|
|
3751
3770
|
function parseSubCompHtml(html) {
|
|
3752
3771
|
return parseHTML(html).document;
|
|
3753
3772
|
}
|
|
3773
|
+
function querySelectorAllIncludingTemplates(root, selector) {
|
|
3774
|
+
const matches = [...root.querySelectorAll(selector)];
|
|
3775
|
+
for (const template of root.querySelectorAll("template")) {
|
|
3776
|
+
const content = template.content;
|
|
3777
|
+
if (content) matches.push(...querySelectorAllIncludingTemplates(content, selector));
|
|
3778
|
+
}
|
|
3779
|
+
return matches;
|
|
3780
|
+
}
|
|
3754
3781
|
var AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([".mp3", ".wav", ".aac", ".ogg", ".m4a", ".flac", ".opus"]);
|
|
3755
|
-
var STYLE_BLOCK_RE = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
3756
|
-
var OPEN_TAG_RE = /<([a-z][\w:-]*)(\s[^<>]*?)?>/gi;
|
|
3757
3782
|
var MASK_IMAGE_URL_RE = /\b(?:-webkit-)?mask-image\s*:\s*[^;{}]*url\(\s*(?:"([^"]+)"|'([^']+)'|([^"')\s]+))\s*\)/gi;
|
|
3758
|
-
function readHtmlAttr(tag, name) {
|
|
3759
|
-
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3760
|
-
const match = tag.match(new RegExp(`\\b${escaped}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, "i"));
|
|
3761
|
-
return match?.[1] ?? match?.[2] ?? null;
|
|
3762
|
-
}
|
|
3763
3783
|
function isLocalStylesheetHref(href) {
|
|
3764
3784
|
return !!href && !/^(https?:|data:|blob:|\/\/)/i.test(href);
|
|
3765
3785
|
}
|
|
3766
|
-
function
|
|
3786
|
+
function collectLocalStylesheets(projectDir, document, compSrcPath) {
|
|
3767
3787
|
const styles = [];
|
|
3768
|
-
const
|
|
3769
|
-
|
|
3770
|
-
while ((match = linkRe.exec(html)) !== null) {
|
|
3771
|
-
const tag = match[0];
|
|
3772
|
-
const rel = tag.match(/\brel\s*=\s*["']([^"']+)["']/i)?.[1] ?? "";
|
|
3788
|
+
for (const link of querySelectorAllIncludingTemplates(document, "link")) {
|
|
3789
|
+
const rel = link.getAttribute("rel") ?? "";
|
|
3773
3790
|
if (!rel.split(/\s+/).some((part) => part.toLowerCase() === "stylesheet")) continue;
|
|
3774
|
-
const href =
|
|
3791
|
+
const href = link.getAttribute("href") ?? "";
|
|
3775
3792
|
if (!isLocalStylesheetHref(href)) continue;
|
|
3776
3793
|
const rootRelative = compSrcPath ? join(dirname(compSrcPath), href) : href;
|
|
3777
3794
|
const stylesheet = resolveExistingLocalAsset(projectDir, rootRelative);
|
|
3778
3795
|
if (!stylesheet) continue;
|
|
3779
|
-
styles.push({
|
|
3796
|
+
styles.push({
|
|
3797
|
+
href,
|
|
3798
|
+
content: readFileSync(stylesheet.resolved, "utf-8"),
|
|
3799
|
+
rootRelativePath: stylesheet.rootRelativePath
|
|
3800
|
+
});
|
|
3801
|
+
}
|
|
3802
|
+
return styles;
|
|
3803
|
+
}
|
|
3804
|
+
function collectExternalStyles(projectDir, html, compSrcPath) {
|
|
3805
|
+
const styles = [];
|
|
3806
|
+
const { document } = parseHTML(html);
|
|
3807
|
+
for (const { href, content } of collectLocalStylesheets(projectDir, document, compSrcPath)) {
|
|
3808
|
+
styles.push({ href, content });
|
|
3780
3809
|
}
|
|
3781
3810
|
return styles;
|
|
3782
3811
|
}
|
|
3783
3812
|
function collectCssSources(projectDir, html, compSrcPath) {
|
|
3784
3813
|
const sources = [];
|
|
3785
|
-
|
|
3786
|
-
const
|
|
3787
|
-
|
|
3788
|
-
sources.push({ content: styleMatch[1] ?? "" });
|
|
3814
|
+
const { document } = parseHTML(html);
|
|
3815
|
+
for (const style of querySelectorAllIncludingTemplates(document, "style")) {
|
|
3816
|
+
sources.push({ content: style.textContent ?? "" });
|
|
3789
3817
|
}
|
|
3790
|
-
const
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
const href = readHtmlAttr(tag, "href") ?? "";
|
|
3797
|
-
if (!isLocalStylesheetHref(href)) continue;
|
|
3798
|
-
const rootRelativePath = compSrcPath ? join(dirname(compSrcPath), href) : href;
|
|
3799
|
-
const stylesheet = resolveExistingLocalAsset(projectDir, rootRelativePath);
|
|
3800
|
-
if (!stylesheet) continue;
|
|
3801
|
-
sources.push({
|
|
3802
|
-
content: readFileSync(stylesheet.resolved, "utf-8"),
|
|
3803
|
-
rootRelativePath: stylesheet.rootRelativePath
|
|
3804
|
-
});
|
|
3818
|
+
for (const { content, rootRelativePath } of collectLocalStylesheets(
|
|
3819
|
+
projectDir,
|
|
3820
|
+
document,
|
|
3821
|
+
compSrcPath
|
|
3822
|
+
)) {
|
|
3823
|
+
sources.push({ content, rootRelativePath });
|
|
3805
3824
|
}
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
while ((tagMatch = tagPattern.exec(html)) !== null) {
|
|
3809
|
-
const tag = tagMatch[0];
|
|
3810
|
-
const style = readHtmlAttr(tag, "style");
|
|
3825
|
+
for (const element of querySelectorAllIncludingTemplates(document, "[style]")) {
|
|
3826
|
+
const style = element.getAttribute("style");
|
|
3811
3827
|
if (!style) continue;
|
|
3812
3828
|
sources.push({ content: style });
|
|
3813
3829
|
}
|
|
@@ -3885,7 +3901,9 @@ async function lintProject(projectDir) {
|
|
|
3885
3901
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
3886
3902
|
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
3887
3903
|
if (entry.isDirectory()) out.push(...collectHtmlFiles(join(dir, entry.name), relPath));
|
|
3888
|
-
else if (entry.isFile() && entry.name.endsWith(".html")
|
|
3904
|
+
else if (entry.isFile() && entry.name.endsWith(".html") && !entry.name.startsWith("._")) {
|
|
3905
|
+
out.push(relPath);
|
|
3906
|
+
}
|
|
3889
3907
|
}
|
|
3890
3908
|
return out;
|
|
3891
3909
|
};
|
|
@@ -4066,7 +4084,9 @@ function lintTextureMaskAssetNotFound(projectDir, htmlSources) {
|
|
|
4066
4084
|
function lintMultipleRootCompositions(projectDir) {
|
|
4067
4085
|
const findings = [];
|
|
4068
4086
|
try {
|
|
4069
|
-
const rootHtmlFiles = readdirSync(projectDir).filter(
|
|
4087
|
+
const rootHtmlFiles = readdirSync(projectDir).filter(
|
|
4088
|
+
(file) => file.endsWith(".html") && !file.startsWith("._")
|
|
4089
|
+
);
|
|
4070
4090
|
const rootCompositions = [];
|
|
4071
4091
|
for (const file of rootHtmlFiles) {
|
|
4072
4092
|
if (file === "caption-skin.html") continue;
|