cfprotected 1.0.0 → 1.1.3
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 -6
- package/index.mjs +115 -48
- package/package.json +1 -1
- package/test/test.mjs +89 -5
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
|
@@ -111,19 +111,16 @@ const Example = abstract(class {
|
|
|
111
111
|
});
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
### Note:
|
|
115
|
-
This method uses a proxy internally to keep things consistent. However, the Proxy is not a membrane. So it's best if any use of static private fields is accessed through the property created by using `saveSelf(...)`.
|
|
116
|
-
|
|
117
114
|
## **final(klass)**
|
|
118
|
-
This method is a class wrapper that prevents instances of the class from being created using descendant classes.
|
|
115
|
+
This method is a class wrapper that prevents instances of the class from being created using descendant classes. It also attempts to prevent creation of descendant classes.
|
|
119
116
|
```js
|
|
120
117
|
const Example = final(class {
|
|
121
118
|
...
|
|
122
119
|
});
|
|
123
120
|
```
|
|
124
121
|
|
|
125
|
-
###
|
|
126
|
-
|
|
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.
|
|
127
124
|
|
|
128
125
|
## Other features
|
|
129
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.
|
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.
|
|
@@ -17,8 +39,6 @@ function getAllOwnKeys(o) {
|
|
|
17
39
|
* @returns {Object} The fully constructed inheritance object.
|
|
18
40
|
*/
|
|
19
41
|
function share(inst, klass, members) {
|
|
20
|
-
let retval = {};
|
|
21
|
-
|
|
22
42
|
if ((typeof(inst) == "function")
|
|
23
43
|
&& klass && (typeof(klass) == "object")
|
|
24
44
|
&& (members === void 0)) {
|
|
@@ -62,58 +82,56 @@ function share(inst, klass, members) {
|
|
|
62
82
|
}
|
|
63
83
|
|
|
64
84
|
//Get the protected data object.
|
|
65
|
-
let
|
|
66
|
-
let
|
|
85
|
+
let ancestorKey = (inst === klass) ? ancestor : inst;
|
|
86
|
+
let memo = ancestorMemo.get(ancestorKey) || {
|
|
87
|
+
data: {},
|
|
88
|
+
$uper: {},
|
|
89
|
+
};
|
|
90
|
+
let retval = {};
|
|
67
91
|
|
|
68
92
|
//Get the details of the protected properties.
|
|
69
93
|
let mDesc = Object.getOwnPropertyDescriptors(members);
|
|
70
94
|
let mKeys = getAllOwnKeys(members);
|
|
71
95
|
|
|
72
96
|
//Change the prototype of protoData using the new members.
|
|
73
|
-
let prototype = Object.getPrototypeOf(
|
|
97
|
+
let prototype = Object.getPrototypeOf(memo.data);
|
|
74
98
|
let proto = Object.create(prototype,
|
|
75
99
|
Object.fromEntries(mKeys
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
Object.defineProperty(retval, "$uper", { value: {} });
|
|
95
|
-
|
|
100
|
+
.map(k => {
|
|
101
|
+
if (mDesc[k].value?.hasOwnProperty(ACCESSOR)) {
|
|
102
|
+
Object.assign(mDesc[k], mDesc[k].value);
|
|
103
|
+
mDesc[k].enumerable = true;
|
|
104
|
+
delete mDesc[k][ACCESSOR];
|
|
105
|
+
delete mDesc[k].value;
|
|
106
|
+
delete mDesc[k].writable;
|
|
107
|
+
}
|
|
108
|
+
bindDescriptor(mDesc[k], inst);
|
|
109
|
+
return [k, mDesc[k]];
|
|
110
|
+
})));
|
|
111
|
+
Object.setPrototypeOf(retval, proto);
|
|
112
|
+
|
|
113
|
+
//Define the "$uper" accessors
|
|
114
|
+
let $uper = {};
|
|
115
|
+
Object.defineProperty(retval, "$uper", { value: $uper });
|
|
116
|
+
|
|
117
|
+
//Build up the "$uper" object
|
|
96
118
|
for (let key of mKeys) {
|
|
97
119
|
if (key in prototype) {
|
|
98
120
|
let obj = prototype;
|
|
99
121
|
while (!obj.hasOwnProperty(key)) {
|
|
100
122
|
obj = Object.getPrototypeOf(obj);
|
|
101
123
|
}
|
|
102
|
-
Object.defineProperty(
|
|
124
|
+
Object.defineProperty($uper, key, Object.getOwnPropertyDescriptor(obj, key));
|
|
103
125
|
}
|
|
104
126
|
}
|
|
105
127
|
|
|
106
128
|
//Attach the super inheritance
|
|
107
|
-
Object.setPrototypeOf(
|
|
108
|
-
|
|
109
|
-
//Inherit the inheritance
|
|
110
|
-
Object.setPrototypeOf(retval, memo.inheritance);
|
|
129
|
+
Object.setPrototypeOf($uper, memo.$uper);
|
|
111
130
|
|
|
112
131
|
//Save the inheritance & protected data
|
|
113
132
|
memos.get(klass).set(inst, {
|
|
114
|
-
data:
|
|
115
|
-
|
|
116
|
-
$uper: retval.$uper
|
|
133
|
+
data: retval,
|
|
134
|
+
$uper: $uper
|
|
117
135
|
});
|
|
118
136
|
|
|
119
137
|
return retval;
|
|
@@ -158,30 +176,79 @@ function accessor(desc) {
|
|
|
158
176
|
* @param {Function} klass The constructor of the current class.
|
|
159
177
|
*/
|
|
160
178
|
function abstract(klass) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
179
|
+
let name = klass.name?klass.name : "";
|
|
180
|
+
let retval = class extends klass {
|
|
181
|
+
constructor (...args) {
|
|
182
|
+
if (new.target === retval) {
|
|
183
|
+
throw new TypeError(`Class constructor ${name} is abstract and cannot be directly invoked with 'new'`);
|
|
184
|
+
}
|
|
185
|
+
super(...args);
|
|
167
186
|
}
|
|
168
|
-
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
if (memos.has(klass)) {
|
|
190
|
+
let memo = memos.get(klass);
|
|
191
|
+
memos.set(retval, memo);
|
|
192
|
+
memo.set(retval, memo.get(klass));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return retval;
|
|
169
196
|
};
|
|
170
197
|
|
|
171
198
|
/**
|
|
172
199
|
* A class wrapper that blocks construction of an instance if the class being
|
|
173
|
-
* constructed is a descendant of the current class.
|
|
200
|
+
* constructed is a descendant of the current class. It also attempts to block
|
|
201
|
+
* extending the targeted class.
|
|
174
202
|
* @param {Function} klass The constructor of the current class.
|
|
175
203
|
*/
|
|
176
204
|
function final(klass) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
205
|
+
let retval = new Proxy(function() {}, {
|
|
206
|
+
handleDefault(fname, args) {
|
|
207
|
+
args.shift();
|
|
208
|
+
args.unshift(klass);
|
|
209
|
+
return Reflect[fname](...args);
|
|
210
|
+
},
|
|
211
|
+
construct(_, args, newTarget) {
|
|
212
|
+
if (newTarget !== retval) {
|
|
213
|
+
throw new TypeError("Cannot create an instance of a descendant of a final class");
|
|
214
|
+
}
|
|
215
|
+
let inst = Reflect.construct(klass, args, newTarget);
|
|
216
|
+
let proto = Object.create(klass.prototype, {
|
|
217
|
+
constructor: {
|
|
218
|
+
enumerable: true,
|
|
219
|
+
configurable: true,
|
|
220
|
+
writable: true,
|
|
221
|
+
value: retval
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
Object.setPrototypeOf(inst, proto);
|
|
225
|
+
return inst;
|
|
226
|
+
},
|
|
227
|
+
get(_, prop, receiver) {
|
|
228
|
+
return (prop == "prototype")
|
|
229
|
+
? void 0
|
|
230
|
+
: Reflect.get(klass, prop, receiver);
|
|
231
|
+
},
|
|
232
|
+
set(...args) { return this.handleDefault("set", args); },
|
|
233
|
+
apply(...args) { return this.handleDefault("apply", args); },
|
|
234
|
+
defineProperty(...args) { return this.handleDefault("defineProperty", args); },
|
|
235
|
+
deleteProperty(...args) { return this.handleDefault("deleteProperty", args); },
|
|
236
|
+
getOwnPropertyDescriptor(...args) { return this.handleDefault("getOwnPropertyDescriptor", args); },
|
|
237
|
+
getPrototypeOf(...args) { return this.handleDefault("getPrototypeOf", args); },
|
|
238
|
+
has(...args) { return this.handleDefault("has", args); },
|
|
239
|
+
isExtensible(...args) { return this.handleDefault("isExtensible", args); },
|
|
240
|
+
ownKeys(...args) { return this.handleDefault("ownKeys", args); },
|
|
241
|
+
preventExtensions(...args) { return this.handleDefault("preventExtensions", args); },
|
|
242
|
+
setPrototypeOf(...args) { return this.handleDefault("setPrototypeOf", args); }
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (memos.has(klass)) {
|
|
246
|
+
let memo = memos.get(klass);
|
|
247
|
+
memos.set(retval, memo);
|
|
248
|
+
memo.set(retval, memo.get(klass));
|
|
249
|
+
}
|
|
181
250
|
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
});
|
|
251
|
+
return retval;
|
|
185
252
|
};
|
|
186
253
|
|
|
187
254
|
export { share, saveSelf, accessor, abstract, final };
|
package/package.json
CHANGED
package/test/test.mjs
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { TestWatcher } from "@jest/core";
|
|
2
|
-
import { share, saveSelf, accessor } from "../index"; //require("cfprotected");
|
|
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
|
|
|
@@ -135,4 +149,74 @@ describe(`Testing shared elements inherited from a non-participant`, () => {
|
|
|
135
149
|
|
|
136
150
|
describe(`Testing that $uper works in all cases`, () => {
|
|
137
151
|
(new SuperTest).run();
|
|
138
|
-
})
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe(`Testing that abstract classes function as expected`, () => {
|
|
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`, () => {
|
|
172
|
+
expect(() => { new ATest; }).toThrow();
|
|
173
|
+
});
|
|
174
|
+
test(`Should be able to instantiate a derived class`, () => {
|
|
175
|
+
expect(() => { new DTest; }).not.toThrow();
|
|
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
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe(`Testing that final classes function as expected`, () => {
|
|
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
|
+
});
|
|
202
|
+
|
|
203
|
+
test(`Should be able to instantiate an instance directly`, () => {
|
|
204
|
+
expect(() => { new FTest; }).not.toThrow();
|
|
205
|
+
});
|
|
206
|
+
test(`Should not be able to extend directly`, () => {
|
|
207
|
+
expect(() => { class DTest extends FTest {}; }).toThrow();
|
|
208
|
+
});
|
|
209
|
+
test(`Should not be able to cheat and create an instance of a derived class`, () => {
|
|
210
|
+
expect(() => {
|
|
211
|
+
FTest.prototype = {};
|
|
212
|
+
class DTest extends FTest {}
|
|
213
|
+
new DTest;
|
|
214
|
+
}).toThrow();
|
|
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
|
+
});
|
|
222
|
+
});
|