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.
@@ -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
- "program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
12
- "internalConsoleOptions": "openOnSessionStart",
13
- "outFiles": [
14
- "${workspaceRoot}/dist/**/*"
12
+ "runtimeArgs": [
13
+ "--experimental-vm-modules",
14
+ "--inspect-brk",
15
+ "${workspaceRoot}/node_modules/jest/bin/jest.js",
16
+ "--runInBand"
15
17
  ],
16
- "envFile": "${workspaceRoot}/.env"
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 memo = ancestorMemo.get(inst) || {data: {}, $uper: {}, inheritance: null};
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
- //Change the prototype of protoData using the new members.
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
- .filter(k => !mDesc[k].value?.hasOwnProperty(ACCESSOR))
77
- .map(k => [k, mDesc[k]])));
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
- let desc = (mDesc[m].value?.hasOwnProperty(ACCESSOR))
83
- ? {
84
- enumerable: true,
85
- get: mDesc[m].value.get,
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 "super" accessors
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 name = klass.name?klass.name : "";
181
- let retval = eval(`(function ${name}(...args) {
182
- if (!new.target) {
183
- throw new TypeError("Class constructor ${name} cannot be invoked without 'new'");
184
- }
185
- if (new.target !== retval) {
186
- throw new TypeError("Cannot create an instance of a descendant of a final class");
187
- }
188
- if (retval.prototype !== void 0) {
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
- Object.setPrototypeOf(inst, proto);
201
- return inst;
202
- })`);
203
- retval.prototype = void 0;
204
-
205
- Object.defineProperties(retval, {
206
- toString: {
207
- enumerable: true,
208
- configurable: true,
209
- writable: true,
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
- length: {
213
- configurable: true,
214
- value: klass.length
237
+ get(_, prop, receiver) {
238
+ return (prop == "prototype")
239
+ ? void 0
240
+ : Reflect.get(klass, prop, receiver);
215
241
  },
216
- [Symbol.hasInstance]: {
217
- value(inst) { return inst instanceof klass; }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cfprotected",
3
- "version": "1.1.0",
3
+ "version": "1.1.4",
4
4
  "description": "An implementation of protected fields on top of class fields.",
5
5
  "main": "index.js",
6
6
  "scripts": {
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 ATest = abstract(class {});
142
- class DTest extends ATest {};
143
-
144
- test(`Should not be able to instantiate an abstract class directly`, () => {
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 class derived from an abstract class`, () => {
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 FTest = final(class {});
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 of a final class directly`, () => {
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 a final class directly`, () => {
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 class derived from a final class`, () => {
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
  });