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.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +14 -0
- package/.circleci/config.yml +82 -0
- package/.eslintrc.js +10 -0
- package/.gitattributes +1 -0
- package/.github/workflows/release.yml +95 -0
- package/.gitleaks.toml +26 -0
- package/.nvmrc +1 -0
- package/.prettierrc +5 -0
- package/.vscode/launch.json +61 -0
- package/.vscode/settings.json +16 -0
- package/.vscode/tasks.json +18 -0
- package/.vscodeignore +17 -1
- package/CHANGELOG.md +172 -1
- package/LICENSE +2 -2
- package/README.md +9 -9
- package/codegen.yml +12 -0
- package/images/IconRun.svg +8 -0
- package/jest.config.ts +11 -0
- package/package.json +102 -22
- package/renovate.json +23 -0
- package/src/__mocks__/fs.js +3 -0
- package/src/__tests__/statusBar.test.ts +8 -7
- package/src/debug.ts +2 -5
- package/src/env/fetch/fetch.ts +32 -0
- package/src/env/fetch/global.ts +30 -0
- package/src/env/fetch/index.d.ts +2 -0
- package/src/env/fetch/index.ts +2 -0
- package/src/env/fetch/url.ts +9 -0
- package/src/env/index.ts +4 -0
- package/src/env/polyfills/array.ts +17 -0
- package/src/env/polyfills/index.ts +2 -0
- package/src/env/polyfills/object.ts +7 -0
- package/src/env/typescript-utility-types.ts +2 -0
- package/src/extension.ts +106 -37
- package/src/language-server/__tests__/diagnostics.test.ts +86 -0
- package/src/language-server/__tests__/document.test.ts +187 -0
- package/src/language-server/__tests__/fileSet.test.ts +46 -0
- package/src/language-server/__tests__/fixtures/starwarsSchema.ts +1917 -0
- package/src/language-server/config/__tests__/config.ts +128 -0
- package/src/language-server/config/__tests__/loadConfig.ts +508 -0
- package/src/language-server/config/__tests__/utils.ts +106 -0
- package/src/language-server/config/config.ts +219 -0
- package/src/language-server/config/index.ts +3 -0
- package/src/language-server/config/loadConfig.ts +228 -0
- package/src/language-server/config/utils.ts +56 -0
- package/src/language-server/diagnostics.ts +109 -0
- package/src/language-server/document.ts +277 -0
- package/src/language-server/engine/GraphQLDataSource.ts +124 -0
- package/src/language-server/engine/index.ts +105 -0
- package/src/language-server/engine/operations/frontendUrlRoot.ts +7 -0
- package/src/language-server/engine/operations/schemaTagsAndFieldStats.ts +24 -0
- package/src/language-server/errors/__tests__/NoMissingClientDirectives.test.ts +220 -0
- package/src/language-server/errors/logger.ts +58 -0
- package/src/language-server/errors/validation.ts +277 -0
- package/src/language-server/fileSet.ts +65 -0
- package/src/language-server/format.ts +48 -0
- package/src/language-server/graphqlTypes.ts +7176 -0
- package/src/language-server/index.ts +29 -0
- package/src/language-server/languageProvider.ts +798 -0
- package/src/language-server/loadingHandler.ts +64 -0
- package/src/language-server/project/base.ts +399 -0
- package/src/language-server/project/client.ts +602 -0
- package/src/language-server/project/defaultClientSchema.ts +45 -0
- package/src/language-server/project/service.ts +48 -0
- package/src/language-server/providers/schema/__tests__/file.ts +150 -0
- package/src/language-server/providers/schema/base.ts +15 -0
- package/src/language-server/providers/schema/endpoint.ts +157 -0
- package/src/language-server/providers/schema/engine.ts +197 -0
- package/src/language-server/providers/schema/file.ts +167 -0
- package/src/language-server/providers/schema/index.ts +75 -0
- package/src/language-server/server.ts +252 -0
- package/src/language-server/typings/codemirror.d.ts +4 -0
- package/src/language-server/typings/graphql.d.ts +27 -0
- package/src/language-server/utilities/__tests__/graphql.test.ts +411 -0
- package/src/language-server/utilities/__tests__/uri.ts +55 -0
- package/src/language-server/utilities/debouncer.ts +8 -0
- package/src/language-server/utilities/debug.ts +89 -0
- package/src/language-server/utilities/graphql.ts +432 -0
- package/src/language-server/utilities/index.ts +3 -0
- package/src/language-server/utilities/source.ts +182 -0
- package/src/language-server/utilities/uri.ts +19 -0
- package/src/language-server/workspace.ts +262 -0
- package/src/languageServerClient.ts +19 -12
- package/src/messages.ts +84 -0
- package/src/statusBar.ts +5 -5
- package/src/tools/__tests__/buildServiceDefinition.test.ts +491 -0
- package/src/tools/__tests__/snapshotSerializers/astSerializer.ts +19 -0
- package/src/tools/__tests__/snapshotSerializers/graphQLTypeSerializer.ts +14 -0
- package/src/tools/buildServiceDefinition.ts +241 -0
- package/src/tools/index.ts +6 -0
- package/src/tools/schema/index.ts +2 -0
- package/src/tools/schema/resolveObject.ts +18 -0
- package/src/tools/schema/resolverMap.ts +23 -0
- package/src/tools/utilities/graphql.ts +22 -0
- package/src/tools/utilities/index.ts +3 -0
- package/src/tools/utilities/invariant.ts +5 -0
- package/src/tools/utilities/predicates.ts +5 -0
- package/src/utils.ts +1 -16
- package/syntaxes/graphql.js.json +3 -3
- package/syntaxes/graphql.json +13 -9
- package/syntaxes/graphql.lua.json +51 -0
- package/syntaxes/graphql.rb.json +1 -1
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +20 -7
- package/create-server-symlink.js +0 -8
- package/lib/debug.d.ts +0 -11
- package/lib/debug.d.ts.map +0 -1
- package/lib/debug.js +0 -48
- package/lib/debug.js.map +0 -1
- package/lib/extension.d.ts +0 -4
- package/lib/extension.d.ts.map +0 -1
- package/lib/extension.js +0 -187
- package/lib/extension.js.map +0 -1
- package/lib/languageServerClient.d.ts +0 -4
- package/lib/languageServerClient.d.ts.map +0 -1
- package/lib/languageServerClient.js +0 -57
- package/lib/languageServerClient.js.map +0 -1
- package/lib/statusBar.d.ts +0 -24
- package/lib/statusBar.d.ts.map +0 -1
- package/lib/statusBar.js +0 -46
- package/lib/statusBar.js.map +0 -1
- package/lib/testRunner/index.d.ts +0 -3
- package/lib/testRunner/index.d.ts.map +0 -1
- package/lib/testRunner/index.js +0 -49
- package/lib/testRunner/index.js.map +0 -1
- package/lib/testRunner/jest-config.d.ts +0 -14
- package/lib/testRunner/jest-config.d.ts.map +0 -1
- package/lib/testRunner/jest-config.js +0 -18
- package/lib/testRunner/jest-config.js.map +0 -1
- package/lib/testRunner/jest-vscode-environment.d.ts +0 -2
- package/lib/testRunner/jest-vscode-environment.d.ts.map +0 -1
- package/lib/testRunner/jest-vscode-environment.js +0 -19
- package/lib/testRunner/jest-vscode-environment.js.map +0 -1
- package/lib/testRunner/jest-vscode-framework-setup.d.ts +0 -1
- package/lib/testRunner/jest-vscode-framework-setup.d.ts.map +0 -1
- package/lib/testRunner/jest-vscode-framework-setup.js +0 -3
- package/lib/testRunner/jest-vscode-framework-setup.js.map +0 -1
- package/lib/testRunner/vscode-test-script.d.ts +0 -2
- package/lib/testRunner/vscode-test-script.d.ts.map +0 -1
- package/lib/testRunner/vscode-test-script.js +0 -23
- package/lib/testRunner/vscode-test-script.js.map +0 -1
- package/lib/utils.d.ts +0 -18
- package/lib/utils.d.ts.map +0 -1
- package/lib/utils.js +0 -52
- package/lib/utils.js.map +0 -1
- package/src/testRunner/README.md +0 -23
- package/src/testRunner/index.ts +0 -72
- package/src/testRunner/jest-config.ts +0 -17
- package/src/testRunner/jest-vscode-environment.ts +0 -25
- package/src/testRunner/jest-vscode-framework-setup.ts +0 -10
- package/src/testRunner/jest.d.ts +0 -37
- package/src/testRunner/vscode-test-script.ts +0 -38
- package/tsconfig.test.json +0 -4
- 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
|
+
}
|