From 852b3dc5da113546bc090d99f9250104f526f5af Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Mon, 19 Sep 2022 19:58:47 +0200 Subject: [PATCH] fix(jsii): unable to return Promise (#3752) The void-check only accounted for the literal `void` type, but failed to account for the `Promise` case. This is now fixed. Fixes #51 --- By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license]. [Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0 --- packages/jsii/lib/assembler.ts | 23 ++++++++++- .../test/__snapshots__/negatives.test.js.snap | 8 ++-- packages/jsii/test/promise.test.ts | 40 +++++++++++++++++++ 3 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 packages/jsii/test/promise.test.ts diff --git a/packages/jsii/lib/assembler.ts b/packages/jsii/lib/assembler.ts index 46816f1a36..94342d2ed0 100644 --- a/packages/jsii/lib/assembler.ts +++ b/packages/jsii/lib/assembler.ts @@ -2222,7 +2222,11 @@ export class Assembler implements Emitter { protected: _isProtected(symbol) || undefined, returns: _isVoid(returnType) ? undefined - : this._optionalValue(returnType, declaration.name, 'return type'), + : this._optionalValue( + returnType, + declaration.type ?? declaration.name, + 'return type', + ), async: _isPromise(returnType) || undefined, static: _isStatic(symbol) || undefined, locationInModule: this.declarationLocation(declaration), @@ -2435,7 +2439,7 @@ export class Assembler implements Emitter { { ...this._optionalValue( this._typeChecker.getTypeAtLocation(paramDeclaration), - paramDeclaration.name, + paramDeclaration.type ?? paramDeclaration.name, 'parameter type', ), name: paramSymbol.name, @@ -2969,7 +2973,22 @@ function _isStatic(symbol: ts.Symbol): boolean { ); } +/** + * Determines whether a given type is void or Promise. + * + * @param type the tested type + * + * @returns `true` if the type is void or Promise + */ function _isVoid(type: ts.Type): boolean { + if (_isPromise(type)) { + const typeRef = type as ts.TypeReference; + return ( + typeRef.typeArguments != null && + typeRef.typeArguments.length === 1 && + _isVoid(typeRef.typeArguments[0]) + ); + } return (type.flags & ts.TypeFlags.Void) !== 0; } diff --git a/packages/jsii/test/__snapshots__/negatives.test.js.snap b/packages/jsii/test/__snapshots__/negatives.test.js.snap index 64b86cf3fc..c0036ba4fa 100644 --- a/packages/jsii/test/__snapshots__/negatives.test.js.snap +++ b/packages/jsii/test/__snapshots__/negatives.test.js.snap @@ -624,19 +624,19 @@ neg.omit.2.ts:7:32 - error JSII3004: Illegal implements clause for an exported A `; exports[`omit.3 1`] = ` -neg.omit.3.ts:8:3 - error JSII1003: Only string-indexed map types are supported +neg.omit.3.ts:8:10 - error JSII1003: Only string-indexed map types are supported 8 bar(): Omit; - ~~~ + ~~~~~~~~~~~~~~~~~~~ `; exports[`omit.4 1`] = ` -neg.omit.4.ts:8:7 - error JSII1003: Only string-indexed map types are supported +neg.omit.4.ts:8:13 - error JSII1003: Only string-indexed map types are supported 8 bar(opts: Omit): void; - ~~~~ + ~~~~~~~~~~~~~~~~~~~ `; diff --git a/packages/jsii/test/promise.test.ts b/packages/jsii/test/promise.test.ts new file mode 100644 index 0000000000..bdb6255c8c --- /dev/null +++ b/packages/jsii/test/promise.test.ts @@ -0,0 +1,40 @@ +import { sourceToAssemblyHelper } from '../lib'; + +// ---------------------------------------------------------------------- +test('Promise is a valid return type', () => { + const assembly = sourceToAssemblyHelper(` + export class PromiseMaker { + public static staticPromise(): Promise { + return Promise.resolve(); + } + + public instancePromise(): Promise { + return Promise.resolve(); + } + + private constructor() {} + } + `); + + expect(assembly.types!['testpkg.PromiseMaker']).toEqual({ + assembly: 'testpkg', + fqn: 'testpkg.PromiseMaker', + kind: 'class', + methods: [ + { + async: true, + locationInModule: { filename: 'index.ts', line: 3 }, + name: 'staticPromise', + static: true, + }, + { + async: true, + locationInModule: { filename: 'index.ts', line: 7 }, + name: 'instancePromise', + }, + ], + locationInModule: { filename: 'index.ts', line: 2 }, + name: 'PromiseMaker', + symbolId: 'index:PromiseMaker', + }); +});