Skip to content

Commit

Permalink
feat(jsto): support for classses
Browse files Browse the repository at this point in the history
  • Loading branch information
MrTelanie committed Oct 16, 2021
1 parent c27f397 commit 676fd4a
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 38 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"no-shadow": "off",
"no-unused-vars": "warn",
"arrow-body-style": "off",
"prefer-arrow-callback": "off"
"prefer-arrow-callback": "off",
"no-useless-constructor": "off"
}
}
8 changes: 5 additions & 3 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ type TypeOf<T> = TypedClass<T> | GetNumber<T> | GetString<T> | GetBoolean<T> | T

declare function typ<T>(type: TypeOf<T>): T;

declare function fun<F>(func: F): F extends (...args: any) => void ? (...args: (Required<Parameters<F>> & Array<any>)) => void : ExpectedReturnType<void>;
declare function fun<F, T>(type: TypeOf<T>, func: F): F extends (...args: any) => T ? (...args: (Required<Parameters<F>> & Array<any>)) => T : ExpectedReturnType<T>;
type Fun<R> = (...args: any) => R;

declare function cls<T>(prototype: T): T extends { constructor?(...args: any): void, [key: string]: unknown } ? new (...args: (Parameters<T['constructor']>)) => T : (new () => T);
declare function fun<F>(func: F): F extends Fun<void> ? (...args: (Required<Parameters<F>> & Array<any>)) => void : ExpectedReturnType<void>;
declare function fun<F, T>(type: TypeOf<T>, func: F): F extends Fun<T> ? (...args: (Required<Parameters<F>> & Array<any>)) => T : ExpectedReturnType<T>;

declare function cls<T>(clazz: T): T extends abstract new (...args: any) => any ? new (...args: (Required<ConstructorParameters<T>> & Array<any>)) => InstanceType<T> : never;
104 changes: 81 additions & 23 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { getSource, readOnlyView, isInstanceOf } from './utils';

const TYPED_FUN = Symbol('typed function');
const TYPED_PROP = Symbol('typed property');
const INNER_DATA = Symbol('inner data');

let activeArgList;
let activeArgIndex;

let insideConstructor = false;

export const string = String;
export const number = Number;
export const boolean = Boolean;
Expand All @@ -15,6 +18,13 @@ export function typ(typeDesc) {
if (!type) {
throw new Error('no type assigned');
}
if ((!activeArgList || activeArgIndex === activeArgList.length) && insideConstructor) {
return {
[TYPED_PROP]: true,
type,
def
};
}
if (activeArgList) {
let arg = activeArgList[activeArgIndex];
activeArgIndex += 1;
Expand Down Expand Up @@ -60,13 +70,80 @@ export function fun(type, func) {
}

export function cls(classDesc) {
if (!(typeof classDesc === 'object')) {
return classDesc;
let { constructor, ...proto } = classDesc;

let realClass = false;
if (typeof classDesc === 'function') {
realClass = true;
constructor = classDesc;
proto = classDesc.prototype;
}
const { constructor, ...proto } = classDesc;

const types = {};
const prototype = {};

let Con;
if (realClass) {
Con = class Con extends classDesc {

constructor(...args) {
activeArgList = args;
activeArgIndex = 0;
insideConstructor = true;

try {
super();
if (activeArgList && activeArgIndex < activeArgList.length) {
throw new Error(`wrong argument length? expected: ${activeArgIndex} is: ${activeArgList.length}`);
}
} finally {
activeArgList = undefined;
activeArgIndex = -999;
insideConstructor = false;
}

Object.entries(this)
.forEach(([key, prop]) => {
if (!prop || !prop[TYPED_PROP]) {
return;
}
const { type, def } = prop;

if (def !== undefined) {
checkType(def, type, `check type for setter of type ${type}`);
}
Object.defineProperty(this, key, {
get() {
return getInner(this, key);
},
set(value) {
checkType(value, type, `check type for setter of type ${type}`);
return setInner(this, key, value);
}
});
});
}
};
} else {
Con = function Constructor(...args) {
if (isInstanceOf(this, Constructor)) {
constructor.apply(this, args);
} else {
if (!args.length) {
return typ(Con);
}
const [funDef] = args;
if (typeof funDef !== 'function') {
if (isInstanceOf(funDef, Con)) {
return funDef;
}
throw new Error('only fun definition allowd');
}
return fun(Con, funDef);
}
};
}

const { prototype } = Con;

Object.entries(proto).forEach(([key, val]) => {
if (!val) {
Expand Down Expand Up @@ -95,25 +172,6 @@ export function cls(classDesc) {
}
});
});
const Con = function Constructor(...args) {
if (isInstanceOf(this, Constructor)) {
constructor.apply(this, args);
} else {
if (!args.length) {
return typ(Con);
}
const [funDef] = args;
if (typeof funDef !== 'function') {
if (isInstanceOf(funDef, Con)) {
return funDef;
}
throw new Error('only fun definition allowd');
}
return fun(Con, funDef);
}
};

Con.prototype = prototype;

return readOnlyView(Con);
}
Expand Down
39 changes: 28 additions & 11 deletions test/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@ describe('type safety tests', () => {
const bool2 = typ(Boolean);
const bool3 = typ(true);

const C = cls({
// h: typ(string),
constructor: fun((hello = typ(string)) => {
// this.h = hello;
}),
hallo: fun(string, (hello = typ(string)) => {
const C = cls(class {

h = typ(string);

constructor(hello = typ(string)) {
this.he = hello;
}

hallo = fun(string, (hello = typ(string)) => {

return hello;
})
});
});

const fun1 = fun(string, (str = typ(string), num = typ(number)) => {
Expand All @@ -42,15 +45,19 @@ describe('type safety tests', () => {
const world = c.hallo('world');

assert.equal(world, 'world');

c.h = 'foo';

assert.equal(c.h, 'foo');
});

it('throw errors on invalid typings', () => {
const funBroken = fun(number, (str = typ(string), blub = typ(number)) => {

return 'bar';
});

assert.throws(() => {
const funBroken = fun(number, (str = typ(string), blub = typ(number)) => {

return 'bar';
});
funBroken('1', 2);
});

Expand All @@ -62,8 +69,18 @@ describe('type safety tests', () => {
c.foo('');
});

assert.throws(() => {
c.hallo(1, 2);
});

assert.throws(() => {
const c2 = new C(1);
});

assert.throws(() => {
const c2 = new C('');

c2.h = 1;
});
});
});

0 comments on commit 676fd4a

Please sign in to comment.