Fixes issues with global.

This commit is contained in:
Henning Dieterichs
2021-06-22 09:59:50 +02:00
parent 683f1e1f76
commit cd88168211
15 changed files with 281 additions and 240 deletions

View 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");
}

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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 };
}

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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) {}
}
}