Skip to content

Commit a04d457

Browse files
Kristján Oddssonjnwng
authored andcommitted
feat: add a check for deprecation errors (#93)
* feat: add a check for deprecation errors * move `no-deprecated-fields` into own rule * add entry to change log for `no-deprecated-fields` * add info on `no-depreacted-fields` to README
1 parent d3de0db commit a04d457

File tree

6 files changed

+153
-3
lines changed

6 files changed

+153
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Change log
22
### vNEXT
3+
- Add new rule `no-deprecated-fields` in [Kristján Oddsson](https://github.com/koddsson/)[#92](https://github.com/apollographql/eslint-plugin-graphql/pull/93)
34

45
### v1.4.1
56
Skipped v1.4.0 because of incorrect version tag in `package.json`

README.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ If you want to lint your GraphQL schema, rather than queries, check out [cjoudre
2121

2222
### Importing schema JSON
2323

24-
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.
24+
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.
2525

2626
### Retrieving a remote GraphQL schema
2727

@@ -502,3 +502,47 @@ module.exports = {
502502
]
503503
}
504504
```
505+
506+
### No Deprecated Fields Validation Rule
507+
508+
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.
509+
510+
**Fail**
511+
```
512+
// 'id' requested and marked as deprecated in the schema
513+
514+
schema {
515+
query {
516+
viewer {
517+
id: Int @deprecated(reason: "Use the 'uuid' field instead")
518+
uuid: String
519+
}
520+
}
521+
}
522+
523+
query ViewerName {
524+
viewer {
525+
id
526+
}
527+
}
528+
```
529+
530+
The rule is defined as `graphql/no-deprecated-fields`.
531+
532+
```js
533+
// In a file called .eslintrc.js
534+
module.exports = {
535+
rules: {
536+
'graphql/no-deprecated-fields': [
537+
'error',
538+
{
539+
env: 'relay',
540+
schemaJson: require('./schema.json')
541+
},
542+
],
543+
},
544+
plugins: [
545+
'graphql'
546+
]
547+
}
548+
```

src/index.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
validate,
55
buildClientSchema,
66
buildSchema,
7-
specifiedRules as allGraphQLValidators,
7+
specifiedRules as allGraphQLValidators
88
} from 'graphql';
99

1010
import {
@@ -222,6 +222,24 @@ export const rules = {
222222
}));
223223
},
224224
},
225+
'no-deprecated-fields': {
226+
meta: {
227+
schema: {
228+
type: 'array',
229+
items: {
230+
additionalProperties: false,
231+
properties: { ...defaultRuleProperties },
232+
...schemaPropsExclusiveness,
233+
},
234+
},
235+
},
236+
create: (context) => {
237+
return createRule(context, (optionGroup) => parseOptions({
238+
validators: ['noDeprecatedFields'],
239+
...optionGroup,
240+
}));
241+
},
242+
},
225243
};
226244

227245
function parseOptions(optionGroup) {

src/rules.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,36 @@ export function typeNamesShouldBeCapitalized(context) {
5858
}
5959
}
6060
}
61+
62+
export function noDeprecatedFields(context) {
63+
return {
64+
Field(node) {
65+
const fieldDef = context.getFieldDef();
66+
if (fieldDef && fieldDef.isDeprecated) {
67+
const parentType = context.getParentType();
68+
if (parentType) {
69+
const reason = fieldDef.deprecationReason;
70+
context.reportError(new GraphQLError(
71+
`The field ${parentType.name}.${fieldDef.name} is deprecated.` +
72+
(reason ? ' ' + reason : ''),
73+
[ node ]
74+
));
75+
}
76+
}
77+
},
78+
EnumValue(node) {
79+
const enumVal = context.getEnumValue();
80+
if (enumVal && enumVal.isDeprecated) {
81+
const type = getNamedType(context.getInputType());
82+
if (type) {
83+
const reason = enumVal.deprecationReason;
84+
errors.push(new GraphQLError(
85+
`The enum value ${type.name}.${enumVal.name} is deprecated.` +
86+
(reason ? ' ' + reason : ''),
87+
[ node ]
88+
));
89+
}
90+
}
91+
}
92+
}
93+
}

test/makeRule.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ const parserOptions = {
427427
greetings: () => Relay.QL\`
428428
fragment on Greetings {
429429
hello,
430+
hi,
430431
}
431432
\`,
432433
}
@@ -526,7 +527,6 @@ const parserOptions = {
526527
column: 19
527528
}]
528529
},
529-
530530
// Example from issue report:
531531
// https://github.com/apollostack/eslint-plugin-graphql/issues/12#issuecomment-215445880
532532
{
@@ -914,6 +914,48 @@ const typeNameCapValidatorCases = {
914914
},
915915
]
916916
};
917+
918+
const noDeprecatedFieldsCases = {
919+
pass: [
920+
`
921+
@relay({
922+
fragments: {
923+
greetings: () => Relay.QL\`
924+
fragment on Greetings {
925+
hello,
926+
}
927+
\`,
928+
}
929+
})
930+
class HelloApp extends React.Component {}
931+
`
932+
],
933+
fail: [
934+
{
935+
options,
936+
parser: 'babel-eslint',
937+
code: `
938+
@relay({
939+
fragments: {
940+
greetings: () => Relay.QL\`
941+
fragment on Greetings {
942+
hi,
943+
}
944+
\`,
945+
}
946+
})
947+
class HelloApp extends React.Component {}
948+
`,
949+
errors: [{
950+
message: "The field Greetings.hi is deprecated. Please use the more formal greeting 'hello'",
951+
type: 'TaggedTemplateExpression',
952+
line: 6,
953+
column: 17
954+
}]
955+
}
956+
]
957+
};
958+
917959
{
918960
let options = [{
919961
schemaJson, tagName: 'gql',
@@ -1000,3 +1042,14 @@ ruleTester.run('testing capitalized-type-name rule', rules['capitalized-type-nam
10001042
valid: typeNameCapValidatorCases.pass.map((code) => ({options, parserOptions, code})),
10011043
invalid: typeNameCapValidatorCases.fail.map(({code, errors}) => ({options, parserOptions, code, errors})),
10021044
});
1045+
1046+
options = [
1047+
{
1048+
schemaJson,
1049+
env: 'relay',
1050+
},
1051+
];
1052+
ruleTester.run('testing no-deprecated-fields rule', rules['no-deprecated-fields'], {
1053+
valid: noDeprecatedFieldsCases.pass.map((code) => ({options, parser: 'babel-eslint', code})),
1054+
invalid: noDeprecatedFieldsCases.fail.map(({code, errors}) => ({options, parser: 'babel-eslint', code, errors})),
1055+
});

test/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type Film {
4545
type Greetings {
4646
id: ID
4747
hello: String
48+
hi: String @deprecated(reason: "Please use the more formal greeting 'hello'")
4849
}
4950

5051
type Story {

0 commit comments

Comments
 (0)