diff --git a/data-extraction/package.json b/data-extraction/package.json index d483c40..f0f42ba 100644 --- a/data-extraction/package.json +++ b/data-extraction/package.json @@ -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": { diff --git a/data-extraction/src/CommonDataTypes.ts b/data-extraction/src/CommonDataTypes.ts index f2153ff..f3dd9ae 100644 --- a/data-extraction/src/CommonDataTypes.ts +++ b/data-extraction/src/CommonDataTypes.ts @@ -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( - data: ExtractedData, - kind: T -): data is Narrow { - 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 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 { - kind: { tree: true }; - root: TreeNode; - } - - 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[]; - } -} - -export interface TreeNode { - name: string; - children: TreeNode[]; - 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 (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; +}; diff --git a/data-extraction/src/DataExtractionResult.ts b/data-extraction/src/DataExtractionResult.ts index 91e6c53..f031769 100644 --- a/data-extraction/src/DataExtractionResult.ts +++ b/data-extraction/src/DataExtractionResult.ts @@ -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; -}; +} -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; } diff --git a/data-extraction/src/js/api/DataExtractorApi.ts b/data-extraction/src/js/api/DataExtractorApi.ts index af4a176..0fd8958 100644 --- a/data-extraction/src/js/api/DataExtractorApi.ts +++ b/data-extraction/src/js/api/DataExtractorApi.ts @@ -1,5 +1,5 @@ import { - ExtractedData, + VisualizationData, DataExtractionResult, } from "../../DataExtractionResult"; @@ -7,14 +7,12 @@ export interface DataExtractorApi { /** * Registers a single extractor. */ - registerExtractor( - extractor: DataExtractor - ): void; + registerExtractor(extractor: DataExtractor): void; /** * Registers multiple extractors. */ - registerExtractors(extractors: DataExtractor[]): void; + registerExtractors(extractors: DataExtractor[]): void; /** * Extracts data from the result of `valueFn`. @@ -46,23 +44,23 @@ export interface JSONString extends String { __brand: { json: T }; } -export interface DataExtractor { +export interface DataExtractor { /** * Must be unique among all data extractors. */ id: string; getExtractions( data: unknown, - extractionCollector: ExtractionCollector, + extractionCollector: ExtractionCollector, context: DataExtractorContext ): void; } -export interface ExtractionCollector { +export interface ExtractionCollector { /** * Suggests a possible extraction. */ - addExtraction(extraction: DataExtraction): void; + addExtraction(extraction: DataExtraction): void; } export interface DataExtractorContext { @@ -72,7 +70,7 @@ export interface DataExtractorContext { evalFn: (expression: string) => TEval; } -export interface DataExtraction { +export interface DataExtraction { /** * Higher priorities are preferred. */ @@ -87,5 +85,5 @@ export interface DataExtraction { * A user friendly name of this extraction. */ name: string; - extractData(): TData; + extractData(): VisualizationData; } diff --git a/data-extraction/src/js/api/DataExtractorApiImpl.ts b/data-extraction/src/js/api/DataExtractorApiImpl.ts index cfc4249..3b74837 100644 --- a/data-extraction/src/js/api/DataExtractorApiImpl.ts +++ b/data-extraction/src/js/api/DataExtractorApiImpl.ts @@ -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: ((expression: string) => T) | undefined; - private readonly extractors = new Map< - string, - DataExtractor - >(); + private readonly extractors = new Map(); private toJson(data: TData): JSONString { return JSON.stringify(data) as any; } - public registerExtractor( - extractor: DataExtractor - ): void { + public registerExtractor(extractor: DataExtractor): void { this.extractors.set(extractor.id, extractor); } - public registerExtractors( - extractors: DataExtractor[] - ): void { + public registerExtractors(extractors: DataExtractor[]): void { for (const e of extractors) { this.registerExtractor(e); } @@ -43,8 +39,8 @@ export class DataExtractorApiImpl implements DataExtractorApi { evalFn: (expression: string) => T, preferredDataExtractorId: string | undefined ): JSONString { - const extractions = new Array>(); - const extractionCollector: ExtractionCollector = { + const extractions = new Array(); + const extractionCollector: ExtractionCollector = { addExtraction(extraction) { extractions.push(extraction); }, @@ -72,9 +68,7 @@ export class DataExtractorApiImpl implements DataExtractorApi { } } - function mapExtractor( - e: DataExtraction - ): DataExtractorInfo { + function mapExtractor(e: DataExtraction): DataExtractorInfo { return { id: e.id as any, name: e.name, diff --git a/data-extraction/src/js/api/default-extractors/AsIsDataExtractor.ts b/data-extraction/src/js/api/default-extractors/AsIsDataExtractor.ts index 8c1bded..2306427 100644 --- a/data-extraction/src/js/api/default-extractors/AsIsDataExtractor.ts +++ b/data-extraction/src/js/api/default-extractors/AsIsDataExtractor.ts @@ -1,18 +1,18 @@ -import { ExtractedData, isExtractedData } from "../../../DataExtractionResult"; +import { isVisualizationData } from "../../../DataExtractionResult"; import { DataExtractor, ExtractionCollector, DataExtractorContext, } from "../.."; -export class AsIsDataExtractor implements DataExtractor { +export class AsIsDataExtractor implements DataExtractor { readonly id = "as-is"; getExtractions( data: unknown, - extractionCollector: ExtractionCollector, + extractionCollector: ExtractionCollector, context: DataExtractorContext ): void { - if (!isExtractedData(data)) { + if (!isVisualizationData(data)) { return; } diff --git a/data-extraction/src/js/api/default-extractors/GetDebugVisualizationDataExtractor.ts b/data-extraction/src/js/api/default-extractors/GetDebugVisualizationDataExtractor.ts index 1bbf9c0..5b687de 100644 --- a/data-extraction/src/js/api/default-extractors/GetDebugVisualizationDataExtractor.ts +++ b/data-extraction/src/js/api/default-extractors/GetDebugVisualizationDataExtractor.ts @@ -1,32 +1,33 @@ -import { ExtractedData } from "../../../DataExtractionResult"; +import { VisualizationData } from "../../../DataExtractionResult"; import { DataExtractor, ExtractionCollector, DataExtractorContext, } from "../DataExtractorApi"; -export class GetDebugVisualizationDataExtractor - implements DataExtractor { - readonly id = "get-debug-visualization"; +export class GetVisualizationDataExtractor implements DataExtractor { + readonly id = "get-visualization-data"; getExtractions( data: unknown, - collector: ExtractionCollector, + 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); }, }); } diff --git a/data-extraction/src/js/api/default-extractors/GridExtractor.ts b/data-extraction/src/js/api/default-extractors/GridExtractor.ts index f6ac601..27b4413 100644 --- a/data-extraction/src/js/api/default-extractors/GridExtractor.ts +++ b/data-extraction/src/js/api/default-extractors/GridExtractor.ts @@ -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 { +export class GridExtractor implements DataExtractor { readonly id = "grid"; getExtractions( data: unknown, - extractionCollector: ExtractionCollector, + extractionCollector: ExtractionCollector, context: DataExtractorContext ): void { if (!Array.isArray(data)) { @@ -21,12 +21,11 @@ export class GridExtractor implements DataExtractor { id: this.id, name: "Array As Grid", priority: 500, - extractData(): CommonDataTypes.Grid { - return { - kind: { array: true }, + extractData: () => + expect({ + kind: { grid: true }, rows: [{ columns: data.map(d => ({ tag: d })) }], - }; - }, + }), }); } } diff --git a/data-extraction/src/js/api/default-extractors/ObjectGraphExtractor.ts b/data-extraction/src/js/api/default-extractors/ObjectGraphExtractor.ts index 942f8a0..ee2dad9 100644 --- a/data-extraction/src/js/api/default-extractors/ObjectGraphExtractor.ts +++ b/data-extraction/src/js/api/default-extractors/ObjectGraphExtractor.ts @@ -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 { +export class ObjectGraphExtractor implements DataExtractor { readonly id = "object-graph"; getExtractions( data: unknown, - collector: ExtractionCollector, + collector: ExtractionCollector, context: DataExtractorContext ): void { function isObject(val: unknown): val is object { diff --git a/data-extraction/src/js/api/default-extractors/PlotlyDataExtractor.ts b/data-extraction/src/js/api/default-extractors/PlotlyDataExtractor.ts index a88e556..43cf18d 100644 --- a/data-extraction/src/js/api/default-extractors/PlotlyDataExtractor.ts +++ b/data-extraction/src/js/api/default-extractors/PlotlyDataExtractor.ts @@ -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 { +export class PlotlyDataExtractor implements DataExtractor { readonly id = "plot"; getExtractions( data: unknown, - collector: ExtractionCollector, + collector: ExtractionCollector, context: DataExtractorContext ): void { if (!Array.isArray(data)) { @@ -25,14 +25,13 @@ export class PlotDataExtractor implements DataExtractor { id: "plot-y", name: "Plot as y-Values", priority: 1001, - extractData() { - return { + extractData: () => + expect({ kind: { plotly: true, }, data: [{ y: data }], - } as CommonDataTypes.Plotly; - }, + }), }); } } diff --git a/data-extraction/src/js/api/default-extractors/ToStringExtractor.ts b/data-extraction/src/js/api/default-extractors/ToStringExtractor.ts index 55fe82f..b7aec3a 100644 --- a/data-extraction/src/js/api/default-extractors/ToStringExtractor.ts +++ b/data-extraction/src/js/api/default-extractors/ToStringExtractor.ts @@ -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 { +export class ToStringDataExtractor implements DataExtractor { readonly id = "to-string"; getExtractions( data: unknown, - collector: ExtractionCollector, + collector: ExtractionCollector, context: DataExtractorContext ): void { collector.addExtraction({ @@ -18,12 +19,12 @@ export class ToStringDataExtractor implements DataExtractor { name: "To String", priority: 100, extractData() { - return { + return expect({ kind: { text: true, }, text: "" + data, - }; + }); }, }); } diff --git a/data-extraction/src/js/api/default-extractors/TypeScriptDataExtractors.ts b/data-extraction/src/js/api/default-extractors/TypeScriptDataExtractors.ts index 30778da..9a7a775 100644 --- a/data-extraction/src/js/api/default-extractors/TypeScriptDataExtractors.ts +++ b/data-extraction/src/js/api/default-extractors/TypeScriptDataExtractors.ts @@ -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 { +export class TypeScriptAstDataExtractor implements DataExtractor { readonly id = "typescript-ast"; getExtractions( data: unknown, - collector: ExtractionCollector, + collector: ExtractionCollector, { evalFn }: DataExtractorContext ): void { if (!data) { @@ -63,116 +40,21 @@ export class TypeScriptAstDataExtractor implements DataExtractor { 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(); - tsApi.forEachChild(node, n => { - result.push(n); - }); - return result; - } - - function toTreeNode( - node: ts.Node, - memberName: string, - marked: Set, - 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; 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 { rootNode = item; } let nodes: Array; - 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 { name: "TypeScript AST", priority: 1000, extractData() { - return { + return expect({ 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 { ), 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(); + this.tsApi.forEachChild(node, n => { + result.push(n); + }); + return result; + } + + toTreeNode( + node: ts.Node, + memberName: string, + marked: Set, + 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(); + } +} diff --git a/data-extraction/src/js/api/default-extractors/registerDefaultDataExtractors.ts b/data-extraction/src/js/api/default-extractors/registerDefaultDataExtractors.ts index 4328877..f11e471 100644 --- a/data-extraction/src/js/api/default-extractors/registerDefaultDataExtractors.ts +++ b/data-extraction/src/js/api/default-extractors/registerDefaultDataExtractors.ts @@ -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(), ]) { diff --git a/data-extraction/src/util.ts b/data-extraction/src/util.ts new file mode 100644 index 0000000..0333786 --- /dev/null +++ b/data-extraction/src/util.ts @@ -0,0 +1,3 @@ +export function expect(data: T): T { + return data; +} diff --git a/extension/src/EvaluationWatchService/EvaluationEngine/parseEvaluationResultFromGenericDebugAdapter.ts b/extension/src/EvaluationWatchService/EvaluationEngine/parseEvaluationResultFromGenericDebugAdapter.ts index 1a31459..b0306b9 100644 --- a/extension/src/EvaluationWatchService/EvaluationEngine/parseEvaluationResultFromGenericDebugAdapter.ts +++ b/extension/src/EvaluationWatchService/EvaluationEngine/parseEvaluationResultFromGenericDebugAdapter.ts @@ -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: { diff --git a/webview/package.json b/webview/package.json index 890ad01..eb9bd05 100644 --- a/webview/package.json +++ b/webview/package.json @@ -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": { diff --git a/webview/src/model/Model.ts b/webview/src/model/Model.ts index 574c4b1..017bcab 100644 --- a/webview/src/model/Model.ts +++ b/webview/src/model/Model.ts @@ -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 }; }