diff --git a/src/Errors.ts b/src/Errors.ts index 883dc667..dcf6d2e6 100644 --- a/src/Errors.ts +++ b/src/Errors.ts @@ -214,8 +214,8 @@ export function resolverParamIsMissingType() { return "Missing type annotation for resolver argument. Expected all resolver arguments to have an explicit type annotation. Grats needs to be able to see the type of the arguments to generate an executable GraphQL schema."; } -export function argumentParamIsNotObject() { - return "Expected GraphQL field arguments to be typed using an inline literal object: `{someField: string}`. If there are no arguments, you can use `args: unknown`. Grats needs to be able to see the type of the arguments to generate a GraphQL schema."; +export function multipleResolverTypeLiterals() { + return "Unexpected multiple resolver parameters typed with an object literal. Grats assumes a resolver parameter typed with object literals describes the GraphQL arguments. Therefore only one such parameter is permitted."; } export function argIsNotProperty() { diff --git a/src/Extractor.ts b/src/Extractor.ts index bee2ba3c..b4aa840f 100644 --- a/src/Extractor.ts +++ b/src/Extractor.ts @@ -1359,34 +1359,8 @@ class Extractor { } collectArgs( - argsParam: ts.ParameterDeclaration, - ): ReadonlyArray | null { - const args: InputValueDefinitionNode[] = []; - - const argsType = argsParam.type; - if (argsType == null) { - return this.report(argsParam, E.resolverParamIsMissingType()); - } - if (argsType.kind === ts.SyntaxKind.UnknownKeyword) { - return []; - } - if (!ts.isTypeLiteralNode(argsType)) { - return this.report(argsType, E.argumentParamIsNotObject()); - } - - let defaults: ArgDefaults | null = null; - if (ts.isObjectBindingPattern(argsParam.name)) { - defaults = this.collectArgDefaults(argsParam.name); - } - - for (const member of argsType.members) { - const arg = this.collectArg(member, defaults); - if (arg != null) { - args.push(arg); - } - } - return args; - } + argsType: ts.TypeLiteralNode, + ): ReadonlyArray | null {} collectArgDefaults(node: ts.ObjectBindingPattern): ArgDefaults { const defaults = new Map(); @@ -1843,7 +1817,10 @@ class Extractor { } | null { const resolverParams: UnresolvedResolverParam[] = []; - let args: readonly InputValueDefinitionNode[] | null = null; + let args: { + param: ts.ParameterDeclaration; + inputs: InputValueDefinitionNode[]; + } | null = null; for (const param of parameters) { if (param.dotDotDotToken != null) { @@ -1857,11 +1834,24 @@ class Extractor { } if (ts.isTypeLiteralNode(param.type)) { if (args != null) { - // FIXME - return this.report(param, "Unexpected second argument."); + return this.report(param, E.multipleResolverTypeLiterals(), [ + tsRelated(args.param, "Previous type literal"), + ]); } resolverParams.push({ kind: "named", name: "args" }); - args = this.collectArgs(param); + args = { param, inputs: [] }; + + let defaults: ArgDefaults | null = null; + if (ts.isObjectBindingPattern(param.name)) { + defaults = this.collectArgDefaults(param.name); + } + + for (const member of param.type.members) { + const arg = this.collectArg(member, defaults); + if (arg != null) { + args.inputs.push(arg); + } + } continue; } if (ts.isTypeReferenceNode(param.type)) { @@ -1885,7 +1875,7 @@ class Extractor { } return this.report(param.type, E.unexpectedResolverParamType()); } - return { resolverParams, args }; + return { resolverParams, args: args ? args.inputs : null }; } modifiersAreValidForField( diff --git a/src/tests/fixtures/arguments/MultipleParamsTypedAsTypeLiteral.ts b/src/tests/fixtures/arguments/MultipleParamsTypedAsTypeLiteral.ts new file mode 100644 index 00000000..042a5868 --- /dev/null +++ b/src/tests/fixtures/arguments/MultipleParamsTypedAsTypeLiteral.ts @@ -0,0 +1,7 @@ +/** @gqlType */ +export default class SomeType { + /** @gqlField */ + hello(args: { greeting: string }, alsoArgs: { farewell: string }): string { + return "Hello world!"; + } +} diff --git a/src/tests/fixtures/arguments/MultipleParamsTypedAsTypeLiteral.ts.expected b/src/tests/fixtures/arguments/MultipleParamsTypedAsTypeLiteral.ts.expected new file mode 100644 index 00000000..d4da1f11 --- /dev/null +++ b/src/tests/fixtures/arguments/MultipleParamsTypedAsTypeLiteral.ts.expected @@ -0,0 +1,23 @@ +----------------- +INPUT +----------------- +/** @gqlType */ +export default class SomeType { + /** @gqlField */ + hello(args: { greeting: string }, alsoArgs: { farewell: string }): string { + return "Hello world!"; + } +} + +----------------- +OUTPUT +----------------- +src/tests/fixtures/arguments/MultipleParamsTypedAsTypeLiteral.ts:4:37 - error: Unexpected multiple resolver parameters typed with an object literal. Grats assumes a resolver parameter typed with object literals describes the GraphQL arguments. Therefore only one such parameter is permitted. + +4 hello(args: { greeting: string }, alsoArgs: { farewell: string }): string { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + src/tests/fixtures/arguments/MultipleParamsTypedAsTypeLiteral.ts:4:9 + 4 hello(args: { greeting: string }, alsoArgs: { farewell: string }): string { + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + Previous type literal