* Updates Dependencies
* Uses webpack to bundle runtime library into a single file * Inject the entire file into the Debugee * Register runtime helpers (hediet_dbgVis)
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
[](https://twitter.com/intent/follow?screen_name=hediet_dev)
|
||||
|
||||
A library that helps implementing data extractors for the Debug Visualizer VS Code extension.
|
||||
It will automatically be injected by the extension when the debugger attaches.
|
||||
Compatible with NodeJS and browsers.
|
||||
|
||||
# Installation
|
||||
|
||||
@@ -14,15 +16,10 @@ yarn add @hediet/debug-visualizer-data-extraction
|
||||
|
||||
# Usage
|
||||
|
||||
## `createGraph` / `createGraphFromPointers` Helper
|
||||
## `createGraphFromPointers` Helper
|
||||
|
||||
```ts
|
||||
import {
|
||||
registerDefaultDataExtractors,
|
||||
createGraphFromPointers,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
registerDefaultDataExtractors();
|
||||
import { createGraphFromPointers } from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
setTimeout(() => {
|
||||
new Main().run();
|
||||
@@ -85,14 +82,7 @@ class DoublyLinkedList {
|
||||
## Registering Custom Data Extractors
|
||||
|
||||
```ts
|
||||
import {
|
||||
registerDefaultDataExtractors,
|
||||
getDataExtractorApi,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
// The default data extractors are usually also registered by the VS Code extension.
|
||||
// Registered them manually ensures that they are up to date.
|
||||
registerDefaultDataExtractors();
|
||||
import { getDataExtractorApi } from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
getDataExtractorApi().registerExtractor({
|
||||
id: "my-foo-extractor",
|
||||
|
||||
@@ -22,11 +22,18 @@
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "tsc --watch",
|
||||
"build": "tsc"
|
||||
"dev": "webpack --watch --mode development",
|
||||
"build": "webpack --mode production"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"typescript": "^"
|
||||
"typescript": "^3.7.5",
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/serve-static": "^1.13.3",
|
||||
"@types/node": "^13.7.1",
|
||||
"tslint": "^6.0.0",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"ts-loader": "^6.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { ExtractedData } from "../DataExtractionResult";
|
||||
|
||||
export interface DataExtractor<T extends ExtractedData> {
|
||||
/**
|
||||
* Must be unique among all data extractors.
|
||||
*/
|
||||
id: string;
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector<T>,
|
||||
context: DataExtractorContext
|
||||
): void;
|
||||
}
|
||||
|
||||
export interface ExtractionCollector<T extends ExtractedData> {
|
||||
/**
|
||||
* Suggests a possible extraction.
|
||||
*/
|
||||
addExtraction(extraction: DataExtraction<T>): void;
|
||||
}
|
||||
|
||||
export interface DataExtractorContext {
|
||||
/**
|
||||
* Evaluates an expression in the context of the active stack frame.
|
||||
*/
|
||||
evalFn: <TEval>(expression: string) => TEval;
|
||||
}
|
||||
|
||||
export interface DataExtraction<TData extends ExtractedData> {
|
||||
/**
|
||||
* Higher priorities are preferred.
|
||||
*/
|
||||
priority: number;
|
||||
|
||||
/**
|
||||
* A unique id identifying this extraction among all extractions.
|
||||
* Required to express extraction preferences.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* A user friendly name of this extraction.
|
||||
*/
|
||||
name: string;
|
||||
extractData(): TData;
|
||||
}
|
||||
|
||||
export function getExtractions<T extends ExtractedData>(
|
||||
extractor: DataExtractor<T>,
|
||||
data: unknown,
|
||||
context: DataExtractorContext
|
||||
): DataExtraction<T>[] {
|
||||
const extractions = new Array<DataExtraction<T>>();
|
||||
const collector: ExtractionCollector<T> = {
|
||||
addExtraction(extraction) {
|
||||
extractions.push(extraction);
|
||||
},
|
||||
};
|
||||
extractor.getExtractions(data, collector, context);
|
||||
return extractions;
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
import {
|
||||
ExtractedData,
|
||||
DataExtractionResult,
|
||||
DataExtractorInfo,
|
||||
} from "../DataExtractionResult";
|
||||
import {
|
||||
DataExtractor,
|
||||
DataExtraction,
|
||||
ExtractionCollector,
|
||||
} from "./DataExtractor";
|
||||
|
||||
export interface DataExtractorApi {
|
||||
/**
|
||||
* Registers a single extractor.
|
||||
* @preferExisting if `true`, an existing extractor is not overwritten.
|
||||
*/
|
||||
registerExtractor<TExtractedData extends ExtractedData>(
|
||||
extractor: DataExtractor<TExtractedData>,
|
||||
preferExisting?: boolean
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Registers multiple extractors.
|
||||
* @preferExisting if `true`, an existing extractor is not overwritten.
|
||||
*/
|
||||
registerExtractors(
|
||||
extractors: DataExtractor<ExtractedData>[],
|
||||
preferExisting?: boolean
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Extracts data from `value`.
|
||||
*/
|
||||
getData(
|
||||
value: unknown,
|
||||
evalFn: <T>(expression: string) => T,
|
||||
preferredDataExtractorId: string | undefined
|
||||
): JSONString<DataResult>;
|
||||
}
|
||||
|
||||
export type DataResult =
|
||||
| {
|
||||
kind: "Data";
|
||||
extractionResult: DataExtractionResult;
|
||||
}
|
||||
| { kind: "NoExtractors" }
|
||||
| { kind: "Error"; message: string };
|
||||
|
||||
export interface JSONString<T> extends String {
|
||||
__brand: { json: T };
|
||||
}
|
||||
|
||||
declare const window: any;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function selfContainedInitDataExtractorApi(): boolean {
|
||||
const globalObj =
|
||||
typeof window === "object" ? (window as any) : (global as any);
|
||||
const key = "@hediet/data-extractor/v1";
|
||||
const prefix = key + "::";
|
||||
|
||||
let api: DataExtractorApi | undefined = globalObj[key];
|
||||
if (api) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function toJson<TData>(data: TData): JSONString<TData> {
|
||||
return JSON.stringify(data) as any;
|
||||
}
|
||||
function getExtractors(): DataExtractor<ExtractedData>[] {
|
||||
return Object.entries(globalObj)
|
||||
.filter(([key, value]) => key.startsWith(prefix))
|
||||
.map(([key, value]) => value as DataExtractor<ExtractedData>);
|
||||
}
|
||||
globalObj["$asData"] = (x: unknown) => x;
|
||||
globalObj[key] = api = {
|
||||
registerExtractor(extractor, preferExisting) {
|
||||
const key = prefix + extractor.id;
|
||||
if (preferExisting && key in globalObj) {
|
||||
// don't overwrite existing data extractor
|
||||
return;
|
||||
}
|
||||
globalObj[key] = extractor;
|
||||
},
|
||||
registerExtractors(
|
||||
extractors: DataExtractor<ExtractedData>[],
|
||||
preferExisting
|
||||
) {
|
||||
for (const e of extractors) {
|
||||
this.registerExtractor(e, preferExisting);
|
||||
}
|
||||
},
|
||||
getData(value, evalFn, preferredDataExtractorId) {
|
||||
const extractions = new Array<DataExtraction<ExtractedData>>();
|
||||
const extractionCollector: ExtractionCollector<ExtractedData> = {
|
||||
addExtraction(extraction) {
|
||||
extractions.push(extraction);
|
||||
},
|
||||
};
|
||||
|
||||
for (const e of getExtractors()) {
|
||||
e.getExtractions(value, extractionCollector, { evalFn });
|
||||
}
|
||||
extractions.sort((a, b) => b.priority - a.priority);
|
||||
let usedExtraction = extractions[0];
|
||||
if (!usedExtraction) {
|
||||
return toJson({ kind: "NoExtractors" } as DataResult);
|
||||
}
|
||||
|
||||
if (preferredDataExtractorId) {
|
||||
const preferred = extractions.find(
|
||||
e => e.id === preferredDataExtractorId
|
||||
);
|
||||
if (preferred) {
|
||||
usedExtraction = preferred;
|
||||
}
|
||||
}
|
||||
|
||||
function mapExtractor(
|
||||
e: DataExtraction<ExtractedData>
|
||||
): DataExtractorInfo {
|
||||
return {
|
||||
id: e.id as any,
|
||||
name: e.name,
|
||||
priority: e.priority,
|
||||
};
|
||||
}
|
||||
|
||||
const data = usedExtraction.extractData();
|
||||
return toJson({
|
||||
kind: "Data",
|
||||
extractionResult: {
|
||||
data,
|
||||
usedExtractor: mapExtractor(usedExtraction),
|
||||
availableExtractors: extractions.map(mapExtractor),
|
||||
},
|
||||
} as DataResult);
|
||||
},
|
||||
};
|
||||
|
||||
api.registerExtractor({
|
||||
id: "to-string",
|
||||
getExtractions: (data, collector) => {
|
||||
collector.addExtraction({
|
||||
id: "to-string",
|
||||
name: "To String",
|
||||
priority: 100,
|
||||
extractData() {
|
||||
return {
|
||||
kind: {
|
||||
text: true,
|
||||
},
|
||||
text: "" + data,
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This code is used to detect if the API has not been initialized yet.
|
||||
* @internal
|
||||
*/
|
||||
export const ApiHasNotBeenInitializedCode = "EgH0cybXij1jYUozyakO" as const;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
|
||||
const obj = typeof window === "object" ? (window as any) : (global as any);
|
||||
const key = "@hediet/data-extractor/v1";
|
||||
let api: DataExtractorApi | undefined = obj[key];
|
||||
if (!api) {
|
||||
const code: typeof ApiHasNotBeenInitializedCode =
|
||||
"EgH0cybXij1jYUozyakO";
|
||||
throw new Error(
|
||||
`Data Extractor API has not been initialized. Code: ${code}`
|
||||
);
|
||||
}
|
||||
return api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data extractor API.
|
||||
*/
|
||||
export function getDataExtractorApi(): DataExtractorApi {
|
||||
selfContainedInitDataExtractorApi();
|
||||
return selfContainedGetInitializedDataExtractorApi();
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export { TypeScriptAstDataExtractor } from "./TypeScriptDataExtractors";
|
||||
export { AsIsDataExtractor } from "./AsIsDataExtractor";
|
||||
export { GetDebugVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor";
|
||||
|
||||
export { registerDefaultDataExtractors } from "./registerDefaultDataExtractors";
|
||||
89
data-extraction/src/js/api/DataExtractorApi.ts
Normal file
89
data-extraction/src/js/api/DataExtractorApi.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
ExtractedData,
|
||||
DataExtractionResult,
|
||||
} from "../../DataExtractionResult";
|
||||
|
||||
export interface DataExtractorApi {
|
||||
/**
|
||||
* Registers a single extractor.
|
||||
*/
|
||||
registerExtractor<TExtractedData extends ExtractedData>(
|
||||
extractor: DataExtractor<TExtractedData>
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Registers multiple extractors.
|
||||
*/
|
||||
registerExtractors(extractors: DataExtractor<ExtractedData>[]): void;
|
||||
|
||||
/**
|
||||
* Extracts data from `value`.
|
||||
*/
|
||||
getData(
|
||||
value: unknown,
|
||||
evalFn: <T>(expression: string) => T,
|
||||
preferredDataExtractorId: string | undefined
|
||||
): JSONString<DataResult>;
|
||||
|
||||
/**
|
||||
* Registers all default (built-in) extractors.
|
||||
* @preferExisting if `true`, existing extractors with the same id are not overwritten.
|
||||
*/
|
||||
registerDefaultExtractors(preferExisting?: boolean): void;
|
||||
}
|
||||
|
||||
export type DataResult =
|
||||
| {
|
||||
kind: "Data";
|
||||
extractionResult: DataExtractionResult;
|
||||
}
|
||||
| { kind: "NoExtractors" }
|
||||
| { kind: "Error"; message: string };
|
||||
|
||||
export interface JSONString<T> extends String {
|
||||
__brand: { json: T };
|
||||
}
|
||||
|
||||
export interface DataExtractor<T extends ExtractedData> {
|
||||
/**
|
||||
* Must be unique among all data extractors.
|
||||
*/
|
||||
id: string;
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector<T>,
|
||||
context: DataExtractorContext
|
||||
): void;
|
||||
}
|
||||
|
||||
export interface ExtractionCollector<T extends ExtractedData> {
|
||||
/**
|
||||
* Suggests a possible extraction.
|
||||
*/
|
||||
addExtraction(extraction: DataExtraction<T>): void;
|
||||
}
|
||||
|
||||
export interface DataExtractorContext {
|
||||
/**
|
||||
* Evaluates an expression in the context of the active stack frame.
|
||||
*/
|
||||
evalFn: <TEval>(expression: string) => TEval;
|
||||
}
|
||||
|
||||
export interface DataExtraction<TData extends ExtractedData> {
|
||||
/**
|
||||
* Higher priorities are preferred.
|
||||
*/
|
||||
priority: number;
|
||||
|
||||
/**
|
||||
* A unique id identifying this extraction among all extractions.
|
||||
* Required to express extraction preferences.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* A user friendly name of this extraction.
|
||||
*/
|
||||
name: string;
|
||||
extractData(): TData;
|
||||
}
|
||||
90
data-extraction/src/js/api/DataExtractorApiImpl.ts
Normal file
90
data-extraction/src/js/api/DataExtractorApiImpl.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
DataExtractorApi,
|
||||
DataResult,
|
||||
JSONString,
|
||||
DataExtractor,
|
||||
DataExtraction,
|
||||
ExtractionCollector,
|
||||
} from "./DataExtractorApi";
|
||||
import { ExtractedData, DataExtractorInfo } from "../../DataExtractionResult";
|
||||
import { registerDefaultDataExtractors } from "./default-extractors";
|
||||
|
||||
export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
private readonly extractors = new Map<
|
||||
string,
|
||||
DataExtractor<ExtractedData>
|
||||
>();
|
||||
|
||||
private toJson<TData>(data: TData): JSONString<TData> {
|
||||
return JSON.stringify(data) as any;
|
||||
}
|
||||
|
||||
public registerExtractor<TExtractedData extends ExtractedData>(
|
||||
extractor: DataExtractor<TExtractedData>
|
||||
): void {
|
||||
this.extractors.set(extractor.id, extractor);
|
||||
}
|
||||
|
||||
public registerExtractors(
|
||||
extractors: DataExtractor<ExtractedData>[]
|
||||
): void {
|
||||
for (const e of extractors) {
|
||||
this.registerExtractor(e);
|
||||
}
|
||||
}
|
||||
|
||||
public getData(
|
||||
value: unknown,
|
||||
evalFn: <T>(expression: string) => T,
|
||||
preferredDataExtractorId: string | undefined
|
||||
): JSONString<DataResult> {
|
||||
const extractions = new Array<DataExtraction<ExtractedData>>();
|
||||
const extractionCollector: ExtractionCollector<ExtractedData> = {
|
||||
addExtraction(extraction) {
|
||||
extractions.push(extraction);
|
||||
},
|
||||
};
|
||||
|
||||
for (const e of this.extractors.values()) {
|
||||
e.getExtractions(value, extractionCollector, { evalFn });
|
||||
}
|
||||
extractions.sort((a, b) => b.priority - a.priority);
|
||||
let usedExtraction = extractions[0];
|
||||
if (!usedExtraction) {
|
||||
return this.toJson({ kind: "NoExtractors" } as DataResult);
|
||||
}
|
||||
|
||||
if (preferredDataExtractorId) {
|
||||
const preferred = extractions.find(
|
||||
e => e.id === preferredDataExtractorId
|
||||
);
|
||||
if (preferred) {
|
||||
usedExtraction = preferred;
|
||||
}
|
||||
}
|
||||
|
||||
function mapExtractor(
|
||||
e: DataExtraction<ExtractedData>
|
||||
): DataExtractorInfo {
|
||||
return {
|
||||
id: e.id as any,
|
||||
name: e.name,
|
||||
priority: e.priority,
|
||||
};
|
||||
}
|
||||
|
||||
const data = usedExtraction.extractData();
|
||||
return this.toJson({
|
||||
kind: "Data",
|
||||
extractionResult: {
|
||||
data,
|
||||
usedExtractor: mapExtractor(usedExtraction),
|
||||
availableExtractors: extractions.map(mapExtractor),
|
||||
},
|
||||
} as DataResult);
|
||||
}
|
||||
|
||||
public registerDefaultExtractors(preferExisting: boolean = false): void {
|
||||
registerDefaultDataExtractors(this);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ExtractedData } from "../../DataExtractionResult";
|
||||
import { DataExtractor, ExtractionCollector, DataExtractorContext } from "..";
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../..";
|
||||
|
||||
export class AsIsDataExtractor implements DataExtractor<ExtractedData> {
|
||||
readonly id = "as-is";
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ExtractedData } from "../../DataExtractionResult";
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import {
|
||||
ExtractionCollector,
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractor";
|
||||
} from "../DataExtractorApi";
|
||||
|
||||
export class GetDebugVisualizationDataExtractor
|
||||
implements DataExtractor<ExtractedData> {
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractorApi";
|
||||
|
||||
export class ToStringDataExtractor implements DataExtractor<ExtractedData> {
|
||||
readonly id = "to-string";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<ExtractedData>,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
collector.addExtraction({
|
||||
id: "to-string",
|
||||
name: "To String",
|
||||
priority: 100,
|
||||
extractData() {
|
||||
return {
|
||||
kind: {
|
||||
text: true,
|
||||
},
|
||||
text: "" + data,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as ts from "typescript";
|
||||
import * as ts from "typescript"; // Only compile-time import!
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractor";
|
||||
import { CommonDataTypes } from "../../CommonDataTypes";
|
||||
} from "../DataExtractorApi";
|
||||
import { CommonDataTypes } from "../../../CommonDataTypes";
|
||||
|
||||
// This class is self contained and can be injected into both nodejs and browser environments.
|
||||
export class TypeScriptAstDataExtractor
|
||||
1
data-extraction/src/js/api/default-extractors/index.ts
Normal file
1
data-extraction/src/js/api/default-extractors/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { registerDefaultDataExtractors } from "./registerDefaultDataExtractors";
|
||||
@@ -1,18 +1,19 @@
|
||||
import { getDataExtractorApi } from "../DataExtractorApi";
|
||||
import { DataExtractorApi } from "../DataExtractorApi";
|
||||
import { TypeScriptAstDataExtractor } from "./TypeScriptDataExtractors";
|
||||
import { AsIsDataExtractor } from "./AsIsDataExtractor";
|
||||
import { GetDebugVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor";
|
||||
import { ToStringDataExtractor } from "./ToStringExtractor";
|
||||
|
||||
/**
|
||||
* The default data extractors should be registered by vs code anyways.
|
||||
* This however ensures that the most up to date extractors are registered.
|
||||
* The default data extractors should be registered by VS Code automatically.
|
||||
* Registering them manually ensures that they are up to date.
|
||||
*/
|
||||
export function registerDefaultDataExtractors() {
|
||||
const api = getDataExtractorApi();
|
||||
export function registerDefaultDataExtractors(api: DataExtractorApi) {
|
||||
for (const item of [
|
||||
new TypeScriptAstDataExtractor(),
|
||||
new AsIsDataExtractor(),
|
||||
new GetDebugVisualizationDataExtractor(),
|
||||
new ToStringDataExtractor(),
|
||||
]) {
|
||||
api.registerExtractor(item);
|
||||
}
|
||||
2
data-extraction/src/js/api/index.ts
Normal file
2
data-extraction/src/js/api/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./DataExtractorApi";
|
||||
export * from "./injection";
|
||||
73
data-extraction/src/js/api/injection.ts
Normal file
73
data-extraction/src/js/api/injection.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import * as fs from "fs";
|
||||
import { DataExtractorApi } from "./DataExtractorApi";
|
||||
import { DataExtractorApiImpl } from "./DataExtractorApiImpl";
|
||||
import * as helpers from "../helpers";
|
||||
import * as globalHelpers from "../global-helpers";
|
||||
|
||||
/**
|
||||
* Returns standalone JS code representing an expression that initializes the data extraction API.
|
||||
* This expression returns nothing.
|
||||
* This function is called in the VS Code extension, the expression is evaluated in the debugee.
|
||||
*/
|
||||
export function getExpressionToInitializeDataExtractorApi(): string {
|
||||
const _fs = require("fs") as typeof fs;
|
||||
const moduleSrc = _fs.readFileSync(__filename, { encoding: "utf8" });
|
||||
return `((function () {
|
||||
let module = {};
|
||||
${moduleSrc}
|
||||
module.exports.installHelpers();
|
||||
return module.exports.getDataExtractorApi();
|
||||
})())`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns standalone JS code representing an expression returns the data extraction API.
|
||||
* This expression returns an object of type `DataExtractorApi`.
|
||||
* If the API must be initialized with `getExpressionToInitializeDataExtractorApi()`,
|
||||
* it throws an exception containing the text of `ApiHasNotBeenInitializedCode`.
|
||||
* This function is called in the VS Code extension, the expression is evaluated in the debugee.
|
||||
*/
|
||||
export function getExpressionForDataExtractorApi(): string {
|
||||
return `((${selfContainedGetInitializedDataExtractorApi.toString()})())`;
|
||||
}
|
||||
|
||||
const apiKey = "@hediet/data-extractor-api/v2";
|
||||
|
||||
export function getDataExtractorApi(): DataExtractorApi {
|
||||
const globalObj =
|
||||
typeof window === "object" ? (window as any) : (global as any);
|
||||
if (!globalObj[apiKey]) {
|
||||
globalObj[apiKey] = new DataExtractorApiImpl();
|
||||
}
|
||||
return globalObj[apiKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* This code is used to detect if the API has not been initialized yet.
|
||||
* @internal
|
||||
*/
|
||||
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) {
|
||||
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);
|
||||
globalObj["hediet_dbgVis"] = { ...helpers, ...globalHelpers };
|
||||
}
|
||||
1
data-extraction/src/js/global-helpers.ts
Normal file
1
data-extraction/src/js/global-helpers.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { getDataExtractorApi as getApi } from "./api/injection";
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
CommonDataTypes,
|
||||
EdgeGraphData,
|
||||
NodeGraphData,
|
||||
} from "../CommonDataTypes";
|
||||
} from "../../CommonDataTypes";
|
||||
|
||||
/**
|
||||
* Given a list of roots, it creates a graph by following their edges recursively.
|
||||
@@ -66,46 +66,3 @@ export function createGraph<T>(
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a labeled list of roots, it creates a graph by following their edges recursively.
|
||||
* Tracks cycles.
|
||||
*/
|
||||
export function createGraphFromPointers<T>(
|
||||
roots: Record<string, T | undefined | null>,
|
||||
infoSelector: (
|
||||
item: T
|
||||
) => {
|
||||
id?: string | number;
|
||||
edges: ({ to: T } & Omit<EdgeGraphData, "from" | "to">)[];
|
||||
} & Omit<NodeGraphData, "id">
|
||||
): CommonDataTypes.Graph {
|
||||
const marker = {};
|
||||
|
||||
interface Pointer {
|
||||
marker: {};
|
||||
name: string;
|
||||
value: T | null | undefined;
|
||||
}
|
||||
|
||||
const items = Object.entries(roots).map<Pointer>(([name, value]) => ({
|
||||
marker,
|
||||
name,
|
||||
value,
|
||||
}));
|
||||
|
||||
return createGraph<T | Pointer>(items, item => {
|
||||
if ("marker" in item && item["marker"] === marker) {
|
||||
return {
|
||||
id: "label____" + item.name,
|
||||
color: "orange",
|
||||
label: item.name,
|
||||
edges: [{ to: item.value!, color: "orange", label: "" }].filter(
|
||||
t => !!t.to
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return infoSelector(item as T);
|
||||
}
|
||||
});
|
||||
}
|
||||
45
data-extraction/src/js/helpers/createGraphFromPointers.ts
Normal file
45
data-extraction/src/js/helpers/createGraphFromPointers.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { EdgeGraphData, NodeGraphData, CommonDataTypes } from "../..";
|
||||
import { createGraph } from ".";
|
||||
|
||||
/**
|
||||
* Given a labeled list of roots, it creates a graph by following their edges recursively.
|
||||
* Tracks cycles.
|
||||
*/
|
||||
export function createGraphFromPointers<T>(
|
||||
roots: Record<string, T | undefined | null>,
|
||||
infoSelector: (
|
||||
item: T
|
||||
) => {
|
||||
id?: string | number;
|
||||
edges: ({ to: T } & Omit<EdgeGraphData, "from" | "to">)[];
|
||||
} & Omit<NodeGraphData, "id">
|
||||
): CommonDataTypes.Graph {
|
||||
const marker = {};
|
||||
|
||||
interface Pointer {
|
||||
marker: {};
|
||||
name: string;
|
||||
value: T | null | undefined;
|
||||
}
|
||||
|
||||
const items = Object.entries(roots).map<Pointer>(([name, value]) => ({
|
||||
marker,
|
||||
name,
|
||||
value,
|
||||
}));
|
||||
|
||||
return createGraph<T | Pointer>(items, item => {
|
||||
if ("marker" in item && item["marker"] === marker) {
|
||||
return {
|
||||
id: "label____" + item.name,
|
||||
color: "orange",
|
||||
label: item.name,
|
||||
edges: [{ to: item.value!, color: "orange", label: "" }].filter(
|
||||
t => !!t.to
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return infoSelector(item as T);
|
||||
}
|
||||
});
|
||||
}
|
||||
2
data-extraction/src/js/helpers/index.ts
Normal file
2
data-extraction/src/js/helpers/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./createGraph";
|
||||
export * from "./createGraphFromPointers";
|
||||
@@ -1,4 +1,2 @@
|
||||
export * from "./Extractors";
|
||||
export * from "./DataExtractor";
|
||||
export * from "./DataExtractorApi";
|
||||
export * from "./createGraph";
|
||||
export * from "./api";
|
||||
export * from "./helpers";
|
||||
|
||||
2
data-extraction/webpack.config.js
Normal file
2
data-extraction/webpack.config.js
Normal file
@@ -0,0 +1,2 @@
|
||||
require("ts-node").register();
|
||||
module.exports = require("./webpack.config.ts");
|
||||
43
data-extraction/webpack.config.ts
Normal file
43
data-extraction/webpack.config.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import * as webpack from "webpack";
|
||||
import path = require("path");
|
||||
import { CleanWebpackPlugin } from "clean-webpack-plugin";
|
||||
|
||||
const r = (file: string) => path.resolve(__dirname, file);
|
||||
|
||||
module.exports = {
|
||||
target: "node",
|
||||
entry: r("./src/index"),
|
||||
output: {
|
||||
path: r("./dist"),
|
||||
filename: "index.js",
|
||||
libraryTarget: "commonjs2",
|
||||
devtoolModuleFilenameTemplate: "../[resource-path]",
|
||||
},
|
||||
devtool: "source-map",
|
||||
resolve: {
|
||||
extensions: [".ts", ".js"],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: "ts-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin({
|
||||
protectWebpackAssets: true,
|
||||
cleanAfterEveryBuildPatterns: ["!**/*.d.ts", "!**/*.d.ts.map"],
|
||||
}),
|
||||
],
|
||||
} as webpack.Configuration;
|
||||
@@ -1,10 +1,7 @@
|
||||
import {
|
||||
registerDefaultDataExtractors,
|
||||
getDataExtractorApi,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { getDataExtractorApi } from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
// Registers all existing extractors.
|
||||
registerDefaultDataExtractors();
|
||||
getDataExtractorApi().registerDefaultExtractors();
|
||||
|
||||
getDataExtractorApi().registerExtractor({
|
||||
id: "my-extractor",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
registerDefaultDataExtractors,
|
||||
getDataExtractorApi,
|
||||
createGraphFromPointers,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
registerDefaultDataExtractors();
|
||||
getDataExtractorApi().registerDefaultExtractors();
|
||||
|
||||
setTimeout(() => {
|
||||
new Main().run();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as ts from "typescript";
|
||||
import { registerDefaultDataExtractors } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { getDataExtractorApi } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { MockLanguageServiceHost } from "./MockLanguageServiceHost";
|
||||
|
||||
registerDefaultDataExtractors();
|
||||
// Registers all existing extractors.
|
||||
getDataExtractorApi().registerDefaultExtractors();
|
||||
|
||||
setTimeout(() => {
|
||||
new Main().run();
|
||||
|
||||
@@ -64,13 +64,14 @@
|
||||
"crypto-random-string": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/serve-static": "^1.13.2",
|
||||
"@types/node": "^12.6.8",
|
||||
"@types/vscode": "1.35.0",
|
||||
"tslint": "^5.18.0",
|
||||
"typescript": "^3.5.3",
|
||||
"webpack": "^4.38.0",
|
||||
"webpack-cli": "^3.3.6"
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/serve-static": "^1.13.3",
|
||||
"@types/node": "^13.7.1",
|
||||
"@types/vscode": "1.42.0",
|
||||
"tslint": "^6.0.0",
|
||||
"typescript": "^3.7.5",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"ts-loader": "^6.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,11 @@ import {
|
||||
import { observable, autorun, action } from "mobx";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import {
|
||||
selfContainedGetInitializedDataExtractorApi,
|
||||
getExpressionForDataExtractorApi,
|
||||
DataResult,
|
||||
DataExtractorId,
|
||||
ApiHasNotBeenInitializedCode,
|
||||
selfContainedInitDataExtractorApi,
|
||||
TypeScriptAstDataExtractor,
|
||||
AsIsDataExtractor,
|
||||
GetDebugVisualizationDataExtractor,
|
||||
getExpressionToInitializeDataExtractorApi,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import {
|
||||
DataExtractionState,
|
||||
@@ -100,14 +97,30 @@ export class JsDebuggerSourceImplementation implements JsDataSource {
|
||||
|
||||
try {
|
||||
w._state = { kind: "loading" };
|
||||
const fnSrc = selfContainedGetInitializedDataExtractorApi.toString();
|
||||
|
||||
const preferredExtractor = w.preferredDataExtractor
|
||||
? `"${w.preferredDataExtractor}"`
|
||||
: "undefined";
|
||||
|
||||
const body = `(${fnSrc})().getData(${w.expression}, expr => eval(expr), ${preferredExtractor})`;
|
||||
const expression = `(() => { try { return ${body}; } catch (e) { return JSON.stringify({ kind: "Error", message: e.message, stack: e.stack }); } })()`;
|
||||
const body = `${getExpressionForDataExtractorApi()}.getData(
|
||||
${w.expression},
|
||||
expr => eval(expr),
|
||||
${preferredExtractor}
|
||||
)`;
|
||||
|
||||
const expression = `
|
||||
(() => {
|
||||
try {
|
||||
return ${body};
|
||||
} catch (e) {
|
||||
return JSON.stringify({
|
||||
kind: "Error",
|
||||
message: e.message,
|
||||
stack: e.stack
|
||||
});
|
||||
}
|
||||
})()
|
||||
`;
|
||||
|
||||
const reply = await session.evaluate({
|
||||
expression,
|
||||
@@ -147,24 +160,14 @@ export class JsDebuggerSourceImplementation implements JsDataSource {
|
||||
frameId: number | undefined
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
let expression = `(${selfContainedInitDataExtractorApi.toString()})();`;
|
||||
// prefer existing is true, so that manually registered (possibly newer) extractors are not overwritten.
|
||||
const expression = `${getExpressionToInitializeDataExtractorApi()}.registerDefaultExtractors(true);`;
|
||||
|
||||
const es = [
|
||||
TypeScriptAstDataExtractor,
|
||||
AsIsDataExtractor,
|
||||
GetDebugVisualizationDataExtractor,
|
||||
].map(e => `new (${e.toString()})()`);
|
||||
expression += `(${selfContainedGetInitializedDataExtractorApi.toString()})()`;
|
||||
// prefer existing is true, so that manually registered extractors are not overwritten.
|
||||
expression += `.registerExtractors([${es.join(",")}], true)`;
|
||||
|
||||
const reply = await session.evaluate({
|
||||
await session.evaluate({
|
||||
expression,
|
||||
frameId,
|
||||
});
|
||||
if (reply.result === "true") {
|
||||
// register extractors
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
|
||||
@@ -28,26 +28,26 @@
|
||||
"vis-data": "^6.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/classnames": "^2.2.8",
|
||||
"@types/react": "^16.8.21",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/react": "^16.9.19",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/line-column": "^1.0.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^3.0.0",
|
||||
"file-loader": "^4.0.0",
|
||||
"fork-ts-checker-webpack-plugin": "^1.4.2",
|
||||
"css-loader": "^3.4.2",
|
||||
"file-loader": "^5.0.2",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.3",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"monaco-editor-webpack-plugin": "^1.7.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"raw-loader": "^3.0.0",
|
||||
"sass": "^1.20.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"ts-loader": "^6.0.3",
|
||||
"ts-node": "^8.3.0",
|
||||
"typescript": "^3.5.2",
|
||||
"webpack": "^4.35.0",
|
||||
"webpack-cli": "^3.3.4",
|
||||
"webpack-dev-server": "^3.7.2"
|
||||
"monaco-editor-webpack-plugin": "^1.8.2",
|
||||
"node-sass": "^4.13.1",
|
||||
"raw-loader": "^4.0.0",
|
||||
"sass": "^1.25.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"style-loader": "^1.1.3",
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^8.6.2",
|
||||
"typescript": "^3.7.5",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.10.3"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user