vscode-apollo 1.19.3 → 2.0.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 (179) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +14 -0
  3. package/.circleci/config.yml +91 -0
  4. package/.eslintrc.js +10 -0
  5. package/.git-blame-ignore-revs +2 -0
  6. package/.gitattributes +1 -0
  7. package/.github/workflows/release.yml +95 -0
  8. package/.gitleaks.toml +26 -0
  9. package/.nvmrc +1 -0
  10. package/.prettierrc +5 -0
  11. package/.vscode/launch.json +66 -0
  12. package/.vscode/settings.json +16 -0
  13. package/.vscode/tasks.json +60 -0
  14. package/.vscodeignore +28 -1
  15. package/CHANGELOG.md +250 -1
  16. package/CODEOWNERS +4 -0
  17. package/LICENSE +2 -2
  18. package/README.md +104 -55
  19. package/codegen.yml +12 -0
  20. package/graphql.configuration.json +5 -1
  21. package/images/IconRun.svg +8 -0
  22. package/images/marketplace/apollo-wordmark.png +0 -0
  23. package/jest.config.ts +21 -0
  24. package/jest.e2e.config.js +17 -0
  25. package/package.json +102 -23
  26. package/renovate.json +30 -0
  27. package/sampleWorkspace/clientSchema/apollo.config.cjs +10 -0
  28. package/sampleWorkspace/clientSchema/src/clientSchema.js +16 -0
  29. package/sampleWorkspace/clientSchema/src/test.js +18 -0
  30. package/sampleWorkspace/fixtures/starwarsSchema.graphql +299 -0
  31. package/sampleWorkspace/httpSchema/apollo.config.ts +8 -0
  32. package/sampleWorkspace/httpSchema/src/test.js +9 -0
  33. package/sampleWorkspace/localSchema/apollo.config.js +8 -0
  34. package/sampleWorkspace/localSchema/src/test.js +8 -0
  35. package/sampleWorkspace/localSchemaArray/apollo.config.js +12 -0
  36. package/sampleWorkspace/localSchemaArray/planets.graphql +20 -0
  37. package/sampleWorkspace/localSchemaArray/src/test.js +12 -0
  38. package/sampleWorkspace/sampleWorkspace.code-workspace +20 -0
  39. package/sampleWorkspace/spotifyGraph/apollo.config.mjs +5 -0
  40. package/sampleWorkspace/spotifyGraph/src/test.js +11 -0
  41. package/src/__e2e__/mockServer.js +117 -0
  42. package/src/__e2e__/mocks.js +13094 -0
  43. package/src/__e2e__/run.js +23 -0
  44. package/src/__e2e__/runTests.js +44 -0
  45. package/src/__e2e__/setup.js +1 -0
  46. package/src/__e2e__/vscode-environment.js +16 -0
  47. package/src/__e2e__/vscode.js +1 -0
  48. package/src/__mocks__/fs.js +3 -0
  49. package/src/__tests__/statusBar.test.ts +8 -7
  50. package/src/build.js +57 -0
  51. package/src/debug.ts +2 -5
  52. package/src/env/index.ts +1 -0
  53. package/src/env/typescript-utility-types.ts +2 -0
  54. package/src/extension.ts +265 -170
  55. package/src/language-server/__e2e__/clientSchema.e2e.ts +147 -0
  56. package/src/language-server/__e2e__/httpSchema.e2e.ts +21 -0
  57. package/src/language-server/__e2e__/localSchema.e2e.ts +25 -0
  58. package/src/language-server/__e2e__/localSchemaArray.e2e.ts +31 -0
  59. package/src/language-server/__e2e__/studioGraph.e2e.ts +65 -0
  60. package/src/language-server/__e2e__/utils.ts +151 -0
  61. package/src/language-server/__tests__/diagnostics.test.ts +86 -0
  62. package/src/language-server/__tests__/document.test.ts +187 -0
  63. package/src/language-server/__tests__/fileSet.test.ts +46 -0
  64. package/src/language-server/__tests__/fixtures/starwarsSchema.ts +1917 -0
  65. package/src/language-server/config/__tests__/config.ts +54 -0
  66. package/src/language-server/config/__tests__/loadConfig.ts +384 -0
  67. package/src/language-server/config/__tests__/utils.ts +99 -0
  68. package/src/language-server/config/config.ts +284 -0
  69. package/src/language-server/config/index.ts +3 -0
  70. package/src/language-server/config/loadConfig.ts +101 -0
  71. package/src/language-server/config/utils.ts +45 -0
  72. package/src/language-server/diagnostics.ts +118 -0
  73. package/src/language-server/document.ts +277 -0
  74. package/src/language-server/engine/index.ts +123 -0
  75. package/src/language-server/engine/operations/frontendUrlRoot.ts +15 -0
  76. package/src/language-server/engine/operations/schemaTagsAndFieldStats.ts +32 -0
  77. package/src/language-server/errors/__tests__/NoMissingClientDirectives.test.ts +225 -0
  78. package/src/language-server/errors/logger.ts +58 -0
  79. package/src/language-server/errors/validation.ts +274 -0
  80. package/src/language-server/fileSet.ts +63 -0
  81. package/src/language-server/format.ts +48 -0
  82. package/src/language-server/graphqlTypes.ts +16741 -0
  83. package/src/language-server/index.ts +28 -0
  84. package/src/language-server/languageProvider.ts +795 -0
  85. package/src/language-server/loadingHandler.ts +47 -0
  86. package/src/language-server/project/base.ts +406 -0
  87. package/src/language-server/project/client.ts +568 -0
  88. package/src/language-server/project/defaultClientSchema.ts +70 -0
  89. package/src/language-server/providers/schema/__tests__/file.ts +191 -0
  90. package/src/language-server/providers/schema/base.ts +15 -0
  91. package/src/language-server/providers/schema/endpoint.ts +138 -0
  92. package/src/language-server/providers/schema/engine.ts +204 -0
  93. package/src/language-server/providers/schema/file.ts +176 -0
  94. package/src/language-server/providers/schema/index.ts +59 -0
  95. package/src/language-server/server.ts +274 -0
  96. package/src/language-server/typings/graphql.d.ts +27 -0
  97. package/src/language-server/utilities/__tests__/graphql.test.ts +399 -0
  98. package/src/language-server/utilities/__tests__/uri.ts +55 -0
  99. package/src/language-server/utilities/debouncer.ts +8 -0
  100. package/src/language-server/utilities/debug.ts +90 -0
  101. package/src/language-server/utilities/graphql.ts +433 -0
  102. package/src/language-server/utilities/index.ts +3 -0
  103. package/src/language-server/utilities/source.ts +182 -0
  104. package/src/language-server/utilities/uri.ts +19 -0
  105. package/src/language-server/workspace.ts +254 -0
  106. package/src/languageServerClient.ts +22 -15
  107. package/src/messages.ts +75 -0
  108. package/src/statusBar.ts +5 -5
  109. package/src/tools/__tests__/buildServiceDefinition.test.ts +491 -0
  110. package/src/tools/__tests__/snapshotSerializers/astSerializer.ts +19 -0
  111. package/src/tools/__tests__/snapshotSerializers/graphQLTypeSerializer.ts +14 -0
  112. package/src/tools/buildServiceDefinition.ts +241 -0
  113. package/src/tools/index.ts +6 -0
  114. package/src/tools/schema/index.ts +2 -0
  115. package/src/tools/schema/resolveObject.ts +18 -0
  116. package/src/tools/schema/resolverMap.ts +23 -0
  117. package/src/tools/utilities/graphql.ts +22 -0
  118. package/src/tools/utilities/index.ts +3 -0
  119. package/src/tools/utilities/invariant.ts +5 -0
  120. package/src/tools/utilities/predicates.ts +5 -0
  121. package/src/utils.ts +7 -21
  122. package/syntaxes/graphql.dart.json +2 -4
  123. package/syntaxes/graphql.ex.json +1 -4
  124. package/syntaxes/graphql.js.json +3 -3
  125. package/syntaxes/graphql.json +13 -9
  126. package/syntaxes/graphql.lua.json +51 -0
  127. package/syntaxes/graphql.rb.json +1 -1
  128. package/tsconfig.build.json +11 -0
  129. package/tsconfig.json +22 -7
  130. package/create-server-symlink.js +0 -8
  131. package/lib/debug.d.ts +0 -11
  132. package/lib/debug.d.ts.map +0 -1
  133. package/lib/debug.js +0 -48
  134. package/lib/debug.js.map +0 -1
  135. package/lib/extension.d.ts +0 -4
  136. package/lib/extension.d.ts.map +0 -1
  137. package/lib/extension.js +0 -187
  138. package/lib/extension.js.map +0 -1
  139. package/lib/languageServerClient.d.ts +0 -4
  140. package/lib/languageServerClient.d.ts.map +0 -1
  141. package/lib/languageServerClient.js +0 -57
  142. package/lib/languageServerClient.js.map +0 -1
  143. package/lib/statusBar.d.ts +0 -24
  144. package/lib/statusBar.d.ts.map +0 -1
  145. package/lib/statusBar.js +0 -46
  146. package/lib/statusBar.js.map +0 -1
  147. package/lib/testRunner/index.d.ts +0 -3
  148. package/lib/testRunner/index.d.ts.map +0 -1
  149. package/lib/testRunner/index.js +0 -49
  150. package/lib/testRunner/index.js.map +0 -1
  151. package/lib/testRunner/jest-config.d.ts +0 -14
  152. package/lib/testRunner/jest-config.d.ts.map +0 -1
  153. package/lib/testRunner/jest-config.js +0 -18
  154. package/lib/testRunner/jest-config.js.map +0 -1
  155. package/lib/testRunner/jest-vscode-environment.d.ts +0 -2
  156. package/lib/testRunner/jest-vscode-environment.d.ts.map +0 -1
  157. package/lib/testRunner/jest-vscode-environment.js +0 -19
  158. package/lib/testRunner/jest-vscode-environment.js.map +0 -1
  159. package/lib/testRunner/jest-vscode-framework-setup.d.ts +0 -1
  160. package/lib/testRunner/jest-vscode-framework-setup.d.ts.map +0 -1
  161. package/lib/testRunner/jest-vscode-framework-setup.js +0 -3
  162. package/lib/testRunner/jest-vscode-framework-setup.js.map +0 -1
  163. package/lib/testRunner/vscode-test-script.d.ts +0 -2
  164. package/lib/testRunner/vscode-test-script.d.ts.map +0 -1
  165. package/lib/testRunner/vscode-test-script.js +0 -23
  166. package/lib/testRunner/vscode-test-script.js.map +0 -1
  167. package/lib/utils.d.ts +0 -18
  168. package/lib/utils.d.ts.map +0 -1
  169. package/lib/utils.js +0 -52
  170. package/lib/utils.js.map +0 -1
  171. package/src/testRunner/README.md +0 -23
  172. package/src/testRunner/index.ts +0 -72
  173. package/src/testRunner/jest-config.ts +0 -17
  174. package/src/testRunner/jest-vscode-environment.ts +0 -25
  175. package/src/testRunner/jest-vscode-framework-setup.ts +0 -10
  176. package/src/testRunner/jest.d.ts +0 -37
  177. package/src/testRunner/vscode-test-script.ts +0 -38
  178. package/tsconfig.test.json +0 -4
  179. package/tsconfig.tsbuildinfo +0 -2486
package/src/extension.ts CHANGED
@@ -9,21 +9,28 @@ import {
9
9
  commands,
10
10
  QuickPickItem,
11
11
  Disposable,
12
- OutputChannel
12
+ OutputChannel,
13
+ MarkdownString,
14
+ Range,
13
15
  } from "vscode";
14
16
  import StatusBar from "./statusBar";
15
17
  import { getLanguageServerClient } from "./languageServerClient";
16
- import { LanguageClient, NotificationType } from "vscode-languageclient";
18
+ import { LanguageClient } from "vscode-languageclient/node";
19
+ import {
20
+ type EngineDecoration,
21
+ LanguageServerCommands as LSCommands,
22
+ LanguageServerNotifications as LSNotifications,
23
+ LanguageServerRequests as LSRequests,
24
+ } from "./messages";
17
25
  import {
18
26
  printNoFileOpenMessage,
19
- printStatsToClientOutputChannel
27
+ printStatsToClientOutputChannel,
20
28
  } from "./utils";
21
29
  import { Debug } from "./debug";
22
30
 
23
31
  const { version } = require("../package.json");
24
32
 
25
- let client: LanguageClient;
26
- let clientDisposable: Disposable;
33
+ let globalClient: LanguageClient | null = null;
27
34
  let statusBar: StatusBar;
28
35
  let outputChannel: OutputChannel;
29
36
  let schemaTagItems: QuickPickItem[] = [];
@@ -41,215 +48,303 @@ function isError(response: any): response is ErrorShape {
41
48
  );
42
49
  }
43
50
 
44
- export function activate(context: ExtensionContext) {
51
+ export interface VSCodeGraphQLExtension {
52
+ outputChannel: OutputChannel;
53
+ client: LanguageClient;
54
+ LanguageServerCommands: typeof LSCommands;
55
+ LanguageServerNotifications: typeof LSNotifications;
56
+ LanguageServerRequests: typeof LSRequests;
57
+ }
58
+
59
+ export async function activate(
60
+ context: ExtensionContext,
61
+ ): Promise<VSCodeGraphQLExtension> {
45
62
  const serverModule = context.asAbsolutePath(
46
- join("node_modules/apollo-language-server/lib", "server.js")
63
+ join("lib/language-server", "server.js"),
47
64
  );
48
-
49
- // Initialize language client
50
- client = getLanguageServerClient(serverModule, outputChannel);
65
+ outputChannel ||= window.createOutputChannel("Apollo GraphQL");
66
+ const client = getLanguageServerClient(serverModule, outputChannel);
67
+ globalClient = client;
51
68
  client.registerProposedFeatures();
52
69
 
53
70
  // Initialize disposables
54
71
  statusBar = new StatusBar({
55
- hasActiveTextEditor: Boolean(window.activeTextEditor)
72
+ hasActiveTextEditor: Boolean(window.activeTextEditor),
56
73
  });
57
- outputChannel = window.createOutputChannel("Apollo GraphQL");
58
74
  Debug.SetOutputConsole(outputChannel);
59
- clientDisposable = client.start();
60
-
61
75
  // Handoff disposables for cleanup
62
- context.subscriptions.push(statusBar, outputChannel, clientDisposable);
63
-
64
- var serverDebugMessage: NotificationType<
65
- { type: string; message: string; stack?: string },
66
- any
67
- > = new NotificationType("serverDebugMessage");
68
-
69
- // Once client is ready, we can send messages and add listeners for various notifications
70
- client.onReady().then(() => {
71
- client.onNotification(serverDebugMessage, message => {
72
- switch (message.type) {
73
- case "info":
74
- Debug.info(message.message, message.stack);
75
- break;
76
- case "error":
77
- Debug.error(message.message, message.stack);
78
- break;
79
- case "warning":
80
- Debug.warning(message.message, message.stack);
81
- break;
82
- default:
83
- Debug.info(message.message, message.stack);
84
- break;
85
- }
86
- });
76
+ context.subscriptions.push(statusBar, outputChannel);
87
77
 
88
- commands.registerCommand("apollographql/showStats", () => {
89
- const fileUri = window.activeTextEditor
90
- ? window.activeTextEditor.document.uri.fsPath
91
- : null;
78
+ client.onNotification(LSNotifications.ServerDebugMessage, (message) => {
79
+ switch (message.type) {
80
+ case "info":
81
+ Debug.info(message.message, message.stack);
82
+ break;
83
+ case "error":
84
+ Debug.error(message.message, message.stack);
85
+ break;
86
+ case "warning":
87
+ Debug.warning(message.message, message.stack);
88
+ break;
89
+ default:
90
+ Debug.info(message.message, message.stack);
91
+ break;
92
+ }
93
+ });
92
94
 
93
- // if no editor is open, but an output channel is, vscode returns something like
94
- // output:extension-output-%234. If an editor IS open, this is something like file://Users/...
95
- // This check is just for either a / or a \ anywhere in a fileUri
96
- const fileOpen = fileUri && /[\/\\]/.test(fileUri);
95
+ commands.registerCommand("apollographql/showStats", () => {
96
+ const fileUri = window.activeTextEditor
97
+ ? window.activeTextEditor.document.uri.fsPath
98
+ : null;
97
99
 
98
- if (fileOpen) {
99
- client.sendNotification("apollographql/getStats", { uri: fileUri });
100
- return;
101
- }
102
- printNoFileOpenMessage(client, version);
103
- client.outputChannel.show();
104
- });
100
+ // if no editor is open, but an output channel is, vscode returns something like
101
+ // output:extension-output-%234. If an editor IS open, this is something like file://Users/...
102
+ // This check is just for either a / or a \ anywhere in a fileUri
103
+ // TODO: this should probably be `registerTextEditorCommand` instead of `registerCommand`
104
+ const fileOpen = fileUri && /[\/\\]/.test(fileUri);
105
105
 
106
- client.onNotification("apollographql/statsLoaded", params => {
107
- printStatsToClientOutputChannel(client, params, version);
108
- client.outputChannel.show();
109
- });
110
- // For some reason, non-strings can only be sent in one direction. For now, messages
111
- // coming from the language server just need to be stringified and parsed.
112
- client.onNotification(
113
- "apollographql/configFilesFound",
114
- (params: string) => {
115
- const response = JSON.parse(params) as Array<any> | ErrorShape;
116
-
117
- const hasActiveTextEditor = Boolean(window.activeTextEditor);
118
- if (isError(response)) {
106
+ if (fileOpen) {
107
+ client.sendNotification(LSCommands.GetStats, {
108
+ uri: fileUri,
109
+ });
110
+ return;
111
+ }
112
+ printNoFileOpenMessage(client, version);
113
+ client.outputChannel.show();
114
+ });
115
+
116
+ client.onNotification(LSNotifications.StatsLoaded, (params) => {
117
+ printStatsToClientOutputChannel(client, params, version);
118
+ client.outputChannel.show();
119
+ });
120
+
121
+ commands.registerCommand("apollographql/fileStats", (uri: string) => {
122
+ // this has been introduced to check the LSP loading status in tests right now
123
+ // TODO: "apollographql/showStats" should use this request as well instead of notifications
124
+ return client.sendRequest(LSRequests.FileStats, { uri });
125
+ });
126
+
127
+ // For some reason, non-strings can only be sent in one direction. For now, messages
128
+ // coming from the language server just need to be stringified and parsed.
129
+ client.onNotification(LSNotifications.ConfigFilesFound, (params: string) => {
130
+ const response = JSON.parse(params) as Array<any> | ErrorShape;
131
+
132
+ const hasActiveTextEditor = Boolean(window.activeTextEditor);
133
+ if (Array.isArray(response)) {
134
+ const errors = response.filter((item) => isError(item));
135
+ if (errors.length) {
136
+ errors.forEach((response) => {
119
137
  statusBar.showWarningState({
120
138
  hasActiveTextEditor,
121
- tooltip: "Configuration Error"
139
+ tooltip: "Configuration Error",
122
140
  });
123
- outputChannel.append(response.stack);
141
+ outputChannel.appendLine("---\n" + response.stack + "\n---");
124
142
 
125
143
  const infoButtonText = "More Info";
126
144
  window
127
145
  .showInformationMessage(response.message, infoButtonText)
128
- .then(clicked => {
146
+ .then((clicked) => {
129
147
  if (clicked === infoButtonText) {
130
148
  outputChannel.show();
131
149
  }
132
150
  });
133
- } else if (Array.isArray(response)) {
134
- if (response.length === 0) {
135
- statusBar.showWarningState({
136
- hasActiveTextEditor,
137
- tooltip: "No apollo.config.js file found"
138
- });
139
- } else {
140
- statusBar.showLoadedState({ hasActiveTextEditor });
141
- }
142
- } else {
143
- Debug.error(
144
- `Invalid response type in message apollographql/configFilesFound:\n${JSON.stringify(
145
- response
146
- )}`
147
- );
148
- }
151
+ });
152
+ } else if (response.length === 0) {
153
+ statusBar.showWarningState({
154
+ hasActiveTextEditor,
155
+ tooltip: "No apollo.config.js file found",
156
+ });
157
+ } else {
158
+ statusBar.showLoadedState({ hasActiveTextEditor });
149
159
  }
150
- );
160
+ } else {
161
+ Debug.error(
162
+ `Invalid response type in message apollographql/configFilesFound:\n${JSON.stringify(
163
+ response,
164
+ )}`,
165
+ );
166
+ }
167
+ });
151
168
 
152
- commands.registerCommand("apollographql/reloadService", () => {
153
- // wipe out tags when reloading
154
- // XXX we should clean up this handling
155
- schemaTagItems = [];
156
- client.sendNotification("apollographql/reloadService");
157
- });
169
+ commands.registerCommand("apollographql/reloadService", () => {
170
+ // wipe out tags when reloading
171
+ // XXX we should clean up this handling
172
+ schemaTagItems = [];
173
+ client.sendNotification(LSCommands.ReloadService);
174
+ });
158
175
 
159
- // For some reason, non-strings can only be sent in one direction. For now, messages
160
- // coming from the language server just need to be stringified and parsed.
161
- client.onNotification("apollographql/tagsLoaded", params => {
162
- const [serviceID, tags]: [string, string[]] = JSON.parse(params);
163
- const items = tags.map(tag => ({
164
- label: tag,
165
- description: "",
166
- detail: serviceID
167
- }));
168
-
169
- schemaTagItems = [...items, ...schemaTagItems];
170
- });
176
+ // For some reason, non-strings can only be sent in one direction. For now, messages
177
+ // coming from the language server just need to be stringified and parsed.
178
+ client.onNotification(LSNotifications.TagsLoaded, (params) => {
179
+ const [serviceID, tags]: [string, string[]] = JSON.parse(params);
180
+ const items = tags.map((tag) => ({
181
+ label: tag,
182
+ description: "",
183
+ detail: serviceID,
184
+ }));
171
185
 
172
- commands.registerCommand("apollographql/selectSchemaTag", async () => {
173
- const selection = await window.showQuickPick(schemaTagItems);
174
- if (selection) {
175
- client.sendNotification("apollographql/tagSelected", selection);
176
- }
177
- });
186
+ schemaTagItems = [...items, ...schemaTagItems];
187
+ });
178
188
 
179
- let currentLoadingResolve: Map<number, () => void> = new Map();
189
+ commands.registerCommand("apollographql/selectSchemaTag", async () => {
190
+ const selection = await window.showQuickPick(schemaTagItems);
191
+ if (selection) {
192
+ client.sendNotification(LSCommands.TagSelected, selection);
193
+ }
194
+ });
180
195
 
181
- client.onNotification("apollographql/loadingComplete", token => {
182
- statusBar.showLoadedState({
183
- hasActiveTextEditor: Boolean(window.activeTextEditor)
184
- });
185
- const inMap = currentLoadingResolve.get(token);
186
- if (inMap) {
187
- inMap();
188
- currentLoadingResolve.delete(token);
189
- }
196
+ let currentLoadingResolve: Map<number, () => void> = new Map();
197
+
198
+ client.onNotification(LSNotifications.LoadingComplete, (token) => {
199
+ statusBar.showLoadedState({
200
+ hasActiveTextEditor: Boolean(window.activeTextEditor),
190
201
  });
202
+ const inMap = currentLoadingResolve.get(token);
203
+ if (inMap) {
204
+ inMap();
205
+ currentLoadingResolve.delete(token);
206
+ }
207
+ });
208
+
209
+ client.onNotification(LSNotifications.Loading, ({ message, token }) => {
210
+ window.withProgress(
211
+ {
212
+ location: ProgressLocation.Notification,
213
+ title: message,
214
+ cancellable: false,
215
+ },
216
+ () => {
217
+ return new Promise<void>((resolve) => {
218
+ currentLoadingResolve.set(token, resolve);
219
+ });
220
+ },
221
+ );
222
+ });
223
+
224
+ const runIconOnDiskPath = Uri.file(
225
+ join(context.extensionPath, "images", "IconRun.svg"),
226
+ );
227
+
228
+ const textDecorationType = window.createTextEditorDecorationType({});
229
+ const runGlyphDecorationType = window.createTextEditorDecorationType({});
230
+ let latestDecorations: EngineDecoration[] | undefined = undefined;
191
231
 
192
- client.onNotification("apollographql/loading", ({ message, token }) => {
193
- window.withProgress(
194
- {
195
- location: ProgressLocation.Notification,
196
- title: message,
197
- cancellable: false
232
+ const updateDecorations = () => {
233
+ if (window.activeTextEditor && latestDecorations) {
234
+ const editor = window.activeTextEditor!;
235
+
236
+ const decorationsForDocument = latestDecorations.filter(
237
+ (decoration) =>
238
+ decoration.document ===
239
+ window.activeTextEditor!.document.uri.toString(),
240
+ );
241
+
242
+ const textDecorations = decorationsForDocument.flatMap(
243
+ (decoration): DecorationOptions | DecorationOptions[] => {
244
+ if (decoration.type !== "text") {
245
+ return [];
246
+ }
247
+
248
+ return {
249
+ range: editor.document.lineAt(decoration.range.start.line).range,
250
+ renderOptions: {
251
+ after: {
252
+ contentText: decoration.message,
253
+ textDecoration: "none; padding-left: 15px; opacity: .5",
254
+ },
255
+ },
256
+ };
198
257
  },
199
- () => {
200
- return new Promise<void>(resolve => {
201
- currentLoadingResolve.set(token, resolve);
202
- });
203
- }
204
258
  );
205
- });
206
259
 
207
- const engineDecoration = window.createTextEditorDecorationType({});
208
- let latestDecs: any[] | undefined = undefined;
209
-
210
- const updateDecorations = () => {
211
- if (window.activeTextEditor && latestDecs) {
212
- const editor = window.activeTextEditor!;
213
- const decorations: DecorationOptions[] = latestDecs
214
- .filter(
215
- d => d.document === window.activeTextEditor!.document.uri.toString()
216
- )
217
- .map(dec => {
218
- return {
219
- range: editor.document.lineAt(dec.range.start.line).range,
220
- renderOptions: {
221
- after: {
222
- contentText: `${dec.message}`,
223
- textDecoration: "none; padding-left: 15px; opacity: .5"
224
- }
225
- }
226
- };
227
- });
260
+ const runGlyphDecorations = decorationsForDocument.flatMap(
261
+ (decoration): DecorationOptions | DecorationOptions[] => {
262
+ if (decoration.type !== "runGlyph") {
263
+ return [];
264
+ }
265
+
266
+ const hoverMessage =
267
+ decoration.hoverMessage === undefined
268
+ ? undefined
269
+ : new MarkdownString(decoration.hoverMessage);
270
+ if (hoverMessage) {
271
+ hoverMessage.isTrusted = true;
272
+ }
228
273
 
229
- window.activeTextEditor!.setDecorations(engineDecoration, decorations);
274
+ const endOfLinePosition = editor.document.lineAt(
275
+ decoration.range.start.line,
276
+ ).range.end;
277
+ return {
278
+ // Hover range of just the end of the line (and the icon) so the hover shows above the icon,
279
+ // not over at the start of the line
280
+ range: new Range(endOfLinePosition, endOfLinePosition),
281
+ renderOptions: {
282
+ after: {
283
+ contentIconPath: runIconOnDiskPath,
284
+ textDecoration:
285
+ "none; border-radius: .20rem; margin-left: 8px; text-align: center;",
286
+ backgroundColor: "#2075D6",
287
+ width: "18px",
288
+ height: "18px",
289
+ },
290
+ },
291
+ hoverMessage,
292
+ };
293
+ },
294
+ );
295
+
296
+ window.activeTextEditor!.setDecorations(
297
+ textDecorationType,
298
+ textDecorations,
299
+ );
300
+ if (
301
+ workspace
302
+ .getConfiguration("apollographql")
303
+ .get("display.showRunInStudioButton")
304
+ ) {
305
+ window.activeTextEditor!.setDecorations(
306
+ runGlyphDecorationType,
307
+ runGlyphDecorations,
308
+ );
230
309
  }
231
- };
310
+ }
311
+ };
232
312
 
233
- client.onNotification("apollographql/engineDecorations", (...decs) => {
234
- latestDecs = decs;
313
+ client.onNotification(
314
+ LSNotifications.EngineDecorations,
315
+ ({ decorations }) => {
316
+ latestDecorations = decorations;
235
317
  updateDecorations();
236
- });
318
+ },
319
+ );
237
320
 
238
- window.onDidChangeActiveTextEditor(() => {
239
- updateDecorations();
240
- });
321
+ window.onDidChangeActiveTextEditor(() => {
322
+ updateDecorations();
323
+ });
241
324
 
242
- workspace.registerTextDocumentContentProvider("graphql-schema", {
243
- provideTextDocumentContent(uri: Uri) {
244
- // the schema source is provided inside the URI, just return that here
245
- return uri.query;
246
- }
247
- });
325
+ workspace.registerTextDocumentContentProvider("graphql-schema", {
326
+ provideTextDocumentContent(uri: Uri) {
327
+ // the schema source is provided inside the URI, just return that here
328
+ return uri.query;
329
+ },
248
330
  });
331
+
332
+ await client.start();
333
+ return {
334
+ outputChannel,
335
+ client,
336
+ LanguageServerCommands: LSCommands,
337
+ LanguageServerNotifications: LSNotifications,
338
+ LanguageServerRequests: LSRequests,
339
+ };
249
340
  }
250
341
 
251
342
  export function deactivate(): Thenable<void> | void {
252
- if (client) {
253
- return client.stop();
343
+ if (globalClient) {
344
+ try {
345
+ return globalClient.stop();
346
+ } finally {
347
+ globalClient = null;
348
+ }
254
349
  }
255
350
  }
@@ -0,0 +1,147 @@
1
+ import { TextEditor } from "vscode";
2
+ import { closeAllEditors, openEditor, testCompletion, getHover } from "./utils";
3
+
4
+ let editor: TextEditor;
5
+ beforeAll(async () => {
6
+ closeAllEditors();
7
+ editor = await openEditor("clientSchema/src/test.js");
8
+ });
9
+
10
+ test("completion", async () => {
11
+ // dro|id
12
+ await testCompletion(editor, [4, 7], [["droid", "Droid"]]);
13
+ // na|me
14
+ await testCompletion(editor, [5, 8], [["name", "String!"]]);
15
+ // mo|del
16
+ await testCompletion(editor, [6, 8], [["model", "String"]]);
17
+ });
18
+
19
+ test("hover", async () => {
20
+ // featu|reFlagDefer
21
+ expect(await getHover(editor, [3, 10])).toMatchInlineSnapshot(`
22
+ "\`\`\`graphql
23
+ Query.featureFlagDefer: Boolean!
24
+ \`\`\`
25
+
26
+ ---
27
+
28
+ \`Client-Only Field\` \`Resolved locally\`
29
+
30
+ ---
31
+
32
+ Whether to use defer"
33
+ `);
34
+
35
+ // @c|lient(always: false)
36
+ expect(await getHover(editor, [3, 24])).toMatchInlineSnapshot(`
37
+ "\`\`\`graphql
38
+ @client(always: Boolean)
39
+ \`\`\`
40
+
41
+ ---
42
+
43
+ Direct the client to resolve this field locally, either from the cache or local resolvers."
44
+ `);
45
+
46
+ // @client(alwa|ys: false)
47
+ expect(await getHover(editor, [3, 33])).toMatchInlineSnapshot(`
48
+ "\`\`\`graphql
49
+ always: Boolean
50
+ \`\`\`
51
+
52
+ ---
53
+
54
+ When true, the client will never use the cache for this value. See
55
+ https://www.apollographql.com/docs/react/local-state/local-resolvers/#forcing-resolvers-with-clientalways-true"
56
+ `);
57
+
58
+ // @expo|rt(as: "defer")
59
+ expect(await getHover(editor, [3, 49])).toMatchInlineSnapshot(`
60
+ "\`\`\`graphql
61
+ @export(as: String!)
62
+ \`\`\`
63
+
64
+ ---
65
+
66
+ Export this locally resolved field as a variable to be used in the remainder of this query. See
67
+ https://www.apollographql.com/docs/react/local-state/local-resolvers/#using-client-fields-as-variables"
68
+ `);
69
+ expect(await getHover(editor, [3, 53])).toMatchInlineSnapshot(`
70
+ "\`\`\`graphql
71
+ as: String!
72
+ \`\`\`
73
+
74
+ ---
75
+
76
+ The variable name to export this field as."
77
+ `);
78
+
79
+ // @nonre|active
80
+ expect(await getHover(editor, [7, 28])).toMatchInlineSnapshot(`
81
+ "\`\`\`graphql
82
+ @nonreactive
83
+ \`\`\`
84
+
85
+ ---
86
+
87
+ The @nonreactive directive can be used to mark query fields or fragment spreads and is used to indicate that changes to the data contained within the subtrees marked @nonreactive should not trigger rerendering.
88
+ This allows parent components to fetch data to be rendered by their children without rerendering themselves when the data corresponding with fields marked as @nonreactive change.
89
+ https://www.apollographql.com/docs/react/data/directives#nonreactive"
90
+ `);
91
+
92
+ // @def|er(if: $defer, label: "fc")
93
+ expect(await getHover(editor, [8, 14])).toMatchInlineSnapshot(`
94
+ "\`\`\`graphql
95
+ @defer(if: Boolean, label: String)
96
+ \`\`\`
97
+
98
+ ---
99
+
100
+ This directive enables your queries to receive data for specific fields incrementally, instead of receiving all field data at the same time.
101
+ This is helpful whenever some fields in a query take much longer to resolve than others.
102
+ https://www.apollographql.com/docs/react/data/directives#defer"
103
+ `);
104
+ //@defer(i|f: $defer, label: "fc")
105
+ expect(await getHover(editor, [8, 18])).toMatchInlineSnapshot(`
106
+ "\`\`\`graphql
107
+ if: Boolean
108
+ \`\`\`
109
+
110
+ ---
111
+
112
+ When true fragment may be deferred, if omitted defaults to true."
113
+ `);
114
+ //@defer(if: $defer, labe|l: "fc")
115
+ expect(await getHover(editor, [8, 33])).toMatchInlineSnapshot(`
116
+ "\`\`\`graphql
117
+ label: String
118
+ \`\`\`
119
+
120
+ ---
121
+
122
+ A unique label across all @defer and @stream directives in an operation.
123
+ This label should be used by GraphQL clients to identify the data from patch responses and associate it with the correct fragment.
124
+ If provided, the GraphQL Server must add it to the payload."
125
+ `);
126
+ // @connec|tion(key: "feed")
127
+ expect(await getHover(editor, [9, 53])).toMatchInlineSnapshot(`
128
+ "\`\`\`graphql
129
+ @connection(key: String!, filter: [String!])
130
+ \`\`\`
131
+
132
+ ---
133
+
134
+ Specify a custom store key for this result. See
135
+ https://www.apollographql.com/docs/react/caching/advanced-topics/#the-connection-directive"
136
+ `);
137
+ // @connection(ke|y: "feed")
138
+ expect(await getHover(editor, [9, 61])).toMatchInlineSnapshot(`
139
+ "\`\`\`graphql
140
+ key: String!
141
+ \`\`\`
142
+
143
+ ---
144
+
145
+ Specify the store key."
146
+ `);
147
+ });
@@ -0,0 +1,21 @@
1
+ import { TextEditor } from "vscode";
2
+ import { closeAllEditors, openEditor, testCompletion, getHover } from "./utils";
3
+
4
+ let editor: TextEditor;
5
+ beforeAll(async () => {
6
+ closeAllEditors();
7
+ editor = await openEditor("httpSchema/src/test.js");
8
+ });
9
+
10
+ test("completion", async () => {
11
+ await testCompletion(editor, [3, 7], [["books", "[Book]"]]);
12
+ await testCompletion(editor, [5, 9], [["author", "String"]]);
13
+ });
14
+
15
+ test("hover", async () => {
16
+ expect(await getHover(editor, [5, 9])).toMatchInlineSnapshot(`
17
+ "\`\`\`graphql
18
+ Book.author: String
19
+ \`\`\`"
20
+ `);
21
+ });