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
@@ -0,0 +1,433 @@
1
+ import {
2
+ GraphQLSchema,
3
+ GraphQLCompositeType,
4
+ GraphQLField,
5
+ FieldNode,
6
+ SchemaMetaFieldDef,
7
+ TypeMetaFieldDef,
8
+ TypeNameMetaFieldDef,
9
+ ASTNode,
10
+ Kind,
11
+ NameNode,
12
+ visit,
13
+ print,
14
+ DirectiveNode,
15
+ SelectionSetNode,
16
+ DirectiveDefinitionNode,
17
+ isObjectType,
18
+ isInterfaceType,
19
+ isUnionType,
20
+ FragmentDefinitionNode,
21
+ InlineFragmentNode,
22
+ DocumentNode,
23
+ } from "graphql";
24
+
25
+ import { ExecutionContext } from "graphql/execution/execute";
26
+
27
+ export function isNode(maybeNode: any): maybeNode is ASTNode {
28
+ return maybeNode && typeof maybeNode.kind === "string";
29
+ }
30
+
31
+ export type NamedNode = ASTNode & {
32
+ name: NameNode;
33
+ };
34
+
35
+ export function isNamedNode(node: ASTNode): node is NamedNode {
36
+ return "name" in node;
37
+ }
38
+
39
+ export function isDirectiveDefinitionNode(
40
+ node: ASTNode,
41
+ ): node is DirectiveDefinitionNode {
42
+ return node.kind === Kind.DIRECTIVE_DEFINITION;
43
+ }
44
+
45
+ export function highlightNodeForNode(node: ASTNode): ASTNode {
46
+ switch (node.kind) {
47
+ case Kind.VARIABLE_DEFINITION:
48
+ return node.variable;
49
+ default:
50
+ return isNamedNode(node) ? node.name : node;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Not exactly the same as the executor's definition of getFieldDef, in this
56
+ * statically evaluated environment we do not always have an Object type,
57
+ * and need to handle Interface and Union types.
58
+ */
59
+ export function getFieldDef(
60
+ schema: GraphQLSchema,
61
+ parentType: GraphQLCompositeType,
62
+ fieldAST: FieldNode,
63
+ ): GraphQLField<any, any> | undefined {
64
+ const name = fieldAST.name.value;
65
+ if (
66
+ name === SchemaMetaFieldDef.name &&
67
+ schema.getQueryType() === parentType
68
+ ) {
69
+ return SchemaMetaFieldDef;
70
+ }
71
+ if (name === TypeMetaFieldDef.name && schema.getQueryType() === parentType) {
72
+ return TypeMetaFieldDef;
73
+ }
74
+ if (
75
+ name === TypeNameMetaFieldDef.name &&
76
+ (isObjectType(parentType) ||
77
+ isInterfaceType(parentType) ||
78
+ isUnionType(parentType))
79
+ ) {
80
+ return TypeNameMetaFieldDef;
81
+ }
82
+ if (isObjectType(parentType) || isInterfaceType(parentType)) {
83
+ return parentType.getFields()[name];
84
+ }
85
+
86
+ return undefined;
87
+ }
88
+
89
+ /**
90
+ * Remove specific directives
91
+ *
92
+ * The `ast` param must extend ASTNode. We use a generic to indicate that this function returns the same type
93
+ * of it's first parameter.
94
+ */
95
+ export function removeDirectives<AST extends ASTNode>(
96
+ ast: AST,
97
+ directiveNames: string[],
98
+ ): AST {
99
+ if (!directiveNames.length) return ast;
100
+ return visit(ast, {
101
+ Directive(node: DirectiveNode): DirectiveNode | null {
102
+ if (!!directiveNames.find((name) => name === node.name.value))
103
+ return null;
104
+ return node;
105
+ },
106
+ });
107
+ }
108
+
109
+ /**
110
+ * Recursively remove orphaned fragment definitions that have their names included in
111
+ * `fragmentNamesEligibleForRemoval`
112
+ *
113
+ * We expclitily require the fragments to be listed in `fragmentNamesEligibleForRemoval` so we only strip
114
+ * fragments that were orphaned by an operation, not fragments that started as oprhans
115
+ *
116
+ * The `ast` param must extend ASTNode. We use a generic to indicate that this function returns the same type
117
+ * of it's first parameter.
118
+ */
119
+ function removeOrphanedFragmentDefinitions<AST extends ASTNode>(
120
+ ast: AST,
121
+ fragmentNamesEligibleForRemoval: Set<string>,
122
+ ): AST {
123
+ /**
124
+ * Flag to keep track of removing any fragments
125
+ */
126
+ let anyFragmentsRemoved = false;
127
+
128
+ // Aquire names of all fragment spreads
129
+ const fragmentSpreadNodeNames = new Set<string>();
130
+ visit(ast, {
131
+ FragmentSpread(node) {
132
+ fragmentSpreadNodeNames.add(node.name.value);
133
+ },
134
+ });
135
+
136
+ // Strip unused fragment definitions. Flag if we've removed any so we know if we need to continue
137
+ // recursively checking.
138
+ ast = visit(ast, {
139
+ FragmentDefinition(node) {
140
+ if (
141
+ fragmentNamesEligibleForRemoval.has(node.name.value) &&
142
+ !fragmentSpreadNodeNames.has(node.name.value)
143
+ ) {
144
+ // This definition is not used, remove it.
145
+ anyFragmentsRemoved = true;
146
+ return null;
147
+ }
148
+
149
+ return undefined;
150
+ },
151
+ });
152
+
153
+ if (anyFragmentsRemoved) {
154
+ /* Handles the special case where a Fragment was not removed because it was not yet orphaned when being
155
+ `visit`ed. As an example:
156
+
157
+ ```jsx
158
+ fragment Two on Node {
159
+ id
160
+ }
161
+ fragment One on Query {
162
+ hero {
163
+ ...Two @client
164
+ }
165
+ }
166
+
167
+ { ...One }
168
+ ```
169
+
170
+ On the first visit, `Two` will not be removed. After `One` is removed, `Two` becomes orphaned. If any
171
+ nodes were removed on this pass; run another pass to see if there are more nodes that are now
172
+ orphaned.
173
+ */
174
+ return removeOrphanedFragmentDefinitions(
175
+ ast,
176
+ fragmentNamesEligibleForRemoval,
177
+ );
178
+ }
179
+
180
+ return ast;
181
+ }
182
+
183
+ /**
184
+ * Remove nodes that have zero-length selection sets
185
+ *
186
+ * The `ast` param must extend ASTNode. We use a generic to indicate that this function returns the same type
187
+ * of it's first parameter.
188
+ */
189
+ function removeNodesWithEmptySelectionSets<AST extends ASTNode>(ast: AST): AST {
190
+ ast = visit(ast, {
191
+ enter(node) {
192
+ // If this node _has_ a `selectionSet` and it's zero-length, then remove it.
193
+ return "selectionSet" in node &&
194
+ node.selectionSet != null &&
195
+ node.selectionSet.selections.length === 0
196
+ ? null
197
+ : undefined;
198
+ },
199
+ });
200
+
201
+ return ast;
202
+ }
203
+
204
+ /**
205
+ * Remove nodes from `ast` when they have a directive in `directiveNames`
206
+ *
207
+ * The `ast` param must extend ASTNode. We use a generic to indicate that this function returns the same type
208
+ * of it's first parameter.
209
+ */
210
+ export function removeDirectiveAnnotatedFields<AST extends ASTNode>(
211
+ ast: AST,
212
+ directiveNames: string[],
213
+ ): AST {
214
+ print;
215
+ if (!directiveNames.length) return ast;
216
+
217
+ /**
218
+ * All fragment definition names we've removed due to a matching directive
219
+ *
220
+ * We keep track of these so we can remove associated spreads
221
+ */
222
+ const removedFragmentDefinitionNames = new Set<string>();
223
+
224
+ /**
225
+ * All fragment spreads that have been removed
226
+ *
227
+ * We can only remove fragment definitions for fragment spreads that we've removed
228
+ */
229
+ const removedFragmentSpreadNames = new Set<string>();
230
+
231
+ // Remove all nodes with a matching directive in `directiveNames`. Also, remove any operations that now have
232
+ // no selection set
233
+ ast = visit(ast, {
234
+ enter(node) {
235
+ // Strip all nodes that contain a directive we wish to remove
236
+ if (
237
+ "directives" in node &&
238
+ node.directives &&
239
+ node.directives.find((directive) =>
240
+ directiveNames.includes(directive.name.value),
241
+ )
242
+ ) {
243
+ /*
244
+ If we're removing a fragment definition then save the name so we can remove anywhere this fragment was
245
+ spread. This happens when a fragment definition itself has a matching directive on it, like this
246
+ (assuming that `@client` is a directive we want to remove):
247
+
248
+ ```graphql
249
+ fragment SomeFragmentDefinition on SomeType @client { fields }
250
+ ```
251
+ */
252
+ if (node.kind === Kind.FRAGMENT_DEFINITION) {
253
+ removedFragmentDefinitionNames.add(node.name.value);
254
+ }
255
+
256
+ /*
257
+ This node is going to be removed. Mark all fragment spreads nested under this node as eligible for
258
+ removal from the document. For example, assuming `@client` is a directive we want to remove:
259
+
260
+ ```graphql
261
+ clientObject @client {
262
+ ...ClientObjectFragment
263
+ }
264
+ ```
265
+
266
+ We're going to remove `clientObject` here, which will also remove `ClientObjectFragment`. If there are
267
+ no other instances of `ClientObjectFragment`, we're goign to remove it's definition as well.
268
+
269
+ We only remove definitions for spreads we've removed so we don't remove fragment definitions that were
270
+ never spread; as this is the kind of error `client:check` is inteded to flag.
271
+ */
272
+ visit(node, {
273
+ FragmentSpread(node) {
274
+ removedFragmentSpreadNames.add(node.name.value);
275
+ },
276
+ });
277
+
278
+ // Remove this node
279
+ return null;
280
+ }
281
+
282
+ return undefined;
283
+ },
284
+ });
285
+
286
+ // For all fragment definitions we removed, also remove the fragment spreads
287
+ ast = visit(ast, {
288
+ FragmentSpread(node) {
289
+ if (removedFragmentDefinitionNames.has(node.name.value)) {
290
+ removedFragmentSpreadNames.add(node.name.value);
291
+
292
+ return null;
293
+ }
294
+
295
+ return undefined;
296
+ },
297
+ });
298
+
299
+ // Remove all orphaned fragment definitions
300
+ ast = removeOrphanedFragmentDefinitions(ast, removedFragmentSpreadNames);
301
+
302
+ // Finally, remove nodes with empty selection sets
303
+ return removeNodesWithEmptySelectionSets(ast);
304
+ }
305
+
306
+ const typenameField = {
307
+ kind: Kind.FIELD,
308
+ name: { kind: Kind.NAME, value: "__typename" },
309
+ };
310
+
311
+ export function withTypenameFieldAddedWhereNeeded(ast: DocumentNode) {
312
+ return visit(ast, {
313
+ SelectionSet: {
314
+ enter(node: SelectionSetNode) {
315
+ return {
316
+ ...node,
317
+ selections: node.selections.filter(
318
+ (selection) =>
319
+ !(
320
+ selection.kind === Kind.FIELD &&
321
+ selection.name.value === "__typename"
322
+ ),
323
+ ),
324
+ };
325
+ },
326
+ },
327
+ leave(node: ASTNode) {
328
+ if (
329
+ !(
330
+ node.kind === Kind.FIELD ||
331
+ node.kind === Kind.FRAGMENT_DEFINITION ||
332
+ node.kind === Kind.INLINE_FRAGMENT
333
+ )
334
+ ) {
335
+ return undefined;
336
+ }
337
+ if (!node.selectionSet) return undefined;
338
+
339
+ return {
340
+ ...node,
341
+ selectionSet: {
342
+ ...node.selectionSet,
343
+ selections: [typenameField, ...node.selectionSet.selections],
344
+ },
345
+ };
346
+ },
347
+ });
348
+ }
349
+
350
+ function getFieldEntryKey(node: FieldNode): string {
351
+ return node.alias ? node.alias.value : node.name.value;
352
+ }
353
+ // this is a simplified verison of the collect fields algorithm that the
354
+ // reference implementation uses during execution
355
+ // in this case, we don't care about boolean conditions of validating the
356
+ // type conditions as other validation has done that already
357
+ export function simpleCollectFields(
358
+ context: ExecutionContext,
359
+ selectionSet: SelectionSetNode,
360
+ fields: Record<string, FieldNode[]>,
361
+ visitedFragmentNames: Record<string, boolean>,
362
+ ): Record<string, FieldNode[]> {
363
+ for (const selection of selectionSet.selections) {
364
+ switch (selection.kind) {
365
+ case Kind.FIELD: {
366
+ const name = getFieldEntryKey(selection);
367
+ if (!fields[name]) {
368
+ fields[name] = [];
369
+ }
370
+ fields[name].push(selection);
371
+ break;
372
+ }
373
+ case Kind.INLINE_FRAGMENT: {
374
+ simpleCollectFields(
375
+ context,
376
+ selection.selectionSet,
377
+ fields,
378
+ visitedFragmentNames,
379
+ );
380
+ break;
381
+ }
382
+ case Kind.FRAGMENT_SPREAD: {
383
+ const fragName = selection.name.value;
384
+ if (visitedFragmentNames[fragName]) continue;
385
+ visitedFragmentNames[fragName] = true;
386
+ const fragment = context.fragments[fragName];
387
+ if (!fragment) continue;
388
+ simpleCollectFields(
389
+ context,
390
+ fragment.selectionSet,
391
+ fields,
392
+ visitedFragmentNames,
393
+ );
394
+ break;
395
+ }
396
+ }
397
+ }
398
+ return fields;
399
+ }
400
+ export function hasClientDirective(
401
+ node: FieldNode | InlineFragmentNode | FragmentDefinitionNode,
402
+ ) {
403
+ return (
404
+ node.directives &&
405
+ node.directives.some((directive) => directive.name.value === "client")
406
+ );
407
+ }
408
+
409
+ export interface ClientSchemaInfo {
410
+ localFields?: string[];
411
+ }
412
+
413
+ declare module "graphql/type/definition" {
414
+ interface GraphQLScalarType {
415
+ clientSchema?: ClientSchemaInfo;
416
+ }
417
+
418
+ interface GraphQLObjectType {
419
+ clientSchema?: ClientSchemaInfo;
420
+ }
421
+
422
+ interface GraphQLInterfaceType {
423
+ clientSchema?: ClientSchemaInfo;
424
+ }
425
+
426
+ interface GraphQLUnionType {
427
+ clientSchema?: ClientSchemaInfo;
428
+ }
429
+
430
+ interface GraphQLEnumType {
431
+ clientSchema?: ClientSchemaInfo;
432
+ }
433
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./debouncer";
2
+ export * from "./uri";
3
+ export { Debug } from "./debug";
@@ -0,0 +1,182 @@
1
+ import {
2
+ Source,
3
+ ASTNode,
4
+ Kind,
5
+ visit,
6
+ BREAK,
7
+ TypeInfo,
8
+ GraphQLSchema,
9
+ getVisitFn,
10
+ ASTVisitor,
11
+ ASTKindToNode,
12
+ } from "graphql";
13
+ import { SourceLocation, getLocation } from "graphql/language/location";
14
+
15
+ import { Position, Range } from "vscode-languageserver/node";
16
+
17
+ import { isNode } from "./graphql";
18
+
19
+ // XXX temp fix to silence ts errors with `apply`
20
+ type applyArg = [
21
+ any,
22
+ string | number | undefined,
23
+ any,
24
+ readonly (string | number)[],
25
+ readonly any[],
26
+ ];
27
+
28
+ /**
29
+ * Creates a new visitor instance which maintains a provided TypeInfo instance
30
+ * along with visiting visitor.
31
+ */
32
+ export function visitWithTypeInfo(
33
+ typeInfo: TypeInfo,
34
+ visitor: ASTVisitor,
35
+ ): ASTVisitor {
36
+ return {
37
+ enter(node: ASTNode) {
38
+ typeInfo.enter(node);
39
+ const fn = getVisitFn(visitor, node.kind, /* isLeaving */ false);
40
+ if (fn) {
41
+ const result = fn.apply(visitor, arguments as unknown as applyArg);
42
+ if (result !== undefined) {
43
+ typeInfo.leave(node);
44
+ if (isNode(result)) {
45
+ typeInfo.enter(result);
46
+ }
47
+ }
48
+ return result;
49
+ }
50
+ },
51
+ leave(node: ASTNode) {
52
+ const fn = getVisitFn(visitor, node.kind, /* isLeaving */ true);
53
+ let result;
54
+ if (fn) {
55
+ result = fn.apply(visitor, arguments as unknown as applyArg);
56
+ }
57
+ // XXX we can't replace this function until we handle this
58
+ // case better. If we replace with the function in `graphql-js`,
59
+ // it breaks onHover types
60
+ if (result !== BREAK) {
61
+ typeInfo.leave(node);
62
+ }
63
+ return result;
64
+ },
65
+ };
66
+ }
67
+
68
+ export function positionFromPositionInContainingDocument(
69
+ source: Source,
70
+ position: Position,
71
+ ) {
72
+ if (!source.locationOffset) return position;
73
+ return Position.create(
74
+ position.line - (source.locationOffset.line - 1),
75
+ position.character,
76
+ );
77
+ }
78
+
79
+ export function positionInContainingDocument(
80
+ source: Source,
81
+ position: Position,
82
+ ): Position {
83
+ if (!source.locationOffset) return position;
84
+ return Position.create(
85
+ source.locationOffset.line - 1 + position.line,
86
+ position.character,
87
+ );
88
+ }
89
+
90
+ export function rangeInContainingDocument(source: Source, range: Range): Range {
91
+ if (!source.locationOffset) return range;
92
+ return Range.create(
93
+ positionInContainingDocument(source, range.start),
94
+ positionInContainingDocument(source, range.end),
95
+ );
96
+ }
97
+
98
+ export function rangeForASTNode(node: ASTNode): Range {
99
+ const location = node.loc!;
100
+ const source = location.source;
101
+
102
+ return Range.create(
103
+ positionFromSourceLocation(source, getLocation(source, location.start)),
104
+ positionFromSourceLocation(source, getLocation(source, location.end)),
105
+ );
106
+ }
107
+
108
+ export function positionFromSourceLocation(
109
+ source: Source,
110
+ location: SourceLocation,
111
+ ) {
112
+ return Position.create(
113
+ (source.locationOffset ? source.locationOffset.line - 1 : 0) +
114
+ location.line -
115
+ 1,
116
+ (source.locationOffset && location.line === 1
117
+ ? source.locationOffset.column - 1
118
+ : 0) +
119
+ location.column -
120
+ 1,
121
+ );
122
+ }
123
+
124
+ export function positionToOffset(source: Source, position: Position): number {
125
+ const lineRegexp = /\r\n|[\n\r]/g;
126
+ const lineEndingLength = /\r\n/g.test(source.body) ? 2 : 1;
127
+
128
+ const linesUntilPosition = source.body
129
+ .split(lineRegexp)
130
+ .slice(0, position.line);
131
+ return (
132
+ position.character +
133
+ linesUntilPosition
134
+ .map(
135
+ (line) => line.length + lineEndingLength, // count EOL
136
+ )
137
+ .reduce((a, b) => a + b, 0)
138
+ );
139
+ }
140
+
141
+ export function getASTNodeAndTypeInfoAtPosition(
142
+ source: Source,
143
+ position: Position,
144
+ root: ASTNode,
145
+ schema: GraphQLSchema,
146
+ ): [ASTNode, TypeInfo] | null {
147
+ const offset = positionToOffset(source, position);
148
+
149
+ let nodeContainingPosition: ASTNode | null = null;
150
+
151
+ const typeInfo = new TypeInfo(schema);
152
+ visit(
153
+ root,
154
+ visitWithTypeInfo(typeInfo, {
155
+ enter(node: ASTNode) {
156
+ if (
157
+ node.kind !== Kind.NAME && // We're usually interested in their parents
158
+ node.loc &&
159
+ node.loc.start <= offset &&
160
+ offset <= node.loc.end
161
+ ) {
162
+ nodeContainingPosition = node;
163
+ } else {
164
+ return false;
165
+ }
166
+ return;
167
+ },
168
+ leave(node: ASTNode) {
169
+ if (node.loc && node.loc.start <= offset && offset <= node.loc.end) {
170
+ return BREAK;
171
+ }
172
+ return;
173
+ },
174
+ }),
175
+ );
176
+
177
+ if (nodeContainingPosition) {
178
+ return [nodeContainingPosition, typeInfo];
179
+ } else {
180
+ return null;
181
+ }
182
+ }
@@ -0,0 +1,19 @@
1
+ import { URI } from "vscode-uri";
2
+
3
+ const withUnixSeparator = (uriString: string) =>
4
+ uriString.split(/[\/\\]/).join("/");
5
+
6
+ export const normalizeURI = (uriString: string) => {
7
+ let parsed;
8
+ if (uriString.indexOf("file:///") === 0) {
9
+ parsed = URI.file(URI.parse(uriString).fsPath);
10
+ } else if (uriString.match(/^[a-zA-Z]:[\/\\].*/)) {
11
+ // uri with a drive prefix but not file:///
12
+ parsed = URI.file(
13
+ URI.parse("file:///" + withUnixSeparator(uriString)).fsPath,
14
+ );
15
+ } else {
16
+ parsed = URI.parse(withUnixSeparator(uriString));
17
+ }
18
+ return withUnixSeparator(parsed.fsPath);
19
+ };