vscode-apollo 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.circleci/config.yml +1 -1
- package/.vscode/launch.json +5 -1
- package/CHANGELOG.md +41 -0
- package/package.json +9 -4
- package/renovate.json +2 -1
- package/sampleWorkspace/localSchema/src/test.js +3 -0
- package/sampleWorkspace/rover/apollo.config.js +3 -0
- package/sampleWorkspace/rover/src/test.graphql +14 -0
- package/sampleWorkspace/rover/src/test.js +30 -0
- package/sampleWorkspace/sampleWorkspace.code-workspace +25 -19
- package/src/language-server/__tests__/document.test.ts +161 -3
- package/src/language-server/__tests__/fixtures/TypeScript.tmLanguage.json +5749 -0
- package/src/language-server/__tests__/fixtures/documents/commentWithTemplate.ts +41 -0
- package/src/language-server/__tests__/fixtures/documents/commentWithTemplate.ts.snap +185 -0
- package/src/language-server/__tests__/fixtures/documents/functionCall.ts +93 -0
- package/src/language-server/__tests__/fixtures/documents/functionCall.ts.snap +431 -0
- package/src/language-server/__tests__/fixtures/documents/taggedTemplate.ts +80 -0
- package/src/language-server/__tests__/fixtures/documents/taggedTemplate.ts.snap +353 -0
- package/src/language-server/__tests__/fixtures/documents/templateWithComment.ts +38 -0
- package/src/language-server/__tests__/fixtures/documents/templateWithComment.ts.snap +123 -0
- package/src/language-server/config/__tests__/loadConfig.ts +28 -16
- package/src/language-server/config/config.ts +50 -12
- package/src/language-server/config/loadConfig.ts +2 -1
- package/src/language-server/config/which.d.ts +19 -0
- package/src/language-server/document.ts +86 -53
- package/src/language-server/fileSet.ts +8 -6
- package/src/language-server/project/base.ts +64 -315
- package/src/language-server/project/client.ts +731 -21
- package/src/language-server/project/internal.ts +354 -0
- package/src/language-server/project/rover/DocumentSynchronization.ts +385 -0
- package/src/language-server/project/rover/__tests__/DocumentSynchronization.test.ts +302 -0
- package/src/language-server/project/rover/project.ts +341 -0
- package/src/language-server/server.ts +187 -98
- package/src/language-server/utilities/__tests__/source.test.ts +162 -0
- package/src/language-server/utilities/languageIdForExtension.ts +39 -0
- package/src/language-server/utilities/source.ts +38 -3
- package/src/language-server/workspace.ts +61 -12
- package/src/languageServerClient.ts +13 -15
- package/src/tools/utilities/getLanguageInformation.ts +41 -0
- package/src/tools/utilities/languageInformation.ts +41 -0
- package/syntaxes/graphql.js.json +18 -21
- package/src/language-server/languageProvider.ts +0 -795
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
ProposedFeatures,
|
|
5
5
|
TextDocuments,
|
|
6
6
|
FileChangeType,
|
|
7
|
-
ServerCapabilities,
|
|
8
7
|
TextDocumentSyncKind,
|
|
8
|
+
SymbolInformation,
|
|
9
|
+
FileEvent,
|
|
9
10
|
} from "vscode-languageserver/node";
|
|
10
11
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
11
12
|
import type { QuickPickItem } from "vscode";
|
|
12
13
|
import { GraphQLWorkspace } from "./workspace";
|
|
13
|
-
import { GraphQLLanguageProvider } from "./languageProvider";
|
|
14
14
|
import { LanguageServerLoadingHandler } from "./loadingHandler";
|
|
15
15
|
import { debounceHandler, Debug } from "./utilities";
|
|
16
16
|
import { URI } from "vscode-uri";
|
|
@@ -20,8 +20,16 @@ import {
|
|
|
20
20
|
LanguageServerRequests as Requests,
|
|
21
21
|
} from "../messages";
|
|
22
22
|
import { isValidationError } from "zod-validation-error";
|
|
23
|
+
import { GraphQLProject } from "./project/base";
|
|
24
|
+
import type { LanguageIdExtensionMap } from "../tools/utilities/languageInformation";
|
|
25
|
+
import { setLanguageIdExtensionMap } from "./utilities/languageIdForExtension";
|
|
26
|
+
|
|
27
|
+
export type InitializationOptions = {
|
|
28
|
+
languageIdExtensionMap: LanguageIdExtensionMap;
|
|
29
|
+
};
|
|
23
30
|
|
|
24
31
|
const connection = createConnection(ProposedFeatures.all);
|
|
32
|
+
export type VSCodeConnection = typeof connection;
|
|
25
33
|
|
|
26
34
|
Debug.SetConnection(connection);
|
|
27
35
|
const { sendNotification: originalSendNotification } = connection;
|
|
@@ -34,8 +42,8 @@ connection.sendNotification = async (...args: [any, ...any[]]) => {
|
|
|
34
42
|
let hasWorkspaceFolderCapability = false;
|
|
35
43
|
|
|
36
44
|
// Awaitable promise for sending messages before the connection is initialized
|
|
37
|
-
let initializeConnection: () => void;
|
|
38
|
-
const whenConnectionInitialized: Promise<
|
|
45
|
+
let initializeConnection: (c: typeof connection) => void;
|
|
46
|
+
const whenConnectionInitialized: Promise<typeof connection> = new Promise(
|
|
39
47
|
(resolve) => (initializeConnection = resolve),
|
|
40
48
|
);
|
|
41
49
|
|
|
@@ -52,6 +60,7 @@ const workspace = new GraphQLWorkspace(
|
|
|
52
60
|
require("../../package.json").version,
|
|
53
61
|
},
|
|
54
62
|
},
|
|
63
|
+
whenConnectionInitialized,
|
|
55
64
|
);
|
|
56
65
|
|
|
57
66
|
workspace.onDiagnostics((params) => {
|
|
@@ -81,45 +90,52 @@ workspace.onConfigFilesFound(async (params) => {
|
|
|
81
90
|
);
|
|
82
91
|
});
|
|
83
92
|
|
|
84
|
-
connection.onInitialize(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
connection.onInitialize(
|
|
94
|
+
async ({ capabilities, workspaceFolders, initializationOptions }) => {
|
|
95
|
+
const { languageIdExtensionMap } =
|
|
96
|
+
initializationOptions as InitializationOptions;
|
|
97
|
+
setLanguageIdExtensionMap(languageIdExtensionMap);
|
|
88
98
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// like `textDocument/codeLens`, and that way these can await `GraphQLProject#whenReady` to make sure
|
|
92
|
-
// we provide them eventually.
|
|
93
|
-
await Promise.all(
|
|
94
|
-
workspaceFolders.map((folder) => workspace.addProjectsInFolder(folder)),
|
|
99
|
+
hasWorkspaceFolderCapability = !!(
|
|
100
|
+
capabilities.workspace && capabilities.workspace.workspaceFolders
|
|
95
101
|
);
|
|
96
|
-
|
|
102
|
+
workspace.capabilities = capabilities;
|
|
103
|
+
|
|
104
|
+
if (workspaceFolders) {
|
|
105
|
+
// We wait until all projects are added, because after `initialize` returns we can get additional requests
|
|
106
|
+
// like `textDocument/codeLens`, and that way these can await `GraphQLProject#whenReady` to make sure
|
|
107
|
+
// we provide them eventually.
|
|
108
|
+
await Promise.all(
|
|
109
|
+
workspaceFolders.map((folder) => workspace.addProjectsInFolder(folder)),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
97
112
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
return {
|
|
114
|
+
capabilities: {
|
|
115
|
+
hoverProvider: true,
|
|
116
|
+
completionProvider: {
|
|
117
|
+
resolveProvider: false,
|
|
118
|
+
triggerCharacters: ["...", "@"],
|
|
119
|
+
},
|
|
120
|
+
definitionProvider: true,
|
|
121
|
+
referencesProvider: true,
|
|
122
|
+
documentSymbolProvider: true,
|
|
123
|
+
workspaceSymbolProvider: true,
|
|
124
|
+
codeLensProvider: {
|
|
125
|
+
resolveProvider: false,
|
|
126
|
+
},
|
|
127
|
+
codeActionProvider: true,
|
|
128
|
+
executeCommandProvider: {
|
|
129
|
+
commands: [],
|
|
130
|
+
},
|
|
131
|
+
textDocumentSync: TextDocumentSyncKind.Full,
|
|
111
132
|
},
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
},
|
|
116
|
-
textDocumentSync: TextDocumentSyncKind.Full,
|
|
117
|
-
} as ServerCapabilities,
|
|
118
|
-
};
|
|
119
|
-
});
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
);
|
|
120
136
|
|
|
121
137
|
connection.onInitialized(async () => {
|
|
122
|
-
initializeConnection();
|
|
138
|
+
initializeConnection(connection);
|
|
123
139
|
if (hasWorkspaceFolderCapability) {
|
|
124
140
|
connection.workspace.onDidChangeWorkspaceFolders(async (event) => {
|
|
125
141
|
await Promise.all([
|
|
@@ -142,21 +158,37 @@ function isFile(uri: string) {
|
|
|
142
158
|
return URI.parse(uri).scheme === "file";
|
|
143
159
|
}
|
|
144
160
|
|
|
145
|
-
documents.onDidChangeContent(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
161
|
+
documents.onDidChangeContent((params) => {
|
|
162
|
+
const project = workspace.projectForFile(
|
|
163
|
+
params.document.uri,
|
|
164
|
+
params.document.languageId,
|
|
165
|
+
);
|
|
166
|
+
if (!project) return;
|
|
149
167
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
168
|
+
// Only watch changes to files
|
|
169
|
+
if (!isFile(params.document.uri)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
project.documentDidChange(params.document);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
documents.onDidOpen(
|
|
177
|
+
(params) =>
|
|
178
|
+
workspace
|
|
179
|
+
.projectForFile(params.document.uri, params.document.languageId)
|
|
180
|
+
?.onDidOpen?.(params),
|
|
181
|
+
);
|
|
154
182
|
|
|
155
|
-
|
|
156
|
-
|
|
183
|
+
documents.onDidClose(
|
|
184
|
+
(params) =>
|
|
185
|
+
workspace
|
|
186
|
+
.projectForFile(params.document.uri, params.document.languageId)
|
|
187
|
+
?.onDidClose?.(params),
|
|
157
188
|
);
|
|
158
189
|
|
|
159
190
|
connection.onDidChangeWatchedFiles((params) => {
|
|
191
|
+
const handledByProject = new Map<GraphQLProject, FileEvent[]>();
|
|
160
192
|
for (const { uri, type } of params.changes) {
|
|
161
193
|
if (
|
|
162
194
|
uri.endsWith("apollo.config.js") ||
|
|
@@ -182,75 +214,82 @@ connection.onDidChangeWatchedFiles((params) => {
|
|
|
182
214
|
const project = workspace.projectForFile(uri);
|
|
183
215
|
if (!project) continue;
|
|
184
216
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
project.fileWasDeleted(uri);
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
217
|
+
handledByProject.set(project, handledByProject.get(project) || []);
|
|
218
|
+
handledByProject.get(project)!.push({ uri, type });
|
|
219
|
+
}
|
|
220
|
+
for (const [project, changes] of handledByProject) {
|
|
221
|
+
project.onDidChangeWatchedFiles({ changes });
|
|
193
222
|
}
|
|
194
223
|
});
|
|
195
224
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
params.position,
|
|
202
|
-
token,
|
|
203
|
-
),
|
|
225
|
+
connection.onHover(
|
|
226
|
+
(params, token, workDoneProgress, resultProgress) =>
|
|
227
|
+
workspace
|
|
228
|
+
.projectForFile(params.textDocument.uri)
|
|
229
|
+
?.onHover?.(params, token, workDoneProgress, resultProgress) ?? null,
|
|
204
230
|
);
|
|
205
231
|
|
|
206
|
-
connection.onDefinition(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
),
|
|
232
|
+
connection.onDefinition(
|
|
233
|
+
(params, token, workDoneProgress, resultProgress) =>
|
|
234
|
+
workspace
|
|
235
|
+
.projectForFile(params.textDocument.uri)
|
|
236
|
+
?.onDefinition?.(params, token, workDoneProgress, resultProgress) ?? null,
|
|
212
237
|
);
|
|
213
238
|
|
|
214
|
-
connection.onReferences(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
token,
|
|
220
|
-
),
|
|
239
|
+
connection.onReferences(
|
|
240
|
+
(params, token, workDoneProgress, resultProgress) =>
|
|
241
|
+
workspace
|
|
242
|
+
.projectForFile(params.textDocument.uri)
|
|
243
|
+
?.onReferences?.(params, token, workDoneProgress, resultProgress) ?? null,
|
|
221
244
|
);
|
|
222
245
|
|
|
223
|
-
connection.onDocumentSymbol(
|
|
224
|
-
|
|
246
|
+
connection.onDocumentSymbol(
|
|
247
|
+
(params, token, workDoneProgress, resultProgress) =>
|
|
248
|
+
workspace
|
|
249
|
+
.projectForFile(params.textDocument.uri)
|
|
250
|
+
?.onDocumentSymbol?.(params, token, workDoneProgress, resultProgress) ??
|
|
251
|
+
[],
|
|
225
252
|
);
|
|
226
253
|
|
|
227
|
-
connection.onWorkspaceSymbol((params, token) =>
|
|
228
|
-
|
|
229
|
-
|
|
254
|
+
connection.onWorkspaceSymbol(async (params, token) => {
|
|
255
|
+
const symbols: SymbolInformation[] = [];
|
|
256
|
+
const symbolPromises = workspace.projects.map(
|
|
257
|
+
(project) =>
|
|
258
|
+
project.provideSymbol?.(params.query, token) || Promise.resolve([]),
|
|
259
|
+
);
|
|
260
|
+
for (const projectSymbols of await Promise.all(symbolPromises)) {
|
|
261
|
+
symbols.push(...projectSymbols);
|
|
262
|
+
}
|
|
263
|
+
return symbols;
|
|
264
|
+
});
|
|
230
265
|
|
|
231
266
|
connection.onCompletion(
|
|
232
|
-
debounceHandler(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
267
|
+
debounceHandler(
|
|
268
|
+
((params, token, workDoneProgress, resultProgress) =>
|
|
269
|
+
workspace
|
|
270
|
+
.projectForFile(params.textDocument.uri)
|
|
271
|
+
?.onCompletion?.(params, token, workDoneProgress, resultProgress) ??
|
|
272
|
+
[]) satisfies Parameters<typeof connection.onCompletion>[0],
|
|
238
273
|
),
|
|
239
274
|
);
|
|
240
275
|
|
|
241
276
|
connection.onCodeLens(
|
|
242
|
-
debounceHandler(
|
|
243
|
-
|
|
277
|
+
debounceHandler(
|
|
278
|
+
((params, token, workDoneProgress, resultProgress) =>
|
|
279
|
+
workspace
|
|
280
|
+
.projectForFile(params.textDocument.uri)
|
|
281
|
+
?.onCodeLens?.(params, token, workDoneProgress, resultProgress) ??
|
|
282
|
+
[]) satisfies Parameters<typeof connection.onCodeLens>[0],
|
|
244
283
|
),
|
|
245
284
|
);
|
|
246
285
|
|
|
247
286
|
connection.onCodeAction(
|
|
248
|
-
debounceHandler(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
287
|
+
debounceHandler(
|
|
288
|
+
((params, token, workDoneProgress, resultProgress) =>
|
|
289
|
+
workspace
|
|
290
|
+
.projectForFile(params.textDocument.uri)
|
|
291
|
+
?.onCodeAction?.(params, token, workDoneProgress, resultProgress) ??
|
|
292
|
+
[]) satisfies Parameters<typeof connection.onCodeAction>[0],
|
|
254
293
|
),
|
|
255
294
|
);
|
|
256
295
|
|
|
@@ -263,12 +302,62 @@ connection.onNotification(Commands.TagSelected, (selection: QuickPickItem) =>
|
|
|
263
302
|
);
|
|
264
303
|
|
|
265
304
|
connection.onNotification(Commands.GetStats, async ({ uri }) => {
|
|
266
|
-
const status = await
|
|
267
|
-
connection.sendNotification(
|
|
305
|
+
const status = await workspace.projectForFile(uri)?.getProjectStats();
|
|
306
|
+
connection.sendNotification(
|
|
307
|
+
Notifications.StatsLoaded,
|
|
308
|
+
status ?? {
|
|
309
|
+
loaded: false,
|
|
310
|
+
},
|
|
311
|
+
);
|
|
268
312
|
});
|
|
269
313
|
connection.onRequest(Requests.FileStats, async ({ uri }) => {
|
|
270
|
-
return
|
|
314
|
+
return workspace.projectForFile(uri)?.getProjectStats() ?? { loaded: false };
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
connection.onRequest((method, params, token) => {
|
|
318
|
+
return getProjectFromUnknownParams(params, workspace)?.onUnhandledRequest?.(
|
|
319
|
+
method,
|
|
320
|
+
params,
|
|
321
|
+
token,
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
connection.onNotification((method, params) => {
|
|
326
|
+
return getProjectFromUnknownParams(
|
|
327
|
+
params,
|
|
328
|
+
workspace,
|
|
329
|
+
)?.onUnhandledNotification?.(connection, method, params);
|
|
271
330
|
});
|
|
272
331
|
|
|
273
332
|
// Listen on the connection
|
|
274
333
|
connection.listen();
|
|
334
|
+
|
|
335
|
+
// ------------------------------ utility functions ------------------------------
|
|
336
|
+
|
|
337
|
+
function getProjectFromUnknownParams(
|
|
338
|
+
params: object | any[] | undefined,
|
|
339
|
+
workspace: GraphQLWorkspace,
|
|
340
|
+
) {
|
|
341
|
+
const param0 = Array.isArray(params) ? params[0] : params;
|
|
342
|
+
if (!param0) return;
|
|
343
|
+
let uri: string | undefined;
|
|
344
|
+
if (typeof param0 === "string" && param0.startsWith("file://")) {
|
|
345
|
+
uri = param0;
|
|
346
|
+
} else if (
|
|
347
|
+
typeof param0 === "object" &&
|
|
348
|
+
"uri" in param0 &&
|
|
349
|
+
param0.uri &&
|
|
350
|
+
typeof param0.uri === "string"
|
|
351
|
+
) {
|
|
352
|
+
uri = param0.uri;
|
|
353
|
+
} else if (
|
|
354
|
+
typeof param0 === "object" &&
|
|
355
|
+
param0.textDocument &&
|
|
356
|
+
typeof param0.textDocument === "object" &&
|
|
357
|
+
param0.textDocument.uri &&
|
|
358
|
+
typeof param0.textDocument.uri === "string"
|
|
359
|
+
) {
|
|
360
|
+
uri = param0.textDocument.uri;
|
|
361
|
+
}
|
|
362
|
+
return uri ? workspace.projectForFile(uri) : undefined;
|
|
363
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { extractGraphQLSources } from "../../document";
|
|
2
|
+
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
3
|
+
import {
|
|
4
|
+
findContainedSourceAndPosition,
|
|
5
|
+
positionFromPositionInContainingDocument,
|
|
6
|
+
positionInContainingDocument,
|
|
7
|
+
} from "../source";
|
|
8
|
+
import { handleFilePartUpdates } from "../../project/rover/DocumentSynchronization";
|
|
9
|
+
|
|
10
|
+
const testText = `import gql from "graphql-tag";
|
|
11
|
+
|
|
12
|
+
const foo = 1
|
|
13
|
+
|
|
14
|
+
gql\`
|
|
15
|
+
query Test {
|
|
16
|
+
droid(id: "2000") {
|
|
17
|
+
name
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
}\`;
|
|
21
|
+
|
|
22
|
+
const verylonglala = gql\`type Foo { baaaaaar: String }\`
|
|
23
|
+
`;
|
|
24
|
+
describe("positionFromPositionInContainingDocument", () => {
|
|
25
|
+
const sources = extractGraphQLSources(
|
|
26
|
+
TextDocument.create("uri", "javascript", 1, testText),
|
|
27
|
+
"gql",
|
|
28
|
+
)!;
|
|
29
|
+
|
|
30
|
+
test("should return the correct position inside a document", () => {
|
|
31
|
+
expect(
|
|
32
|
+
positionFromPositionInContainingDocument(sources[0], {
|
|
33
|
+
line: 5,
|
|
34
|
+
character: 3,
|
|
35
|
+
}),
|
|
36
|
+
).toEqual({ line: 1, character: 3 });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("should return the correct position on the first line of a document", () => {
|
|
40
|
+
expect(
|
|
41
|
+
positionFromPositionInContainingDocument(sources[0], {
|
|
42
|
+
line: 4,
|
|
43
|
+
character: 4,
|
|
44
|
+
}),
|
|
45
|
+
).toEqual({ line: 0, character: 0 });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("should return the correct position on a single line document", () => {
|
|
49
|
+
expect(
|
|
50
|
+
positionFromPositionInContainingDocument(sources[1], {
|
|
51
|
+
line: 12,
|
|
52
|
+
character: 46,
|
|
53
|
+
}),
|
|
54
|
+
).toEqual({ line: 0, character: 21 });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("findContainedSourceAndPosition", () => {
|
|
59
|
+
const parts = handleFilePartUpdates(
|
|
60
|
+
extractGraphQLSources(
|
|
61
|
+
TextDocument.create("uri", "javascript", 1, testText),
|
|
62
|
+
"gql",
|
|
63
|
+
)!,
|
|
64
|
+
[],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
test("should return the correct position inside a document", () => {
|
|
68
|
+
expect(
|
|
69
|
+
findContainedSourceAndPosition(parts, {
|
|
70
|
+
line: 5,
|
|
71
|
+
character: 3,
|
|
72
|
+
}),
|
|
73
|
+
).toEqual({ ...parts[0], position: { line: 1, character: 3 } });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("should return the correct position on the first line of a document", () => {
|
|
77
|
+
expect(
|
|
78
|
+
findContainedSourceAndPosition(parts, {
|
|
79
|
+
line: 4,
|
|
80
|
+
character: 4,
|
|
81
|
+
}),
|
|
82
|
+
).toEqual({ ...parts[0], position: { line: 0, character: 0 } });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("should return the correct position on the last line of a document", () => {
|
|
86
|
+
expect(
|
|
87
|
+
findContainedSourceAndPosition(parts, {
|
|
88
|
+
line: 10,
|
|
89
|
+
character: 0,
|
|
90
|
+
}),
|
|
91
|
+
).toEqual({ ...parts[0], position: { line: 6, character: 0 } });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("should return null if the position is outside of the document", () => {
|
|
95
|
+
expect(
|
|
96
|
+
findContainedSourceAndPosition(parts, {
|
|
97
|
+
line: 4,
|
|
98
|
+
character: 3,
|
|
99
|
+
}),
|
|
100
|
+
).toBeNull();
|
|
101
|
+
expect(
|
|
102
|
+
findContainedSourceAndPosition(parts, {
|
|
103
|
+
line: 10,
|
|
104
|
+
character: 1,
|
|
105
|
+
}),
|
|
106
|
+
).toBeNull();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("should return the correct position on a single line document", () => {
|
|
110
|
+
expect(
|
|
111
|
+
findContainedSourceAndPosition(parts, {
|
|
112
|
+
line: 12,
|
|
113
|
+
character: 46,
|
|
114
|
+
}),
|
|
115
|
+
).toEqual({ ...parts[1], position: { line: 0, character: 21 } });
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe("positionInContainingDocument", () => {
|
|
119
|
+
const parts = handleFilePartUpdates(
|
|
120
|
+
extractGraphQLSources(
|
|
121
|
+
TextDocument.create("uri", "javascript", 1, testText),
|
|
122
|
+
"gql",
|
|
123
|
+
)!,
|
|
124
|
+
[],
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
test("should return the correct position inside a document", () => {
|
|
128
|
+
expect(
|
|
129
|
+
positionInContainingDocument(parts[0].source, {
|
|
130
|
+
line: 1,
|
|
131
|
+
character: 3,
|
|
132
|
+
}),
|
|
133
|
+
).toEqual({ line: 5, character: 3 });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("should return the correct position on the first line of a document", () => {
|
|
137
|
+
expect(
|
|
138
|
+
positionInContainingDocument(parts[0].source, {
|
|
139
|
+
line: 0,
|
|
140
|
+
character: 0,
|
|
141
|
+
}),
|
|
142
|
+
).toEqual({ line: 4, character: 4 });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("should return the correct position on the last line of a document", () => {
|
|
146
|
+
expect(
|
|
147
|
+
positionInContainingDocument(parts[0].source, {
|
|
148
|
+
line: 6,
|
|
149
|
+
character: 0,
|
|
150
|
+
}),
|
|
151
|
+
).toEqual({ line: 10, character: 0 });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("should return the correct position on a single line document", () => {
|
|
155
|
+
expect(
|
|
156
|
+
positionInContainingDocument(parts[1].source, {
|
|
157
|
+
line: 0,
|
|
158
|
+
character: 21,
|
|
159
|
+
}),
|
|
160
|
+
).toEqual({ line: 12, character: 46 });
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FileExtension,
|
|
3
|
+
LanguageIdExtensionMap,
|
|
4
|
+
supportedLanguageIds,
|
|
5
|
+
} from "../../tools/utilities/languageInformation";
|
|
6
|
+
|
|
7
|
+
let languageIdPerExtension: Record<FileExtension, string> | undefined;
|
|
8
|
+
let supportedExtensions: FileExtension[] | undefined;
|
|
9
|
+
|
|
10
|
+
export function setLanguageIdExtensionMap(map: LanguageIdExtensionMap) {
|
|
11
|
+
languageIdPerExtension = Object.fromEntries(
|
|
12
|
+
Object.entries(map).flatMap(([languageId, extensions]) =>
|
|
13
|
+
extensions.map((extension) => [extension, languageId]),
|
|
14
|
+
),
|
|
15
|
+
);
|
|
16
|
+
supportedExtensions = supportedLanguageIds.flatMap(
|
|
17
|
+
(languageId) => map[languageId],
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @throws if called before the language server has received options via `onInitialize`.
|
|
23
|
+
*/
|
|
24
|
+
export function getLanguageIdForExtension(ext: FileExtension) {
|
|
25
|
+
if (!languageIdPerExtension) {
|
|
26
|
+
throw new Error("LanguageIdExtensionMap not set");
|
|
27
|
+
}
|
|
28
|
+
return languageIdPerExtension[ext];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @throws if called before the language server has received options via `onInitialize`.
|
|
33
|
+
*/
|
|
34
|
+
export function getSupportedExtensions() {
|
|
35
|
+
if (!supportedExtensions) {
|
|
36
|
+
throw new Error("LanguageIdExtensionMap not set");
|
|
37
|
+
}
|
|
38
|
+
return supportedExtensions;
|
|
39
|
+
}
|
|
@@ -65,14 +65,48 @@ export function visitWithTypeInfo(
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
export function findContainedSourceAndPosition<T extends { source: Source }>(
|
|
69
|
+
parts: ReadonlyArray<T>,
|
|
70
|
+
absolutePosition: Position,
|
|
71
|
+
) {
|
|
72
|
+
for (const part of parts) {
|
|
73
|
+
const lines = part.source.body.split("\n");
|
|
74
|
+
const position = positionFromPositionInContainingDocument(
|
|
75
|
+
part.source,
|
|
76
|
+
absolutePosition,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// we are in a sub-document that's beyond the position we're looking for
|
|
80
|
+
// exit early to save on computing time
|
|
81
|
+
if (position.line < 0) return null;
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
position.line >= 0 &&
|
|
85
|
+
position.line < lines.length &&
|
|
86
|
+
position.character >= 0 &&
|
|
87
|
+
(position.line < lines.length - 1 ||
|
|
88
|
+
position.character < lines[position.line].length)
|
|
89
|
+
) {
|
|
90
|
+
return {
|
|
91
|
+
...part,
|
|
92
|
+
position,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
68
99
|
export function positionFromPositionInContainingDocument(
|
|
69
100
|
source: Source,
|
|
70
101
|
position: Position,
|
|
71
102
|
) {
|
|
72
103
|
if (!source.locationOffset) return position;
|
|
104
|
+
const line = position.line - (source.locationOffset.line - 1);
|
|
73
105
|
return Position.create(
|
|
74
|
-
|
|
75
|
-
|
|
106
|
+
line,
|
|
107
|
+
line === 0
|
|
108
|
+
? position.character - (source.locationOffset.column - 1)
|
|
109
|
+
: position.character,
|
|
76
110
|
);
|
|
77
111
|
}
|
|
78
112
|
|
|
@@ -83,7 +117,8 @@ export function positionInContainingDocument(
|
|
|
83
117
|
if (!source.locationOffset) return position;
|
|
84
118
|
return Position.create(
|
|
85
119
|
source.locationOffset.line - 1 + position.line,
|
|
86
|
-
position.
|
|
120
|
+
(position.line === 0 ? source.locationOffset.column - 1 : 0) +
|
|
121
|
+
position.character,
|
|
87
122
|
);
|
|
88
123
|
}
|
|
89
124
|
|