Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(functionliteraltypes): add some compatibility for functions without type definition #889

Merged
merged 4 commits into from
Sep 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/transformer/descriptor/descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,25 @@ import { GetUndefinedDescriptor } from './undefined/undefined';
import { GetUnionDescriptor } from './union/union';
import { GetTypeOperatorDescriptor } from './typeOperator/typeOperator';
import { GetTupleDescriptor } from './tuple/tuple';
import { GetShorthandPropertyAssignmentDescriptor } from './shorthandPropertyAssignment/shorthandPropertyAssignment';
import { GetParameterDescriptor } from './parameter/parameter';
import { GetVariableDeclarationDescriptor } from './variable/variable';

export function GetDescriptor(node: ts.Node, scope: Scope): ts.Expression {
switch (node.kind) {
case core.ts.SyntaxKind.ShorthandPropertyAssignment:
return GetShorthandPropertyAssignmentDescriptor(
node as ts.ShorthandPropertyAssignment,
scope
);

case core.ts.SyntaxKind.VariableDeclaration:
return GetVariableDeclarationDescriptor(
node as ts.VariableDeclaration,
scope
);
case core.ts.SyntaxKind?.Parameter:
return GetParameterDescriptor(node as ts.ParameterDeclaration, scope);
case core.ts.SyntaxKind.TypeAliasDeclaration:
return GetTypeAliasDescriptor(node as ts.TypeAliasDeclaration, scope);
case core.ts.SyntaxKind.TypeReference:
Expand Down
19 changes: 19 additions & 0 deletions src/transformer/descriptor/parameter/parameter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as ts from 'typescript';
import { Scope } from '../../scope/scope';
import { GetNullDescriptor } from '../null/null';
import { GetDescriptor } from '../descriptor';

export const GetParameterDescriptor: (
node: ts.ParameterDeclaration,
scope: Scope
) => ts.Expression = (node: ts.ParameterDeclaration, scope: Scope) => {
if (node.type) {
return GetDescriptor(node.type, scope);
}

if (node.initializer) {
return GetDescriptor(node.initializer, scope);
}

return GetNullDescriptor();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as ts from 'typescript';
import { Scope } from '../../scope/scope';
import { core } from '../../core/core';
import { TypescriptHelper } from '../helper/helper';
import { GetDescriptor } from '../descriptor';

export const GetShorthandPropertyAssignmentDescriptor: (
node: ts.ShorthandPropertyAssignment,
scope: Scope
) => ts.Expression = (node: ts.ShorthandPropertyAssignment, scope) => {
const typeChecker: ts.TypeChecker = core.typeChecker;

const symbol: ts.Symbol | undefined =
typeChecker.getShorthandAssignmentValueSymbol(node);

if (!symbol) {
throw new Error(
`The type checker failed to look up a symbol for \`${node.getText()}'.
Perhaps, the checker was searching an outdated source.`
);
}

const declaration: ts.Declaration =
TypescriptHelper.GetDeclarationFromSymbol(symbol);

return GetDescriptor(declaration, scope);
};
52 changes: 52 additions & 0 deletions src/transformer/descriptor/variable/variable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as ts from 'typescript';
import { Scope } from '../../scope/scope';
import { core } from '../../core/core';
import { GetMockPropertiesFromSymbol } from '../mock/mockProperties';
import { GetNullDescriptor } from '../null/null';
import { GetDescriptor } from '../descriptor';

export const GetVariableDeclarationDescriptor: (
node: ts.VariableDeclaration,
scope: Scope
) => ts.Expression = (node: ts.VariableDeclaration, scope: Scope) => {
const typeChecker: ts.TypeChecker = core.typeChecker;
const coreTs: typeof core.ts = core.ts;
if (node.type) {
return GetDescriptor(node.type, scope);
}

const symbol: ts.Symbol | undefined = typeChecker.getSymbolAtLocation(
node.name
);

if (!symbol) {
throw new Error(
`The type checker failed to look up a symbol for \`${node.getText()}'.
Perhaps, the checker was searching an outdated source.`
);
}

const type: ts.Type = typeChecker.getTypeOfSymbolAtLocation(symbol, node);
const typeToNode: ts.TypeNode | undefined = typeChecker.typeToTypeNode(
type,
undefined,
undefined
);

if (!typeToNode) {
throw new Error(
`The type checker failed to look up a node for \`${node.getText()}'.
Perhaps, the checker was searching an outdated source.`
);
}

if (coreTs.isTypeLiteralNode(typeToNode)) {
const properties: ts.Symbol[] = typeChecker.getPropertiesOfType(type);
return GetMockPropertiesFromSymbol(properties, [], scope);
}

if (coreTs.isLiteralTypeNode(typeToNode)) {
return GetDescriptor(typeToNode.literal, scope);
}
return GetNullDescriptor();
};
192 changes: 192 additions & 0 deletions test/transformer/descriptor/functions/functionsWithoutTypes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { createMock } from 'ts-auto-mock';
import { anImportedObject } from '../utils/object/object';

describe('functions without types defined', () => {
it('should infer basic boolean object literal types', () => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function functionToMock() {
// eslint-disable-next-line @typescript-eslint/typedef
const primitiveValue = false;

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function whateverFunction() {
return true;
}

return { primitiveValue, whateverFunction };
}

const type: typeof functionToMock = createMock<typeof functionToMock>();
expect(type().primitiveValue).toBe(false);
expect(type().whateverFunction()).toBe(true);
});

it('should infer basic string object literal types', () => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function functionToMock() {
// eslint-disable-next-line @typescript-eslint/typedef
const primitiveValue = 'Hello world';

return { primitiveValue };
}

const type: typeof functionToMock = createMock<typeof functionToMock>();
expect(type().primitiveValue).toBe('Hello world');
});

it('should infer basic object literal types', () => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function functionToMock() {
// eslint-disable-next-line @typescript-eslint/typedef
const primitiveValue = {
test: 'hello',
};

return { primitiveValue };
}

const type: typeof functionToMock = createMock<typeof functionToMock>();
expect(type().primitiveValue).toEqual({
test: 'hello',
});
});

it('should use the default behaviour for variables with a type defined', () => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function functionToMock() {
const primitiveValue: boolean = true;

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function whateverFunction() {
return true;
}

return { primitiveValue, whateverFunction };
}

const type: typeof functionToMock = createMock<typeof functionToMock>();
expect(type().primitiveValue).toBe(false);
});

it('should use the default behaviour for internal function declarations with a type defined', () => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function functionToMock() {
const primitiveValue: boolean = true;

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function whateverFunction(): boolean {
return true;
}

return { primitiveValue, whateverFunction };
}

const type: typeof functionToMock = createMock<typeof functionToMock>();
expect(type().whateverFunction()).toBe(false);
});

it('should use the default behaviour for internal function declarations with a type defined', () => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function functionToMock() {
const primitiveValue: boolean = true;

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function whateverFunction(): boolean {
return true;
}

return { primitiveValue, whateverFunction };
}

const type: typeof functionToMock = createMock<typeof functionToMock>();
expect(type().whateverFunction()).toBe(false);
});

it('should infer object literal return types', () => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function functionToMock() {
return { a: 'hello world', b: 123 };
}

const type: typeof functionToMock = createMock<typeof functionToMock>();
expect(type()).toEqual({
a: 'hello world',
b: 123,
});
});

it('should infer variables outside the function', () => {
// eslint-disable-next-line @typescript-eslint/typedef
const anObject = { a: 'hello world', b: 123 };
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function functionToMock() {
return anObject;
}

const type: typeof functionToMock = createMock<typeof functionToMock>();
expect(type()).toEqual({
a: 'hello world',
b: 123,
});
});

it('should infer variables from a different file', () => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function functionToMock() {
return anImportedObject;
}

const type: typeof functionToMock = createMock<typeof functionToMock>();
expect(type()).toEqual({
a: 'hello world',
b: 123,
});
});

it('should infer a spread object', () => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function functionToMock() {
return {
...anImportedObject,
};
}

const type: typeof functionToMock = createMock<typeof functionToMock>();
expect(type()).toEqual({
a: 'hello world',
b: 123,
});
});

it('should set null when a parameter has not type', () => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function functionToMock(param) {
return {
param,
};
}

const type: typeof functionToMock = createMock<typeof functionToMock>();
expect(
type({
test: 'hello',
})
).toEqual({
param: null,
});
});

it('should be able to infer parameter types', () => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function functionToMock(param: string) {
return {
param,
};
}

const type: typeof functionToMock = createMock<typeof functionToMock>();
expect(type('hello')).toEqual({
param: '',
});
});
});
2 changes: 2 additions & 0 deletions test/transformer/descriptor/utils/object/object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line @typescript-eslint/typedef
export const anImportedObject = { a: 'hello world', b: 123 };