Adds types for bundled visualizations & improves data extractor types.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hediet/debug-visualizer-data-extraction",
|
||||
"description": "A library that helps implementing data extractors for the Debug Visualizer VS Code extension.",
|
||||
"version": "0.12.0",
|
||||
"version": "0.13.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": {
|
||||
|
||||
@@ -1,122 +1,256 @@
|
||||
import { ExtractedData } from "./DataExtractionResult";
|
||||
// This file was created automatically. Do not edit it manually!
|
||||
|
||||
export type CommonDataType =
|
||||
| CommonDataTypes.Text
|
||||
| CommonDataTypes.Svg
|
||||
| CommonDataTypes.Html
|
||||
| CommonDataTypes.DotGraph
|
||||
| CommonDataTypes.Tree
|
||||
| CommonDataTypes.Ast
|
||||
| CommonDataTypes.Graph
|
||||
| CommonDataTypes.Plotly
|
||||
| CommonDataTypes.Grid;
|
||||
export type KnownVisualizationData =
|
||||
| TreeVisualizationData
|
||||
| AstTreeVisualizationData
|
||||
| GraphvizDotVisualizationData
|
||||
| GraphVisualizationData
|
||||
| GraphVisualizationData
|
||||
| GridVisualizationData
|
||||
| ImageVisualizationData
|
||||
| MonacoTextVisualizationData
|
||||
| TableVisualizationData
|
||||
| PlotlyVisualizationData
|
||||
| SimpleTextVisualizationData
|
||||
| SvgVisualizationData;
|
||||
|
||||
export function isCommonDataType<T>(
|
||||
data: ExtractedData,
|
||||
kind: T
|
||||
): data is Narrow<CommonDataType, { kind: T }> {
|
||||
for (const key of Object.keys(kind)) {
|
||||
if (!(key in data.kind)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
export type TreeVisualizationData = {
|
||||
kind: {
|
||||
tree: true;
|
||||
};
|
||||
root: TreeNode;
|
||||
};
|
||||
|
||||
type Narrow<T, TKind> = T extends TKind ? T : never;
|
||||
|
||||
export module CommonDataTypes {
|
||||
export interface Text {
|
||||
kind: { text: true };
|
||||
text: string;
|
||||
mimeType?: string;
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
export interface Graph {
|
||||
kind: { graph: true };
|
||||
nodes: NodeGraphData[];
|
||||
edges: EdgeGraphData[];
|
||||
}
|
||||
|
||||
export interface Svg extends Text {
|
||||
kind: { text: true; svg: true };
|
||||
}
|
||||
|
||||
export interface Html extends Text {
|
||||
kind: { text: true; html: true };
|
||||
}
|
||||
|
||||
export interface DotGraph extends Text {
|
||||
kind: { text: true; dotGraph: true };
|
||||
}
|
||||
|
||||
export interface Tree<TData = unknown> {
|
||||
kind: { tree: true };
|
||||
root: TreeNode<TData>;
|
||||
}
|
||||
|
||||
export interface Grid {
|
||||
kind: { array: true };
|
||||
columnLabels?: { label?: string }[];
|
||||
rows: {
|
||||
label?: string;
|
||||
columns: {
|
||||
content?: string;
|
||||
tag?: string;
|
||||
color?: string;
|
||||
}[];
|
||||
}[];
|
||||
markers?: {
|
||||
id: string;
|
||||
|
||||
row: number;
|
||||
column: number;
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
|
||||
label?: string;
|
||||
color?: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface Ast
|
||||
extends Tree<{
|
||||
position: number;
|
||||
length: number;
|
||||
}>,
|
||||
Text {
|
||||
kind: { text: true; tree: true; ast: true };
|
||||
}
|
||||
|
||||
export interface Plotly {
|
||||
kind: { plotly: true };
|
||||
data: Partial<Plotly.Data>[];
|
||||
}
|
||||
}
|
||||
|
||||
export interface TreeNode<TExtraData> {
|
||||
name: string;
|
||||
children: TreeNode<TExtraData>[];
|
||||
data: TExtraData;
|
||||
id?: string;
|
||||
value?: string;
|
||||
emphasizedValue?: string;
|
||||
export type TreeNode = {
|
||||
/**
|
||||
* The children of this tree-node
|
||||
*/
|
||||
children: TreeNode[];
|
||||
/**
|
||||
* The parts that make up the text of this item
|
||||
*/
|
||||
items: TreeNodeItem[];
|
||||
/**
|
||||
* If a node is selected, the concatenation of all segment values from root to the selected node is shown to the user.
|
||||
*/
|
||||
segment?: string;
|
||||
/**
|
||||
* Marked nodes are highlighted and scrolled into view on every visualization update.
|
||||
*/
|
||||
isMarked?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export interface NodeGraphData {
|
||||
export type TreeNodeItem = {
|
||||
/**
|
||||
* The text to show
|
||||
*/
|
||||
text: string;
|
||||
/**
|
||||
* The style of the text
|
||||
*/
|
||||
emphasis?: "style1" | "style2" | "style3" | string;
|
||||
};
|
||||
|
||||
export type AstTreeVisualizationData = {
|
||||
kind: {
|
||||
ast: true;
|
||||
tree: true;
|
||||
text: true;
|
||||
};
|
||||
root: AstTreeNode;
|
||||
text: string;
|
||||
fileName?: string;
|
||||
};
|
||||
|
||||
export type AstTreeNode = {
|
||||
children: AstTreeNode[];
|
||||
items: AstTreeNodeItem[];
|
||||
segment?: string;
|
||||
isMarked?: boolean;
|
||||
span: {
|
||||
start: number;
|
||||
length: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type AstTreeNodeItem = {
|
||||
text: string;
|
||||
emphasis?: "style1" | "style2" | "style3" | string;
|
||||
};
|
||||
|
||||
export type GraphvizDotVisualizationData = {
|
||||
kind: {
|
||||
dotGraph: true;
|
||||
};
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type GraphVisualizationData = {
|
||||
kind: {
|
||||
graph: true;
|
||||
};
|
||||
nodes: GraphNode[];
|
||||
edges: GraphEdge[];
|
||||
};
|
||||
|
||||
export type GraphNode = {
|
||||
id: string;
|
||||
label?: string;
|
||||
color?: string;
|
||||
shape?: "ellipse" | "box";
|
||||
}
|
||||
};
|
||||
|
||||
export interface EdgeGraphData {
|
||||
export type GraphEdge = {
|
||||
from: string;
|
||||
to: string;
|
||||
label?: string;
|
||||
id?: string;
|
||||
color?: string;
|
||||
dashes?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export type GridVisualizationData = {
|
||||
kind: {
|
||||
grid: true;
|
||||
};
|
||||
columnLabels?: {
|
||||
label?: string;
|
||||
}[];
|
||||
rows: {
|
||||
label?: string;
|
||||
columns: {
|
||||
content?: string;
|
||||
/**
|
||||
* A value to identify this cell. Should be unique.
|
||||
*/
|
||||
tag?: string;
|
||||
color?: string;
|
||||
}[];
|
||||
}[];
|
||||
markers?: {
|
||||
id: string;
|
||||
row: number;
|
||||
column: number;
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
label?: string;
|
||||
color?: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type ImageVisualizationData = {
|
||||
kind: {
|
||||
imagePng: true;
|
||||
};
|
||||
/**
|
||||
* The base 64 encoded PNG representation of the image
|
||||
*/
|
||||
base64Data: string;
|
||||
};
|
||||
|
||||
export type MonacoTextVisualizationData = {
|
||||
kind: {
|
||||
text: true;
|
||||
};
|
||||
/**
|
||||
* The text to show
|
||||
*/
|
||||
text: string;
|
||||
/**
|
||||
* An optional filename that might be used for chosing a syntax highlighter
|
||||
*/
|
||||
fileName?: string;
|
||||
};
|
||||
|
||||
export type TableVisualizationData = {
|
||||
kind: {
|
||||
table: true;
|
||||
};
|
||||
/**
|
||||
* An array of objects. The properties of the objects are used as columns.
|
||||
*/
|
||||
rows: {}[];
|
||||
};
|
||||
|
||||
export type PlotlyVisualizationData = {
|
||||
kind: {
|
||||
plotly: true;
|
||||
};
|
||||
/**
|
||||
* Expecting Plotly.Data[] (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/795ce172038dbafcb9cba030d637d733a7eea19c/types/plotly.js/index.d.ts#L1036)
|
||||
*/
|
||||
data: {
|
||||
text?: string | string[];
|
||||
xaxis?: string;
|
||||
yaxis?: string;
|
||||
x?: (string | number | null)[] | (string | number | null)[][];
|
||||
y?: (string | number | null)[] | (string | number | null)[][];
|
||||
z?: (string | number | null)[] | (string | number | null)[][];
|
||||
type?:
|
||||
| "bar"
|
||||
| "box"
|
||||
| "candlestick"
|
||||
| "choropleth"
|
||||
| "contour"
|
||||
| "heatmap"
|
||||
| "histogram"
|
||||
| "indicator"
|
||||
| "mesh3d"
|
||||
| "ohlc"
|
||||
| "parcoords"
|
||||
| "pie"
|
||||
| "pointcloud"
|
||||
| "scatter"
|
||||
| "scatter3d"
|
||||
| "scattergeo"
|
||||
| "scattergl"
|
||||
| "scatterpolar"
|
||||
| "scatterternary"
|
||||
| "sunburst"
|
||||
| "surface"
|
||||
| "treemap"
|
||||
| "waterfall"
|
||||
| "funnel"
|
||||
| "funnelarea"
|
||||
| "scattermapbox";
|
||||
mode?:
|
||||
| "lines"
|
||||
| "markers"
|
||||
| "text"
|
||||
| "lines+markers"
|
||||
| "text+markers"
|
||||
| "text+lines"
|
||||
| "text+lines+markers"
|
||||
| "none"
|
||||
| "gauge"
|
||||
| "number"
|
||||
| "delta"
|
||||
| "number+delta"
|
||||
| "gauge+number"
|
||||
| "gauge+number+delta"
|
||||
| "gauge+delta";
|
||||
}[];
|
||||
/**
|
||||
* Expecting Partial<Plotly.Layout> (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/795ce172038dbafcb9cba030d637d733a7eea19c/types/plotly.js/index.d.ts#L329)
|
||||
*/
|
||||
layout?: {
|
||||
title?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type SimpleTextVisualizationData = {
|
||||
kind: {
|
||||
text: true;
|
||||
};
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type SvgVisualizationData = {
|
||||
kind: {
|
||||
svg: true;
|
||||
};
|
||||
/**
|
||||
* The svg content
|
||||
*/
|
||||
text: string;
|
||||
};
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
export type DataExtractionResult = {
|
||||
data: ExtractedData;
|
||||
export interface DataExtractionResult {
|
||||
data: VisualizationData;
|
||||
usedExtractor: DataExtractorInfo;
|
||||
availableExtractors: DataExtractorInfo[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Instances must be valid json values.
|
||||
*/
|
||||
export type ExtractedData = {
|
||||
export interface VisualizationData {
|
||||
kind: Record<string, true>;
|
||||
};
|
||||
}
|
||||
|
||||
export type DataExtractorInfo = {
|
||||
export interface DataExtractorInfo {
|
||||
id: DataExtractorId;
|
||||
name: string;
|
||||
priority: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type DataExtractorId = {
|
||||
__brand: "DataExtractorId";
|
||||
} & string;
|
||||
|
||||
export function isExtractedData(val: unknown): val is ExtractedData {
|
||||
export function isVisualizationData(val: unknown): val is VisualizationData {
|
||||
if (typeof val !== "object" || !val || !("kind" in val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
ExtractedData,
|
||||
VisualizationData,
|
||||
DataExtractionResult,
|
||||
} from "../../DataExtractionResult";
|
||||
|
||||
@@ -7,14 +7,12 @@ export interface DataExtractorApi {
|
||||
/**
|
||||
* Registers a single extractor.
|
||||
*/
|
||||
registerExtractor<TExtractedData extends ExtractedData>(
|
||||
extractor: DataExtractor<TExtractedData>
|
||||
): void;
|
||||
registerExtractor(extractor: DataExtractor): void;
|
||||
|
||||
/**
|
||||
* Registers multiple extractors.
|
||||
*/
|
||||
registerExtractors(extractors: DataExtractor<ExtractedData>[]): void;
|
||||
registerExtractors(extractors: DataExtractor[]): void;
|
||||
|
||||
/**
|
||||
* Extracts data from the result of `valueFn`.
|
||||
@@ -46,23 +44,23 @@ export interface JSONString<T> extends String {
|
||||
__brand: { json: T };
|
||||
}
|
||||
|
||||
export interface DataExtractor<T extends ExtractedData> {
|
||||
export interface DataExtractor {
|
||||
/**
|
||||
* Must be unique among all data extractors.
|
||||
*/
|
||||
id: string;
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector<T>,
|
||||
extractionCollector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void;
|
||||
}
|
||||
|
||||
export interface ExtractionCollector<T extends ExtractedData> {
|
||||
export interface ExtractionCollector {
|
||||
/**
|
||||
* Suggests a possible extraction.
|
||||
*/
|
||||
addExtraction(extraction: DataExtraction<T>): void;
|
||||
addExtraction(extraction: DataExtraction): void;
|
||||
}
|
||||
|
||||
export interface DataExtractorContext {
|
||||
@@ -72,7 +70,7 @@ export interface DataExtractorContext {
|
||||
evalFn: <TEval>(expression: string) => TEval;
|
||||
}
|
||||
|
||||
export interface DataExtraction<TData extends ExtractedData> {
|
||||
export interface DataExtraction {
|
||||
/**
|
||||
* Higher priorities are preferred.
|
||||
*/
|
||||
@@ -87,5 +85,5 @@ export interface DataExtraction<TData extends ExtractedData> {
|
||||
* A user friendly name of this extraction.
|
||||
*/
|
||||
name: string;
|
||||
extractData(): TData;
|
||||
extractData(): VisualizationData;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
DataExtraction,
|
||||
ExtractionCollector,
|
||||
} from "./DataExtractorApi";
|
||||
import { ExtractedData, DataExtractorInfo } from "../../DataExtractionResult";
|
||||
import {
|
||||
VisualizationData,
|
||||
DataExtractorInfo,
|
||||
} from "../../DataExtractionResult";
|
||||
import { registerDefaultExtractors } from "./default-extractors";
|
||||
|
||||
/**
|
||||
@@ -15,24 +18,17 @@ import { registerDefaultExtractors } from "./default-extractors";
|
||||
export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
public static lastEvalFn: (<T>(expression: string) => T) | undefined;
|
||||
|
||||
private readonly extractors = new Map<
|
||||
string,
|
||||
DataExtractor<ExtractedData>
|
||||
>();
|
||||
private readonly extractors = new Map<string, DataExtractor>();
|
||||
|
||||
private toJson<TData>(data: TData): JSONString<TData> {
|
||||
return JSON.stringify(data) as any;
|
||||
}
|
||||
|
||||
public registerExtractor<TExtractedData extends ExtractedData>(
|
||||
extractor: DataExtractor<TExtractedData>
|
||||
): void {
|
||||
public registerExtractor(extractor: DataExtractor): void {
|
||||
this.extractors.set(extractor.id, extractor);
|
||||
}
|
||||
|
||||
public registerExtractors(
|
||||
extractors: DataExtractor<ExtractedData>[]
|
||||
): void {
|
||||
public registerExtractors(extractors: DataExtractor[]): void {
|
||||
for (const e of extractors) {
|
||||
this.registerExtractor(e);
|
||||
}
|
||||
@@ -43,8 +39,8 @@ export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
evalFn: <T>(expression: string) => T,
|
||||
preferredDataExtractorId: string | undefined
|
||||
): JSONString<DataResult> {
|
||||
const extractions = new Array<DataExtraction<ExtractedData>>();
|
||||
const extractionCollector: ExtractionCollector<ExtractedData> = {
|
||||
const extractions = new Array<DataExtraction>();
|
||||
const extractionCollector: ExtractionCollector = {
|
||||
addExtraction(extraction) {
|
||||
extractions.push(extraction);
|
||||
},
|
||||
@@ -72,9 +68,7 @@ export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
}
|
||||
}
|
||||
|
||||
function mapExtractor(
|
||||
e: DataExtraction<ExtractedData>
|
||||
): DataExtractorInfo {
|
||||
function mapExtractor(e: DataExtraction): DataExtractorInfo {
|
||||
return {
|
||||
id: e.id as any,
|
||||
name: e.name,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { ExtractedData, isExtractedData } from "../../../DataExtractionResult";
|
||||
import { isVisualizationData } from "../../../DataExtractionResult";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../..";
|
||||
|
||||
export class AsIsDataExtractor implements DataExtractor<ExtractedData> {
|
||||
export class AsIsDataExtractor implements DataExtractor {
|
||||
readonly id = "as-is";
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector<ExtractedData>,
|
||||
extractionCollector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (!isExtractedData(data)) {
|
||||
if (!isVisualizationData(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import { VisualizationData } from "../../../DataExtractionResult";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractorApi";
|
||||
|
||||
export class GetDebugVisualizationDataExtractor
|
||||
implements DataExtractor<ExtractedData> {
|
||||
readonly id = "get-debug-visualization";
|
||||
export class GetVisualizationDataExtractor implements DataExtractor {
|
||||
readonly id = "get-visualization-data";
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<ExtractedData>,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (
|
||||
typeof data !== "object" ||
|
||||
!data ||
|
||||
!("getDebugVisualization" in data)
|
||||
) {
|
||||
if (typeof data !== "object" || !data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getVisualizationData = (data as any)
|
||||
.getVisualizationData as Function;
|
||||
if (typeof getVisualizationData !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
collector.addExtraction({
|
||||
id: this.id,
|
||||
name: "Use Method 'getDebugVisualization'",
|
||||
name: "Use Method 'getVisualizationData'",
|
||||
priority: 600,
|
||||
extractData() {
|
||||
return (data as any).getDebugVisualization();
|
||||
return getVisualizationData.apply(data);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../..";
|
||||
import { CommonDataTypes } from "../../../CommonDataTypes";
|
||||
import { GridVisualizationData } from "../../../CommonDataTypes";
|
||||
import { expect } from "../../../util";
|
||||
|
||||
export class GridExtractor implements DataExtractor<ExtractedData> {
|
||||
export class GridExtractor implements DataExtractor {
|
||||
readonly id = "grid";
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector<ExtractedData>,
|
||||
extractionCollector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (!Array.isArray(data)) {
|
||||
@@ -21,12 +21,11 @@ export class GridExtractor implements DataExtractor<ExtractedData> {
|
||||
id: this.id,
|
||||
name: "Array As Grid",
|
||||
priority: 500,
|
||||
extractData(): CommonDataTypes.Grid {
|
||||
return {
|
||||
kind: { array: true },
|
||||
extractData: () =>
|
||||
expect<GridVisualizationData>({
|
||||
kind: { grid: true },
|
||||
rows: [{ columns: data.map(d => ({ tag: d })) }],
|
||||
};
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import { VisualizationData } from "../../../DataExtractionResult";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
} from "../DataExtractorApi";
|
||||
import { createGraph, CreateGraphEdge } from "../../helpers";
|
||||
|
||||
export class ObjectGraphExtractor implements DataExtractor<ExtractedData> {
|
||||
export class ObjectGraphExtractor implements DataExtractor {
|
||||
readonly id = "object-graph";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<ExtractedData>,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
function isObject(val: unknown): val is object {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import { PlotlyVisualizationData } from "../../../CommonDataTypes";
|
||||
import { expect } from "../../../util";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractorApi";
|
||||
import { CommonDataTypes } from "../../../CommonDataTypes";
|
||||
|
||||
export class PlotDataExtractor implements DataExtractor<ExtractedData> {
|
||||
export class PlotlyDataExtractor implements DataExtractor {
|
||||
readonly id = "plot";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<ExtractedData>,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (!Array.isArray(data)) {
|
||||
@@ -25,14 +25,13 @@ export class PlotDataExtractor implements DataExtractor<ExtractedData> {
|
||||
id: "plot-y",
|
||||
name: "Plot as y-Values",
|
||||
priority: 1001,
|
||||
extractData() {
|
||||
return {
|
||||
extractData: () =>
|
||||
expect<PlotlyVisualizationData>({
|
||||
kind: {
|
||||
plotly: true,
|
||||
},
|
||||
data: [{ y: data }],
|
||||
} as CommonDataTypes.Plotly;
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import { MonacoTextVisualizationData } from "../../../CommonDataTypes";
|
||||
import { expect } from "../../../util";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractorApi";
|
||||
|
||||
export class ToStringDataExtractor implements DataExtractor<ExtractedData> {
|
||||
export class ToStringDataExtractor implements DataExtractor {
|
||||
readonly id = "to-string";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<ExtractedData>,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
collector.addExtraction({
|
||||
@@ -18,12 +19,12 @@ export class ToStringDataExtractor implements DataExtractor<ExtractedData> {
|
||||
name: "To String",
|
||||
priority: 100,
|
||||
extractData() {
|
||||
return {
|
||||
return expect<MonacoTextVisualizationData>({
|
||||
kind: {
|
||||
text: true,
|
||||
},
|
||||
text: "" + data,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,42 +1,19 @@
|
||||
import * as ts from "typescript"; // Only compile-time import!
|
||||
import { AstTreeNode } from "../../..";
|
||||
import { AstTreeVisualizationData } from "../../../CommonDataTypes";
|
||||
import { expect } from "../../../util";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractorApi";
|
||||
|
||||
type AstType = {
|
||||
kind: {
|
||||
tree: true;
|
||||
ast: true;
|
||||
text: true;
|
||||
};
|
||||
root: TreeNode;
|
||||
text: string;
|
||||
};
|
||||
|
||||
type TreeNode = {
|
||||
children: TreeNode[];
|
||||
items: TreeNodeItem[];
|
||||
segment?: string;
|
||||
isMarked?: boolean;
|
||||
span: {
|
||||
start: number;
|
||||
length: number;
|
||||
};
|
||||
};
|
||||
|
||||
type TreeNodeItem = {
|
||||
text: string;
|
||||
emphasis?: "style1" | "style2" | "style3";
|
||||
};
|
||||
|
||||
export class TypeScriptAstDataExtractor implements DataExtractor<AstType> {
|
||||
export class TypeScriptAstDataExtractor implements DataExtractor {
|
||||
readonly id = "typescript-ast";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<AstType>,
|
||||
collector: ExtractionCollector,
|
||||
{ evalFn }: DataExtractorContext
|
||||
): void {
|
||||
if (!data) {
|
||||
@@ -63,116 +40,21 @@ export class TypeScriptAstDataExtractor implements DataExtractor<AstType> {
|
||||
return;
|
||||
}
|
||||
|
||||
function findKey(value: any, object: any): string | null {
|
||||
for (var key in object) {
|
||||
if (key.startsWith("_")) continue;
|
||||
|
||||
var member = object[key];
|
||||
if (member === value) return key;
|
||||
|
||||
if (Array.isArray(member) && member.indexOf(value) !== -1) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getChildren(node: ts.Node): ts.Node[] {
|
||||
const result = new Array<ts.Node>();
|
||||
tsApi.forEachChild(node, n => {
|
||||
result.push(n);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function toTreeNode(
|
||||
node: ts.Node,
|
||||
memberName: string,
|
||||
marked: Set<ts.Node>,
|
||||
emphasizedValueFn: (node: ts.Node) => string | undefined
|
||||
): TreeNode {
|
||||
const name = tsApi.SyntaxKind[node.kind];
|
||||
const children = getChildren(node)
|
||||
.map((childNode, idx) => {
|
||||
let parentPropertyName = findKey(childNode, node) || "";
|
||||
if (childNode.kind == tsApi.SyntaxKind.SyntaxList) {
|
||||
const children = getChildren(childNode);
|
||||
children.some(c => {
|
||||
parentPropertyName = findKey(c, node) || "";
|
||||
return !!parentPropertyName;
|
||||
});
|
||||
|
||||
if (children.length === 0) return null!;
|
||||
}
|
||||
|
||||
if (node.kind == tsApi.SyntaxKind.SyntaxList) {
|
||||
parentPropertyName = "" + idx;
|
||||
}
|
||||
|
||||
return toTreeNode(
|
||||
childNode,
|
||||
parentPropertyName,
|
||||
marked,
|
||||
emphasizedValueFn
|
||||
);
|
||||
})
|
||||
.filter(c => c !== null);
|
||||
|
||||
let value: string | undefined = undefined;
|
||||
|
||||
if (tsApi.isIdentifier(node)) {
|
||||
value = node.text || (node.escapedText as string);
|
||||
} else if (tsApi.isLiteralExpression(node)) {
|
||||
value = node.text;
|
||||
}
|
||||
|
||||
return {
|
||||
items: [
|
||||
{ text: memberName, emphasis: "style1" },
|
||||
{ text: name },
|
||||
],
|
||||
children: children,
|
||||
span: {
|
||||
length: node.end - node.pos,
|
||||
start: node.pos,
|
||||
},
|
||||
//emphasizedValue: emphasizedValueFn(node),
|
||||
isMarked: marked.has(node),
|
||||
//value,
|
||||
};
|
||||
}
|
||||
|
||||
function isNode(node: unknown): node is ts.Node {
|
||||
return (
|
||||
typeof node === "object" &&
|
||||
node !== null &&
|
||||
(tsApi.isToken(node as any) || (tsApi as any).isNode(node))
|
||||
);
|
||||
}
|
||||
|
||||
function getSourceFile(node: ts.Node | any): ts.SourceFile {
|
||||
if (!node) {
|
||||
throw new Error("Detached node");
|
||||
}
|
||||
if (tsApi.isSourceFile(node)) {
|
||||
return node;
|
||||
}
|
||||
if (!("getSourceFile" in node)) {
|
||||
return getSourceFile(node.parent);
|
||||
}
|
||||
return node.getSourceFile();
|
||||
}
|
||||
const helper = new Helper(tsApi);
|
||||
|
||||
let rootSourceFile: ts.SourceFile | undefined = undefined;
|
||||
let rootNode: ts.Node | undefined = undefined;
|
||||
let marked: Set<ts.Node>;
|
||||
let fn: (n: ts.Node) => string | undefined = (n: ts.Node) => undefined;
|
||||
if (Array.isArray(data) && data.every(isNode) && data.length > 0) {
|
||||
rootSourceFile = getSourceFile(data[0] as ts.Node);
|
||||
if (
|
||||
Array.isArray(data) &&
|
||||
data.every(helper.isNode) &&
|
||||
data.length > 0
|
||||
) {
|
||||
rootSourceFile = helper.getSourceFile(data[0] as ts.Node);
|
||||
marked = new Set(data);
|
||||
} else if (isNode(data)) {
|
||||
rootSourceFile = getSourceFile(data);
|
||||
} else if (helper.isNode(data)) {
|
||||
rootSourceFile = helper.getSourceFile(data);
|
||||
marked = new Set([data]);
|
||||
} else if (typeof data === "object" && data) {
|
||||
marked = new Set();
|
||||
@@ -187,15 +69,18 @@ export class TypeScriptAstDataExtractor implements DataExtractor<AstType> {
|
||||
rootNode = item;
|
||||
}
|
||||
let nodes: Array<ts.Node>;
|
||||
if (isNode(item)) {
|
||||
if (helper.isNode(item)) {
|
||||
nodes = [item];
|
||||
} else if (Array.isArray(item) && item.every(isNode)) {
|
||||
} else if (
|
||||
Array.isArray(item) &&
|
||||
item.every(helper.isNode)
|
||||
) {
|
||||
nodes = item;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (nodes.length > 0 && !rootSourceFile) {
|
||||
rootSourceFile = getSourceFile(nodes[0]);
|
||||
rootSourceFile = helper.getSourceFile(nodes[0]);
|
||||
}
|
||||
for (const n of nodes) {
|
||||
marked.add(n);
|
||||
@@ -217,9 +102,9 @@ export class TypeScriptAstDataExtractor implements DataExtractor<AstType> {
|
||||
name: "TypeScript AST",
|
||||
priority: 1000,
|
||||
extractData() {
|
||||
return {
|
||||
return expect<AstTreeVisualizationData>({
|
||||
kind: { text: true, tree: true, ast: true },
|
||||
root: toTreeNode(
|
||||
root: helper.toTreeNode(
|
||||
rootNode || finalRootSourceFile,
|
||||
"root",
|
||||
marked,
|
||||
@@ -227,8 +112,111 @@ export class TypeScriptAstDataExtractor implements DataExtractor<AstType> {
|
||||
),
|
||||
text: finalRootSourceFile.text,
|
||||
fileName: "index.ts",
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Helper {
|
||||
constructor(private readonly tsApi: typeof ts) {}
|
||||
|
||||
findKey(value: any, object: any): string | null {
|
||||
for (var key in object) {
|
||||
if (key.startsWith("_")) continue;
|
||||
|
||||
var member = object[key];
|
||||
if (member === value) return key;
|
||||
|
||||
if (Array.isArray(member) && member.indexOf(value) !== -1) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
getChildren(node: ts.Node): ts.Node[] {
|
||||
const result = new Array<ts.Node>();
|
||||
this.tsApi.forEachChild(node, n => {
|
||||
result.push(n);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
toTreeNode(
|
||||
node: ts.Node,
|
||||
memberName: string,
|
||||
marked: Set<ts.Node>,
|
||||
emphasizedValueFn: (node: ts.Node) => string | undefined
|
||||
): AstTreeNode {
|
||||
const name = this.tsApi.SyntaxKind[node.kind];
|
||||
const children = this.getChildren(node)
|
||||
.map((childNode, idx) => {
|
||||
let parentPropertyName = this.findKey(childNode, node) || "";
|
||||
if (childNode.kind == this.tsApi.SyntaxKind.SyntaxList) {
|
||||
const children = this.getChildren(childNode);
|
||||
children.some(c => {
|
||||
parentPropertyName = this.findKey(c, node) || "";
|
||||
return !!parentPropertyName;
|
||||
});
|
||||
|
||||
if (children.length === 0) return null!;
|
||||
}
|
||||
|
||||
if (node.kind == this.tsApi.SyntaxKind.SyntaxList) {
|
||||
parentPropertyName = "" + idx;
|
||||
}
|
||||
|
||||
return this.toTreeNode(
|
||||
childNode,
|
||||
parentPropertyName,
|
||||
marked,
|
||||
emphasizedValueFn
|
||||
);
|
||||
})
|
||||
.filter(c => c !== null);
|
||||
|
||||
let value: string | undefined = undefined;
|
||||
|
||||
if (this.tsApi.isIdentifier(node)) {
|
||||
value = node.text || (node.escapedText as string);
|
||||
} else if (this.tsApi.isLiteralExpression(node)) {
|
||||
value = node.text;
|
||||
}
|
||||
|
||||
return {
|
||||
items: [{ text: memberName, emphasis: "style1" }, { text: name }],
|
||||
children: children,
|
||||
span: {
|
||||
length: node.end - node.pos,
|
||||
start: node.pos,
|
||||
},
|
||||
//emphasizedValue: emphasizedValueFn(node),
|
||||
isMarked: marked.has(node),
|
||||
//value,
|
||||
};
|
||||
}
|
||||
|
||||
isNode = (node: unknown): node is ts.Node => {
|
||||
return (
|
||||
typeof node === "object" &&
|
||||
node !== null &&
|
||||
(this.tsApi.isToken(node as any) ||
|
||||
(this.tsApi as any).isNode(node))
|
||||
);
|
||||
};
|
||||
|
||||
getSourceFile(node: ts.Node | any): ts.SourceFile {
|
||||
if (!node) {
|
||||
throw new Error("Detached node");
|
||||
}
|
||||
if (this.tsApi.isSourceFile(node)) {
|
||||
return node;
|
||||
}
|
||||
if (!("getSourceFile" in node)) {
|
||||
return this.getSourceFile(node.parent);
|
||||
}
|
||||
return node.getSourceFile();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DataExtractorApi } from "../DataExtractorApi";
|
||||
import { TypeScriptAstDataExtractor } from "./TypeScriptDataExtractors";
|
||||
import { AsIsDataExtractor } from "./AsIsDataExtractor";
|
||||
import { GetDebugVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor";
|
||||
import { GetVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor";
|
||||
import { ToStringDataExtractor } from "./ToStringExtractor";
|
||||
import { PlotDataExtractor } from "./PlotlyDataExtractor";
|
||||
import { PlotlyDataExtractor } from "./PlotlyDataExtractor";
|
||||
import { ObjectGraphExtractor } from "./ObjectGraphExtractor";
|
||||
import { getDataExtractorApi } from "../injection";
|
||||
import { GridExtractor } from "./GridExtractor";
|
||||
@@ -18,9 +18,9 @@ export function registerDefaultExtractors(
|
||||
for (const item of [
|
||||
new TypeScriptAstDataExtractor(),
|
||||
new AsIsDataExtractor(),
|
||||
new GetDebugVisualizationDataExtractor(),
|
||||
new GetVisualizationDataExtractor(),
|
||||
new ToStringDataExtractor(),
|
||||
new PlotDataExtractor(),
|
||||
new PlotlyDataExtractor(),
|
||||
new ObjectGraphExtractor(),
|
||||
new GridExtractor(),
|
||||
]) {
|
||||
|
||||
3
data-extraction/src/util.ts
Normal file
3
data-extraction/src/util.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function expect<T>(data: T): T {
|
||||
return data;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
DataExtractionResult,
|
||||
isExtractedData,
|
||||
isVisualizationData,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { FormattedMessage } from "../../webviewContract";
|
||||
|
||||
@@ -40,7 +40,7 @@ export function parseEvaluationResultFromGenericDebugAdapter(
|
||||
// result is now { kind: { ... }, text: "some\ntext" }
|
||||
}
|
||||
|
||||
if (!isExtractedData(resultObj)) {
|
||||
if (!isVisualizationData(resultObj)) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "GPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --hot",
|
||||
"build": "webpack"
|
||||
"build": "node --max_old_space_size=4096 ../node_modules/webpack/bin/webpack.js --mode production"
|
||||
},
|
||||
"main": "./index.js",
|
||||
"dependencies": {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
|
||||
import { observable, action, computed, when, runInAction } from "mobx";
|
||||
import {
|
||||
DataExtractorId,
|
||||
ExtractedData,
|
||||
VisualizationData,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import "@hediet/visualization-bundle";
|
||||
import {
|
||||
@@ -41,7 +41,7 @@ export class Model {
|
||||
@observable.ref state:
|
||||
| DataExtractionState
|
||||
| { kind: "noExpression" }
|
||||
| { kind: "visualizationError"; data: ExtractedData } = {
|
||||
| { kind: "visualizationError"; data: VisualizationData } = {
|
||||
kind: "noExpression",
|
||||
};
|
||||
|
||||
@@ -62,7 +62,7 @@ export class Model {
|
||||
}
|
||||
|
||||
@action
|
||||
public setVisualizationError(data: ExtractedData) {
|
||||
public setVisualizationError(data: VisualizationData) {
|
||||
this.state = { kind: "visualizationError", data: data };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user