mirror of
https://github.com/morluto/rea.git
synced 2026-10-29 19:05:45 +00:00
fix(javascript): reconstruct HTML script loads accurately (#1042)
* fix: parse HTML script references with HTML semantics Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> * test(javascript): cover exact HTML script resource identity --------- Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> Co-authored-by: N0zoM1z0 <1705004398@qq.com>
This commit is contained in:
1 parent
53409ab196
commit
1154f2f03c
11 files changed
+475
-22
No files matched your search
@@ -25,6 +25,7 @@ import type {
|
||||
JavaScriptSourceMapOriginal,
|
||||
} from "./JavaScriptArtifactAnalysisTypes.js";
|
||||
import { analyzeJavaScriptJsonModule } from "./JavaScriptJsonModules.js";
|
||||
import { htmlArtifactReferences } from "../../domain/javascript/htmlArtifactReferences.js";
|
||||
|
||||
interface MutableArtifactAnalysis {
|
||||
readonly files: AnalyzedJavaScriptArtifactFile[];
|
||||
@@ -205,26 +206,13 @@ const parseHtmlScripts = (
|
||||
path: string,
|
||||
text: string,
|
||||
): JavaScriptHtmlScriptObservation[] => {
|
||||
const scripts: JavaScriptHtmlScriptObservation[] = [];
|
||||
const baseHref = htmlBaseHref(text);
|
||||
const pattern = /<script\b[^>]*\bsrc\s*=\s*(["'])([^"']+)\1[^>]*>/giu;
|
||||
for (const match of text.matchAll(pattern)) {
|
||||
const script = match[2];
|
||||
if (script === undefined) continue;
|
||||
const start = match.index;
|
||||
scripts.push({
|
||||
html_path: path,
|
||||
script_path: script,
|
||||
base_href: baseHref,
|
||||
location: rangeForOffsets(text, start, start + match[0].length),
|
||||
});
|
||||
}
|
||||
return scripts;
|
||||
};
|
||||
|
||||
const htmlBaseHref = (text: string): string | null => {
|
||||
const match = /<base\b[^>]*\bhref\s*=\s*(["'])([^"']+)\1[^>]*>/iu.exec(text);
|
||||
return match?.[2] ?? null;
|
||||
const { scripts, baseHref } = htmlArtifactReferences(text);
|
||||
return scripts.map(({ scriptPath, startOffset, endOffset }) => ({
|
||||
html_path: path,
|
||||
script_path: scriptPath,
|
||||
base_href: baseHref,
|
||||
location: rangeForOffsets(text, startOffset, endOffset),
|
||||
}));
|
||||
};
|
||||
|
||||
const parseSourceMap = (
|
||||
|
||||
@@ -5,7 +5,10 @@ import {
|
||||
linkElectronRoleToAsset,
|
||||
type JavaScriptArtifactGraphContext,
|
||||
} from "./JavaScriptArtifactGraphContext.js";
|
||||
import { artifactObservationEvidence } from "./JavaScriptArtifactGraphEvidence.js";
|
||||
import {
|
||||
artifactObservationEvidence,
|
||||
staticInferenceEvidence,
|
||||
} from "./JavaScriptArtifactGraphEvidence.js";
|
||||
import { completeApplicationCoverage } from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js";
|
||||
import { resolveArtifactPathByContext } from "./JavaScriptArtifactPathResolution.js";
|
||||
|
||||
@@ -57,7 +60,32 @@ export const addJavaScriptHtmlRoles = (
|
||||
htmlBaseHref: script.base_href,
|
||||
});
|
||||
const resolvedPath = resolution.resolved_path;
|
||||
if (resolvedPath === null) continue;
|
||||
if (resolvedPath === null) {
|
||||
context.accumulator.addNode({
|
||||
kind: role.kind,
|
||||
identity: role.identity,
|
||||
observations: [
|
||||
{
|
||||
label: script.script_path,
|
||||
properties: {
|
||||
mechanism: "html-script-reference",
|
||||
script_path: script.script_path,
|
||||
base_href: script.base_href,
|
||||
...resolution,
|
||||
},
|
||||
evidence: staticInferenceEvidence({
|
||||
sha256: html.sha256,
|
||||
path: html.path,
|
||||
range: script.location,
|
||||
operation: "resolve-html-script-reference",
|
||||
coverage: completeApplicationCoverage(),
|
||||
limitations: resolution.limitations,
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const resolved = context.fileNodes.get(resolvedPath);
|
||||
if (resolved === undefined) continue;
|
||||
addStaticInferenceEdge(context, {
|
||||
|
||||
@@ -676,3 +676,52 @@ const file = (
|
||||
? { included: false, reason: "invalid-utf8" }
|
||||
: { included: true, value: text },
|
||||
});
|
||||
|
||||
describe("exact HTML resource resolution", () => {
|
||||
it.each([
|
||||
["extension", ["app.js"]],
|
||||
["directory index", ["app/index.js"]],
|
||||
["directory package", ["app/package.json", "app/entry.js"]],
|
||||
] as const)("does not infer a file from %s fallback", (_name, paths) => {
|
||||
const files = fileMap([
|
||||
file("index.html", "root"),
|
||||
...paths.map((path) =>
|
||||
file(
|
||||
path,
|
||||
"root",
|
||||
path.endsWith("package.json") ? '{"main":"entry.js"}' : "",
|
||||
),
|
||||
),
|
||||
]);
|
||||
expect(
|
||||
resolve({
|
||||
declaredPath: "./app",
|
||||
sourcePath: "index.html",
|
||||
context: "html-reference",
|
||||
files,
|
||||
}),
|
||||
).toMatchObject({ resolution_status: "not-found", resolved_path: null });
|
||||
});
|
||||
|
||||
it("prefers an exact extensionless resource and retains URL suffix handling", () => {
|
||||
const files = fileMap([
|
||||
file("renderer/index.html", "root"),
|
||||
file("assets/app", "root"),
|
||||
file("assets/app.js", "root"),
|
||||
file("assets/app/index.js", "root"),
|
||||
]);
|
||||
expect(
|
||||
resolve({
|
||||
declaredPath: "./app?cache=1#v2",
|
||||
sourcePath: "renderer/index.html",
|
||||
context: "html-reference",
|
||||
htmlBaseHref: "/assets/",
|
||||
files,
|
||||
}),
|
||||
).toMatchObject({
|
||||
declared_path: "./app?cache=1#v2",
|
||||
resolution_status: "resolved",
|
||||
resolved_path: "assets/app",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -93,6 +93,17 @@ const resolvePackagePath = (
|
||||
if (typeof candidate !== "string") return candidate;
|
||||
const confined = confineCandidate(input, candidate);
|
||||
if (typeof confined !== "string") return confined;
|
||||
if (input.context === "html-reference")
|
||||
return outcome(
|
||||
input,
|
||||
resolveFileCandidates(input, [confined]) ?? {
|
||||
resolvedPath: null,
|
||||
status: "not-found",
|
||||
limitations: [
|
||||
`The exact HTML resource ${confined} was not found among the selected application files.`,
|
||||
],
|
||||
},
|
||||
);
|
||||
const resolved =
|
||||
mode === "files-and-index"
|
||||
? (resolveFileCandidates(input, [
|
||||
|
||||
Reference in new issue
Block a user