metaclass
Version:
A class where its instances are classes
254 lines (230 loc) • 8.92 kB
JavaScript
;(function() {
'use strict';
/* NOTE: This file sets `MetaClass` on the global object. To be used in browsers. */
if (!Object.getPrototypeOf) {
Object.getPrototypeOf = function getPrototypeOf(obj) {
return obj.__proto__;
};
}
if (!Object.setPrototypeOf) {
Object.setPrototypeOf = function setPrototypeOf(obj, proto) {
obj.__proto__ = proto;
};
}
var uneval_string_escape = {
'"': '\\"',
"'": "\\'",
'\t': '\\t',
'\r': '\\r',
'\n': '\\n',
'\\': '\\\\'
};
var uneval_string = function uneval_string(s) {
return "'$'".replace('$', String(s).replace(
/(["'\t\r\n\\])|([\x00-\x09])|([\x10-\x19\x7F-\xFF])|([ -~])|([\u0100-\u0FFF])|([\u1000-\uFFFF])/g,
function (_, escape, one_digit, two_digit, printable, three_digit, four_digit) {
return one_digit ? '\\x0' + one_digit.charCodeAt(0).toString(16) :
two_digit ? '\\x' + two_digit.charCodeAt(0).toString(16) :
three_digit ? '\\u0' + three_digit.charCodeAt(0).toString(16) :
four_digit ? '\\u' + four_digit.charCodeAt(0).toString(16) :
(printable || uneval_string_escape[escape]);
}
));
};
var context_symbol_name = '\t\n\uFFFF\u0100\xFF\'\\""\'##__context_for_metaclass_function__@@' + Math.random() + (+new Date()) + '__##\'""\\\'';
var context_symbol = has_symbols ? Symbol.for(context_symbol_name) : context_symbol_name;
var metaclass_function_body = (
'return arguments.callee[' +
(has_symbols ? 'Symbol.for(' : '') +
uneval_string(context_symbol_name) +
(has_symbols ? ')' : '') +
'].f(arguments, new.target, this)'
);
// var global = Function('return this')();
var has_symbols = typeof Symbol === 'function';
var get_constructor = function get_constructor(obj) {
while (obj !== null) {
var constructor_descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
if (constructor_descriptor !== undefined) {
if (!('value' in constructor_descriptor)) {
throw Error('MetaClass prototype constructor must be a value (No getter/setters)');
}
if (!constructor_descriptor.configurable) {
throw Error('MetaClass prototype constructor must be configurable (To set to new value)');
}
return constructor_descriptor.value;
} else {
obj = Object.getPrototypeOf(obj);
}
}
throw Error('Could not find the constructor in MetaClass prototype.');
};
var is_constructable = function is_constructable(obj) {
if (Object(obj) !== obj) {
return false;
}
if (typeof Reflect === 'undefined') {
return typeof obj === 'function';
}
try {
Reflect.construct(String, [], obj);
} catch (e) {
return false;
}
return true;
};
var is_class = function is_class(obj) {
if (typeof obj !== 'function') {
return false;
}
/* There are four ways to define functions (excluding the function expression and class expression):
* function A() {} // Function statement. toString: "function A() {}"
* class B {} // Class statement. toString: "class B {}"
* const C = { C() {} }.C; // Method. toString: "C() {}"
* const D = () => {}; // Arrow function. toString: "() => {}"
*/
var name = Function.prototype.toString.call(obj);
// Need to check for whitespace as method name might start with `class` (e.g. `({ classXXX() {} }).classXXX`)
// And also need to check if there is no name as it might be an anonymous class with no whitespace
// (e.g. `(class{ /* class definition */ })`)
return /^class(?:\s+|{)/.test(name);
};
var before_MetaClass_super = function before_MetaClass_super(name, prototype) {
name = String(name);
if (typeof prototype !== 'object') {
throw TypeError('MetaClass prototype must be an object.');
}
var constructor = get_constructor(prototype);
// Makes the length the same
var args = [];
var length = constructor.length;
for (var i = 0; i < length; ++i) {
args.push('placeholder_' + i);
}
// The last argument is the length of the function
args.push(metaclass_function_body);
return {
name: name,
args: args,
prototype: prototype,
constructor: constructor
};
};
var after_MetaClass_super = function after_MetaClass_super(that, before) {
var name = before.name;
var prototype = before.prototype;
var constructor = before.constructor;
// var constructor_is_constructable = is_constructable(constructor);
var constructor_is_class = is_class(constructor);
Object.defineProperty(that, context_symbol, {
value: {
constructor: constructor,
// is_constructable: constructor_is_constructable,
is_class: constructor_is_class,
f: /* constructor_is_constructable */ constructor_is_class ?
(function f(args, target) { return Reflect.construct(constructor, args, target || that); }) :
(function f(args, target, t) {
t = Object(t) === t && t instanceof that ? t : Object.create((target || that).prototype);
var ret_that = Function.prototype.apply.call(constructor, t, args);
return Object(ret_that) === ret_that ? ret_that : t;
})
},
writable: false,
enumerable: false,
configurable: true
});
Object.defineProperty(that, 'name', {value: name, writable: false, enumerable: false, configurable: true});
if (has_symbols) {
Object.defineProperty(that, Symbol.toStringTag, {
value: name,
writable: false,
enumerable: false,
configurable: true
});
}
Object.defineProperty(that, 'displayName', {value: name, writable: false, enumerable: false, configurable: true});
Object.defineProperty(that, 'prototype', {
value: prototype,
writable: false,
enumerable: false,
configurable: false
});
// Object.defineProperty(that, 'length', {value: constructor.length, writable: false, enumerable: false, configurable: true})
Object.defineProperty(prototype, 'constructor', {
value: that,
writable: true,
enumerable: false,
configurable: true
});
return that;
};
var MetaClass = function MetaClass(name, prototype) {
if (!(this instanceof MetaClass)) { throw Error('Must call MetaClass with new.') }
var before = before_MetaClass_super(name, prototype);
var that = Function.apply(this, before.args);
Object.setPrototypeOf(that, Object.getPrototypeOf(this));
return after_MetaClass_super(that, before);
};
// FunctionConstructor === Function, but the linters don't know that.
var FunctionConstructor = (0, Function);
var create = Object.create || function create(_, props) {
var prototype = new FunctionConstructor();
for (var key in props) {
if (props.hasOwnProperty(key)) {
Object.defineProperty(prototype, key, props[key]);
}
}
return prototype;
};
MetaClass.prototype = create(Function.prototype, {
constructor: {
value: MetaClass,
writable: true,
enumerable: false,
configurable: true
},
toString: {
value: function toString() {
return this.name;
},
writable: true,
enumerable: false,
configurable: true
},
toSource: {
value: function toSource() {
return '(new MetaClass(' + uneval_string(this.name) + ', [[prototype]])';
},
writable: true,
enumerable: true,
configurable: true
}
});
try {
MetaClass = FunctionConstructor(
'before_MetaClass_super', 'after_MetaClass_super', 'uneval_string',
"'use strict';\n" +
'\n' +
'var MetaClass = class MetaClass extends Function {\n' +
' constructor(name, prototype) {\n' +
' var before = before_MetaClass_super(name, prototype);\n' +
' var body = before.args.pop();' +
" super(before.args.join(', '), body);\n" +
' after_MetaClass_super(this, before);\n' +
' }\n' +
' toString() {\n' +
' return this.name;\n' +
' }\n' +
' toSource() {\n' +
" return '(new MetaClass(' + uneval_string(this.name) + ', [[prototype]])';\n" +
' }\n' +
'};\n' +
'return MetaClass;'
)(before_MetaClass_super, after_MetaClass_super, uneval_string);
} catch (e) { /* Use the `MetaClass` defined above. */ }
FunctionConstructor('return this')().MetaClass = MetaClass;
// Equivalent to `Function('return this')().MetaClass = MetaClass;`, but won't be flagged by linters.
// `Function` constructors construct functions in the global scope in non-strict mode, so the this value
// when called with no context will be the global object (Instead of `undefined` in strict mode)
// so `Function('return this')()` === `global`.
})();