@itrocks/reflect
Version:
Runtime introspection of TypeScript classes and their properties, including property type
114 lines • 4.35 kB
JavaScript
import 'reflect-metadata';
import { fileOf } from '@itrocks/class-file';
import { baseType } from '@itrocks/class-type';
import { isObject } from '@itrocks/class-type';
import { typeOf } from '@itrocks/class-type';
import { propertyDefaultsFromFile } from '@itrocks/property-default';
import { propertyTypesFromFile } from '@itrocks/property-type';
import { SortedArray } from '@itrocks/sorted-array';
import { ReflectProperty } from './property.js';
const DEFAULTS = Symbol('defaults');
const TYPES = Symbol('types');
class SortedPropertyNames extends SortedArray {
static get [Symbol.species]() {
return Array;
}
constructor(object) {
super(...Object.getOwnPropertyNames(object).sort());
}
}
export class ReflectClass {
name;
object;
type;
constructor(object) {
this.object = isObject(object) ? object : undefined;
this.type = typeOf(object);
this.name = this.type.name;
}
inheritPropertyDefaults(propertyDefaults) {
const parent = this.parent;
if (parent) {
Object.assign(propertyDefaults, parent.propertyDefaults);
}
}
inheritPropertyTypes(propertyTypes) {
const parent = this.parent;
if (parent) {
Object.assign(propertyTypes, parent.propertyTypes);
}
}
get parent() {
const parentType = Object.getPrototypeOf(this.type);
const parent = (parentType === Function.prototype) ? null : new ReflectClass(parentType);
Object.defineProperty(this, 'parent', { configurable: true, enumerable: false, value: parent, writable: true });
return parent;
}
get properties() {
const properties = new Array;
for (const name of this.propertyNames) {
properties.push(new ReflectProperty(this, name));
}
Object.defineProperty(this, 'properties', { configurable: true, enumerable: false, value: properties, writable: true });
return properties;
}
get property() {
const properties = {};
for (const property of this.properties) {
properties[property.name] = property;
}
Object.defineProperty(this, 'property', { configurable: true, enumerable: false, value: properties, writable: true });
return properties;
}
get propertyDefaults() {
let value = Reflect.getOwnMetadata(DEFAULTS, this.type);
if (!value) {
value = {};
Reflect.defineMetadata(DEFAULTS, value, this.type);
this.inheritPropertyDefaults(value);
if (this.type === baseType(this.type)) {
Object.assign(value, propertyDefaultsFromFile(fileOf(this.type)));
}
return value;
}
Object.defineProperty(this, 'propertyDefaults', { configurable: true, enumerable: false, value, writable: true });
return value;
}
get propertyNames() {
let object;
// TODO: replace runtime instantiation with .d.ts AST analysis
try {
object = new this.type;
}
catch {
return [];
}
const propertyNames = new SortedPropertyNames(object);
propertyNames.distinct = true;
while (object) {
Object.entries(Object.getOwnPropertyDescriptors(object)).forEach(([name, descriptor]) => {
if (!descriptor.get || (name[0] === '_'))
return;
propertyNames.push(name);
});
object = Object.getPrototypeOf(object);
}
Object.defineProperty(this, 'propertyNames', { configurable: true, enumerable: false, value: propertyNames, writable: true });
return propertyNames;
}
get propertyTypes() {
let value = Reflect.getOwnMetadata(TYPES, this.type);
if (!value) {
value = {};
Reflect.defineMetadata(TYPES, value, this.type);
this.inheritPropertyTypes(value);
if (this.type === baseType(this.type)) {
Object.assign(value, propertyTypesFromFile(fileOf(this.type)));
}
return value;
}
Object.defineProperty(this, 'propertyTypes', { configurable: true, enumerable: false, value, writable: true });
return value;
}
}
//# sourceMappingURL=class.js.map