diff --git a/client/package.json b/client/package.json index 56992b79..f44691eb 100644 --- a/client/package.json +++ b/client/package.json @@ -4,29 +4,28 @@ "author": "renandelmonico", "license": "MIT", "version": "0.0.1", - "publisher": "renandelmonico", + "publisher": "satiromarra", "contributors": [ - "recca0120" + "recca0120", + "renandelmonico" ], "repository": { "type": "git", - "url": "https://github.com/renandelmonico/vscode-phpunit" + "url": "https://github.com/satiromarra/vscode-phpunit" }, "engines": { "vscode": "^1.30.0" }, - "scripts": { - "update-vscode": "vscode-install" - }, + "scripts": {}, "dependencies": { - "md5": "^2.3.0", + "md5": "^2.2.1", "vscode-languageclient": "^5.2.1", - "vscode-test-adapter-api": "^1.9.0", - "vscode-test-adapter-util": "^0.7.1" + "vscode-test-adapter-api": "^1.7.0", + "vscode-test-adapter-util": "^0.7.0" }, "devDependencies": { - "@types/md5": "^2.3.1", - "diff": "^4.0.2", - "vscode": "^1.1.37" + "@types/md5": "^2.1.33", + "diff": "^4.0.1", + "@types/vscode": "^1.1.34" } } diff --git a/client/src/LanguageClientAdapter.ts b/client/src/LanguageClientAdapter.ts index 59472ced..1d3fddd0 100644 --- a/client/src/LanguageClientAdapter.ts +++ b/client/src/LanguageClientAdapter.ts @@ -3,163 +3,163 @@ import { Event, EventEmitter, WorkspaceFolder } from 'vscode'; import { LanguageClient } from 'vscode-languageclient'; import { Log } from 'vscode-test-adapter-util'; import { - TestAdapter, - TestLoadStartedEvent, - TestLoadFinishedEvent, - TestRunStartedEvent, - TestRunFinishedEvent, - TestSuiteEvent, - TestEvent, - RetireEvent, + TestAdapter, + TestLoadStartedEvent, + TestLoadFinishedEvent, + TestRunStartedEvent, + TestRunFinishedEvent, + TestSuiteEvent, + TestEvent, + RetireEvent, } from 'vscode-test-adapter-api'; export class LanguageClientAdapter implements TestAdapter { - private disposables: { dispose(): void }[] = []; - - private readonly testsEmitter = new EventEmitter< - TestLoadStartedEvent | TestLoadFinishedEvent - >(); - - private readonly testStatesEmitter = new EventEmitter< - TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | TestEvent - >(); - - private readonly retireEmitter = new EventEmitter(); - - get tests(): Event { - return this.testsEmitter.event; - } - - get testStates(): Event< - TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | TestEvent - > { - return this.testStatesEmitter.event; - } - - get retire(): Event { - return this.retireEmitter.event; - } - - constructor( - public workspaceFolder: WorkspaceFolder, - private client: LanguageClient, - private log: Log - ) { - this.onTestLoadStartedEvent(); - this.onTestLoadFinishedEvent(); - this.onTestRunStartedEvent(); - this.onTestRunFinishedEvent(); - this.onTestRetryEvent(); - - this.disposables.push(this.testsEmitter); - this.disposables.push(this.testStatesEmitter); - this.disposables.push(this.retireEmitter); - } - - public requestName(name: string) { - return [name, md5(this.workspaceFolder.uri.toString())].join('-'); - } - - async load(): Promise { - await this.client.onReady(); - - this.client.sendNotification(this.requestName('TestLoadStartedEvent')); - } - - async run(tests: string[]): Promise { - await this.client.onReady(); - - this.client.sendNotification(this.requestName('TestRunStartedEvent'), { - tests, - }); - } - - // debug?(tests: string[]): Promise { - // console.log(tests); - // throw new Error('Method not implemented.'); - // } - - async cancel() { - await this.client.onReady(); - - this.client.sendNotification(this.requestName('TestCancelEvent')); - } - - async dispose(): Promise { - await this.cancel(); - for (const disposable of this.disposables) { - disposable.dispose(); - } - this.disposables = []; - } - - private async onTestLoadStartedEvent() { - await this.client.onReady(); - - this.client.onRequest(this.requestName('TestLoadStartedEvent'), () => - this.testsEmitter.fire({ type: 'started' }) - ); - } - - private async onTestLoadFinishedEvent() { - await this.client.onReady(); - - this.client.onRequest( - this.requestName('TestLoadFinishedEvent'), - ({ suite }) => { - this.testsEmitter.fire({ - type: 'finished', - suite: suite, - }); - } - ); - } - - private async onTestRunStartedEvent() { - await this.client.onReady(); - - this.client.onRequest( - this.requestName('TestRunStartedEvent'), - ({ tests, events }) => { - this.testStatesEmitter.fire({ - type: 'started', - tests, - }); - - this.updateEvents(events); - } - ); - } - - private async onTestRunFinishedEvent() { - await this.client.onReady(); - - this.client.onRequest( - this.requestName('TestRunFinishedEvent'), - ({ events, command }) => { - this.log.info(command); - this.updateEvents(events); - - this.testStatesEmitter.fire({ - type: 'finished', - }); - } - ); - } - - private async onTestRetryEvent() { - await this.client.onReady(); - - this.client.onRequest(this.requestName('TestRetryEvent'), () => { - this.retireEmitter.fire(); - }); - } - - private updateEvents(events: (TestSuiteEvent | TestEvent)[]): void { - events.forEach(event => { - event.type === 'suite' - ? this.testStatesEmitter.fire(event) - : this.testStatesEmitter.fire(event); - }); - } + private disposables: { dispose(): void }[] = []; + + private readonly testsEmitter = new EventEmitter< + TestLoadStartedEvent | TestLoadFinishedEvent + >(); + + private readonly testStatesEmitter = new EventEmitter< + TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | TestEvent + >(); + + private readonly retireEmitter = new EventEmitter(); + + get tests(): Event { + return this.testsEmitter.event; + } + + get testStates(): Event< + TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | TestEvent + > { + return this.testStatesEmitter.event; + } + + get retire(): Event { + return this.retireEmitter.event; + } + + constructor( + public workspaceFolder: WorkspaceFolder, + private client: LanguageClient, + private log: Log + ) { + this.onTestLoadStartedEvent(); + this.onTestLoadFinishedEvent(); + this.onTestRunStartedEvent(); + this.onTestRunFinishedEvent(); + this.onTestRetryEvent(); + + this.disposables.push(this.testsEmitter); + this.disposables.push(this.testStatesEmitter); + this.disposables.push(this.retireEmitter); + } + + public requestName(name: string) { + return [name, md5(this.workspaceFolder.uri.toString())].join('-'); + } + + async load(): Promise { + await this.client.onReady(); + + this.client.sendNotification(this.requestName('TestLoadStartedEvent')); + } + + async run(tests: string[]): Promise { + await this.client.onReady(); + + this.client.sendNotification(this.requestName('TestRunStartedEvent'), { + tests, + }); + } + + // debug?(tests: string[]): Promise { + // console.log(tests); + // throw new Error('Method not implemented.'); + // } + + async cancel() { + await this.client.onReady(); + + this.client.sendNotification(this.requestName('TestCancelEvent')); + } + + async dispose(): Promise { + await this.cancel(); + for (const disposable of this.disposables) { + disposable.dispose(); + } + this.disposables = []; + } + + private async onTestLoadStartedEvent() { + await this.client.onReady(); + + this.client.onRequest(this.requestName('TestLoadStartedEvent'), () => + this.testsEmitter.fire({ type: 'started' }) + ); + } + + private async onTestLoadFinishedEvent() { + await this.client.onReady(); + + this.client.onRequest( + this.requestName('TestLoadFinishedEvent'), + ({ suite }) => { + this.testsEmitter.fire({ + type: 'finished', + suite: suite, + }); + } + ); + } + + private async onTestRunStartedEvent() { + await this.client.onReady(); + + this.client.onRequest( + this.requestName('TestRunStartedEvent'), + ({ tests, events }) => { + this.testStatesEmitter.fire({ + type: 'started', + tests, + }); + + this.updateEvents(events); + } + ); + } + + private async onTestRunFinishedEvent() { + await this.client.onReady(); + + this.client.onRequest( + this.requestName('TestRunFinishedEvent'), + ({ events, command }) => { + this.log.info(command); + this.updateEvents(events); + + this.testStatesEmitter.fire({ + type: 'finished', + }); + } + ); + } + + private async onTestRetryEvent() { + await this.client.onReady(); + + this.client.onRequest(this.requestName('TestRetryEvent'), (data) => { + this.retireEmitter.fire(data); + }); + } + + private updateEvents(events: (TestSuiteEvent | TestEvent)[]): void { + events.forEach(event => { + event.type === 'suite' + ? this.testStatesEmitter.fire(event) + : this.testStatesEmitter.fire(event); + }); + } } diff --git a/client/tsconfig.json b/client/tsconfig.json index 95c1ad7a..019b889f 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -1,13 +1,13 @@ { - "extends": "../tsconfig.base.json", - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "strict": true, - "outDir": "out", - "rootDir": "src", - "sourceMap": true - }, - "include": ["src"], - "exclude": ["node_modules", "__mocks__"] -} + "extends": "../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "strict": true, + "outDir": "out", + "rootDir": "src", + "sourceMap": true + }, + "include": ["src"], + "exclude": ["node_modules", "__mocks__"] +} \ No newline at end of file diff --git a/package.json b/package.json index bd860f32..394c3f21 100644 --- a/package.json +++ b/package.json @@ -3,17 +3,18 @@ "description": "PHP: Unit Test Explorer UI", "displayName": "PHP: Unit Test Explorer UI", "icon": "img/icon.png", - "author": "renandelmonico", + "author": "satiromarra", "license": "MIT", - "version": "1.0.2", + "version": "1.0.3", "preview": true, "repository": { "type": "git", - "url": "https://github.com/renandelmonico/vscode-phpunit" + "url": "https://github.com/satiromarra/vscode-phpunit" }, - "publisher": "renandelmonico", + "publisher": "satiromarra", "contributors": [ - "recca0120" + "recca0120", + "renandelmonico" ], "categories": [ "Other", @@ -183,6 +184,10 @@ "phpunit.dockerImage": { "type": "string", "description": "Specifies the docker run command line" + }, + "phpunit.pathMappings": { + "type": "object", + "description": "Path mappings for your virtual/remote environments. Ex: { \"local/workspace/folder\": \"docker/workspace/folder\" }" } } }, @@ -195,7 +200,7 @@ ] }, "scripts": { - "vscode:prepublish": "cd client && npm run update-vscode && cd .. && npm run clean && npm run compile", + "vscode:prepublish": "npm run clean && npm run compile", "postinstall": "cd client && npm install && cd ../server && npm install && cd ..", "compile": "npm run compile:client && npm run compile:server", "compile:client": "webpack --mode production --config ./client/webpack.config.js", @@ -213,20 +218,16 @@ "report:watch": "jest --coverage --watch" }, "devDependencies": { - "@types/jest": "^24.9.1", - "@types/node": "^12.20.41", - "@types/vscode": "^1.30.0", - "jest": "^28.1.0", + "@types/jest": "^24.0.13", + "@types/node": "^12.0.4", + "jest": "^24.8.0", "merge-options": "^1.0.1", - "rimraf": "^2.7.1", - "ts-jest": "^28.0.1", - "ts-loader": "^6.2.2", - "tslint": "^5.20.1", - "typescript": "^3.9.10", - "webpack": "^5.72.0", - "webpack-cli": "^3.3.12" - }, - "dependencies": { - "@vscode/test-electron": "^2.0.3" + "rimraf": "^2.6.3", + "ts-jest": "^24.0.2", + "ts-loader": "^6.0.2", + "tslint": "^5.17.0", + "typescript": "^3.5.1", + "webpack": "^4.32.2", + "webpack-cli": "^3.3.2" } } diff --git a/server/package.json b/server/package.json index 701dd5b0..49f186e7 100644 --- a/server/package.json +++ b/server/package.json @@ -2,14 +2,14 @@ "name": "lsp-phpunit-server", "description": "PHPUnit Language Server.", "version": "1.0.0", - "author": "renandelmonico", + "author": "satiromarra", "license": "MIT", "engines": { "node": "*" }, "repository": { "type": "git", - "url": "https://github.com/renandelmonico/vscode-phpunit" + "url": "https://github.com/satiromarra/vscode-phpunit" }, "dependencies": { "glob": "^7.1.4", diff --git a/server/src/Configuration.ts b/server/src/Configuration.ts index 7c46009f..90bbdb5e 100644 --- a/server/src/Configuration.ts +++ b/server/src/Configuration.ts @@ -16,6 +16,7 @@ interface IConfiguration { dockerImage?: string; configFile?: string; discoverConfigFile: boolean; + pathMappings: object; } export class Configuration implements IConfiguration { @@ -25,13 +26,14 @@ export class Configuration implements IConfiguration { relativeFilePath: false, shell: '', remoteCwd: '', - discoverConfigFile: false + discoverConfigFile: false, + pathMappings: {} }; constructor( private connection: Connection, private workspaceFolder: _WorkspaceFolder - ) {} + ) { } get maxNumberOfProblems(): number { return this.defaults.maxNumberOfProblems; @@ -81,6 +83,10 @@ export class Configuration implements IConfiguration { return this.defaults.discoverConfigFile; } + get pathMappings(): object { + return this.defaults.pathMappings; + } + async update(configurationCapability = true) { if (configurationCapability) { this.defaults = await this.connection.workspace.getConfiguration({ diff --git a/server/src/Parser.ts b/server/src/Parser.ts index 77d0a0d8..0d910781 100644 --- a/server/src/Parser.ts +++ b/server/src/Parser.ts @@ -30,7 +30,7 @@ const engine = Engine.create({ }); class ClassNode { - constructor(private node: any, private options: TestOptions) {} + constructor(private node: any, private options: TestOptions) { } asTestSuite(): TestSuiteNode | undefined { const options = this.getTestOptions(); @@ -107,7 +107,7 @@ export default class Parser { }, private _engine = engine, private _files = files - ) {} + ) { } async parse(uri: PathLike | URI): Promise { return this.parseCode(await this._files.get(uri), uri); @@ -140,12 +140,12 @@ export default class Parser { return this.isTestClass(node) ? classes.concat( - new ClassNode(node, { - workspaceFolder: this.workspaceFolder, - uri, - namespace, - }) - ) + new ClassNode(node, { + workspaceFolder: this.workspaceFolder, + uri, + namespace, + }) + ) : classes; }, []); } diff --git a/server/src/TestRunner.ts b/server/src/TestRunner.ts index bb63c70d..b53d297c 100644 --- a/server/src/TestRunner.ts +++ b/server/src/TestRunner.ts @@ -30,22 +30,19 @@ export class TestRunner { constructor(private process = new Process(), private _files = files) { } setPhpBinary(phpBinary: PathLike | URI | undefined) { - this.phpBinary = phpBinary ? this._files.asUri(phpBinary).fsPath : ''; - + //this.phpBinary = phpBinary ? this._files.asUri(phpBinary).fsPath : ''; + this.phpBinary = phpBinary ? phpBinary.toString() : ''; return this; } setPhpUnitBinary(phpUnitBinary: PathLike | URI | undefined) { - this.phpUnitBinary = phpUnitBinary - ? this._files.asUri(phpUnitBinary).fsPath - : ''; - + //this.phpUnitBinary = phpUnitBinary ? this._files.asUri(phpUnitBinary).fsPath : ''; + this.phpUnitBinary = phpUnitBinary ? phpUnitBinary.toString() : ''; return this; } setIsDocker(isDocker: boolean | undefined) { this.isDocker = isDocker ? isDocker : false; - return this; } @@ -57,7 +54,8 @@ export class TestRunner { setConfigFile(configFile: PathLike | URI | undefined) { if (configFile) { - this.args.push(`-c ${this._files.asUri(configFile).fsPath}`); + //this.args.push(`-c ${this._files.asUri(configFile).fsPath}`); + this.args.push(`-c ${configFile}`); } return this; @@ -121,7 +119,7 @@ export class TestRunner { if (p.file) { let testFilePath = this._files.asUri(p.file).fsPath; if (this.relativeFilePath && options && options.cwd) { - testFilePath = testFilePath.replace(new RegExp(options.cwd.replace(/\\/g, '\\\\') + '[\\/\\\\]'), ''); + //testFilePath = testFilePath.replace(new RegExp(options.cwd.replace(/\\/g, '\\\\') + '[\\/\\\\]'), ''); } params.push(testFilePath); } @@ -190,8 +188,9 @@ export class TestRunner { params = params.concat(this.args, args).filter(arg => !!arg); if (this.isDocker) { - const phpUnitFile = phpUnitBinary ? phpUnitBinary.substring(1) : ''; - const command = `${dockerImage} bash -c "${phpUnitFile} ${params.join(' ')}"`; + const phpUnitFile = phpUnitBinary ? phpUnitBinary.toString() : ''; + //const command = `${dockerImage} bash -c "${phpUnitFile} ${params.join(' ')}"`; + const command = `${dockerImage} ${phpUnitFile} ${params.join(' ')}`; return { title: 'PHPUnit LSP', diff --git a/server/src/WorkspaceFolder.ts b/server/src/WorkspaceFolder.ts index 7ad9190c..373de370 100644 --- a/server/src/WorkspaceFolder.ts +++ b/server/src/WorkspaceFolder.ts @@ -54,6 +54,34 @@ export class WorkspaceFolder { getConfig() { return this.config; } + replaceSpecialDir(path: string): string { + return path + .replace(/\$\{workspaceRoot\}/gi, this.fsPath()) + .replace(/\$\{remoteCwd\}/gi, this.config.remoteCwd || this.fsPath()) + .replace(/\\/gi, "/"); + } + replacepath(file: string, local: boolean = true): string { + if (this.config.pathMappings) { + const MapV: string[] = Object.values(this.config.pathMappings); + const MapK: string[] = Object.keys(this.config.pathMappings); + let key: number, value: string, localPath: string; + for (let kv of Object.keys(MapK)) { + key = Number(kv); + localPath = this.replaceSpecialDir(MapV[key]); + value = this.replaceSpecialDir(MapK[key]); + file = file.replace(new RegExp(local ? localPath : value, "ig"), local ? value : localPath); + } + } + file = file.replace(/\$\{workspaceRoot\}/gi, this.fsPath()); + return file; + + } + remote2local(file: string) { + return this._files.asUri(this.replacepath(file, true)); + } + local2remote(file: string) { + return this._files.asUri(this.replacepath(file, false)); + } async detectChange(event: FileEvent) { if (this.isFileChanged(event)) { @@ -125,7 +153,10 @@ export class WorkspaceFolder { rerun = false ) { await this.sendTestRunStartedEvent(tests); - + let configFile = this.config.configFile; + if (this.config.docker && configFile) { + configFile = this.replaceSpecialDir(configFile); + } this.testRunner .setPhpBinary(this.config.php) .setPhpUnitBinary(this.config.phpunit) @@ -133,7 +164,7 @@ export class WorkspaceFolder { .setRelativeFilePath(this.config.relativeFilePath) .setIsDocker(this.config.docker) .setDockerImage(this.config.dockerImage) - .setConfigFile(this.config.configFile) + .setConfigFile(configFile) .setDiscoverConfigFile(this.config.configFile ? false : this.config.discoverConfigFile) this.problems.setRemoteCwd(this.config.remoteCwd); @@ -142,7 +173,9 @@ export class WorkspaceFolder { cwd: this.fsPath(), shell: this.config.shell, }; - + if (this.config.docker && params.file) { + params.options.uri = this.local2remote(this._files.asUri(params.file).fsPath); + } rerun === false ? await this.testRunner.run(params, options) : await this.testRunner.rerun(params, options); @@ -205,9 +238,9 @@ export class WorkspaceFolder { id === 'root' ? { command: 'phpunit.lsp.run-all', arguments: [] } : { - command: 'phpunit.lsp.run-test-at-cursor', - arguments: [id], - }; + command: 'phpunit.lsp.run-test-at-cursor', + arguments: [id], + }; return this.executeCommand(command); } @@ -282,7 +315,7 @@ export class WorkspaceFolder { } private async changeEventsState(response: ITestResponse) { - const problems = await response.asProblems(); + let problems = await response.asProblems(); const result = response.getTestResult(); const state = result.tests === 0 ? 'errored' : 'passed'; @@ -290,6 +323,13 @@ export class WorkspaceFolder { .where(event => event.state === 'running') .map(event => this.fillTestEventState(event, response, state)); + problems = problems.map((i) => { + if (this.config.docker) { + i.file = this.remote2local(i.file).fsPath; + } + return i; + }); + this.problems.put(events).put(problems); this.events.put(events).put(problems); diff --git a/server/tsconfig.json b/server/tsconfig.json index 24c13834..e00e33af 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,14 +1,14 @@ { - "extends": "../tsconfig.base.json", - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "moduleResolution": "node", - "sourceMap": true, - "strict": true, - "outDir": "out", - "rootDir": "src" - }, - "include": ["types", "src"], - "exclude": ["node_modules"] -} + "extends": "../tsconfig.base.json", + "compilerOptions": { + "target": "es2017", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "strict": true, + "outDir": "out", + "rootDir": "src" + }, + "include": ["types", "src"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index d4c38c81..42ee02bb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,20 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "outDir": "out", - "rootDir": "src", - "lib": ["es6"], - "sourceMap": true - }, - "include": ["src"], - "exclude": ["node_modules", "**/__mocks__/*", "**/tests/*"], - "references": [ - { - "path": "./client" - }, - { - "path": "./server" - } - ] -} + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "outDir": "out", + "rootDir": "src", + "lib": ["es2017"], + "sourceMap": true + }, + "include": ["src"], + "exclude": ["node_modules", "**/__mocks__/*", "**/tests/*"], + "references": [{ + "path": "./client" + }, + { + "path": "./server" + } + ] +} \ No newline at end of file