vscode-apollo 2.0.1 → 2.2.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 (42) hide show
  1. package/.circleci/config.yml +1 -1
  2. package/.vscode/launch.json +5 -1
  3. package/CHANGELOG.md +41 -0
  4. package/package.json +9 -4
  5. package/renovate.json +2 -1
  6. package/sampleWorkspace/localSchema/src/test.js +3 -0
  7. package/sampleWorkspace/rover/apollo.config.js +3 -0
  8. package/sampleWorkspace/rover/src/test.graphql +14 -0
  9. package/sampleWorkspace/rover/src/test.js +30 -0
  10. package/sampleWorkspace/sampleWorkspace.code-workspace +25 -19
  11. package/src/language-server/__tests__/document.test.ts +161 -3
  12. package/src/language-server/__tests__/fixtures/TypeScript.tmLanguage.json +5749 -0
  13. package/src/language-server/__tests__/fixtures/documents/commentWithTemplate.ts +41 -0
  14. package/src/language-server/__tests__/fixtures/documents/commentWithTemplate.ts.snap +185 -0
  15. package/src/language-server/__tests__/fixtures/documents/functionCall.ts +93 -0
  16. package/src/language-server/__tests__/fixtures/documents/functionCall.ts.snap +431 -0
  17. package/src/language-server/__tests__/fixtures/documents/taggedTemplate.ts +80 -0
  18. package/src/language-server/__tests__/fixtures/documents/taggedTemplate.ts.snap +353 -0
  19. package/src/language-server/__tests__/fixtures/documents/templateWithComment.ts +38 -0
  20. package/src/language-server/__tests__/fixtures/documents/templateWithComment.ts.snap +123 -0
  21. package/src/language-server/config/__tests__/loadConfig.ts +28 -16
  22. package/src/language-server/config/config.ts +50 -12
  23. package/src/language-server/config/loadConfig.ts +2 -1
  24. package/src/language-server/config/which.d.ts +19 -0
  25. package/src/language-server/document.ts +86 -53
  26. package/src/language-server/fileSet.ts +8 -6
  27. package/src/language-server/project/base.ts +64 -315
  28. package/src/language-server/project/client.ts +731 -21
  29. package/src/language-server/project/internal.ts +354 -0
  30. package/src/language-server/project/rover/DocumentSynchronization.ts +385 -0
  31. package/src/language-server/project/rover/__tests__/DocumentSynchronization.test.ts +302 -0
  32. package/src/language-server/project/rover/project.ts +341 -0
  33. package/src/language-server/server.ts +187 -98
  34. package/src/language-server/utilities/__tests__/source.test.ts +162 -0
  35. package/src/language-server/utilities/languageIdForExtension.ts +39 -0
  36. package/src/language-server/utilities/source.ts +38 -3
  37. package/src/language-server/workspace.ts +61 -12
  38. package/src/languageServerClient.ts +13 -15
  39. package/src/tools/utilities/getLanguageInformation.ts +41 -0
  40. package/src/tools/utilities/languageInformation.ts +41 -0
  41. package/syntaxes/graphql.js.json +18 -21
  42. package/src/language-server/languageProvider.ts +0 -795
@@ -11,7 +11,6 @@ import {
11
11
  FragmentDefinitionNode,
12
12
  Kind,
13
13
  FragmentSpreadNode,
14
- separateOperations,
15
14
  OperationDefinitionNode,
16
15
  extendSchema,
17
16
  DocumentNode,
@@ -21,16 +20,52 @@ import {
21
20
  DefinitionNode,
22
21
  ExecutableDefinitionNode,
23
22
  print,
23
+ GraphQLNamedType,
24
+ GraphQLField,
25
+ GraphQLNonNull,
26
+ isAbstractType,
27
+ TypeNameMetaFieldDef,
28
+ SchemaMetaFieldDef,
29
+ TypeMetaFieldDef,
30
+ typeFromAST,
31
+ GraphQLType,
32
+ isObjectType,
33
+ isListType,
34
+ GraphQLList,
35
+ isNonNullType,
36
+ ASTNode,
37
+ FieldDefinitionNode,
38
+ isExecutableDefinitionNode,
39
+ isTypeSystemDefinitionNode,
40
+ isTypeSystemExtensionNode,
41
+ DirectiveLocation,
24
42
  } from "graphql";
25
43
  import { ValidationRule } from "graphql/validation/ValidationContext";
26
44
  import {
27
45
  NotificationHandler,
28
46
  DiagnosticSeverity,
47
+ CancellationToken,
48
+ Position,
49
+ Location,
50
+ CodeLens,
51
+ InsertTextFormat,
52
+ DocumentSymbol,
53
+ SymbolKind,
54
+ SymbolInformation,
55
+ CodeAction,
56
+ CodeActionKind,
57
+ MarkupKind,
58
+ CompletionItemKind,
29
59
  } from "vscode-languageserver/node";
30
60
  import LZString from "lz-string";
31
61
  import { URL } from "node:url";
32
62
 
33
- import { rangeForASTNode } from "../utilities/source";
63
+ import {
64
+ positionFromPositionInContainingDocument,
65
+ rangeForASTNode,
66
+ getASTNodeAndTypeInfoAtPosition,
67
+ positionToOffset,
68
+ } from "../utilities/source";
34
69
  import { formatMS } from "../format";
35
70
  import { LoadingHandler } from "../loadingHandler";
36
71
  import { apolloClientSchemaDocument } from "./defaultClientSchema";
@@ -43,9 +78,6 @@ import {
43
78
  } from "../engine";
44
79
  import { ClientConfig } from "../config";
45
80
  import {
46
- removeDirectives,
47
- removeDirectiveAnnotatedFields,
48
- withTypenameFieldAddedWhereNeeded,
49
81
  ClientSchemaInfo,
50
82
  isDirectiveDefinitionNode,
51
83
  } from "../utilities/graphql";
@@ -58,7 +90,20 @@ import {
58
90
  } from "../diagnostics";
59
91
  import { URI } from "vscode-uri";
60
92
  import type { EngineDecoration } from "../../messages";
61
- import { join } from "path";
93
+
94
+ // should eventually be moved into this package, since we're overriding a lot of the existing behavior here
95
+ import { getAutocompleteSuggestions } from "graphql-language-service";
96
+ import { Position as GraphQlPosition } from "graphql-language-service";
97
+ import { getTokenAtPosition, getTypeInfo } from "graphql-language-service";
98
+ import type { DocumentUri } from "../project/base";
99
+
100
+ import { highlightNodeForNode } from "../utilities/graphql";
101
+
102
+ import { isNotNullOrUndefined } from "../../tools";
103
+ import type { CodeActionInfo } from "../errors/validation";
104
+ import { GraphQLDiagnostic } from "../diagnostics";
105
+ import { isInterfaceType } from "graphql";
106
+ import { GraphQLInternalProject } from "./internal";
62
107
 
63
108
  type Maybe<T> = null | undefined | T;
64
109
 
@@ -84,6 +129,41 @@ function augmentSchemaWithGeneratedSDLIfNeeded(
84
129
  );
85
130
  }
86
131
 
132
+ const DirectiveLocations = Object.keys(DirectiveLocation);
133
+
134
+ function hasFields(type: GraphQLType): boolean {
135
+ return (
136
+ isObjectType(type) ||
137
+ (isListType(type) && hasFields((type as GraphQLList<any>).ofType)) ||
138
+ (isNonNullType(type) && hasFields((type as GraphQLNonNull<any>).ofType))
139
+ );
140
+ }
141
+
142
+ function uriForASTNode(node: ASTNode): DocumentUri | null {
143
+ const uri = node.loc && node.loc.source && node.loc.source.name;
144
+ if (!uri || uri === "GraphQL") {
145
+ return null;
146
+ }
147
+ return uri;
148
+ }
149
+
150
+ function locationForASTNode(node: ASTNode): Location | null {
151
+ const uri = uriForASTNode(node);
152
+ if (!uri) return null;
153
+ return Location.create(uri, rangeForASTNode(node));
154
+ }
155
+
156
+ function symbolForFieldDefinition(
157
+ definition: FieldDefinitionNode,
158
+ ): DocumentSymbol {
159
+ return {
160
+ name: definition.name.value,
161
+ kind: SymbolKind.Field,
162
+ range: rangeForASTNode(definition),
163
+ selectionRange: rangeForASTNode(definition),
164
+ };
165
+ }
166
+
87
167
  export function isClientProject(
88
168
  project: GraphQLProject,
89
169
  ): project is GraphQLClientProject {
@@ -96,7 +176,7 @@ export interface GraphQLClientProjectConfig {
96
176
  configFolderURI: URI;
97
177
  loadingHandler: LoadingHandler;
98
178
  }
99
- export class GraphQLClientProject extends GraphQLProject {
179
+ export class GraphQLClientProject extends GraphQLInternalProject {
100
180
  public serviceID?: string;
101
181
  public config!: ClientConfig;
102
182
 
@@ -121,20 +201,7 @@ export class GraphQLClientProject extends GraphQLProject {
121
201
  super({ config, configFolderURI, loadingHandler, clientIdentity });
122
202
  this.serviceID = config.graph;
123
203
 
124
- /**
125
- * This function is used in the Array.filter function below it to remove any .env files and config files.
126
- * If there are 0 files remaining after removing those files, we should warn the user that their config
127
- * may be wrong. We shouldn't throw an error here, since they could just be initially setting up a project
128
- * and there's no way to know for sure that there _should_ be files.
129
- */
130
- const filterConfigAndEnvFiles = (path: string) =>
131
- !(
132
- path.includes("apollo.config") ||
133
- path.includes(".env") ||
134
- (config.configURI && path === config.configURI.fsPath)
135
- );
136
-
137
- if (this.allIncludedFiles().filter(filterConfigAndEnvFiles).length === 0) {
204
+ if (this.allIncludedFiles().length === 0) {
138
205
  console.warn(
139
206
  "⚠️ It looks like there are 0 files associated with this Apollo Project. " +
140
207
  "This may be because you don't have any files yet, or your includes/excludes " +
@@ -534,6 +601,649 @@ export class GraphQLClientProject extends GraphQLProject {
534
601
  // if we don't have an `engineClient`, we are not in a studio project and `this.config.graph` could be just about anything
535
602
  return this.engineClient ? this.config.graph : undefined;
536
603
  }
604
+
605
+ onCompletion: GraphQLProject["onCompletion"] = async (
606
+ { textDocument: { uri }, position },
607
+ _token: CancellationToken,
608
+ ) => {
609
+ const document = this.documentAt(uri, position);
610
+ if (!document) return [];
611
+
612
+ if (!this.schema) return [];
613
+
614
+ const rawPositionInDocument = positionFromPositionInContainingDocument(
615
+ document.source,
616
+ position,
617
+ );
618
+ const positionInDocument = new GraphQlPosition(
619
+ rawPositionInDocument.line,
620
+ rawPositionInDocument.character,
621
+ );
622
+
623
+ const token = getTokenAtPosition(document.source.body, positionInDocument);
624
+ const state =
625
+ token.state.kind === "Invalid" ? token.state.prevState : token.state;
626
+ const typeInfo = getTypeInfo(this.schema, token.state);
627
+
628
+ if (state?.kind === "DirectiveDef") {
629
+ return DirectiveLocations.map((location) => ({
630
+ label: location,
631
+ kind: CompletionItemKind.Constant,
632
+ }));
633
+ }
634
+
635
+ const suggestions = getAutocompleteSuggestions(
636
+ this.schema,
637
+ document.source.body,
638
+ positionInDocument,
639
+ );
640
+
641
+ if (
642
+ state?.kind === "SelectionSet" ||
643
+ state?.kind === "Field" ||
644
+ state?.kind === "AliasedField"
645
+ ) {
646
+ const parentType = typeInfo.parentType;
647
+ const parentFields =
648
+ isInterfaceType(parentType) || isObjectType(parentType)
649
+ ? parentType.getFields()
650
+ : {};
651
+
652
+ if (isAbstractType(parentType)) {
653
+ parentFields[TypeNameMetaFieldDef.name] = TypeNameMetaFieldDef;
654
+ }
655
+
656
+ if (parentType === this.schema.getQueryType()) {
657
+ parentFields[SchemaMetaFieldDef.name] = SchemaMetaFieldDef;
658
+ parentFields[TypeMetaFieldDef.name] = TypeMetaFieldDef;
659
+ }
660
+
661
+ return suggestions.map((suggest) => {
662
+ // when code completing fields, expand out required variables and open braces
663
+ const suggestedField = parentFields[suggest.label] as GraphQLField<
664
+ void,
665
+ void
666
+ >;
667
+ if (!suggestedField) {
668
+ return suggest;
669
+ } else {
670
+ const requiredArgs = suggestedField.args.filter((a) =>
671
+ isNonNullType(a.type),
672
+ );
673
+ const paramsSection =
674
+ requiredArgs.length > 0
675
+ ? `(${requiredArgs
676
+ .map((a, i) => `${a.name}: $${i + 1}`)
677
+ .join(", ")})`
678
+ : ``;
679
+
680
+ const isClientType =
681
+ parentType &&
682
+ "clientSchema" in parentType &&
683
+ parentType.clientSchema?.localFields?.includes(suggestedField.name);
684
+ const directives = isClientType ? " @client" : "";
685
+
686
+ const snippet = hasFields(suggestedField.type)
687
+ ? `${suggest.label}${paramsSection}${directives} {\n\t$0\n}`
688
+ : `${suggest.label}${paramsSection}${directives}`;
689
+
690
+ return {
691
+ ...suggest,
692
+ insertText: snippet,
693
+ insertTextFormat: InsertTextFormat.Snippet,
694
+ };
695
+ }
696
+ });
697
+ }
698
+
699
+ if (state?.kind === "Directive") {
700
+ return suggestions.map((suggest) => {
701
+ const directive = this.schema!.getDirective(suggest.label);
702
+ if (!directive) {
703
+ return suggest;
704
+ }
705
+
706
+ const requiredArgs = directive.args.filter(isNonNullType);
707
+ const paramsSection =
708
+ requiredArgs.length > 0
709
+ ? `(${requiredArgs
710
+ .map((a, i) => `${a.name}: $${i + 1}`)
711
+ .join(", ")})`
712
+ : ``;
713
+
714
+ const snippet = `${suggest.label}${paramsSection}`;
715
+
716
+ const argsString =
717
+ directive.args.length > 0
718
+ ? `(${directive.args
719
+ .map((a) => `${a.name}: ${a.type}`)
720
+ .join(", ")})`
721
+ : "";
722
+
723
+ const content = [
724
+ [`\`\`\`graphql`, `@${suggest.label}${argsString}`, `\`\`\``].join(
725
+ "\n",
726
+ ),
727
+ ];
728
+
729
+ if (suggest.documentation) {
730
+ if (typeof suggest.documentation === "string") {
731
+ content.push(suggest.documentation);
732
+ } else {
733
+ // TODO (jason) `(string | MarkupContent) & (string | null)` is a weird type,
734
+ // leaving this for safety for now
735
+ content.push((suggest.documentation as any).value);
736
+ }
737
+ }
738
+
739
+ const doc = {
740
+ kind: MarkupKind.Markdown,
741
+ value: content.join("\n\n"),
742
+ };
743
+
744
+ return {
745
+ ...suggest,
746
+ documentation: doc,
747
+ insertText: snippet,
748
+ insertTextFormat: InsertTextFormat.Snippet,
749
+ };
750
+ });
751
+ }
752
+
753
+ return suggestions;
754
+ };
755
+
756
+ onHover: GraphQLProject["onHover"] = async (
757
+ { textDocument: { uri }, position },
758
+ _token,
759
+ ) => {
760
+ const document = this.documentAt(uri, position);
761
+ if (!(document && document.ast)) return null;
762
+
763
+ if (!this.schema) return null;
764
+
765
+ const positionInDocument = positionFromPositionInContainingDocument(
766
+ document.source,
767
+ position,
768
+ );
769
+
770
+ const nodeAndTypeInfo = getASTNodeAndTypeInfoAtPosition(
771
+ document.source,
772
+ positionInDocument,
773
+ document.ast,
774
+ this.schema,
775
+ );
776
+
777
+ if (nodeAndTypeInfo) {
778
+ const [node, typeInfo] = nodeAndTypeInfo;
779
+
780
+ switch (node.kind) {
781
+ case Kind.FRAGMENT_SPREAD: {
782
+ const fragmentName = node.name.value;
783
+ const fragment = this.fragments[fragmentName];
784
+ if (fragment) {
785
+ return {
786
+ contents: {
787
+ language: "graphql",
788
+ value: `fragment ${fragmentName} on ${fragment.typeCondition.name.value}`,
789
+ },
790
+ };
791
+ }
792
+ break;
793
+ }
794
+
795
+ case Kind.FIELD: {
796
+ const parentType = typeInfo.getParentType();
797
+ const fieldDef = typeInfo.getFieldDef();
798
+
799
+ if (parentType && fieldDef) {
800
+ const argsString =
801
+ fieldDef.args.length > 0
802
+ ? `(${fieldDef.args
803
+ .map((a) => `${a.name}: ${a.type}`)
804
+ .join(", ")})`
805
+ : "";
806
+ const isClientType =
807
+ parentType.clientSchema &&
808
+ parentType.clientSchema.localFields &&
809
+ parentType.clientSchema.localFields.includes(fieldDef.name);
810
+
811
+ const isResolvedLocally =
812
+ node.directives &&
813
+ node.directives.some(
814
+ (directive) => directive.name.value === "client",
815
+ );
816
+
817
+ const content = [
818
+ [
819
+ `\`\`\`graphql`,
820
+ `${parentType}.${fieldDef.name}${argsString}: ${fieldDef.type}`,
821
+ `\`\`\``,
822
+ ].join("\n"),
823
+ ];
824
+
825
+ const info: string[] = [];
826
+ if (isClientType) {
827
+ info.push("`Client-Only Field`");
828
+ }
829
+ if (isResolvedLocally) {
830
+ info.push("`Resolved locally`");
831
+ }
832
+
833
+ if (info.length !== 0) {
834
+ content.push(info.join(" "));
835
+ }
836
+
837
+ if (fieldDef.description) {
838
+ content.push(fieldDef.description);
839
+ }
840
+
841
+ return {
842
+ contents: content.join("\n\n---\n\n"),
843
+ range: rangeForASTNode(highlightNodeForNode(node)),
844
+ };
845
+ }
846
+
847
+ break;
848
+ }
849
+
850
+ case Kind.NAMED_TYPE: {
851
+ const type = this.schema.getType(
852
+ node.name.value,
853
+ ) as GraphQLNamedType | void;
854
+ if (!type) break;
855
+
856
+ const content = [[`\`\`\`graphql`, `${type}`, `\`\`\``].join("\n")];
857
+
858
+ if (type.description) {
859
+ content.push(type.description);
860
+ }
861
+
862
+ return {
863
+ contents: content.join("\n\n---\n\n"),
864
+ range: rangeForASTNode(highlightNodeForNode(node)),
865
+ };
866
+ }
867
+
868
+ case Kind.ARGUMENT: {
869
+ const argumentNode = typeInfo.getArgument()!;
870
+ const content = [
871
+ [
872
+ `\`\`\`graphql`,
873
+ `${argumentNode.name}: ${argumentNode.type}`,
874
+ `\`\`\``,
875
+ ].join("\n"),
876
+ ];
877
+ if (argumentNode.description) {
878
+ content.push(argumentNode.description);
879
+ }
880
+ return {
881
+ contents: content.join("\n\n---\n\n"),
882
+ range: rangeForASTNode(highlightNodeForNode(node)),
883
+ };
884
+ }
885
+
886
+ case Kind.DIRECTIVE: {
887
+ const directiveNode = typeInfo.getDirective();
888
+ if (!directiveNode) break;
889
+ const argsString =
890
+ directiveNode.args.length > 0
891
+ ? `(${directiveNode.args
892
+ .map((a) => `${a.name}: ${a.type}`)
893
+ .join(", ")})`
894
+ : "";
895
+ const content = [
896
+ [
897
+ `\`\`\`graphql`,
898
+ `@${directiveNode.name}${argsString}`,
899
+ `\`\`\``,
900
+ ].join("\n"),
901
+ ];
902
+ if (directiveNode.description) {
903
+ content.push(directiveNode.description);
904
+ }
905
+ return {
906
+ contents: content.join("\n\n---\n\n"),
907
+ range: rangeForASTNode(highlightNodeForNode(node)),
908
+ };
909
+ }
910
+ }
911
+ }
912
+ return null;
913
+ };
914
+
915
+ onDefinition: GraphQLProject["onDefinition"] = async ({
916
+ position,
917
+ textDocument: { uri },
918
+ }) => {
919
+ const document = this.documentAt(uri, position);
920
+ if (!(document && document.ast)) return null;
921
+
922
+ if (!this.schema) return null;
923
+
924
+ const positionInDocument = positionFromPositionInContainingDocument(
925
+ document.source,
926
+ position,
927
+ );
928
+
929
+ const nodeAndTypeInfo = getASTNodeAndTypeInfoAtPosition(
930
+ document.source,
931
+ positionInDocument,
932
+ document.ast,
933
+ this.schema,
934
+ );
935
+
936
+ if (nodeAndTypeInfo) {
937
+ const [node, typeInfo] = nodeAndTypeInfo;
938
+
939
+ switch (node.kind) {
940
+ case Kind.FRAGMENT_SPREAD: {
941
+ const fragmentName = node.name.value;
942
+ const fragment = this.fragments[fragmentName];
943
+ if (fragment && fragment.loc) {
944
+ return locationForASTNode(fragment);
945
+ }
946
+ break;
947
+ }
948
+ case Kind.FIELD: {
949
+ const fieldDef = typeInfo.getFieldDef();
950
+
951
+ if (!(fieldDef && fieldDef.astNode && fieldDef.astNode.loc)) break;
952
+
953
+ return locationForASTNode(fieldDef.astNode);
954
+ }
955
+ case Kind.NAMED_TYPE: {
956
+ const type = typeFromAST(this.schema, node);
957
+
958
+ if (!(type && type.astNode && type.astNode.loc)) break;
959
+
960
+ return locationForASTNode(type.astNode);
961
+ }
962
+ case Kind.DIRECTIVE: {
963
+ const directive = this.schema.getDirective(node.name.value);
964
+
965
+ if (!(directive && directive.astNode && directive.astNode.loc)) break;
966
+
967
+ return locationForASTNode(directive.astNode);
968
+ }
969
+ }
970
+ }
971
+ return null;
972
+ };
973
+
974
+ onReferences: GraphQLProject["onReferences"] = async ({
975
+ position,
976
+ textDocument: { uri },
977
+ }) => {
978
+ const document = this.documentAt(uri, position);
979
+ if (!(document && document.ast)) return null;
980
+
981
+ if (!this.schema) return null;
982
+
983
+ const positionInDocument = positionFromPositionInContainingDocument(
984
+ document.source,
985
+ position,
986
+ );
987
+
988
+ const nodeAndTypeInfo = getASTNodeAndTypeInfoAtPosition(
989
+ document.source,
990
+ positionInDocument,
991
+ document.ast,
992
+ this.schema,
993
+ );
994
+
995
+ if (nodeAndTypeInfo) {
996
+ const [node, typeInfo] = nodeAndTypeInfo;
997
+
998
+ switch (node.kind) {
999
+ case Kind.FRAGMENT_DEFINITION: {
1000
+ const fragmentName = node.name.value;
1001
+ return this.fragmentSpreadsForFragment(fragmentName)
1002
+ .map((fragmentSpread) => locationForASTNode(fragmentSpread))
1003
+ .filter(isNotNullOrUndefined);
1004
+ }
1005
+ // TODO(jbaxleyiii): manage no parent type references (unions + scalars)
1006
+ // TODO(jbaxleyiii): support more than fields
1007
+ case Kind.FIELD_DEFINITION: {
1008
+ // case Kind.ENUM_VALUE_DEFINITION:
1009
+ // case Kind.INPUT_OBJECT_TYPE_DEFINITION:
1010
+ // case Kind.INPUT_OBJECT_TYPE_EXTENSION: {
1011
+ const offset = positionToOffset(document.source, positionInDocument);
1012
+ // withWithTypeInfo doesn't suppport SDL so we instead
1013
+ // write our own visitor methods here to collect the fields that we
1014
+ // care about
1015
+ let parent: ASTNode | null = null;
1016
+ visit(document.ast, {
1017
+ enter(node: ASTNode) {
1018
+ // the parent types we care about
1019
+ if (
1020
+ node.loc &&
1021
+ node.loc.start <= offset &&
1022
+ offset <= node.loc.end &&
1023
+ (node.kind === Kind.OBJECT_TYPE_DEFINITION ||
1024
+ node.kind === Kind.OBJECT_TYPE_EXTENSION ||
1025
+ node.kind === Kind.INTERFACE_TYPE_DEFINITION ||
1026
+ node.kind === Kind.INTERFACE_TYPE_EXTENSION ||
1027
+ node.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION ||
1028
+ node.kind === Kind.INPUT_OBJECT_TYPE_EXTENSION ||
1029
+ node.kind === Kind.ENUM_TYPE_DEFINITION ||
1030
+ node.kind === Kind.ENUM_TYPE_EXTENSION)
1031
+ ) {
1032
+ parent = node;
1033
+ }
1034
+ return;
1035
+ },
1036
+ });
1037
+ return this.getOperationFieldsFromFieldDefinition(
1038
+ node.name.value,
1039
+ parent,
1040
+ )
1041
+ .map((fieldNode) => locationForASTNode(fieldNode))
1042
+ .filter(isNotNullOrUndefined);
1043
+ }
1044
+ }
1045
+ }
1046
+
1047
+ return null;
1048
+ };
1049
+
1050
+ onDocumentSymbol: GraphQLProject["onDocumentSymbol"] = async ({
1051
+ textDocument: { uri },
1052
+ }) => {
1053
+ const definitions = this.definitionsAt(uri);
1054
+
1055
+ const symbols: DocumentSymbol[] = [];
1056
+
1057
+ for (const definition of definitions) {
1058
+ if (isExecutableDefinitionNode(definition)) {
1059
+ if (!definition.name) continue;
1060
+ const location = locationForASTNode(definition);
1061
+ if (!location) continue;
1062
+ symbols.push({
1063
+ name: definition.name.value,
1064
+ kind: SymbolKind.Function,
1065
+ range: rangeForASTNode(definition),
1066
+ selectionRange: rangeForASTNode(highlightNodeForNode(definition)),
1067
+ });
1068
+ } else if (
1069
+ isTypeSystemDefinitionNode(definition) ||
1070
+ isTypeSystemExtensionNode(definition)
1071
+ ) {
1072
+ if (
1073
+ definition.kind === Kind.SCHEMA_DEFINITION ||
1074
+ definition.kind === Kind.SCHEMA_EXTENSION
1075
+ ) {
1076
+ continue;
1077
+ }
1078
+ symbols.push({
1079
+ name: definition.name.value,
1080
+ kind: SymbolKind.Class,
1081
+ range: rangeForASTNode(definition),
1082
+ selectionRange: rangeForASTNode(highlightNodeForNode(definition)),
1083
+ children:
1084
+ definition.kind === Kind.OBJECT_TYPE_DEFINITION ||
1085
+ definition.kind === Kind.OBJECT_TYPE_EXTENSION
1086
+ ? (definition.fields || []).map(symbolForFieldDefinition)
1087
+ : undefined,
1088
+ });
1089
+ }
1090
+ }
1091
+
1092
+ return symbols;
1093
+ };
1094
+
1095
+ async provideSymbol(
1096
+ _query: string,
1097
+ _token: CancellationToken,
1098
+ ): Promise<SymbolInformation[]> {
1099
+ const symbols: SymbolInformation[] = [];
1100
+
1101
+ for (const definition of this.definitions) {
1102
+ if (isExecutableDefinitionNode(definition)) {
1103
+ if (!definition.name) continue;
1104
+ const location = locationForASTNode(definition);
1105
+ if (!location) continue;
1106
+ symbols.push({
1107
+ name: definition.name.value,
1108
+ kind: SymbolKind.Function,
1109
+ location,
1110
+ });
1111
+ }
1112
+ }
1113
+ return symbols;
1114
+ }
1115
+
1116
+ onCodeLens: GraphQLProject["onCodeLens"] = async ({
1117
+ textDocument: { uri },
1118
+ }) => {
1119
+ // Wait for the project to be fully initialized, so we always provide code lenses for open files, even
1120
+ // if we receive the request before the project is ready.
1121
+ await this.whenReady;
1122
+
1123
+ const documents = this.documentsAt(uri);
1124
+ if (!documents) return [];
1125
+
1126
+ let codeLenses: CodeLens[] = [];
1127
+
1128
+ for (const document of documents) {
1129
+ if (!document.ast) continue;
1130
+
1131
+ for (const definition of document.ast.definitions) {
1132
+ if (definition.kind === Kind.OPERATION_DEFINITION) {
1133
+ /*
1134
+ if (set.endpoint) {
1135
+ const fragmentSpreads: Set<
1136
+ graphql.FragmentDefinitionNode
1137
+ > = new Set();
1138
+ const searchForReferencedFragments = (node: graphql.ASTNode) => {
1139
+ visit(node, {
1140
+ FragmentSpread(node: FragmentSpreadNode) {
1141
+ const fragDefn = project.fragments[node.name.value];
1142
+ if (!fragDefn) return;
1143
+
1144
+ if (!fragmentSpreads.has(fragDefn)) {
1145
+ fragmentSpreads.add(fragDefn);
1146
+ searchForReferencedFragments(fragDefn);
1147
+ }
1148
+ }
1149
+ });
1150
+ };
1151
+
1152
+ searchForReferencedFragments(definition);
1153
+
1154
+ codeLenses.push({
1155
+ range: rangeForASTNode(definition),
1156
+ command: Command.create(
1157
+ `Run ${definition.operation}`,
1158
+ "apollographql.runQuery",
1159
+ graphql.parse(
1160
+ [definition, ...fragmentSpreads]
1161
+ .map(n => graphql.print(n))
1162
+ .join("\n")
1163
+ ),
1164
+ definition.operation === "subscription"
1165
+ ? set.endpoint.subscriptions
1166
+ : set.endpoint.url,
1167
+ set.endpoint.headers,
1168
+ graphql.printSchema(set.schema!)
1169
+ )
1170
+ });
1171
+ }
1172
+ */
1173
+ } else if (definition.kind === Kind.FRAGMENT_DEFINITION) {
1174
+ // remove project references for fragment now
1175
+ // const fragmentName = definition.name.value;
1176
+ // const locations = project
1177
+ // .fragmentSpreadsForFragment(fragmentName)
1178
+ // .map(fragmentSpread => locationForASTNode(fragmentSpread))
1179
+ // .filter(isNotNullOrUndefined);
1180
+ // const command = Command.create(
1181
+ // `${locations.length} references`,
1182
+ // "editor.action.showReferences",
1183
+ // uri,
1184
+ // rangeForASTNode(definition).start,
1185
+ // locations
1186
+ // );
1187
+ // codeLenses.push({
1188
+ // range: rangeForASTNode(definition),
1189
+ // command
1190
+ // });
1191
+ }
1192
+ }
1193
+ }
1194
+ return codeLenses;
1195
+ };
1196
+
1197
+ onCodeAction: GraphQLProject["onCodeAction"] = async ({
1198
+ textDocument: { uri },
1199
+ range,
1200
+ }) => {
1201
+ function isPositionLessThanOrEqual(a: Position, b: Position) {
1202
+ return a.line !== b.line ? a.line < b.line : a.character <= b.character;
1203
+ }
1204
+
1205
+ if (!this.diagnosticSet) return [];
1206
+
1207
+ await this.whenReady;
1208
+
1209
+ const documents = this.documentsAt(uri);
1210
+ if (!documents) return [];
1211
+
1212
+ const errors: Set<GraphQLError> = new Set();
1213
+
1214
+ for (const [diagnosticUri, diagnostics] of this.diagnosticSet.entries()) {
1215
+ if (diagnosticUri !== uri) continue;
1216
+
1217
+ for (const diagnostic of diagnostics) {
1218
+ if (
1219
+ GraphQLDiagnostic.is(diagnostic) &&
1220
+ isPositionLessThanOrEqual(range.start, diagnostic.range.end) &&
1221
+ isPositionLessThanOrEqual(diagnostic.range.start, range.end)
1222
+ ) {
1223
+ errors.add(diagnostic.error);
1224
+ }
1225
+ }
1226
+ }
1227
+
1228
+ const result: CodeAction[] = [];
1229
+
1230
+ for (const error of errors) {
1231
+ const { extensions } = error;
1232
+ if (!extensions || !extensions.codeAction) continue;
1233
+
1234
+ const { message, edits }: CodeActionInfo = extensions.codeAction as any;
1235
+
1236
+ const codeAction = CodeAction.create(
1237
+ message,
1238
+ { changes: { [uri]: edits } },
1239
+ CodeActionKind.QuickFix,
1240
+ );
1241
+
1242
+ result.push(codeAction);
1243
+ }
1244
+
1245
+ return result;
1246
+ };
537
1247
  }
538
1248
 
539
1249
  function buildExplorerURL({