1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.NoMissingClientDirectives = exports.NoTypenameAlias = exports.NoAnonymousQueries = exports.validateQueryDocument = exports.getValidationErrors = exports.defaultValidationRules = void 0;
|
4 | const graphql_1 = require("graphql");
|
5 | const vscode_languageserver_1 = require("vscode-languageserver");
|
6 | const logger_1 = require("./logger");
|
7 | const source_1 = require("../utilities/source");
|
8 | const execute_1 = require("graphql/execution/execute");
|
9 | const graphql_2 = require("../utilities/graphql");
|
10 | const utilities_1 = require("../utilities");
|
11 | const specifiedRulesToBeRemoved = [graphql_1.NoUnusedFragmentsRule];
|
12 | exports.defaultValidationRules = [
|
13 | NoAnonymousQueries,
|
14 | NoTypenameAlias,
|
15 | NoMissingClientDirectives,
|
16 | ...graphql_1.specifiedRules.filter((rule) => !specifiedRulesToBeRemoved.includes(rule)),
|
17 | ];
|
18 | function getValidationErrors(schema, document, fragments, rules = exports.defaultValidationRules) {
|
19 | const typeInfo = new graphql_1.TypeInfo(schema);
|
20 | const errors = [];
|
21 | const onError = (err) => errors.push(err);
|
22 | const context = new graphql_1.ValidationContext(schema, document, typeInfo, onError);
|
23 | if (fragments) {
|
24 | context._fragments = fragments;
|
25 | }
|
26 | const visitors = rules.map((rule) => rule(context));
|
27 | (0, graphql_1.visit)(document, (0, graphql_1.visitWithTypeInfo)(typeInfo, (0, graphql_1.visitInParallel)(visitors)));
|
28 | if (typeof context.getErrors === "function")
|
29 | return context.getErrors();
|
30 | return errors;
|
31 | }
|
32 | exports.getValidationErrors = getValidationErrors;
|
33 | function validateQueryDocument(schema, document) {
|
34 | try {
|
35 | const validationErrors = getValidationErrors(schema, document);
|
36 | if (validationErrors && validationErrors.length > 0) {
|
37 | for (const error of validationErrors) {
|
38 | (0, logger_1.logError)(error);
|
39 | }
|
40 | return utilities_1.Debug.error("Validation of GraphQL query document failed");
|
41 | }
|
42 | }
|
43 | catch (e) {
|
44 | console.error(e);
|
45 | throw e;
|
46 | }
|
47 | }
|
48 | exports.validateQueryDocument = validateQueryDocument;
|
49 | function NoAnonymousQueries(context) {
|
50 | return {
|
51 | OperationDefinition(node) {
|
52 | if (!node.name) {
|
53 | context.reportError(new graphql_1.GraphQLError("Apollo does not support anonymous operations", [
|
54 | node,
|
55 | ]));
|
56 | }
|
57 | return false;
|
58 | },
|
59 | };
|
60 | }
|
61 | exports.NoAnonymousQueries = NoAnonymousQueries;
|
62 | function NoTypenameAlias(context) {
|
63 | return {
|
64 | Field(node) {
|
65 | const aliasName = node.alias && node.alias.value;
|
66 | if (aliasName == "__typename") {
|
67 | context.reportError(new graphql_1.GraphQLError("Apollo needs to be able to insert __typename when needed, please do not use it as an alias", [node]));
|
68 | }
|
69 | },
|
70 | };
|
71 | }
|
72 | exports.NoTypenameAlias = NoTypenameAlias;
|
73 | function hasClientSchema(schema) {
|
74 | const query = schema.getQueryType();
|
75 | const mutation = schema.getMutationType();
|
76 | const subscription = schema.getSubscriptionType();
|
77 | return Boolean((query && query.clientSchema) ||
|
78 | (mutation && mutation.clientSchema) ||
|
79 | (subscription && subscription.clientSchema));
|
80 | }
|
81 | function NoMissingClientDirectives(context) {
|
82 | const root = context.getDocument();
|
83 | const schema = context.getSchema();
|
84 | if (!hasClientSchema(schema))
|
85 | return {};
|
86 | const executionContext = (0, execute_1.buildExecutionContext)(schema, root, Object.create(null), Object.create(null), undefined, undefined, undefined);
|
87 | function visitor(node) {
|
88 | const parentType = node.kind === graphql_1.Kind.FRAGMENT_DEFINITION
|
89 | ? schema.getType(node.typeCondition.name.value)
|
90 | : context.getParentType();
|
91 | const fieldDef = context.getFieldDef();
|
92 | if (!parentType)
|
93 | return;
|
94 | const clientFields = parentType &&
|
95 | (0, graphql_1.isObjectType)(parentType) &&
|
96 | parentType.clientSchema &&
|
97 | parentType.clientSchema.localFields;
|
98 | let clientDirectivePresent = (0, graphql_2.hasClientDirective)(node);
|
99 | let message = "@client directive is missing on ";
|
100 | let selectsClientFieldSet = false;
|
101 | switch (node.kind) {
|
102 | case graphql_1.Kind.FIELD:
|
103 | selectsClientFieldSet = Boolean(clientFields && clientFields.includes(fieldDef.name));
|
104 | message += `local field "${node.name.value}"`;
|
105 | break;
|
106 | case graphql_1.Kind.INLINE_FRAGMENT:
|
107 | case graphql_1.Kind.FRAGMENT_DEFINITION:
|
108 | if (Array.isArray(executionContext))
|
109 | break;
|
110 | const fields = (0, graphql_2.simpleCollectFields)(executionContext, node.selectionSet, Object.create(null), Object.create(null));
|
111 | const fieldNames = Object.entries(fields).map(([name]) => name);
|
112 | selectsClientFieldSet = fieldNames.every((field) => clientFields && clientFields.includes(field));
|
113 | message += `fragment ${"name" in node ? `"${node.name.value}" ` : ""}around local fields "${fieldNames.join(",")}"`;
|
114 | break;
|
115 | }
|
116 | if (selectsClientFieldSet && !clientDirectivePresent) {
|
117 | let extensions = null;
|
118 | const name = "name" in node && node.name;
|
119 | if (name && name.loc) {
|
120 | let { source, end: locToInsertDirective } = name.loc;
|
121 | if ("arguments" in node &&
|
122 | node.arguments &&
|
123 | node.arguments.length !== 0) {
|
124 | const endOfArgs = source.body.indexOf(")", locToInsertDirective);
|
125 | locToInsertDirective = endOfArgs + 1;
|
126 | }
|
127 | const codeAction = {
|
128 | message: `Add @client directive to "${name.value}"`,
|
129 | edits: [
|
130 | vscode_languageserver_1.TextEdit.insert((0, source_1.positionFromSourceLocation)(source, (0, graphql_1.getLocation)(source, locToInsertDirective)), " @client"),
|
131 | ],
|
132 | };
|
133 | extensions = { codeAction };
|
134 | }
|
135 | context.reportError(new graphql_1.GraphQLError(message, [node], null, null, null, null, extensions));
|
136 | }
|
137 | if (selectsClientFieldSet) {
|
138 | return false;
|
139 | }
|
140 | return;
|
141 | }
|
142 | return {
|
143 | InlineFragment: visitor,
|
144 | FragmentDefinition: visitor,
|
145 | Field: visitor,
|
146 | };
|
147 | }
|
148 | exports.NoMissingClientDirectives = NoMissingClientDirectives;
|
149 |
|
\ | No newline at end of file |