Minor refactoring of @hediet/debug-visualizer-data-extraction's API. Improved documentation.
This commit is contained in:
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Henning Dieterichs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
126
data-extraction/README.md
Normal file
126
data-extraction/README.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# @hediet/debug-visualizer-data-extraction
|
||||
|
||||
[](https://twitter.com/intent/follow?screen_name=hediet_dev)
|
||||
|
||||
A library that helps implementing data extractors for the Debug Visualizer VS Code extension.
|
||||
|
||||
# Installation
|
||||
|
||||
Use the following command to install the library using yarn:
|
||||
|
||||
```
|
||||
yarn add @hediet/debug-visualizer-data-extraction
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
## `createGraph` / `createGraphFromPointers` Helper
|
||||
|
||||
```ts
|
||||
import {
|
||||
registerDefaultDataExtractors,
|
||||
createGraphFromPointers,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
registerDefaultDataExtractors();
|
||||
|
||||
setTimeout(() => {
|
||||
new Main().run();
|
||||
}, 0);
|
||||
|
||||
class Main {
|
||||
run() {
|
||||
const list = new DoublyLinkedList("1");
|
||||
list.setNext(new DoublyLinkedList("2"));
|
||||
list.next!.setNext(new DoublyLinkedList("3"));
|
||||
list.next!.next!.setNext(new DoublyLinkedList("4"));
|
||||
|
||||
// Watch `visualize()` with the Debug Visualizer Extension for VS Code!
|
||||
const visualize = () =>
|
||||
// Returns `CommonDataTypes.Graph` data which can be visualized by
|
||||
// either the vis.js or the graphviz visualizer.
|
||||
createGraphFromPointers({ list, last, cur }, i => ({
|
||||
id: i.id,
|
||||
label: i.name,
|
||||
color: finished.has(i) ? "lime" : undefined,
|
||||
edges: [
|
||||
{ to: i.next!, label: "next" },
|
||||
{ to: i.prev!, label: "prev", color: "lightgray" },
|
||||
].filter(r => !!r.to),
|
||||
}));
|
||||
|
||||
const finished = new Set();
|
||||
var cur: DoublyLinkedList | undefined = list;
|
||||
// Reverses `list`. Finished nodes have correct pointers,
|
||||
// their next node is also finished.
|
||||
var last: DoublyLinkedList | undefined = undefined;
|
||||
while (cur) {
|
||||
cur.prev = cur.next;
|
||||
cur.next = last;
|
||||
finished.add(cur);
|
||||
last = cur;
|
||||
cur = cur.prev;
|
||||
}
|
||||
console.log("finished");
|
||||
}
|
||||
}
|
||||
|
||||
let id = 0;
|
||||
class DoublyLinkedList {
|
||||
public readonly id = (id++).toString();
|
||||
constructor(public name: string) {}
|
||||
|
||||
next: DoublyLinkedList | undefined;
|
||||
prev: DoublyLinkedList | undefined;
|
||||
|
||||
public setNext(val: DoublyLinkedList): void {
|
||||
val.prev = this;
|
||||
this.next = val;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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();
|
||||
|
||||
getDataExtractorApi().registerExtractor({
|
||||
id: "my-foo-extractor",
|
||||
getExtractions: (data, collector) => {
|
||||
if (data instanceof Foo) {
|
||||
collector.addExtraction({
|
||||
id: "my-foo-extraction",
|
||||
name: "My Foo Extraction",
|
||||
priority: 2000,
|
||||
extractData: () => ({ kind: { text: true }, text: "Foo" }),
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
new Main().run();
|
||||
}, 0);
|
||||
|
||||
class Foo {}
|
||||
|
||||
class Main {
|
||||
run() {
|
||||
const f = new Foo();
|
||||
// if `f` is watched by the Debug Visualizer,
|
||||
// `my-foo-extractor` will provide the data for the visualizers.
|
||||
// See `CommonDataTypes` for data types that have built in visualizers.
|
||||
debugger;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -25,5 +25,8 @@
|
||||
"dev": "tsc --watch",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {}
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"typescript": "^"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { ExtractedData } from "./DataExtractionResult";
|
||||
|
||||
export type CommonDataType =
|
||||
| CommonDataTypes.Text
|
||||
| CommonDataTypes.Svg
|
||||
| CommonDataTypes.Html
|
||||
| CommonDataTypes.DotGraph
|
||||
| CommonDataTypes.Tree
|
||||
| CommonDataTypes.Ast
|
||||
| CommonDataTypes.Graph;
|
||||
|
||||
export function isCommonDataType<T>(
|
||||
data: ExtractedData,
|
||||
kind: T
|
||||
@@ -14,15 +23,6 @@ export function isCommonDataType<T>(
|
||||
|
||||
type Narrow<T, TKind> = T extends TKind ? T : never;
|
||||
|
||||
export type CommonDataType =
|
||||
| CommonDataTypes.Text
|
||||
| CommonDataTypes.Svg
|
||||
| CommonDataTypes.Html
|
||||
| CommonDataTypes.DotGraph
|
||||
| CommonDataTypes.Tree
|
||||
| CommonDataTypes.Ast
|
||||
| CommonDataTypes.Graph;
|
||||
|
||||
export module CommonDataTypes {
|
||||
export interface Text {
|
||||
kind: { text: true };
|
||||
|
||||
@@ -17,4 +17,4 @@ export type DataExtractorInfo = {
|
||||
|
||||
export type DataExtractorId = {
|
||||
__brand: "DataExtractorId";
|
||||
};
|
||||
} & string;
|
||||
|
||||
@@ -1,22 +1,45 @@
|
||||
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>,
|
||||
evalFn: <TEval>(expression: string) => TEval
|
||||
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> {
|
||||
// the higher the better
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -24,7 +47,7 @@ export interface DataExtraction<TData extends ExtractedData> {
|
||||
export function getExtractions<T extends ExtractedData>(
|
||||
extractor: DataExtractor<T>,
|
||||
data: unknown,
|
||||
evalFn: <T>(expression: string) => T
|
||||
context: DataExtractorContext
|
||||
): DataExtraction<T>[] {
|
||||
const extractions = new Array<DataExtraction<T>>();
|
||||
const collector: ExtractionCollector<T> = {
|
||||
@@ -32,6 +55,6 @@ export function getExtractions<T extends ExtractedData>(
|
||||
extractions.push(extraction);
|
||||
},
|
||||
};
|
||||
extractor.getExtractions(data, collector, evalFn);
|
||||
extractor.getExtractions(data, collector, context);
|
||||
return extractions;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,35 @@ import {
|
||||
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";
|
||||
@@ -21,26 +50,18 @@ export interface JSONString<T> extends String {
|
||||
__brand: { json: T };
|
||||
}
|
||||
|
||||
export interface DataExtractorApi {
|
||||
registerExtractor<TExtractedData extends ExtractedData>(
|
||||
extractor: DataExtractor<TExtractedData>
|
||||
): void;
|
||||
registerExtractors(extractors: DataExtractor<ExtractedData>[]): void;
|
||||
getData(
|
||||
value: unknown,
|
||||
evalFn: <T>(expression: string) => T,
|
||||
preferredDataExtractorId: string | undefined
|
||||
): JSONString<DataResult>;
|
||||
}
|
||||
|
||||
declare const window: any;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function selfContainedInitDataExtractorApi(): boolean {
|
||||
const obj = typeof window === "object" ? (window as any) : (global as any);
|
||||
const globalObj =
|
||||
typeof window === "object" ? (window as any) : (global as any);
|
||||
const key = "@hediet/data-extractor/v1";
|
||||
const prefix = key + "::";
|
||||
|
||||
let api: DataExtractorApi | undefined = obj[key];
|
||||
let api: DataExtractorApi | undefined = globalObj[key];
|
||||
if (api) {
|
||||
return false;
|
||||
}
|
||||
@@ -49,29 +70,38 @@ export function selfContainedInitDataExtractorApi(): boolean {
|
||||
return JSON.stringify(data) as any;
|
||||
}
|
||||
function getExtractors(): DataExtractor<ExtractedData>[] {
|
||||
return Object.entries(obj)
|
||||
return Object.entries(globalObj)
|
||||
.filter(([key, value]) => key.startsWith(prefix))
|
||||
.map(([key, value]) => value as DataExtractor<ExtractedData>);
|
||||
}
|
||||
obj["$asData"] = (x: unknown) => x;
|
||||
obj[key] = api = {
|
||||
registerExtractor(extractor) {
|
||||
obj[prefix + extractor.id] = extractor;
|
||||
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>[]) {
|
||||
registerExtractors(
|
||||
extractors: DataExtractor<ExtractedData>[],
|
||||
preferExisting
|
||||
) {
|
||||
for (const e of extractors) {
|
||||
this.registerExtractor(e);
|
||||
this.registerExtractor(e, preferExisting);
|
||||
}
|
||||
},
|
||||
getData(value, evalFn, preferredDataExtractorId) {
|
||||
const extractions = new Array<DataExtraction<ExtractedData>>();
|
||||
const collector: ExtractionCollector<ExtractedData> = {
|
||||
const extractionCollector: ExtractionCollector<ExtractedData> = {
|
||||
addExtraction(extraction) {
|
||||
extractions.push(extraction);
|
||||
},
|
||||
};
|
||||
|
||||
for (const e of getExtractors()) {
|
||||
e.getExtractions(value, collector, evalFn);
|
||||
e.getExtractions(value, extractionCollector, { evalFn });
|
||||
}
|
||||
extractions.sort((a, b) => b.priority - a.priority);
|
||||
let usedExtraction = extractions[0];
|
||||
@@ -111,10 +141,10 @@ export function selfContainedInitDataExtractorApi(): boolean {
|
||||
};
|
||||
|
||||
api.registerExtractor({
|
||||
id: "toString",
|
||||
getExtractions(data, collector) {
|
||||
id: "to-string",
|
||||
getExtractions: (data, collector) => {
|
||||
collector.addExtraction({
|
||||
id: "toString",
|
||||
id: "to-string",
|
||||
name: "To String",
|
||||
priority: 100,
|
||||
extractData() {
|
||||
@@ -131,8 +161,15 @@ export function selfContainedInitDataExtractorApi(): boolean {
|
||||
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";
|
||||
@@ -147,6 +184,9 @@ export function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi
|
||||
return api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data extractor API.
|
||||
*/
|
||||
export function getDataExtractorApi(): DataExtractorApi {
|
||||
selfContainedInitDataExtractorApi();
|
||||
return selfContainedGetInitializedDataExtractorApi();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { ExtractedData } from "../../DataExtractionResult";
|
||||
import { DataExtractor, ExtractionCollector } from "..";
|
||||
import { DataExtractor, ExtractionCollector, DataExtractorContext } from "..";
|
||||
|
||||
export class AsIsDataExtractor implements DataExtractor<ExtractedData> {
|
||||
readonly id = "as-is";
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector<ExtractedData>,
|
||||
evalFn: <TEval>(expression: string) => TEval
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (typeof data !== "object" || !data || !("kind" in data)) {
|
||||
return;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { ExtractedData } from "../../DataExtractionResult";
|
||||
import { DataExtractor, ExtractionCollector } from "..";
|
||||
import {
|
||||
ExtractionCollector,
|
||||
DataExtractor,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractor";
|
||||
|
||||
export class GetDebugVisualizationDataExtractor
|
||||
implements DataExtractor<ExtractedData> {
|
||||
readonly id = "get-debug-visualization";
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector<ExtractedData>,
|
||||
evalFn: <TEval>(expression: string) => TEval
|
||||
collector: ExtractionCollector<ExtractedData>,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (
|
||||
typeof data !== "object" ||
|
||||
@@ -17,10 +21,10 @@ export class GetDebugVisualizationDataExtractor
|
||||
return;
|
||||
}
|
||||
|
||||
extractionCollector.addExtraction({
|
||||
collector.addExtraction({
|
||||
id: this.id,
|
||||
name: "Use Method 'getDebugVisualization'",
|
||||
priority: 500,
|
||||
priority: 600,
|
||||
extractData() {
|
||||
return (data as any).getDebugVisualization();
|
||||
},
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import * as ts from "typescript";
|
||||
import { DataExtractor, ExtractionCollector } from "../DataExtractor";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractor";
|
||||
import { CommonDataTypes } from "../../CommonDataTypes";
|
||||
|
||||
// This class is self contained and can be injected into both nodejs and browser environments.
|
||||
export class TypeScriptAstDataExtractor
|
||||
implements DataExtractor<CommonDataTypes.Ast> {
|
||||
readonly id = "TypeScriptAst";
|
||||
readonly id = "typescript-ast";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<CommonDataTypes.Ast>,
|
||||
evalFn: <TEval>(expression: string) => TEval
|
||||
{ evalFn }: DataExtractorContext
|
||||
): void {
|
||||
if (!data) {
|
||||
return;
|
||||
|
||||
@@ -1,21 +1,5 @@
|
||||
import { getDataExtractorApi } from "../DataExtractorApi";
|
||||
import { TypeScriptAstDataExtractor } from "./TypeScriptDataExtractors";
|
||||
import { AsIsDataExtractor } from "./AsIsDataExtractor";
|
||||
import { GetDebugVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor";
|
||||
export { TypeScriptAstDataExtractor } from "./TypeScriptDataExtractors";
|
||||
export { AsIsDataExtractor } from "./AsIsDataExtractor";
|
||||
export { GetDebugVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor";
|
||||
|
||||
export {
|
||||
AsIsDataExtractor,
|
||||
TypeScriptAstDataExtractor,
|
||||
GetDebugVisualizationDataExtractor,
|
||||
};
|
||||
|
||||
export function registerAll() {
|
||||
const api = getDataExtractorApi();
|
||||
for (const item of [
|
||||
new TypeScriptAstDataExtractor(),
|
||||
new AsIsDataExtractor(),
|
||||
new GetDebugVisualizationDataExtractor(),
|
||||
]) {
|
||||
api.registerExtractor(item);
|
||||
}
|
||||
}
|
||||
export { registerDefaultDataExtractors } from "./registerDefaultDataExtractors";
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { getDataExtractorApi } from "../DataExtractorApi";
|
||||
import { TypeScriptAstDataExtractor } from "./TypeScriptDataExtractors";
|
||||
import { AsIsDataExtractor } from "./AsIsDataExtractor";
|
||||
import { GetDebugVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor";
|
||||
|
||||
/**
|
||||
* The default data extractors should be registered by vs code anyways.
|
||||
* This however ensures that the most up to date extractors are registered.
|
||||
*/
|
||||
export function registerDefaultDataExtractors() {
|
||||
const api = getDataExtractorApi();
|
||||
for (const item of [
|
||||
new TypeScriptAstDataExtractor(),
|
||||
new AsIsDataExtractor(),
|
||||
new GetDebugVisualizationDataExtractor(),
|
||||
]) {
|
||||
api.registerExtractor(item);
|
||||
}
|
||||
}
|
||||
@@ -4,47 +4,10 @@ import {
|
||||
NodeGraphData,
|
||||
} from "../CommonDataTypes";
|
||||
|
||||
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,
|
||||
}));
|
||||
|
||||
const includeLabelInName = false;
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of roots, it creates a graph by following their edges recursively.
|
||||
* Tracks cycles.
|
||||
*/
|
||||
export function createGraph<T>(
|
||||
roots: T[],
|
||||
infoSelector: (
|
||||
@@ -103,3 +66,46 @@ 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
34
example-project/src/demo_custom-data-extractor.ts
Normal file
34
example-project/src/demo_custom-data-extractor.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
registerDefaultDataExtractors,
|
||||
getDataExtractorApi,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
// Registers all existing extractors.
|
||||
registerDefaultDataExtractors();
|
||||
|
||||
getDataExtractorApi().registerExtractor({
|
||||
id: "my-extractor",
|
||||
getExtractions: (data, collector) => {
|
||||
if (data instanceof Foo) {
|
||||
collector.addExtraction({
|
||||
id: "my-extractor",
|
||||
name: "My Extractor",
|
||||
priority: 2000,
|
||||
extractData: () => ({ kind: { text: true }, text: "Foo" }),
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
new Main().run();
|
||||
}, 0);
|
||||
|
||||
class Foo {}
|
||||
|
||||
class Main {
|
||||
run() {
|
||||
const f = new Foo();
|
||||
debugger;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import {
|
||||
registerAll,
|
||||
CommonDataTypes,
|
||||
registerDefaultDataExtractors,
|
||||
createGraphFromPointers,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
registerAll();
|
||||
registerDefaultDataExtractors();
|
||||
|
||||
setTimeout(() => {
|
||||
new Main().run();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as ts from "typescript";
|
||||
import { registerAll } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { registerDefaultDataExtractors } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { MockLanguageServiceHost } from "./MockLanguageServiceHost";
|
||||
|
||||
registerAll();
|
||||
registerDefaultDataExtractors();
|
||||
|
||||
setTimeout(() => {
|
||||
new Main().run();
|
||||
|
||||
@@ -3,3 +3,8 @@
|
||||
## 0.9.0
|
||||
|
||||
- Initial release
|
||||
|
||||
## Current
|
||||
|
||||
- Minor refactoring of `@hediet/debug-visualizer-data-extraction`'s API
|
||||
- Improved documentation.
|
||||
|
||||
@@ -9,12 +9,15 @@ A VS Code extension for visualizing data structures during debugging.
|
||||
## Usage
|
||||
|
||||
After installing this extension, use the command `Open a new Debug Visualizer View` to open a new visualizer view.
|
||||
In this view you can enter an expression that is visualized when debugging your application, e.g.
|
||||
In this view you can enter an expression that is evaluated and visualized while stepping through your application, e.g.
|
||||
|
||||
```ts
|
||||
{ kind: { graph: true }, nodes: [ { id: "1", label: "1" }, { id: "2", label: "2" } ], edges: [{ from: "1", to: "2", label: "edge" }]}
|
||||
```
|
||||
|
||||
You can implement your own functions to extract this debug data from your custom data structures.
|
||||
See [here](../data-extraction/README.md) for documentation of the `createGraphFromPointers` helper.
|
||||
|
||||
## Integrated Visualizers
|
||||
|
||||
Visualizers present data extracted by a _Data Extractor_.
|
||||
@@ -159,6 +162,11 @@ Calls `.getDebugVisualization()` on values and treats the result as direct input
|
||||
Currently, only JavaScript (and thus TypeScript) values can be visualized and only a few visualizations are supported.
|
||||
The architecture is solid enough to support other languages in the future.
|
||||
|
||||
# `@hediet/debug-visualizer-data-extraction`
|
||||
|
||||
A library that provides infrastructure to implement and register custom data extractors.
|
||||
See [README](../data-extraction/README.md) of the library for more info.
|
||||
|
||||
# See Also
|
||||
|
||||
This extension works very well together with my library [`@hediet/node-reload`](https://github.com/hediet/node-reload).
|
||||
|
||||
@@ -3,8 +3,7 @@ import {
|
||||
EvaluationWatcher,
|
||||
EvaluationWatcherOptions,
|
||||
} from "./DataSource";
|
||||
import { observable, autorun } from "mobx";
|
||||
import * as vscode from "vscode";
|
||||
import { observable, autorun, action } from "mobx";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import {
|
||||
selfContainedGetInitializedDataExtractorApi,
|
||||
@@ -156,7 +155,8 @@ export class JsDebuggerSourceImplementation implements JsDataSource {
|
||||
GetDebugVisualizationDataExtractor,
|
||||
].map(e => `new (${e.toString()})()`);
|
||||
expression += `(${selfContainedGetInitializedDataExtractorApi.toString()})()`;
|
||||
expression += `.registerExtractors([${es.join(",")}])`;
|
||||
// prefer existing is true, so that manually registered extractors are not overwritten.
|
||||
expression += `.registerExtractors([${es.join(",")}], true)`;
|
||||
|
||||
const reply = await session.evaluate({
|
||||
expression,
|
||||
@@ -182,12 +182,13 @@ class ObservableEvaluationWatcher implements EvaluationWatcher {
|
||||
}
|
||||
|
||||
@observable
|
||||
_preferredDataExtractor: DataExtractorId | undefined = undefined;
|
||||
private _preferredDataExtractor: DataExtractorId | undefined = undefined;
|
||||
|
||||
get preferredDataExtractor(): DataExtractorId | undefined {
|
||||
public get preferredDataExtractor(): DataExtractorId | undefined {
|
||||
return this._preferredDataExtractor;
|
||||
}
|
||||
|
||||
@action
|
||||
public setPreferredDataExtractor(id: DataExtractorId | undefined): void {
|
||||
this._preferredDataExtractor = id;
|
||||
this.refresh();
|
||||
|
||||
Reference in New Issue
Block a user