vscode-apollo 1.19.3 → 1.20.1
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/build-prs.yml +57 -0
- package/.github/workflows/release.yml +114 -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 +178 -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,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,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
|
+
};
|