8000 Better error reporting for decorator assertions · SebastienGllmt/typescript-is@8d35a05 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8d35a05

Browse files
Better error reporting for decorator assertions
1 parent d663fa7 commit 8d35a05

File tree

4 files changed

+26
-24
lines changed

4 files changed

+26
-24
lines changed

index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ function ValidateClass(errorConstructor = Error) {
2222
const originalMethod = target.prototype[propertyKey];
2323
target.prototype[propertyKey] = function (...args) {
2424
for (let i = 0; i < assertions.length; i++) {
25-
if (!assertions[i].assertion(args[i])) {
26-
throw new errorConstructor(assertions[i].options.message || 'Type assertion failed.');
25+
const error = assertions[i].assertion(args[i]);
26+
if (error !== null) {
27+
throw new errorConstructor(assertions[i].options.message || error);
2728
}
2829
}
2930
return originalMethod.apply(this, args);

src/transform-inline/transform-node.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ 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+
79
const objectIdentifier = ts.createIdentifier('object');
810
const pathIdentifier = ts.createIdentifier('path');
911

10-
function createArrowFunction(type: ts.Type, optional: boolean, partialVisitorContext: PartialVisitorContext, isAssert: boolean) {
12+
function createArrowFunction(type: ts.Type, optional: boolean, partialVisitorContext: PartialVisitorContext, assertMode: AssertMode) {
1113
const functionMap: VisitorContext['functionMap'] = new Map();
1214
const functionNames: VisitorContext['functionNames'] = new Set();
1315
const visitorContext = { ...partialVisitorContext, functionNames, functionMap };
@@ -21,6 +23,19 @@ function createArrowFunction(type: ts.Type, optional: boolean, partialVisitorCon
2123
const errorIdentifier = ts.createIdentifier('error');
2224
const declarations = sliceMapValues(functionMap);
2325

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+
2439
return ts.createArrowFunction(
2540
undefined,
2641
undefined,
@@ -46,13 +61,7 @@ function createArrowFunction(type: ts.Type, optional: boolean, partialVisitorCon
4661
[ts.createModifier(ts.SyntaxKind.ConstKeyword)],
4762
[ts.createVariableDeclaration(errorIdentifier, undefined, ts.createCall(ts.createIdentifier(functionName), undefined, [objectIdentifier]))]
4863
),
49-
isAssert
50-
? ts.createIf(
51-
errorIdentifier,
52-
ts.createThrow(ts.createNew(ts.createIdentifier('Error'), undefined, [errorIdentifier])),
53-
ts.createReturn(objectIdentifier)
54-
)
55-
: ts.createReturn(ts.createStrictEquality(errorIdentifier, ts.createNull()))
64+
finalStatement
5665
])
5766
);
5867
}
@@ -66,7 +75,7 @@ function transformDecorator(node: ts.Decorator, parameterType: ts.Type, optional
6675
&& path.resolve(signature.declaration.getSourceFile().fileName) === path.resolve(path.join(__dirname, '..', '..', 'index.d.ts'))
6776
&& node.expression.arguments.length <= 1
6877
) {
69-
const arrowFunction: ts.Expression = createArrowFunction(parameterType, optional, visitorContext, false);
78+
const arrowFunction: ts.Expression = createArrowFunction(parameterType, optional, visitorContext, 'decorator');
7079
const expression = ts.updateCall(
7180
node.expression,
7281
node.expression.expression,
@@ -116,7 +125,7 @@ export function transformNode(node: ts.Node, visitorContext: PartialVisitorConte
116125
throw new Error('Calls to `is` and `assertType` should have one argument, calls to `createIs` and `createAssertType` should have no arguments.');
117126
}
118127

119-
const arrowFunction = createArrowFunction(type, false, visitorContext, isAssert);
128+
const arrowFunction = createArrowFunction(type, false, visitorContext, isAssert ? 'assert' : 'boolean');
120129

121130
if (isCreate) {
122131
return arrowFunction;

test/case-11.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@ import { AssertType, ValidateClass } from '../index';
55

66
describe('@ValidateClass, @AssertType', () => {
77
describe('@ValidateClass(), @AssertType() parameter: number', () => {
8-
const expectedMessageRegExp = /Type assertion failed.$/;
9-
108
@ValidateClass()
119
class TestClass {
1210
testMethod(@AssertType() parameter: number) {
1311
return parameter;
1412
}
1513
}
16-
1714
const instance = new TestClass();
1815

1916
it('should pass validation for numbers', () => {
@@ -27,6 +24,7 @@ describe('@ValidateClass, @AssertType', () => {
2724
});
2825

2926
it('should throw an error for non-numbers', () => {
27+
const expectedMessageRegExp = /validation failed at \$: expected a number$/;
3028
assert.throws(() => instance.testMethod('' as any), expectedMessageRegExp);
3129
assert.throws(() => instance.testMethod('0' as any), expectedMessageRegExp);
3230
assert.throws(() => instance.testMethod('1' as any), expectedMessageRegExp);
@@ -38,15 +36,12 @@ describe('@ValidateClass, @AssertType', () => {
3836
});
3937

4038
describe('@ValidateClass(), @AssertType() parameter: string', () => {
41-
const expectedMessageRegExp = /Type assertion failed.$/;
42-
4339
@ValidateClass()
4440
class TestClass {
4541
testMethod(@AssertType() parameter: string) {
4642
return parameter;
4743
}
4844
}
49-
5045
const instance = new TestClass();
5146

5247
it('should pass validation for strings', () => {
@@ -57,6 +52,7 @@ describe('@ValidateClass, @AssertType', () => {
5752
});
5853

5954
it('should throw an error for non-strings', () => {
55+
const expectedMessageRegExp = /validation failed at \$: expected a string$/;
6056
assert.throws(() => instance.testMethod(0 as any), expectedMessageRegExp);
6157
assert.throws(() => instance.testMethod(1 as any), expectedMessageRegExp);
6258
assert.throws(() => instance.testMethod(true as any), expectedMessageRegExp);

test/case-15.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@ import { AssertType, ValidateClass } from '../index';
55

66
describe('@ValidateClass, @AssertType', () => {
77
describe('@ValidateClass(), @AssertType() parameter: number = 50', () => {
8-
const expectedMessageRegExp = /Type assertion failed.$/;
9-
108
@ValidateClass()
119
class TestClass {
1210
testMethod(@AssertType() parameter: number = 50) {
1311
return parameter;
1412
}
1513
}
16-
1714
const instance = new TestClass();
1815

1916
it('should pass validation for numbers', () => {
@@ -32,6 +29,7 @@ describe('@ValidateClass, @AssertType', () => {
3229
});
3330

3431
it('should throw an error for non-numbers', () => {
32+
const expectedMessageRegExp = /validation failed at \$: expected a number$/;
3533
assert.throws(() => instance.testMethod('' as any), expectedMessageRegExp);
3634
assert.throws(() => instance.testMethod('0' as any), expectedMessageRegExp);
3735
assert.throws(() => instance.testMethod('1' as any), expectedMessageRegExp);
@@ -44,15 +42,12 @@ describe('@ValidateClass, @AssertType', () => {
4442
});
4543

4644
describe('@ValidateClass(), @AssertType() parameter?: number', () => {
47-
const expectedMessageRegExp = /Type assertion failed.$/;
48-
4945
@ValidateClass()
5046
class TestClass {
5147
testMethod(@AssertType() parameter?: number) {
5248
return parameter;
5349
}
5450
}
55-
5651
const instance = new TestClass();
5752

5853
it('should pass validation for numbers', () => {
@@ -72,6 +67,7 @@ describe('@ValidateClass, @AssertType', () => {
7267
});
7368

7469
it('should throw an error for non-numbers', () => {
70+
const expectedMessageRegExp = /validation failed at \$: expected a number$/;
7571
assert.throws(() => instance.testMethod('' as any), expectedMessageRegExp);
7672
assert.throws(() => instance.testMethod('0' as any), expectedMessageRegExp);
7773
assert.throws(() => instance.testMethod('1' as any), expectedMessageRegExp);

0 commit comments

Comments
 (0)
0