Skip to content

Commit

Permalink
Add NoDeprecatedCustomRule and deprecate findDeprecatedUsages
Browse files Browse the repository at this point in the history
  • Loading branch information
danielrearden committed Jun 6, 2020
1 parent d10cf6c commit f2a9252
Show file tree
Hide file tree
Showing 15 changed files with 226 additions and 121 deletions.
1 change: 1 addition & 0 deletions .nycrc.yml
Expand Up @@ -14,6 +14,7 @@ exclude:
- 'src/validation/rules/UniqueFieldDefinitionNames.js'
- 'src/validation/rules/UniqueTypeNames.js'
- 'src/validation/rules/UniqueOperationTypes.js'
- 'src/utilities/findDeprecatedUsages.js'
clean: true
temp-directory: 'coverage/tests'
report-dir: 'coverage/tests'
Expand Down
2 changes: 1 addition & 1 deletion src/index.d.ts
Expand Up @@ -414,7 +414,7 @@ export {
DangerousChangeType,
findBreakingChanges,
findDangerousChanges,
// Report all deprecated usage within a GraphQL document.
// @deprecated: Report all deprecated usage within a GraphQL document.
findDeprecatedUsages,
} from './utilities/index';

Expand Down
4 changes: 3 additions & 1 deletion src/index.js
Expand Up @@ -333,6 +333,8 @@ export {
UniqueFieldDefinitionNamesRule,
UniqueDirectiveNamesRule,
PossibleTypeExtensionsRule,
// Custom rules
NoDeprecatedCustomRule,
} from './validation/index';

export type { ValidationRule } from './validation/index';
Expand Down Expand Up @@ -414,7 +416,7 @@ export {
DangerousChangeType,
findBreakingChanges,
findDangerousChanges,
// Report all deprecated usage within a GraphQL document.
// @deprecated: Report all deprecated usage within a GraphQL document.
findDeprecatedUsages,
} from './utilities/index';

Expand Down
76 changes: 0 additions & 76 deletions src/utilities/__tests__/findDeprecatedUsages-test.js

This file was deleted.

10 changes: 9 additions & 1 deletion src/utilities/findDeprecatedUsages.d.ts
Expand Up @@ -6,8 +6,16 @@ import { GraphQLSchema } from '../type/schema';
* A validation rule which reports deprecated usages.
*
* Returns a list of GraphQLError instances describing each deprecated use.
*
* @deprecated Please use `validate` with `NoDeprecatedCustomRule` instead:
*
* ```
* import { validate, NoDeprecatedCustomRule } from 'graphql'
*
* const errors = validate(schema, document, [NoDeprecatedCustomRule])
* ```
*/
export function findDeprecatedUsages(
schema: GraphQLSchema,
ast: DocumentNode,
): Array<GraphQLError>;
): ReadonlyArray<GraphQLError>;
52 changes: 12 additions & 40 deletions src/utilities/findDeprecatedUsages.js
Expand Up @@ -2,57 +2,29 @@

import { GraphQLError } from '../error/GraphQLError';

import { visit } from '../language/visitor';
import { type DocumentNode } from '../language/ast';

import { getNamedType } from '../type/definition';
import { type GraphQLSchema } from '../type/schema';

import { TypeInfo, visitWithTypeInfo } from './TypeInfo';
import { validate } from '../validation/validate';
import { NoDeprecatedCustomRule } from '../validation/rules/custom/NoDeprecatedCustomRule';

/**
* A validation rule which reports deprecated usages.
*
* Returns a list of GraphQLError instances describing each deprecated use.
*
* @deprecated Please use `validate` with `NoDeprecatedCustomRule` instead:
*
* ```
* import { validate, NoDeprecatedCustomRule } from 'graphql'
*
* const errors = validate(schema, document, [NoDeprecatedCustomRule])
* ```
*/
export function findDeprecatedUsages(
schema: GraphQLSchema,
ast: DocumentNode,
): Array<GraphQLError> {
const errors = [];
const typeInfo = new TypeInfo(schema);

visit(
ast,
visitWithTypeInfo(typeInfo, {
Field(node) {
const parentType = typeInfo.getParentType();
const fieldDef = typeInfo.getFieldDef();
if (parentType && fieldDef?.deprecationReason != null) {
errors.push(
new GraphQLError(
`The field "${parentType.name}.${fieldDef.name}" is deprecated. ` +
fieldDef.deprecationReason,
node,
),
);
}
},
EnumValue(node) {
const type = getNamedType(typeInfo.getInputType());
const enumVal = typeInfo.getEnumValue();
if (type && enumVal?.deprecationReason != null) {
errors.push(
new GraphQLError(
`The enum value "${type.name}.${enumVal.name}" is deprecated. ` +
enumVal.deprecationReason,
node,
),
);
}
},
}),
);

return errors;
): $ReadOnlyArray<GraphQLError> {
return validate(schema, ast, [NoDeprecatedCustomRule]);
}
2 changes: 1 addition & 1 deletion src/utilities/index.d.ts
Expand Up @@ -112,5 +112,5 @@ export {
DangerousChange,
} from './findBreakingChanges';

// Report all deprecated usage within a GraphQL document.
// @deprecated: Report all deprecated usage within a GraphQL document.
export { findDeprecatedUsages } from './findDeprecatedUsages';
2 changes: 1 addition & 1 deletion src/utilities/index.js
Expand Up @@ -111,5 +111,5 @@ export {
} from './findBreakingChanges';
export type { BreakingChange, DangerousChange } from './findBreakingChanges';

// Report all deprecated usage within a GraphQL document.
// @deprecated: Report all deprecated usage within a GraphQL document.
export { findDeprecatedUsages } from './findDeprecatedUsages';
3 changes: 3 additions & 0 deletions src/validation/ValidationContext.d.ts
Expand Up @@ -18,6 +18,7 @@ import {
GraphQLCompositeType,
GraphQLField,
GraphQLArgument,
GraphQLEnumValue,
} from '../type/definition';
import { TypeInfo } from '../utilities/TypeInfo';

Expand Down Expand Up @@ -90,6 +91,8 @@ export class ValidationContext extends ASTValidationContext {
getDirective(): Maybe<GraphQLDirective>;

getArgument(): Maybe<GraphQLArgument>;

getEnumValue(): Maybe<GraphQLEnumValue>;
}

export type ValidationRule = (context: ValidationContext) => ASTVisitor;
5 changes: 5 additions & 0 deletions src/validation/ValidationContext.js
Expand Up @@ -23,6 +23,7 @@ import {
type GraphQLCompositeType,
type GraphQLField,
type GraphQLArgument,
type GraphQLEnumValue,
} from '../type/definition';

import { TypeInfo, visitWithTypeInfo } from '../utilities/TypeInfo';
Expand Down Expand Up @@ -243,6 +244,10 @@ export class ValidationContext extends ASTValidationContext {
getArgument(): ?GraphQLArgument {
return this._typeInfo.getArgument();
}

getEnumValue(): ?GraphQLEnumValue {
return this._typeInfo.getEnumValue();
}
}

export type ValidationRule = (ValidationContext) => ASTVisitor;
119 changes: 119 additions & 0 deletions src/validation/__tests__/NoDeprecatedCustomRule-test.js
@@ -0,0 +1,119 @@
// @flow strict

import { describe, it } from 'mocha';

import { buildSchema } from '../../utilities/buildASTSchema';

import { NoDeprecatedCustomRule } from '../rules/custom/NoDeprecatedCustomRule';

import { expectValidationErrorsWithSchema } from './harness';

function expectErrors(queryStr) {
return expectValidationErrorsWithSchema(
schema,
NoDeprecatedCustomRule,
queryStr,
);
}

function expectValid(queryStr) {
expectErrors(queryStr).to.deep.equal([]);
}

const schema = buildSchema(`
enum EnumType {
NORMAL_VALUE
DEPRECATED_VALUE @deprecated(reason: "Some enum reason.")
DEPRECATED_VALUE_WITH_NO_REASON @deprecated
}
type Query {
normalField(enumArg: [EnumType]): String
deprecatedField: String @deprecated(reason: "Some field reason.")
deprecatedFieldWithNoReason: String @deprecated
}
`);

describe('Validate: no deprecated', () => {
it('ignores fields and enum values that are not deprecated', () => {
expectValid(`
{
normalField(enumArg: [NORMAL_VALUE])
}
`);
});

it('ignores unknown fields and enum values', () => {
expectValid(`
fragment UnknownFragment on UnknownType {
unknownField(unknownArg: UNKNOWN_VALUE)
}
fragment QueryFragment on Query {
unknownField(unknownArg: UNKNOWN_VALUE)
normalField(enumArg: UNKNOWN_VALUE)
}
`);
});

it('reports error when a deprecated field is selected', () => {
expectErrors(`
{
normalField
deprecatedField
deprecatedFieldWithNoReason
}
`).to.deep.equal([
{
message:
'The field Query.deprecatedField is deprecated. Some field reason.',
locations: [{ line: 4, column: 9 }],
},
{
message:
'The field Query.deprecatedFieldWithNoReason is deprecated. No longer supported',
locations: [{ line: 5, column: 9 }],
},
]);
});

it('reports error when a deprecated enum value is used', () => {
expectErrors(`
{
normalField(enumArg: [NORMAL_VALUE, DEPRECATED_VALUE])
normalField(enumArg: [DEPRECATED_VALUE_WITH_NO_REASON])
}
`).to.deep.equal([
{
message:
'The enum value "EnumType.DEPRECATED_VALUE" is deprecated. Some enum reason.',
locations: [{ line: 3, column: 45 }],
},
{
message:
'The enum value "EnumType.DEPRECATED_VALUE_WITH_NO_REASON" is deprecated. No longer supported',
locations: [{ line: 4, column: 31 }],
},
]);
});

it('reports error when a deprecated field is selected or an enum value is used inside a fragment', () => {
expectErrors(`
fragment QueryFragment on Query {
deprecatedField
normalField(enumArg: [NORMAL_VALUE, DEPRECATED_VALUE])
}
`).to.deep.equal([
{
message:
'The field Query.deprecatedField is deprecated. Some field reason.',
locations: [{ line: 3, column: 9 }],
},
{
message:
'The enum value "EnumType.DEPRECATED_VALUE" is deprecated. Some enum reason.',
locations: [{ line: 4, column: 45 }],
},
]);
});
});
3 changes: 3 additions & 0 deletions src/validation/index.d.ts
Expand Up @@ -90,3 +90,6 @@ export { UniqueEnumValueNamesRule } from './rules/UniqueEnumValueNamesRule';
export { UniqueFieldDefinitionNamesRule } from './rules/UniqueFieldDefinitionNamesRule';
export { UniqueDirectiveNamesRule } from './rules/UniqueDirectiveNamesRule';
export { PossibleTypeExtensionsRule } from './rules/PossibleTypeExtensionsRule';

// Optional rules not defined by the GraphQL Specification
export { NoDeprecatedCustomRule } from './rules/custom/NoDeprecatedCustomRule';
3 changes: 3 additions & 0 deletions src/validation/index.js
Expand Up @@ -94,3 +94,6 @@ export { UniqueEnumValueNamesRule } from './rules/UniqueEnumValueNamesRule';
export { UniqueFieldDefinitionNamesRule } from './rules/UniqueFieldDefinitionNamesRule';
export { UniqueDirectiveNamesRule } from './rules/UniqueDirectiveNamesRule';
export { PossibleTypeExtensionsRule } from './rules/PossibleTypeExtensionsRule';

// Optional rules not defined by the GraphQL Specification
export { NoDeprecatedCustomRule } from './rules/custom/NoDeprecatedCustomRule';
14 changes: 14 additions & 0 deletions src/validation/rules/custom/NoDeprecatedCustomRule.d.ts
@@ -0,0 +1,14 @@
import { ASTVisitor } from '../../../language/visitor';
import { ValidationContext } from '../../ValidationContext';

/**
* No deprecated
*
* A GraphQL document is only valid if all selected fields and all used enum values have not been
* deprecated.
*
* Note: This rule is optional and is not part of the Validation section of the GraphQL
* Specification. The main purpose of this rule is detection of deprecated usages and not
* necessarily to forbid their use when querying a service.
*/
export function NoDeprecatedCustomRule(context: ValidationContext): ASTVisitor;

0 comments on commit f2a9252

Please sign in to comment.