This commit is contained in:
Henning Dieterichs
2023-09-07 15:00:50 +02:00
parent d45dfdaa71
commit a9656e1e12
37 changed files with 5502 additions and 3664 deletions

View File

@@ -1,19 +1,22 @@
import {
DataExtractorApi,
DataResult,
JSONString,
DataExtractor,
DataExtraction,
ExtractionCollector,
DataExtractorContext,
} from "./DataExtractorApi";
import {
DataExtractorInfo,
VisualizationData,
} from "../../DataExtractionResult";
import { registerDefaultExtractors } from "./default-extractors";
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
import * as helpers from "../helpers";
import {
CallFrameInfo,
CallFrameRequest,
CallFramesSnapshot,
DataExtraction,
DataExtractor,
DataExtractorApi,
DataExtractorContext,
DataResult,
JSONString,
SkippedCallFrames,
} from "./DataExtractorApi";
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
import { registerDefaultExtractors } from "./default-extractors";
/**
* @internal
@@ -42,51 +45,18 @@ export class DataExtractorApiImpl implements DataExtractorApi {
valueFn: () => unknown,
evalFn: <T>(expression: string) => T,
preferredDataExtractorId: string | undefined,
variablesInScope: Record<string, () => unknown>
variablesInScope: Record<string, () => unknown>,
callFramesSnapshot: CallFramesSnapshot | null
): JSONString<DataResult> {
class ContextImpl implements DataExtractorContext {
constructor(
public readonly variablesInScope: Record<string, () => unknown>,
public readonly expression: string | undefined,
public readonly evalFn: <T>(expression: string) => T,
private readonly _api: DataExtractorApiImpl,
private readonly _parent: ContextImpl | undefined
) {}
get _level(): number {
return this._parent ? this._parent._level + 1 : 0;
}
extract(value: any): VisualizationData | undefined {
if (this._level > 10) {
throw new Error(
"extract() called too many times recursively"
);
}
const extractions = this._api.getExtractions(
value,
new ContextImpl(
this.variablesInScope,
undefined,
this.evalFn,
this._api,
this
)
);
if (extractions.length === 0) {
return undefined;
}
return extractions[0].extractData();
}
}
const callFrameRequests: CallFrameRequest[] = [];
const rootContext = new ContextImpl(
variablesInScope,
removeEnd(removeStart(valueFn.toString(), "() => ("), ")").trim(),
evalFn,
this,
undefined
undefined,
callFramesSnapshot?.frames ?? [],
callFrameRequests
);
DataExtractorApiImpl.lastContext = rootContext;
@@ -94,6 +64,20 @@ export class DataExtractorApiImpl implements DataExtractorApi {
const extractions = this.getExtractions(value, rootContext);
DataExtractorApiImpl.lastContext = undefined;
const requestId =
callFrameRequests.length === 0
? ""
: "" + cyrb53(JSON.stringify(callFrameRequests));
if ((callFramesSnapshot?.requestId ?? "") !== requestId) {
return this.toJson({
kind: "OutdatedCallFrameSnapshot",
callFramesRequest: {
requestId,
requestedCallFrames: callFrameRequests,
},
});
}
let usedExtraction = extractions[0];
if (!usedExtraction) {
return this.toJson({ kind: "NoExtractors" } as DataResult);
@@ -108,14 +92,6 @@ export class DataExtractorApiImpl implements DataExtractorApi {
}
}
function mapExtractor(e: DataExtraction): DataExtractorInfo {
return {
id: e.id! as any,
name: e.name!,
priority: e.priority,
};
}
const data = usedExtraction.extractData();
return this.toJson({
kind: "Data",
@@ -188,6 +164,60 @@ export class DataExtractorApiImpl implements DataExtractorApi {
}
}
function mapExtractor(e: DataExtraction): DataExtractorInfo {
return {
id: e.id! as any,
name: e.name!,
priority: e.priority,
};
}
class ContextImpl implements DataExtractorContext {
constructor(
public readonly variablesInScope: Record<string, () => unknown>,
public readonly expression: string | undefined,
public readonly evalFn: <T>(expression: string) => T,
private readonly _api: DataExtractorApiImpl,
private readonly _parent: ContextImpl | undefined,
public readonly callFrameInfos: readonly (
| CallFrameInfo
| SkippedCallFrames
)[],
private readonly _callFrameRequests: CallFrameRequest[]
) {}
addCallFrameRequest(request: CallFrameRequest): void {
this._callFrameRequests.push(request);
}
get _level(): number {
return this._parent ? this._parent._level + 1 : 0;
}
extract(value: any): VisualizationData | undefined {
if (this._level > 10) {
throw new Error("extract() called too many times recursively");
}
const extractions = this._api.getExtractions(
value,
new ContextImpl(
this.variablesInScope,
undefined,
this.evalFn,
this._api,
this,
this.callFrameInfos,
this._callFrameRequests
)
);
if (extractions.length === 0) {
return undefined;
}
return extractions[0].extractData();
}
}
function removeStart(str: string, start: string): string {
if (str.startsWith(start)) {
return str.substr(start.length);
@@ -201,3 +231,20 @@ function removeEnd(str: string, end: string): string {
}
return str;
}
// From https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
function cyrb53(str: string, seed = 0) {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}