Skip to content

Commit caca007

Browse files
authored
fix: restore handling of zipfile: schema for neovim + yarn berry (typescript-language-server#862)
1 parent a00c1cc commit caca007

33 files changed

+896
-26
lines changed

.github/workflows/ci.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ jobs:
2727
with:
2828
node-version: ${{ matrix.node-version }}
2929
cache: yarn
30+
- run: corepack enable
3031
- name: yarn install
3132
run: yarn
33+
- name: prepare yarnpnp
34+
run: cd test-data/yarn-pnp && yarn
3235
- name: build
3336
run: yarn build
3437
- name: Unittest

.github/workflows/release.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
with:
2222
node-version: 18
2323
registry-url: https://registry.npmjs.org
24+
- run: corepack enable
2425
- name: Lint and Test
2526
if: ${{ steps.release.outputs.release_created }}
2627
run: yarn && yarn build && yarn lint

.github/workflows/size-limit.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424
with:
2525
node-version: ${{ matrix.node-version }}
2626
cache: yarn
27+
- run: corepack enable
2728
- name: yarn install
2829
run: yarn
2930
- name: build

src/configuration/fileSchemes.test.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { URI } from 'vscode-uri';
2+
import * as lsp from 'vscode-languageserver';
3+
import { beforeAll, beforeEach, afterAll, describe, it, expect } from 'vitest';
4+
import { uri, createServer, position, TestLspServer, openDocumentAndWaitForDiagnostics, readContents, filePath, isWindows } from '../test-utils.js';
5+
import { ZipfileURI } from '../utils/uri.js';
6+
7+
const ZIPFILE_URI = 'zipfile:///dir/foo.zip::path/file.ts';
8+
9+
describe('uri handling', () => {
10+
it('parses zipfile:// uri', () => {
11+
const parsed = URI.parse(ZIPFILE_URI);
12+
expect(parsed.scheme).toBe('zipfile');
13+
expect(parsed.authority).toBe('');
14+
expect(parsed.path).toBe('/dir/foo.zip::path/file.ts');
15+
expect(parsed.fsPath).toBe(isWindows ? '\\dir\\foo.zip::path\\file.ts' : '/dir/foo.zip::path/file.ts');
16+
expect(parsed.query).toBe('');
17+
expect(parsed.fragment).toBe('');
18+
});
19+
20+
it('stringifies zipfile uri without encoding', () => {
21+
const parsed = URI.parse(ZIPFILE_URI);
22+
expect(parsed.toString(true)).toBe('zipfile:/dir/foo.zip::path/file.ts');
23+
});
24+
25+
it('stringifies zipfile uri with encoding', () => {
26+
const parsed = URI.parse(ZIPFILE_URI);
27+
expect(parsed.toString()).toBe('zipfile:/dir/foo.zip%3A%3Apath/file.ts');
28+
});
29+
});
30+
31+
describe('zipfileuri handling', () => {
32+
it('parses zipfile:// uri', () => {
33+
const parsed = ZipfileURI.parse(ZIPFILE_URI);
34+
expect(parsed.scheme).toBe('zipfile');
35+
expect(parsed.authority).toBe('');
36+
expect(parsed.path).toBe('/dir/foo.zip::path/file.ts');
37+
expect(parsed.fsPath).toBe(isWindows ? '\\dir\\foo.zip::path\\file.ts' : '/dir/foo.zip::path/file.ts');
38+
expect(parsed.query).toBe('');
39+
expect(parsed.fragment).toBe('');
40+
});
41+
42+
it('stringifies zipfile uri with and without encoding', () => {
43+
const parsed = ZipfileURI.parse(ZIPFILE_URI);
44+
expect(parsed.toString(true)).toBe('zipfile:///dir/foo.zip::path/file.ts');
45+
expect(parsed.toString()).toBe('zipfile:///dir/foo.zip::path/file.ts');
46+
});
47+
});
48+
49+
describe('neovim zipfile scheme handling with yarn pnp', () => {
50+
let server: TestLspServer;
51+
52+
beforeAll(async () => {
53+
server = await createServer({
54+
rootUri: uri('yarn-pnp'),
55+
initializationOptionsOverrides: {
56+
hostInfo: 'neovim',
57+
},
58+
publishDiagnostics() {},
59+
});
60+
});
61+
62+
beforeEach(() => {
63+
server.closeAllForTesting();
64+
});
65+
66+
afterAll(() => {
67+
server.closeAllForTesting();
68+
server.shutdown();
69+
});
70+
71+
it('returns zipfile: uri for definition inside node_modules', async () => {
72+
const doc = {
73+
uri: uri('yarn-pnp', 'testfile.ts'),
74+
languageId: 'typescript',
75+
version: 1,
76+
text: readContents(filePath('yarn-pnp', 'testfile.ts')),
77+
};
78+
await openDocumentAndWaitForDiagnostics(server, doc);
79+
const pos = position(doc, 'AxiosHeaderValue');
80+
const results = await server.definition({ textDocument: doc, position: pos });
81+
const defintion = Array.isArray(results) ? results[0] as lsp.Location : null;
82+
expect(defintion).toBeDefined();
83+
expect(defintion!.uri).toMatch(/zipfile:\/\/.+.zip::node_modules\/axios\/.+/);
84+
});
85+
});

src/configuration/fileSchemes.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export const untitled = 'untitled';
1414
export const git = 'git';
1515
export const github = 'github';
1616
export const azurerepos = 'azurerepos';
17+
// Equivalent of "untitled" in Sublime Text.
18+
export const buffer = 'buffer';
19+
// For yarn berry support in neovim.
20+
export const zipfile = 'zipfile';
1721

1822
/** Live share scheme */
1923
export const vsls = 'vsls';
@@ -23,6 +27,17 @@ export const memFs = 'memfs';
2327
export const vscodeVfs = 'vscode-vfs';
2428
export const officeScript = 'office-script';
2529

30+
export function getSemanticSupportedSchemes(): string[] {
31+
return [
32+
file,
33+
untitled,
34+
buffer,
35+
// walkThroughSnippet,
36+
// vscodeNotebookCell,
37+
zipfile,
38+
];
39+
}
40+
2641
/**
2742
* File scheme for which JS/TS language feature should be disabled
2843
*/

src/diagnostic-queue.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export class DiagnosticEventQueue {
8787
if (this.ignoredDiagnosticCodes.size) {
8888
diagnostics = diagnostics.filter(diagnostic => !this.isDiagnosticIgnored(diagnostic));
8989
}
90-
const uri = this.client.toResource(file).toString();
90+
const uri = this.client.toResourceUri(file);
9191
const diagnosticsForFile = this.diagnostics.get(uri) || new FileDiagnostics(uri, this.publishDiagnostics, this.client, this.features);
9292
diagnosticsForFile.update(kind, diagnostics);
9393
this.diagnostics.set(uri, diagnosticsForFile);
@@ -98,12 +98,12 @@ export class DiagnosticEventQueue {
9898
}
9999

100100
public getDiagnosticsForFile(file: string): lsp.Diagnostic[] {
101-
const uri = this.client.toResource(file).toString();
101+
const uri = this.client.toResourceUri(file);
102102
return this.diagnostics.get(uri)?.getDiagnostics() || [];
103103
}
104104

105105
public onDidCloseFile(file: string): void {
106-
const uri = this.client.toResource(file).toString();
106+
const uri = this.client.toResourceUri(file);
107107
const diagnosticsForFile = this.diagnostics.get(uri);
108108
diagnosticsForFile?.onDidClose();
109109
this.diagnostics.delete(uri);
@@ -113,7 +113,7 @@ export class DiagnosticEventQueue {
113113
* A testing function to clear existing file diagnostics, request fresh ones and wait for all to arrive.
114114
*/
115115
public async waitForDiagnosticsForTesting(file: string): Promise<void> {
116-
const uri = this.client.toResource(file).toString();
116+
const uri = this.client.toResourceUri(file);
117117
let diagnosticsForFile = this.diagnostics.get(uri);
118118
if (diagnosticsForFile) {
119119
diagnosticsForFile.onDidClose();

src/features/call-hierarchy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function fromProtocolCallHierarchyItem(item: ts.server.protocol.CallHiera
2626
kind: fromProtocolScriptElementKind(item.kind),
2727
name,
2828
detail,
29-
uri: client.toResource(item.file).toString(),
29+
uri: client.toResourceUri(item.file),
3030
range: Range.fromTextSpan(item.span),
3131
selectionRange: Range.fromTextSpan(item.selectionSpan),
3232
};

src/features/code-lens/implementationsCodeLens.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip
5151
const locations = response.body
5252
.map(reference =>
5353
// Only take first line on implementation: https://github.com/microsoft/vscode/issues/23924
54-
Location.create(this.client.toResource(reference.file).toString(),
54+
Location.create(this.client.toResourceUri(reference.file),
5555
reference.start.line === reference.end.line
5656
? typeConverters.Range.fromTextSpan(reference)
5757
: Range.create(

src/features/code-lens/referencesCodeLens.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens
4747
const locations = response.body.refs
4848
.filter(reference => !reference.isDefinition)
4949
.map(reference =>
50-
typeConverters.Location.fromTextSpan(this.client.toResource(reference.file).toString(), reference));
50+
typeConverters.Location.fromTextSpan(this.client.toResourceUri(reference.file), reference));
5151

5252
codeLens.command = {
5353
title: this.getCodeLensLabel(locations),

src/features/source-definition.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class SourceDefinitionCommand {
4646
return;
4747
}
4848

49-
const document = client.toOpenDocument(client.toResource(file).toString());
49+
const document = client.toOpenDocument(client.toResourceUri(file));
5050

5151
if (!document) {
5252
lspClient.showErrorMessage('Go to Source Definition failed. File not opened in the editor.');

src/lsp-server.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ export class LspServer {
147147
disableAutomaticTypingAcquisition,
148148
maxTsServerMemory,
149149
npmLocation,
150+
hostInfo,
150151
locale,
151152
plugins: plugins || [],
152153
onEvent: this.onTsEvent.bind(this),
@@ -550,7 +551,7 @@ export class LspServer {
550551

551552
async completionResolve(item: lsp.CompletionItem, token?: lsp.CancellationToken): Promise<lsp.CompletionItem> {
552553
item.data = item.data?.cacheId !== undefined ? this.completionDataCache.get(item.data.cacheId) : item.data;
553-
const uri = this.tsClient.toResource(item.data.file).toString();
554+
const uri = this.tsClient.toResourceUri(item.data.file);
554555
const document = item.data?.file ? this.tsClient.toOpenDocument(uri) : undefined;
555556
if (!document) {
556557
return item;
@@ -636,7 +637,7 @@ export class LspServer {
636637
const changes: lsp.WorkspaceEdit['changes'] = {};
637638
result.locs
638639
.forEach((spanGroup) => {
639-
const uri = this.tsClient.toResource(spanGroup.file).toString();
640+
const uri = this.tsClient.toResourceUri(spanGroup.file);
640641
const textEdits = changes[uri] || (changes[uri] = []);
641642

642643
spanGroup.locs.forEach((textSpan) => {
@@ -868,7 +869,7 @@ export class LspServer {
868869
if (renameLocation) {
869870
await this.options.lspClient.rename({
870871
textDocument: {
871-
uri: this.tsClient.toResource(args.file).toString(),
872+
uri: this.tsClient.toResourceUri(args.file),
872873
},
873874
position: Position.fromLocation(renameLocation),
874875
});
@@ -878,7 +879,7 @@ export class LspServer {
878879
this.tsClient.configurePlugin(pluginName, configuration);
879880
} else if (params.command === Commands.ORGANIZE_IMPORTS && params.arguments) {
880881
const file = params.arguments[0] as string;
881-
const uri = this.tsClient.toResource(file).toString();
882+
const uri = this.tsClient.toResourceUri(file);
882883
const document = this.tsClient.toOpenDocument(uri);
883884
if (!document) {
884885
return;
@@ -944,7 +945,7 @@ export class LspServer {
944945
}
945946
const changes: { [uri: string]: lsp.TextEdit[]; } = {};
946947
for (const edit of edits) {
947-
changes[this.tsClient.toResource(edit.fileName).toString()] = edit.textChanges.map(toTextEdit);
948+
changes[this.tsClient.toResourceUri(edit.fileName)] = edit.textChanges.map(toTextEdit);
948949
}
949950
const { applied } = await this.options.lspClient.applyWorkspaceEdit({
950951
edit: { changes },
@@ -957,7 +958,7 @@ export class LspServer {
957958
for (const rename of params.files) {
958959
const codeEdits = await this.getEditsForFileRename(rename.oldUri, rename.newUri, token);
959960
for (const codeEdit of codeEdits) {
960-
const uri = this.tsClient.toResource(codeEdit.fileName).toString();
961+
const uri = this.tsClient.toResourceUri(codeEdit.fileName);
961962
const textEdits = changes[uri] || (changes[uri] = []);
962963
textEdits.push(...codeEdit.textChanges.map(toTextEdit));
963964
}
@@ -1061,7 +1062,7 @@ export class LspServer {
10611062
return response.body.map(item => {
10621063
return <lsp.SymbolInformation>{
10631064
location: {
1064-
uri: this.tsClient.toResource(item.file).toString(),
1065+
uri: this.tsClient.toResourceUri(item.file),
10651066
range: {
10661067
start: Position.fromLocation(item.start),
10671068
end: Position.fromLocation(item.end),

src/protocol-translation.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import type { ts } from './ts-protocol.js';
1212
import { Position, Range } from './utils/typeConverters.js';
1313

1414
export function toLocation(fileSpan: ts.server.protocol.FileSpan, client: TsClient): lsp.Location {
15-
const uri = client.toResource(fileSpan.file);
15+
const uri = client.toResourceUri(fileSpan.file);
1616
return {
17-
uri: uri.toString(),
17+
uri,
1818
range: {
1919
start: Position.fromLocation(fileSpan.start),
2020
end: Position.fromLocation(fileSpan.end),
@@ -125,11 +125,11 @@ export function toTextEdit(edit: ts.server.protocol.CodeEdit): lsp.TextEdit {
125125
}
126126

127127
export function toTextDocumentEdit(change: ts.server.protocol.FileCodeEdits, client: TsClient): lsp.TextDocumentEdit {
128-
const uri = client.toResource(change.fileName);
129-
const document = client.toOpenDocument(uri.toString());
128+
const uri = client.toResourceUri(change.fileName);
129+
const document = client.toOpenDocument(uri);
130130
return {
131131
textDocument: {
132-
uri: uri.toString(),
132+
uri,
133133
version: document?.version ?? null,
134134
},
135135
edits: change.textChanges.map(c => toTextEdit(c)),

src/test-utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ const DEFAULT_TEST_CLIENT_INITIALIZATION_OPTIONS: TypeScriptInitializationOption
7272

7373
const DEFAULT_WORKSPACE_SETTINGS: WorkspaceConfiguration = {};
7474

75+
export const isWindows = process.platform === 'win32';
76+
7577
export async function openDocumentAndWaitForDiagnostics(server: TestLspServer, textDocument: lsp.TextDocumentItem): Promise<void> {
7678
server.didOpenTextDocument({ textDocument });
7779
await server.waitForDiagnosticsForFile(textDocument.uri);
@@ -204,6 +206,7 @@ interface TestLspServerOptions {
204206
rootUri: string | null;
205207
publishDiagnostics: (args: lsp.PublishDiagnosticsParams) => void;
206208
clientCapabilitiesOverride?: lsp.ClientCapabilities;
209+
initializationOptionsOverrides?: TypeScriptInitializationOptions;
207210
}
208211

209212
export async function createServer(options: TestLspServerOptions): Promise<TestLspServer> {
@@ -223,7 +226,7 @@ export async function createServer(options: TestLspServerOptions): Promise<TestL
223226
rootUri: options.rootUri,
224227
processId: 42,
225228
capabilities: deepmerge(DEFAULT_TEST_CLIENT_CAPABILITIES, options.clientCapabilitiesOverride || {}),
226-
initializationOptions: DEFAULT_TEST_CLIENT_INITIALIZATION_OPTIONS,
229+
initializationOptions: deepmerge(DEFAULT_TEST_CLIENT_INITIALIZATION_OPTIONS, options.initializationOptionsOverrides || {}),
227230
workspaceFolders: null,
228231
});
229232
server.updateWorkspaceSettings({});

0 commit comments

Comments
 (0)