vscode-apollo 2.0.0 → 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.
Files changed (39) hide show
  1. package/.circleci/config.yml +1 -1
  2. package/.vscode/launch.json +4 -1
  3. package/CHANGELOG.md +33 -0
  4. package/package.json +9 -3
  5. package/renovate.json +2 -1
  6. package/sampleWorkspace/localSchema/src/test.js +3 -0
  7. package/sampleWorkspace/rover/apollo.config.js +3 -0
  8. package/sampleWorkspace/rover/src/test.graphql +14 -0
  9. package/sampleWorkspace/rover/src/test.js +30 -0
  10. package/sampleWorkspace/sampleWorkspace.code-workspace +25 -19
  11. package/src/language-server/__tests__/document.test.ts +161 -3
  12. package/src/language-server/__tests__/fixtures/TypeScript.tmLanguage.json +5749 -0
  13. package/src/language-server/__tests__/fixtures/documents/commentWithTemplate.ts +41 -0
  14. package/src/language-server/__tests__/fixtures/documents/commentWithTemplate.ts.snap +185 -0
  15. package/src/language-server/__tests__/fixtures/documents/functionCall.ts +93 -0
  16. package/src/language-server/__tests__/fixtures/documents/functionCall.ts.snap +431 -0
  17. package/src/language-server/__tests__/fixtures/documents/taggedTemplate.ts +80 -0
  18. package/src/language-server/__tests__/fixtures/documents/taggedTemplate.ts.snap +353 -0
  19. package/src/language-server/__tests__/fixtures/documents/templateWithComment.ts +38 -0
  20. package/src/language-server/__tests__/fixtures/documents/templateWithComment.ts.snap +123 -0
  21. package/src/language-server/config/__tests__/loadConfig.ts +43 -10
  22. package/src/language-server/config/config.ts +26 -1
  23. package/src/language-server/config/loadConfig.ts +7 -1
  24. package/src/language-server/config/loadTsConfig.ts +70 -0
  25. package/src/language-server/config/which.d.ts +19 -0
  26. package/src/language-server/document.ts +86 -53
  27. package/src/language-server/fileSet.ts +7 -0
  28. package/src/language-server/project/base.ts +58 -316
  29. package/src/language-server/project/client.ts +730 -7
  30. package/src/language-server/project/internal.ts +349 -0
  31. package/src/language-server/project/rover/DocumentSynchronization.ts +308 -0
  32. package/src/language-server/project/rover/__tests__/DocumentSynchronization.test.ts +302 -0
  33. package/src/language-server/project/rover/project.ts +276 -0
  34. package/src/language-server/server.ts +129 -62
  35. package/src/language-server/utilities/__tests__/source.test.ts +162 -0
  36. package/src/language-server/utilities/source.ts +38 -3
  37. package/src/language-server/workspace.ts +34 -9
  38. package/syntaxes/graphql.js.json +18 -21
  39. 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
- debounceHandler((params) => {
147
- const project = workspace.projectForFile(params.document.uri);
148
- if (!project) return;
149
+ documents.onDidChangeContent((params) => {
150
+ const project = workspace.projectForFile(params.document.uri);
151
+ if (!project) return;
149
152
 
150
- // Only watch changes to files
151
- if (!isFile(params.document.uri)) {
152
- return;
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
- project.documentDidChange(params.document);
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
- switch (type) {
186
- case FileChangeType.Created:
187
- project.fileDidChange(uri);
188
- break;
189
- case FileChangeType.Deleted:
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
- const languageProvider = new GraphQLLanguageProvider(workspace);
197
-
198
- connection.onHover((params, token) =>
199
- languageProvider.provideHover(
200
- params.textDocument.uri,
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((params, token) =>
207
- languageProvider.provideDefinition(
208
- params.textDocument.uri,
209
- params.position,
210
- token,
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((params, token) =>
215
- languageProvider.provideReferences(
216
- params.textDocument.uri,
217
- params.position,
218
- params.context,
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((params, token) =>
224
- languageProvider.provideDocumentSymbol(params.textDocument.uri, token),
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
- languageProvider.provideWorkspaceSymbol(params.query, token),
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((params, token) =>
233
- languageProvider.provideCompletionItems(
234
- params.textDocument.uri,
235
- params.position,
236
- token,
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((params, token) =>
243
- languageProvider.provideCodeLenses(params.textDocument.uri, token),
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((params, token) =>
249
- languageProvider.provideCodeAction(
250
- params.textDocument.uri,
251
- params.range,
252
- token,
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 languageProvider.provideStats(uri);
267
- connection.sendNotification(Notifications.StatsLoaded, status);
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 languageProvider.provideStats(uri);
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
- position.line - (source.locationOffset.line - 1),
75
- position.character,
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.character,
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("TODO rover project");
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((project) => {
161
- project.clearAllDiagnostics();
162
- return this.createProject({
163
- config: project.config,
164
- folder: { uri } as WorkspaceFolder,
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) => project.clearAllDiagnostics());
252
+ projects.forEach((project) => {
253
+ project.clearAllDiagnostics();
254
+ project.dispose?.();
255
+ });
231
256
  this.projectsByFolderUri.delete(folder.uri);
232
257
  }
233
258
  }
@@ -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": "entity.name.function.tagged-template.js"
16
+ "name": "meta.embedded.block.graphql comment.line.graphql.js"
12
17
  },
13
18
  "2": {
14
- "name": "punctuation.definition.string.template.begin.js"
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": "(`)(#graphql)",
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": "punctuation.definition.string.template.begin.js"
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
  ],