diff --git a/package-lock.json b/package-lock.json index 7c598fbe..e97425ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "lodash": "^4.18.1", "lru-cache": "^7.17.0", "vscode-extension-telemetry-wrapper": "^0.15.3", - "vscode-languageclient": "6.0.0-next.9", + "vscode-languageserver-types": "3.18.0", "vscode-tas-client": "^0.1.75" }, "devDependencies": { @@ -4167,47 +4167,11 @@ "@vscode/extension-telemetry": "^1.2.0" } }, - "node_modules/vscode-jsonrpc": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz", - "integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageclient": { - "version": "6.0.0-next.9", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.0.0-next.9.tgz", - "integrity": "sha512-NEpeeFM9FKrrRqlBHXGfwpkhtnjruDz3zfFBP+Cymr10qigAEtE/JsODJsIG/ErGqjh3/JXxu8SUOVTGu5oK+w==", - "dependencies": { - "semver": "^6.3.0", - "vscode-languageserver-protocol": "^3.15.0-next.14" - }, - "engines": { - "vscode": "^1.40.0" - } - }, - "node_modules/vscode-languageclient/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz", - "integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==", - "dependencies": { - "vscode-jsonrpc": "8.1.0", - "vscode-languageserver-types": "3.17.3" - } - }, "node_modules/vscode-languageserver-types": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", - "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.0.tgz", + "integrity": "sha512-8TsGPNMIMiiBdkORgRSvLjuiEIiAFtO+KssmYWxQ+uSVvlf7RjK8YKCOjPzZ+YA04jXEV7+7LvkSmHkhpNS99g==", + "license": "MIT" }, "node_modules/vscode-tas-client": { "version": "0.1.75", diff --git a/package.json b/package.json index 6d68cd27..9174508d 100644 --- a/package.json +++ b/package.json @@ -578,7 +578,7 @@ "lodash": "^4.18.1", "lru-cache": "^7.17.0", "vscode-extension-telemetry-wrapper": "^0.15.3", - "vscode-languageclient": "6.0.0-next.9", + "vscode-languageserver-types": "3.18.0", "vscode-tas-client": "^0.1.75" }, "overrides": { diff --git a/src/commands/generationCommands.ts b/src/commands/generationCommands.ts index 5293fb34..16fc0106 100644 --- a/src/commands/generationCommands.ts +++ b/src/commands/generationCommands.ts @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import { TextEdit, Uri, window, workspace, WorkspaceEdit } from 'vscode'; -import * as protocolConverter from 'vscode-languageclient/lib/protocolConverter'; +import { Position, Range, SnippetString, SnippetTextEdit, TextEdit, Uri, window, workspace, WorkspaceEdit, WorkspaceEditEntryMetadata } from 'vscode'; +import { AnnotatedTextEdit, ChangeAnnotation, CreateFile, DeleteFile, RenameFile, SnippetTextEdit as ProtocolSnippetTextEdit, TextDocumentEdit, TextEdit as ProtocolTextEdit, WorkspaceEdit as ProtocolWorkspaceEdit } from 'vscode-languageserver-types'; import { JavaTestRunnerDelegateCommands } from '../constants'; import { executeJavaLanguageServerCommand } from '../utils/commandUtils'; -const converter: protocolConverter.Converter = protocolConverter.createConverter(); export async function generateTests(uri: Uri, cursorOffset: number): Promise { - const edit: WorkspaceEdit = converter.asWorkspaceEdit(await askServerToGenerateTests(uri, cursorOffset)); + const protocolEdit: ProtocolWorkspaceEdit | undefined = await askServerToGenerateTests(uri, cursorOffset); + const edit: WorkspaceEdit | undefined = protocolEdit && asWorkspaceEdit(protocolEdit); if (edit) { await workspace.applyEdit(edit); const entries: [Uri, TextEdit[]][] = edit.entries(); @@ -20,6 +20,58 @@ export async function generateTests(uri: Uri, cursorOffset: number): Promise { - return await executeJavaLanguageServerCommand(JavaTestRunnerDelegateCommands.GENERATE_TESTS, uri.toString(), cursorOffset); +export function asWorkspaceEdit(protocolEdit: ProtocolWorkspaceEdit): WorkspaceEdit { + const edit: WorkspaceEdit = new WorkspaceEdit(); + const metadata: (annotationId?: string) => WorkspaceEditEntryMetadata | undefined = (annotationId?: string): WorkspaceEditEntryMetadata | undefined => { + const annotation: ChangeAnnotation | undefined = annotationId ? protocolEdit.changeAnnotations?.[annotationId] : undefined; + return annotation && { + label: annotation.label, + needsConfirmation: !!annotation.needsConfirmation, + description: annotation.description, + }; + }; + + if (protocolEdit.documentChanges) { + for (const change of protocolEdit.documentChanges) { + if (CreateFile.is(change)) { + edit.createFile(Uri.parse(change.uri), change.options, metadata(change.annotationId)); + } else if (RenameFile.is(change)) { + edit.renameFile(Uri.parse(change.oldUri), Uri.parse(change.newUri), change.options, metadata(change.annotationId)); + } else if (DeleteFile.is(change)) { + edit.deleteFile(Uri.parse(change.uri), change.options, metadata(change.annotationId)); + } else if (TextDocumentEdit.is(change)) { + const edits: [TextEdit | SnippetTextEdit, WorkspaceEditEntryMetadata | undefined][] = change.edits.map( + (textEdit: ProtocolTextEdit | AnnotatedTextEdit | ProtocolSnippetTextEdit): [TextEdit | SnippetTextEdit, WorkspaceEditEntryMetadata | undefined] => { + const range: Range = new Range( + new Position(textEdit.range.start.line, textEdit.range.start.character), + new Position(textEdit.range.end.line, textEdit.range.end.character), + ); + if (ProtocolSnippetTextEdit.is(textEdit)) { + return [new SnippetTextEdit(range, new SnippetString(textEdit.snippet.value)), metadata(textEdit.annotationId)]; + } + return [new TextEdit(range, textEdit.newText), metadata(AnnotatedTextEdit.is(textEdit) ? textEdit.annotationId : undefined)]; + }, + ); + edit.set(Uri.parse(change.textDocument.uri), edits); + } else { + throw new Error(`Unknown workspace edit change received: ${JSON.stringify(change)}`); + } + } + } else if (protocolEdit.changes) { + for (const uri of Object.keys(protocolEdit.changes)) { + const textEdits: ProtocolTextEdit[] = protocolEdit.changes[uri]; + edit.set(Uri.parse(uri), textEdits.map((textEdit: ProtocolTextEdit) => new TextEdit( + new Range( + new Position(textEdit.range.start.line, textEdit.range.start.character), + new Position(textEdit.range.end.line, textEdit.range.end.character), + ), + textEdit.newText, + ))); + } + } + return edit; +} + +async function askServerToGenerateTests(uri: Uri, cursorOffset: number): Promise { + return await executeJavaLanguageServerCommand(JavaTestRunnerDelegateCommands.GENERATE_TESTS, uri.toString(), cursorOffset); } diff --git a/test/suite/generationCommands.test.ts b/test/suite/generationCommands.test.ts new file mode 100644 index 00000000..16c139bd --- /dev/null +++ b/test/suite/generationCommands.test.ts @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { SnippetTextEdit, TextEdit, Uri, WorkspaceEdit } from 'vscode'; +import { WorkspaceEdit as ProtocolWorkspaceEdit } from 'vscode-languageserver-types'; +import { asWorkspaceEdit } from '../../src/commands/generationCommands'; + +suite('Generation Commands Tests', () => { + test('converts protocol changes to VS Code text edits', () => { + const uri: Uri = Uri.file('/workspace/AppTest.java'); + const protocolEdit: ProtocolWorkspaceEdit = { + changes: { + [uri.toString()]: [{ + range: { start: { line: 1, character: 2 }, end: { line: 3, character: 4 } }, + newText: 'generated', + }], + }, + }; + + const entries = asWorkspaceEdit(protocolEdit).entries(); + + assert.strictEqual(entries.length, 1); + assert.strictEqual(entries[0][0].toString(), uri.toString()); + assert.ok(entries[0][1][0] instanceof TextEdit); + assert.strictEqual(entries[0][1][0].newText, 'generated'); + assert.strictEqual(entries[0][1][0].range.start.line, 1); + assert.strictEqual(entries[0][1][0].range.start.character, 2); + assert.strictEqual(entries[0][1][0].range.end.line, 3); + assert.strictEqual(entries[0][1][0].range.end.character, 4); + }); + + test('converts annotated document and resource changes', () => { + const uri: Uri = Uri.file('/workspace/GeneratedTest.java'); + const protocolEdit: ProtocolWorkspaceEdit = { + changeAnnotations: { + generated: { label: 'Generate test', needsConfirmation: true }, + }, + documentChanges: [ + { kind: 'create', uri: uri.toString(), annotationId: 'generated' }, + { + textDocument: { uri: uri.toString(), version: null }, + edits: [{ + range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, + snippet: { kind: 'snippet', value: 'class ${1:GeneratedTest} {}' }, + annotationId: 'generated', + }], + }, + ], + }; + + const setSpy = sinon.spy(WorkspaceEdit.prototype, 'set'); + + try { + asWorkspaceEdit(protocolEdit); + + assert.ok(setSpy.calledOnce); + assert.strictEqual(setSpy.firstCall.args[0].toString(), uri.toString()); + const convertedEdit: unknown = setSpy.firstCall.args[1][0][0]; + assert.ok(convertedEdit instanceof SnippetTextEdit); + assert.strictEqual(convertedEdit.snippet.value, 'class ${1:GeneratedTest} {}'); + } finally { + setSpy.restore(); + } + }); +});