UNPKG

apollo-language-server

Version:

A language server for Apollo GraphQL projects

149 lines 6.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NoMissingClientDirectives = exports.NoTypenameAlias = exports.NoAnonymousQueries = exports.validateQueryDocument = exports.getValidationErrors = exports.defaultValidationRules = void 0; const graphql_1 = require("graphql"); const vscode_languageserver_1 = require("vscode-languageserver"); const logger_1 = require("./logger"); const source_1 = require("../utilities/source"); const execute_1 = require("graphql/execution/execute"); const graphql_2 = require("../utilities/graphql"); const utilities_1 = require("../utilities"); const specifiedRulesToBeRemoved = [graphql_1.NoUnusedFragmentsRule]; exports.defaultValidationRules = [ NoAnonymousQueries, NoTypenameAlias, NoMissingClientDirectives, ...graphql_1.specifiedRules.filter((rule) => !specifiedRulesToBeRemoved.includes(rule)), ]; function getValidationErrors(schema, document, fragments, rules = exports.defaultValidationRules) { const typeInfo = new graphql_1.TypeInfo(schema); const errors = []; const onError = (err) => errors.push(err); const context = new graphql_1.ValidationContext(schema, document, typeInfo, onError); if (fragments) { context._fragments = fragments; } const visitors = rules.map((rule) => rule(context)); (0, graphql_1.visit)(document, (0, graphql_1.visitWithTypeInfo)(typeInfo, (0, graphql_1.visitInParallel)(visitors))); if (typeof context.getErrors === "function") return context.getErrors(); return errors; } exports.getValidationErrors = getValidationErrors; function validateQueryDocument(schema, document) { try { const validationErrors = getValidationErrors(schema, document); if (validationErrors && validationErrors.length > 0) { for (const error of validationErrors) { (0, logger_1.logError)(error); } return utilities_1.Debug.error("Validation of GraphQL query document failed"); } } catch (e) { console.error(e); throw e; } } exports.validateQueryDocument = validateQueryDocument; function NoAnonymousQueries(context) { return { OperationDefinition(node) { if (!node.name) { context.reportError(new graphql_1.GraphQLError("Apollo does not support anonymous operations", [ node, ])); } return false; }, }; } exports.NoAnonymousQueries = NoAnonymousQueries; function NoTypenameAlias(context) { return { Field(node) { const aliasName = node.alias && node.alias.value; if (aliasName == "__typename") { 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])); } }, }; } exports.NoTypenameAlias = NoTypenameAlias; function hasClientSchema(schema) { const query = schema.getQueryType(); const mutation = schema.getMutationType(); const subscription = schema.getSubscriptionType(); return Boolean((query && query.clientSchema) || (mutation && mutation.clientSchema) || (subscription && subscription.clientSchema)); } function NoMissingClientDirectives(context) { const root = context.getDocument(); const schema = context.getSchema(); if (!hasClientSchema(schema)) return {}; const executionContext = (0, execute_1.buildExecutionContext)(schema, root, Object.create(null), Object.create(null), undefined, undefined, undefined); function visitor(node) { const parentType = node.kind === graphql_1.Kind.FRAGMENT_DEFINITION ? schema.getType(node.typeCondition.name.value) : context.getParentType(); const fieldDef = context.getFieldDef(); if (!parentType) return; const clientFields = parentType && (0, graphql_1.isObjectType)(parentType) && parentType.clientSchema && parentType.clientSchema.localFields; let clientDirectivePresent = (0, graphql_2.hasClientDirective)(node); let message = "@client directive is missing on "; let selectsClientFieldSet = false; switch (node.kind) { case graphql_1.Kind.FIELD: selectsClientFieldSet = Boolean(clientFields && clientFields.includes(fieldDef.name)); message += `local field "${node.name.value}"`; break; case graphql_1.Kind.INLINE_FRAGMENT: case graphql_1.Kind.FRAGMENT_DEFINITION: if (Array.isArray(executionContext)) break; const fields = (0, graphql_2.simpleCollectFields)(executionContext, node.selectionSet, Object.create(null), Object.create(null)); const fieldNames = Object.entries(fields).map(([name]) => name); selectsClientFieldSet = fieldNames.every((field) => clientFields && clientFields.includes(field)); message += `fragment ${"name" in node ? `"${node.name.value}" ` : ""}around local fields "${fieldNames.join(",")}"`; break; } if (selectsClientFieldSet && !clientDirectivePresent) { let extensions = null; const name = "name" in node && node.name; if (name && name.loc) { let { source, end: locToInsertDirective } = name.loc; if ("arguments" in node && node.arguments && node.arguments.length !== 0) { const endOfArgs = source.body.indexOf(")", locToInsertDirective); locToInsertDirective = endOfArgs + 1; } const codeAction = { message: `Add @client directive to "${name.value}"`, edits: [ vscode_languageserver_1.TextEdit.insert((0, source_1.positionFromSourceLocation)(source, (0, graphql_1.getLocation)(source, locToInsertDirective)), " @client"), ], }; extensions = { codeAction }; } context.reportError(new graphql_1.GraphQLError(message, [node], null, null, null, null, extensions)); } if (selectsClientFieldSet) { return false; } return; } return { InlineFragment: visitor, FragmentDefinition: visitor, Field: visitor, }; } exports.NoMissingClientDirectives = NoMissingClientDirectives; //# sourceMappingURL=validation.js.map