diff --git a/src/diagnostic/hook-diagnostic.ts b/src/diagnostic/hook-diagnostic.ts index a1981a9..27dc078 100644 --- a/src/diagnostic/hook-diagnostic.ts +++ b/src/diagnostic/hook-diagnostic.ts @@ -5,6 +5,9 @@ import { Position, Range } from '../diff' import { GraphQLContext, createGraphQLContext, getGQLNodeLocationRange } from '../gql' import { getGQLContent, getGraphQLQueryVariable, getTSNodeLocationRange } from '../ts' import { Diagnostic, DiagnosticSeverity } from './diagnostic-type' +import { NoDuplicateFieldName } from './rules/NoDuplicateFieldName' + +const additionalRules = [NoDuplicateFieldName] function toError(error: gql.GraphQLError, context: Pick) { const lineOffset = context?.offset?.line ?? 0 @@ -46,7 +49,9 @@ export function diagnoseGraphQLQuery( ): Diagnostic[] { try { const document = gql.parse(query) - const result = gql.validate(schema, document) + const result = gql + .validate(schema, document) + .concat(additionalRules.flatMap(rule => rule(document, schema))) return result.flatMap(error => toError(error, context)) } catch (e) { return [toError(e, context)].flat() diff --git a/src/diagnostic/rules/NoDuplicateFieldName.ts b/src/diagnostic/rules/NoDuplicateFieldName.ts new file mode 100644 index 0000000..8c568cc --- /dev/null +++ b/src/diagnostic/rules/NoDuplicateFieldName.ts @@ -0,0 +1,36 @@ +import * as gql from 'graphql' +import { isFieldNode, isInlineFragmentNode } from '../../gql' + +export function NoDuplicateFieldName(document: gql.DocumentNode, schema: gql.GraphQLSchema) { + const errors: gql.GraphQLError[] = [] + const typeInfo = new gql.TypeInfo(schema) + gql.visit( + document, + gql.visitWithTypeInfo(typeInfo, { + SelectionSet(node) { + const parent = typeInfo.getParentType() + const type = parent?.name + node.selections.reduce( + (a, node) => { + const name = isFieldNode(node) + ? node.name.value + : isInlineFragmentNode(node) + ? node.typeCondition?.name.value + : undefined + if (!name) return a + if (!!a[name]) { + errors.push( + new gql.GraphQLError(`Duplicate field "${name}" on type "${type}" `, { + nodes: [node], + }), + ) + } + return { ...a, [name]: true } + }, + {} as Record, + ) + }, + }), + ) + return errors +} diff --git a/src/diagnostic/rules/index.ts b/src/diagnostic/rules/index.ts new file mode 100644 index 0000000..f21684d --- /dev/null +++ b/src/diagnostic/rules/index.ts @@ -0,0 +1 @@ +export * from './NoDuplicateFieldName'