cfprotected 1.1.0 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/launch.json +9 -5
- package/README.md +3 -0
- package/index.mjs +93 -53
- package/package.json +1 -1
- package/test/test.mjs +67 -13
package/.vscode/launch.json
CHANGED
|
@@ -4,16 +4,20 @@
|
|
|
4
4
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
5
|
"version": "0.2.0",
|
|
6
6
|
"configurations": [
|
|
7
|
+
|
|
7
8
|
{
|
|
8
9
|
"type": "node",
|
|
9
10
|
"request": "launch",
|
|
10
11
|
"name": "Jest Tests",
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"${workspaceRoot}/
|
|
12
|
+
"runtimeArgs": [
|
|
13
|
+
"--experimental-vm-modules",
|
|
14
|
+
"--inspect-brk",
|
|
15
|
+
"${workspaceRoot}/node_modules/jest/bin/jest.js",
|
|
16
|
+
"--runInBand"
|
|
15
17
|
],
|
|
16
|
-
"
|
|
18
|
+
"internalConsoleOptions": "neverOpen",
|
|
19
|
+
"console": "integratedTerminal",
|
|
20
|
+
"port": 9229
|
|
17
21
|
}
|
|
18
22
|
]
|
|
19
23
|
}
|
package/README.md
CHANGED
|
@@ -119,6 +119,9 @@ const Example = final(class {
|
|
|
119
119
|
});
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
### Notes:
|
|
123
|
+
In order to ensure the functionality of final would not interfere with the ability to access static members, final is implemented using a Proxy. It is therefore very important to use `saveSelf(...)` and the resulting property (or a similar approach) to ensure that access to static private properties is not interrupted.
|
|
124
|
+
|
|
122
125
|
## Other features
|
|
123
126
|
There will be occasions when a shared function that shadows an ancestor function needs to call the ancestor's function. Unfortunately, `super` cannot give you access to these. There is a similar problem when accessing accessors and data properties. To satisfy this need, the class-specific accessor option is given an additional property: `$uper`. Using this property, it is possible to reach the ancestor version of any shared member.
|
|
124
127
|
|
package/index.mjs
CHANGED
|
@@ -8,6 +8,28 @@ function getAllOwnKeys(o) {
|
|
|
8
8
|
.concat(Object.getOwnPropertySymbols(o));
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
function useDescriptor(desc, fn) {
|
|
12
|
+
if ("value" in desc) {
|
|
13
|
+
if (typeof(desc.value) == "function") {
|
|
14
|
+
fn("value");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
if (typeof(desc.get) == "function") {
|
|
19
|
+
fn("get");
|
|
20
|
+
}
|
|
21
|
+
if (typeof(desc.set) == "function") {
|
|
22
|
+
fn("set");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function bindDescriptor(desc, context) {
|
|
28
|
+
useDescriptor(desc, (key) => {
|
|
29
|
+
desc[key] = desc[key].bind(context);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
11
33
|
/**
|
|
12
34
|
* Used to both store inherited property information as well as retrieve it.
|
|
13
35
|
* @param {Object} inst The instance object that will own the shared members.
|
|
@@ -47,7 +69,7 @@ function share(inst, klass, members) {
|
|
|
47
69
|
* }
|
|
48
70
|
*/
|
|
49
71
|
|
|
50
|
-
//Find the nearest known registered ancestor
|
|
72
|
+
//Find the nearest known registered ancestor class
|
|
51
73
|
let ancestor = Object.getPrototypeOf(klass);
|
|
52
74
|
while (ancestor && !memos.has(ancestor)) {
|
|
53
75
|
ancestor = Object.getPrototypeOf(ancestor);
|
|
@@ -56,43 +78,49 @@ function share(inst, klass, members) {
|
|
|
56
78
|
//Get the memo from that ancestor
|
|
57
79
|
let ancestorMemo = memos.get(ancestor) || new WeakMap();
|
|
58
80
|
|
|
59
|
-
//Create a memo for the current class
|
|
81
|
+
//Create a memo map for the current class
|
|
60
82
|
if (!memos.has(klass)) {
|
|
61
83
|
memos.set(klass, new WeakMap());
|
|
62
84
|
}
|
|
63
85
|
|
|
64
86
|
//Get the protected data object.
|
|
65
|
-
let
|
|
87
|
+
let ancestorKey = (inst === klass) ? ancestor : inst;
|
|
88
|
+
let memo = ancestorMemo.get(ancestorKey) || {data: {}, $uper: {}, inheritance: null};
|
|
66
89
|
let protData = memo.data;
|
|
67
90
|
|
|
68
91
|
//Get the details of the protected properties.
|
|
69
92
|
let mDesc = Object.getOwnPropertyDescriptors(members);
|
|
70
93
|
let mKeys = getAllOwnKeys(members);
|
|
71
94
|
|
|
72
|
-
//
|
|
95
|
+
//Add the new members to the prototype chain of protData.
|
|
73
96
|
let prototype = Object.getPrototypeOf(protData);
|
|
74
97
|
let proto = Object.create(prototype,
|
|
75
98
|
Object.fromEntries(mKeys
|
|
76
|
-
.
|
|
77
|
-
|
|
99
|
+
.map(k => {
|
|
100
|
+
if (mDesc[k].value?.hasOwnProperty(ACCESSOR)) {
|
|
101
|
+
Object.assign(mDesc[k], mDesc[k].value);
|
|
102
|
+
mDesc[k].enumerable = true;
|
|
103
|
+
delete mDesc[k][ACCESSOR];
|
|
104
|
+
delete mDesc[k].value;
|
|
105
|
+
delete mDesc[k].writable;
|
|
106
|
+
}
|
|
107
|
+
bindDescriptor(mDesc[k], inst);
|
|
108
|
+
return [k, mDesc[k]];
|
|
109
|
+
})));
|
|
78
110
|
Object.setPrototypeOf(protData, proto);
|
|
79
111
|
|
|
80
112
|
//Build the accessors for this class.
|
|
81
113
|
mKeys.forEach(m => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
set: mDesc[m].value.set
|
|
87
|
-
}
|
|
88
|
-
: mDesc[m];
|
|
89
|
-
|
|
90
|
-
Object.defineProperty(retval, m, desc);
|
|
114
|
+
Object.defineProperty(retval, m, {
|
|
115
|
+
get() { return protData[m]; },
|
|
116
|
+
set(v) { protData[m] = v; }
|
|
117
|
+
});
|
|
91
118
|
});
|
|
92
119
|
|
|
93
|
-
//Define the "
|
|
120
|
+
//Define the "$uper" accessors
|
|
94
121
|
Object.defineProperty(retval, "$uper", { value: {} });
|
|
95
122
|
|
|
123
|
+
//Build up the "$uper" object
|
|
96
124
|
for (let key of mKeys) {
|
|
97
125
|
if (key in prototype) {
|
|
98
126
|
let obj = prototype;
|
|
@@ -167,6 +195,13 @@ function abstract(klass) {
|
|
|
167
195
|
super(...args);
|
|
168
196
|
}
|
|
169
197
|
};
|
|
198
|
+
|
|
199
|
+
if (memos.has(klass)) {
|
|
200
|
+
let memo = memos.get(klass);
|
|
201
|
+
memos.set(retval, memo);
|
|
202
|
+
memo.set(retval, memo.get(klass));
|
|
203
|
+
}
|
|
204
|
+
|
|
170
205
|
return retval;
|
|
171
206
|
};
|
|
172
207
|
|
|
@@ -177,46 +212,51 @@ function abstract(klass) {
|
|
|
177
212
|
* @param {Function} klass The constructor of the current class.
|
|
178
213
|
*/
|
|
179
214
|
function final(klass) {
|
|
180
|
-
let
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
retval.prototype = void 0;
|
|
190
|
-
}
|
|
191
|
-
let inst = new klass(...args);
|
|
192
|
-
let proto = Object.create(klass.prototype, {
|
|
193
|
-
constructor: {
|
|
194
|
-
enumerable: true,
|
|
195
|
-
configurable: true,
|
|
196
|
-
writable: true,
|
|
197
|
-
value: retval
|
|
215
|
+
let retval = new Proxy(function() {}, {
|
|
216
|
+
handleDefault(fname, args) {
|
|
217
|
+
args.shift();
|
|
218
|
+
args.unshift(klass);
|
|
219
|
+
return Reflect[fname](...args);
|
|
220
|
+
},
|
|
221
|
+
construct(_, args, newTarget) {
|
|
222
|
+
if (newTarget !== retval) {
|
|
223
|
+
throw new TypeError("Cannot create an instance of a descendant of a final class");
|
|
198
224
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
value() { return klass.toString(); }
|
|
225
|
+
let inst = Reflect.construct(klass, args, newTarget);
|
|
226
|
+
let proto = Object.create(klass.prototype, {
|
|
227
|
+
constructor: {
|
|
228
|
+
enumerable: true,
|
|
229
|
+
configurable: true,
|
|
230
|
+
writable: true,
|
|
231
|
+
value: retval
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
Object.setPrototypeOf(inst, proto);
|
|
235
|
+
return inst;
|
|
211
236
|
},
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
237
|
+
get(_, prop, receiver) {
|
|
238
|
+
return (prop == "prototype")
|
|
239
|
+
? void 0
|
|
240
|
+
: Reflect.get(klass, prop, receiver);
|
|
215
241
|
},
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
242
|
+
set(...args) { return this.handleDefault("set", args); },
|
|
243
|
+
apply(...args) { return this.handleDefault("apply", args); },
|
|
244
|
+
defineProperty(...args) { return this.handleDefault("defineProperty", args); },
|
|
245
|
+
deleteProperty(...args) { return this.handleDefault("deleteProperty", args); },
|
|
246
|
+
getOwnPropertyDescriptor(...args) { return this.handleDefault("getOwnPropertyDescriptor", args); },
|
|
247
|
+
getPrototypeOf(...args) { return this.handleDefault("getPrototypeOf", args); },
|
|
248
|
+
has(...args) { return this.handleDefault("has", args); },
|
|
249
|
+
isExtensible(...args) { return this.handleDefault("isExtensible", args); },
|
|
250
|
+
ownKeys(...args) { return this.handleDefault("ownKeys", args); },
|
|
251
|
+
preventExtensions(...args) { return this.handleDefault("preventExtensions", args); },
|
|
252
|
+
setPrototypeOf(...args) { return this.handleDefault("setPrototypeOf", args); }
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
if (memos.has(klass)) {
|
|
256
|
+
let memo = memos.get(klass);
|
|
257
|
+
memos.set(retval, memo);
|
|
258
|
+
memo.set(retval, memo.get(klass));
|
|
259
|
+
}
|
|
220
260
|
|
|
221
261
|
return retval;
|
|
222
262
|
};
|
package/package.json
CHANGED
package/test/test.mjs
CHANGED
|
@@ -2,6 +2,10 @@ import { TestWatcher } from "@jest/core";
|
|
|
2
2
|
import { share, saveSelf, accessor, abstract, final } from "../index"; //require("cfprotected");
|
|
3
3
|
|
|
4
4
|
class Base {
|
|
5
|
+
static #greeting = "Hello!";
|
|
6
|
+
static #sprot = share(this, {
|
|
7
|
+
getGreeting() { return this.#greeting; }
|
|
8
|
+
});
|
|
5
9
|
#prot = share(this, Base, {
|
|
6
10
|
num: 42,
|
|
7
11
|
name: "John Jacob Jingleheimerschmidt",
|
|
@@ -12,7 +16,6 @@ class Base {
|
|
|
12
16
|
get: () => this.propTestVal
|
|
13
17
|
}),
|
|
14
18
|
superTest: () => {
|
|
15
|
-
console.log(`Called Base::superTest() ...`);
|
|
16
19
|
return 1;
|
|
17
20
|
}
|
|
18
21
|
});
|
|
@@ -76,12 +79,16 @@ class Derived extends Base {
|
|
|
76
79
|
class NonParticipant extends Base {}
|
|
77
80
|
|
|
78
81
|
class GrandChild extends NonParticipant {
|
|
82
|
+
static #sprot = share(this, {
|
|
83
|
+
getGreeting() {
|
|
84
|
+
return `${this.#sprot.$uper.getGreeting()} My name is`;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
79
87
|
#prot = share(this, GrandChild, {
|
|
80
88
|
otherMethod: () => {
|
|
81
89
|
this.#prot.name = this.testName;
|
|
82
90
|
},
|
|
83
91
|
superTest: () => {
|
|
84
|
-
console.log(`Called GrandChild::superTest() ...`);
|
|
85
92
|
return 1 + this.pvt.#prot.$uper.superTest();
|
|
86
93
|
}
|
|
87
94
|
});
|
|
@@ -103,9 +110,13 @@ class GrandChild extends NonParticipant {
|
|
|
103
110
|
}
|
|
104
111
|
|
|
105
112
|
class SuperTest extends GrandChild {
|
|
113
|
+
static #sprot = share(this, {
|
|
114
|
+
getGreeting() {
|
|
115
|
+
return `${this.#sprot.$uper.getGreeting()} "${this.name}"!`
|
|
116
|
+
}
|
|
117
|
+
});
|
|
106
118
|
#prot = share(this, SuperTest, {
|
|
107
119
|
superTest: () => {
|
|
108
|
-
console.log(`Called SuperTest::superTest() ...`);
|
|
109
120
|
return 1 + this.pvt.#prot.$uper.superTest();
|
|
110
121
|
}
|
|
111
122
|
});
|
|
@@ -119,6 +130,9 @@ class SuperTest extends GrandChild {
|
|
|
119
130
|
test(`Should be able to call super through the entire inheritance chain`, () => {
|
|
120
131
|
expect(this.pvt.#prot.superTest()).toBe(3);
|
|
121
132
|
});
|
|
133
|
+
test(`Should be able to call super through the entire static inheritance chain`, () => {
|
|
134
|
+
expect(SuperTest.#sprot.getGreeting()).toBe(`Hello! My name is "SuperTest"!`);
|
|
135
|
+
});
|
|
122
136
|
}
|
|
123
137
|
}
|
|
124
138
|
|
|
@@ -138,31 +152,71 @@ describe(`Testing that $uper works in all cases`, () => {
|
|
|
138
152
|
});
|
|
139
153
|
|
|
140
154
|
describe(`Testing that abstract classes function as expected`, () => {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
155
|
+
const key = Symbol();
|
|
156
|
+
const ATest = abstract(class ATest {
|
|
157
|
+
static #sshared = share(this, {
|
|
158
|
+
[key]: true
|
|
159
|
+
});
|
|
160
|
+
#shared = share(this, ATest, {
|
|
161
|
+
[key]: true
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
class DTest extends ATest {
|
|
165
|
+
static #sshared = share(this, {});
|
|
166
|
+
#shared = share(this, DTest, {});
|
|
167
|
+
run() { return this.#shared[key]; }
|
|
168
|
+
static run() { return this.#sshared[key]; }
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
test(`Should not be able to instantiate directly`, () => {
|
|
145
172
|
expect(() => { new ATest; }).toThrow();
|
|
146
173
|
});
|
|
147
|
-
test(`Should be able to instantiate a
|
|
174
|
+
test(`Should be able to instantiate a derived class`, () => {
|
|
148
175
|
expect(() => { new DTest; }).not.toThrow();
|
|
149
176
|
});
|
|
177
|
+
test(`Should see shared members from constructed instance`, () => {
|
|
178
|
+
expect((new DTest).run()).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
test(`Should see static shared members from constructed instance`, () => {
|
|
181
|
+
expect(DTest.run()).toBe(true);
|
|
182
|
+
});
|
|
150
183
|
});
|
|
151
184
|
|
|
152
185
|
describe(`Testing that final classes function as expected`, () => {
|
|
153
|
-
const
|
|
186
|
+
const key = Symbol();
|
|
187
|
+
class TestBase {
|
|
188
|
+
static #sshared = share(this, {
|
|
189
|
+
[key]: true
|
|
190
|
+
});
|
|
191
|
+
#shared = share(this, TestBase, {
|
|
192
|
+
[key]: true
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
const FTest = final(class FTest extends TestBase {
|
|
196
|
+
static { saveSelf(this, "pvt"); }
|
|
197
|
+
static #sshared = share(this, {});
|
|
198
|
+
#shared = share(this, FTest, {});
|
|
199
|
+
run() { return this.#shared[key]; }
|
|
200
|
+
static run() { return this.pvt.#sshared[key]; }
|
|
201
|
+
});
|
|
154
202
|
|
|
155
|
-
test(`Should be able to instantiate an instance
|
|
203
|
+
test(`Should be able to instantiate an instance directly`, () => {
|
|
156
204
|
expect(() => { new FTest; }).not.toThrow();
|
|
157
205
|
});
|
|
158
|
-
test(`Should not be able to extend
|
|
206
|
+
test(`Should not be able to extend directly`, () => {
|
|
159
207
|
expect(() => { class DTest extends FTest {}; }).toThrow();
|
|
160
208
|
});
|
|
161
|
-
test(`Should not be able to cheat and create an instance of a
|
|
209
|
+
test(`Should not be able to cheat and create an instance of a derived class`, () => {
|
|
162
210
|
expect(() => {
|
|
163
211
|
FTest.prototype = {};
|
|
164
212
|
class DTest extends FTest {}
|
|
165
213
|
new DTest;
|
|
166
214
|
}).toThrow();
|
|
167
|
-
})
|
|
215
|
+
});
|
|
216
|
+
test(`Should see shared members from constructed instance`, () => {
|
|
217
|
+
expect((new FTest).run()).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
test(`Should see static shared members from constructed instance`, () => {
|
|
220
|
+
expect(FTest.run()).toBe(true);
|
|
221
|
+
});
|
|
168
222
|
});
|