From cfe523bfff2a959a924bd1415c0d27330b860873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Augusto=20C=C3=A9sar=20Dias?= Date: Tue, 2 Apr 2019 15:53:24 +0200 Subject: [PATCH 1/6] feat: initial dev of logpoint. (#258) --- package-lock.json | 24 ++++++++++++++++++------ package.json | 1 + src/logpoint.ts | 32 ++++++++++++++++++++++++++++++++ src/phpDebug.ts | 24 ++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 src/logpoint.ts diff --git a/package-lock.json b/package-lock.json index fe91b753..ad88af7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -331,6 +331,7 @@ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -342,6 +343,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -1447,8 +1449,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { "version": "4.19.1", @@ -2955,7 +2956,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true + "dev": true, + "optional": true }, "loud-rejection": { "version": "1.6.0", @@ -6525,6 +6527,7 @@ "version": "0.1.4", "bundled": true, "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -7335,7 +7338,8 @@ "longest": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "loose-envify": { "version": "1.3.1", @@ -8083,8 +8087,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -9294,6 +9297,15 @@ "readable-stream": "^2.0.2" } }, + "string-replace-async": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/string-replace-async/-/string-replace-async-1.2.1.tgz", + "integrity": "sha1-1SzcfjOBQbvq6jRx3jEhUCjJo6o=", + "requires": { + "escape-string-regexp": "^1.0.4", + "object-assign": "^4.0.1" + } + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", diff --git a/package.json b/package.json index 7d622b6c..d2b96063 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "iconv-lite": "^0.4.15", "minimatch": "^3.0.3", "moment": "^2.17.1", + "string-replace-async": "^1.2.1", "url-relative": "^1.0.0", "urlencode": "^1.1.0", "vscode-debugadapter": "^1.11.0", diff --git a/src/logpoint.ts b/src/logpoint.ts new file mode 100644 index 00000000..6d8e2fd5 --- /dev/null +++ b/src/logpoint.ts @@ -0,0 +1,32 @@ +import stringReplaceAsync = require('string-replace-async') + +export class LogPointManager { + private _logpoints = new Map>() + + public addLogPoint(fileUri: string, lineNumber: number, logMessage: string) { + if (!this._logpoints.has(fileUri)) { + this._logpoints.set(fileUri, new Map()) + } + this._logpoints.get(fileUri)!.set(lineNumber, logMessage) + } + + public clearFromFile(fileUri: string) { + if (this._logpoints.has(fileUri)) { + this._logpoints.get(fileUri)!.clear() + } + } + + public hasLogPoint(fileUri: string, lineNumber: number): boolean { + return this._logpoints.has(fileUri) && this._logpoints.get(fileUri)!.has(lineNumber); + } + + public async resolveExpressions(fileUri: string, lineNumber: number, callback: (expr: string) => Promise): Promise { + if(!this.hasLogPoint(fileUri, lineNumber)) { + return Promise.reject('Logpoint not found') + } + const expressionRegex = /\{(.*?)\}/gm + return await stringReplaceAsync(this._logpoints.get(fileUri)!.get(lineNumber), expressionRegex, function(_: string, group: string) { + return callback(group); + }); + } +} diff --git a/src/phpDebug.ts b/src/phpDebug.ts index 76e84692..88afa62e 100644 --- a/src/phpDebug.ts +++ b/src/phpDebug.ts @@ -10,6 +10,7 @@ import * as util from 'util' import * as fs from 'fs' import { Terminal } from './terminal' import { isSameUri, convertClientPathToDebugger, convertDebuggerPathToClient } from './paths' +import {LogPointManager} from './logpoint' import minimatch = require('minimatch') if (process.env['VSCODE_NLS_CONFIG']) { @@ -104,6 +105,13 @@ class PhpDebugSession extends vscode.DebugSession { */ private _connections = new Map() + /** + * A map containing all the logpoints configured in VSCode + * the key will be a pair of fileUri and line number + * the value will be the expression to be evaluated + */ + private _logPointManager = new LogPointManager() + /** A set of connections which are not yet running and are waiting for configurationDoneRequest */ private _waitingConnections = new Set() @@ -156,6 +164,7 @@ class PhpDebugSession extends vscode.DebugSession { supportsEvaluateForHovers: false, supportsConditionalBreakpoints: true, supportsFunctionBreakpoints: true, + supportsLogPoints: true, exceptionBreakpointFilters: [ { filter: 'Notice', @@ -366,6 +375,17 @@ class PhpDebugSession extends vscode.DebugSession { exceptionText = response.exception.name + ': ' + response.exception.message // this seems to be ignored currently by VS Code } else if (this._args.stopOnEntry) { stoppedEventReason = 'entry' + } else if (this._logPointManager.hasLogPoint(response.fileUri, response.line)) { + const logMessage = await this._logPointManager.resolveExpressions(response.fileUri, response.line, async (expr: string): Promise => { + const evaluated = await connection.sendEvalCommand(expr); + return formatPropertyValue(evaluated.result) + }); + + this.sendEvent(new vscode.OutputEvent(logMessage + '\n', 'console')) + + const responseCommand = await connection.sendRunCommand() + await this._checkStatus(responseCommand) + return } else if (response.command.indexOf('step') === 0) { stoppedEventReason = 'step' } else { @@ -448,6 +468,7 @@ class PhpDebugSession extends vscode.DebugSession { response.body = { breakpoints: [] } // this is returned to VS Code let vscodeBreakpoints: VSCodeDebugProtocol.Breakpoint[] + this._logPointManager.clearFromFile(fileUri) if (connections.length === 0) { // if there are no connections yet, we cannot verify any breakpoint vscodeBreakpoints = args.breakpoints!.map(breakpoint => ({ verified: false, line: breakpoint.line })) @@ -458,6 +479,9 @@ class PhpDebugSession extends vscode.DebugSession { if (breakpoint.condition) { return new xdebug.ConditionalBreakpoint(breakpoint.condition, fileUri, breakpoint.line) } else { + if (breakpoint.logMessage !== undefined) { + this._logPointManager.addLogPoint(fileUri, breakpoint.line, breakpoint.logMessage) + } return new xdebug.LineBreakpoint(fileUri, breakpoint.line) } }) From 3f1764eb638ac0888d6d473291e39aae9d0ebbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Augusto=20C=C3=A9sar=20Dias?= Date: Tue, 2 Apr 2019 16:11:23 +0200 Subject: [PATCH 2/6] feat: adding types for string-replace-async library --- src/logpoint.ts | 2 +- tsconfig.json | 2 ++ types/string-replace-async.d.ts | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 types/string-replace-async.d.ts diff --git a/src/logpoint.ts b/src/logpoint.ts index 6d8e2fd5..7021eb36 100644 --- a/src/logpoint.ts +++ b/src/logpoint.ts @@ -25,7 +25,7 @@ export class LogPointManager { return Promise.reject('Logpoint not found') } const expressionRegex = /\{(.*?)\}/gm - return await stringReplaceAsync(this._logpoints.get(fileUri)!.get(lineNumber), expressionRegex, function(_: string, group: string) { + return await stringReplaceAsync(this._logpoints.get(fileUri)!.get(lineNumber)!, expressionRegex, function(_: string, group: string) { return callback(group); }); } diff --git a/tsconfig.json b/tsconfig.json index 5afb649b..2bfc4bd8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,8 @@ { "exclude": ["node_modules", "out"], "compilerOptions": { + "baseUrl": ".", + "paths": { "*": ["types/*"] }, "target": "es6", "module": "commonjs", "rootDir": "src", diff --git a/types/string-replace-async.d.ts b/types/string-replace-async.d.ts new file mode 100644 index 00000000..6657809a --- /dev/null +++ b/types/string-replace-async.d.ts @@ -0,0 +1,5 @@ +export = index; +declare function index(str: string, re: RegExp | string, replacer: (match: string, ...args: any[]) => Promise): string; +declare namespace index { + function seq(str: string, re: RegExp | string, replacer: (match: string, ...args: any[]) => Promise): string; +} From ea10a1b365e1e11c63065cb229448c1c9570d6df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Augusto=20C=C3=A9sar=20Dias?= Date: Tue, 2 Apr 2019 16:22:56 +0200 Subject: [PATCH 3/6] feat: linting and prettifying --- src/logpoint.ts | 19 +++++++++++++------ src/phpDebug.ts | 14 +++++++++----- tsconfig.json | 4 ++-- types/string-replace-async.d.ts | 10 +++++++--- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/logpoint.ts b/src/logpoint.ts index 7021eb36..de79fdb5 100644 --- a/src/logpoint.ts +++ b/src/logpoint.ts @@ -17,16 +17,23 @@ export class LogPointManager { } public hasLogPoint(fileUri: string, lineNumber: number): boolean { - return this._logpoints.has(fileUri) && this._logpoints.get(fileUri)!.has(lineNumber); + return this._logpoints.has(fileUri) && this._logpoints.get(fileUri)!.has(lineNumber) } - public async resolveExpressions(fileUri: string, lineNumber: number, callback: (expr: string) => Promise): Promise { - if(!this.hasLogPoint(fileUri, lineNumber)) { + public async resolveExpressions( + fileUri: string, + lineNumber: number, + callback: (expr: string) => Promise + ): Promise { + if (!this.hasLogPoint(fileUri, lineNumber)) { return Promise.reject('Logpoint not found') } const expressionRegex = /\{(.*?)\}/gm - return await stringReplaceAsync(this._logpoints.get(fileUri)!.get(lineNumber)!, expressionRegex, function(_: string, group: string) { - return callback(group); - }); + return await stringReplaceAsync(this._logpoints.get(fileUri)!.get(lineNumber)!, expressionRegex, function( + _: string, + group: string + ) { + return callback(group) + }) } } diff --git a/src/phpDebug.ts b/src/phpDebug.ts index 88afa62e..e253a73c 100644 --- a/src/phpDebug.ts +++ b/src/phpDebug.ts @@ -10,7 +10,7 @@ import * as util from 'util' import * as fs from 'fs' import { Terminal } from './terminal' import { isSameUri, convertClientPathToDebugger, convertDebuggerPathToClient } from './paths' -import {LogPointManager} from './logpoint' +import { LogPointManager } from './logpoint' import minimatch = require('minimatch') if (process.env['VSCODE_NLS_CONFIG']) { @@ -376,10 +376,14 @@ class PhpDebugSession extends vscode.DebugSession { } else if (this._args.stopOnEntry) { stoppedEventReason = 'entry' } else if (this._logPointManager.hasLogPoint(response.fileUri, response.line)) { - const logMessage = await this._logPointManager.resolveExpressions(response.fileUri, response.line, async (expr: string): Promise => { - const evaluated = await connection.sendEvalCommand(expr); - return formatPropertyValue(evaluated.result) - }); + const logMessage = await this._logPointManager.resolveExpressions( + response.fileUri, + response.line, + async (expr: string): Promise => { + const evaluated = await connection.sendEvalCommand(expr) + return formatPropertyValue(evaluated.result) + } + ) this.sendEvent(new vscode.OutputEvent(logMessage + '\n', 'console')) diff --git a/tsconfig.json b/tsconfig.json index 2bfc4bd8..ddbef08c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "exclude": ["node_modules", "out"], "compilerOptions": { - "baseUrl": ".", - "paths": { "*": ["types/*"] }, + "baseUrl": ".", + "paths": { "*": ["types/*"] }, "target": "es6", "module": "commonjs", "rootDir": "src", diff --git a/types/string-replace-async.d.ts b/types/string-replace-async.d.ts index 6657809a..9d7cdc99 100644 --- a/types/string-replace-async.d.ts +++ b/types/string-replace-async.d.ts @@ -1,5 +1,9 @@ -export = index; -declare function index(str: string, re: RegExp | string, replacer: (match: string, ...args: any[]) => Promise): string; +export = index +declare function index( + str: string, + re: RegExp | string, + replacer: (match: string, ...args: any[]) => Promise +): string declare namespace index { - function seq(str: string, re: RegExp | string, replacer: (match: string, ...args: any[]) => Promise): string; + function seq(str: string, re: RegExp | string, replacer: (match: string, ...args: any[]) => Promise): string } From 3152e2403860a9cefa48e9da16ef12241b0f3c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Augusto=20C=C3=A9sar=20Dias?= Date: Tue, 2 Apr 2019 17:16:48 +0200 Subject: [PATCH 4/6] feat: adding tests for logpoint class --- src/logpoint.ts | 2 +- src/test/logpoint.ts | 97 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/test/logpoint.ts diff --git a/src/logpoint.ts b/src/logpoint.ts index de79fdb5..b1f592e5 100644 --- a/src/logpoint.ts +++ b/src/logpoint.ts @@ -33,7 +33,7 @@ export class LogPointManager { _: string, group: string ) { - return callback(group) + return group.length === 0 ? Promise.resolve('') : callback(group) }) } } diff --git a/src/test/logpoint.ts b/src/test/logpoint.ts new file mode 100644 index 00000000..cdffa0ca --- /dev/null +++ b/src/test/logpoint.ts @@ -0,0 +1,97 @@ +import { LogPointManager } from '../logpoint' +import * as assert from 'assert' + +describe('logpoint', () => { + + const FILE_URI1 = 'file://my/file1' + const FILE_URI2 = 'file://my/file2' + const FILE_URI3 = 'file://my/file3' + + const LOG_MESSAGE_VAR = '{$variable1}' + const LOG_MESSAGE_MULTIPLE = '{$variable1} {$variable3} {$variable2}' + const LOG_MESSAGE_TEXT_AND_VAR = 'This is my {$variable1}' + const LOG_MESSAGE_TEXT_AND_MULTIVAR = 'Those variables: {$variable1} ${$variable2} should be replaced' + const LOG_MESSAGE_REPEATED_VAR = 'This {$variable1} and {$variable1} should be equal' + const LOG_MESSAGE_BADLY_FORMATED_VAR = 'Only {$variable1} should be resolved and not }$variable1 and $variable1{}' + + const REPLACE_FUNCTION = (str: string): Promise => { return Promise.resolve(`${str}_value`) } + + let logPointManager: LogPointManager + + beforeEach('create new instance', () => logPointManager = new LogPointManager()) + + describe('basic map management', () => { + it('should contain added logpoints', () => { + logPointManager.addLogPoint(FILE_URI1, 10, LOG_MESSAGE_VAR) + logPointManager.addLogPoint(FILE_URI1, 11, LOG_MESSAGE_VAR) + logPointManager.addLogPoint(FILE_URI2, 12, LOG_MESSAGE_VAR) + logPointManager.addLogPoint(FILE_URI3, 13, LOG_MESSAGE_VAR) + + assert.equal(logPointManager.hasLogPoint(FILE_URI1, 10), true) + assert.equal(logPointManager.hasLogPoint(FILE_URI1, 11), true) + assert.equal(logPointManager.hasLogPoint(FILE_URI2, 12), true) + assert.equal(logPointManager.hasLogPoint(FILE_URI3, 13), true) + + assert.equal(logPointManager.hasLogPoint(FILE_URI1, 12), false) + assert.equal(logPointManager.hasLogPoint(FILE_URI2, 13), false) + assert.equal(logPointManager.hasLogPoint(FILE_URI3, 10), false) + }) + + it('should add and clear entries', () => { + logPointManager.addLogPoint(FILE_URI1, 10, LOG_MESSAGE_VAR) + logPointManager.addLogPoint(FILE_URI1, 11, LOG_MESSAGE_VAR) + logPointManager.addLogPoint(FILE_URI2, 12, LOG_MESSAGE_VAR) + logPointManager.addLogPoint(FILE_URI3, 13, LOG_MESSAGE_VAR) + + assert.equal(logPointManager.hasLogPoint(FILE_URI1, 10), true) + assert.equal(logPointManager.hasLogPoint(FILE_URI1, 11), true) + assert.equal(logPointManager.hasLogPoint(FILE_URI2, 12), true) + assert.equal(logPointManager.hasLogPoint(FILE_URI3, 13), true) + + logPointManager.clearFromFile(FILE_URI1) + + assert.equal(logPointManager.hasLogPoint(FILE_URI1, 10), false) + assert.equal(logPointManager.hasLogPoint(FILE_URI1, 11), false) + assert.equal(logPointManager.hasLogPoint(FILE_URI2, 12), true) + assert.equal(logPointManager.hasLogPoint(FILE_URI3, 13), true) + }) + }) + + describe('variable resolution', () => { + it('should resolve variables', async () => { + logPointManager.addLogPoint(FILE_URI1, 10, LOG_MESSAGE_VAR) + const result = await logPointManager.resolveExpressions(FILE_URI1, 10, REPLACE_FUNCTION) + assert.equal(result, '$variable1_value') + }) + + it('should resolve multiple variables', async () => { + logPointManager.addLogPoint(FILE_URI1, 10, LOG_MESSAGE_MULTIPLE) + const result = await logPointManager.resolveExpressions(FILE_URI1, 10, REPLACE_FUNCTION) + assert.equal(result, '$variable1_value $variable3_value $variable2_value') + }) + + it('should resolve variables with text', async () => { + logPointManager.addLogPoint(FILE_URI1, 10, LOG_MESSAGE_TEXT_AND_VAR) + const result = await logPointManager.resolveExpressions(FILE_URI1, 10, REPLACE_FUNCTION) + assert.equal(result, 'This is my $variable1_value') + }) + + it('should resolve multiple variables with text', async () => { + logPointManager.addLogPoint(FILE_URI1, 10, LOG_MESSAGE_TEXT_AND_MULTIVAR) + const result = await logPointManager.resolveExpressions(FILE_URI1, 10, REPLACE_FUNCTION) + assert.equal(result, 'Those variables: $variable1_value $$variable2_value should be replaced') + }) + + it('should resolve repeated variables', async () => { + logPointManager.addLogPoint(FILE_URI1, 10, LOG_MESSAGE_REPEATED_VAR) + const result = await logPointManager.resolveExpressions(FILE_URI1, 10, REPLACE_FUNCTION) + assert.equal(result, 'This $variable1_value and $variable1_value should be equal') + }) + + it('should resolve repeated bad formated messages correctly', async () => { + logPointManager.addLogPoint(FILE_URI1, 10, LOG_MESSAGE_BADLY_FORMATED_VAR) + const result = await logPointManager.resolveExpressions(FILE_URI1, 10, REPLACE_FUNCTION) + assert.equal(result, 'Only $variable1_value should be resolved and not }$variable1 and $variable1') + }) + }) +}) From 2db0ab8ce815c054897119ddccfbc9c47aabddd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Augusto=20C=C3=A9sar=20Dias?= Date: Tue, 2 Apr 2019 17:18:27 +0200 Subject: [PATCH 5/6] feat: fixing formatting --- src/test/logpoint.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/logpoint.ts b/src/test/logpoint.ts index cdffa0ca..7be746b4 100644 --- a/src/test/logpoint.ts +++ b/src/test/logpoint.ts @@ -2,7 +2,6 @@ import { LogPointManager } from '../logpoint' import * as assert from 'assert' describe('logpoint', () => { - const FILE_URI1 = 'file://my/file1' const FILE_URI2 = 'file://my/file2' const FILE_URI3 = 'file://my/file3' @@ -14,11 +13,13 @@ describe('logpoint', () => { const LOG_MESSAGE_REPEATED_VAR = 'This {$variable1} and {$variable1} should be equal' const LOG_MESSAGE_BADLY_FORMATED_VAR = 'Only {$variable1} should be resolved and not }$variable1 and $variable1{}' - const REPLACE_FUNCTION = (str: string): Promise => { return Promise.resolve(`${str}_value`) } + const REPLACE_FUNCTION = (str: string): Promise => { + return Promise.resolve(`${str}_value`) + } let logPointManager: LogPointManager - beforeEach('create new instance', () => logPointManager = new LogPointManager()) + beforeEach('create new instance', () => (logPointManager = new LogPointManager())) describe('basic map management', () => { it('should contain added logpoints', () => { From 47e8435b04c7fc3989bedbb7623d68cce39d3b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Augusto=20C=C3=A9sar=20Dias?= Date: Tue, 2 Apr 2019 17:31:19 +0200 Subject: [PATCH 6/6] feat: fixing doc for attribute --- src/phpDebug.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/phpDebug.ts b/src/phpDebug.ts index e253a73c..9d862073 100644 --- a/src/phpDebug.ts +++ b/src/phpDebug.ts @@ -106,9 +106,9 @@ class PhpDebugSession extends vscode.DebugSession { private _connections = new Map() /** - * A map containing all the logpoints configured in VSCode - * the key will be a pair of fileUri and line number - * the value will be the expression to be evaluated + * The manager for logpoints. Since xdebug does not support anything like logpoints, + * it has to be managed by the extension/debug server. It does that by a Map referencing + * the log messages per file. XDebug sees it as a regular breakpoint. */ private _logPointManager = new LogPointManager()