8000 Added custom error class in assert related functions · SebastienGllmt/typescript-is@7f25d28 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7f25d28

Browse files
Added custom error class in assert related functions
1 parent 717b5a9 commit 7f25d28

File tree

4 files changed

+116
-43
lines changed

4 files changed

+116
-43
lines changed

index.d.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,27 @@ export function AssertType(options?: AssertTypeOptions): (target: object, proper
9494
```
9595
*/
9696
export function ValidateClass(errorConstructor?: { new(): Error }): <TFunction extends Function>(target: TFunction) => void;
97+
98+
/**
99+
* Class which helps catch errors specifically from this library.
100+
* When `assertType` or `createAssertType` throw an error, it uses this class to create an instance.
101+
* By default, a class decorated with `@ValidateClass` will also throw errors of this class, unless it's overriden using the options.
102+
*
103+
* @example
104+
* ```
105+
// Somewhere in the code:
106+
{
107+
assertType<MyType>(obj);
108+
}
109+
110+
// Somewhere higher up the call stack:
111+
try {
112+
...
113+
} catch (error) {
114+
if (error instanceof TypeGuardError) {
115+
// An error from this library occurred.
116+
}
117+
}
118+
```
119+
*/
120+
export class TypeGuardError extends Error { }

index.js

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
function warn() {
2-
throw new Error('This module should not be used in runtime. Instead, use a transformer during compilation.');
1+
function checkGetErrorMessage(getErrorMessage) {
2+
if (typeof getErrorMessage !== 'function') {
3+
throw new Error('This module should not be used in runtime. Instead, use a transformer during compilation.');
4+
}
35
}
46

57
const assertionsMetadataKey = Symbol('assertions');
68

9+
class TypeGuardError extends Error { }
10+
711
function AssertType(assertion, options = {}) {
812
require('reflect-metadata');
913
return function (target, propertyKey, parameterIndex) {
@@ -13,7 +17,7 @@ function AssertType(assertion, options = {}) {
1317
};
1418
}
1519

16-
function ValidateClass(errorConstructor = Error) {
20+
function ValidateClass(errorConstructor = TypeGuardError) {
1721
require('reflect-metadata');
1822
return function (target) {
1923
for (const propertyKey of Object.getOwnPropertyNames(target.prototype)) {
@@ -22,9 +26,9 @@ function ValidateClass(errorConstructor = Error) {
2226
const originalMethod = target.prototype[propertyKey];
2327
target.prototype[propertyKey] = function (...args) {
2428
for (let i = 0; i < assertions.length; i++) {
25-
const error = assertions[i].assertion(args[i]);
26-
if (error !== null) {
27-
throw new errorConstructor(assertions[i].options.message || error);
29+
const errorMessage = assertions[i].assertion(args[i]);
30+
if (errorMessage !== null) {
31+
throw new errorConstructor(assertions[i].options.message || errorMessage);
2832
}
2933
}
3034
return originalMethod.apply(this, args);
@@ -34,4 +38,30 @@ function ValidateClass(errorConstructor = Error) {
3438
};
3539
}
3640

37-
module.exports = { is: warn, assertType: warn, createIs: warn, createAssertType: warn, AssertType, ValidateClass };
41+
function is(obj, getErrorMessage) {
42+
checkGetErrorMessage(getErrorMessage);
43+
const errorMessage = getErrorMessage(obj);
44+
return errorMessage === null;
45+
}
46+
47+
function assertType(obj, getErrorMessage) {
48+
checkGetErrorMessage(getErrorMessage);
49+
const errorMessage = getErrorMessage(obj);
50+
if (errorMessage === null) {
51+
return obj;
52+
} else {
53+
throw new TypeGuardError(errorMessage);
54+
}
55+
}
56+
57+
function createIs(getErrorMessage) {
58+
checkGetErrorMessage(getErrorMessage);
59+
return (obj) => is(obj, getErrorMessage);
60+
}
61+
62+
function createAssertType(getErrorMessage) {
63+
checkGetErrorMessage(getErrorMessage);
64+
return (obj) => assertType(obj, getErrorMessage);
65+
}
66+
67+
module.exports = { is, assertType, createIs, createAssertType, AssertType, ValidateClass, TypeGuardError };

src/transform-inline/transform-node.ts

Lines changed: 13 additions & 36 deletions
< 10000 /tr>
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@ import { VisitorContext, PartialVisitorContext } from './visitor-context';
44
import { visitType, visitUndefinedOrType, visitShortCircuit } from './visitor-type-check';
55
import { sliceMapValues } from './utils';
66

7-
type AssertMode = 'assert' | 'decorator' | 'boolean';
8-
97
const objectIdentifier = ts.createIdentifier('object');
108
const pathIdentifier = ts.createIdentifier('path');
119

12-
function createArrowFunction(type: ts.Type, optional: boolean, partialVisitorContext: PartialVisitorContext, assertMode: AssertMode) {
10+
function createArrowFunction(type: ts.Type, optional: boolean, partialVisitorContext: PartialVisitorContext) {
1311
const functionMap: VisitorContext['functionMap'] = new Map();
1412
const functionNames: VisitorContext['functionNames'] = new Set();
1513
const visitorContext = { ...partialVisitorContext, functionNames, functionMap };
@@ -23,19 +21,6 @@ function createArrowFunction(type: ts.Type, optional: boolean, partialVisitorCon
2321
const errorIdentifier = ts.createIdentifier('error');
2422
const declarations = sliceMapValues(functionMap);
2523

26-
let finalStatement: ts.Statement;
27-
if (assertMode === 'assert') {
28-
finalStatement = ts.createIf(
29-
errorIdentifier,
30-
ts.createThrow(ts.createNew(ts.createIdentifier('Error'), undefined, [errorIdentifier])),
31-
ts.createReturn(objectIdentifier)
32-
);
33-
} else if (assertMode === 'boolean') {
34-
finalStatement = ts.createReturn(ts.createStrictEquality(errorIdentifier, ts.createNull()));
35-
} else {
36-
finalStatement = ts.createReturn(errorIdentifier);
37-
}
38-
3924
return ts.createArrowFunction(
4025
undefined,
4126
undefined,
@@ -61,7 +46,7 @@ function createArrowFunction(type: ts.Type, optional: boolean, partialVisitorCon
6146
[ts.createModifier(ts.SyntaxKind.ConstKeyword)],
6247
[ts.createVariableDeclaration(errorIdentifier, undefined, ts.createCall(ts.createIdentifier(functionName), undefined, [objectIdentifier]))]
6348
),
64-
finalStatement
49+
ts.createReturn(errorIdentifier)
6550
])
6651
);
6752
}
@@ -75,7 +60,7 @@ function transformDecorator(node: ts.Decorator, parameterType: ts.Type, optional
7560
&& path.resolve(signature.declaration.getSourceFile().fileName) === path.resolve(path.join(__dirname, '..', '..', 'index.d.ts'))
7661
&& node.expression.arguments.length <= 1
7762
) {
78-
const arrowFunction: ts.Expression = createArrowFunction(parameterType, optional, visitorContext, 'decorator');
63+
const arrowFunction: ts.Expression = createArrowFunction(parameterType, optional, visitorContext);
7964
const expression = ts.updateCall(
8065
node.expression,
8166
node.expression.expression,
@@ -115,27 +100,19 @@ export function transformNode(node: ts.Node, visitorContext: PartialVisitorConte
115100
&& node.typeArguments !== undefined
116101
&& node.typeArguments.length === 1
117102
) {
118-
const name = visitorContext.checker.getTypeAtLocation(signature.declaration).symbol.name;
119-
const isCreate = name === 'createIs' || name === 'createAssertType';
120-
const isAssert = name === 'assertType' || name === 'createAssertType';
121103
const typeArgument = node.typeArguments[0];
122104
const type = visitorContext.checker.getTypeFromTypeNode(typeArgument);
105+
const arrowFunction = createArrowFunction(type, false, visitorContext);
123106

124-
if (!(isCreate && node.arguments.length === 0) && !(!isCreate && node.arguments.length === 1)) {
125-
throw new Error('Calls to `is` and `assertType` should have one argument, calls to `createIs` and `createAssertType` should have no arguments.');
126-
}
127-
128-
const arrowFunction = createArrowFunction(type, false, visitorContext, isAssert ? 'assert' : 'boolean');
129-
130-
if (isCreate) {
131-
return arrowFunction;
132-
} else {
133-
return ts.createCall(
134-
arrowFunction,
135-
undefined,
136-
node.arguments
137-
);
138-
}
107+
return ts.updateCall(
108+
node,
109+
node.expression,
110+
node.typeArguments,
111+
[
112+
...node.arguments,
113+
arrowFunction
114+
]
115+
);
139116
}
140117
}
141118
return node;

test/issue-19.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import * as assert from 'assert';
2+
import { assertType, TypeGuardError, createAssertType } from '../index';
3+
4+
/* https://github.com/woutervh-/typescript-is/issues/19 */
5+
6+
describe('is', () => {
7+
describe('assertType<{}>', () => {
8+
it('should return the objects passed to it', () => {
9+
assert.deepStrictEqual(assertType<{}>({}), {});
10+
});
11+
12+
it('should throw a TypeGuardValidation error if failing objects are passed to it', () => {
13+
try {
14+
assertType<{}>('');
15+
} catch (error) {
16+
if (!(error instanceof TypeGuardError)) {
17+
throw new Error('Expected error of class TypeGuardError.');
18+
}
19+
}
20+
});
21+
});
22+
23+
describe('createAssertType<{}>', () => {
24+
it('should return a function', () => {
25+
assert.deepStrictEqual(typeof createAssertType<{}>(), 'function');
26+
});
27+
28+
it('should return a function that returns the objects passed to it', () => {
29+
assert.deepStrictEqual(createAssertType<{}>()({}), {});
30+
});
31+
32+
it('should throw a TypeGuardValidation error if failing objects are passed to it', () => {
33+
try {
34+
createAssertType<{}>()('');
35+
} catch (error) {
36+
if (!(error instanceof TypeGuardError)) {
37+
throw new Error('Expected error of class TypeGuardError.');
38+
}
39+
}
40+
});
41+
});
42+
});

0 commit comments

Comments
 (0)
0