From ba24262436b13ef617e21464bc305aa39432d4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Oddsson?= Date: Thu, 9 Nov 2017 10:51:41 +0000 Subject: [PATCH 1/4] feat: add a check for deprecation errors --- src/index.js | 11 +++++++++++ test/makeRule.js | 22 ++++++++++++++++++++++ test/schema.graphql | 1 + 3 files changed, 34 insertions(+) diff --git a/src/index.js b/src/index.js index b56d60c..44f1293 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,7 @@ import { buildClientSchema, buildSchema, specifiedRules as allGraphQLValidators, + findDeprecatedUsages } from 'graphql'; import { @@ -372,6 +373,16 @@ function handleTemplateTag(node, context, schema, env, validators) { }); return; } + + const deprecationErrors = schema ? findDeprecatedUsages(schema, ast) : []; + if (deprecationErrors && deprecationErrors.length > 0) { + context.report({ + node, + message: deprecationErrors[0].message, + loc: locFrom(node, deprecationErrors[0]), + }); + return; + } } function locFrom(node, error) { diff --git a/test/makeRule.js b/test/makeRule.js index 3ff5648..0f9d84d 100644 --- a/test/makeRule.js +++ b/test/makeRule.js @@ -526,6 +526,28 @@ const parserOptions = { column: 19 }] }, + { + options, + parser, + code: ` + @relay({ + fragments: { + greetings: () => Relay.QL\` + fragment on Greetings { + hi, + } + \`, + } + }) + class HelloApp extends React.Component {} + `, + errors: [{ + message: "The field Greetings.hi is deprecated. Please use the more formal greeting 'hello'", + type: 'TaggedTemplateExpression', + line: 6, + column: 19 + }] + }, // Example from issue report: // https://github.com/apollostack/eslint-plugin-graphql/issues/12#issuecomment-215445880 diff --git a/test/schema.graphql b/test/schema.graphql index 5082b44..d311e05 100644 --- a/test/schema.graphql +++ b/test/schema.graphql @@ -45,6 +45,7 @@ type Film { type Greetings { id: ID hello: String + hi: String @deprecated(reason: "Please use the more formal greeting 'hello'") } type Story { From c6af804230f84fc3337753172c2c6b11d9a05075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Oddsson?= Date: Fri, 10 Nov 2017 11:44:46 +0000 Subject: [PATCH 2/4] move `no-deprecated-fields` into own rule --- src/index.js | 31 +++++++++++-------- src/rules.js | 33 +++++++++++++++++++++ test/makeRule.js | 77 +++++++++++++++++++++++++++++++++--------------- 3 files changed, 106 insertions(+), 35 deletions(-) diff --git a/src/index.js b/src/index.js index 44f1293..3c853f2 100644 --- a/src/index.js +++ b/src/index.js @@ -4,8 +4,7 @@ import { validate, buildClientSchema, buildSchema, - specifiedRules as allGraphQLValidators, - findDeprecatedUsages + specifiedRules as allGraphQLValidators } from 'graphql'; import { @@ -223,6 +222,24 @@ export const rules = { })); }, }, + 'no-deprecated-fields': { + meta: { + schema: { + type: 'array', + items: { + additionalProperties: false, + properties: { ...defaultRuleProperties }, + ...schemaPropsExclusiveness, + }, + }, + }, + create: (context) => { + return createRule(context, (optionGroup) => parseOptions({ + validators: ['noDeprecatedFields'], + ...optionGroup, + })); + }, + }, }; function parseOptions(optionGroup) { @@ -373,16 +390,6 @@ function handleTemplateTag(node, context, schema, env, validators) { }); return; } - - const deprecationErrors = schema ? findDeprecatedUsages(schema, ast) : []; - if (deprecationErrors && deprecationErrors.length > 0) { - context.report({ - node, - message: deprecationErrors[0].message, - loc: locFrom(node, deprecationErrors[0]), - }); - return; - } } function locFrom(node, error) { diff --git a/src/rules.js b/src/rules.js index 3c77fa3..784584d 100644 --- a/src/rules.js +++ b/src/rules.js @@ -58,3 +58,36 @@ export function typeNamesShouldBeCapitalized(context) { } } } + +export function noDeprecatedFields(context) { + return { + Field(node) { + const fieldDef = context.getFieldDef(); + if (fieldDef && fieldDef.isDeprecated) { + const parentType = context.getParentType(); + if (parentType) { + const reason = fieldDef.deprecationReason; + context.reportError(new GraphQLError( + `The field ${parentType.name}.${fieldDef.name} is deprecated.` + + (reason ? ' ' + reason : ''), + [ node ] + )); + } + } + }, + EnumValue(node) { + const enumVal = context.getEnumValue(); + if (enumVal && enumVal.isDeprecated) { + const type = getNamedType(context.getInputType()); + if (type) { + const reason = enumVal.deprecationReason; + errors.push(new GraphQLError( + `The enum value ${type.name}.${enumVal.name} is deprecated.` + + (reason ? ' ' + reason : ''), + [ node ] + )); + } + } + } + } +} diff --git a/test/makeRule.js b/test/makeRule.js index 0f9d84d..14cb687 100644 --- a/test/makeRule.js +++ b/test/makeRule.js @@ -427,6 +427,7 @@ const parserOptions = { greetings: () => Relay.QL\` fragment on Greetings { hello, + hi, } \`, } @@ -526,29 +527,6 @@ const parserOptions = { column: 19 }] }, - { - options, - parser, - code: ` - @relay({ - fragments: { - greetings: () => Relay.QL\` - fragment on Greetings { - hi, - } - \`, - } - }) - class HelloApp extends React.Component {} - `, - errors: [{ - message: "The field Greetings.hi is deprecated. Please use the more formal greeting 'hello'", - type: 'TaggedTemplateExpression', - line: 6, - column: 19 - }] - }, - // Example from issue report: // https://github.com/apollostack/eslint-plugin-graphql/issues/12#issuecomment-215445880 { @@ -936,6 +914,48 @@ const typeNameCapValidatorCases = { }, ] }; + +const noDeprecatedFieldsCases = { + pass: [ + ` + @relay({ + fragments: { + greetings: () => Relay.QL\` + fragment on Greetings { + hello, + } + \`, + } + }) + class HelloApp extends React.Component {} + ` + ], + fail: [ + { + options, + parser: 'babel-eslint', + code: ` + @relay({ + fragments: { + greetings: () => Relay.QL\` + fragment on Greetings { + hi, + } + \`, + } + }) + class HelloApp extends React.Component {} + `, + errors: [{ + message: "The field Greetings.hi is deprecated. Please use the more formal greeting 'hello'", + type: 'TaggedTemplateExpression', + line: 6, + column: 17 + }] + } + ] +}; + { let options = [{ schemaJson, tagName: 'gql', @@ -1022,3 +1042,14 @@ ruleTester.run('testing capitalized-type-name rule', rules['capitalized-type-nam valid: typeNameCapValidatorCases.pass.map((code) => ({options, parserOptions, code})), invalid: typeNameCapValidatorCases.fail.map(({code, errors}) => ({options, parserOptions, code, errors})), }); + +options = [ + { + schemaJson, + env: 'relay', + }, +]; +ruleTester.run('testing no-deprecated-fields rule', rules['no-deprecated-fields'], { + valid: noDeprecatedFieldsCases.pass.map((code) => ({options, parser: 'babel-eslint', code})), + invalid: noDeprecatedFieldsCases.fail.map(({code, errors}) => ({options, parser: 'babel-eslint', code, errors})), +}); From aef2b96e621feea90728e4a6d0440a7e3ecf98eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Oddsson?= Date: Fri, 10 Nov 2017 11:48:00 +0000 Subject: [PATCH 3/4] add entry to change log for `no-deprecated-fields` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08fa59b..a5e88b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Change log ### vNEXT +- Add new rule `no-deprecated-fields` in [Kristján Oddsson](https://github.com/koddsson/)[#92](https://github.com/apollographql/eslint-plugin-graphql/pull/93) ### v1.4.1 Skipped v1.4.0 because of incorrect version tag in `package.json` From 80bd0d42fe982f759dc8720384adceb4fa872320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Oddsson?= Date: Fri, 10 Nov 2017 11:53:45 +0000 Subject: [PATCH 4/4] add info on `no-depreacted-fields` to README --- README.md | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ff1b48..b1d4d4e 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ If you want to lint your GraphQL schema, rather than queries, check out [cjoudre ### Importing schema JSON -You'll need to import your [introspection query result](https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js) or the schema as a string in the Schema Language format. This can be done if you define your ESLint config in a JS file. +You'll need to import your [introspection query result](https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js) or the schema as a string in the Schema Language format. This can be done if you define your ESLint config in a JS file. ### Retrieving a remote GraphQL schema @@ -502,3 +502,47 @@ module.exports = { ] } ``` + +### No Deprecated Fields Validation Rule + +The No Deprecated Fields rule validates that no deprecated fields are part of the query. This is useful to discover fields that have been marked as deprecated and shouldn't be used. + +**Fail** +``` +// 'id' requested and marked as deprecated in the schema + +schema { + query { + viewer { + id: Int @deprecated(reason: "Use the 'uuid' field instead") + uuid: String + } + } +} + +query ViewerName { + viewer { + id + } +} +``` + +The rule is defined as `graphql/no-deprecated-fields`. + +```js +// In a file called .eslintrc.js +module.exports = { + rules: { + 'graphql/no-deprecated-fields': [ + 'error', + { + env: 'relay', + schemaJson: require('./schema.json') + }, + ], + }, + plugins: [ + 'graphql' + ] +} +``` \ No newline at end of file