diff --git a/cspell.yml b/cspell.yml index 3870f6f531..0de546fe97 100644 --- a/cspell.yml +++ b/cspell.yml @@ -48,6 +48,7 @@ ignoreRegExpList: words: - graphiql + - metafield - uncoerce - uncoerced diff --git a/src/language/__tests__/parser-test.ts b/src/language/__tests__/parser-test.ts index 2ca4c86216..9bbfa17b18 100644 --- a/src/language/__tests__/parser-test.ts +++ b/src/language/__tests__/parser-test.ts @@ -790,6 +790,60 @@ describe('Parser', () => { }); }); + it('parses __Type', () => { + const result = parseSchemaCoordinate('__Type'); + expectJSON(result).toDeepEqual({ + kind: Kind.TYPE_COORDINATE, + loc: { start: 0, end: 6 }, + name: { + kind: Kind.NAME, + loc: { start: 0, end: 6 }, + value: '__Type', + }, + }); + }); + + it('parses Type.__metafield', () => { + const result = parseSchemaCoordinate('Type.__metafield'); + expectJSON(result).toDeepEqual({ + kind: Kind.MEMBER_COORDINATE, + loc: { start: 0, end: 16 }, + name: { + kind: Kind.NAME, + loc: { start: 0, end: 4 }, + value: 'Type', + }, + memberName: { + kind: Kind.NAME, + loc: { start: 5, end: 16 }, + value: '__metafield', + }, + }); + }); + + it('parses Type.__metafield(arg:)', () => { + const result = parseSchemaCoordinate('Type.__metafield(arg:)'); + expectJSON(result).toDeepEqual({ + kind: Kind.ARGUMENT_COORDINATE, + loc: { start: 0, end: 22 }, + name: { + kind: Kind.NAME, + loc: { start: 0, end: 4 }, + value: 'Type', + }, + fieldName: { + kind: Kind.NAME, + loc: { start: 5, end: 16 }, + value: '__metafield', + }, + argumentName: { + kind: Kind.NAME, + loc: { start: 17, end: 20 }, + value: 'arg', + }, + }); + }); + it('rejects @ Name . Name', () => { expect(() => parseSchemaCoordinate('@myDirective.field')) .to.throw() diff --git a/src/language/__tests__/printer-test.ts b/src/language/__tests__/printer-test.ts index a7a604bcba..6ac39ef3d3 100644 --- a/src/language/__tests__/printer-test.ts +++ b/src/language/__tests__/printer-test.ts @@ -308,6 +308,13 @@ describe('Printer: Query document', () => { ); expect(print(parseSchemaCoordinate('@name'))).to.equal('@name'); expect(print(parseSchemaCoordinate('@name(arg:)'))).to.equal('@name(arg:)'); + expect(print(parseSchemaCoordinate('__Type'))).to.equal('__Type'); + expect(print(parseSchemaCoordinate('Type.__metafield'))).to.equal( + 'Type.__metafield', + ); + expect(print(parseSchemaCoordinate('Type.__metafield(arg:)'))).to.equal( + 'Type.__metafield(arg:)', + ); }); it('throws syntax error for ignored tokens in schema coordinates', () => { diff --git a/src/utilities/__tests__/resolveSchemaCoordinate-test.ts b/src/utilities/__tests__/resolveSchemaCoordinate-test.ts index 42d4310e0e..ae6435f137 100644 --- a/src/utilities/__tests__/resolveSchemaCoordinate-test.ts +++ b/src/utilities/__tests__/resolveSchemaCoordinate-test.ts @@ -1,4 +1,4 @@ -import { expect } from 'chai'; +import { assert, expect } from 'chai'; import { describe, it } from 'mocha'; import type { @@ -12,32 +12,32 @@ import type { GraphQLDirective } from '../../type/directives.js'; import { buildSchema } from '../buildASTSchema.js'; import { resolveSchemaCoordinate } from '../resolveSchemaCoordinate.js'; -describe('resolveSchemaCoordinate', () => { - const schema = buildSchema(` - type Query { - searchBusiness(criteria: SearchCriteria!): [Business] - } - - input SearchCriteria { - name: String - filter: SearchFilter - } - - enum SearchFilter { - OPEN_NOW - DELIVERS_TAKEOUT - VEGETARIAN_MENU - } - - type Business { - id: ID - name: String - email: String @private(scope: "loggedIn") - } - - directive @private(scope: String!) on FIELD_DEFINITION - `); +const schema = buildSchema(` + type Query { + searchBusiness(criteria: SearchCriteria!): [Business] + } + + input SearchCriteria { + name: String + filter: SearchFilter + } + + enum SearchFilter { + OPEN_NOW + DELIVERS_TAKEOUT + VEGETARIAN_MENU + } + + type Business { + id: ID + name: String + email: String @private(scope: "loggedIn") + } + directive @private(scope: String!) on FIELD_DEFINITION +`); + +describe('resolveSchemaCoordinate', () => { it('resolves a Named Type', () => { expect(resolveSchemaCoordinate(schema, 'Business')).to.deep.equal({ kind: 'NamedType', @@ -181,10 +181,20 @@ describe('resolveSchemaCoordinate', () => { 'Expected "unknown" to be defined as a directive in the schema.', ); }); +}); +/* + * NOTE: the following are not required for spec compliance; resolution + * of meta-fields is implementation-defined. + * + * These tests are here to ensure a change of behavior will only be made + * in a semver-major release of GraphQL.js. + */ +describe('resolveSchemaCoordinate (meta-fields and introspection types)', () => { it('resolves a meta-field', () => { const type = schema.getType('Business') as GraphQLObjectType; const field = schema.getField(type, '__typename'); + assert.ok(field); expect( resolveSchemaCoordinate(schema, 'Business.__typename'), ).to.deep.equal({ diff --git a/src/utilities/resolveSchemaCoordinate.ts b/src/utilities/resolveSchemaCoordinate.ts index db780930a0..018cb0eed4 100644 --- a/src/utilities/resolveSchemaCoordinate.ts +++ b/src/utilities/resolveSchemaCoordinate.ts @@ -14,10 +14,14 @@ import type { Source } from '../language/source.js'; import type { GraphQLArgument, + GraphQLEnumType, GraphQLEnumValue, GraphQLField, GraphQLInputField, + GraphQLInputObjectType, + GraphQLInterfaceType, GraphQLNamedType, + GraphQLObjectType, } from '../type/definition.js'; import { isEnumType, @@ -38,25 +42,25 @@ export interface ResolvedNamedType { export interface ResolvedField { readonly kind: 'Field'; - readonly type: GraphQLNamedType; + readonly type: GraphQLObjectType | GraphQLInterfaceType; readonly field: GraphQLField; } export interface ResolvedInputField { readonly kind: 'InputField'; - readonly type: GraphQLNamedType; + readonly type: GraphQLInputObjectType; readonly inputField: GraphQLInputField; } export interface ResolvedEnumValue { readonly kind: 'EnumValue'; - readonly type: GraphQLNamedType; + readonly type: GraphQLEnumType; readonly enumValue: GraphQLEnumValue; } export interface ResolvedFieldArgument { readonly kind: 'FieldArgument'; - readonly type: GraphQLNamedType; + readonly type: GraphQLObjectType | GraphQLInterfaceType; readonly field: GraphQLField; readonly fieldArgument: GraphQLArgument; } @@ -83,8 +87,10 @@ export type ResolvedSchemaElement = /** * A schema coordinate is resolved in the context of a GraphQL schema to - * uniquely identifies a schema element. It returns undefined if the schema - * coordinate does not resolve to a schema element. + * uniquely identify a schema element. It returns undefined if the schema + * coordinate does not resolve to a schema element, meta-field, or introspection + * schema element. It will throw if the containing schema element (if + * applicable) does not exist. * * https://spec.graphql.org/draft/#sec-Schema-Coordinates.Semantics */