cfprotected 1.0.0 → 1.1.0

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
@@ -111,20 +111,14 @@ 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
- ### Note:
126
- Just as with `abstract(...)`, 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(...)`. Unfortunately, there's also a limitation with final. Normally in compiled languages, it is a syntax error to even create a class that extends from a final class. However, without taking over the means of building the class (as can be done with Babel or my ClassicJS library) it's just not possible to restrict that event. As a result, the only thing left to check for is construction of instances of such an error.
127
-
128
122
  ## Other features
129
123
  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.
130
124
 
package/index.mjs CHANGED
@@ -158,30 +158,67 @@ function accessor(desc) {
158
158
  * @param {Function} klass The constructor of the current class.
159
159
  */
160
160
  function abstract(klass) {
161
- return new Proxy(klass, {
162
- construct(target, args, newTarget) {
163
- if (newTarget.prototype === klass.prototype)
164
- throw new TypeError(`Cannot construct instance of abstract class.`);
165
-
166
- return Reflect.construct(target, args, newTarget);
161
+ let name = klass.name?klass.name : "";
162
+ let retval = class extends klass {
163
+ constructor (...args) {
164
+ if (new.target === retval) {
165
+ throw new TypeError(`Class constructor ${name} is abstract and cannot be directly invoked with 'new'`);
166
+ }
167
+ super(...args);
167
168
  }
168
- });
169
+ };
170
+ return retval;
169
171
  };
170
172
 
171
173
  /**
172
174
  * A class wrapper that blocks construction of an instance if the class being
173
- * constructed is a descendant of the current class.
175
+ * constructed is a descendant of the current class. It also attempts to block
176
+ * extending the targeted class.
174
177
  * @param {Function} klass The constructor of the current class.
175
178
  */
176
179
  function final(klass) {
177
- return new Proxy(klass, {
178
- construct(target, args, newTarget) {
179
- if (newTarget.prototype !== klass.prototype)
180
- throw new TypeError(`Cannot extend final class.`);
181
-
182
- return Reflect.construct(target, args, newTarget);
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
198
+ }
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(); }
211
+ },
212
+ length: {
213
+ configurable: true,
214
+ value: klass.length
215
+ },
216
+ [Symbol.hasInstance]: {
217
+ value(inst) { return inst instanceof klass; }
183
218
  }
184
219
  });
220
+
221
+ return retval;
185
222
  };
186
223
 
187
224
  export { share, saveSelf, accessor, abstract, final };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cfprotected",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
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
@@ -1,5 +1,5 @@
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
5
  #prot = share(this, Base, {
@@ -135,4 +135,34 @@ describe(`Testing shared elements inherited from a non-participant`, () => {
135
135
 
136
136
  describe(`Testing that $uper works in all cases`, () => {
137
137
  (new SuperTest).run();
138
- })
138
+ });
139
+
140
+ 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`, () => {
145
+ expect(() => { new ATest; }).toThrow();
146
+ });
147
+ test(`Should be able to instantiate a class derived from an abstract class`, () => {
148
+ expect(() => { new DTest; }).not.toThrow();
149
+ });
150
+ });
151
+
152
+ describe(`Testing that final classes function as expected`, () => {
153
+ const FTest = final(class {});
154
+
155
+ test(`Should be able to instantiate an instance of a final class directly`, () => {
156
+ expect(() => { new FTest; }).not.toThrow();
157
+ });
158
+ test(`Should not be able to extend a final class directly`, () => {
159
+ expect(() => { class DTest extends FTest {}; }).toThrow();
160
+ });
161
+ test(`Should not be able to cheat and create an instance of a class derived from a final class`, () => {
162
+ expect(() => {
163
+ FTest.prototype = {};
164
+ class DTest extends FTest {}
165
+ new DTest;
166
+ }).toThrow();
167
+ })
168
+ });