From 1a1c4e4cae00b755c6fc9f35ba1d86eb22785794 Mon Sep 17 00:00:00 2001 From: spacedragon Date: Fri, 22 Feb 2019 16:19:42 +0800 Subject: [PATCH] [Code] correctly handle url when workspace is a symbol link. --- .../code/server/lsp/workspace_handler.test.ts | 123 ++++++++++++++++++ .../code/server/lsp/workspace_handler.ts | 17 ++- 2 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/code/server/lsp/workspace_handler.test.ts diff --git a/x-pack/plugins/code/server/lsp/workspace_handler.test.ts b/x-pack/plugins/code/server/lsp/workspace_handler.test.ts new file mode 100644 index 0000000000000..0cb02c5cc7584 --- /dev/null +++ b/x-pack/plugins/code/server/lsp/workspace_handler.test.ts @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import fs from 'fs'; +import path from 'path'; + +import mkdirp from 'mkdirp'; +import * as os from 'os'; +import rimraf from 'rimraf'; +import { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; +import { LspRequest } from '../../model'; +import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; +import { WorkspaceHandler } from './workspace_handler'; + +const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'code_test')); +const workspaceDir = path.join(baseDir, 'workspace'); +const repoDir = path.join(baseDir, 'repo'); + +function handleResponseUri(wh: WorkspaceHandler, uri: string) { + const dummyRequest: LspRequest = { + method: 'textDocument/edefinition', + params: [], + }; + const dummyResponse: ResponseMessage = { + id: null, + jsonrpc: '', + result: [ + { + location: { + uri, + }, + }, + ], + }; + wh.handleResponse(dummyRequest, dummyResponse); + return dummyResponse.result[0].location.uri; +} + +function makeAFile( + workspacePath: string = workspaceDir, + repo = 'github.com/Microsoft/TypeScript-Node-Starter', + revision = 'master', + file = 'src/controllers/user.ts' +) { + const fullPath = path.join(workspacePath, repo, '__randomString', revision, file); + mkdirp.sync(path.dirname(fullPath)); + fs.writeFileSync(fullPath, ''); + const strInUrl = fullPath + .split(path.sep) + .map(value => encodeURIComponent(value)) + .join('/'); + const uri = `file:///${strInUrl}`; + return { repo, revision, file, uri }; +} + +test('file system url should be converted', async () => { + const workspaceHandler = new WorkspaceHandler( + repoDir, + workspaceDir, + // @ts-ignore + null, + new ConsoleLoggerFactory() + ); + const { repo, revision, file, uri } = makeAFile(workspaceDir); + const converted = handleResponseUri(workspaceHandler, uri); + expect(converted).toBe(`git://${repo}/blob/${revision}/${file}`); +}); + +test('should support symbol link', async () => { + const symlinkToWorkspace = path.join(baseDir, 'linkWorkspace'); + fs.symlinkSync(workspaceDir, symlinkToWorkspace, 'dir'); + // @ts-ignore + const workspaceHandler = new WorkspaceHandler( + repoDir, + symlinkToWorkspace, + // @ts-ignore + null, + new ConsoleLoggerFactory() + ); + + const { repo, revision, file, uri } = makeAFile(workspaceDir); + const converted = handleResponseUri(workspaceHandler, uri); + expect(converted).toBe(`git://${repo}/blob/${revision}/${file}`); +}); + +test('should support spaces in workspace dir', async () => { + const workspaceHasSpaces = path.join(baseDir, 'work space'); + const workspaceHandler = new WorkspaceHandler( + repoDir, + workspaceHasSpaces, + // @ts-ignore + null, + new ConsoleLoggerFactory() + ); + const { repo, revision, file, uri } = makeAFile(workspaceHasSpaces); + const converted = handleResponseUri(workspaceHandler, uri); + expect(converted).toBe(`git://${repo}/blob/${revision}/${file}`); +}); + +test('should throw a error if url is invalid', async () => { + const workspaceHandler = new WorkspaceHandler( + repoDir, + workspaceDir, + // @ts-ignore + null, + new ConsoleLoggerFactory() + ); + const invalidDir = path.join(baseDir, 'invalid_dir'); + const { uri } = makeAFile(invalidDir); + expect(() => handleResponseUri(workspaceHandler, uri)).toThrow(); +}); + +beforeAll(() => { + mkdirp.sync(workspaceDir); + mkdirp.sync(repoDir); +}); + +afterAll(() => { + rimraf.sync(baseDir); +}); diff --git a/x-pack/plugins/code/server/lsp/workspace_handler.ts b/x-pack/plugins/code/server/lsp/workspace_handler.ts index 39d92d4774f67..de51c3332a2e7 100644 --- a/x-pack/plugins/code/server/lsp/workspace_handler.ts +++ b/x-pack/plugins/code/server/lsp/workspace_handler.ts @@ -9,7 +9,7 @@ import del from 'del'; import fs from 'fs'; import { delay } from 'lodash'; import mkdirp from 'mkdirp'; -import { Clone, Commit, Error, Repository, Reset } from 'nodegit'; +import { Clone, Commit, Error as GitError, Repository, Reset } from 'nodegit'; import path from 'path'; import { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; import { Hover, Location, TextDocumentPositionParams } from 'vscode-languageserver'; @@ -93,7 +93,7 @@ export class WorkspaceHandler { this.log.info(`checkout ${workspaceRepo.workdir()} to commit ${targetCommit.sha()}`); // @ts-ignore const result = await Reset.reset(workspaceRepo, commit, Reset.TYPE.HARD, {}); - if (result !== undefined && result !== Error.CODE.OK) { + if (result !== undefined && result !== GitError.CODE.OK) { throw Boom.internal(`checkout workspace to commit ${targetCommit.sha()} failed.`); } } @@ -244,13 +244,16 @@ export class WorkspaceHandler { } } - // todo add an unit test private parseLocation(location: Location) { const uri = location.uri; if (uri && uri.startsWith('file://')) { - const workspaceUrl = new URL(`file://${this.workspacePath}`).toString(); - if (uri.startsWith(workspaceUrl)) { - const relativePath = uri.substring(workspaceUrl.length + 1); + const locationPath = fs.realpathSync(decodeURIComponent(uri.substring('file://'.length))); + const workspacePath = fs.realpathSync(decodeURIComponent(this.workspacePath)); + if (locationPath.startsWith(workspacePath)) { + const relativePath = path.relative(workspacePath, locationPath); + if (path.sep !== '/') { + relativePath.replace(path.sep, '/'); + } const regex = /^(.*?\/.*?\/.*?)\/(__.*?\/)?([^_]+?)\/(.*)$/; const m = relativePath.match(regex); if (m) { @@ -261,6 +264,8 @@ export class WorkspaceHandler { return { repoUri, revision: gitRevision, file }; } } + // @ts-ignore + throw new Error("path in response doesn't not starts with workspace path"); } return null; }