Skip to content

Commit

Permalink
feat(functionliteraltypes): add some compatibility for functions with…
Browse files Browse the repository at this point in the history
…out type definitions! (#889)
  • Loading branch information
uittorio authored Sep 2, 2021
1 parent 6f3a0c5 commit a21e012
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 0 deletions.
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 };

0 comments on commit a21e012

Please sign in to comment.