Skip to content

Commit

Permalink
feat(genericDefault): add support for default generics on declaration…
Browse files Browse the repository at this point in the history
… and extensions (#126)

* add support for default generics on declaration and extensions

* reduce amount of nested if statements

* reduce amount of if statement in generic extensions
  • Loading branch information
uittorio authored Dec 31, 2019
1 parent 0feb05a commit a9df32a
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/transformer/descriptor/helper/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export namespace TypescriptHelper {
const typeDeclaration: ts.Declaration = ts.getTypeParameterOwner(declaration);

// THIS IS TO FIX A MISSING IMPLEMENTATION IN TYPESCRIPT https://github.com/microsoft/TypeScript/blob/ba5e86f1406f39e89d56d4b32fd6ff8de09a0bf3/src/compiler/utilities.ts#L5138
if ((typeDeclaration as Declaration).typeParameters) {
if (typeDeclaration && (typeDeclaration as Declaration).typeParameters) {
return typeDeclaration;
}

Expand Down
63 changes: 41 additions & 22 deletions src/transformer/genericDeclaration/genericDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import { GenericParameter } from './genericParameter';
export function GenericDeclaration(scope: Scope): IGenericDeclaration {
const generics: GenericParameter[] = [];

function isGenericProvided(node: ts.TypeReferenceNode | ts.ExpressionWithTypeArguments, index: number): boolean {
return !!node.typeArguments && !!node.typeArguments[index];
}

function getGenericTypeNode(node: ts.TypeReferenceNode | ts.ExpressionWithTypeArguments, nodeDeclaration: ts.TypeParameterDeclaration, index: number): ts.TypeNode {
return isGenericProvided(node, index) ? node.typeArguments[index] : nodeDeclaration.default;
}

function addGenericParameterToExisting(
ownerParameterDeclaration: ts.TypeParameterDeclaration,
typeParameterDeclaration: ts.TypeParameterDeclaration,
Expand Down Expand Up @@ -42,11 +50,21 @@ export function GenericDeclaration(scope: Scope): IGenericDeclaration {
addFromTypeReferenceNode(node: ts.TypeReferenceNode, declarationKey: string): void {
const typeParameterDeclarations: ts.NodeArray<ts.TypeParameterDeclaration> = TypescriptHelper.GetParameterOfNode(node.typeName);

node.typeArguments.forEach((argument: ts.TypeNode, index: number) => {
const genericDescriptor: ts.Expression = GetDescriptor(argument, scope);
const genericParameter: GenericParameter = createGenericParameter(declarationKey, typeParameterDeclarations[index], genericDescriptor);
if (!typeParameterDeclarations) {
return;
}

typeParameterDeclarations.forEach((declaration: ts.TypeParameterDeclaration, index: number) => {
const genericTypeNode: ts.TypeNode = getGenericTypeNode(node, declaration, index);

const genericParameter: GenericParameter = createGenericParameter(
declarationKey,
typeParameterDeclarations[index],
GetDescriptor(genericTypeNode, scope));

generics.push(genericParameter);
});

},
addFromDeclarationExtension(
declarationKey: string,
Expand All @@ -55,36 +73,37 @@ export function GenericDeclaration(scope: Scope): IGenericDeclaration {
extension: ts.ExpressionWithTypeArguments): void {
const extensionDeclarationTypeParameters: ts.NodeArray<ts.TypeParameterDeclaration> = extensionDeclaration.typeParameters;

extension.typeArguments.forEach((typeArgument: ts.TypeNode, index: number) => {
if (ts.isTypeReferenceNode(typeArgument)) {
const typeParameterDeclaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode(typeArgument.typeName);
if (!extensionDeclarationTypeParameters) {
return;
}

extensionDeclarationTypeParameters.reduce((acc: GenericParameter[], declaration: ts.TypeParameterDeclaration, index: number) => {
const genericTypeNode: ts.TypeNode = getGenericTypeNode(extension, declaration, index);

if (ts.isTypeReferenceNode(genericTypeNode)) {
const typeParameterDeclaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode(genericTypeNode.typeName);
if (ts.isTypeParameterDeclaration(typeParameterDeclaration)) {
addGenericParameterToExisting(
extensionDeclarationTypeParameters[index],
typeParameterDeclaration,
declarationKey,
extensionDeclarationKey,
);
} else {
const genericParameter: GenericParameter = createGenericParameter(
extensionDeclarationKey,
extensionDeclarationTypeParameters[index],
GetDescriptor(typeArgument, scope),
);

generics.push(genericParameter);
return acc;
}
}

} else {
const genericParameter: GenericParameter = createGenericParameter(
extensionDeclarationKey,
extensionDeclarationTypeParameters[index],
GetDescriptor(typeArgument, scope),
);
const genericParameter: GenericParameter = createGenericParameter(
extensionDeclarationKey,
extensionDeclarationTypeParameters[index],
GetDescriptor(genericTypeNode, scope),
);

generics.push(genericParameter);
}
});
acc.push(genericParameter);

return acc;
}, generics);
},
getExpressionForAllGenerics(): ts.ObjectLiteralExpression[] {
return generics.map((s: GenericParameter) => {
Expand Down
26 changes: 10 additions & 16 deletions src/transformer/mockFactoryCall/mockFactoryCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ export function GetMockFactoryCallIntersection(intersection: ts.IntersectionType
const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode((type as ts.TypeReferenceNode).typeName);
const declarationKey: string = MockDefiner.instance.getDeclarationKeyMap(declaration);

if (type.typeArguments) {
genericDeclaration.addFromTypeReferenceNode(type, declarationKey);
}
genericDeclaration.addFromTypeReferenceNode(type, declarationKey);

addFromDeclarationExtensions(declaration as GenericDeclarationSupported, declarationKey, genericDeclaration);

Expand Down Expand Up @@ -74,9 +72,7 @@ function getDeclarationMockFactoryCall(declaration: ts.Declaration, typeReferenc
const mockFactoryCall: ts.Expression = MockDefiner.instance.getMockFactoryByKey(declarationKey);
const genericDeclaration: IGenericDeclaration = GenericDeclaration(scope);

if (typeReferenceNode.typeArguments) {
genericDeclaration.addFromTypeReferenceNode(typeReferenceNode, declarationKey);
}
genericDeclaration.addFromTypeReferenceNode(typeReferenceNode, declarationKey);

addFromDeclarationExtensions(declaration as GenericDeclarationSupported, declarationKey, genericDeclaration);

Expand All @@ -93,19 +89,17 @@ function addFromDeclarationExtensions(declaration: GenericDeclarationSupported,
if (declaration.heritageClauses) {
declaration.heritageClauses.forEach((clause: ts.HeritageClause) => {
clause.types.forEach((extension: ts.ExpressionWithTypeArguments) => {
if (extension.typeArguments) {
const extensionDeclaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode(extension.expression);
const extensionDeclaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode(extension.expression);

const extensionDeclarationKey: string = MockDefiner.instance.getDeclarationKeyMap(extensionDeclaration);
const extensionDeclarationKey: string = MockDefiner.instance.getDeclarationKeyMap(extensionDeclaration);

genericDeclaration.addFromDeclarationExtension(
declarationKey,
extensionDeclaration as GenericDeclarationSupported,
extensionDeclarationKey,
extension);
genericDeclaration.addFromDeclarationExtension(
declarationKey,
extensionDeclaration as GenericDeclarationSupported,
extensionDeclarationKey,
extension);

addFromDeclarationExtensions(extensionDeclaration as GenericDeclarationSupported, extensionDeclarationKey, genericDeclaration);
}
addFromDeclarationExtensions(extensionDeclaration as GenericDeclarationSupported, extensionDeclarationKey, genericDeclaration);
});
});
}
Expand Down
49 changes: 49 additions & 0 deletions test/transformer/descriptor/generic/default.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { createMock } from 'ts-auto-mock';

describe('generic default', () => {
it('should assign the default value when not provided', () => {
interface B<P> {
prop: P;
}

interface A<P = { a: string }> extends B<P> {
}

const mock: A = createMock<A>();

expect(mock.prop.a).toEqual('');
});

it('should assign the default value of the second argument when not provided', () => {
interface B<P, S> {
prop: P;
prop2: S;
}

interface A<P = { a: string }, S = number> extends B<P, S> {
}

const mock: A<{ a: number }> = createMock<A<{ a: number }>>();

expect(mock.prop.a).toEqual(0);
expect(mock.prop2).toEqual(0);
});

it('should assign the default value for extension with default value', () => {
interface C<T> {
cProp: T;
}

interface B<P = { a: string }> extends C<P> {
bProp: P;
}

interface A extends B {
}

const mock: A = createMock<A>();

expect(mock.cProp.a).toEqual('');
expect(mock.bProp.a).toEqual('');
});
});

0 comments on commit a9df32a

Please sign in to comment.