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