Skip to content

Commit

Permalink
feat(logs): enhance unsupported type logs (#513)
Browse files Browse the repository at this point in the history
* feat(logs): enhance unsupported type logs by adding source file where the mock is created and source file where the type is failing

* feat(logs): use template literals instead of string composition
  • Loading branch information
uittorio committed Sep 9, 2020
1 parent 180eb31 commit 5dd6711
Show file tree
Hide file tree
Showing 16 changed files with 207 additions and 34 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"build:modules:debug": "cross-env DEBUG=true webpack --config config/modules/webpack.js",
"build:transformer:definitelyTyped": "webpack --config config/modules/definitelyTypedTransformer/webpack.functions.js && webpack --config config/modules/definitelyTypedTransformer/webpack.js",
"build:playground": "ttsc --project ./test/playground/tsconfig.build.json",
"test": "npm run test:transformer && npm run test:noTransformer && npm run test:framework:context && npm run test:framework && npm run test:frameworkDeprecated && npm run test:registerMock && npm run test:features && npm run test:unit",
"test": "npm run test:transformer && npm run test:noTransformer && npm run test:framework:context && npm run test:framework && npm run test:frameworkDeprecated && npm run test:registerMock && npm run test:features && npm run test:logs && npm run test:unit",
"test:unit": "cross-env JASMINE_CONFIG=./test/unit/jasmine.json TSCONFIG=./test/tsconfig/tsconfig.json npm run test:common",
"test:transformer": "cross-env JASMINE_CONFIG=./test/transformer/jasmine.json TSCONFIG=./test/transformer/tsconfig.json npm run test:common",
"test:noTransformer": "cross-env JASMINE_CONFIG=./test/noTransformer/jasmine.json TSCONFIG=./test/tsconfig/tsconfig.json npm run test:common",
Expand All @@ -20,6 +20,7 @@
"test:frameworkDeprecated": "cross-env JASMINE_CONFIG=./test/frameworkContext/jasmineDeprecated.json TSCONFIG=./test/frameworkContext/tsconfig.json npm run test:common",
"test:framework": "cross-env JASMINE_CONFIG=./test/framework/jasmine.json TSCONFIG=./test/framework/tsconfig.json npm run test:common",
"test:features": "cross-env JASMINE_CONFIG=./test/features/jasmine.json TSCONFIG=./test/features/tsconfig.json npm run test:common",
"test:logs": "cross-env JASMINE_CONFIG=./test/logs/jasmine.json TSCONFIG=./test/logs/tsconfig.json npm run test:common",
"test:playground": "cross-env JASMINE_CONFIG=./test/playground/jasmine.json TSCONFIG=./test/playground/tsconfig.json npm run test:common",
"test:common": "ts-node --files -r tsconfig-paths/register --compiler ttypescript --project $TSCONFIG node_modules/jasmine/bin/jasmine --config=$JASMINE_CONFIG",
"dist:collect": "cp -r package.json package-lock.json README.md dist",
Expand Down
42 changes: 23 additions & 19 deletions src/logger/fileLogger.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import { Format, TransformableInfo } from 'logform';
import { TransformableInfo } from 'logform';
import * as winston from 'winston';
import { FileTransportInstance } from 'winston/lib/winston/transports';

let winstonFileLogger: FileTransportInstance;

export function FileLogger(): FileTransportInstance {
const customFormat: Format = winston.format.printf(
(info: TransformableInfo) =>
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${info.timestamp} - ${info.level}: ${info.message}`
return (
winstonFileLogger ||
(winstonFileLogger = new winston.transports.File({
filename: 'tsAutoMock.log',
options: { flags: 'w' },
level: 'error',
format: winston.format.combine(
winston.format((info: TransformableInfo) => {
info.level = info.level.toUpperCase();
return info;
})(),
winston.format.simple(),
winston.format.timestamp(),
winston.format.printf(
(info: TransformableInfo) =>
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${info.timestamp} - ${info.level}: ${info.message}`
)
),
}))
);

return new winston.transports.File({
filename: 'tsAutoMock.log',
level: 'error',
format: winston.format.combine(
winston.format((info: TransformableInfo) => {
info.level = info.level.toUpperCase();
return info;
})(),
winston.format.simple(),
winston.format.timestamp(),
customFormat
),
});
}
6 changes: 3 additions & 3 deletions src/transformer/base/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ function visitNode(
const mockFunction: string = node.getText();

throw new Error(
`It seems you've called \`${mockFunction}' without specifying a type argument to mock. ` +
`Please refer to the documentation on how to use \`${mockFunction}': ` +
'https://github.com/Typescript-TDD/ts-auto-mock#quick-overview'
`It seems you've called \`${mockFunction}' without specifying a type argument to mock.
Please refer to the documentation on how to use \`${mockFunction}':
https://github.com/Typescript-TDD/ts-auto-mock#quick-overview`
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/transformer/descriptor/descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export function GetDescriptor(node: ts.Node, scope: Scope): ts.Expression {
case ts.SyntaxKind.CallExpression:
return GetCallExpressionDescriptor(node as ts.CallExpression, scope);
default:
TransformerLogger().typeNotSupported(ts.SyntaxKind[node.kind]);
TransformerLogger().typeNotSupported(ts.SyntaxKind[node.kind], node);
return GetNullDescriptor();
}
}
4 changes: 2 additions & 2 deletions src/transformer/descriptor/helper/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export namespace TypescriptHelper {

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.'
`The type checker failed to look up a symbol for \`${node.getText()}'.
Perhaps, the checker was searching an outdated source.`
);
}

Expand Down
7 changes: 5 additions & 2 deletions src/transformer/descriptor/indexedAccess/indexedAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ export function GetIndexedAccessTypeDescriptor(
break;
default:
TransformerLogger().typeNotSupported(
'IndexedAccess of TypeReference of ' +
`IndexedAccess of TypeReference of ${
ts.SyntaxKind[declaration.kind]
}`,
declaration
);
break;
}
Expand All @@ -44,7 +46,8 @@ export function GetIndexedAccessTypeDescriptor(
break;
default:
TransformerLogger().typeNotSupported(
'IndexedAccess of ' + ts.SyntaxKind[node.indexType.kind]
`IndexedAccess of ${ts.SyntaxKind[node.indexType.kind]}`,
node.indexType
);
break;
}
Expand Down
3 changes: 2 additions & 1 deletion src/transformer/descriptor/typeQuery/typeQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ export function GetTypeQueryDescriptorFromDeclaration(
}
default:
TransformerLogger().typeNotSupported(
`TypeQuery of ${ts.SyntaxKind[declaration.kind]}`
`TypeQuery of ${ts.SyntaxKind[declaration.kind]}`,
declaration
);
return GetNullDescriptor();
}
Expand Down
47 changes: 42 additions & 5 deletions src/transformer/logger/transformerLogger.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,70 @@
import ts from 'typescript';
import { Logger } from '../../logger/logger';
import { ILogger } from '../../logger/logger.interface';
import { GetCurrentCreateMock } from '../mock/currentCreateMockNode';

let logger: ILogger;

export interface TransformerLogger {
circularGenericNotSupported(nodeName: string): void;

unexpectedCreateMock(mockFileName: string, expectedFileName: string): void;
typeNotSupported(type: string): void;

typeNotSupported(type: string, currentNode?: ts.Node): void;

typeOfFunctionCallNotFound(node: string): void;

indexedAccessTypeFailed(propertyName: string, nodeText: string): void;
}

const notSupportedTypeMessage: (
type: string,
createMockFileUrl: string,
currentNodeFileUrl: string
) => string = (
type: string,
createMockFileUrl: string,
currentNodeFileUrl: string
) => `Not supported type: ${type} - it will convert to null
created ${createMockFileUrl}
used by ${currentNodeFileUrl}`;

export const getNodeFileUrl: (node: ts.Node) => string = (node: ts.Node) => {
const sourceFile: ts.SourceFile = node.getSourceFile();
const {
line,
character,
}: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(
node.getStart()
);

return `file://${sourceFile.fileName}:${line + 1}:${character + 1}`;
};

export function TransformerLogger(): TransformerLogger {
logger = logger || Logger('Transformer');

return {
circularGenericNotSupported(nodeName: string): void {
logger.warning(
`Found a circular generic of \`${nodeName}' and such generics are currently not supported. ` +
'The generated mock will be incomplete.'
`Found a circular generic of \`${nodeName}' and such generics are currently not supported.
The generated mock will be incomplete.`
);
},
unexpectedCreateMock(mockFileName: string, expectedFileName: string): void {
logger.warning(`I\'ve found a mock creator but it comes from a different folder
found: ${mockFileName}
expected: ${expectedFileName}`);
},
typeNotSupported(type: string): void {
logger.warning(`Not supported type: ${type} - it will convert to null`);
typeNotSupported(type: string, currentNode: ts.Node): void {
const createMockNode: ts.Node = GetCurrentCreateMock();

const createMockFileUrl: string = getNodeFileUrl(createMockNode);
const currentNodeFileUrl: string = getNodeFileUrl(currentNode);

logger.warning(
notSupportedTypeMessage(type, createMockFileUrl, currentNodeFileUrl)
);
},
typeOfFunctionCallNotFound(node: string): void {
logger.warning(
Expand Down
11 changes: 11 additions & 0 deletions src/transformer/mock/currentCreateMockNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import ts from 'typescript';

let currentCreateMock: ts.Node;

export const GetCurrentCreateMock: () => ts.Node = () => currentCreateMock;

export const SetCurrentCreateMock: (node: ts.Node) => void = (
node: ts.Node
) => {
currentCreateMock = node;
};
5 changes: 5 additions & 0 deletions src/transformer/mock/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '../mergeExpression/mergeExpression';
import { MockDefiner } from '../mockDefiner/mockDefiner';
import { Scope } from '../scope/scope';
import { SetCurrentCreateMock } from './currentCreateMockNode';

function getMockExpression(nodeToMock: ts.TypeNode): ts.Expression {
return GetDescriptor(nodeToMock, new Scope());
Expand Down Expand Up @@ -54,6 +55,8 @@ export function getMock(
nodeToMock: ts.TypeNode,
node: ts.CallExpression
): ts.Expression {
SetCurrentCreateMock(node);

const mockExpression: ts.Expression = getMockExpression(nodeToMock);

if (hasDefaultValues(node)) {
Expand All @@ -67,6 +70,7 @@ export function getMockForList(
nodeToMock: ts.TypeNode,
node: ts.CallExpression
): ts.ArrayLiteralExpression {
SetCurrentCreateMock(node);
const mock: ts.Expression = getMockExpression(nodeToMock);
const lengthLiteral: ts.NumericLiteral = node
.arguments[0] as ts.NumericLiteral;
Expand Down Expand Up @@ -96,6 +100,7 @@ export function storeRegisterMock(
typeToMock: ts.TypeNode,
node: ts.CallExpression
): ts.Node {
SetCurrentCreateMock(node);
if (ts.isTypeReferenceNode(typeToMock)) {
const factory: ts.FunctionExpression = node
.arguments[0] as ts.FunctionExpression;
Expand Down
11 changes: 11 additions & 0 deletions test/logs/conditionalType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type ConditionalType<T> = T extends string
? 'string'
: T extends number
? 'number'
: T extends boolean
? 'boolean'
: T extends undefined
? 'undefined'
: T extends Function
? 'function'
: 'object';
27 changes: 27 additions & 0 deletions test/logs/createMock.warning.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createMock } from 'ts-auto-mock';
import { ConditionalType } from './conditionalType';
import { getLogsByCreateMockFileName, UnsupportedTypeLog } from './utils/log';

describe('Warning', () => {
it('should log an unsupported type warning', () => {
const logs: UnsupportedTypeLog[] = getLogsByCreateMockFileName(
'createMock.warning.test.ts'
);
interface InterfaceWithConditionalType {
conditional: ConditionalType<string>;
}

createMock<InterfaceWithConditionalType>();
expect(logs.length).toBe(1);

expect(logs[0].header).toContain(
'WARNING: Transformer - Not supported type: ConditionalType - it will convert to null'
);
expect(logs[0].created).toMatch(
/created file:\/\/.*createMock.warning\.test\.ts:[0-9]*:[0-9]*/
);
expect(logs[0].usedBy).toMatch(
/used by file:\/\/.*conditionalType\.ts:[0-9]*:[0-9]*/
);
});
});
27 changes: 27 additions & 0 deletions test/logs/createMockList.warning.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createMockList } from 'ts-auto-mock';
import { ConditionalType } from './conditionalType';
import { getLogsByCreateMockFileName, UnsupportedTypeLog } from './utils/log';

describe('Warning', () => {
it('should log an unsupported type warning', () => {
const logs: UnsupportedTypeLog[] = getLogsByCreateMockFileName(
'createMockList.warning.test.ts'
);
interface InterfaceWithConditionalType {
conditional: ConditionalType<string>;
}

createMockList<InterfaceWithConditionalType>(2);
expect(logs.length).toBe(1);

expect(logs[0].header).toContain(
'WARNING: Transformer - Not supported type: ConditionalType - it will convert to null'
);
expect(logs[0].created).toMatch(
/created file:\/\/.*createMockList.warning\.test\.ts:[0-9]*:[0-9]*/
);
expect(logs[0].usedBy).toMatch(
/used by file:\/\/.*conditionalType\.ts:[0-9]*:[0-9]*/
);
});
});
5 changes: 5 additions & 0 deletions test/logs/jasmine.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"spec_dir": "./test/logs",
"spec_files": ["**/*test.ts"],
"helpers": ["../reporter.js"]
}
12 changes: 12 additions & 0 deletions test/logs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../tsconfig/tsconfig.json",
"compilerOptions": {
"plugins": [
{
"transform": "./dist/transformer",
"debug": "file",
"cacheBetweenTests": false
}
]
}
}
29 changes: 29 additions & 0 deletions test/logs/utils/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import fs from 'fs';

export interface UnsupportedTypeLog {
header: string;
created: string;
usedBy: string;
}

export const getLogsByCreateMockFileName: (
fileName: string
) => UnsupportedTypeLog[] = (fileName: string) => {
const logErrorFile: string = fs.readFileSync('./tsAutoMock.log', 'utf-8');
const logs: UnsupportedTypeLog[] = [];
const lines: string[] = logErrorFile
.split('\n')
.filter((line: string) => !!line);

for (let i: number = 0; i < lines.length; i += 3) {
logs.push({
header: lines[i],
created: lines[i + 1],
usedBy: lines[i + 2],
});
}

return logs.filter(
(log: UnsupportedTypeLog) => log.created.indexOf(fileName) > -1
);
};

0 comments on commit 5dd6711

Please sign in to comment.