Skip to content
This repository was archived by the owner on Oct 16, 2020. It is now read-only.

Normalize URI encodings #228

Merged
merged 3 commits into from
May 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import iterate from 'iterare';
import { Span } from 'opentracing';
import Semaphore from 'semaphore-async-await';
import { InMemoryFileSystem } from './memfs';
import { normalizeDir, path2uri, toUnixPath, uri2path } from './util';
import { normalizeDir, normalizeUri, path2uri, uriToLocalPath } from './util';

export interface FileSystem {
/**
Expand Down Expand Up @@ -36,7 +36,7 @@ export class RemoteFileSystem implements FileSystem {
*/
async getWorkspaceFiles(base?: string, childOf = new Span()): Promise<Iterable<string>> {
return iterate(await this.client.workspaceXfiles({ base }, childOf))
.map(textDocument => textDocument.uri);
.map(textDocument => normalizeUri(textDocument.uri));
}

/**
Expand All @@ -59,7 +59,7 @@ export class LocalFileSystem implements FileSystem {
* Converts the URI to an absolute path
*/
protected resolveUriToPath(uri: string): string {
return toUnixPath(path.resolve(this.rootPath, uri2path(uri)));
return path.resolve(this.rootPath, uriToLocalPath(uri));
}

async getWorkspaceFiles(base?: string): Promise<Iterable<string>> {
Expand All @@ -69,7 +69,7 @@ export class LocalFileSystem implements FileSystem {
const files = await new Promise<string[]>((resolve, reject) => {
glob('*', { cwd: root, nodir: true, matchBase: true }, (err, matches) => err ? reject(err) : resolve(matches));
});
return iterate(files).map(file => baseUri + file.split('/').map(encodeURIComponent).join('/'));
return iterate(files).map(file => normalizeUri(baseUri + file));
}

async getTextDocumentContent(uri: string): Promise<string> {
Expand Down
3 changes: 0 additions & 3 deletions src/test/fs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ describe('fs.ts', () => {
});
});
describe('getTextDocumentContent()', () => {
it('should read files denoted by relative URI', async () => {
assert.equal(await fileSystem.getTextDocumentContent('tweedledee'), 'hi');
});
it('should read files denoted by absolute URI', async () => {
assert.equal(await fileSystem.getTextDocumentContent(baseUri + 'tweedledee'), 'hi');
});
Expand Down
22 changes: 11 additions & 11 deletions src/typescript-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ export class TypeScriptService {
* location of a symbol at a given text document position.
*/
textDocumentDefinition(params: TextDocumentPositionParams, span = new Span()): Observable<Location[]> {
const uri = params.textDocument.uri;
const uri = util.normalizeUri(params.textDocument.uri);

// Fetch files needed to resolve definition
return this.projectManager.ensureReferencedFiles(uri, undefined, undefined, span)
Expand Down Expand Up @@ -277,7 +277,7 @@ export class TypeScriptService {
* know some information about it.
*/
textDocumentXdefinition(params: TextDocumentPositionParams, span = new Span()): Observable<SymbolLocationInformation[]> {
const uri = params.textDocument.uri;
const uri = util.normalizeUri(params.textDocument.uri);

// Ensure files needed to resolve SymbolLocationInformation are fetched
return this.projectManager.ensureReferencedFiles(uri, undefined, undefined, span)
Expand Down Expand Up @@ -326,7 +326,7 @@ export class TypeScriptService {
* given text document position.
*/
textDocumentHover(params: TextDocumentPositionParams, span = new Span()): Observable<Hover> {
const uri = params.textDocument.uri;
const uri = util.normalizeUri(params.textDocument.uri);

// Ensure files needed to resolve hover are fetched
return this.projectManager.ensureReferencedFiles(uri, undefined, undefined, span)
Expand Down Expand Up @@ -373,7 +373,7 @@ export class TypeScriptService {
* Returns all references to the symbol at the position in the own workspace, including references inside node_modules.
*/
textDocumentReferences(params: ReferenceParams, span = new Span()): Observable<Location[]> {
const uri = params.textDocument.uri;
const uri = util.normalizeUri(params.textDocument.uri);
// Ensure all files were fetched to collect all references
return Observable.from(this.projectManager.ensureOwnFiles(span))
.mergeMap(() => {
Expand Down Expand Up @@ -551,7 +551,7 @@ export class TypeScriptService {
* in a given text document.
*/
textDocumentDocumentSymbol(params: DocumentSymbolParams, span = new Span()): Observable<SymbolInformation[]> {
const uri = params.textDocument.uri;
const uri = util.normalizeUri(params.textDocument.uri);

// Ensure files needed to resolve symbols are fetched
return this.projectManager.ensureReferencedFiles(uri, undefined, undefined, span)
Expand Down Expand Up @@ -742,7 +742,7 @@ export class TypeScriptService {
* property filled in.
*/
textDocumentCompletion(params: TextDocumentPositionParams, span = new Span()): Observable<CompletionList> {
const uri = params.textDocument.uri;
const uri = util.normalizeUri(params.textDocument.uri);

// Ensure files needed to suggest completions are fetched
return this.projectManager.ensureReferencedFiles(uri, undefined, undefined, span)
Expand Down Expand Up @@ -793,7 +793,7 @@ export class TypeScriptService {
* information at a given cursor position.
*/
textDocumentSignatureHelp(params: TextDocumentPositionParams, span = new Span()): Observable<SignatureHelp> {
const uri = params.textDocument.uri;
const uri = util.normalizeUri(params.textDocument.uri);

// Ensure files needed to resolve signature are fetched
return this.projectManager.ensureReferencedFiles(uri, undefined, undefined, span)
Expand Down Expand Up @@ -844,7 +844,7 @@ export class TypeScriptService {
* to read the document's truth using the document's uri.
*/
async textDocumentDidOpen(params: DidOpenTextDocumentParams): Promise<void> {
const uri = params.textDocument.uri;
const uri = util.normalizeUri(params.textDocument.uri);
// Ensure files needed for most operations are fetched
await this.projectManager.ensureReferencedFiles(uri).toPromise();
this.projectManager.didOpen(uri, params.textDocument.text);
Expand All @@ -857,7 +857,7 @@ export class TypeScriptService {
* and language ids.
*/
async textDocumentDidChange(params: DidChangeTextDocumentParams): Promise<void> {
const uri = params.textDocument.uri;
const uri = util.normalizeUri(params.textDocument.uri);
let text: string | undefined;
for (const change of params.contentChanges) {
if (change.range || change.rangeLength) {
Expand Down Expand Up @@ -900,7 +900,7 @@ export class TypeScriptService {
* saved in the client.
*/
async textDocumentDidSave(params: DidSaveTextDocumentParams): Promise<void> {
const uri = params.textDocument.uri;
const uri = util.normalizeUri(params.textDocument.uri);

// Ensure files needed to suggest completions are fetched
await this.projectManager.ensureReferencedFiles(uri).toPromise();
Expand All @@ -913,7 +913,7 @@ export class TypeScriptService {
* (e.g. if the document's uri is a file uri the truth now exists on disk).
*/
async textDocumentDidClose(params: DidCloseTextDocumentParams): Promise<void> {
const uri = params.textDocument.uri;
const uri = util.normalizeUri(params.textDocument.uri);

// Ensure files needed to suggest completions are fetched
await this.projectManager.ensureReferencedFiles(uri).toPromise();
Expand Down
33 changes: 31 additions & 2 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as os from 'os';
import * as path from 'path';
import * as ts from 'typescript';
import * as url from 'url';
import { Position, Range, SymbolKind } from 'vscode-languageserver';
import * as rt from './request-type';

Expand Down Expand Up @@ -78,6 +79,23 @@ export function convertStringtoSymbolKind(kind: string): SymbolKind {
}
}

/**
* Normalizes URI encoding by encoding _all_ special characters in the pathname
*/
export function normalizeUri(uri: string): string {
const parts = url.parse(uri);
if (!parts.pathname) {
return uri;
}
const pathParts = parts.pathname.split('/').map(segment => encodeURIComponent(decodeURIComponent(segment)));
// Decode Windows drive letter colon
if (/^[a-z]%3A$/i.test(pathParts[1])) {
pathParts[1] = decodeURIComponent(pathParts[1]);
}
parts.pathname = pathParts.join('/');
return url.format(parts);
}

export function path2uri(root: string, file: string): string {
let ret = 'file://';
if (!strict && process.platform === 'win32') {
Expand All @@ -89,8 +107,11 @@ export function path2uri(root: string, file: string): string {
} else {
p = file;
}
p = toUnixPath(p).split('/').map((val, i) => i <= 1 && /^[a-z]:$/i.test(val) ? val : encodeURIComponent(val)).join('/');
return ret + p;
if (/^[a-z]:[\\\/]/i.test(p)) {
p = '/' + p;
}
p = p.split(/[\\\/]/g).map((val, i) => i <= 1 && /^[a-z]:$/i.test(val) ? val : encodeURIComponent(val)).join('/');
return normalizeUri(ret + p);
}

export function uri2path(uri: string): string {
Expand All @@ -106,6 +127,14 @@ export function uri2path(uri: string): string {
return uri;
}

export function uriToLocalPath(uri: string): string {
uri = uri.substring('file://'.length);
if (/^\/[a-z]:\//i.test(uri)) {
uri = uri.substring(1);
}
return uri.split('/').map(decodeURIComponent).join(path.sep);
}

export function isLocalUri(uri: string): boolean {
return uri.startsWith('file://');
}
Expand Down