mirror of
https://github.com/morluto/rea.git
synced 2026-10-28 18:35:53 +00:00
refactor(javascript): centralize package and JSON document facts
This commit is contained in:
1 parent
4cfc61147b
commit
37e92e430b
29 files changed
+748
-113
No files matched your search
@@ -0,0 +1,156 @@
|
||||
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
|
||||
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
|
||||
|
||||
interface JavaScriptJsonDocumentSource {
|
||||
readonly path: string;
|
||||
readonly container_sha256: string;
|
||||
readonly sha256: string;
|
||||
}
|
||||
|
||||
interface JavaScriptJsonDocumentFactBase {
|
||||
readonly source: JavaScriptJsonDocumentSource;
|
||||
readonly topLevelKeys: readonly string[];
|
||||
}
|
||||
|
||||
export type JavaScriptJsonDocumentFacts =
|
||||
| (JavaScriptJsonDocumentFactBase & {
|
||||
readonly status: "included";
|
||||
readonly packageFields: Readonly<
|
||||
Partial<Record<(typeof PACKAGE_PROPERTIES)[number], unknown>>
|
||||
> | null;
|
||||
})
|
||||
| (JavaScriptJsonDocumentFactBase & {
|
||||
readonly status: "invalid";
|
||||
readonly packageFields: null;
|
||||
readonly syntaxError: SyntaxError;
|
||||
})
|
||||
| (JavaScriptJsonDocumentFactBase & {
|
||||
readonly status: "unavailable";
|
||||
readonly packageFields: null;
|
||||
readonly reason:
|
||||
| "not-applicable"
|
||||
| "invalid-utf8"
|
||||
| "outside-analysis-scope";
|
||||
});
|
||||
|
||||
interface SourceSnapshot {
|
||||
readonly source: JavaScriptJsonDocumentSource;
|
||||
readonly kind: JavaScriptArtifactFile["kind"];
|
||||
readonly text: JavaScriptArtifactFile["text"];
|
||||
}
|
||||
|
||||
interface CachedFacts {
|
||||
readonly snapshot: SourceSnapshot;
|
||||
readonly facts: JavaScriptJsonDocumentFacts;
|
||||
}
|
||||
|
||||
const PACKAGE_PROPERTIES = [
|
||||
"name",
|
||||
"version",
|
||||
"main",
|
||||
"module",
|
||||
"renderer",
|
||||
"browser",
|
||||
"exports",
|
||||
"imports",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Lazy JSON facts shared by one analysis and its graph projection. Entries follow
|
||||
* the admitted source files' lifetime; changed identity, format or text replaces them.
|
||||
*/
|
||||
export class JavaScriptJsonDocuments {
|
||||
readonly #byFile = new Map<JavaScriptArtifactFile, CachedFacts>();
|
||||
|
||||
read(file: JavaScriptArtifactFile): JavaScriptJsonDocumentFacts {
|
||||
const cached = this.#byFile.get(file);
|
||||
if (cached !== undefined && sameSnapshot(cached.snapshot, file))
|
||||
return cached.facts;
|
||||
|
||||
const snapshot = sourceSnapshot(file);
|
||||
const facts = parseDocument(snapshot);
|
||||
this.#byFile.set(file, { snapshot, facts });
|
||||
return facts;
|
||||
}
|
||||
}
|
||||
|
||||
const sourceSnapshot = (file: JavaScriptArtifactFile): SourceSnapshot => ({
|
||||
source: Object.freeze({
|
||||
path: file.path,
|
||||
container_sha256: file.container_sha256,
|
||||
sha256: file.sha256,
|
||||
}),
|
||||
kind: file.kind,
|
||||
text: { ...file.text },
|
||||
});
|
||||
|
||||
const sameSnapshot = (
|
||||
left: SourceSnapshot,
|
||||
right: JavaScriptArtifactFile,
|
||||
): boolean =>
|
||||
left.source.path === right.path &&
|
||||
left.source.container_sha256 === right.container_sha256 &&
|
||||
left.source.sha256 === right.sha256 &&
|
||||
left.kind === right.kind &&
|
||||
left.text.included === right.text.included &&
|
||||
(left.text.included
|
||||
? right.text.included && left.text.value === right.text.value
|
||||
: !right.text.included && left.text.reason === right.text.reason);
|
||||
|
||||
const parseDocument = (
|
||||
snapshot: SourceSnapshot,
|
||||
): JavaScriptJsonDocumentFacts => {
|
||||
if (!snapshot.text.included)
|
||||
return Object.freeze({
|
||||
source: snapshot.source,
|
||||
status: "unavailable",
|
||||
packageFields: null,
|
||||
topLevelKeys: Object.freeze([]),
|
||||
reason: snapshot.text.reason,
|
||||
});
|
||||
|
||||
let root: unknown;
|
||||
try {
|
||||
root = JSON.parse(snapshot.text.value);
|
||||
} catch (cause: unknown) {
|
||||
if (!(cause instanceof SyntaxError)) throw cause;
|
||||
return Object.freeze({
|
||||
source: snapshot.source,
|
||||
status: "invalid",
|
||||
packageFields: null,
|
||||
topLevelKeys: Object.freeze([]),
|
||||
syntaxError: cause,
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof root !== "object" || root === null || Array.isArray(root))
|
||||
return Object.freeze({
|
||||
source: snapshot.source,
|
||||
status: "included",
|
||||
packageFields: null,
|
||||
topLevelKeys: Object.freeze([]),
|
||||
});
|
||||
|
||||
const record = root as Record<string, unknown>;
|
||||
const topLevelKeys = Object.freeze(
|
||||
Object.keys(record).sort(compareUnicodeCodePoints),
|
||||
);
|
||||
const packageFields =
|
||||
snapshot.kind === "package-json"
|
||||
? Object.freeze(
|
||||
Object.fromEntries(
|
||||
PACKAGE_PROPERTIES.flatMap((key) =>
|
||||
Object.hasOwn(record, key)
|
||||
? [[key, Reflect.get(record, key)]]
|
||||
: [],
|
||||
),
|
||||
),
|
||||
)
|
||||
: null;
|
||||
return Object.freeze({
|
||||
source: snapshot.source,
|
||||
status: "included",
|
||||
packageFields,
|
||||
topLevelKeys,
|
||||
});
|
||||
};
|
||||
Reference in new issue
Block a user