cfprotected 1.1.1 → 1.1.2

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/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
@@ -62,7 +62,8 @@ function share(inst, klass, members) {
62
62
  }
63
63
 
64
64
  //Get the protected data object.
65
- let memo = ancestorMemo.get(inst) || {data: {}, $uper: {}, inheritance: null};
65
+ let ancestorKey = (inst === klass) ? ancestor : inst;
66
+ let memo = ancestorMemo.get(ancestorKey) || {data: {}, $uper: {}, inheritance: null};
66
67
  let protData = memo.data;
67
68
 
68
69
  //Get the details of the protected properties.
@@ -169,9 +170,11 @@ function abstract(klass) {
169
170
  };
170
171
 
171
172
  if (memos.has(klass)) {
172
- memos.set(retval, memos.get(klass));
173
+ let memo = memos.get(klass);
174
+ memos.set(retval, memo);
175
+ memo.set(retval, memo.get(klass));
173
176
  }
174
-
177
+
175
178
  return retval;
176
179
  };
177
180
 
@@ -182,51 +185,52 @@ function abstract(klass) {
182
185
  * @param {Function} klass The constructor of the current class.
183
186
  */
184
187
  function final(klass) {
185
- let name = klass.name?klass.name : "";
186
- let retval = eval(`(function ${name}(...args) {
187
- if (!new.target) {
188
- throw new TypeError("Class constructor ${name} cannot be invoked without 'new'");
189
- }
190
- if (new.target !== retval) {
191
- throw new TypeError("Cannot create an instance of a descendant of a final class");
192
- }
193
- if (retval.prototype !== void 0) {
194
- retval.prototype = void 0;
195
- }
196
- let inst = new klass(...args);
197
- let proto = Object.create(klass.prototype, {
198
- constructor: {
199
- enumerable: true,
200
- configurable: true,
201
- writable: true,
202
- value: retval
188
+ let retval = new Proxy(function() {}, {
189
+ handleDefault(fname, args) {
190
+ args.shift();
191
+ args.unshift(klass);
192
+ return Reflect[fname](...args);
193
+ },
194
+ construct(_, args, newTarget) {
195
+ if (newTarget !== retval) {
196
+ throw new TypeError("Cannot create an instance of a descendant of a final class");
203
197
  }
204
- });
205
- Object.setPrototypeOf(inst, proto);
206
- return inst;
207
- })`);
208
- retval.prototype = void 0;
209
-
210
- Object.defineProperties(retval, {
211
- toString: {
212
- enumerable: true,
213
- configurable: true,
214
- writable: true,
215
- value() { return klass.toString(); }
198
+ let inst = Reflect.construct(klass, args, newTarget);
199
+ let proto = Object.create(klass.prototype, {
200
+ constructor: {
201
+ enumerable: true,
202
+ configurable: true,
203
+ writable: true,
204
+ value: retval
205
+ }
206
+ });
207
+ Object.setPrototypeOf(inst, proto);
208
+ return inst;
216
209
  },
217
- length: {
218
- configurable: true,
219
- value: klass.length
210
+ get(_, prop, receiver) {
211
+ return (prop == "prototype")
212
+ ? void 0
213
+ : Reflect.get(klass, prop, receiver);
220
214
  },
221
- [Symbol.hasInstance]: {
222
- value(inst) { return inst instanceof klass; }
223
- }
224
- });
215
+ set(...args) { return this.handleDefault("set", args); },
216
+ apply(...args) { return this.handleDefault("apply", args); },
217
+ defineProperty(...args) { return this.handleDefault("defineProperty", args); },
218
+ deleteProperty(...args) { return this.handleDefault("deleteProperty", args); },
219
+ getOwnPropertyDescriptor(...args) { return this.handleDefault("getOwnPropertyDescriptor", args); },
220
+ getPrototypeOf(...args) { return this.handleDefault("getPrototypeOf", args); },
221
+ has(...args) { return this.handleDefault("has", args); },
222
+ isExtensible(...args) { return this.handleDefault("isExtensible", args); },
223
+ ownKeys(...args) { return this.handleDefault("ownKeys", args); },
224
+ preventExtensions(...args) { return this.handleDefault("preventExtensions", args); },
225
+ setPrototypeOf(...args) { return this.handleDefault("setPrototypeOf", args); }
226
+ });
225
227
 
226
228
  if (memos.has(klass)) {
227
- memos.set(retval, memos.get(klass));
229
+ let memo = memos.get(klass);
230
+ memos.set(retval, memo);
231
+ memo.set(retval, memo.get(klass));
228
232
  }
229
-
233
+
230
234
  return retval;
231
235
  };
232
236
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cfprotected",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
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
@@ -140,13 +140,18 @@ describe(`Testing that $uper works in all cases`, () => {
140
140
  describe(`Testing that abstract classes function as expected`, () => {
141
141
  const key = Symbol();
142
142
  const ATest = abstract(class ATest {
143
+ static #sshared = share(this, {
144
+ [key]: true
145
+ });
143
146
  #shared = share(this, ATest, {
144
147
  [key]: true
145
148
  });
146
149
  });
147
150
  class DTest extends ATest {
151
+ static #sshared = share(this, {});
148
152
  #shared = share(this, DTest, {});
149
153
  run() { return this.#shared[key]; }
154
+ static run() { return this.#sshared[key]; }
150
155
  };
151
156
 
152
157
  test(`Should not be able to instantiate directly`, () => {
@@ -158,20 +163,27 @@ describe(`Testing that abstract classes function as expected`, () => {
158
163
  test(`Should see shared members from constructed instance`, () => {
159
164
  expect((new DTest).run()).toBe(true);
160
165
  });
166
+ test(`Should see static shared members from constructed instance`, () => {
167
+ expect(DTest.run()).toBe(true);
168
+ });
161
169
  });
162
170
 
163
171
  describe(`Testing that final classes function as expected`, () => {
164
172
  const key = Symbol();
165
173
  class TestBase {
174
+ static #sshared = share(this, {
175
+ [key]: true
176
+ });
166
177
  #shared = share(this, TestBase, {
167
178
  [key]: true
168
179
  });
169
180
  }
170
181
  const FTest = final(class FTest extends TestBase {
182
+ static { saveSelf(this, "pvt"); }
183
+ static #sshared = share(this, {});
171
184
  #shared = share(this, FTest, {});
172
- run() {
173
- return this.#shared[key];
174
- }
185
+ run() { return this.#shared[key]; }
186
+ static run() { return this.pvt.#sshared[key]; }
175
187
  });
176
188
 
177
189
  test(`Should be able to instantiate an instance directly`, () => {
@@ -190,4 +202,7 @@ describe(`Testing that final classes function as expected`, () => {
190
202
  test(`Should see shared members from constructed instance`, () => {
191
203
  expect((new FTest).run()).toBe(true);
192
204
  });
205
+ test(`Should see static shared members from constructed instance`, () => {
206
+ expect(FTest.run()).toBe(true);
207
+ });
193
208
  });