UNPKG

eslint-plugin-graphql

Version:
388 lines (346 loc) 11.1 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.processors = exports.rules = void 0; var _fs = _interopRequireDefault(require("fs")); var _path = _interopRequireDefault(require("path")); var _graphql = require("graphql"); var _lodash = _interopRequireDefault(require("lodash.flatten")); var _lodash2 = _interopRequireDefault(require("lodash.without")); var _graphqlConfig = require("graphql-config"); var customRules = _interopRequireWildcard(require("./customGraphQLValidationRules")); var _constants = require("./constants"); var _createRule = require("./createRule"); const allGraphQLValidatorNames = _graphql.specifiedRules.map(rule => rule.name); // Map of env name to list of rule names. const envGraphQLValidatorNames = { apollo: (0, _lodash2.default)(allGraphQLValidatorNames, "KnownFragmentNames", "NoUnusedFragments", // `graphql`@15 "KnownFragmentNamesRule", "NoUnusedFragmentsRule"), lokka: (0, _lodash2.default)(allGraphQLValidatorNames, "KnownFragmentNames", "NoUnusedFragments", // `graphql`@15 "KnownFragmentNamesRule", "NoUnusedFragmentsRule"), fraql: (0, _lodash2.default)(allGraphQLValidatorNames, "KnownFragmentNames", "NoUnusedFragments", // `graphql`@15 "KnownFragmentNamesRule", "NoUnusedFragmentsRule"), relay: (0, _lodash2.default)(allGraphQLValidatorNames, "KnownDirectives", "KnownFragmentNames", "NoUndefinedVariables", "NoUnusedFragments", // `graphql`@15 "KnownDirectivesRule", "KnownFragmentNamesRule", "NoUndefinedVariablesRule", "NoUnusedFragmentsRule", // `graphql` < 14 "ProvidedNonNullArguments", // `graphql`@14 "ProvidedRequiredArguments", "ScalarLeafs", // `graphql`@15 "ProvidedRequiredArgumentsRule", "ScalarLeafsRule"), literal: (0, _lodash2.default)(allGraphQLValidatorNames, "KnownFragmentNames", "NoUnusedFragments", // `graphql`@15 "KnownFragmentNamesRule", "NoUnusedFragmentsRule") }; const gqlFiles = ["gql", "graphql"]; const defaultRuleProperties = { env: { enum: ["lokka", "fraql", "relay", "apollo", "literal"] }, schemaJson: { type: "object" }, schemaJsonFilepath: { type: "string" }, schemaString: { type: "string" }, tagName: { type: "string", pattern: "^[$_a-zA-Z$_][a-zA-Z0-9$_]+(\\.[a-zA-Z0-9$_]+)?$" }, projectName: { type: "string" } }; // schemaJson, schemaJsonFilepath, schemaString and projectName are mutually exclusive: const schemaPropsExclusiveness = { oneOf: [{ required: ["schemaJson"], not: { required: ["schemaString", "schemaJsonFilepath", "projectName"] } }, { required: ["schemaJsonFilepath"], not: { required: ["schemaJson", "schemaString", "projectName"] } }, { required: ["schemaString"], not: { required: ["schemaJson", "schemaJsonFilepath", "projectName"] } }, { not: { anyOf: [{ required: ["schemaString"] }, { required: ["schemaJson"] }, { required: ["schemaJsonFilepath"] }] } }] }; const rules = { "template-strings": { meta: { schema: { type: "array", items: { additionalProperties: false, properties: { ...defaultRuleProperties, validators: { oneOf: [{ type: "array", uniqueItems: true, items: { enum: allGraphQLValidatorNames } }, { enum: ["all"] }] } }, ...schemaPropsExclusiveness } } }, create: context => (0, _createRule.createRule)(context, optionGroup => parseOptions(optionGroup, context)) }, "named-operations": { meta: { schema: { type: "array", items: { additionalProperties: false, properties: { ...defaultRuleProperties }, ...schemaPropsExclusiveness } } }, create: context => { return (0, _createRule.createRule)(context, optionGroup => parseOptions({ validators: ["OperationsMustHaveNames"], ...optionGroup }, context)); } }, "required-fields": { meta: { schema: { type: "array", minItems: 1, items: { additionalProperties: false, properties: { ...defaultRuleProperties, requiredFields: { type: "array", items: { type: "string" } } }, required: ["requiredFields"], ...schemaPropsExclusiveness } } }, create: context => { return (0, _createRule.createRule)(context, optionGroup => parseOptions({ validators: ["RequiredFields"], options: { requiredFields: optionGroup.requiredFields }, ...optionGroup }, context)); } }, "capitalized-type-name": { meta: { schema: { type: "array", items: { additionalProperties: false, properties: { ...defaultRuleProperties }, ...schemaPropsExclusiveness } } }, create: context => { return (0, _createRule.createRule)(context, optionGroup => parseOptions({ validators: ["typeNamesShouldBeCapitalized"], ...optionGroup }, context)); } }, "no-deprecated-fields": { meta: { schema: { type: "array", items: { additionalProperties: false, properties: { ...defaultRuleProperties }, ...schemaPropsExclusiveness } } }, create: context => { return (0, _createRule.createRule)(context, optionGroup => parseOptions({ validators: ["noDeprecatedFields"], ...optionGroup }, context)); } } }; exports.rules = rules; const schemaCache = {}; const projectCache = {}; function parseOptions(optionGroup, context) { const { schemaJson, // Schema via JSON object schemaJsonFilepath, // Or Schema via absolute filepath schemaString, // Or Schema as string, env, projectName, tagName: tagNameOption, validators: validatorNamesOption } = optionGroup; const cacheHit = schemaCache[JSON.stringify(optionGroup)]; if (cacheHit && env !== "literal") { return cacheHit; } // Validate and unpack schema let schema; if (schemaJson) { schema = initSchema(schemaJson); } else if (schemaJsonFilepath) { schema = initSchemaFromFile(schemaJsonFilepath); } else if (schemaString) { schema = initSchemaFromString(schemaString); } else { try { const config = (0, _graphqlConfig.loadConfigSync)({ rootDir: _path.default.resolve(process.cwd(), _path.default.dirname(context.getFilename())) }); let projectConfig; if (projectName) { projectConfig = config.getProject(projectName); if (!projectConfig) { throw new Error(`Project with name "${projectName}" not found in ${config.filepath}.`); } } else { try { projectConfig = config.getProjectForFile(context.getFilename()); } catch (e) { if (!(e instanceof _graphqlConfig.ProjectNotFoundError)) { throw e; } } } if (projectConfig) { const key = `${config.filepath}[${projectConfig.name}]`; schema = projectCache[key]; if (!schema) { schema = projectConfig.getSchemaSync(); projectCache[key] = schema; } } if (cacheHit) { return { ...cacheHit, schema }; } } catch (e) { if (e instanceof _graphqlConfig.ConfigNotFoundError) { throw new Error("Must provide GraphQL Config file or pass in `schemaJson` option " + "with schema object or `schemaJsonFilepath` with absolute path to the json file."); } throw e; } } // Validate env if (env && env !== "lokka" && env !== "fraql" && env !== "relay" && env !== "apollo" && env !== "literal") { throw new Error("Invalid option for env, only `apollo`, `lokka`, `fraql`, `relay`, and `literal` supported."); } // Validate tagName and set default let tagName; if (tagNameOption) { tagName = tagNameOption; } else if (env === "relay") { tagName = "Relay.QL"; } else if (env === "literal") { tagName = _constants.internalTag; } else { tagName = "gql"; } // The validator list may be: // The string 'all' to use all rules. // An array of rule names. // null/undefined to use the default rule set of the environment, or all rules. let validatorNames; if (validatorNamesOption === "all") { validatorNames = allGraphQLValidatorNames; } else if (validatorNamesOption) { validatorNames = validatorNamesOption; } else { validatorNames = envGraphQLValidatorNames[env] || allGraphQLValidatorNames; } const validators = validatorNames.map(name => { if (name in customRules) { return customRules[name]; } else { return require(`graphql/validation/rules/${name}`)[name]; } }); const results = { schema, env, tagName, validators }; schemaCache[JSON.stringify(optionGroup)] = results; return results; } function initSchema(json) { const unpackedSchemaJson = json.data ? json.data : json; if (!unpackedSchemaJson.__schema) { throw new Error("Please pass a valid GraphQL introspection query result."); } return (0, _graphql.buildClientSchema)(unpackedSchemaJson); } function initSchemaFromFile(jsonFile) { return initSchema(JSON.parse(_fs.default.readFileSync(jsonFile, "utf8"))); } function initSchemaFromString(source) { return (0, _graphql.buildSchema)(source); } const gqlProcessor = { preprocess: function (text) { // Wrap the text in backticks and prepend the internal tag. First the text // must be escaped, because of the three sequences that have special // meaning in JavaScript template literals, and could change the meaning of // the text or cause syntax errors. // https://tc39.github.io/ecma262/#prod-TemplateCharacter // // - "`" would end the template literal. // - "\" would start an escape sequence. // - "${" would start an interpolation. const escaped = text.replace(/[`\\]|\$\{/g, "\\$&"); return [`${_constants.internalTag}\`${escaped}\``]; }, postprocess: function (messages) { // only report graphql-errors return (0, _lodash.default)(messages).filter(message => Object.keys(rules).map(key => `graphql/${key}`).includes(message.ruleId)); } }; const processors = gqlFiles.reduce((result, value) => { return { ...result, [`.${value}`]: gqlProcessor }; }, {}); exports.processors = processors; var _default = { rules, processors }; exports.default = _default;