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

Commit 4f678e7

Browse files
authored
Normalize URI encodings (#228)
* Normalize URI encodings * Use it in path2uri * Fix tests on Windows
1 parent f3a99b7 commit 4f678e7

File tree

4 files changed

+46
-20
lines changed

4 files changed

+46
-20
lines changed

src/fs.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import iterate from 'iterare';
66
import { Span } from 'opentracing';
77
import Semaphore from 'semaphore-async-await';
88
import { InMemoryFileSystem } from './memfs';
9-
import { normalizeDir, path2uri, toUnixPath, uri2path } from './util';
9+
import { normalizeDir, normalizeUri, path2uri, uriToLocalPath } from './util';
1010

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

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

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

7575
async getTextDocumentContent(uri: string): Promise<string> {

src/test/fs.test.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,6 @@ describe('fs.ts', () => {
5252
});
5353
});
5454
describe('getTextDocumentContent()', () => {
55-
it('should read files denoted by relative URI', async () => {
56-
assert.equal(await fileSystem.getTextDocumentContent('tweedledee'), 'hi');
57-
});
5855
it('should read files denoted by absolute URI', async () => {
5956
assert.equal(await fileSystem.getTextDocumentContent(baseUri + 'tweedledee'), 'hi');
6057
});

src/typescript-service.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ export class TypeScriptService {
228228
* location of a symbol at a given text document position.
229229
*/
230230
textDocumentDefinition(params: TextDocumentPositionParams, span = new Span()): Observable<Location[]> {
231-
const uri = params.textDocument.uri;
231+
const uri = util.normalizeUri(params.textDocument.uri);
232232

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

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

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

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

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

798798
// Ensure files needed to resolve signature are fetched
799799
return this.projectManager.ensureReferencedFiles(uri, undefined, undefined, span)
@@ -844,7 +844,7 @@ export class TypeScriptService {
844844
* to read the document's truth using the document's uri.
845845
*/
846846
async textDocumentDidOpen(params: DidOpenTextDocumentParams): Promise<void> {
847-
const uri = params.textDocument.uri;
847+
const uri = util.normalizeUri(params.textDocument.uri);
848848
// Ensure files needed for most operations are fetched
849849
await this.projectManager.ensureReferencedFiles(uri).toPromise();
850850
this.projectManager.didOpen(uri, params.textDocument.text);
@@ -857,7 +857,7 @@ export class TypeScriptService {
857857
* and language ids.
858858
*/
859859
async textDocumentDidChange(params: DidChangeTextDocumentParams): Promise<void> {
860-
const uri = params.textDocument.uri;
860+
const uri = util.normalizeUri(params.textDocument.uri);
861861
let text: string | undefined;
862862
for (const change of params.contentChanges) {
863863
if (change.range || change.rangeLength) {
@@ -900,7 +900,7 @@ export class TypeScriptService {
900900
* saved in the client.
901901
*/
902902
async textDocumentDidSave(params: DidSaveTextDocumentParams): Promise<void> {
903-
const uri = params.textDocument.uri;
903+
const uri = util.normalizeUri(params.textDocument.uri);
904904

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

918918
// Ensure files needed to suggest completions are fetched
919919
await this.projectManager.ensureReferencedFiles(uri).toPromise();

src/util.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as os from 'os';
22
import * as path from 'path';
33
import * as ts from 'typescript';
4+
import * as url from 'url';
45
import { Position, Range, SymbolKind } from 'vscode-languageserver';
56
import * as rt from './request-type';
67

@@ -78,6 +79,23 @@ export function convertStringtoSymbolKind(kind: string): SymbolKind {
7879
}
7980
}
8081

82+
/**
83+
* Normalizes URI encoding by encoding _all_ special characters in the pathname
84+
*/
85+
export function normalizeUri(uri: string): string {
86+
const parts = url.parse(uri);
87+
if (!parts.pathname) {
88+
return uri;
89+
}
90+
const pathParts = parts.pathname.split('/').map(segment => encodeURIComponent(decodeURIComponent(segment)));
91+
// Decode Windows drive letter colon
92+
if (/^[a-z]%3A$/i.test(pathParts[1])) {
93+
pathParts[1] = decodeURIComponent(pathParts[1]);
94+
}
95+
parts.pathname = pathParts.join('/');
96+
return url.format(parts);
97+
}
98+
8199
export function path2uri(root: string, file: string): string {
82100
let ret = 'file://';
83101
if (!strict && process.platform === 'win32') {
@@ -89,8 +107,11 @@ export function path2uri(root: string, file: string): string {
89107
} else {
90108
p = file;
91109
}
92-
p = toUnixPath(p).split('/').map((val, i) => i <= 1 && /^[a-z]:$/i.test(val) ? val : encodeURIComponent(val)).join('/');
93-
return ret + p;
110+
if (/^[a-z]:[\\\/]/i.test(p)) {
111+
p = '/' + p;
112+
}
113+
p = p.split(/[\\\/]/g).map((val, i) => i <= 1 && /^[a-z]:$/i.test(val) ? val : encodeURIComponent(val)).join('/');
114+
return normalizeUri(ret + p);
94115
}
95116

96117
export function uri2path(uri: string): string {
@@ -106,6 +127,14 @@ export function uri2path(uri: string): string {
106127
return uri;
107128
}
108129

130+
export function uriToLocalPath(uri: string): string {
131+
uri = uri.substring('file://'.length);
132+
if (/^\/[a-z]:\//i.test(uri)) {
133+
uri = uri.substring(1);
134+
}
135+
return uri.split('/').map(decodeURIComponent).join(path.sep);
136+
}
137+
109138
export function isLocalUri(uri: string): boolean {
110139
return uri.startsWith('file://');
111140
}

0 commit comments

Comments
 (0)