Skip to content

Commit 0563912

Browse files
authored
enhancement(types): Enable strictNullChecks and throw errors in unhandled situations (#301)
1 parent 788acd3 commit 0563912

File tree

34 files changed

+264
-87
lines changed

34 files changed

+264
-87
lines changed

src/extension/extensionHandler.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,19 @@ export class ExtensionHandler<TMock> {
1818
extension: Extension<TMock, TMockedPropertyHandler>,
1919
): TMockedPropertyHandler;
2020
public get<TPropName extends keyof TMock, TMockedPropertyHandler>(
21-
extensionOrPropertyName: Function | TPropName,
21+
extensionOrPropertyName: Extension<TMock, TMockedPropertyHandler> | TPropName,
2222
maybePropertyHandler?: AsMockedPropertyHandler<TMockedPropertyHandler, TMock, TPropName>,
2323
): TMockedPropertyHandler {
2424
if (isFunction(extensionOrPropertyName)) {
2525
return extensionOrPropertyName(this._mock);
2626
}
2727

28+
if (!maybePropertyHandler) {
29+
throw new Error(
30+
`It looks like you are trying to get an extension for ${extensionOrPropertyName} without specifying the handler.`,
31+
);
32+
}
33+
2834
return maybePropertyHandler(this._mock[extensionOrPropertyName], this._mock, extensionOrPropertyName);
2935
}
3036
}

src/options/options.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@ export interface TsAutoMockOptions {
77
cacheBetweenTests: TsAutoMockCacheOptions;
88
}
99

10-
let options: TsAutoMockOptions = null;
10+
let tsAutoMockOptions: TsAutoMockOptions = defaultOptions;
1111

12-
export function SetTsAutoMockOptions(_options: TsAutoMockOptions): void {
13-
options = _options;
12+
export function SetTsAutoMockOptions(options: TsAutoMockOptions): void {
13+
tsAutoMockOptions = {
14+
...defaultOptions,
15+
...options,
16+
};
1417
}
1518

1619
export function GetOptionByKey<T extends keyof TsAutoMockOptions>(optionKey: T): TsAutoMockOptions[T] {
17-
if (options) {
18-
return options.hasOwnProperty(optionKey) ? options[optionKey] : defaultOptions[optionKey];
19-
}
20-
21-
return defaultOptions[optionKey];
20+
return tsAutoMockOptions[optionKey];
2221
}

src/transformer/base/base.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import {
99
isFunctionFromThisLibrary,
1010
} from '../matcher/matcher';
1111

12-
export type Visitor = (node: ts.CallExpression, declaration: ts.FunctionDeclaration) => ts.Node;
12+
export type Visitor = (node: ts.CallExpression & { typeArguments: ts.NodeArray<ts.TypeNode> }, declaration: ts.FunctionDeclaration) => ts.Node;
1313

1414
export function baseTransformer(visitor: Visitor, customFunctions: CustomFunction[]): (program: ts.Program, options?: TsAutoMockOptions) => ts.TransformerFactory<ts.SourceFile> {
1515
return (program: ts.Program, options?: TsAutoMockOptions): ts.TransformerFactory<ts.SourceFile> => {
16-
SetTsAutoMockOptions(options);
16+
if (options) {
17+
SetTsAutoMockOptions(options);
18+
}
19+
1720
SetTypeChecker(program.getTypeChecker());
1821
SetProgram(program);
1922

@@ -37,18 +40,35 @@ function visitNodeAndChildren(node: ts.Node, context: ts.TransformationContext,
3740
return ts.visitEachChild(visitNode(node, visitor, customFunctions), (childNode: ts.Node) => visitNodeAndChildren(childNode, context, visitor, customFunctions), context);
3841
}
3942

43+
function isObjectWithProperty<T extends {}, K extends keyof T>(
44+
obj: T,
45+
key: K,
46+
): obj is T & Required<{ [key in K]: T[K] }> {
47+
return typeof obj[key] !== 'undefined';
48+
}
49+
4050
function visitNode(node: ts.Node, visitor: Visitor, customFunctions: CustomFunction[]): ts.Node {
4151
if (!ts.isCallExpression(node)) {
4252
return node;
4353
}
4454

45-
const signature: ts.Signature = TypescriptHelper.getSignatureOfCallExpression(node);
55+
const signature: ts.Signature | undefined = TypescriptHelper.getSignatureOfCallExpression(node);
4656

47-
if (!isFunctionFromThisLibrary(signature, customFunctions)) {
57+
if (!signature || !isFunctionFromThisLibrary(signature, customFunctions)) {
4858
return node;
4959
}
5060

51-
const nodeToMock: ts.TypeNode = node.typeArguments[0];
61+
if (!isObjectWithProperty(node, 'typeArguments') || !node.typeArguments?.length) {
62+
const mockFunction: string = node.getText();
63+
64+
throw new Error(
65+
`It seems you've called \`${mockFunction}' without specifying a type argument to mock. ` +
66+
`Please refer to the documentation on how to use \`${mockFunction}': ` +
67+
'https://github.com/Typescript-TDD/ts-auto-mock#quick-overview'
68+
);
69+
}
70+
71+
const [nodeToMock]: ts.NodeArray<ts.TypeNode> = node.typeArguments;
5272

5373
MockDefiner.instance.setFileNameFromNode(nodeToMock);
5474
MockDefiner.instance.setTsAutoMockImportIdentifier();

src/transformer/descriptor/descriptor.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ export function GetDescriptor(node: ts.Node, scope: Scope): ts.Expression {
6363
case ts.SyntaxKind.Identifier:
6464
return GetIdentifierDescriptor(node as ts.Identifier, scope);
6565
case ts.SyntaxKind.ThisType:
66+
if (!scope.currentMockKey) {
67+
throw new Error(
68+
`The transformer attempted to look up a mock factory call for \`${node.getText()}' without a mock key.`,
69+
);
70+
}
71+
6672
return GetMockFactoryCallForThis(scope.currentMockKey);
6773
case ts.SyntaxKind.ImportSpecifier:
6874
return GetImportDescriptor(node as ts.ImportSpecifier, scope);

src/transformer/descriptor/helper/helper.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ export namespace TypescriptHelper {
1616

1717
export function GetDeclarationFromNode(node: ts.Node): ts.Declaration {
1818
const typeChecker: ts.TypeChecker = TypeChecker();
19-
const symbol: ts.Symbol = typeChecker.getSymbolAtLocation(node);
19+
const symbol: ts.Symbol | undefined = typeChecker.getSymbolAtLocation(node);
20+
21+
if (!symbol) {
22+
throw new Error(
23+
`The type checker failed to look up a symbol for \`${node.getText()}'. ` +
24+
'Perhaps, the checker was searching an outdated source.',
25+
);
26+
}
2027

2128
return GetDeclarationFromSymbol(symbol);
2229
}
@@ -56,11 +63,13 @@ export namespace TypescriptHelper {
5663
export function GetParameterOfNode(node: ts.EntityName): ts.NodeArray<ts.TypeParameterDeclaration> {
5764
const declaration: ts.Declaration = GetDeclarationFromNode(node);
5865

59-
return (declaration as Declaration).typeParameters;
66+
const { typeParameters = ts.createNodeArray([]) }: Declaration = (declaration as Declaration);
67+
68+
return typeParameters;
6069
}
6170

62-
export function GetTypeParameterOwnerMock(declaration: ts.Declaration): ts.Declaration {
63-
const typeDeclaration: ts.Declaration = ts.getTypeParameterOwner(declaration);
71+
export function GetTypeParameterOwnerMock(declaration: ts.Declaration): ts.Declaration | undefined {
72+
const typeDeclaration: ts.Declaration | undefined = ts.getTypeParameterOwner(declaration);
6473

6574
// THIS IS TO FIX A MISSING IMPLEMENTATION IN TYPESCRIPT https://github.com/microsoft/TypeScript/blob/ba5e86f1406f39e89d56d4b32fd6ff8de09a0bf3/src/compiler/utilities.ts#L5138
6675
if (typeDeclaration && (typeDeclaration as Declaration).typeParameters) {
@@ -79,7 +88,14 @@ export namespace TypescriptHelper {
7988
return propertyName.text;
8089
}
8190

82-
const symbol: ts.Symbol = TypeChecker().getSymbolAtLocation(propertyName);
91+
const symbol: ts.Symbol | undefined = TypeChecker().getSymbolAtLocation(propertyName);
92+
93+
if (!symbol) {
94+
throw new Error(
95+
`The type checker failed to look up symbol for property: ${propertyName.getText()}.`,
96+
);
97+
}
98+
8399
return symbol.escapedName.toString();
84100
}
85101

@@ -88,7 +104,7 @@ export namespace TypescriptHelper {
88104
}
89105

90106

91-
export function getSignatureOfCallExpression(node: ts.CallExpression): ts.Signature {
107+
export function getSignatureOfCallExpression(node: ts.CallExpression): ts.Signature | undefined {
92108
const typeChecker: ts.TypeChecker = TypeChecker();
93109

94110
return typeChecker.getResolvedSignature(node);
@@ -109,9 +125,9 @@ export namespace TypescriptHelper {
109125

110126
function GetDeclarationsForImport(node: ImportDeclaration): ts.Declaration[] {
111127
const typeChecker: ts.TypeChecker = TypeChecker();
112-
const symbol: ts.Symbol = typeChecker.getSymbolAtLocation(node.name);
113-
const originalSymbol: ts.Symbol = typeChecker.getAliasedSymbol(symbol);
128+
const symbol: ts.Symbol | undefined = node.name && typeChecker.getSymbolAtLocation(node.name);
129+
const originalSymbol: ts.Symbol | undefined = symbol && typeChecker.getAliasedSymbol(symbol);
114130

115-
return originalSymbol.declarations;
131+
return originalSymbol?.declarations ?? [];
116132
}
117133
}

src/transformer/descriptor/indexedAccess/indexedAccess.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@ export function GetIndexedAccessTypeDescriptor(node: ts.IndexedAccessTypeNode, s
3737
}
3838

3939
if (propertyName !== null) {
40-
const propertySymbol: ts.Symbol = typeChecker.getPropertyOfType(typeChecker.getTypeFromTypeNode(node.objectType), propertyName);
40+
const propertySymbol: ts.Symbol | undefined = typeChecker.getPropertyOfType(typeChecker.getTypeFromTypeNode(node.objectType), propertyName);
41+
42+
if (!propertySymbol) {
43+
throw new Error(
44+
`The type checker failed to look up symbol for property: \`${propertyName}' of \`${node.getText()}'.`,
45+
);
46+
}
47+
4148
return GetDescriptor(TypescriptHelper.GetDeclarationFromSymbol(propertySymbol), scope);
4249
}
4350

src/transformer/descriptor/mapped/mapped.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@ import { GetMockPropertiesFromDeclarations } from '../mock/mockProperties';
66
import { GetTypes } from '../type/type';
77

88
export function GetMappedDescriptor(node: ts.MappedTypeNode, scope: Scope): ts.Expression {
9-
const typeParameter: ts.TypeNode = node.typeParameter.constraint;
9+
const typeParameter: ts.TypeNode | undefined = node.typeParameter.constraint;
1010
const typeChecker: ts.TypeChecker = TypeChecker();
11-
const types: ts.Node[] = GetTypes(ts.createNodeArray([typeParameter]), scope);
11+
12+
const parameters: ts.TypeNode[] = [];
13+
if (typeParameter) {
14+
parameters.push(typeParameter);
15+
}
16+
17+
const types: ts.Node[] = GetTypes(ts.createNodeArray(parameters), scope);
1218

1319
const properties: ts.PropertyDeclaration[] = types.reduce((acc: ts.PropertyDeclaration[], possibleType: ts.Node) => {
1420
if (ts.isLiteralTypeNode(possibleType)) {

src/transformer/descriptor/method/bodyReturnType.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ export function GetReturnTypeFromBodyDescriptor(node: ts.ArrowFunction | ts.Func
88
}
99

1010
export function GetReturnNodeFromBody(node: ts.FunctionLikeDeclaration): ts.Node {
11-
let returnValue: ts.Node;
11+
let returnValue: ts.Node | undefined;
1212

13-
const functionBody: ts.ConciseBody = node.body;
13+
const functionBody: ts.ConciseBody | undefined = node.body;
1414

15-
if (ts.isBlock(functionBody)) {
15+
if (functionBody && ts.isBlock(functionBody)) {
1616
const returnStatement: ts.ReturnStatement = GetReturnStatement(functionBody);
1717

1818
if (returnStatement) {
@@ -24,6 +24,10 @@ export function GetReturnNodeFromBody(node: ts.FunctionLikeDeclaration): ts.Node
2424
returnValue = node.body;
2525
}
2626

27+
if (!returnValue) {
28+
throw new Error(`Failed to determine the return value of ${node.getText()}.`);
29+
}
30+
2731
return returnValue;
2832
}
2933

src/transformer/descriptor/method/functionType.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { GetMethodDescriptor } from './method';
77
export function GetFunctionTypeDescriptor(node: ts.FunctionTypeNode | ts.CallSignatureDeclaration | ts.ConstructSignatureDeclaration, scope: Scope): ts.Expression {
88
const property: ts.PropertyName = PropertySignatureCache.instance.get();
99

10+
if (!node.type) {
11+
throw new Error(`No type was declared for ${node.getText()}.`);
12+
}
13+
1014
const returnValue: ts.Expression = GetDescriptor(node.type, scope);
1115

1216
return GetMethodDescriptor(property, returnValue);

src/transformer/descriptor/method/methodDeclaration.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,11 @@ export function GetMethodDeclarationDescriptor(node: ts.MethodDeclaration | ts.F
88
const returnTypeNode: ts.Node = GetFunctionReturnType(node);
99
const returnType: ts.Expression = GetDescriptor(returnTypeNode, scope);
1010

11+
if (!node.name) {
12+
throw new Error(
13+
`The transformer couldn't determine the name of ${node.getText()}. Please report this incident.`,
14+
);
15+
}
16+
1117
return GetMethodDescriptor(node.name, returnType);
1218
}

0 commit comments

Comments
 (0)