Skip to content

Commit

Permalink
refactor: tweak tslint and clean code according to it
Browse files Browse the repository at this point in the history
Closes #10
  • Loading branch information
Noémi Salaün committed Jul 25, 2017
1 parent 2091945 commit 57670d0
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 47 deletions.
4 changes: 2 additions & 2 deletions karma.conf.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as webpack from 'webpack';
import * as ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
const ForkTsCheckerWebpackPlugin: any = require('fork-ts-checker-webpack-plugin');

export default config => {
export default (config: any) => {

config.set({

Expand Down
4 changes: 3 additions & 1 deletion src/decorator/deserialize-as.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'reflect-metadata';
import { Instantiable } from '../instantiable';

/**
* Tags a property to be deserialized as a given class.
*
Expand All @@ -12,7 +14,7 @@ import 'reflect-metadata';
*
* @decorator Property
*/
export function DeserializeAs(clazz: { new(...args: any[]): any }): (...args: any[]) => void {
export function DeserializeAs(clazz: Instantiable): (...args: any[]) => void {
return (target: any, propertyKey: string) => {
return Reflect.defineMetadata('serializer:class', clazz, target, propertyKey);
};
Expand Down
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './serializer';
export * from './registration';
export * from './decorator/index';
export * from './instantiable';
export * from './registration';
export * from './serializer';
4 changes: 4 additions & 0 deletions src/instantiable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Type for instantiable classname.
*/
export type Instantiable<T = any> = {new(...args: any[]): T};
4 changes: 3 additions & 1 deletion src/registration.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Instantiable } from './instantiable';

/**
* Registrations allow the serializer to handle inheritance,
* they have to be defined if you want to be able to handle inheritance.
Expand All @@ -19,5 +21,5 @@
*/
export interface Registration {
parent: any;
children: { [index: string]: { new(...args: any[]): any } };
children: { [index: string]: Instantiable };
}
54 changes: 28 additions & 26 deletions src/serializer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'reflect-metadata';
import { Registration } from './registration';
import { ParentOptions } from './decorator/parent-options';
import { Instantiable } from './instantiable';

/**
* The main class of the serializer, used to deserialize `Objects` into class instances in order to add
Expand All @@ -24,17 +25,13 @@ export class Serializer {

/**
* Our current registrations for inheritance handling.
* @type {Array}
* @private
*/
private _registrations: Registration[] = [];

/**
* Gets the discriminator field for a given class.
* @param clazz
* @returns string | undefined The discriminator field name if present, else undefined.
*/
private static getParentOptions(clazz: new(...args: any[]) => any): ParentOptions {
private static getParentOptions(clazz: Instantiable): ParentOptions | undefined {
return Reflect.getMetadata('serializer:parent', clazz);
}

Expand All @@ -45,10 +42,10 @@ export class Serializer {
public register(registration: Registration[]): void {
for (const reg of registration) {

const parentOption: ParentOptions = Serializer.getParentOptions(reg.parent);
const parentOption = Serializer.getParentOptions(reg.parent);

for (const value in reg.children) {
const child: { new(...args: any[]): any } = reg.children[value];
const child = reg.children[value];

//Check if the child is the parent itself
if (child === reg.parent) {
Expand All @@ -74,26 +71,26 @@ export class Serializer {
* ```
* @param obj The object, usually coming from a simple `JSON.parse`
* @param clazz The class constructor.
* @returns {T} an instance of the type `T` with the prototype of `clazz` or one of its registered children.
* @returns An instance of the type `T` with the prototype of `clazz` or one of its registered children.
*/
public deserialize<T>(obj: any, clazz?: any): T {
//if the class parameter is not specified, we can directly return the basic object.
if (!clazz) {
return obj;
}
//First of all, we'll create an instance of our class
let result: T = this.getInstance<T>(obj, clazz);
const result: any = this.getInstance<T>(obj, clazz);
//Then we copy every property of our object to our clazz
for (let prop in obj) {
for (const prop in obj) {
//Simple check to avoid iterations over strange things.
if (obj.hasOwnProperty(prop)) {
//We get our metadata for the class to deserialize
let metadata: new() => any = Reflect.getMetadata('serializer:class', result, prop);
const metadata: new() => any = Reflect.getMetadata('serializer:class', result, prop);
//If we have some metadata, we'll handle them
if (metadata !== undefined) {
if (obj[prop] instanceof Array) {
result[prop] = [];
for (let item of obj[prop]) {
for (const item of obj[prop]) {
result[prop].push(this.deserialize(item, metadata));
}
} else {
Expand All @@ -105,7 +102,7 @@ export class Serializer {
}
}
}
return result;
return result as T;
}

/**
Expand All @@ -114,15 +111,15 @@ export class Serializer {
*
* @param obj The object we need a class for.
* @param clazz The base class of the object, can be an abstract class.
* @returns {any} An instance of the class we wanted.
* @returns An instance of the class we wanted.
*/
private getInstance<T>(obj: any, clazz: new(...args: any[]) => T): T {
const parentOptions: ParentOptions = Serializer.getParentOptions(clazz);
private getInstance<T>(obj: any, clazz: Instantiable<T>): T {
const parentOptions = Serializer.getParentOptions(clazz);
// If we don't have metadata for inheritance, we can return the instance of the class we created.
if (parentOptions === undefined) {
return new clazz();
}
const discriminatorValue: any = obj[parentOptions.discriminatorField];
const discriminatorValue = obj[parentOptions.discriminatorField];
// In case of missing discriminator value...
if (discriminatorValue === undefined || discriminatorValue === null) {
// ...check if the parent allows itself and no explicit discriminators are defined.
Expand All @@ -132,11 +129,14 @@ export class Serializer {

return new clazz();
}
let resultConstructor: new() => any = this.getClass(clazz, obj, parentOptions);
const resultConstructor = this.getClass(clazz, obj, parentOptions);
return new resultConstructor();
}

private parentHasExplicitDiscriminator(clazz: new(...args: any[]) => any): boolean {
/**
* Check if the given parent class has explicitly defined its discriminator value.
*/
private parentHasExplicitDiscriminator(clazz: Instantiable): boolean {
for (const reg of this._registrations) {
// Ignore registrations that does not concern this parent.
if (reg.parent !== clazz) {
Expand All @@ -159,12 +159,12 @@ export class Serializer {
* @param parent The parent class of our current class.
* @param obj The javascript object with data inside.
* @param options The Options used to configure the parent class.
* @returns constructor The constructor of the class we're looking for, or the parent constructor if none is found.
* @returns The constructor of the class we're looking for, or the parent constructor if none is found.
*/
private getClass(parent: any, obj: any, options: ParentOptions): new() => any {
let discriminatorValue: string = obj[options.discriminatorField];
let children: { [index: string]: { new(...args: any[]): any } } = {};
for (let entry of this._registrations) {
private getClass(parent: any, obj: any, options: ParentOptions): Instantiable {
const discriminatorValue: string = obj[options.discriminatorField];
let children: { [index: string]: Instantiable } = {};
for (const entry of this._registrations) {
// If the parent of this entry is the one we're looking for.
// This allows to declare a map for the same parent in different modules.
if (entry.parent === parent) {
Expand All @@ -180,9 +180,11 @@ export class Serializer {
`with discriminator value ${obj[options.discriminatorField]}`);
}
}
let childOptions: ParentOptions = Serializer.getParentOptions(children[discriminatorValue]);
const childOptions = Serializer.getParentOptions(children[discriminatorValue]);
// If the child used has children too.
if (childOptions.discriminatorField !== undefined && childOptions.discriminatorField !== options.discriminatorField) {
if (childOptions !== undefined
&& childOptions.discriminatorField !== undefined
&& childOptions.discriminatorField !== options.discriminatorField) {
return this.getClass(children[discriminatorValue], obj, childOptions);
}
return children[discriminatorValue];
Expand Down
10 changes: 5 additions & 5 deletions test/serializer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ describe('Serializer service', () => {
}));

it('Should deserialize class instance', (() => {
let res: Foo = serializer.deserialize<Foo>({
const res = serializer.deserialize<Foo>({
'attrString': 'val',
'attrNumber': 5,
'attrBoolean': true
Expand All @@ -191,8 +191,8 @@ describe('Serializer service', () => {
}));

it('Has to be able to handle object instances array inside an object', (() => {
let example: BazArray = new BazArray();
let bar: Bar = new Bar();
const example = new BazArray();
const bar = new Bar();
bar.prop = 'hey';
example.bars = [];
example.bars.push(bar);
Expand Down Expand Up @@ -308,7 +308,7 @@ describe('Serializer service', () => {
});

it('Should throw if the parent discriminator is explicitly defined and the discriminator value is missing', () => {
const expectedError: string = 'Missing attribute type to discriminate the subclass of AbstractParentExample';
const expectedError = 'Missing attribute type to discriminate the subclass of AbstractParentExample';

expect(() => serializer.deserialize<AbstractParentExample>({type: null}, AbstractParentExample))
.to.throw(TypeError, expectedError);
Expand All @@ -329,7 +329,7 @@ describe('Serializer service', () => {
],
);

const expectedError: string = 'Missing attribute type to discriminate the subclass of AbstractParentExample';
const expectedError = 'Missing attribute type to discriminate the subclass of AbstractParentExample';

expect(() => serializer.deserialize<AbstractParentExample>({type: null}, AbstractParentExample))
.to.throw(TypeError, expectedError);
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false,
"noImplicitAny": true,
"noUnusedLocals": true,
"lib": [
"es2015",
"dom"
Expand Down
40 changes: 32 additions & 8 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"no-duplicate-variable": true,
"no-empty": true,
"no-eval": true,
"no-inferrable-types": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"one-line": [
Expand All @@ -28,6 +29,7 @@
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
Expand All @@ -37,23 +39,45 @@
true,
"allow-null-check"
],
"variable-name": false,
"variable-name": [
true,
"check-format",
"ban-keywords",
"allow-leading-underscore",
"allow-pascal-case"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-module",
"check-separator",
"check-type"
"check-type",
"check-typecast",
"check-preblock"
],
"typedef": [
true
],
"typedef-whitespace": [
true,
"call-signature",
"parameter",
"property-declaration",
"variable-declaration",
"member-variable-declaration"
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
],
"no-access-missing-member": false
"no-access-missing-member": false,
"no-unnecessary-type-assertion": false
}
}
3 changes: 2 additions & 1 deletion webpack.config.umd.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as path from 'path';
import * as fs from 'fs';
import * as webpack from 'webpack';
import * as UglifyJSPlugin from 'uglifyjs-webpack-plugin';

const UglifyJSPlugin: any = require('uglifyjs-webpack-plugin');

const pkg = JSON.parse(fs.readFileSync('./package.json').toString());

Expand Down

0 comments on commit 57670d0

Please sign in to comment.