From a3e9841cca5790f5a2586c409bc046b9612d43d5 Mon Sep 17 00:00:00 2001 From: Pmyl Date: Mon, 16 Dec 2019 18:10:17 +0000 Subject: [PATCH] feat(indexedAccess): add indexed access support for mocks (#119) * feat(indexedAccess): add indexed access support for mocks * update documentation --- docs/DETAILS.md | 11 ++++ docs/NOT_SUPPORTED.md | 13 ---- src/transformer/descriptor/descriptor.ts | 3 + .../descriptor/indexedAccess/indexedAccess.ts | 45 ++++++++++++++ .../indexedAccess/indexedAccess.test.ts | 62 +++++++++++++++++++ .../descriptor/utils/types/literals.ts | 1 + 6 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 src/transformer/descriptor/indexedAccess/indexedAccess.ts create mode 100644 test/transformer/descriptor/indexedAccess/indexedAccess.test.ts create mode 100644 test/transformer/descriptor/utils/types/literals.ts diff --git a/docs/DETAILS.md b/docs/DETAILS.md index f92fc59fe..f1f303757 100644 --- a/docs/DETAILS.md +++ b/docs/DETAILS.md @@ -271,3 +271,14 @@ function AFunction(): number; const mockFunction = createMock(); mockFunction() // 0 ``` + +## IndexedAccessType +```ts +class Class { + a: string +} + +type KeyOf = {[key in keyof Class]: Class[key]}; +const mock = createMock(); +mock.a // '' +``` diff --git a/docs/NOT_SUPPORTED.md b/docs/NOT_SUPPORTED.md index c4a365f80..4075f508d 100644 --- a/docs/NOT_SUPPORTED.md +++ b/docs/NOT_SUPPORTED.md @@ -1,18 +1,5 @@ # Not supported types -## IndexedAccessType - -[bug](https://github.com/uittorio/ts-auto-mock/issues/3) -```ts -class Class { - a: string -} - -type KeyOf = {[key in keyof Class]: Class[key]}; -const mock = createMock(); -mock.a // will be null -``` - ## ConditionalType ```ts diff --git a/src/transformer/descriptor/descriptor.ts b/src/transformer/descriptor/descriptor.ts index 95a18812c..ae663bfc3 100644 --- a/src/transformer/descriptor/descriptor.ts +++ b/src/transformer/descriptor/descriptor.ts @@ -13,6 +13,7 @@ import { GetExpressionWithTypeArgumentsDescriptor } from './expression/expressio import { GetIdentifierDescriptor } from './identifier/identifier'; import { GetImportDescriptor } from './import/import'; import { GetImportEqualsDescriptor } from './import/importEquals'; +import { GetIndexedAccessTypeDescriptor } from './indexedAccess/indexedAccess'; import { GetInterfaceDeclarationDescriptor } from './interface/interfaceDeclaration'; import { GetIntersectionDescriptor } from './intersection/intersection'; import { GetLiteralDescriptor } from './literal/literal'; @@ -109,6 +110,8 @@ export function GetDescriptor(node: ts.Node, scope: Scope): ts.Expression { return GetLiteralDescriptor(node as ts.LiteralTypeNode, scope); case ts.SyntaxKind.ObjectLiteralExpression: return GetObjectLiteralDescriptor(node as ts.ObjectLiteralExpression, scope); + case ts.SyntaxKind.IndexedAccessType: + return GetIndexedAccessTypeDescriptor(node as ts.IndexedAccessTypeNode, scope); case ts.SyntaxKind.BooleanKeyword: return GetBooleanDescriptor(); case ts.SyntaxKind.ObjectKeyword: diff --git a/src/transformer/descriptor/indexedAccess/indexedAccess.ts b/src/transformer/descriptor/indexedAccess/indexedAccess.ts new file mode 100644 index 000000000..ea1096b13 --- /dev/null +++ b/src/transformer/descriptor/indexedAccess/indexedAccess.ts @@ -0,0 +1,45 @@ +import * as ts from 'typescript'; +import { TransformerLogger } from '../../logger/transformerLogger'; +import { Scope } from '../../scope/scope'; +import { TypeChecker } from '../../typeChecker/typeChecker'; +import { GetDescriptor } from '../descriptor'; +import { TypescriptHelper } from '../helper/helper'; +import { GetNullDescriptor } from '../null/null'; +import { PropertySignatureCache } from '../property/cache'; + +export function GetIndexedAccessTypeDescriptor(node: ts.IndexedAccessTypeNode, scope: Scope): ts.Expression { + const typeChecker: ts.TypeChecker = TypeChecker(); + let propertyName: string | null = null; + + switch (node.indexType.kind) { + case ts.SyntaxKind.TypeReference: + const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode((node.indexType as ts.TypeReferenceNode).typeName); + + switch (declaration.kind) { + case ts.SyntaxKind.TypeParameter: + const propertyNameIdentifier: ts.PropertyName = PropertySignatureCache.instance.get(); + propertyName = (propertyNameIdentifier as ts.Identifier).escapedText as string; + break; + case ts.SyntaxKind.TypeAliasDeclaration: + propertyName = (((declaration as ts.TypeAliasDeclaration).type as ts.LiteralTypeNode).literal as ts.StringLiteral).text; + break; + default: + TransformerLogger().typeNotSupported('IndexedAccess of TypeReference of ' + ts.SyntaxKind[declaration.kind]); + break; + } + break; + case ts.SyntaxKind.LiteralType: + propertyName = ((node.indexType as ts.LiteralTypeNode).literal as ts.StringLiteral).text; + break; + default: + TransformerLogger().typeNotSupported('IndexedAccess of ' + ts.SyntaxKind[node.indexType.kind]); + break; + } + + if (propertyName !== null) { + const propertySymbol: ts.Symbol = typeChecker.getPropertyOfType(typeChecker.getTypeFromTypeNode(node.objectType), propertyName); + return GetDescriptor(TypescriptHelper.GetDeclarationFromSymbol(propertySymbol), scope); + } + + return GetNullDescriptor(); +} diff --git a/test/transformer/descriptor/indexedAccess/indexedAccess.test.ts b/test/transformer/descriptor/indexedAccess/indexedAccess.test.ts new file mode 100644 index 000000000..53bf878b8 --- /dev/null +++ b/test/transformer/descriptor/indexedAccess/indexedAccess.test.ts @@ -0,0 +1,62 @@ +import { createMock } from 'ts-auto-mock'; +import { Interface } from '../utils/interfaces/basic'; +import { LiteralA } from '../utils/types/literals'; + +describe('indexedAccess', () => { + interface A { + a: string; + b: number; + } + + it('should work with key in keyof', () => { + type AType = {[key in keyof A]: A[key]}; + const mock: AType = createMock(); + expect(mock.a).toEqual(''); + expect(mock.b).toEqual(0); + }); + + it('should work with key in literal', () => { + type AType = {[key in 'a']: A[key]}; + const mock: AType = createMock(); + expect(mock.a).toEqual(''); + expect((mock as any).b).toBeUndefined(); + }); + + it('should work with key in keyof with literal index', () => { + type AType = {[key in keyof A]: A['b']}; + const mock: AType = createMock(); + expect(mock.a).toEqual(0); + expect(mock.b).toEqual(0); + }); + + it('should work with key in keyof with imported literal index', () => { + type AType = {[key in keyof A]: A[LiteralA]}; + const mock: AType = createMock(); + expect(mock.a).toEqual(''); + expect(mock.b).toEqual(''); + }); + + it('should work with key in keyof with imported interface and literal index', () => { + type AType = {[key in keyof Interface]: Interface[LiteralA]}; + const mock: AType = createMock(); + expect(mock.a).toEqual(''); + expect(mock.b).toEqual(''); + }); + + it('should work with key in keyof with interface having complex properties', () => { + interface InterfaceWithComplex { + a: A; + b: string; + c: InterfaceWithComplex; + } + + type InterfaceType = {[key in keyof InterfaceWithComplex]: InterfaceWithComplex[key]}; + const mock: InterfaceType = createMock(); + expect(mock.a.a).toEqual(''); + expect(mock.a.b).toEqual(0); + expect(mock.b).toEqual(''); + expect(mock.c.a.a).toEqual(''); + expect(mock.c.a.b).toEqual(0); + expect(mock.c.b).toEqual(''); + }); +}); diff --git a/test/transformer/descriptor/utils/types/literals.ts b/test/transformer/descriptor/utils/types/literals.ts new file mode 100644 index 000000000..fee4dbbec --- /dev/null +++ b/test/transformer/descriptor/utils/types/literals.ts @@ -0,0 +1 @@ +export type LiteralA = 'a';