vscode-apollo 2.0.1 → 2.1.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 +4 -1
- package/CHANGELOG.md +27 -0
- package/package.json +9 -3
- 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 +19 -10
- package/src/language-server/config/config.ts +26 -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 +7 -0
- package/src/language-server/project/base.ts +58 -316
- package/src/language-server/project/client.ts +730 -7
- package/src/language-server/project/internal.ts +349 -0
- package/src/language-server/project/rover/DocumentSynchronization.ts +308 -0
- package/src/language-server/project/rover/__tests__/DocumentSynchronization.test.ts +302 -0
- package/src/language-server/project/rover/project.ts +276 -0
- package/src/language-server/server.ts +129 -62
- package/src/language-server/utilities/__tests__/source.test.ts +162 -0
- package/src/language-server/utilities/source.ts +38 -3
- package/src/language-server/workspace.ts +34 -9
- package/syntaxes/graphql.js.json +18 -21
- package/src/language-server/languageProvider.ts +0 -795
|
@@ -6,11 +6,12 @@ import {
|
|
|
6
6
|
FileChangeType,
|
|
7
7
|
ServerCapabilities,
|
|
8
8
|
TextDocumentSyncKind,
|
|
9
|
+
SymbolInformation,
|
|
10
|
+
FileEvent,
|
|
9
11
|
} from "vscode-languageserver/node";
|
|
10
12
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
11
13
|
import type { QuickPickItem } from "vscode";
|
|
12
14
|
import { GraphQLWorkspace } from "./workspace";
|
|
13
|
-
import { GraphQLLanguageProvider } from "./languageProvider";
|
|
14
15
|
import { LanguageServerLoadingHandler } from "./loadingHandler";
|
|
15
16
|
import { debounceHandler, Debug } from "./utilities";
|
|
16
17
|
import { URI } from "vscode-uri";
|
|
@@ -20,6 +21,8 @@ import {
|
|
|
20
21
|
LanguageServerRequests as Requests,
|
|
21
22
|
} from "../messages";
|
|
22
23
|
import { isValidationError } from "zod-validation-error";
|
|
24
|
+
import { Trie } from "@wry/trie";
|
|
25
|
+
import { GraphQLProject } from "./project/base";
|
|
23
26
|
|
|
24
27
|
const connection = createConnection(ProposedFeatures.all);
|
|
25
28
|
|
|
@@ -85,6 +88,7 @@ connection.onInitialize(async ({ capabilities, workspaceFolders }) => {
|
|
|
85
88
|
hasWorkspaceFolderCapability = !!(
|
|
86
89
|
capabilities.workspace && capabilities.workspace.workspaceFolders
|
|
87
90
|
);
|
|
91
|
+
workspace.capabilities = capabilities;
|
|
88
92
|
|
|
89
93
|
if (workspaceFolders) {
|
|
90
94
|
// We wait until all projects are added, because after `initialize` returns we can get additional requests
|
|
@@ -142,21 +146,30 @@ function isFile(uri: string) {
|
|
|
142
146
|
return URI.parse(uri).scheme === "file";
|
|
143
147
|
}
|
|
144
148
|
|
|
145
|
-
documents.onDidChangeContent(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (!project) return;
|
|
149
|
+
documents.onDidChangeContent((params) => {
|
|
150
|
+
const project = workspace.projectForFile(params.document.uri);
|
|
151
|
+
if (!project) return;
|
|
149
152
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
// Only watch changes to files
|
|
154
|
+
if (!isFile(params.document.uri)) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
project.documentDidChange(params.document);
|
|
159
|
+
});
|
|
154
160
|
|
|
155
|
-
|
|
156
|
-
|
|
161
|
+
documents.onDidOpen(
|
|
162
|
+
(params) =>
|
|
163
|
+
workspace.projectForFile(params.document.uri)?.onDidOpen?.(params),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
documents.onDidClose(
|
|
167
|
+
(params) =>
|
|
168
|
+
workspace.projectForFile(params.document.uri)?.onDidClose?.(params),
|
|
157
169
|
);
|
|
158
170
|
|
|
159
171
|
connection.onDidChangeWatchedFiles((params) => {
|
|
172
|
+
const handledByProject = new Map<GraphQLProject, FileEvent[]>();
|
|
160
173
|
for (const { uri, type } of params.changes) {
|
|
161
174
|
if (
|
|
162
175
|
uri.endsWith("apollo.config.js") ||
|
|
@@ -182,75 +195,79 @@ connection.onDidChangeWatchedFiles((params) => {
|
|
|
182
195
|
const project = workspace.projectForFile(uri);
|
|
183
196
|
if (!project) continue;
|
|
184
197
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
project.fileWasDeleted(uri);
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
198
|
+
handledByProject.set(project, handledByProject.get(project) || []);
|
|
199
|
+
handledByProject.get(project)!.push({ uri, type });
|
|
200
|
+
}
|
|
201
|
+
for (const [project, changes] of handledByProject) {
|
|
202
|
+
project.onDidChangeWatchedFiles({ changes });
|
|
193
203
|
}
|
|
194
204
|
});
|
|
195
205
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
params.position,
|
|
202
|
-
token,
|
|
203
|
-
),
|
|
206
|
+
connection.onHover(
|
|
207
|
+
(params, token, workDoneProgress, resultProgress) =>
|
|
208
|
+
workspace
|
|
209
|
+
.projectForFile(params.textDocument.uri)
|
|
210
|
+
?.onHover?.(params, token, workDoneProgress, resultProgress) ?? null,
|
|
204
211
|
);
|
|
205
212
|
|
|
206
|
-
connection.onDefinition(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
),
|
|
213
|
+
connection.onDefinition(
|
|
214
|
+
(params, token, workDoneProgress, resultProgress) =>
|
|
215
|
+
workspace
|
|
216
|
+
.projectForFile(params.textDocument.uri)
|
|
217
|
+
?.onDefinition?.(params, token, workDoneProgress, resultProgress) ?? null,
|
|
212
218
|
);
|
|
213
219
|
|
|
214
|
-
connection.onReferences(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
token,
|
|
220
|
-
),
|
|
220
|
+
connection.onReferences(
|
|
221
|
+
(params, token, workDoneProgress, resultProgress) =>
|
|
222
|
+
workspace
|
|
223
|
+
.projectForFile(params.textDocument.uri)
|
|
224
|
+
?.onReferences?.(params, token, workDoneProgress, resultProgress) ?? null,
|
|
221
225
|
);
|
|
222
226
|
|
|
223
|
-
connection.onDocumentSymbol(
|
|
224
|
-
|
|
227
|
+
connection.onDocumentSymbol(
|
|
228
|
+
(params, token, workDoneProgress, resultProgress) =>
|
|
229
|
+
workspace
|
|
230
|
+
.projectForFile(params.textDocument.uri)
|
|
231
|
+
?.onDocumentSymbol?.(params, token, workDoneProgress, resultProgress) ??
|
|
232
|
+
[],
|
|
225
233
|
);
|
|
226
234
|
|
|
227
|
-
connection.onWorkspaceSymbol((params, token) =>
|
|
228
|
-
|
|
229
|
-
|
|
235
|
+
connection.onWorkspaceSymbol(async (params, token) => {
|
|
236
|
+
const symbols: SymbolInformation[] = [];
|
|
237
|
+
const symbolPromises = workspace.projects.map(
|
|
238
|
+
(project) =>
|
|
239
|
+
project.provideSymbol?.(params.query, token) || Promise.resolve([]),
|
|
240
|
+
);
|
|
241
|
+
for (const projectSymbols of await Promise.all(symbolPromises)) {
|
|
242
|
+
symbols.push(...projectSymbols);
|
|
243
|
+
}
|
|
244
|
+
return symbols;
|
|
245
|
+
});
|
|
230
246
|
|
|
231
247
|
connection.onCompletion(
|
|
232
|
-
debounceHandler(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
),
|
|
248
|
+
debounceHandler(
|
|
249
|
+
(params, token, workDoneProgress, resultProgress) =>
|
|
250
|
+
workspace
|
|
251
|
+
.projectForFile(params.textDocument.uri)
|
|
252
|
+
?.onCompletion?.(params, token, workDoneProgress, resultProgress) ?? [],
|
|
238
253
|
),
|
|
239
254
|
);
|
|
240
255
|
|
|
241
256
|
connection.onCodeLens(
|
|
242
|
-
debounceHandler(
|
|
243
|
-
|
|
257
|
+
debounceHandler(
|
|
258
|
+
(params, token, workDoneProgress, resultProgress) =>
|
|
259
|
+
workspace
|
|
260
|
+
.projectForFile(params.textDocument.uri)
|
|
261
|
+
?.onCodeLens?.(params, token, workDoneProgress, resultProgress) ?? [],
|
|
244
262
|
),
|
|
245
263
|
);
|
|
246
264
|
|
|
247
265
|
connection.onCodeAction(
|
|
248
|
-
debounceHandler(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
),
|
|
266
|
+
debounceHandler(
|
|
267
|
+
(params, token, workDoneProgress, resultProgress) =>
|
|
268
|
+
workspace
|
|
269
|
+
.projectForFile(params.textDocument.uri)
|
|
270
|
+
?.onCodeAction?.(params, token, workDoneProgress, resultProgress) ?? [],
|
|
254
271
|
),
|
|
255
272
|
);
|
|
256
273
|
|
|
@@ -263,12 +280,62 @@ connection.onNotification(Commands.TagSelected, (selection: QuickPickItem) =>
|
|
|
263
280
|
);
|
|
264
281
|
|
|
265
282
|
connection.onNotification(Commands.GetStats, async ({ uri }) => {
|
|
266
|
-
const status = await
|
|
267
|
-
connection.sendNotification(
|
|
283
|
+
const status = await workspace.projectForFile(uri)?.getProjectStats();
|
|
284
|
+
connection.sendNotification(
|
|
285
|
+
Notifications.StatsLoaded,
|
|
286
|
+
status ?? {
|
|
287
|
+
loaded: false,
|
|
288
|
+
},
|
|
289
|
+
);
|
|
268
290
|
});
|
|
269
291
|
connection.onRequest(Requests.FileStats, async ({ uri }) => {
|
|
270
|
-
return
|
|
292
|
+
return workspace.projectForFile(uri)?.getProjectStats() ?? { loaded: false };
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
connection.onRequest((method, params, token) => {
|
|
296
|
+
return getProjectFromUnknownParams(params, workspace)?.onUnhandledRequest?.(
|
|
297
|
+
method,
|
|
298
|
+
params,
|
|
299
|
+
token,
|
|
300
|
+
);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
connection.onNotification((method, params) => {
|
|
304
|
+
return getProjectFromUnknownParams(
|
|
305
|
+
params,
|
|
306
|
+
workspace,
|
|
307
|
+
)?.onUnhandledNotification?.(connection, method, params);
|
|
271
308
|
});
|
|
272
309
|
|
|
273
310
|
// Listen on the connection
|
|
274
311
|
connection.listen();
|
|
312
|
+
|
|
313
|
+
// ------------------------------ utility functions ------------------------------
|
|
314
|
+
|
|
315
|
+
function getProjectFromUnknownParams(
|
|
316
|
+
params: object | any[] | undefined,
|
|
317
|
+
workspace: GraphQLWorkspace,
|
|
318
|
+
) {
|
|
319
|
+
const param0 = Array.isArray(params) ? params[0] : params;
|
|
320
|
+
if (!param0) return;
|
|
321
|
+
let uri: string | undefined;
|
|
322
|
+
if (typeof param0 === "string" && param0.startsWith("file://")) {
|
|
323
|
+
uri = param0;
|
|
324
|
+
} else if (
|
|
325
|
+
typeof param0 === "object" &&
|
|
326
|
+
"uri" in param0 &&
|
|
327
|
+
param0.uri &&
|
|
328
|
+
typeof param0.uri === "string"
|
|
329
|
+
) {
|
|
330
|
+
uri = param0.uri;
|
|
331
|
+
} else if (
|
|
332
|
+
typeof param0 === "object" &&
|
|
333
|
+
param0.textDocument &&
|
|
334
|
+
typeof param0.textDocument === "object" &&
|
|
335
|
+
param0.textDocument.uri &&
|
|
336
|
+
typeof param0.textDocument.uri === "string"
|
|
337
|
+
) {
|
|
338
|
+
uri = param0.textDocument.uri;
|
|
339
|
+
}
|
|
340
|
+
return uri ? workspace.projectForFile(uri) : undefined;
|
|
341
|
+
}
|
|
@@ -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
|
+
});
|
|
@@ -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
|
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
WorkspaceFolder,
|
|
3
3
|
NotificationHandler,
|
|
4
4
|
PublishDiagnosticsParams,
|
|
5
|
+
ClientCapabilities,
|
|
5
6
|
} from "vscode-languageserver/node";
|
|
6
7
|
import { QuickPickItem } from "vscode";
|
|
7
8
|
import { GraphQLProject, DocumentUri } from "./project/base";
|
|
@@ -15,6 +16,7 @@ import { URI } from "vscode-uri";
|
|
|
15
16
|
import { Debug } from "./utilities";
|
|
16
17
|
import type { EngineDecoration } from "../messages";
|
|
17
18
|
import { equal } from "@wry/equality";
|
|
19
|
+
import { isRoverConfig, RoverProject } from "./project/rover/project";
|
|
18
20
|
|
|
19
21
|
export interface WorkspaceConfig {
|
|
20
22
|
clientIdentity: ClientIdentity;
|
|
@@ -26,6 +28,7 @@ export class GraphQLWorkspace {
|
|
|
26
28
|
private _onSchemaTags?: NotificationHandler<[ServiceID, SchemaTag[]]>;
|
|
27
29
|
private _onConfigFilesFound?: NotificationHandler<(ApolloConfig | Error)[]>;
|
|
28
30
|
private _projectForFileCache: Map<string, GraphQLProject> = new Map();
|
|
31
|
+
public capabilities?: ClientCapabilities;
|
|
29
32
|
|
|
30
33
|
private projectsByFolderUri: Map<string, GraphQLProject[]> = new Map();
|
|
31
34
|
|
|
@@ -65,8 +68,15 @@ export class GraphQLWorkspace {
|
|
|
65
68
|
configFolderURI: URI.parse(folder.uri),
|
|
66
69
|
clientIdentity,
|
|
67
70
|
})
|
|
71
|
+
: isRoverConfig(config)
|
|
72
|
+
? new RoverProject({
|
|
73
|
+
config,
|
|
74
|
+
loadingHandler: this.LanguageServerLoadingHandler,
|
|
75
|
+
configFolderURI: URI.parse(folder.uri),
|
|
76
|
+
capabilities: this.capabilities!, // TODO?
|
|
77
|
+
})
|
|
68
78
|
: (() => {
|
|
69
|
-
throw new Error("
|
|
79
|
+
throw new Error("Impossible config!");
|
|
70
80
|
})();
|
|
71
81
|
|
|
72
82
|
project.onDiagnostics((params) => {
|
|
@@ -86,7 +96,7 @@ export class GraphQLWorkspace {
|
|
|
86
96
|
// after a project has loaded, we do an initial validation to surface errors
|
|
87
97
|
// on the start of the language server. Instead of doing this in the
|
|
88
98
|
// base class which is used by codegen and other tools
|
|
89
|
-
project.whenReady.then(() => project.validate());
|
|
99
|
+
project.whenReady.then(() => project.validate?.());
|
|
90
100
|
|
|
91
101
|
return project;
|
|
92
102
|
}
|
|
@@ -157,12 +167,24 @@ export class GraphQLWorkspace {
|
|
|
157
167
|
this.projectsByFolderUri.forEach((projects, uri) => {
|
|
158
168
|
this.projectsByFolderUri.set(
|
|
159
169
|
uri,
|
|
160
|
-
projects.map((
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
170
|
+
projects.map((oldProject) => {
|
|
171
|
+
oldProject.clearAllDiagnostics();
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const newProject = this.createProject({
|
|
175
|
+
config: oldProject.config,
|
|
176
|
+
folder: { uri } as WorkspaceFolder,
|
|
177
|
+
});
|
|
178
|
+
if (
|
|
179
|
+
oldProject instanceof RoverProject &&
|
|
180
|
+
newProject instanceof RoverProject
|
|
181
|
+
) {
|
|
182
|
+
newProject.restoreFromPreviousProject(oldProject);
|
|
183
|
+
}
|
|
184
|
+
return newProject;
|
|
185
|
+
} finally {
|
|
186
|
+
oldProject.dispose?.();
|
|
187
|
+
}
|
|
166
188
|
}),
|
|
167
189
|
);
|
|
168
190
|
});
|
|
@@ -227,7 +249,10 @@ export class GraphQLWorkspace {
|
|
|
227
249
|
removeProjectsInFolder(folder: WorkspaceFolder) {
|
|
228
250
|
const projects = this.projectsByFolderUri.get(folder.uri);
|
|
229
251
|
if (projects) {
|
|
230
|
-
projects.forEach((project) =>
|
|
252
|
+
projects.forEach((project) => {
|
|
253
|
+
project.clearAllDiagnostics();
|
|
254
|
+
project.dispose?.();
|
|
255
|
+
});
|
|
231
256
|
this.projectsByFolderUri.delete(folder.uri);
|
|
232
257
|
}
|
|
233
258
|
}
|
package/syntaxes/graphql.js.json
CHANGED
|
@@ -3,43 +3,40 @@
|
|
|
3
3
|
"injectionSelector": "L:source -string -comment",
|
|
4
4
|
"patterns": [
|
|
5
5
|
{
|
|
6
|
-
"name": "taggedTemplates",
|
|
7
6
|
"contentName": "meta.embedded.block.graphql",
|
|
8
|
-
"begin": "(Relay\\.QL|gql|graphql(\\.experimental)?)\\(
|
|
7
|
+
"begin": "(?:Relay\\.QL|gql|graphql(\\.experimental)?)\\s*(?:<.*?>\\s*)?`",
|
|
8
|
+
"end": "`",
|
|
9
|
+
"patterns": [{ "include": "source.graphql" }]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"contentName": "meta.embedded.block.graphql",
|
|
13
|
+
"begin": "`(\\s*#[ ]*(gql|graphql|GraphQL))",
|
|
9
14
|
"beginCaptures": {
|
|
10
15
|
"1": {
|
|
11
|
-
"name": "
|
|
16
|
+
"name": "meta.embedded.block.graphql comment.line.graphql.js"
|
|
12
17
|
},
|
|
13
18
|
"2": {
|
|
14
|
-
"name": "
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
"end": "`\\)?",
|
|
18
|
-
"endCaptures": {
|
|
19
|
-
"0": {
|
|
20
|
-
"name": "punctuation.definition.string.template.end.js"
|
|
19
|
+
"name": "markup.italic"
|
|
21
20
|
}
|
|
22
21
|
},
|
|
22
|
+
"end": "`",
|
|
23
23
|
"patterns": [{ "include": "source.graphql" }]
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
|
-
"name": "taggedTemplates",
|
|
27
26
|
"contentName": "meta.embedded.block.graphql",
|
|
28
|
-
"begin": "(
|
|
27
|
+
"begin": "(?:gql|graphql)\\s*(?:<.*?>\\s*)?\\(\\s*`",
|
|
28
|
+
"end": "`\\s*\\)",
|
|
29
|
+
"patterns": [{ "include": "source.graphql" }]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"contentName": "meta.embedded.block.graphql",
|
|
33
|
+
"begin": "(?:\\/\\*[\\s\\*]*(gql|graphql|GraphQL)\\s*\\*\\/)\\s*(`)",
|
|
29
34
|
"beginCaptures": {
|
|
30
35
|
"1": {
|
|
31
|
-
"name": "
|
|
32
|
-
},
|
|
33
|
-
"2": {
|
|
34
|
-
"name": "comment.line.graphql.js"
|
|
36
|
+
"name": "markup.italic"
|
|
35
37
|
}
|
|
36
38
|
},
|
|
37
39
|
"end": "`",
|
|
38
|
-
"endCaptures": {
|
|
39
|
-
"0": {
|
|
40
|
-
"name": "punctuation.definition.string.template.end.js"
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
40
|
"patterns": [{ "include": "source.graphql" }]
|
|
44
41
|
}
|
|
45
42
|
],
|