vscode-apollo 1.19.2 → 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,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GraphQLSchema,
|
|
3
|
+
GraphQLError,
|
|
4
|
+
FragmentDefinitionNode,
|
|
5
|
+
findDeprecatedUsages,
|
|
6
|
+
isExecutableDefinitionNode,
|
|
7
|
+
} from "graphql";
|
|
8
|
+
|
|
9
|
+
import { Diagnostic, DiagnosticSeverity } from "vscode-languageserver";
|
|
10
|
+
|
|
11
|
+
import { GraphQLDocument } from "./document";
|
|
12
|
+
import { highlightNodeForNode } from "./utilities/graphql";
|
|
13
|
+
import { rangeForASTNode } from "./utilities/source";
|
|
14
|
+
|
|
15
|
+
import { getValidationErrors } from "./errors/validation";
|
|
16
|
+
import { DocumentUri } from "./project/base";
|
|
17
|
+
import { ValidationRule } from "graphql/validation/ValidationContext";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Build an array of code diagnostics for all executable definitions in a document.
|
|
21
|
+
*/
|
|
22
|
+
export function collectExecutableDefinitionDiagnositics(
|
|
23
|
+
schema: GraphQLSchema,
|
|
24
|
+
queryDocument: GraphQLDocument,
|
|
25
|
+
fragments: { [fragmentName: string]: FragmentDefinitionNode } = {},
|
|
26
|
+
rules?: ValidationRule[]
|
|
27
|
+
): Diagnostic[] {
|
|
28
|
+
const ast = queryDocument.ast;
|
|
29
|
+
if (!ast) return queryDocument.syntaxErrors;
|
|
30
|
+
|
|
31
|
+
const astWithExecutableDefinitions = {
|
|
32
|
+
...ast,
|
|
33
|
+
definitions: ast.definitions.filter(isExecutableDefinitionNode),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const diagnostics = [];
|
|
37
|
+
|
|
38
|
+
for (const error of getValidationErrors(
|
|
39
|
+
schema,
|
|
40
|
+
astWithExecutableDefinitions,
|
|
41
|
+
fragments,
|
|
42
|
+
rules
|
|
43
|
+
)) {
|
|
44
|
+
diagnostics.push(
|
|
45
|
+
...diagnosticsFromError(error, DiagnosticSeverity.Error, "Validation")
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const error of findDeprecatedUsages(
|
|
50
|
+
schema,
|
|
51
|
+
astWithExecutableDefinitions
|
|
52
|
+
)) {
|
|
53
|
+
diagnostics.push(
|
|
54
|
+
...diagnosticsFromError(error, DiagnosticSeverity.Warning, "Deprecation")
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return diagnostics;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function diagnosticsFromError(
|
|
62
|
+
error: GraphQLError,
|
|
63
|
+
severity: DiagnosticSeverity,
|
|
64
|
+
type: string
|
|
65
|
+
): GraphQLDiagnostic[] {
|
|
66
|
+
if (!error.nodes) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return error.nodes.map((node) => {
|
|
71
|
+
return {
|
|
72
|
+
source: `GraphQL: ${type}`,
|
|
73
|
+
message: error.message,
|
|
74
|
+
severity,
|
|
75
|
+
range: rangeForASTNode(highlightNodeForNode(node) || node),
|
|
76
|
+
error,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface GraphQLDiagnostic extends Diagnostic {
|
|
82
|
+
/**
|
|
83
|
+
* The GraphQLError that produced this Diagnostic
|
|
84
|
+
*/
|
|
85
|
+
error: GraphQLError;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export namespace GraphQLDiagnostic {
|
|
89
|
+
export function is(diagnostic: Diagnostic): diagnostic is GraphQLDiagnostic {
|
|
90
|
+
return "error" in diagnostic;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class DiagnosticSet {
|
|
95
|
+
private diagnosticsByFile = new Map<DocumentUri, Diagnostic[]>();
|
|
96
|
+
|
|
97
|
+
entries() {
|
|
98
|
+
return this.diagnosticsByFile.entries();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
addDiagnostics(uri: DocumentUri, diagnostics: Diagnostic[]) {
|
|
102
|
+
const existingDiagnostics = this.diagnosticsByFile.get(uri);
|
|
103
|
+
if (!existingDiagnostics) {
|
|
104
|
+
this.diagnosticsByFile.set(uri, diagnostics);
|
|
105
|
+
} else {
|
|
106
|
+
existingDiagnostics.push(...diagnostics);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { parse, Source, DocumentNode } from "graphql";
|
|
2
|
+
import { SourceLocation, getLocation } from "graphql/language/location";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
TextDocument,
|
|
6
|
+
Position,
|
|
7
|
+
Diagnostic,
|
|
8
|
+
DiagnosticSeverity,
|
|
9
|
+
} from "vscode-languageserver";
|
|
10
|
+
|
|
11
|
+
import { getRange as rangeOfTokenAtLocation } from "graphql-language-service-interface/dist/getDiagnostics";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
positionFromSourceLocation,
|
|
15
|
+
rangeInContainingDocument,
|
|
16
|
+
} from "./utilities/source";
|
|
17
|
+
|
|
18
|
+
export class GraphQLDocument {
|
|
19
|
+
ast?: DocumentNode;
|
|
20
|
+
syntaxErrors: Diagnostic[] = [];
|
|
21
|
+
|
|
22
|
+
constructor(public source: Source) {
|
|
23
|
+
try {
|
|
24
|
+
this.ast = parse(source);
|
|
25
|
+
} catch (error: any) {
|
|
26
|
+
// Don't add syntax errors when GraphQL has been commented out
|
|
27
|
+
if (maybeCommentedOut(source.body)) return;
|
|
28
|
+
|
|
29
|
+
// A GraphQL syntax error only has a location and no node, because we don't have an AST
|
|
30
|
+
// So we use the online parser to get the range of the token at that location
|
|
31
|
+
const range = rangeInContainingDocument(
|
|
32
|
+
source,
|
|
33
|
+
rangeOfTokenAtLocation(error.locations[0], source.body)
|
|
34
|
+
);
|
|
35
|
+
this.syntaxErrors.push({
|
|
36
|
+
severity: DiagnosticSeverity.Error,
|
|
37
|
+
message: error.message,
|
|
38
|
+
source: "GraphQL: Syntax",
|
|
39
|
+
range,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
containsPosition(position: Position): boolean {
|
|
45
|
+
if (position.line < this.source.locationOffset.line - 1) return false;
|
|
46
|
+
const end = positionFromSourceLocation(
|
|
47
|
+
this.source,
|
|
48
|
+
getLocation(this.source, this.source.body.length)
|
|
49
|
+
);
|
|
50
|
+
return position.line <= end.line;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function extractGraphQLDocuments(
|
|
55
|
+
document: TextDocument,
|
|
56
|
+
tagName: string = "gql"
|
|
57
|
+
): GraphQLDocument[] | null {
|
|
58
|
+
switch (document.languageId) {
|
|
59
|
+
case "graphql":
|
|
60
|
+
return [
|
|
61
|
+
new GraphQLDocument(new Source(document.getText(), document.uri)),
|
|
62
|
+
];
|
|
63
|
+
case "javascript":
|
|
64
|
+
case "javascriptreact":
|
|
65
|
+
case "typescript":
|
|
66
|
+
case "typescriptreact":
|
|
67
|
+
case "vue":
|
|
68
|
+
case "svelte":
|
|
69
|
+
return extractGraphQLDocumentsFromJSTemplateLiterals(document, tagName);
|
|
70
|
+
case "python":
|
|
71
|
+
return extractGraphQLDocumentsFromPythonStrings(document, tagName);
|
|
72
|
+
case "ruby":
|
|
73
|
+
return extractGraphQLDocumentsFromRubyStrings(document, tagName);
|
|
74
|
+
case "dart":
|
|
75
|
+
return extractGraphQLDocumentsFromDartStrings(document, tagName);
|
|
76
|
+
case "reason":
|
|
77
|
+
return extractGraphQLDocumentsFromReasonStrings(document, tagName);
|
|
78
|
+
case "elixir":
|
|
79
|
+
return extractGraphQLDocumentsFromElixirStrings(document, tagName);
|
|
80
|
+
default:
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function extractGraphQLDocumentsFromJSTemplateLiterals(
|
|
86
|
+
document: TextDocument,
|
|
87
|
+
tagName: string
|
|
88
|
+
): GraphQLDocument[] | null {
|
|
89
|
+
const text = document.getText();
|
|
90
|
+
|
|
91
|
+
const documents: GraphQLDocument[] = [];
|
|
92
|
+
|
|
93
|
+
const regExp = new RegExp(
|
|
94
|
+
`(?:${tagName}(?:\\s|\\()*\`|\`#graphql)([\\s\\S]+?)\`\\)?`,
|
|
95
|
+
"gm"
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
let result;
|
|
99
|
+
while ((result = regExp.exec(text)) !== null) {
|
|
100
|
+
const contents = replacePlaceholdersWithWhiteSpace(result[1]);
|
|
101
|
+
const position = document.positionAt(result.index + (tagName.length + 1));
|
|
102
|
+
const locationOffset: SourceLocation = {
|
|
103
|
+
line: position.line + 1,
|
|
104
|
+
column: position.character + 1,
|
|
105
|
+
};
|
|
106
|
+
const source = new Source(contents, document.uri, locationOffset);
|
|
107
|
+
documents.push(new GraphQLDocument(source));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (documents.length < 1) return null;
|
|
111
|
+
|
|
112
|
+
return documents;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function extractGraphQLDocumentsFromPythonStrings(
|
|
116
|
+
document: TextDocument,
|
|
117
|
+
tagName: string
|
|
118
|
+
): GraphQLDocument[] | null {
|
|
119
|
+
const text = document.getText();
|
|
120
|
+
|
|
121
|
+
const documents: GraphQLDocument[] = [];
|
|
122
|
+
|
|
123
|
+
const regExp = new RegExp(
|
|
124
|
+
`\\b(${tagName}\\s*\\(\\s*[bfru]*("(?:"")?|'(?:'')?))([\\s\\S]+?)\\2\\s*\\)`,
|
|
125
|
+
"gm"
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
let result;
|
|
129
|
+
while ((result = regExp.exec(text)) !== null) {
|
|
130
|
+
const contents = replacePlaceholdersWithWhiteSpace(result[3]);
|
|
131
|
+
const position = document.positionAt(result.index + result[1].length);
|
|
132
|
+
const locationOffset: SourceLocation = {
|
|
133
|
+
line: position.line + 1,
|
|
134
|
+
column: position.character + 1,
|
|
135
|
+
};
|
|
136
|
+
const source = new Source(contents, document.uri, locationOffset);
|
|
137
|
+
documents.push(new GraphQLDocument(source));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (documents.length < 1) return null;
|
|
141
|
+
|
|
142
|
+
return documents;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function extractGraphQLDocumentsFromRubyStrings(
|
|
146
|
+
document: TextDocument,
|
|
147
|
+
tagName: string
|
|
148
|
+
): GraphQLDocument[] | null {
|
|
149
|
+
const text = document.getText();
|
|
150
|
+
|
|
151
|
+
const documents: GraphQLDocument[] = [];
|
|
152
|
+
|
|
153
|
+
const regExp = new RegExp(`(<<-${tagName})([\\s\\S]+?)${tagName}`, "gm");
|
|
154
|
+
|
|
155
|
+
let result;
|
|
156
|
+
while ((result = regExp.exec(text)) !== null) {
|
|
157
|
+
const contents = replacePlaceholdersWithWhiteSpace(result[2]);
|
|
158
|
+
const position = document.positionAt(result.index + result[1].length);
|
|
159
|
+
const locationOffset: SourceLocation = {
|
|
160
|
+
line: position.line + 1,
|
|
161
|
+
column: position.character + 1,
|
|
162
|
+
};
|
|
163
|
+
const source = new Source(contents, document.uri, locationOffset);
|
|
164
|
+
documents.push(new GraphQLDocument(source));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (documents.length < 1) return null;
|
|
168
|
+
|
|
169
|
+
return documents;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function extractGraphQLDocumentsFromDartStrings(
|
|
173
|
+
document: TextDocument,
|
|
174
|
+
tagName: string
|
|
175
|
+
): GraphQLDocument[] | null {
|
|
176
|
+
const text = document.getText();
|
|
177
|
+
|
|
178
|
+
const documents: GraphQLDocument[] = [];
|
|
179
|
+
|
|
180
|
+
const regExp = new RegExp(
|
|
181
|
+
`\\b(${tagName}\\(\\s*r?("""|'''))([\\s\\S]+?)\\2\\s*\\)`,
|
|
182
|
+
"gm"
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
let result;
|
|
186
|
+
while ((result = regExp.exec(text)) !== null) {
|
|
187
|
+
const contents = replacePlaceholdersWithWhiteSpace(result[3]);
|
|
188
|
+
const position = document.positionAt(result.index + result[1].length);
|
|
189
|
+
const locationOffset: SourceLocation = {
|
|
190
|
+
line: position.line + 1,
|
|
191
|
+
column: position.character + 1,
|
|
192
|
+
};
|
|
193
|
+
const source = new Source(contents, document.uri, locationOffset);
|
|
194
|
+
documents.push(new GraphQLDocument(source));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (documents.length < 1) return null;
|
|
198
|
+
|
|
199
|
+
return documents;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function extractGraphQLDocumentsFromReasonStrings(
|
|
203
|
+
document: TextDocument,
|
|
204
|
+
tagName: string
|
|
205
|
+
): GraphQLDocument[] | null {
|
|
206
|
+
const text = document.getText();
|
|
207
|
+
|
|
208
|
+
const documents: GraphQLDocument[] = [];
|
|
209
|
+
|
|
210
|
+
const reasonFileFilter = new RegExp(/(\[%(graphql|relay\.))/g);
|
|
211
|
+
|
|
212
|
+
if (!reasonFileFilter.test(text)) {
|
|
213
|
+
return documents;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const reasonRegexp = new RegExp(
|
|
217
|
+
/(?<=\[%(graphql|relay\.\w*)[\s\S]*{\|)[.\s\S]+?(?=\|})/gm
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
let result;
|
|
221
|
+
while ((result = reasonRegexp.exec(text)) !== null) {
|
|
222
|
+
const contents = result[0];
|
|
223
|
+
const position = document.positionAt(result.index);
|
|
224
|
+
const locationOffset: SourceLocation = {
|
|
225
|
+
line: position.line + 1,
|
|
226
|
+
column: position.character + 1,
|
|
227
|
+
};
|
|
228
|
+
const source = new Source(contents, document.uri, locationOffset);
|
|
229
|
+
documents.push(new GraphQLDocument(source));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (documents.length < 1) return null;
|
|
233
|
+
|
|
234
|
+
return documents;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function extractGraphQLDocumentsFromElixirStrings(
|
|
238
|
+
document: TextDocument,
|
|
239
|
+
tagName: string
|
|
240
|
+
): GraphQLDocument[] | null {
|
|
241
|
+
const text = document.getText();
|
|
242
|
+
const documents: GraphQLDocument[] = [];
|
|
243
|
+
|
|
244
|
+
const regExp = new RegExp(
|
|
245
|
+
`\\b(${tagName}\\(\\s*r?("""))([\\s\\S]+?)\\2\\s*\\)`,
|
|
246
|
+
"gm"
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
let result;
|
|
250
|
+
while ((result = regExp.exec(text)) !== null) {
|
|
251
|
+
const contents = replacePlaceholdersWithWhiteSpace(result[3]);
|
|
252
|
+
const position = document.positionAt(result.index + result[1].length);
|
|
253
|
+
const locationOffset: SourceLocation = {
|
|
254
|
+
line: position.line + 1,
|
|
255
|
+
column: position.character + 1,
|
|
256
|
+
};
|
|
257
|
+
const source = new Source(contents, document.uri, locationOffset);
|
|
258
|
+
documents.push(new GraphQLDocument(source));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (documents.length < 1) return null;
|
|
262
|
+
|
|
263
|
+
return documents;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function replacePlaceholdersWithWhiteSpace(content: string) {
|
|
267
|
+
return content.replace(/\$\{([\s\S]+?)\}/gm, (match) => {
|
|
268
|
+
return Array(match.length).join(" ");
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function maybeCommentedOut(content: string) {
|
|
273
|
+
return (
|
|
274
|
+
(content.indexOf("/*") > -1 && content.indexOf("*/") > -1) ||
|
|
275
|
+
content.split("//").length > 1
|
|
276
|
+
);
|
|
277
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { DataSourceConfig } from "apollo-datasource";
|
|
2
|
+
import { ApolloLink, execute, GraphQLRequest, makePromise } from "apollo-link";
|
|
3
|
+
import { setContext } from "apollo-link-context";
|
|
4
|
+
import { onError } from "apollo-link-error";
|
|
5
|
+
import { createHttpLink } from "apollo-link-http";
|
|
6
|
+
import {
|
|
7
|
+
ApolloError,
|
|
8
|
+
AuthenticationError,
|
|
9
|
+
ForbiddenError,
|
|
10
|
+
} from "apollo-server-errors";
|
|
11
|
+
import to from "await-to-js";
|
|
12
|
+
import { GraphQLError } from "graphql";
|
|
13
|
+
import { fetch } from "../../env";
|
|
14
|
+
|
|
15
|
+
export interface GraphQLResponse<T> {
|
|
16
|
+
data?: T;
|
|
17
|
+
errors?: GraphQLError[];
|
|
18
|
+
}
|
|
19
|
+
export class GraphQLDataSource<TContext = any> {
|
|
20
|
+
public baseURL!: string;
|
|
21
|
+
public context!: TContext;
|
|
22
|
+
|
|
23
|
+
public initialize(config: DataSourceConfig<TContext>): void {
|
|
24
|
+
this.context = config.context;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// XXX can we kill the casting here?
|
|
28
|
+
public async execute<T>(
|
|
29
|
+
operation: GraphQLRequest
|
|
30
|
+
): Promise<GraphQLResponse<T>> {
|
|
31
|
+
return this.executeSingleOperation(operation) as Promise<
|
|
32
|
+
GraphQLResponse<T>
|
|
33
|
+
>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected willSendRequest?(request: any): any;
|
|
37
|
+
|
|
38
|
+
private composeLinks(): ApolloLink {
|
|
39
|
+
const uri = this.resolveUri();
|
|
40
|
+
|
|
41
|
+
return ApolloLink.from([
|
|
42
|
+
this.onErrorLink(),
|
|
43
|
+
this.onRequestLink(),
|
|
44
|
+
createHttpLink({ fetch, uri }),
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private didEncounterError(error: any) {
|
|
49
|
+
const status = error.statusCode ? error.statusCode : null;
|
|
50
|
+
const message = error.bodyText
|
|
51
|
+
? error.bodyText
|
|
52
|
+
: error.message
|
|
53
|
+
? error.message
|
|
54
|
+
: null;
|
|
55
|
+
|
|
56
|
+
let apolloError: ApolloError;
|
|
57
|
+
|
|
58
|
+
switch (status) {
|
|
59
|
+
case 401:
|
|
60
|
+
apolloError = new AuthenticationError(message);
|
|
61
|
+
break;
|
|
62
|
+
case 403:
|
|
63
|
+
apolloError = new ForbiddenError(message);
|
|
64
|
+
break;
|
|
65
|
+
default:
|
|
66
|
+
apolloError = new ApolloError(message);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw apolloError;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private async executeSingleOperation(operation: GraphQLRequest) {
|
|
73
|
+
const link = this.composeLinks();
|
|
74
|
+
|
|
75
|
+
const [error, response] = await to(makePromise(execute(link, operation)));
|
|
76
|
+
|
|
77
|
+
if (error) {
|
|
78
|
+
this.didEncounterError(error);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return response;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private resolveUri(): string {
|
|
85
|
+
const baseURL = this.baseURL;
|
|
86
|
+
|
|
87
|
+
if (!baseURL) {
|
|
88
|
+
throw new ApolloError(
|
|
89
|
+
"Cannot make request to GraphQL API, missing baseURL"
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return baseURL;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private onRequestLink() {
|
|
97
|
+
return setContext((_, request) => {
|
|
98
|
+
if (this.willSendRequest) {
|
|
99
|
+
this.willSendRequest(request);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return request;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private onErrorLink() {
|
|
107
|
+
return onError(({ graphQLErrors, networkError, operation }) => {
|
|
108
|
+
const { result, response } = operation.getContext();
|
|
109
|
+
if (graphQLErrors) {
|
|
110
|
+
graphQLErrors.map((graphqlError) =>
|
|
111
|
+
console.error(`[GraphQL error]: ${graphqlError.message}`)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (networkError) {
|
|
116
|
+
console.log(`[Network Error]: ${networkError}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (response && response.status >= 400) {
|
|
120
|
+
console.log(`[Network Error] ${response.bodyText}`);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { GraphQLDataSource } from "./GraphQLDataSource";
|
|
2
|
+
import { DefaultEngineConfig } from "../config";
|
|
3
|
+
import { SCHEMA_TAGS_AND_FIELD_STATS } from "./operations/schemaTagsAndFieldStats";
|
|
4
|
+
import {
|
|
5
|
+
FrontendUrlRootQuery,
|
|
6
|
+
SchemaTagsAndFieldStatsQuery,
|
|
7
|
+
} from "../graphqlTypes";
|
|
8
|
+
import { FRONTEND_URL_ROOT } from "./operations/frontendUrlRoot";
|
|
9
|
+
|
|
10
|
+
export interface ClientIdentity {
|
|
11
|
+
name?: string;
|
|
12
|
+
version?: string;
|
|
13
|
+
referenceID?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ServiceID = string;
|
|
17
|
+
export type ClientID = string;
|
|
18
|
+
export type SchemaTag = string;
|
|
19
|
+
export type ServiceIDAndTag = [ServiceID, SchemaTag?];
|
|
20
|
+
export type ServiceSpecifier = string;
|
|
21
|
+
// Map from parent type name to field name to latency in ms.
|
|
22
|
+
export type FieldLatenciesMS = Map<string, Map<string, number | null>>;
|
|
23
|
+
|
|
24
|
+
export function noServiceError(service: string | undefined, endpoint?: string) {
|
|
25
|
+
return `Could not find graph ${
|
|
26
|
+
service ? service : ""
|
|
27
|
+
} from Apollo at ${endpoint}. Please check your API key and graph ID`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ApolloEngineClient extends GraphQLDataSource {
|
|
31
|
+
constructor(
|
|
32
|
+
private engineKey: string,
|
|
33
|
+
engineEndpoint: string = DefaultEngineConfig.endpoint,
|
|
34
|
+
private clientIdentity?: ClientIdentity
|
|
35
|
+
) {
|
|
36
|
+
super();
|
|
37
|
+
this.baseURL = engineEndpoint;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// XXX fix typings on base package
|
|
41
|
+
willSendRequest(request: any) {
|
|
42
|
+
if (!request.headers) request.headers = {};
|
|
43
|
+
request.headers["x-api-key"] = this.engineKey;
|
|
44
|
+
if (this.clientIdentity && this.clientIdentity.name) {
|
|
45
|
+
request.headers["apollo-client-name"] = this.clientIdentity.name;
|
|
46
|
+
request.headers["apollo-client-reference-id"] =
|
|
47
|
+
this.clientIdentity.referenceID;
|
|
48
|
+
request.headers["apollo-client-version"] = this.clientIdentity.version;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// default values
|
|
53
|
+
request.headers["apollo-client-name"] = "Apollo Language Server";
|
|
54
|
+
request.headers["apollo-client-reference-id"] =
|
|
55
|
+
"146d29c0-912c-46d3-b686-920e52586be6";
|
|
56
|
+
request.headers["apollo-client-version"] =
|
|
57
|
+
require("../../package.json").version;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async loadSchemaTagsAndFieldLatencies(serviceID: string) {
|
|
61
|
+
const { data, errors } = await this.execute<SchemaTagsAndFieldStatsQuery>({
|
|
62
|
+
query: SCHEMA_TAGS_AND_FIELD_STATS,
|
|
63
|
+
variables: {
|
|
64
|
+
id: serviceID,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!(data && data.service && data.service.schemaTags) || errors) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
errors
|
|
71
|
+
? errors.map((error) => error.message).join("\n")
|
|
72
|
+
: "No service returned. Make sure your service name and API key match"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const schemaTags: string[] = data.service.schemaTags.map(
|
|
77
|
+
({ tag }: { tag: string }) => tag
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const fieldLatenciesMS: FieldLatenciesMS = new Map();
|
|
81
|
+
|
|
82
|
+
data.service.stats.fieldLatencies.forEach((fieldLatency) => {
|
|
83
|
+
const { parentType, fieldName } = fieldLatency.groupBy;
|
|
84
|
+
|
|
85
|
+
if (!parentType || !fieldName) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const fieldsMap =
|
|
89
|
+
fieldLatenciesMS.get(parentType) ||
|
|
90
|
+
fieldLatenciesMS.set(parentType, new Map()).get(parentType)!;
|
|
91
|
+
|
|
92
|
+
fieldsMap.set(fieldName, fieldLatency.metrics.fieldHistogram.durationMs);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return { schemaTags, fieldLatenciesMS };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async loadFrontendUrlRoot() {
|
|
99
|
+
const { data } = await this.execute<FrontendUrlRootQuery>({
|
|
100
|
+
query: FRONTEND_URL_ROOT,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return data?.frontendUrlRoot;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import gql from "graphql-tag";
|
|
2
|
+
|
|
3
|
+
export const SCHEMA_TAGS_AND_FIELD_STATS = gql`
|
|
4
|
+
query SchemaTagsAndFieldStats($id: ID!) {
|
|
5
|
+
service(id: $id) {
|
|
6
|
+
schemaTags {
|
|
7
|
+
tag
|
|
8
|
+
}
|
|
9
|
+
stats(from: "-86400", to: "-0") {
|
|
10
|
+
fieldLatencies {
|
|
11
|
+
groupBy {
|
|
12
|
+
parentType
|
|
13
|
+
fieldName
|
|
14
|
+
}
|
|
15
|
+
metrics {
|
|
16
|
+
fieldHistogram {
|
|
17
|
+
durationMs(percentile: 0.95)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
`;
|