diff --git a/.eslintrc.yml b/.eslintrc.yml index ef499d6cdd..a6c9524834 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -603,8 +603,8 @@ overrides: '@typescript-eslint/no-use-before-define': off '@typescript-eslint/no-duplicate-imports': off # Superseded by `import/no-duplicates` - # Bellow rules are disabled because coflicts with Prettier, see: - # https://github.com/prettier/eslint-config-prettier/blob/master/%40typescript-eslint.js + # Below rules are disabled because they conflict with Prettier, see: + # https://github.com/prettier/eslint-config-prettier/blob/main/index.js '@typescript-eslint/object-curly-spacing': off '@typescript-eslint/quotes': off '@typescript-eslint/brace-style': off diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43eb89ea45..cd3f3c1232 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ env.NODE_VERSION_USED_FOR_DEVELOPMENT }} @@ -63,7 +63,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ env.NODE_VERSION_USED_FOR_DEVELOPMENT }} @@ -81,7 +81,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ env.NODE_VERSION_USED_FOR_DEVELOPMENT }} @@ -101,7 +101,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ env.NODE_VERSION_USED_FOR_DEVELOPMENT }} @@ -127,7 +127,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ env.NODE_VERSION_USED_FOR_DEVELOPMENT }} @@ -163,7 +163,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Node.js v${{ matrix.node_version_to_setup }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ matrix.node_version_to_setup }} @@ -191,7 +191,7 @@ jobs: fetch-depth: 2 - name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ env.NODE_VERSION_USED_FOR_DEVELOPMENT }} @@ -222,7 +222,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ env.NODE_VERSION_USED_FOR_DEVELOPMENT }} @@ -258,7 +258,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ env.NODE_VERSION_USED_FOR_DEVELOPMENT }} diff --git a/package-lock.json b/package-lock.json index c5ed21c5b6..a254838b5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "graphql", - "version": "16.0.0-alpha.3", + "version": "16.0.0-alpha.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "graphql", - "version": "16.0.0-alpha.3", + "version": "16.0.0-alpha.4", "license": "MIT", "devDependencies": { "@babel/core": "7.14.3", diff --git a/package.json b/package.json index 309be94a87..e05b2bbe80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graphql", - "version": "16.0.0-alpha.3", + "version": "16.0.0-alpha.4", "description": "A Query Language and Runtime which can target any service.", "license": "MIT", "private": true, diff --git a/src/__testUtils__/__tests__/genFuzzStrings-test.ts b/src/__testUtils__/__tests__/genFuzzStrings-test.ts index c4961625e0..516ed00fe7 100644 --- a/src/__testUtils__/__tests__/genFuzzStrings-test.ts +++ b/src/__testUtils__/__tests__/genFuzzStrings-test.ts @@ -4,7 +4,7 @@ import { describe, it } from 'mocha'; import { genFuzzStrings } from '../genFuzzStrings'; function expectFuzzStrings(options: { - allowedChars: Array; + allowedChars: ReadonlyArray; maxLength: number; }) { return expect([...genFuzzStrings(options)]); diff --git a/src/__testUtils__/genFuzzStrings.ts b/src/__testUtils__/genFuzzStrings.ts index 9a9ffeac99..f29e1bb860 100644 --- a/src/__testUtils__/genFuzzStrings.ts +++ b/src/__testUtils__/genFuzzStrings.ts @@ -2,7 +2,7 @@ * Generator that produces all possible combinations of allowed characters. */ export function* genFuzzStrings(options: { - allowedChars: Array; + allowedChars: ReadonlyArray; maxLength: number; }): Generator { const { allowedChars, maxLength } = options; diff --git a/src/__testUtils__/kitchenSinkQuery.ts b/src/__testUtils__/kitchenSinkQuery.ts index ba23543a18..9ed9a7e983 100644 --- a/src/__testUtils__/kitchenSinkQuery.ts +++ b/src/__testUtils__/kitchenSinkQuery.ts @@ -28,7 +28,9 @@ mutation likeStory @onMutation { } } -subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) +subscription StoryLikeSubscription( + $input: StoryLikeSubscribeInput @onVariableDefinition +) @onSubscription { storyLikeSubscribe(input: $input) { story { diff --git a/src/__tests__/starWarsData.ts b/src/__tests__/starWarsData.ts index 04cd648546..60c4331bb6 100644 --- a/src/__tests__/starWarsData.ts +++ b/src/__tests__/starWarsData.ts @@ -5,16 +5,16 @@ export interface Character { id: string; name: string; - friends: Array; - appearsIn: Array; + friends: ReadonlyArray; + appearsIn: ReadonlyArray; } export interface Human { type: 'Human'; id: string; name: string; - friends: Array; - appearsIn: Array; + friends: ReadonlyArray; + appearsIn: ReadonlyArray; homePlanet?: string; } @@ -22,8 +22,8 @@ export interface Droid { type: 'Droid'; id: string; name: string; - friends: Array; - appearsIn: Array; + friends: ReadonlyArray; + appearsIn: ReadonlyArray; primaryFunction: string; } diff --git a/src/execution/__tests__/lists-test.ts b/src/execution/__tests__/lists-test.ts index e5efd74db5..53314ecba8 100644 --- a/src/execution/__tests__/lists-test.ts +++ b/src/execution/__tests__/lists-test.ts @@ -37,7 +37,7 @@ describe('Execute: Accepts any iterable as list value', () => { }); it('Accepts function arguments as a List value', () => { - function getArgs(..._args: Array) { + function getArgs(..._args: ReadonlyArray) { return arguments; } const listField = getArgs('one', 'two'); diff --git a/src/execution/__tests__/union-interface-test.ts b/src/execution/__tests__/union-interface-test.ts index 7ff9bd72bb..987f35ddec 100644 --- a/src/execution/__tests__/union-interface-test.ts +++ b/src/execution/__tests__/union-interface-test.ts @@ -19,7 +19,7 @@ class Dog { barks: boolean; mother?: Dog; father?: Dog; - progeny: Array; + progeny: ReadonlyArray; constructor(name: string, barks: boolean) { this.name = name; @@ -33,7 +33,7 @@ class Cat { meows: boolean; mother?: Cat; father?: Cat; - progeny: Array; + progeny: ReadonlyArray; constructor(name: string, meows: boolean) { this.name = name; @@ -44,13 +44,13 @@ class Cat { class Person { name: string; - pets?: Array; - friends?: Array; + pets?: ReadonlyArray; + friends?: ReadonlyArray; constructor( name: string, - pets?: Array, - friends?: Array, + pets?: ReadonlyArray, + friends?: ReadonlyArray, ) { this.name = name; this.pets = pets; diff --git a/src/execution/execute.ts b/src/execution/execute.ts index ef8a7d9e5d..c4a3fdce93 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -375,7 +375,7 @@ function executeFieldsSerially( parentType: GraphQLObjectType, sourceValue: unknown, path: Path | undefined, - fields: Map>, + fields: Map>, ): PromiseOrValue> { return promiseReduce( fields.entries(), @@ -413,7 +413,7 @@ function executeFields( parentType: GraphQLObjectType, sourceValue: unknown, path: Path | undefined, - fields: Map>, + fields: Map>, ): PromiseOrValue> { const results = Object.create(null); let containsPromise = false; @@ -463,7 +463,7 @@ export function collectFields( selectionSet: SelectionSetNode, fields: Map>, visitedFragmentNames: Set, -): Map> { +): Map> { for (const selection of selectionSet.selections) { switch (selection.kind) { case Kind.FIELD: { @@ -1075,7 +1075,7 @@ function _collectSubfields( exeContext: ExecutionContext, returnType: GraphQLObjectType, fieldNodes: ReadonlyArray, -): Map> { +): Map> { let subFieldNodes = new Map(); const visitedFragmentNames = new Set(); for (const node of fieldNodes) { diff --git a/src/index.ts b/src/index.ts index 21e2153630..529df660ff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -139,7 +139,7 @@ export type { GraphQLNamedType, GraphQLNamedInputType, GraphQLNamedOutputType, - ThunkArray, + ThunkReadonlyArray, ThunkObjMap, GraphQLSchemaConfig, GraphQLSchemaExtensions, diff --git a/src/jsutils/__tests__/suggestionList-test.ts b/src/jsutils/__tests__/suggestionList-test.ts index 3e2037950d..2b90524885 100644 --- a/src/jsutils/__tests__/suggestionList-test.ts +++ b/src/jsutils/__tests__/suggestionList-test.ts @@ -3,7 +3,7 @@ import { describe, it } from 'mocha'; import { suggestionList } from '../suggestionList'; -function expectSuggestions(input: string, options: Array) { +function expectSuggestions(input: string, options: ReadonlyArray) { return expect(suggestionList(input, options)); } diff --git a/src/jsutils/inspect.ts b/src/jsutils/inspect.ts index 9076490de0..514cbaad39 100644 --- a/src/jsutils/inspect.ts +++ b/src/jsutils/inspect.ts @@ -8,7 +8,10 @@ export function inspect(value: unknown): string { return formatValue(value, []); } -function formatValue(value: unknown, seenValues: Array): string { +function formatValue( + value: unknown, + seenValues: ReadonlyArray, +): string { switch (typeof value) { case 'string': return JSON.stringify(value); @@ -23,7 +26,7 @@ function formatValue(value: unknown, seenValues: Array): string { function formatObjectValue( value: object | null, - previouslySeenValues: Array, + previouslySeenValues: ReadonlyArray, ): string { if (value === null) { return 'null'; @@ -55,7 +58,10 @@ function isJSONable(value: any): value is { toJSON: () => unknown } { return typeof value.toJSON === 'function'; } -function formatObject(object: object, seenValues: Array): string { +function formatObject( + object: object, + seenValues: ReadonlyArray, +): string { const entries = Object.entries(object); if (entries.length === 0) { return '{}'; @@ -72,8 +78,8 @@ function formatObject(object: object, seenValues: Array): string { } function formatArray( - array: Array, - seenValues: Array, + array: ReadonlyArray, + seenValues: ReadonlyArray, ): string { if (array.length === 0) { return '[]'; diff --git a/src/language/__tests__/blockString-test.ts b/src/language/__tests__/blockString-test.ts index 3211dae0fb..9f0022fe70 100644 --- a/src/language/__tests__/blockString-test.ts +++ b/src/language/__tests__/blockString-test.ts @@ -7,7 +7,7 @@ import { printBlockString, } from '../blockString'; -function joinLines(...args: Array) { +function joinLines(...args: ReadonlyArray) { return args.join('\n'); } diff --git a/src/language/__tests__/printer-test.ts b/src/language/__tests__/printer-test.ts index cfa1e14052..5e6776b8ac 100644 --- a/src/language/__tests__/printer-test.ts +++ b/src/language/__tests__/printer-test.ts @@ -182,7 +182,7 @@ describe('Printer: Query document', () => { } } - subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) @onSubscription { + subscription StoryLikeSubscription($input: StoryLikeSubscribeInput @onVariableDefinition) @onSubscription { storyLikeSubscribe(input: $input) { story { likers { diff --git a/src/language/__tests__/visitor-test.ts b/src/language/__tests__/visitor-test.ts index 12a3351850..93b8fe072e 100644 --- a/src/language/__tests__/visitor-test.ts +++ b/src/language/__tests__/visitor-test.ts @@ -747,6 +747,10 @@ describe('Visitor', () => { ['enter', 'Name', 'name', 'NamedType'], ['leave', 'Name', 'name', 'NamedType'], ['leave', 'NamedType', 'type', 'VariableDefinition'], + ['enter', 'Directive', 0, undefined], + ['enter', 'Name', 'name', 'Directive'], + ['leave', 'Name', 'name', 'Directive'], + ['leave', 'Directive', 0, undefined], ['leave', 'VariableDefinition', 0, undefined], ['enter', 'Directive', 0, undefined], ['enter', 'Name', 'name', 'Directive'], diff --git a/src/type/__tests__/validation-test.ts b/src/type/__tests__/validation-test.ts index 6d8ef8f789..b36021055b 100644 --- a/src/type/__tests__/validation-test.ts +++ b/src/type/__tests__/validation-test.ts @@ -79,7 +79,7 @@ function withModifiers( ]; } -const outputTypes: Array = [ +const outputTypes: ReadonlyArray = [ ...withModifiers(GraphQLString), ...withModifiers(SomeScalarType), ...withModifiers(SomeEnumType), @@ -88,18 +88,18 @@ const outputTypes: Array = [ ...withModifiers(SomeInterfaceType), ]; -const notOutputTypes: Array = [ +const notOutputTypes: ReadonlyArray = [ ...withModifiers(SomeInputObjectType), ]; -const inputTypes: Array = [ +const inputTypes: ReadonlyArray = [ ...withModifiers(GraphQLString), ...withModifiers(SomeScalarType), ...withModifiers(SomeEnumType), ...withModifiers(SomeInputObjectType), ]; -const notInputTypes: Array = [ +const notInputTypes: ReadonlyArray = [ ...withModifiers(SomeObjectType), ...withModifiers(SomeUnionType), ...withModifiers(SomeInterfaceType), diff --git a/src/type/definition.ts b/src/type/definition.ts index 5714f2c7a5..ebed1f210a 100644 --- a/src/type/definition.ts +++ b/src/type/definition.ts @@ -526,10 +526,12 @@ export function getNamedType( * Used while defining GraphQL types to allow for circular references in * otherwise immutable type definitions. */ -export type ThunkArray = (() => Array) | Array; +export type ThunkReadonlyArray = (() => ReadonlyArray) | ReadonlyArray; export type ThunkObjMap = (() => ObjMap) | ObjMap; -function resolveArrayThunk(thunk: ThunkArray): Array { +function resolveReadonlyArrayThunk( + thunk: ThunkReadonlyArray, +): ReadonlyArray { return typeof thunk === 'function' ? thunk() : thunk; } @@ -748,7 +750,7 @@ export class GraphQLObjectType { extensionASTNodes: ReadonlyArray; private _fields: ThunkObjMap>; - private _interfaces: ThunkArray; + private _interfaces: ThunkReadonlyArray; constructor(config: Readonly>) { this.name = config.name; @@ -775,7 +777,7 @@ export class GraphQLObjectType { return this._fields; } - getInterfaces(): Array { + getInterfaces(): ReadonlyArray { if (typeof this._interfaces === 'function') { this._interfaces = this._interfaces(); } @@ -812,8 +814,8 @@ function defineInterfaces( config: Readonly< GraphQLObjectTypeConfig | GraphQLInterfaceTypeConfig >, -): Array { - const interfaces = resolveArrayThunk(config.interfaces ?? []); +): ReadonlyArray { + const interfaces = resolveReadonlyArrayThunk(config.interfaces ?? []); devAssert( Array.isArray(interfaces), `${config.name} interfaces must be an Array or a function which returns an Array.`, @@ -920,7 +922,7 @@ export function argsToArgsConfig( export interface GraphQLObjectTypeConfig { name: string; description?: Maybe; - interfaces?: ThunkArray; + interfaces?: ThunkReadonlyArray; fields: ThunkObjMap>; isTypeOf?: Maybe>; extensions?: Maybe>>; @@ -930,7 +932,7 @@ export interface GraphQLObjectTypeConfig { interface GraphQLObjectTypeNormalizedConfig extends GraphQLObjectTypeConfig { - interfaces: Array; + interfaces: ReadonlyArray; fields: GraphQLFieldConfigMap; extensions: Maybe>>; extensionASTNodes: ReadonlyArray; @@ -1112,7 +1114,7 @@ export class GraphQLInterfaceType { extensionASTNodes: ReadonlyArray; private _fields: ThunkObjMap>; - private _interfaces: ThunkArray; + private _interfaces: ThunkReadonlyArray; constructor(config: Readonly>) { this.name = config.name; @@ -1139,7 +1141,7 @@ export class GraphQLInterfaceType { return this._fields; } - getInterfaces(): Array { + getInterfaces(): ReadonlyArray { if (typeof this._interfaces === 'function') { this._interfaces = this._interfaces(); } @@ -1175,7 +1177,7 @@ export class GraphQLInterfaceType { export interface GraphQLInterfaceTypeConfig { name: string; description?: Maybe; - interfaces?: ThunkArray; + interfaces?: ThunkReadonlyArray; fields: ThunkObjMap>; /** * Optionally provide a custom type resolver function. If one is not provided, @@ -1190,7 +1192,7 @@ export interface GraphQLInterfaceTypeConfig { export interface GraphQLInterfaceTypeNormalizedConfig extends GraphQLInterfaceTypeConfig { - interfaces: Array; + interfaces: ReadonlyArray; fields: GraphQLFieldConfigMap; extensions: Maybe>; extensionASTNodes: ReadonlyArray; @@ -1240,7 +1242,7 @@ export class GraphQLUnionType { astNode: Maybe; extensionASTNodes: ReadonlyArray; - private _types: ThunkArray; + private _types: ThunkReadonlyArray; constructor(config: Readonly>) { this.name = config.name; @@ -1259,7 +1261,7 @@ export class GraphQLUnionType { ); } - getTypes(): Array { + getTypes(): ReadonlyArray { if (typeof this._types === 'function') { this._types = this._types(); } @@ -1293,8 +1295,8 @@ export class GraphQLUnionType { function defineTypes( config: Readonly>, -): Array { - const types = resolveArrayThunk(config.types); +): ReadonlyArray { + const types = resolveReadonlyArrayThunk(config.types); devAssert( Array.isArray(types), `Must provide Array of types or a function which returns such an array for Union ${config.name}.`, @@ -1305,7 +1307,7 @@ function defineTypes( export interface GraphQLUnionTypeConfig { name: string; description?: Maybe; - types: ThunkArray; + types: ThunkReadonlyArray; /** * Optionally provide a custom type resolver function. If one is not provided, * the default implementation will call `isTypeOf` on each implementing @@ -1319,7 +1321,7 @@ export interface GraphQLUnionTypeConfig { interface GraphQLUnionTypeNormalizedConfig extends GraphQLUnionTypeConfig { - types: Array; + types: ReadonlyArray; extensions: Maybe>; extensionASTNodes: ReadonlyArray; } @@ -1365,8 +1367,8 @@ export class GraphQLEnumType /* */ { astNode: Maybe; extensionASTNodes: ReadonlyArray; - private _values: Array */>; - private _valueLookup: Map; + private _values: ReadonlyArray */>; + private _valueLookup: ReadonlyMap; private _nameLookup: ObjMap; constructor(config: Readonly */>) { @@ -1385,7 +1387,7 @@ export class GraphQLEnumType /* */ { devAssert(typeof config.name === 'string', 'Must provide name.'); } - getValues(): Array */> { + getValues(): ReadonlyArray */> { return this._values; } @@ -1497,7 +1499,7 @@ function didYouMeanEnumValue( function defineEnumValues( typeName: string, valueMap: GraphQLEnumValueConfigMap /* */, -): Array */> { +): ReadonlyArray */> { devAssert( isPlainObj(valueMap), `${typeName} values must be an object with value names as keys.`, diff --git a/src/type/directives.ts b/src/type/directives.ts index 3dff8298bb..c2678a6a25 100644 --- a/src/type/directives.ts +++ b/src/type/directives.ts @@ -56,7 +56,7 @@ export interface GraphQLDirectiveExtensions { export class GraphQLDirective { name: string; description: Maybe; - locations: Array; + locations: ReadonlyArray; args: ReadonlyArray; isRepeatable: boolean; extensions: Maybe>; @@ -113,7 +113,7 @@ export class GraphQLDirective { export interface GraphQLDirectiveConfig { name: string; description?: Maybe; - locations: Array; + locations: ReadonlyArray; args?: Maybe; isRepeatable?: Maybe; extensions?: Maybe>; diff --git a/src/type/index.ts b/src/type/index.ts index fab8bb65dc..5686fd71f9 100644 --- a/src/type/index.ts +++ b/src/type/index.ts @@ -76,7 +76,7 @@ export type { GraphQLNamedType, GraphQLNamedInputType, GraphQLNamedOutputType, - ThunkArray, + ThunkReadonlyArray, ThunkObjMap, GraphQLArgument, GraphQLArgumentConfig, @@ -166,16 +166,13 @@ export { __InputValue, __EnumValue, __TypeKind, + /** "Enum" of Type Kinds */ + TypeKind, /** Meta-field definitions. */ SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef, } from './introspection'; -export type { - /** "Enum" of Type Kinds */ - TypeKind, -} from './introspection'; - /** Validate GraphQL schema. */ export { validateSchema, assertValidSchema } from './validate'; diff --git a/src/type/schema.ts b/src/type/schema.ts index dca24eda5b..43893ee813 100644 --- a/src/type/schema.ts +++ b/src/type/schema.ts @@ -288,8 +288,8 @@ export class GraphQLSchema { } getImplementations(interfaceType: GraphQLInterfaceType): { - objects: /* $ReadOnly */ Array; - interfaces: /* $ReadOnly */ Array; + objects: ReadonlyArray; + interfaces: ReadonlyArray; } { const implementations = this._implementationsMap[interfaceType.name]; return implementations ?? { objects: [], interfaces: [] }; @@ -337,7 +337,7 @@ export class GraphQLSchema { mutation: this.getMutationType(), subscription: this.getSubscriptionType(), types: Object.values(this.getTypeMap()), - directives: this.getDirectives().slice(), + directives: this.getDirectives(), extensions: this.extensions, astNode: this.astNode, extensionASTNodes: this.extensionASTNodes, @@ -368,8 +368,8 @@ export interface GraphQLSchemaConfig extends GraphQLSchemaValidationOptions { query?: Maybe; mutation?: Maybe; subscription?: Maybe; - types?: Maybe>; - directives?: Maybe>; + types?: Maybe>; + directives?: Maybe>; extensions?: Maybe>; astNode?: Maybe; extensionASTNodes?: Maybe>; @@ -380,8 +380,8 @@ export interface GraphQLSchemaConfig extends GraphQLSchemaValidationOptions { */ export interface GraphQLSchemaNormalizedConfig extends GraphQLSchemaConfig { description: Maybe; - types: Array; - directives: Array; + types: ReadonlyArray; + directives: ReadonlyArray; extensions: Maybe>; extensionASTNodes: ReadonlyArray; assumeValid: boolean; diff --git a/src/utilities/__tests__/TypeInfo-test.ts b/src/utilities/__tests__/TypeInfo-test.ts index 1d28eb7d05..6008e1d927 100644 --- a/src/utilities/__tests__/TypeInfo-test.ts +++ b/src/utilities/__tests__/TypeInfo-test.ts @@ -7,16 +7,49 @@ import { parse, parseValue } from '../../language/parser'; import { print } from '../../language/printer'; import { visit } from '../../language/visitor'; +import { GraphQLSchema } from '../../type/schema'; import { getNamedType, isCompositeType } from '../../type/definition'; import { buildSchema } from '../buildASTSchema'; import { TypeInfo, visitWithTypeInfo } from '../TypeInfo'; -import { testSchema } from '../../validation/__tests__/harness'; +const testSchema = buildSchema(` + interface Pet { + name: String + } + + type Dog implements Pet { + name: String + } + + type Cat implements Pet { + name: String + } + + type Human { + name: String + pets: [Pet] + } + + type Alien { + name(surname: Boolean): String + } + + type QueryRoot { + human(id: ID): Human + alien: Alien + } + + schema { + query: QueryRoot + } +`); describe('TypeInfo', () => { + const schema = new GraphQLSchema({}); + it('can be Object.toStringified', () => { - const typeInfo = new TypeInfo(testSchema); + const typeInfo = new TypeInfo(schema); expect(Object.prototype.toString.call(typeInfo)).to.equal( '[object TypeInfo]', @@ -24,7 +57,7 @@ describe('TypeInfo', () => { }); it('allow all methods to be called before entering any node', () => { - const typeInfo = new TypeInfo(testSchema); + const typeInfo = new TypeInfo(schema); expect(typeInfo.getType()).to.equal(undefined); expect(typeInfo.getParentType()).to.equal(undefined); @@ -316,11 +349,16 @@ describe('visitWithTypeInfo', () => { }); it('supports traversals of input values', () => { + const schema = buildSchema(` + input ComplexInput { + stringListField: [String] + } + `); const ast = parseValue('{ stringListField: ["foo"] }'); - const complexInputType = testSchema.getType('ComplexInput'); + const complexInputType = schema.getType('ComplexInput'); invariant(complexInputType != null); - const typeInfo = new TypeInfo(testSchema, complexInputType); + const typeInfo = new TypeInfo(schema, complexInputType); const visited: Array = []; visit( diff --git a/src/utilities/buildASTSchema.ts b/src/utilities/buildASTSchema.ts index 6aa872c085..2c92bcb124 100644 --- a/src/utilities/buildASTSchema.ts +++ b/src/utilities/buildASTSchema.ts @@ -78,15 +78,17 @@ export function buildASTSchema( } } - const { directives } = config; - // If specified directives were not explicitly declared, add them. - for (const stdDirective of specifiedDirectives) { - if (directives.every((directive) => directive.name !== stdDirective.name)) { - directives.push(stdDirective); - } - } + const directives = [ + ...config.directives, + // If specified directives were not explicitly declared, add them. + ...specifiedDirectives.filter((stdDirective) => + config.directives.every( + (directive) => directive.name !== stdDirective.name, + ), + ), + ]; - return new GraphQLSchema(config); + return new GraphQLSchema({ ...config, directives }); } /** diff --git a/src/utilities/findBreakingChanges.ts b/src/utilities/findBreakingChanges.ts index 0da4cceaa7..7aa0760481 100644 --- a/src/utilities/findBreakingChanges.ts +++ b/src/utilities/findBreakingChanges.ts @@ -557,9 +557,9 @@ function diff( oldArray: ReadonlyArray, newArray: ReadonlyArray, ): { - added: Array; - removed: Array; - persisted: Array<[T, T]>; + added: ReadonlyArray; + removed: ReadonlyArray; + persisted: ReadonlyArray<[T, T]>; } { const added: Array = []; const removed: Array = []; diff --git a/src/utilities/separateOperations.ts b/src/utilities/separateOperations.ts index 18884059e9..ece8c2739d 100644 --- a/src/utilities/separateOperations.ts +++ b/src/utilities/separateOperations.ts @@ -63,7 +63,7 @@ export function separateOperations( return separatedDocumentASTs; } -type DepGraph = ObjMap>; +type DepGraph = ObjMap>; // From a dependency graph, collects a list of transitive dependencies by // recursing through a dependency graph. diff --git a/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts b/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts index 6ec857a683..21d638641f 100644 --- a/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts +++ b/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts @@ -10,16 +10,49 @@ import { buildSchema } from '../../utilities/buildASTSchema'; import { validate } from '../validate'; import { FieldsOnCorrectTypeRule } from '../rules/FieldsOnCorrectTypeRule'; -import { expectValidationErrors } from './harness'; +import { expectValidationErrorsWithSchema } from './harness'; function expectErrors(queryStr: string) { - return expectValidationErrors(FieldsOnCorrectTypeRule, queryStr); + return expectValidationErrorsWithSchema( + testSchema, + FieldsOnCorrectTypeRule, + queryStr, + ); } function expectValid(queryStr: string) { expectErrors(queryStr).to.deep.equal([]); } +const testSchema = buildSchema(` + interface Pet { + name: String + } + + type Dog implements Pet { + name: String + nickname: String + barkVolume: Int + } + + type Cat implements Pet { + name: String + nickname: String + meowVolume: Int + } + + union CatOrDog = Cat | Dog + + type Human { + name: String + pets: [Pet] + } + + type Query { + human: Human + } +`); + describe('Validate: Fields on correct type', () => { it('Object field selection', () => { expectValid(` @@ -237,7 +270,7 @@ describe('Validate: Fields on correct type', () => { `).to.deep.equal([ { message: - 'Cannot query field "name" on type "CatOrDog". Did you mean to use an inline fragment on "Being", "Pet", "Canine", "Cat", or "Dog"?', + 'Cannot query field "name" on type "CatOrDog". Did you mean to use an inline fragment on "Pet", "Cat", or "Dog"?', locations: [{ line: 3, column: 9 }], }, ]); @@ -332,7 +365,7 @@ describe('Validate: Fields on correct type', () => { }); it('Sort type suggestions based on inheritance order', () => { - const schema = buildSchema(` + const interfaceSchema = buildSchema(` interface T { bar: String } type Query { t: T } @@ -352,9 +385,27 @@ describe('Validate: Fields on correct type', () => { } `); - expectErrorMessage(schema, '{ t { foo } }').to.equal( + expectErrorMessage(interfaceSchema, '{ t { foo } }').to.equal( 'Cannot query field "foo" on type "T". Did you mean to use an inline fragment on "Z", "Y", or "X"?', ); + + const unionSchema = buildSchema(` + interface Animal { name: String } + interface Mammal implements Animal { name: String } + + interface Canine implements Animal & Mammal { name: String } + type Dog implements Animal & Mammal & Canine { name: String } + + interface Feline implements Animal & Mammal { name: String } + type Cat implements Animal & Mammal & Feline { name: String } + + union CatOrDog = Cat | Dog + type Query { catOrDog: CatOrDog } + `); + + expectErrorMessage(unionSchema, '{ catOrDog { name } }').to.equal( + 'Cannot query field "name" on type "CatOrDog". Did you mean to use an inline fragment on "Animal", "Mammal", "Canine", "Dog", or "Feline"?', + ); }); it('Limits lots of type suggestions', () => { diff --git a/src/validation/__tests__/KnownDirectivesRule-test.ts b/src/validation/__tests__/KnownDirectivesRule-test.ts index 13491bc6c5..cfbc9555e4 100644 --- a/src/validation/__tests__/KnownDirectivesRule-test.ts +++ b/src/validation/__tests__/KnownDirectivesRule-test.ts @@ -6,10 +6,17 @@ import { buildSchema } from '../../utilities/buildASTSchema'; import { KnownDirectivesRule } from '../rules/KnownDirectivesRule'; -import { expectValidationErrors, expectSDLValidationErrors } from './harness'; +import { + expectValidationErrorsWithSchema, + expectSDLValidationErrors, +} from './harness'; function expectErrors(queryStr: string) { - return expectValidationErrors(KnownDirectivesRule, queryStr); + return expectValidationErrorsWithSchema( + schemaWithDirectives, + KnownDirectivesRule, + queryStr, + ); } function expectValid(queryStr: string) { @@ -24,6 +31,21 @@ function expectValidSDL(sdlStr: string, schema?: GraphQLSchema) { expectSDLErrors(sdlStr, schema).to.deep.equal([]); } +const schemaWithDirectives = buildSchema(` + type Query { + dummy: String + } + + directive @onQuery on QUERY + directive @onMutation on MUTATION + directive @onSubscription on SUBSCRIPTION + directive @onField on FIELD + directive @onFragmentDefinition on FRAGMENT_DEFINITION + directive @onFragmentSpread on FRAGMENT_SPREAD + directive @onInlineFragment on INLINE_FRAGMENT + directive @onVariableDefinition on VARIABLE_DEFINITION +`); + const schemaWithSDLDirectives = buildSchema(` directive @onSchema on SCHEMA directive @onScalar on SCALAR @@ -52,14 +74,16 @@ describe('Validate: Known directives', () => { `); }); - it('with known directives', () => { + it('with standard directives', () => { expectValid(` { - dog @include(if: true) { - name - } human @skip(if: false) { name + pets { + ... on Dog @include(if: true) { + name + } + } } } `); @@ -68,14 +92,14 @@ describe('Validate: Known directives', () => { it('with unknown directive', () => { expectErrors(` { - dog @unknown(directive: "value") { + human @unknown(directive: "value") { name } } `).to.deep.equal([ { message: 'Unknown directive "@unknown".', - locations: [{ line: 3, column: 13 }], + locations: [{ line: 3, column: 15 }], }, ]); }); @@ -83,12 +107,10 @@ describe('Validate: Known directives', () => { it('with many unknown directives', () => { expectErrors(` { - dog @unknown(directive: "value") { + __typename @unknown + human @unknown { name - } - human @unknown(directive: "value") { - name - pets @unknown(directive: "value") { + pets @unknown { name } } @@ -96,93 +118,114 @@ describe('Validate: Known directives', () => { `).to.deep.equal([ { message: 'Unknown directive "@unknown".', - locations: [{ line: 3, column: 13 }], + locations: [{ line: 3, column: 20 }], }, { message: 'Unknown directive "@unknown".', - locations: [{ line: 6, column: 15 }], + locations: [{ line: 4, column: 15 }], }, { message: 'Unknown directive "@unknown".', - locations: [{ line: 8, column: 16 }], + locations: [{ line: 6, column: 16 }], }, ]); }); it('with well placed directives', () => { expectValid(` - query ($var: Boolean) @onQuery { - name @include(if: $var) - ...Frag @include(if: true) - skippedField @skip(if: true) - ...SkippedFrag @skip(if: true) - - ... @skip(if: true) { - skippedField + query ($var: Boolean @onVariableDefinition) @onQuery { + human @onField { + ...Frag @onFragmentSpread + ... @onInlineFragment { + name @onField + } } } mutation @onMutation { - someField + someField @onField } subscription @onSubscription { - someField - } - - fragment Frag on SomeType @onFragmentDefinition { - someField + someField @onField } - `); - }); - it('with well placed variable definition directive', () => { - expectValid(` - query Foo($var: Boolean @onVariableDefinition) { - name + fragment Frag on Human @onFragmentDefinition { + name @onField } `); }); it('with misplaced directives', () => { expectErrors(` - query Foo($var: Boolean) @include(if: true) { - name @onQuery @include(if: $var) - ...Frag @onQuery + query ($var: Boolean @onQuery) @onMutation { + human @onQuery { + ...Frag @onQuery + ... @onQuery { + name @onQuery + } + } } - mutation Bar @onQuery { - someField + mutation @onQuery { + someField @onQuery + } + + subscription @onQuery { + someField @onQuery + } + + fragment Frag on Human @onQuery { + name @onQuery } `).to.deep.equal([ { - message: 'Directive "@include" may not be used on QUERY.', - locations: [{ line: 2, column: 32 }], + message: 'Directive "@onQuery" may not be used on VARIABLE_DEFINITION.', + locations: [{ line: 2, column: 28 }], + }, + { + message: 'Directive "@onMutation" may not be used on QUERY.', + locations: [{ line: 2, column: 38 }], }, { message: 'Directive "@onQuery" may not be used on FIELD.', - locations: [{ line: 3, column: 14 }], + locations: [{ line: 3, column: 15 }], }, { message: 'Directive "@onQuery" may not be used on FRAGMENT_SPREAD.', - locations: [{ line: 4, column: 17 }], + locations: [{ line: 4, column: 19 }], + }, + { + message: 'Directive "@onQuery" may not be used on INLINE_FRAGMENT.', + locations: [{ line: 5, column: 15 }], + }, + { + message: 'Directive "@onQuery" may not be used on FIELD.', + locations: [{ line: 6, column: 18 }], }, { message: 'Directive "@onQuery" may not be used on MUTATION.', - locations: [{ line: 7, column: 20 }], + locations: [{ line: 11, column: 16 }], }, - ]); - }); - - it('with misplaced variable definition directive', () => { - expectErrors(` - query Foo($var: Boolean @onField) { - name - } - `).to.deep.equal([ { - message: 'Directive "@onField" may not be used on VARIABLE_DEFINITION.', - locations: [{ line: 2, column: 31 }], + message: 'Directive "@onQuery" may not be used on FIELD.', + locations: [{ column: 19, line: 12 }], + }, + { + message: 'Directive "@onQuery" may not be used on SUBSCRIPTION.', + locations: [{ column: 20, line: 15 }], + }, + { + message: 'Directive "@onQuery" may not be used on FIELD.', + locations: [{ column: 19, line: 16 }], + }, + { + message: 'Directive "@onQuery" may not be used on FRAGMENT_DEFINITION.', + locations: [{ column: 30, line: 19 }], + }, + { + message: 'Directive "@onQuery" may not be used on FIELD.', + locations: [{ column: 14, line: 20 }], }, ]); }); diff --git a/src/validation/__tests__/PossibleFragmentSpreadsRule-test.ts b/src/validation/__tests__/PossibleFragmentSpreadsRule-test.ts index 267dbd3b38..c93d4d6457 100644 --- a/src/validation/__tests__/PossibleFragmentSpreadsRule-test.ts +++ b/src/validation/__tests__/PossibleFragmentSpreadsRule-test.ts @@ -1,17 +1,70 @@ import { describe, it } from 'mocha'; +import { buildSchema } from '../../utilities/buildASTSchema'; + import { PossibleFragmentSpreadsRule } from '../rules/PossibleFragmentSpreadsRule'; -import { expectValidationErrors } from './harness'; +import { expectValidationErrorsWithSchema } from './harness'; function expectErrors(queryStr: string) { - return expectValidationErrors(PossibleFragmentSpreadsRule, queryStr); + return expectValidationErrorsWithSchema( + testSchema, + PossibleFragmentSpreadsRule, + queryStr, + ); } function expectValid(queryStr: string) { expectErrors(queryStr).to.deep.equal([]); } +const testSchema = buildSchema(` + interface Being { + name: String + } + + interface Pet implements Being { + name: String + } + + type Dog implements Being & Pet { + name: String + barkVolume: Int + } + + type Cat implements Being & Pet { + name: String + meowVolume: Int + } + + union CatOrDog = Cat | Dog + + interface Intelligent { + iq: Int + } + + type Human implements Being & Intelligent { + name: String + pets: [Pet] + iq: Int + } + + type Alien implements Being & Intelligent { + name: String + iq: Int + } + + union DogOrHuman = Dog | Human + + union HumanOrAlien = Human | Alien + + type Query { + catOrDog: CatOrDog + dogOrHuman: DogOrHuman + humanOrAlien: HumanOrAlien + } +`); + describe('Validate: Possible fragment spreads', () => { it('of the same object', () => { expectValid(` diff --git a/src/validation/__tests__/harness.ts b/src/validation/__tests__/harness.ts index 711ea91a6e..139827af11 100644 --- a/src/validation/__tests__/harness.ts +++ b/src/validation/__tests__/harness.ts @@ -12,20 +12,16 @@ import { validate, validateSDL } from '../validate'; import type { ValidationRule, SDLValidationRule } from '../ValidationContext'; export const testSchema: GraphQLSchema = buildSchema(` - interface Being { - name(surname: Boolean): String - } - interface Mammal { mother: Mammal father: Mammal } - interface Pet implements Being { + interface Pet { name(surname: Boolean): String } - interface Canine implements Mammal & Being { + interface Canine implements Mammal { name(surname: Boolean): String mother: Canine father: Canine @@ -37,7 +33,7 @@ export const testSchema: GraphQLSchema = buildSchema(` DOWN } - type Dog implements Being & Pet & Mammal & Canine { + type Dog implements Pet & Mammal & Canine { name(surname: Boolean): String nickname: String barkVolume: Int @@ -49,7 +45,7 @@ export const testSchema: GraphQLSchema = buildSchema(` father: Dog } - type Cat implements Being & Pet { + type Cat implements Pet { name(surname: Boolean): String nickname: String meows: Boolean @@ -59,27 +55,12 @@ export const testSchema: GraphQLSchema = buildSchema(` union CatOrDog = Cat | Dog - interface Intelligent { - iq: Int - } - - type Human implements Being & Intelligent { + type Human { name(surname: Boolean): String pets: [Pet] relatives: [Human] - iq: Int } - type Alien implements Being & Intelligent { - name(surname: Boolean): String - numEyes: Int - iq: Int - } - - union DogOrHuman = Dog | Human - - union HumanOrAlien = Human | Alien - enum FurColor { BROWN BLACK @@ -120,13 +101,10 @@ export const testSchema: GraphQLSchema = buildSchema(` type QueryRoot { human(id: ID): Human - alien: Alien dog: Dog cat: Cat pet: Pet catOrDog: CatOrDog - dogOrHuman: DogOrHuman - humanOrAlien: HumanOrAlien complicatedArgs: ComplicatedArgs } @@ -134,14 +112,7 @@ export const testSchema: GraphQLSchema = buildSchema(` query: QueryRoot } - directive @onQuery on QUERY - directive @onMutation on MUTATION - directive @onSubscription on SUBSCRIPTION directive @onField on FIELD - directive @onFragmentDefinition on FRAGMENT_DEFINITION - directive @onFragmentSpread on FRAGMENT_SPREAD - directive @onInlineFragment on INLINE_FRAGMENT - directive @onVariableDefinition on VARIABLE_DEFINITION `); export function expectValidationErrorsWithSchema( diff --git a/src/validation/__tests__/validation-test.ts b/src/validation/__tests__/validation-test.ts index ed68d68788..af8508912f 100644 --- a/src/validation/__tests__/validation-test.ts +++ b/src/validation/__tests__/validation-test.ts @@ -23,12 +23,14 @@ describe('Validate: Supports full validation', () => { it('validates queries', () => { const doc = parse(` query { - catOrDog { - ... on Cat { - furColor - } - ... on Dog { - isHouseTrained + human { + pets { + ... on Cat { + meowsVolume + } + ... on Dog { + barkVolume + } } } } @@ -60,12 +62,14 @@ describe('Validate: Supports full validation', () => { const doc = parse(` query { - catOrDog { - ... on Cat { - furColor - } - ... on Dog { - isHouseTrained + human { + pets { + ... on Cat { + meowsVolume + } + ... on Dog { + barkVolume + } } } } @@ -75,9 +79,9 @@ describe('Validate: Supports full validation', () => { const errorMessages = errors.map((error) => error.message); expect(errorMessages).to.deep.equal([ - 'Cannot query field "catOrDog" on type "QueryRoot". Did you mean "catOrDog"?', - 'Cannot query field "furColor" on type "Cat". Did you mean "furColor"?', - 'Cannot query field "isHouseTrained" on type "Dog". Did you mean "isHouseTrained"?', + 'Cannot query field "human" on type "QueryRoot". Did you mean "human"?', + 'Cannot query field "meowsVolume" on type "Cat". Did you mean "meowsVolume"?', + 'Cannot query field "barkVolume" on type "Dog". Did you mean "barkVolume"?', ]); }); diff --git a/src/version.ts b/src/version.ts index b5964b9656..f38bd67e33 100644 --- a/src/version.ts +++ b/src/version.ts @@ -4,7 +4,7 @@ /** * A string containing the version of the GraphQL.js library */ -export const version = '16.0.0-alpha.3'; +export const version = '16.0.0-alpha.4'; /** * An object containing the components of the GraphQL.js version string @@ -13,5 +13,5 @@ export const versionInfo = Object.freeze({ major: 16, minor: 0, patch: 0, - preReleaseTag: 'alpha.3', + preReleaseTag: 'alpha.4', });