vscode-apollo 1.19.2 → 1.20.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 (155) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +14 -0
  3. package/.circleci/config.yml +82 -0
  4. package/.eslintrc.js +10 -0
  5. package/.gitattributes +1 -0
  6. package/.github/workflows/release.yml +95 -0
  7. package/.gitleaks.toml +26 -0
  8. package/.nvmrc +1 -0
  9. package/.prettierrc +5 -0
  10. package/.vscode/launch.json +61 -0
  11. package/.vscode/settings.json +16 -0
  12. package/.vscode/tasks.json +18 -0
  13. package/.vscodeignore +17 -1
  14. package/CHANGELOG.md +172 -1
  15. package/LICENSE +2 -2
  16. package/README.md +9 -9
  17. package/codegen.yml +12 -0
  18. package/images/IconRun.svg +8 -0
  19. package/jest.config.ts +11 -0
  20. package/package.json +102 -22
  21. package/renovate.json +23 -0
  22. package/src/__mocks__/fs.js +3 -0
  23. package/src/__tests__/statusBar.test.ts +8 -7
  24. package/src/debug.ts +2 -5
  25. package/src/env/fetch/fetch.ts +32 -0
  26. package/src/env/fetch/global.ts +30 -0
  27. package/src/env/fetch/index.d.ts +2 -0
  28. package/src/env/fetch/index.ts +2 -0
  29. package/src/env/fetch/url.ts +9 -0
  30. package/src/env/index.ts +4 -0
  31. package/src/env/polyfills/array.ts +17 -0
  32. package/src/env/polyfills/index.ts +2 -0
  33. package/src/env/polyfills/object.ts +7 -0
  34. package/src/env/typescript-utility-types.ts +2 -0
  35. package/src/extension.ts +106 -37
  36. package/src/language-server/__tests__/diagnostics.test.ts +86 -0
  37. package/src/language-server/__tests__/document.test.ts +187 -0
  38. package/src/language-server/__tests__/fileSet.test.ts +46 -0
  39. package/src/language-server/__tests__/fixtures/starwarsSchema.ts +1917 -0
  40. package/src/language-server/config/__tests__/config.ts +128 -0
  41. package/src/language-server/config/__tests__/loadConfig.ts +508 -0
  42. package/src/language-server/config/__tests__/utils.ts +106 -0
  43. package/src/language-server/config/config.ts +219 -0
  44. package/src/language-server/config/index.ts +3 -0
  45. package/src/language-server/config/loadConfig.ts +228 -0
  46. package/src/language-server/config/utils.ts +56 -0
  47. package/src/language-server/diagnostics.ts +109 -0
  48. package/src/language-server/document.ts +277 -0
  49. package/src/language-server/engine/GraphQLDataSource.ts +124 -0
  50. package/src/language-server/engine/index.ts +105 -0
  51. package/src/language-server/engine/operations/frontendUrlRoot.ts +7 -0
  52. package/src/language-server/engine/operations/schemaTagsAndFieldStats.ts +24 -0
  53. package/src/language-server/errors/__tests__/NoMissingClientDirectives.test.ts +220 -0
  54. package/src/language-server/errors/logger.ts +58 -0
  55. package/src/language-server/errors/validation.ts +277 -0
  56. package/src/language-server/fileSet.ts +65 -0
  57. package/src/language-server/format.ts +48 -0
  58. package/src/language-server/graphqlTypes.ts +7176 -0
  59. package/src/language-server/index.ts +29 -0
  60. package/src/language-server/languageProvider.ts +798 -0
  61. package/src/language-server/loadingHandler.ts +64 -0
  62. package/src/language-server/project/base.ts +399 -0
  63. package/src/language-server/project/client.ts +602 -0
  64. package/src/language-server/project/defaultClientSchema.ts +45 -0
  65. package/src/language-server/project/service.ts +48 -0
  66. package/src/language-server/providers/schema/__tests__/file.ts +150 -0
  67. package/src/language-server/providers/schema/base.ts +15 -0
  68. package/src/language-server/providers/schema/endpoint.ts +157 -0
  69. package/src/language-server/providers/schema/engine.ts +197 -0
  70. package/src/language-server/providers/schema/file.ts +167 -0
  71. package/src/language-server/providers/schema/index.ts +75 -0
  72. package/src/language-server/server.ts +252 -0
  73. package/src/language-server/typings/codemirror.d.ts +4 -0
  74. package/src/language-server/typings/graphql.d.ts +27 -0
  75. package/src/language-server/utilities/__tests__/graphql.test.ts +411 -0
  76. package/src/language-server/utilities/__tests__/uri.ts +55 -0
  77. package/src/language-server/utilities/debouncer.ts +8 -0
  78. package/src/language-server/utilities/debug.ts +89 -0
  79. package/src/language-server/utilities/graphql.ts +432 -0
  80. package/src/language-server/utilities/index.ts +3 -0
  81. package/src/language-server/utilities/source.ts +182 -0
  82. package/src/language-server/utilities/uri.ts +19 -0
  83. package/src/language-server/workspace.ts +262 -0
  84. package/src/languageServerClient.ts +19 -12
  85. package/src/messages.ts +84 -0
  86. package/src/statusBar.ts +5 -5
  87. package/src/tools/__tests__/buildServiceDefinition.test.ts +491 -0
  88. package/src/tools/__tests__/snapshotSerializers/astSerializer.ts +19 -0
  89. package/src/tools/__tests__/snapshotSerializers/graphQLTypeSerializer.ts +14 -0
  90. package/src/tools/buildServiceDefinition.ts +241 -0
  91. package/src/tools/index.ts +6 -0
  92. package/src/tools/schema/index.ts +2 -0
  93. package/src/tools/schema/resolveObject.ts +18 -0
  94. package/src/tools/schema/resolverMap.ts +23 -0
  95. package/src/tools/utilities/graphql.ts +22 -0
  96. package/src/tools/utilities/index.ts +3 -0
  97. package/src/tools/utilities/invariant.ts +5 -0
  98. package/src/tools/utilities/predicates.ts +5 -0
  99. package/src/utils.ts +1 -16
  100. package/syntaxes/graphql.js.json +3 -3
  101. package/syntaxes/graphql.json +13 -9
  102. package/syntaxes/graphql.lua.json +51 -0
  103. package/syntaxes/graphql.rb.json +1 -1
  104. package/tsconfig.build.json +4 -0
  105. package/tsconfig.json +20 -7
  106. package/create-server-symlink.js +0 -8
  107. package/lib/debug.d.ts +0 -11
  108. package/lib/debug.d.ts.map +0 -1
  109. package/lib/debug.js +0 -48
  110. package/lib/debug.js.map +0 -1
  111. package/lib/extension.d.ts +0 -4
  112. package/lib/extension.d.ts.map +0 -1
  113. package/lib/extension.js +0 -187
  114. package/lib/extension.js.map +0 -1
  115. package/lib/languageServerClient.d.ts +0 -4
  116. package/lib/languageServerClient.d.ts.map +0 -1
  117. package/lib/languageServerClient.js +0 -57
  118. package/lib/languageServerClient.js.map +0 -1
  119. package/lib/statusBar.d.ts +0 -24
  120. package/lib/statusBar.d.ts.map +0 -1
  121. package/lib/statusBar.js +0 -46
  122. package/lib/statusBar.js.map +0 -1
  123. package/lib/testRunner/index.d.ts +0 -3
  124. package/lib/testRunner/index.d.ts.map +0 -1
  125. package/lib/testRunner/index.js +0 -49
  126. package/lib/testRunner/index.js.map +0 -1
  127. package/lib/testRunner/jest-config.d.ts +0 -14
  128. package/lib/testRunner/jest-config.d.ts.map +0 -1
  129. package/lib/testRunner/jest-config.js +0 -18
  130. package/lib/testRunner/jest-config.js.map +0 -1
  131. package/lib/testRunner/jest-vscode-environment.d.ts +0 -2
  132. package/lib/testRunner/jest-vscode-environment.d.ts.map +0 -1
  133. package/lib/testRunner/jest-vscode-environment.js +0 -19
  134. package/lib/testRunner/jest-vscode-environment.js.map +0 -1
  135. package/lib/testRunner/jest-vscode-framework-setup.d.ts +0 -1
  136. package/lib/testRunner/jest-vscode-framework-setup.d.ts.map +0 -1
  137. package/lib/testRunner/jest-vscode-framework-setup.js +0 -3
  138. package/lib/testRunner/jest-vscode-framework-setup.js.map +0 -1
  139. package/lib/testRunner/vscode-test-script.d.ts +0 -2
  140. package/lib/testRunner/vscode-test-script.d.ts.map +0 -1
  141. package/lib/testRunner/vscode-test-script.js +0 -23
  142. package/lib/testRunner/vscode-test-script.js.map +0 -1
  143. package/lib/utils.d.ts +0 -18
  144. package/lib/utils.d.ts.map +0 -1
  145. package/lib/utils.js +0 -52
  146. package/lib/utils.js.map +0 -1
  147. package/src/testRunner/README.md +0 -23
  148. package/src/testRunner/index.ts +0 -72
  149. package/src/testRunner/jest-config.ts +0 -17
  150. package/src/testRunner/jest-vscode-environment.ts +0 -25
  151. package/src/testRunner/jest-vscode-framework-setup.ts +0 -10
  152. package/src/testRunner/jest.d.ts +0 -37
  153. package/src/testRunner/vscode-test-script.ts +0 -38
  154. package/tsconfig.test.json +0 -4
  155. package/tsconfig.tsbuildinfo +0 -2486
@@ -0,0 +1,798 @@
1
+ import {
2
+ CancellationToken,
3
+ Position,
4
+ Location,
5
+ Range,
6
+ CompletionItem,
7
+ Hover,
8
+ Definition,
9
+ CodeLens,
10
+ ReferenceContext,
11
+ InsertTextFormat,
12
+ DocumentSymbol,
13
+ SymbolKind,
14
+ SymbolInformation,
15
+ CodeAction,
16
+ CodeActionKind,
17
+ MarkupKind,
18
+ CompletionItemKind,
19
+ } from "vscode-languageserver";
20
+
21
+ // should eventually be moved into this package, since we're overriding a lot of the existing behavior here
22
+ import { getAutocompleteSuggestions } from "graphql-language-service-interface";
23
+ import { Position as GraphQlPosition } from "graphql-language-service-utils";
24
+ import {
25
+ getTokenAtPosition,
26
+ getTypeInfo,
27
+ } from "graphql-language-service-interface/dist/getAutocompleteSuggestions";
28
+ import type { GraphQLWorkspace } from "./workspace";
29
+ import type { DocumentUri } from "./project/base";
30
+
31
+ import {
32
+ positionFromPositionInContainingDocument,
33
+ rangeForASTNode,
34
+ getASTNodeAndTypeInfoAtPosition,
35
+ positionToOffset,
36
+ } from "./utilities/source";
37
+
38
+ import {
39
+ GraphQLNamedType,
40
+ Kind,
41
+ GraphQLField,
42
+ GraphQLNonNull,
43
+ isAbstractType,
44
+ TypeNameMetaFieldDef,
45
+ SchemaMetaFieldDef,
46
+ TypeMetaFieldDef,
47
+ typeFromAST,
48
+ GraphQLType,
49
+ isObjectType,
50
+ isListType,
51
+ GraphQLList,
52
+ isNonNullType,
53
+ ASTNode,
54
+ FieldDefinitionNode,
55
+ visit,
56
+ isExecutableDefinitionNode,
57
+ isTypeSystemDefinitionNode,
58
+ isTypeSystemExtensionNode,
59
+ GraphQLError,
60
+ DirectiveLocation,
61
+ } from "graphql";
62
+ import { highlightNodeForNode } from "./utilities/graphql";
63
+
64
+ import { GraphQLClientProject, isClientProject } from "./project/client";
65
+ import { isNotNullOrUndefined } from "../tools";
66
+ import type { CodeActionInfo } from "./errors/validation";
67
+ import { GraphQLDiagnostic } from "./diagnostics";
68
+ import type { ProjectStats } from "src/messages";
69
+ import { isInterfaceType } from "graphql";
70
+
71
+ const DirectiveLocations = Object.keys(DirectiveLocation);
72
+
73
+ function hasFields(type: GraphQLType): boolean {
74
+ return (
75
+ isObjectType(type) ||
76
+ (isListType(type) && hasFields((type as GraphQLList<any>).ofType)) ||
77
+ (isNonNullType(type) && hasFields((type as GraphQLNonNull<any>).ofType))
78
+ );
79
+ }
80
+
81
+ function uriForASTNode(node: ASTNode): DocumentUri | null {
82
+ const uri = node.loc && node.loc.source && node.loc.source.name;
83
+ if (!uri || uri === "GraphQL") {
84
+ return null;
85
+ }
86
+ return uri;
87
+ }
88
+
89
+ function locationForASTNode(node: ASTNode): Location | null {
90
+ const uri = uriForASTNode(node);
91
+ if (!uri) return null;
92
+ return Location.create(uri, rangeForASTNode(node));
93
+ }
94
+
95
+ function symbolForFieldDefinition(
96
+ definition: FieldDefinitionNode
97
+ ): DocumentSymbol {
98
+ return {
99
+ name: definition.name.value,
100
+ kind: SymbolKind.Field,
101
+ range: rangeForASTNode(definition),
102
+ selectionRange: rangeForASTNode(definition),
103
+ };
104
+ }
105
+
106
+ export class GraphQLLanguageProvider {
107
+ constructor(public workspace: GraphQLWorkspace) {}
108
+
109
+ async provideStats(uri?: DocumentUri): Promise<ProjectStats> {
110
+ if (this.workspace.projects.length && uri) {
111
+ const project = this.workspace.projectForFile(uri);
112
+ return project ? project.getProjectStats() : { loaded: false };
113
+ }
114
+
115
+ return { loaded: false };
116
+ }
117
+
118
+ async provideCompletionItems(
119
+ uri: DocumentUri,
120
+ position: Position,
121
+ _token: CancellationToken
122
+ ): Promise<CompletionItem[]> {
123
+ const project = this.workspace.projectForFile(uri);
124
+ if (!(project && project instanceof GraphQLClientProject)) return [];
125
+
126
+ const document = project.documentAt(uri, position);
127
+ if (!document) return [];
128
+
129
+ if (!project.schema) return [];
130
+
131
+ const rawPositionInDocument = positionFromPositionInContainingDocument(
132
+ document.source,
133
+ position
134
+ );
135
+ const positionInDocument = new GraphQlPosition(
136
+ rawPositionInDocument.line,
137
+ rawPositionInDocument.character
138
+ );
139
+
140
+ const token = getTokenAtPosition(document.source.body, positionInDocument);
141
+ const state =
142
+ token.state.kind === "Invalid" ? token.state.prevState : token.state;
143
+ const typeInfo = getTypeInfo(project.schema, token.state);
144
+
145
+ if (state?.kind === "DirectiveDef") {
146
+ return DirectiveLocations.map((location) => ({
147
+ label: location,
148
+ kind: CompletionItemKind.Constant,
149
+ }));
150
+ }
151
+
152
+ const suggestions = getAutocompleteSuggestions(
153
+ project.schema,
154
+ document.source.body,
155
+ positionInDocument
156
+ );
157
+
158
+ if (
159
+ state?.kind === "SelectionSet" ||
160
+ state?.kind === "Field" ||
161
+ state?.kind === "AliasedField"
162
+ ) {
163
+ const parentType = typeInfo.parentType;
164
+ const parentFields =
165
+ isInterfaceType(parentType) || isObjectType(parentType)
166
+ ? parentType.getFields()
167
+ : {};
168
+
169
+ if (isAbstractType(parentType)) {
170
+ parentFields[TypeNameMetaFieldDef.name] = TypeNameMetaFieldDef;
171
+ }
172
+
173
+ if (parentType === project.schema.getQueryType()) {
174
+ parentFields[SchemaMetaFieldDef.name] = SchemaMetaFieldDef;
175
+ parentFields[TypeMetaFieldDef.name] = TypeMetaFieldDef;
176
+ }
177
+
178
+ return suggestions.map((suggest) => {
179
+ // when code completing fields, expand out required variables and open braces
180
+ const suggestedField = parentFields[suggest.label] as GraphQLField<
181
+ void,
182
+ void
183
+ >;
184
+ if (!suggestedField) {
185
+ return suggest;
186
+ } else {
187
+ const requiredArgs = suggestedField.args.filter((a) =>
188
+ isNonNullType(a.type)
189
+ );
190
+ const paramsSection =
191
+ requiredArgs.length > 0
192
+ ? `(${requiredArgs
193
+ .map((a, i) => `${a.name}: $${i + 1}`)
194
+ .join(", ")})`
195
+ : ``;
196
+
197
+ const isClientType =
198
+ parentType &&
199
+ "clientSchema" in parentType &&
200
+ parentType.clientSchema?.localFields?.includes(suggestedField.name);
201
+ const directives = isClientType ? " @client" : "";
202
+
203
+ const snippet = hasFields(suggestedField.type)
204
+ ? `${suggest.label}${paramsSection}${directives} {\n\t$0\n}`
205
+ : `${suggest.label}${paramsSection}${directives}`;
206
+
207
+ return {
208
+ ...suggest,
209
+ insertText: snippet,
210
+ insertTextFormat: InsertTextFormat.Snippet,
211
+ };
212
+ }
213
+ });
214
+ }
215
+
216
+ if (state?.kind === "Directive") {
217
+ return suggestions.map((suggest) => {
218
+ const directive = project.schema!.getDirective(suggest.label);
219
+ if (!directive) {
220
+ return suggest;
221
+ }
222
+
223
+ const requiredArgs = directive.args.filter(isNonNullType);
224
+ const paramsSection =
225
+ requiredArgs.length > 0
226
+ ? `(${requiredArgs
227
+ .map((a, i) => `${a.name}: $${i + 1}`)
228
+ .join(", ")})`
229
+ : ``;
230
+
231
+ const snippet = `${suggest.label}${paramsSection}`;
232
+
233
+ const argsString =
234
+ directive.args.length > 0
235
+ ? `(${directive.args
236
+ .map((a) => `${a.name}: ${a.type}`)
237
+ .join(", ")})`
238
+ : "";
239
+
240
+ const content = [
241
+ [`\`\`\`graphql`, `@${suggest.label}${argsString}`, `\`\`\``].join(
242
+ "\n"
243
+ ),
244
+ ];
245
+
246
+ if (suggest.documentation) {
247
+ if (typeof suggest.documentation === "string") {
248
+ content.push(suggest.documentation);
249
+ } else {
250
+ // TODO (jason) `(string | MarkupContent) & (string | null)` is a weird type,
251
+ // leaving this for safety for now
252
+ content.push((suggest.documentation as any).value);
253
+ }
254
+ }
255
+
256
+ const doc = {
257
+ kind: MarkupKind.Markdown,
258
+ value: content.join("\n\n"),
259
+ };
260
+
261
+ return {
262
+ ...suggest,
263
+ documentation: doc,
264
+ insertText: snippet,
265
+ insertTextFormat: InsertTextFormat.Snippet,
266
+ };
267
+ });
268
+ }
269
+
270
+ return suggestions;
271
+ }
272
+
273
+ async provideHover(
274
+ uri: DocumentUri,
275
+ position: Position,
276
+ _token: CancellationToken
277
+ ): Promise<Hover | null> {
278
+ const project = this.workspace.projectForFile(uri);
279
+ if (!(project && project instanceof GraphQLClientProject)) return null;
280
+
281
+ const document = project.documentAt(uri, position);
282
+ if (!(document && document.ast)) return null;
283
+
284
+ if (!project.schema) return null;
285
+
286
+ const positionInDocument = positionFromPositionInContainingDocument(
287
+ document.source,
288
+ position
289
+ );
290
+
291
+ const nodeAndTypeInfo = getASTNodeAndTypeInfoAtPosition(
292
+ document.source,
293
+ positionInDocument,
294
+ document.ast,
295
+ project.schema
296
+ );
297
+
298
+ if (nodeAndTypeInfo) {
299
+ const [node, typeInfo] = nodeAndTypeInfo;
300
+
301
+ switch (node.kind) {
302
+ case Kind.FRAGMENT_SPREAD: {
303
+ const fragmentName = node.name.value;
304
+ const fragment = project.fragments[fragmentName];
305
+ if (fragment) {
306
+ return {
307
+ contents: {
308
+ language: "graphql",
309
+ value: `fragment ${fragmentName} on ${fragment.typeCondition.name.value}`,
310
+ },
311
+ };
312
+ }
313
+ break;
314
+ }
315
+
316
+ case Kind.FIELD: {
317
+ const parentType = typeInfo.getParentType();
318
+ const fieldDef = typeInfo.getFieldDef();
319
+
320
+ if (parentType && fieldDef) {
321
+ const argsString =
322
+ fieldDef.args.length > 0
323
+ ? `(${fieldDef.args
324
+ .map((a) => `${a.name}: ${a.type}`)
325
+ .join(", ")})`
326
+ : "";
327
+ const isClientType =
328
+ parentType.clientSchema &&
329
+ parentType.clientSchema.localFields &&
330
+ parentType.clientSchema.localFields.includes(fieldDef.name);
331
+
332
+ const isResolvedLocally =
333
+ node.directives &&
334
+ node.directives.some(
335
+ (directive) => directive.name.value === "client"
336
+ );
337
+
338
+ const content = [
339
+ [
340
+ `\`\`\`graphql`,
341
+ `${parentType}.${fieldDef.name}${argsString}: ${fieldDef.type}`,
342
+ `\`\`\``,
343
+ ].join("\n"),
344
+ ];
345
+
346
+ const info: string[] = [];
347
+ if (isClientType) {
348
+ info.push("`Client-Only Field`");
349
+ }
350
+ if (isResolvedLocally) {
351
+ info.push("`Resolved locally`");
352
+ }
353
+
354
+ if (info.length !== 0) {
355
+ content.push(info.join(" "));
356
+ }
357
+
358
+ if (fieldDef.description) {
359
+ content.push(fieldDef.description);
360
+ }
361
+
362
+ return {
363
+ contents: content.join("\n\n---\n\n"),
364
+ range: rangeForASTNode(highlightNodeForNode(node)),
365
+ };
366
+ }
367
+
368
+ break;
369
+ }
370
+
371
+ case Kind.NAMED_TYPE: {
372
+ const type = project.schema.getType(
373
+ node.name.value
374
+ ) as GraphQLNamedType | void;
375
+ if (!type) break;
376
+
377
+ const content = [[`\`\`\`graphql`, `${type}`, `\`\`\``].join("\n")];
378
+
379
+ if (type.description) {
380
+ content.push(type.description);
381
+ }
382
+
383
+ return {
384
+ contents: content.join("\n\n---\n\n"),
385
+ range: rangeForASTNode(highlightNodeForNode(node)),
386
+ };
387
+ }
388
+
389
+ case Kind.ARGUMENT: {
390
+ const argumentNode = typeInfo.getArgument()!;
391
+ const content = [
392
+ [
393
+ `\`\`\`graphql`,
394
+ `${argumentNode.name}: ${argumentNode.type}`,
395
+ `\`\`\``,
396
+ ].join("\n"),
397
+ ];
398
+ if (argumentNode.description) {
399
+ content.push(argumentNode.description);
400
+ }
401
+ return {
402
+ contents: content.join("\n\n---\n\n"),
403
+ range: rangeForASTNode(highlightNodeForNode(node)),
404
+ };
405
+ }
406
+
407
+ case Kind.DIRECTIVE: {
408
+ const directiveNode = typeInfo.getDirective();
409
+ if (!directiveNode) break;
410
+ const argsString =
411
+ directiveNode.args.length > 0
412
+ ? `(${directiveNode.args
413
+ .map((a) => `${a.name}: ${a.type}`)
414
+ .join(", ")})`
415
+ : "";
416
+ const content = [
417
+ [
418
+ `\`\`\`graphql`,
419
+ `@${directiveNode.name}${argsString}`,
420
+ `\`\`\``,
421
+ ].join("\n"),
422
+ ];
423
+ if (directiveNode.description) {
424
+ content.push(directiveNode.description);
425
+ }
426
+ return {
427
+ contents: content.join("\n\n---\n\n"),
428
+ range: rangeForASTNode(highlightNodeForNode(node)),
429
+ };
430
+ }
431
+ }
432
+ }
433
+ return null;
434
+ }
435
+
436
+ async provideDefinition(
437
+ uri: DocumentUri,
438
+ position: Position,
439
+ _token: CancellationToken
440
+ ): Promise<Definition | null> {
441
+ const project = this.workspace.projectForFile(uri);
442
+ if (!(project && project instanceof GraphQLClientProject)) return null;
443
+
444
+ const document = project.documentAt(uri, position);
445
+ if (!(document && document.ast)) return null;
446
+
447
+ if (!project.schema) return null;
448
+
449
+ const positionInDocument = positionFromPositionInContainingDocument(
450
+ document.source,
451
+ position
452
+ );
453
+
454
+ const nodeAndTypeInfo = getASTNodeAndTypeInfoAtPosition(
455
+ document.source,
456
+ positionInDocument,
457
+ document.ast,
458
+ project.schema
459
+ );
460
+
461
+ if (nodeAndTypeInfo) {
462
+ const [node, typeInfo] = nodeAndTypeInfo;
463
+
464
+ switch (node.kind) {
465
+ case Kind.FRAGMENT_SPREAD: {
466
+ const fragmentName = node.name.value;
467
+ const fragment = project.fragments[fragmentName];
468
+ if (fragment && fragment.loc) {
469
+ return locationForASTNode(fragment);
470
+ }
471
+ break;
472
+ }
473
+ case Kind.FIELD: {
474
+ const fieldDef = typeInfo.getFieldDef();
475
+
476
+ if (!(fieldDef && fieldDef.astNode && fieldDef.astNode.loc)) break;
477
+
478
+ return locationForASTNode(fieldDef.astNode);
479
+ }
480
+ case Kind.NAMED_TYPE: {
481
+ const type = typeFromAST(project.schema, node);
482
+
483
+ if (!(type && type.astNode && type.astNode.loc)) break;
484
+
485
+ return locationForASTNode(type.astNode);
486
+ }
487
+ case Kind.DIRECTIVE: {
488
+ const directive = project.schema.getDirective(node.name.value);
489
+
490
+ if (!(directive && directive.astNode && directive.astNode.loc)) break;
491
+
492
+ return locationForASTNode(directive.astNode);
493
+ }
494
+ }
495
+ }
496
+ return null;
497
+ }
498
+
499
+ async provideReferences(
500
+ uri: DocumentUri,
501
+ position: Position,
502
+ _context: ReferenceContext,
503
+ _token: CancellationToken
504
+ ): Promise<Location[] | null> {
505
+ const project = this.workspace.projectForFile(uri);
506
+ if (!project) return null;
507
+ const document = project.documentAt(uri, position);
508
+ if (!(document && document.ast)) return null;
509
+
510
+ if (!project.schema) return null;
511
+
512
+ const positionInDocument = positionFromPositionInContainingDocument(
513
+ document.source,
514
+ position
515
+ );
516
+
517
+ const nodeAndTypeInfo = getASTNodeAndTypeInfoAtPosition(
518
+ document.source,
519
+ positionInDocument,
520
+ document.ast,
521
+ project.schema
522
+ );
523
+
524
+ if (nodeAndTypeInfo) {
525
+ const [node, typeInfo] = nodeAndTypeInfo;
526
+
527
+ switch (node.kind) {
528
+ case Kind.FRAGMENT_DEFINITION: {
529
+ if (!isClientProject(project)) return null;
530
+ const fragmentName = node.name.value;
531
+ return project
532
+ .fragmentSpreadsForFragment(fragmentName)
533
+ .map((fragmentSpread) => locationForASTNode(fragmentSpread))
534
+ .filter(isNotNullOrUndefined);
535
+ }
536
+ // TODO(jbaxleyiii): manage no parent type references (unions + scalars)
537
+ // TODO(jbaxleyiii): support more than fields
538
+ case Kind.FIELD_DEFINITION: {
539
+ // case Kind.ENUM_VALUE_DEFINITION:
540
+ // case Kind.INPUT_OBJECT_TYPE_DEFINITION:
541
+ // case Kind.INPUT_OBJECT_TYPE_EXTENSION: {
542
+ if (!isClientProject(project)) return null;
543
+ const offset = positionToOffset(document.source, positionInDocument);
544
+ // withWithTypeInfo doesn't suppport SDL so we instead
545
+ // write our own visitor methods here to collect the fields that we
546
+ // care about
547
+ let parent: ASTNode | null = null;
548
+ visit(document.ast, {
549
+ enter(node: ASTNode) {
550
+ // the parent types we care about
551
+ if (
552
+ node.loc &&
553
+ node.loc.start <= offset &&
554
+ offset <= node.loc.end &&
555
+ (node.kind === Kind.OBJECT_TYPE_DEFINITION ||
556
+ node.kind === Kind.OBJECT_TYPE_EXTENSION ||
557
+ node.kind === Kind.INTERFACE_TYPE_DEFINITION ||
558
+ node.kind === Kind.INTERFACE_TYPE_EXTENSION ||
559
+ node.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION ||
560
+ node.kind === Kind.INPUT_OBJECT_TYPE_EXTENSION ||
561
+ node.kind === Kind.ENUM_TYPE_DEFINITION ||
562
+ node.kind === Kind.ENUM_TYPE_EXTENSION)
563
+ ) {
564
+ parent = node;
565
+ }
566
+ return;
567
+ },
568
+ });
569
+ return project
570
+ .getOperationFieldsFromFieldDefinition(node.name.value, parent)
571
+ .map((fieldNode) => locationForASTNode(fieldNode))
572
+ .filter(isNotNullOrUndefined);
573
+ }
574
+ }
575
+ }
576
+
577
+ return null;
578
+ }
579
+
580
+ async provideDocumentSymbol(
581
+ uri: DocumentUri,
582
+ _token: CancellationToken
583
+ ): Promise<DocumentSymbol[]> {
584
+ const project = this.workspace.projectForFile(uri);
585
+ if (!project) return [];
586
+
587
+ const definitions = project.definitionsAt(uri);
588
+
589
+ const symbols: DocumentSymbol[] = [];
590
+
591
+ for (const definition of definitions) {
592
+ if (isExecutableDefinitionNode(definition)) {
593
+ if (!definition.name) continue;
594
+ const location = locationForASTNode(definition);
595
+ if (!location) continue;
596
+ symbols.push({
597
+ name: definition.name.value,
598
+ kind: SymbolKind.Function,
599
+ range: rangeForASTNode(definition),
600
+ selectionRange: rangeForASTNode(highlightNodeForNode(definition)),
601
+ });
602
+ } else if (
603
+ isTypeSystemDefinitionNode(definition) ||
604
+ isTypeSystemExtensionNode(definition)
605
+ ) {
606
+ if (
607
+ definition.kind === Kind.SCHEMA_DEFINITION ||
608
+ definition.kind === Kind.SCHEMA_EXTENSION
609
+ ) {
610
+ continue;
611
+ }
612
+ symbols.push({
613
+ name: definition.name.value,
614
+ kind: SymbolKind.Class,
615
+ range: rangeForASTNode(definition),
616
+ selectionRange: rangeForASTNode(highlightNodeForNode(definition)),
617
+ children:
618
+ definition.kind === Kind.OBJECT_TYPE_DEFINITION ||
619
+ definition.kind === Kind.OBJECT_TYPE_EXTENSION
620
+ ? (definition.fields || []).map(symbolForFieldDefinition)
621
+ : undefined,
622
+ });
623
+ }
624
+ }
625
+
626
+ return symbols;
627
+ }
628
+
629
+ async provideWorkspaceSymbol(
630
+ query: string,
631
+ _token: CancellationToken
632
+ ): Promise<SymbolInformation[]> {
633
+ const symbols: SymbolInformation[] = [];
634
+ for (const project of this.workspace.projects) {
635
+ for (const definition of project.definitions) {
636
+ if (isExecutableDefinitionNode(definition)) {
637
+ if (!definition.name) continue;
638
+ const location = locationForASTNode(definition);
639
+ if (!location) continue;
640
+ symbols.push({
641
+ name: definition.name.value,
642
+ kind: SymbolKind.Function,
643
+ location,
644
+ });
645
+ }
646
+ }
647
+ }
648
+ return symbols;
649
+ }
650
+
651
+ async provideCodeLenses(
652
+ uri: DocumentUri,
653
+ _token: CancellationToken
654
+ ): Promise<CodeLens[]> {
655
+ const project = this.workspace.projectForFile(uri);
656
+ if (!(project && project instanceof GraphQLClientProject)) return [];
657
+
658
+ // Wait for the project to be fully initialized, so we always provide code lenses for open files, even
659
+ // if we receive the request before the project is ready.
660
+ await project.whenReady;
661
+
662
+ const documents = project.documentsAt(uri);
663
+ if (!documents) return [];
664
+
665
+ let codeLenses: CodeLens[] = [];
666
+
667
+ for (const document of documents) {
668
+ if (!document.ast) continue;
669
+
670
+ for (const definition of document.ast.definitions) {
671
+ if (definition.kind === Kind.OPERATION_DEFINITION) {
672
+ /*
673
+ if (set.endpoint) {
674
+ const fragmentSpreads: Set<
675
+ graphql.FragmentDefinitionNode
676
+ > = new Set();
677
+ const searchForReferencedFragments = (node: graphql.ASTNode) => {
678
+ visit(node, {
679
+ FragmentSpread(node: FragmentSpreadNode) {
680
+ const fragDefn = project.fragments[node.name.value];
681
+ if (!fragDefn) return;
682
+
683
+ if (!fragmentSpreads.has(fragDefn)) {
684
+ fragmentSpreads.add(fragDefn);
685
+ searchForReferencedFragments(fragDefn);
686
+ }
687
+ }
688
+ });
689
+ };
690
+
691
+ searchForReferencedFragments(definition);
692
+
693
+ codeLenses.push({
694
+ range: rangeForASTNode(definition),
695
+ command: Command.create(
696
+ `Run ${definition.operation}`,
697
+ "apollographql.runQuery",
698
+ graphql.parse(
699
+ [definition, ...fragmentSpreads]
700
+ .map(n => graphql.print(n))
701
+ .join("\n")
702
+ ),
703
+ definition.operation === "subscription"
704
+ ? set.endpoint.subscriptions
705
+ : set.endpoint.url,
706
+ set.endpoint.headers,
707
+ graphql.printSchema(set.schema!)
708
+ )
709
+ });
710
+ }
711
+ */
712
+ } else if (definition.kind === Kind.FRAGMENT_DEFINITION) {
713
+ // remove project references for fragment now
714
+ // const fragmentName = definition.name.value;
715
+ // const locations = project
716
+ // .fragmentSpreadsForFragment(fragmentName)
717
+ // .map(fragmentSpread => locationForASTNode(fragmentSpread))
718
+ // .filter(isNotNullOrUndefined);
719
+ // const command = Command.create(
720
+ // `${locations.length} references`,
721
+ // "editor.action.showReferences",
722
+ // uri,
723
+ // rangeForASTNode(definition).start,
724
+ // locations
725
+ // );
726
+ // codeLenses.push({
727
+ // range: rangeForASTNode(definition),
728
+ // command
729
+ // });
730
+ }
731
+ }
732
+ }
733
+ return codeLenses;
734
+ }
735
+
736
+ async provideCodeAction(
737
+ uri: DocumentUri,
738
+ range: Range,
739
+ _token: CancellationToken
740
+ ): Promise<CodeAction[]> {
741
+ function isPositionLessThanOrEqual(a: Position, b: Position) {
742
+ return a.line !== b.line ? a.line < b.line : a.character <= b.character;
743
+ }
744
+
745
+ const project = this.workspace.projectForFile(uri);
746
+ if (
747
+ !(
748
+ project &&
749
+ project instanceof GraphQLClientProject &&
750
+ project.diagnosticSet
751
+ )
752
+ )
753
+ return [];
754
+
755
+ await project.whenReady;
756
+
757
+ const documents = project.documentsAt(uri);
758
+ if (!documents) return [];
759
+
760
+ const errors: Set<GraphQLError> = new Set();
761
+
762
+ for (const [
763
+ diagnosticUri,
764
+ diagnostics,
765
+ ] of project.diagnosticSet.entries()) {
766
+ if (diagnosticUri !== uri) continue;
767
+
768
+ for (const diagnostic of diagnostics) {
769
+ if (
770
+ GraphQLDiagnostic.is(diagnostic) &&
771
+ isPositionLessThanOrEqual(range.start, diagnostic.range.end) &&
772
+ isPositionLessThanOrEqual(diagnostic.range.start, range.end)
773
+ ) {
774
+ errors.add(diagnostic.error);
775
+ }
776
+ }
777
+ }
778
+
779
+ const result: CodeAction[] = [];
780
+
781
+ for (const error of errors) {
782
+ const { extensions } = error;
783
+ if (!extensions || !extensions.codeAction) continue;
784
+
785
+ const { message, edits }: CodeActionInfo = extensions.codeAction;
786
+
787
+ const codeAction = CodeAction.create(
788
+ message,
789
+ { changes: { [uri]: edits } },
790
+ CodeActionKind.QuickFix
791
+ );
792
+
793
+ result.push(codeAction);
794
+ }
795
+
796
+ return result;
797
+ }
798
+ }