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 +1 -7
- package/index.mjs +51 -14
- package/package.json +1 -1
- package/test/test.mjs +32 -2
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
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
|
+
});
|