Skip to content

Commit 676fd4a

Browse files
committed
feat(jsto): support for classses
1 parent c27f397 commit 676fd4a

File tree

4 files changed

+116
-38
lines changed

4 files changed

+116
-38
lines changed

.eslintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"no-shadow": "off",
3131
"no-unused-vars": "warn",
3232
"arrow-body-style": "off",
33-
"prefer-arrow-callback": "off"
33+
"prefer-arrow-callback": "off",
34+
"no-useless-constructor": "off"
3435
}
3536
}

src/index.d.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ type TypeOf<T> = TypedClass<T> | GetNumber<T> | GetString<T> | GetBoolean<T> | T
1818

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

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

24-
declare function cls<T>(prototype: T): T extends { constructor?(...args: any): void, [key: string]: unknown } ? new (...args: (Parameters<T['constructor']>)) => T : (new () => T);
23+
declare function fun<F>(func: F): F extends Fun<void> ? (...args: (Required<Parameters<F>> & Array<any>)) => void : ExpectedReturnType<void>;
24+
declare function fun<F, T>(type: TypeOf<T>, func: F): F extends Fun<T> ? (...args: (Required<Parameters<F>> & Array<any>)) => T : ExpectedReturnType<T>;
25+
26+
declare function cls<T>(clazz: T): T extends abstract new (...args: any) => any ? new (...args: (Required<ConstructorParameters<T>> & Array<any>)) => InstanceType<T> : never;

src/index.js

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { getSource, readOnlyView, isInstanceOf } from './utils';
22

33
const TYPED_FUN = Symbol('typed function');
4+
const TYPED_PROP = Symbol('typed property');
45
const INNER_DATA = Symbol('inner data');
56

67
let activeArgList;
78
let activeArgIndex;
89

10+
let insideConstructor = false;
11+
912
export const string = String;
1013
export const number = Number;
1114
export const boolean = Boolean;
@@ -15,6 +18,13 @@ export function typ(typeDesc) {
1518
if (!type) {
1619
throw new Error('no type assigned');
1720
}
21+
if ((!activeArgList || activeArgIndex === activeArgList.length) && insideConstructor) {
22+
return {
23+
[TYPED_PROP]: true,
24+
type,
25+
def
26+
};
27+
}
1828
if (activeArgList) {
1929
let arg = activeArgList[activeArgIndex];
2030
activeArgIndex += 1;
@@ -60,13 +70,80 @@ export function fun(type, func) {
6070
}
6171

6272
export function cls(classDesc) {
63-
if (!(typeof classDesc === 'object')) {
64-
return classDesc;
73+
let { constructor, ...proto } = classDesc;
74+
75+
let realClass = false;
76+
if (typeof classDesc === 'function') {
77+
realClass = true;
78+
constructor = classDesc;
79+
proto = classDesc.prototype;
6580
}
66-
const { constructor, ...proto } = classDesc;
6781

6882
const types = {};
69-
const prototype = {};
83+
84+
let Con;
85+
if (realClass) {
86+
Con = class Con extends classDesc {
87+
88+
constructor(...args) {
89+
activeArgList = args;
90+
activeArgIndex = 0;
91+
insideConstructor = true;
92+
93+
try {
94+
super();
95+
if (activeArgList && activeArgIndex < activeArgList.length) {
96+
throw new Error(`wrong argument length? expected: ${activeArgIndex} is: ${activeArgList.length}`);
97+
}
98+
} finally {
99+
activeArgList = undefined;
100+
activeArgIndex = -999;
101+
insideConstructor = false;
102+
}
103+
104+
Object.entries(this)
105+
.forEach(([key, prop]) => {
106+
if (!prop || !prop[TYPED_PROP]) {
107+
return;
108+
}
109+
const { type, def } = prop;
110+
111+
if (def !== undefined) {
112+
checkType(def, type, `check type for setter of type ${type}`);
113+
}
114+
Object.defineProperty(this, key, {
115+
get() {
116+
return getInner(this, key);
117+
},
118+
set(value) {
119+
checkType(value, type, `check type for setter of type ${type}`);
120+
return setInner(this, key, value);
121+
}
122+
});
123+
});
124+
}
125+
};
126+
} else {
127+
Con = function Constructor(...args) {
128+
if (isInstanceOf(this, Constructor)) {
129+
constructor.apply(this, args);
130+
} else {
131+
if (!args.length) {
132+
return typ(Con);
133+
}
134+
const [funDef] = args;
135+
if (typeof funDef !== 'function') {
136+
if (isInstanceOf(funDef, Con)) {
137+
return funDef;
138+
}
139+
throw new Error('only fun definition allowd');
140+
}
141+
return fun(Con, funDef);
142+
}
143+
};
144+
}
145+
146+
const { prototype } = Con;
70147

71148
Object.entries(proto).forEach(([key, val]) => {
72149
if (!val) {
@@ -95,25 +172,6 @@ export function cls(classDesc) {
95172
}
96173
});
97174
});
98-
const Con = function Constructor(...args) {
99-
if (isInstanceOf(this, Constructor)) {
100-
constructor.apply(this, args);
101-
} else {
102-
if (!args.length) {
103-
return typ(Con);
104-
}
105-
const [funDef] = args;
106-
if (typeof funDef !== 'function') {
107-
if (isInstanceOf(funDef, Con)) {
108-
return funDef;
109-
}
110-
throw new Error('only fun definition allowd');
111-
}
112-
return fun(Con, funDef);
113-
}
114-
};
115-
116-
Con.prototype = prototype;
117175

118176
return readOnlyView(Con);
119177
}

test/types.js

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@ describe('type safety tests', () => {
1515
const bool2 = typ(Boolean);
1616
const bool3 = typ(true);
1717

18-
const C = cls({
19-
// h: typ(string),
20-
constructor: fun((hello = typ(string)) => {
21-
// this.h = hello;
22-
}),
23-
hallo: fun(string, (hello = typ(string)) => {
18+
const C = cls(class {
19+
20+
h = typ(string);
21+
22+
constructor(hello = typ(string)) {
23+
this.he = hello;
24+
}
25+
26+
hallo = fun(string, (hello = typ(string)) => {
2427

2528
return hello;
26-
})
29+
});
2730
});
2831

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

4447
assert.equal(world, 'world');
48+
49+
c.h = 'foo';
50+
51+
assert.equal(c.h, 'foo');
4552
});
4653

4754
it('throw errors on invalid typings', () => {
48-
const funBroken = fun(number, (str = typ(string), blub = typ(number)) => {
49-
50-
return 'bar';
51-
});
5255

5356
assert.throws(() => {
57+
const funBroken = fun(number, (str = typ(string), blub = typ(number)) => {
58+
59+
return 'bar';
60+
});
5461
funBroken('1', 2);
5562
});
5663

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

72+
assert.throws(() => {
73+
c.hallo(1, 2);
74+
});
75+
6576
assert.throws(() => {
6677
const c2 = new C(1);
6778
});
79+
80+
assert.throws(() => {
81+
const c2 = new C('');
82+
83+
c2.h = 1;
84+
});
6885
});
6986
});

0 commit comments

Comments
 (0)