Fixes issues with global.
This commit is contained in:
11
data-extraction/src/getGlobal.ts
Normal file
11
data-extraction/src/getGlobal.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export function getGlobal(): any {
|
||||
if (typeof globalThis === "object") {
|
||||
return globalThis;
|
||||
} else if (typeof global === "object") {
|
||||
return global;
|
||||
} else if (typeof window === "object") {
|
||||
return window;
|
||||
}
|
||||
|
||||
throw new Error("No global available");
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
VisualizationData,
|
||||
DataExtractionResult,
|
||||
} from "../../DataExtractionResult";
|
||||
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
|
||||
|
||||
export interface DataExtractorApi {
|
||||
/**
|
||||
@@ -31,6 +32,9 @@ export interface DataExtractorApi {
|
||||
* @preferExisting if `true`, existing extractors with the same id are not overwritten.
|
||||
*/
|
||||
registerDefaultExtractors(preferExisting?: boolean): void;
|
||||
|
||||
registerDataExtractorsSource(id: string, fn: LoadDataExtractorsFn): void;
|
||||
unregisterDataExtractorsSource(id: string): void;
|
||||
}
|
||||
|
||||
export type DataResult =
|
||||
@@ -70,7 +74,7 @@ export interface DataExtractorContext {
|
||||
*/
|
||||
evalFn: <TEval>(expression: string) => TEval;
|
||||
|
||||
variablesInScope: Record<string, unknown>;
|
||||
variablesInScope: Record<string, () => unknown>;
|
||||
}
|
||||
|
||||
export interface DataExtraction {
|
||||
|
||||
@@ -5,20 +5,21 @@ import {
|
||||
DataExtractor,
|
||||
DataExtraction,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "./DataExtractorApi";
|
||||
import { DataExtractorInfo } from "../../DataExtractionResult";
|
||||
import { registerDefaultExtractors } from "./default-extractors";
|
||||
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
|
||||
import * as helpers from "../helpers";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
public static lastEvalFn: (<T>(expression: string) => T) | undefined;
|
||||
public static lastVariablesInScope:
|
||||
| Record<string, unknown>
|
||||
| undefined = undefined;
|
||||
public static lastContext: DataExtractorContext | undefined = undefined;
|
||||
|
||||
private readonly extractors = new Map<string, DataExtractor>();
|
||||
private readonly extractorSources = new Map<string, LoadDataExtractorsFn>();
|
||||
|
||||
private toJson<TData>(data: TData): JSONString<TData> {
|
||||
return JSON.stringify(data) as any;
|
||||
@@ -38,7 +39,7 @@ export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
valueFn: () => unknown,
|
||||
evalFn: <T>(expression: string) => T,
|
||||
preferredDataExtractorId: string | undefined,
|
||||
variablesInScope: Record<string, unknown>
|
||||
variablesInScope: Record<string, () => unknown>
|
||||
): JSONString<DataResult> {
|
||||
const extractions = new Array<DataExtraction>();
|
||||
const extractionCollector: ExtractionCollector = {
|
||||
@@ -47,19 +48,27 @@ export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
},
|
||||
};
|
||||
|
||||
DataExtractorApiImpl.lastVariablesInScope = variablesInScope;
|
||||
DataExtractorApiImpl.lastEvalFn = evalFn;
|
||||
const context: DataExtractorContext = {
|
||||
evalFn,
|
||||
variablesInScope,
|
||||
};
|
||||
|
||||
DataExtractorApiImpl.lastContext = context;
|
||||
const value = valueFn();
|
||||
|
||||
for (const e of this.extractors.values()) {
|
||||
e.getExtractions(value, extractionCollector, {
|
||||
evalFn,
|
||||
variablesInScope,
|
||||
});
|
||||
const extractors = new Array<DataExtractor>();
|
||||
|
||||
for (const fn of this.extractorSources.values()) {
|
||||
fn((extractor) => {
|
||||
extractors.push(extractor);
|
||||
}, helpers);
|
||||
}
|
||||
|
||||
DataExtractorApiImpl.lastVariablesInScope = undefined;
|
||||
DataExtractorApiImpl.lastEvalFn = undefined;
|
||||
for (const e of [...this.extractors.values(), ...extractors]) {
|
||||
e.getExtractions(value, extractionCollector, context);
|
||||
}
|
||||
|
||||
DataExtractorApiImpl.lastContext = undefined;
|
||||
|
||||
extractions.sort((a, b) => b.priority - a.priority);
|
||||
let usedExtraction = extractions[0];
|
||||
@@ -69,7 +78,7 @@ export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
|
||||
if (preferredDataExtractorId) {
|
||||
const preferred = extractions.find(
|
||||
e => e.id === preferredDataExtractorId
|
||||
(e) => e.id === preferredDataExtractorId
|
||||
);
|
||||
if (preferred) {
|
||||
usedExtraction = preferred;
|
||||
@@ -99,4 +108,15 @@ export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
// TODO consider preferExisting
|
||||
registerDefaultExtractors(this);
|
||||
}
|
||||
|
||||
public registerDataExtractorsSource(
|
||||
id: string,
|
||||
fn: LoadDataExtractorsFn
|
||||
): void {
|
||||
this.extractorSources.set(id, fn);
|
||||
}
|
||||
|
||||
public unregisterDataExtractorsSource(id: string): void {
|
||||
this.extractorSources.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
LoadDataExtractorsFn,
|
||||
} from "..";
|
||||
import * as helpers from "../../helpers";
|
||||
|
||||
export class InjectedExtractor implements DataExtractor {
|
||||
readonly id = "injected-extractor";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
const key = "@hediet/debug-visualizer/injectedScripts";
|
||||
const injectedExtractorsFns = Object.values(
|
||||
(global as any)[key]
|
||||
) as LoadDataExtractorsFn[];
|
||||
|
||||
const extractors = new Array<DataExtractor>();
|
||||
|
||||
for (const fn of injectedExtractorsFns) {
|
||||
fn(extractor => {
|
||||
extractors.push(extractor);
|
||||
}, helpers);
|
||||
}
|
||||
|
||||
for (const extractor of extractors) {
|
||||
extractor.getExtractions(data, extractionCollector, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import { getDataExtractorApi } from "../injection";
|
||||
import { GridExtractor } from "./GridExtractor";
|
||||
import { TableDataExtractor } from "./TableExtractor";
|
||||
import { StringDiffExtractor } from "./StringDiffExtractor";
|
||||
import { InjectedExtractor } from "./InjectedExtractor";
|
||||
|
||||
/**
|
||||
* The default data extractors should be registered by VS Code automatically.
|
||||
@@ -28,7 +27,6 @@ export function registerDefaultExtractors(
|
||||
new GridExtractor(),
|
||||
new TableDataExtractor(),
|
||||
new StringDiffExtractor(),
|
||||
new InjectedExtractor(),
|
||||
]) {
|
||||
api.registerExtractor(item);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { DataExtractorApi } from "./DataExtractorApi";
|
||||
import { DataExtractorApiImpl } from "./DataExtractorApiImpl";
|
||||
import * as helpers from "../helpers";
|
||||
import * as globalHelpers from "../global-helpers";
|
||||
import { getGlobal } from "../../getGlobal";
|
||||
|
||||
/**
|
||||
* Returns standalone JS code representing an expression that initializes the data extraction API.
|
||||
@@ -28,16 +29,11 @@ export function getExpressionForDataExtractorApi(): string {
|
||||
return `((${selfContainedGetInitializedDataExtractorApi.toString()})())`;
|
||||
}
|
||||
|
||||
export function getExpressionToDetectDataExtractorApiPresence(): string {
|
||||
return `((${selfContainedIsDataExtractorApiInitialized.toString()})())`;
|
||||
}
|
||||
|
||||
const apiKey = "@hediet/data-extractor-api/v2";
|
||||
const apiKey = "@hediet/data-extractor-api/v3";
|
||||
|
||||
export function getDataExtractorApi(): DataExtractorApi {
|
||||
installHelpers();
|
||||
const globalObj =
|
||||
typeof window === "object" ? (window as any) : (global as any);
|
||||
const globalObj = getGlobal();
|
||||
if (!globalObj[apiKey]) {
|
||||
globalObj[apiKey] = new DataExtractorApiImpl();
|
||||
}
|
||||
@@ -45,33 +41,31 @@ export function getDataExtractorApi(): DataExtractorApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* This code is used to detect if the API has not been initialized yet.
|
||||
* @internal
|
||||
*/
|
||||
function selfContainedIsDataExtractorApiInitialized(): boolean {
|
||||
const globalObj =
|
||||
typeof window === "object" ? (window as any) : (global as any);
|
||||
const key: typeof apiKey = "@hediet/data-extractor-api/v2";
|
||||
let api: DataExtractorApi | undefined = globalObj[key];
|
||||
return api !== undefined;
|
||||
}
|
||||
export const ApiHasNotBeenInitializedCode = "EgH0cybXij1jYUozyakO" as const;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
|
||||
const globalObj =
|
||||
typeof window === "object" ? (window as any) : (global as any);
|
||||
const key: typeof apiKey = "@hediet/data-extractor-api/v2";
|
||||
let api: DataExtractorApi | undefined = globalObj[key];
|
||||
if (!api) {
|
||||
throw new Error(`Data Extractor API has not been initialized.`);
|
||||
}
|
||||
return api;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
|
||||
const globalObj =
|
||||
typeof window === "object" ? (window as any) : (global as any);
|
||||
const key: typeof apiKey = "@hediet/data-extractor-api/v3";
|
||||
let api: DataExtractorApi | undefined = globalObj[key];
|
||||
if (!api) {
|
||||
const code: typeof ApiHasNotBeenInitializedCode =
|
||||
"EgH0cybXij1jYUozyakO";
|
||||
throw new Error(
|
||||
`Data Extractor API has not been initialized. Code: ${code}`
|
||||
);
|
||||
}
|
||||
return api;
|
||||
}
|
||||
|
||||
export function installHelpers(): void {
|
||||
const globalObj =
|
||||
typeof window === "object" ? (window as any) : (global as any);
|
||||
const globalObj = getGlobal();
|
||||
// `hediet` as prefix to avoid name collision (I own `hediet.de`).
|
||||
globalObj["hedietDbgVis"] = { ...helpers, ...globalHelpers };
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ export function cache<T>(
|
||||
let resultFn: () => any;
|
||||
let key: string;
|
||||
if (typeof expression === "string") {
|
||||
const evalFn = DataExtractorApiImpl.lastEvalFn!;
|
||||
resultFn = () => evalFn(expression);
|
||||
const context = DataExtractorApiImpl.lastContext!;
|
||||
resultFn = () => context.evalFn(expression);
|
||||
key = JSON.stringify({ expr: expression, id });
|
||||
} else {
|
||||
resultFn = () => expression();
|
||||
|
||||
@@ -2,12 +2,22 @@ import { DataExtractorApiImpl } from "../api";
|
||||
|
||||
export function find(predicate: (obj: unknown) => boolean): unknown {
|
||||
const processed = new Set();
|
||||
if (!DataExtractorApiImpl.lastVariablesInScope) {
|
||||
throw new Error("No variables in scope!");
|
||||
if (!DataExtractorApiImpl.lastContext) {
|
||||
throw new Error("No data extractor context!");
|
||||
}
|
||||
|
||||
const values = Object.values(DataExtractorApiImpl.lastVariablesInScope);
|
||||
const queue = [...values];
|
||||
const values = Object.values(
|
||||
DataExtractorApiImpl.lastContext.variablesInScope
|
||||
);
|
||||
const queue = [
|
||||
...values.map((v) => {
|
||||
try {
|
||||
return v();
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
let i = 10000;
|
||||
while (i > 0) {
|
||||
|
||||
@@ -30,17 +30,17 @@ export function tryEval(
|
||||
obj: Record<string, string> | string[] | string
|
||||
): Record<string, unknown> | unknown {
|
||||
const result: Record<string, unknown> = {};
|
||||
const evalFn = DataExtractorApiImpl.lastEvalFn!;
|
||||
const context = DataExtractorApiImpl.lastContext!;
|
||||
if (Array.isArray(obj)) {
|
||||
for (const val of obj) {
|
||||
try {
|
||||
result[val] = evalFn(val);
|
||||
result[val] = context.evalFn(val);
|
||||
} catch (e) {}
|
||||
}
|
||||
} else {
|
||||
for (const [key, val] of Object.entries(obj)) {
|
||||
try {
|
||||
result[key] = evalFn(val);
|
||||
result[key] = context.evalFn(val);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user