Skip to content

Commit

Permalink
Added
Browse files Browse the repository at this point in the history
New tests (#28) - coverage ~88 %.

Changed
- Changed way of handling types of runtime value.
Now there is SomeClass.prototype[REFLECTED_TYPE_ID] = ID of SomeClass's type; generated for each class in the project (not just those decorated via @reflect). It does not mean that metadata of those classes will be generated. Logic of generating type's metadata is still the same, use getType<SomeClass>() somewhere, apply @reflect decorator or apply any other decorator tagged by @reflect JSDoc tag.
This is preparation for include/exclude configuration (issue #29).
- custom decorators tagged by @reflect JSDoc tag does not have to have generic parameter
  • Loading branch information
Hookyns committed Feb 14, 2022
1 parent d9201cc commit 80c6b9d
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 35 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[//]: # (### Added)
[//]: # (### Changed)

## [0.7.2] - 2022-02-14
### Added
- New tests (#28) - coverage ~88 %.

### Changed
- Changed way of handling types of runtime value.
\
Now there is `SomeClass.prototype[REFLECTED_TYPE_ID] = ID of SomeClass's type;` generated for each class in the project (not just those decorated via `@reflect`).
It does not mean that metadata of those classes will be generated.
Logic of generating type's metadata is still the same,
use `getType<SomeClass>()` somewhere, apply `@reflect` decorator
or apply any other decorator tagged by `@reflect` JSDoc tag.
\
This is preparation for include/exclude configuration (issue #29).
- custom decorators tagged by `@reflect` JSDoc tag does not have to have generic parameter

## [0.7.1] - 2022-02-14
### Added
- `Type.isAny()`

### Changed
- `Type.isAssignableTo()` - added support of Arrays; fixed issue with optional members


## [0.7.0] - 2022-02-14
### Added
- `Type.toString()`
Expand All @@ -33,6 +50,7 @@ Native types (String, Number, Object, etc..) are always generated as eg.: `Promi
- \[BREAKING] `Type.intersection` changed to `Type.isIntersection()`
- Fixed of issue #23 - access to ctor of not exported class


## [0.7.0-alpha.0] - 2022-02-13
### Added
- `getType(val: any)` it is possible to get type of runtime value,
Expand Down
2 changes: 1 addition & 1 deletion coverage/badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion runtime/src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ export const REFLECT_STORE_SYMBOL = Symbol("tst_reflect_store");
/**
* Name of the property used to store Type instance on constructors
*/
export const REFLECTED_TYPE = "__reflectedType__";
export const REFLECTED_TYPE_ID = "__reflectedTypeId__";
15 changes: 3 additions & 12 deletions runtime/src/reflect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ObjectLiteralTypeBuilder } from "./type-builder/ObjectLiteralTypeBuilder";
import { TypeBuilder } from "./type-builder/TypeBuilder";
import { REFLECTED_TYPE } from "./consts";
import { REFLECTED_TYPE_ID } from "./consts";
import { TypeKind } from "./enums";
import {
Type,
Expand All @@ -13,7 +13,7 @@ const ArrayItemsCountToCheckItsType = 10;
* @param value
* @internal
*/
export function getTypeOfRuntimeValue(value: any)
export function getTypeOfRuntimeValue(value: any): Type
{
if (value === undefined) return Type.Undefined;
if (value === null) return Type.Null;
Expand Down Expand Up @@ -59,7 +59,7 @@ export function getTypeOfRuntimeValue(value: any)
return arrayBuilder.setGenericType(unionBuilder.build()).build();
}

return value.constructor[REFLECTED_TYPE] || Type.Unknown;
return Type.store.get(value.constructor.prototype[REFLECTED_TYPE_ID]) || Type.Unknown;
}

/**
Expand Down Expand Up @@ -101,10 +101,7 @@ getType.__tst_reflect__ = true;
*/
export function reflect<TType>()
{
const typeOfTType = arguments[0]?.TType;

return function <T>(Constructor: { new(...args: any[]): T }) {
(Constructor as any)[REFLECTED_TYPE] = typeOfTType;
return Constructor;
};
}
Expand Down Expand Up @@ -145,12 +142,6 @@ const nativeTypes = {
"BigInt": createNativeType("BigInt"),
};

(Object as any)[REFLECTED_TYPE] = nativeTypes.Object;
(String as any)[REFLECTED_TYPE] = nativeTypes.String;
(Number as any)[REFLECTED_TYPE] = nativeTypes.Number;
(Boolean as any)[REFLECTED_TYPE] = nativeTypes.Boolean;
(Date as any)[REFLECTED_TYPE] = nativeTypes.Date;

/**
* @internal
*/
Expand Down
8 changes: 6 additions & 2 deletions tests/src/07-gettype-of-runtime-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ import {
} from "tst-reflect";

test("getType(val) called with runtime value and it is not Type.Unknown", () => {
@reflect()
// @reflect() decorator required in some configurations
class A
{
constructor(public foo: string)
{
}
}

const a: unknown = new A();
const a: unknown = new A("Lipsum");

expect(getType(a) instanceof Type).toBe(true);
expect(getType(a)).not.toBe(Type.Unknown);
expect(getType(a).name).toBe("A");
expect(getType(a)).toBe(getType<A>());
});
33 changes: 32 additions & 1 deletion tests/src/08-native-types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Type } from "tst-reflect";
import {
getType,
Type
} from "tst-reflect";

test("All native types are correct", () => {
expect(Type.Unknown.name).toBe("unknown");
Expand All @@ -10,4 +13,32 @@ test("All native types are correct", () => {
expect(Type.String.name).toBe("String");
expect(Type.Boolean.name).toBe("Boolean");
expect(Type.Date.name).toBe("Date");
});

test("Type of number is Type.Number", () => {
expect(getType(5)).toBe(Type.Number);
});

test("Type of string is Type.String", () => {
expect(getType("foo")).toBe(Type.String);
});

test("Type of boolean is Type.Boolean", () => {
expect(getType(true)).toBe(Type.Boolean);
});

test("Type of undefined is Type.Undefined", () => {
expect(getType(undefined)).toBe(Type.Undefined);
});

test("Type of null is Type.Null", () => {
expect(getType(null)).toBe(Type.Null);
});

test("Type of Date is Type.Date", () => {
expect(getType(new Date())).toBe(Type.Date);
});

test("Base type of object literal is Type.Object", () => {
expect(getType({}).baseType).toBe(Type.Object);
});
30 changes: 24 additions & 6 deletions tests/src/09-reflect-decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,30 @@ import {
Type
} from "tst-reflect";

test("@reflect decorator force type to appear in metadata", () => {
@reflect()
class A
{
}
@reflect()
class A
{
}

/**
* @reflect
*/
function foo()
{
return function(_: any) {}
}

@foo()
class B
{
}

expect(Type.getTypes()).toHaveLength(1);
test("@reflect decorator force type to appear in metadata", () => {
expect(Type.getTypes()).toHaveLength(2);
expect(Type.getTypes()[0].name).toBe("A");
});

test("Custom tagged decorator force type to appear in metadata", () => {
expect(Type.getTypes()).toHaveLength(2);
expect(Type.getTypes()[1].name).toBe("B");
});
16 changes: 16 additions & 0 deletions tests/src/16-base-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {
getType,
Type
} from "tst-reflect";

test("getType(val) called with runtime value and it is not Type.Unknown", () => {
const type = getType({});

expect(type).toBeDefined();
expect(type instanceof Type).toBe(true);
expect(type).not.toBe(Type.Unknown);
expect(type.isObjectLiteral()).toBeTruthy();

expect(type.baseType).toBe(Type.Object);
expect(type.baseType!.baseType).toBe(undefined);
});
82 changes: 82 additions & 0 deletions tests/src/17-assignable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { getType } from "tst-reflect";

interface ISomeInterface
{
stringProp: string;
numberProp: number;
booleanProp: boolean;
arrayProp: Array<string>;
stringOrNumber: string | number;
}

class SomeClass
{
stringProp: string;
anyProp: any;
stringArrayProp: string[];

constructor(stringProp: string, stringArrayProp: string[])
{
this.stringProp = stringProp;
this.stringArrayProp = stringArrayProp;
}

optionalMethod?(this: ISomeInterface, size: number): void
{
}
}

test("Type.isAssignableTo()", () => {
const someObject = {
anyProp: true,
stringProp: "",
stringArrayProp: ["foo"],

optionalMethod()
{
}
};

const someObject2 = {
stringProp: "",
numberProp: 123,
booleanProp: true,
arrayProp: ["foo"],
stringOrNumber: 0
};

const someObject3 = {
stringProp: "",
numberProp: 123
};

const someObject4 = {
anyProp: new Date(),
stringProp: "",
numberProp: 123,
booleanProp: false,
stringArrayProp: ["foo"],
stringOrNumber: "lorem",
arrayProp: ["bar"]
};

const someInterfaceType = getType<ISomeInterface>();
const someClassType = getType<SomeClass>();

const obj1Type = getType(someObject);
const obj2Type = getType(someObject2);
const obj3Type = getType(someObject3);
const obj4Type = getType(someObject4);

expect(obj1Type.isAssignableTo(someInterfaceType)).toBeFalsy();
expect(obj1Type.isAssignableTo(someClassType)).toBeTruthy();

expect(obj2Type.isAssignableTo(someInterfaceType)).toBeTruthy();
expect(obj2Type.isAssignableTo(someClassType)).toBeFalsy();

expect(obj3Type.isAssignableTo(someInterfaceType)).toBeFalsy();
expect(obj3Type.isAssignableTo(someClassType)).toBeFalsy();

expect(obj4Type.isAssignableTo(someInterfaceType)).toBeTruthy();
expect(obj4Type.isAssignableTo(someClassType)).toBeTruthy();
});
2 changes: 1 addition & 1 deletion transformer/src/getTypeCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const creatingTypes: Array<number> = [];
* @param context
* @param typeCtor
*/
export function getTypeCall(type: ts.Type, symbol: ts.Symbol | undefined, context: Context, typeCtor?: ts.EntityName | ts.DeclarationName): GetTypeCall // TODO: Remove symbol parameter
export function getTypeCall(type: ts.Type, symbol: ts.Symbol | undefined, context: Context, typeCtor?: ts.EntityName | ts.DeclarationName): GetTypeCall // TODO: Remove symbol parameter if possible
{
const id: number | undefined = (type.aliasSymbol || type.symbol as any)?.["id"];
let typePropertiesObjectLiteral: ts.ObjectLiteralExpression | undefined = undefined;
Expand Down
27 changes: 18 additions & 9 deletions transformer/src/processDecorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { updateCallExpression } from "./updateCallExpr
export function processDecorator(node: ts.Decorator, decoratorType: ts.Type, context: Context): ts.Decorator | undefined
{
// Method/function declaration
const declaration = decoratorType.symbol.declarations?.[0] as ts.FunctionLikeDeclarationBase;
const declaration = getDeclaration(decoratorType.symbol) as ts.FunctionLikeDeclarationBase;

if (!declaration)
{
Expand All @@ -22,14 +22,6 @@ export function processDecorator(node: ts.Decorator, decoratorType: ts.Type, con
// Try to get State
const state: FunctionLikeDeclarationGenericParametersDetail = getGenericParametersDetails(declaration, context, []);

if (!state || !state.usedGenericParameters || !state.indexesOfGenericParameters || !state.requestedGenericsReflection)
{
return undefined;
}

// Decorator has no generic parameters in nature; we just abusing it so only one generic parameter makes sense
const genericParamName = state.usedGenericParameters[0];

// Type of Class
let genericTypeNode: ts.NamedDeclaration, genericType: ts.Type;

Expand All @@ -46,6 +38,23 @@ export function processDecorator(node: ts.Decorator, decoratorType: ts.Type, con

const genericTypeSymbol = genericType.getSymbol();

if (!state || !state.usedGenericParameters || !state.indexesOfGenericParameters || !state.requestedGenericsReflection)
{
// Decorator does not accept generic type argument but processDecorator was
// forced by @reflect, so we'll generate metadata and keep decorator as is.
getTypeCall(
genericType,
genericTypeSymbol,
context,
genericTypeNode.name
);

return undefined;
}

// Decorator has no generic parameters in nature; we just abusing it so only one generic parameter makes sense
const genericParamName = state.usedGenericParameters[0];

let callExpression: ts.CallExpression;
const typeArgumentDescription = {
genericTypeName: genericParamName,
Expand Down
Loading

0 comments on commit 80c6b9d

Please sign in to comment.