From 06d94194c0317fe07703ad13abc8082fe79ad82a Mon Sep 17 00:00:00 2001 From: Maximilian Wehrstedt Date: Thu, 13 Sep 2018 16:19:50 +0200 Subject: [PATCH] ability to evaluate documents classes --- .gitignore | 2 ++ out/.gitignore | 4 --- out/src/.gitignore | 3 -- src/debugSession.ts | 7 +++-- src/variablesMap.ts | 65 +++++++++++++++++++++++++++++++-------- test/variablesMap.test.ts | 9 +++--- 6 files changed, 64 insertions(+), 26 deletions(-) delete mode 100644 out/.gitignore delete mode 100644 out/src/.gitignore diff --git a/.gitignore b/.gitignore index 4a0938a..73a6729 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,5 @@ jspm_packages # Coverage report coverage + +out diff --git a/out/.gitignore b/out/.gitignore deleted file mode 100644 index 67a38b4..0000000 --- a/out/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore everything in this directory except this file -* -!src -!.gitignore diff --git a/out/src/.gitignore b/out/src/.gitignore deleted file mode 100644 index fbe2824..0000000 --- a/out/src/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore everything in this directory except this file -* -!.gitignore diff --git a/src/debugSession.ts b/src/debugSession.ts index 56eb4b2..58f0a0c 100644 --- a/src/debugSession.ts +++ b/src/debugSession.ts @@ -1005,8 +1005,8 @@ export class JanusDebugSession extends DebugSession { // different for each scope // the frameId itself is not used in variablesMap const frameRef = frame.frameId + 1; - locals.forEach(variable => { - this.variablesMap.createVariable(variable.name, variable.value, contextId, frameRef); + locals.forEach(async variable => { + await this.variablesMap.createVariable(variable.name, variable.value, contextId, context, frameRef); }); log.debug(`stackTraceRequest succeeded`); response.success = true; @@ -1072,10 +1072,11 @@ export class JanusDebugSession extends DebugSession { // Request the variable and insert it to the variables map const evaluateName = collapsedVariable.evaluateName.replace(".___jsrdbg_collapsed___", ""); const variable = await context.evaluate(evaluateName); - this.variablesMap.createVariable( + await this.variablesMap.createVariable( evaluateName.substr(evaluateName.lastIndexOf('.')), JSON.parse(variable.value), variablesContainer.contextId, + context, args.variablesReference, evaluateName ); diff --git a/src/variablesMap.ts b/src/variablesMap.ts index db88067..bda0a21 100644 --- a/src/variablesMap.ts +++ b/src/variablesMap.ts @@ -1,7 +1,7 @@ import { Logger } from 'node-file-log'; import { DebugProtocol } from 'vscode-debugprotocol'; import { cantorPairing, reverseCantorPairing } from './cantor'; - +import { Context } from "./context"; const log = Logger.create('VariablesMap'); export type VariablesReference = number; @@ -69,7 +69,7 @@ export class VariablesMap { * @param {number} frameId The frame id. * @param {string} [evaluateName] This param is need for evaluate variables that are properties of object or elements of arrays. For this variables we need also the name of their parent to access the value. */ - public createVariable(variableName: string, variableValue: any, contextId: number, frameId: number, evaluateName?: string) { + public async createVariable(variableName: string, variableValue: any, contextId: number, context: Context, frameId: number, evaluateName?: string) { if (typeof evaluateName === 'undefined') { evaluateName = ''; } @@ -78,7 +78,7 @@ export class VariablesMap { const variablesContainer: VariablesContainer = this.variablesMap.get(frameId) || new VariablesContainer(contextId); // If the container already contains a variable with this name => update - const variable = this._createVariable(variableName, variableValue, contextId, frameId, evaluateName); + const variable = await this._createVariable(variableName, variableValue, contextId, context, frameId, evaluateName); if (variablesContainer.variables.length > 0) { const filterResult = variablesContainer.variables.filter((element) => { @@ -109,7 +109,7 @@ export class VariablesMap { * @param {string} [evaluateName=variableName] This param is need for evaluate variables that are properties of object or elements of arrays. For this variables we need also the name of their parent to access the value. * @returns {Variable} A full qualified variable object */ - private _createVariable(variableName: string, variableValue: any, contextId: number, frameId: number, evaluateName?: string): DebugProtocol.Variable { + private async _createVariable(variableName: string, variableValue: any, contextId: number, context: Context, frameId: number, evaluateName?: string): Promise { if (typeof evaluateName === 'undefined' || evaluateName === '') { evaluateName = variableName; } @@ -183,9 +183,9 @@ export class VariablesMap { if (variableValue === null) { return this.createPrimitiveVariable(variableName, variableValue, evaluateName); } else if (variableValue.hasOwnProperty("length")) { - return this.createArrayVariable(variableName, variableValue, contextId, frameId, evaluateName); + return await this.createArrayVariable(variableName, variableValue, contextId, context, frameId, evaluateName); } else { - return this.createObjectVariable(variableName, variableValue, contextId, frameId, evaluateName); + return await this.createObjectVariable(variableName, variableValue, contextId, context, frameId, evaluateName); } default: @@ -232,7 +232,7 @@ export class VariablesMap { * @param {string} evaluateName This param is need for evaluate variables that are properties of object or elements of arrays. For this variables we need also the name of their parent to access the value. * @returns {Variable} A full qualified variables object. */ - private createArrayVariable(variableName: string, variableValue: any[], contextId: number, frameId: number, evaluateName: string): DebugProtocol.Variable { + private async createArrayVariable(variableName: string, variableValue: any[], contextId: number, context: Context, frameId: number, evaluateName: string): Promise { if (variableName === '') { throw new Error('Variables name cannot be empty.'); } @@ -249,7 +249,7 @@ export class VariablesMap { const _evaluateName = (key === 'length') ? `${evaluateName}.length` : `${evaluateName}[${index.toString()}]`; variablesContainer.variables.push( - this._createVariable(_variableName, variableValue[key], contextId, frameId, _evaluateName) + await this._createVariable(_variableName, variableValue[key], contextId, context, frameId, _evaluateName) ); index++; @@ -277,25 +277,66 @@ export class VariablesMap { * @param {string} evaluateName This param is need for evaluate variables that are properties of object or elements of arrays. For this variables we need also the name of their parent to access the value. * @returns {Variable} A full qualified variables object. */ - private createObjectVariable(variableName: string, variableValue: any, contextId: number, frameId: number, evaluateName: string): DebugProtocol.Variable { + private async createObjectVariable(variableName: string, variableValue: any, contextId: number, context: Context, frameId: number, evaluateName: string): Promise { if (variableName === '') { throw new Error('Variables name cannot be empty.'); } const variablesContainer: VariablesContainer = new VariablesContainer(contextId); + // if it's an object, request the string representation to parse it + const stringifyFunctionsReplacer = (key: any, value: any) => { + if (typeof value === "function") { + return "function " + value.toString().match(/(\([^\)]*\))/)[1] + "{ ... }"; + } else { + return value; + } + }; + const evaluateExpression = `(function () { + if (${variableName}) { + if (typeof ${variableName} === "object") { + if (${variableName} instanceof DocFile) { + return ${variableName}.asJSON(); + } else if (${variableName} instanceof Date) { + return "new Date(" + ${variableName}.getTime() + ")"; + } else if (${variableName} instanceof FileResultset) { + return JSON.stringify({ + getIds: ${variableName}.getIds(), + size: ${variableName}.size() + }); + } else if (${variableName} instanceof HitResultset) { + return JSON.stringify({ + getHitIds: (typeof ${variableName}.getHitIds === "function") ? ${variableName}.getHitIds() : null, + size: ${variableName}.size() + }); + } + } + } + + return JSON.stringify(${variableName}, ${stringifyFunctionsReplacer.toString()}); + }`.replace(/\r?\n|\t/g, "") + ")()"; + + try { + let _variableValue = await context.evaluate2(evaluateExpression); + /* tslint:disable-next-line */ + _variableValue = eval("_variableValue = " + _variableValue + ";"); + variableValue = _variableValue; + } catch (err) { + log.debug(`evaluate failed: ${JSON.stringify(err)} => ${err.message}`); + } + if (variableValue.hasOwnProperty('___jsrdbg_function_desc___')) { // Functions will be recognized as objects because of the way the debugger evaluate functions let functionParams = variableValue.___jsrdbg_function_desc___.parameterNames; functionParams = functionParams.toString().replace(/,/, ', '); return this.createPrimitiveVariable(variableName, 'function (' + functionParams + ') { ... }', `${evaluateName}.${variableName}`); - } else { + } else if (!(variableValue instanceof Date)) { // Create a new variable for each property on this object and chain them together with the reference property for (const key in variableValue) { if (variableValue.hasOwnProperty(key)) { variablesContainer.variables.push( - this._createVariable(key, variableValue[key], contextId, frameId, `${evaluateName}.${key}`) + await this._createVariable(key, variableValue[key], contextId, context, frameId, `${evaluateName}.${key}`) ); } } @@ -308,7 +349,7 @@ export class VariablesMap { name: variableName, evaluateName, type: 'object', - value: '[Object]', + value: (variableValue.constructor.name === "Date") ? variableValue : variableValue.constructor.name, variablesReference: reference }; } diff --git a/test/variablesMap.test.ts b/test/variablesMap.test.ts index 3ba95de..7456bfa 100644 --- a/test/variablesMap.test.ts +++ b/test/variablesMap.test.ts @@ -1,4 +1,5 @@ import * as assert from 'assert'; +import { Context } from "../src/context"; import { VariablesContainer, VariablesMap } from '../src/variablesMap'; suite('variables map tests', () => { @@ -12,7 +13,7 @@ suite('variables map tests', () => { }); test('createStringVariable', () => { - variablesMap.createVariable('stringVariable', 'myValue', 1, 1); + variablesMap.createVariable('stringVariable', 'myValue', 1, null as any, 1); const variablesContainer: VariablesContainer = variablesMap.getVariables(1); assert.equal(variablesContainer.variables.length, 1); @@ -24,7 +25,7 @@ suite('variables map tests', () => { }); test('createIntVariable', () => { - variablesMap.createVariable('intVariable', 666, 1, 2); + variablesMap.createVariable('intVariable', 666, 1, null as any, 2); const variablesContainer: VariablesContainer = variablesMap.getVariables(2); assert.equal(variablesContainer.variables.length, 1); @@ -36,7 +37,7 @@ suite('variables map tests', () => { }); test('createBooleanVariable', () => { - variablesMap.createVariable('booleanVariable', true, 1, 3); + variablesMap.createVariable('booleanVariable', true, 1, null as any, 3); const variablesContainer: VariablesContainer = variablesMap.getVariables(3); assert.equal(variablesContainer.variables.length, 1); @@ -48,7 +49,7 @@ suite('variables map tests', () => { }); test('createUndefinedVariable', () => { - variablesMap.createVariable('undefinedVariable', undefined, 1, 4); + variablesMap.createVariable('undefinedVariable', undefined, 1, null as any, 4); const variablesContainer: VariablesContainer = variablesMap.getVariables(4); assert.equal(variablesContainer.variables.length, 1);