Skip to content

Commit 9f6058e

Browse files
authored
Merge pull request #61 from t-winter/master
added support for Date
2 parents 9db28ed + a338c26 commit 9f6058e

File tree

15 files changed

+222
-84
lines changed

15 files changed

+222
-84
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules/
22
lib/
33
test/*.js
4+
.idea

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ There are some options to configure the transformer.
130130
| Property | Description |
131131
|--|--|
132132
| `shortCircuit` | Boolean (default `false`). If `true`, all type guards will return `true`, i.e. no validation takes place. Can be used for example in production deployments where doing a lot of validation can cost too much CPU. |
133-
| `ignoreClasses` | Boolean (default: `false`). If `true`, when the transformer encounters a class, it will ignore it and simply return `true`. If `false`, an error is generated at compile time. |
133+
| `ignoreClasses` | Boolean (default: `false`). If `true`, when the transformer encounters a class (except for `Date`), it will ignore it and simply return `true`. If `false`, an error is generated at compile time. |
134134
| `ignoreMethods` | Boolean (default: `false`). If `true`, when the transformer encounters a method, it will ignore it and simply return `true`. If `false`, an error is generated at compile time. |
135135
| `ignoreFunctions` | Boolean (default: `false`). If `true`, when the transformer encounters a function, it will ignore it and simply return `true`. If `false`, an error is generated at compile time. |
136136
| `disallowSuperfluousObjectProperties` | Boolean (default: `false`). If `true`, objects are checked for having superfluous properties and will cause the validation to fail if they do. If `false`, no check for superfluous properties is made. |
@@ -279,7 +279,7 @@ There you can find all the different types that are tested for.
279279

280280
* This library aims to be able to check any serializable data.
281281
* This library will not check functions. Function signatures are impossible to check at run-time.
282-
* This library will not check classes. Instead, you are encouraged to use the native `instanceof` operator. For example:
282+
* This library will not check classes (except the global `Date`). Instead, you are encouraged to use the native `instanceof` operator. For example:
283283

284284
```typescript
285285
import { is } from 'typescript-is';

index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ interface ExpectedObject {
204204
type: 'object';
205205
}
206206

207+
interface ExpectedDate {
208+
type: 'date';
209+
}
210+
207211
interface ExpectedNonPrimitive {
208212
type: 'non-primitive';
209213
}
@@ -253,6 +257,7 @@ type Reason = ExpectedString
253257
| ExpectedBigInt
254258
| ExpectedBoolean
255259
| ExpectedObject
260+
| ExpectedDate
256261
| ExpectedNonPrimitive
257262
| MissingObjectProperty
258263
| SuperfluousObjectProperty

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "typescript-is",
3-
"version": "0.15.0",
3+
"version": "0.16.0",
44
"engines": {
55
"node": ">=6.14.4"
66
},

src/transform-inline/visitor-type-check.ts

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,70 @@ import * as VisitorIsStringKeyof from './visitor-is-string-keyof';
88
import * as VisitorTypeName from './visitor-type-name';
99
import { sliceSet } from './utils';
1010

11+
function visitDateType(type: ts.ObjectType, visitorContext: VisitorContext) {
12+
const name = VisitorTypeName.visitType(type, visitorContext, { type: 'type-check' });
13+
return VisitorUtils.setFunctionIfNotExists(name, visitorContext, () => {
14+
return ts.createFunctionDeclaration(
15+
undefined,
16+
undefined,
17+
undefined,
18+
name,
19+
undefined,
20+
[ts.createParameter(undefined, undefined, undefined, VisitorUtils.objectIdentifier, undefined, undefined, undefined)],
21+
undefined,
22+
ts.createBlock(
23+
[
24+
ts.createVariableStatement(
25+
undefined,
26+
ts.createVariableDeclarationList(
27+
[ts.createVariableDeclaration(
28+
ts.createIdentifier('nativeDateObject'),
29+
undefined,
30+
undefined
31+
)],
32+
ts.NodeFlags.Let
33+
)
34+
),
35+
ts.createIf(
36+
ts.createBinary(
37+
ts.createTypeOf(ts.createIdentifier('global')),
38+
ts.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),
39+
ts.createStringLiteral('undefined')
40+
),
41+
ts.createExpressionStatement(ts.createBinary(
42+
ts.createIdentifier('nativeDateObject'),
43+
ts.createToken(ts.SyntaxKind.EqualsToken),
44+
ts.createPropertyAccess(
45+
ts.createIdentifier('window'),
46+
ts.createIdentifier('Date')
47+
)
48+
)),
49+
ts.createExpressionStatement(ts.createBinary(
50+
ts.createIdentifier('nativeDateObject'),
51+
ts.createToken(ts.SyntaxKind.EqualsToken),
52+
ts.createPropertyAccess(
53+
ts.createIdentifier('global'),
54+
ts.createIdentifier('Date')
55+
)
56+
))
57+
),
58+
ts.createIf(
59+
ts.createLogicalNot(
60+
ts.createBinary(
61+
ts.createIdentifier('object'),
62+
ts.createToken(ts.SyntaxKind.InstanceOfKeyword),
63+
ts.createIdentifier('nativeDateObject')
64+
)
65+
),
66+
ts.createReturn(VisitorUtils.createErrorObject({ type: 'date' })),
67+
ts.createReturn(ts.createNull())
68+
)],
69+
true
70+
)
71+
)
72+
});
73+
}
74+
1175
function visitTupleObjectType(type: ts.TupleType, visitorContext: VisitorContext) {
1276
const name = VisitorTypeName.visitType(type, visitorContext, { type: 'type-check' });
1377
return VisitorUtils.setFunctionIfNotExists(name, visitorContext, () => {
@@ -379,7 +443,17 @@ function visitTypeParameter(type: ts.Type, visitorContext: VisitorContext) {
379443

380444
function visitObjectType(type: ts.ObjectType, visitorContext: VisitorContext) {
381445
if (VisitorUtils.checkIsClass(type, visitorContext)) {
382-
return VisitorUtils.getIgnoredTypeFunction(visitorContext);
446+
// Dates
447+
if (VisitorUtils.checkIsDateClass(type)) {
448+
return visitDateType(type, visitorContext);
449+
}
450+
451+
// all other classes
452+
if (visitorContext.options.ignoreClasses) {
453+
return VisitorUtils.getIgnoredTypeFunction(visitorContext);
454+
} else {
455+
throw new Error('Classes cannot be validated. https://github.com/woutervh-/typescript-is/issues/3');
456+
}
383457
}
384458
if (tsutils.isTupleType(type)) {
385459
// Tuple with finite length.

src/transform-inline/visitor-type-name.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { VisitorContext } from './visitor-context';
44
import * as VisitorUtils from './visitor-utils';
55
import * as VisitorKeyof from './visitor-keyof';
66
import * as VisitorIndexedAccess from './visitor-indexed-access';
7+
import { checkIsDateClass } from './visitor-utils';
78

89
interface TypeCheckNameMode {
910
type: 'type-check';
@@ -67,6 +68,8 @@ function visitObjectType(type: ts.ObjectType, visitorContext: VisitorContext, mo
6768
return visitTupleObjectType(type, visitorContext, mode);
6869
} else if (visitorContext.checker.getIndexTypeOfType(type, ts.IndexKind.Number)) {
6970
return visitArrayObjectType(type, visitorContext, mode);
71+
} else if (checkIsDateClass(type)) {
72+
return '_date';
7073
} else {
7174
return visitRegularObjectType(type);
7275
}

src/transform-inline/visitor-utils.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as ts from 'typescript';
2+
import { ModifierFlags } from 'typescript';
23
import * as tsutils from 'tsutils/typeguard/3.0';
34
import { VisitorContext } from './visitor-context';
45
import { Reason } from '../../index';
@@ -23,14 +24,16 @@ export function checkIsClass(type: ts.ObjectType, visitorContext: VisitorContext
2324
hasConstructSignatures = constructSignatures.length >= 1;
2425
}
2526

26-
if (type.isClass() || hasConstructSignatures) {
27-
if (visitorContext.options.ignoreClasses) {
28-
return true;
29-
} else {
30-
throw new Error('Classes cannot be validated. https://github.com/woutervh-/typescript-is/issues/3');
31-
}
32-
} else {
33-
return false;
27+
return type.isClass() || hasConstructSignatures;
28+
}
29+
30+
export function checkIsDateClass(type: ts.ObjectType) {
31+
if (
32+
type.symbol !== undefined
33+
&& type.symbol.escapedName === 'Date'
34+
&& (ts.getCombinedModifierFlags(type.symbol.valueDeclaration) & ModifierFlags.Ambient) !== 0
35+
) {
36+
return true;
3437
}
3538
}
3639

@@ -589,5 +592,7 @@ function createErrorMessage(reason: Reason): ts.Expression {
589592
return createAssertionString(`expected ${reason.value ? 'true' : 'false'}`);
590593
case 'non-primitive':
591594
return createAssertionString('expected a non-primitive');
595+
case 'date':
596+
return createAssertionString('expected a Date');
592597
}
593598
}

test-fixtures/issue-16-a.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { is } from '../index';
22

3-
class ClassX {
4-
method() {
5-
//
6-
}
7-
}
3+
// overwriting the global Date with a custom class, this isn't supported and should raise an error if ignoreClasses is false
4+
class Date {}
85

9-
is<ClassX>(new ClassX()); // ignore when ignoreClasses is true
6+
is<Date>(new Date()); // ignore when ignoreClasses is true

test-fixtures/issue-16-c.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import { is } from '../index';
22

3-
is<Date>({}); // Ignore when ignoreClasses and ignoreMethods are true.
3+
is<Date>({}); // Do NOT ignore when ignoreClasses and ignoreMethods are false.

0 commit comments

Comments
 (0)