Skip to content

Instantly share code, notes, and snippets.

@rbuckton
Last active May 3, 2022 12:29
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rbuckton/8e6806fb6852b50e4052 to your computer and use it in GitHub Desktop.
Save rbuckton/8e6806fb6852b50e4052 to your computer and use it in GitHub Desktop.
Prototype of possible mirroring API for decorators (and/or Object.mirror)
// extensions to global Reflect object
declare namespace Reflect {
function mirror(value: (...args: any[]) => any): FunctionMirror;
function mirror(value: new (...args: any[]) => any): ClassMirror;
function mirror(value: any): ObjectMirror;
function mirror(value: any, propertyKey: PropertyKey): MemberMirror;
}
// Extensions to the Object() constructor function object.
interface ObjectConstructor {
mirror(value: (...args: any[]) => any): FunctionMirror;
mirror(value: new (...args: any[]) => any): ClassMirror;
mirror(value: any): ObjectMirror;
mirror(value: any, propertyKey: PropertyKey): MemberMirror;
}
// Base mirror API
interface Mirror {
/*readonly*/ kind: string;
/*readonly*/ state: "initializing" | "initialized";
// Mirror API for metadata
defineMetadata(key: any, value: any): void;
deleteMetadata(key: any): boolean;
hasMetadata(key: any): boolean;
getMetadata(key: any): any;
hasOwnMetadata(key: any): boolean;
getOwnMetadata(key: any): any;
getOwnMetadataKeys(): any[];
}
// Base mirror for a function, class, or object literal.
interface ObjectMirror extends Mirror {
/*readonly*/ kind: "function" | "class" | "prototype" | "object";
// Mirror API for objects
isPrototypeOf(other: any): boolean; // from Object
getPrototype(): ObjectMirror; // from ObjectConstructor (getPrototypeOf)
setPrototype(proto: any): boolean; // from Reflect (setPrototypeOf)
isExtensible(): boolean; // from ObjectConstructor
preventExtensions(): boolean; // from ObjectConstructor
// Mirror API for object properties
defineProperty(propertyKey: PropertyKey, descriptor: PropertyDescriptor): PropertyMirror; // from ObjectConstructor
deleteProperty(propertyKey: PropertyKey): boolean; // from Reflect
hasOwnProperty(propertyKey: PropertyKey): boolean; // from Object
getOwnPropertyDescriptor(propertyKey: PropertyKey): PropertyDescriptor; // from ObjectConstructor
getOwnProperty(propertyKey: PropertyKey): PropertyMirror; // from ObjectConstructor
getOwnProperties(): PropertyMirror[];
getOwnPropertyKeys(): PropertyKey[]; // from ObjectConstructor (getOwnPropertyNames/getOwnPropertySymbols)
has(propertyKey: PropertyKey): boolean; // from Reflect
get(propertyKey: PropertyKey, receiver?: any): any; // from Reflect
set(propertyKey: PropertyKey, value: any, receiver?: any): boolean; // from Reflect
ownKeys(): PropertyKey[]; // from Reflect
enumerate(): IterableIterator<any>; // from Reflect
}
// Mirror for a function
interface FunctionMirror extends ObjectMirror {
/*readonly*/ kind: "function";
/*readonly*/ name: string;
/*readonly*/ prototype: ObjectMirror; // undefined if arrow function
/*readonly*/ length: number;
/*readonly*/ generator: boolean; // true if generator function
/*readonly*/ async: boolean; // true if async function
/*readonly*/ arrow: boolean; // true if arrow function
// Properties used during function declaration initialization.
value: Function; // Set throws if not initializing (see Mirror.state).
// Mirror API for parameters
getParameters(): ParameterMirror[];
// Mirror API for functions
construct(argumentsList: ArrayLike<any>, newTarget?: any): any; // from Reflect
apply(thisArg: any, argArray?: any): any; // from Function
call(thisArg: any, ...argArray: any[]): any; // from Function
bind(thisArg: any, ...argArray: any[]): any; // from Function
[Symbol.hasInstance](value: any): boolean; // from Function
}
// Mirror for a class
interface ClassMirror extends ObjectMirror {
/*readonly*/ kind: "class";
/*readonly*/ name: string;
/*readonly*/ length: number;
/*readonly*/ prototype: PrototypeMirror;
// Properties used during class declaration initialization
constructor: Function; // Set throws if not initializing (see Mirror.state).
// Mirror API for parameters
getParameters(): ParameterMirror[];
// Mirror API for constructors
construct(argumentsList: ArrayLike<any>, newTarget?: any): any; // from Reflect
apply(thisArg: any, argArray?: any): any; // from Function
call(thisArg: any, ...argArray: any[]): any; // from Function
bind(thisArg: any, ...argArray: any[]): any; // from Function
[Symbol.hasInstance](value: any): boolean; // from Function
// Mirror API for static fields
defineField(propertyKey: PropertyKey): FieldMirror; // Throws if not initializing (see Mirror.state).
deleteField(propertyKey: PropertyKey): boolean; // Ignored if not initializing (see Mirror.state).
hasOwnField(propertyKey: PropertyKey): boolean;
getOwnField(propertyKey: PropertyKey): FieldMirror;
getOwnFields(): FieldMirror[];
getOwnFieldKeys(): PropertyKey[];
// Mirror API for static private state
defineSlot(name?: string): SlotMirror; // Throws if not initializing (see Mirror.state).
}
// Mirror for a class prototype and instance members
interface PrototypeMirror extends ObjectMirror {
/*readonly*/ kind: "prototype";
/*readonly*/ class: ClassMirror;
// Mirror API for class instance fields
defineField(propertyKey: PropertyKey): FieldMirror; // Throws if not initializing (see Mirror.state).
deleteField(propertyKey: PropertyKey): boolean; // Ignored if not initializing (see Mirror.state).
hasOwnField(propertyKey: PropertyKey): boolean;
getOwnField(propertyKey: PropertyKey): FieldMirror;
getOwnFields(): FieldMirror[];
getOwnFieldKeys(): PropertyKey[];
// Mirror API for class instance private state
defineSlot(name?: string): SlotMirror; // Throws if not initializing (see Mirror.state).
}
// Mirror for members (properties, fields, slots)
interface MemberMirror extends Mirror {
/*readonly*/ kind: "method" | "accessor" | "property" | "field" | "slot";
/*readonly*/ name: PropertyKey;
/*readonly*/ owner: ObjectMirror;
/*readonly*/ class: ClassMirror; // undefined if not a static, prototype, or instance class member.
/*readonly*/ static: boolean; // true if static member of a class
// Mirror API common to members
getValue(target: any): any; // from Reflect (get)
setValue(target: any, value: any): boolean; // from Reflect (set)
}
// Mirror for properties (class methods, accessors, etc.)
interface PropertyMirror extends MemberMirror {
/*readonly*/ kind: "method" | "accessor" | "property";
// Properties used during property declaration initialization.
enumerable: boolean; // Set throws if not initializing (see Mirror.state).
configurable: boolean; // Set throws if not initializing (see Mirror.state).
// Mirror API common to properties
hasProperty(target: any): boolean; // from Reflect (has)
hasOwnProperty(target: any): boolean; // from ObjectConstructor
}
// Mirror for a data property
interface DataPropertyMirror extends PropertyMirror {
/*readonly*/ kind: "property";
// Properties used during data property declaration initialization.
writable: boolean; // Set throws if not initializing (see Mirror.state).
value: any; // Set throws if not initializing (see Mirror.state).
}
// Mirror for a method
interface MethodMirror extends PropertyMirror {
/*readonly*/ kind: "method";
/*readonly*/ length: number;
/*readonly*/ generator: boolean; // true if generator method
/*readonly*/ async: boolean; // true if async method
// Properties used during method declaration initialization.
writable: boolean; // Set throws if not initializing (see Mirror.state).
value: Function; // Set throws if not initializing (see Mirror.state).
// Mirror API for methods
getSuperMethod(prototype?: any): MethodMirror;
// Mirror API for parameters
getParameters(): ParameterMirror[];
// Mirror API for functions (less "construct" and "@@hasInstance")
apply(thisArg: any, argArray?: any): any; // from Function
call(thisArg: any, ...argArray: any[]): any; // from Function
bind(thisArg: any, ...argArray: any[]): any; // from Function
}
// Mirror for an accessor
interface AccessorMirror extends PropertyMirror {
/*readonly*/ kind: "accessor";
/*readonly*/ getMethod: MethodMirror;
/*readonly*/ setMethod: MethodMirror;
// Properties used during accessor declaration initialization.
get: Function; // Set throws if not initializing (see Mirror.state).
set: Function; // Set throws if not initializing (see Mirror.state).
// Mirror API for accessors
getSuperAccessor(prototype?: any): AccessorMirror;
}
// Mirror for an instance field
interface FieldMirror extends MemberMirror {
/*readonly*/ kind: "field";
// Properties used during field declaration initialization.
initializer: () => any; // Set throws if not initializing (see Mirror.state).
// Mirror API for fields
hasField(target: any): boolean;
hasOwnField(target: any): boolean;
}
// Mirror for a private slot
interface SlotMirror extends MemberMirror {
/*readonly*/ kind: "slot";
/*readonly*/ name: string;
// Properties used during slot declaration initialization.
initializer: () => any; // Set throws if not initializing (see Mirror.state).
// Mirror API for slots
hasSlot(target: any): boolean;
}
// Mirror for a parameter
interface ParameterMirror extends Mirror {
/*readonly*/ kind: "parameter";
/*readonly*/ name: string;
/*readonly*/ index: number;
/*readonly*/ owner: FunctionMirror | ClassMirror | MethodMirror;
/*readonly*/ rest: boolean; // true if rest parameter
/*readonly*/ optional: boolean; // true if parameter has initializer
/*readonly*/ pattern: boolean; // true if parameter is a binding element
}

The shape of each PropertyMirror subtype is specifically designed to model a PropertyDescriptor, e.g.:

let method: MethodMirror;
let descriptor: PropertyDescriptor = method;

As such, a MethodMirror has the enumerable, configurable, writable, and value properties, and an AccessorMirror has the enumerable, configurable, get, and set properties.

// Sample - Composition
// utilities:
declare namespace ContainerRegistry {
function provideClass(class_: ClassMirror, contractName: string | symbol): void;
function injectParameter(parameter: ParameterMirror, contractName: string | symbol): void;
}
// decorators:
function provide(contractName: string | symbol) {
return function (class_: ClassMirror) {
ContainerRegistry.provideClass(class_, contractName);
};
}
function inject(contractName: string | symbol) {
return function (parameter: ParameterMirror) {
ContainerRegistry.injectParameter(parameter, contractName);
};
}
// usage:
@provide("service")
class C {
constructor(@inject("dependency") d) {
}
@inject("circular") c;
}
// Sample - Debug-mode assertions
// decorator:
function conditional(test: boolean) {
return function (func: FunctionMirror | MethodMirror) {
if (!test) {
func.value = function() {}
}
};
}
// usage:
const DEBUG = false;
@conditional(DEBUG)
function debugWarn(message: string) {
console.warn(message);
}
// Sample - Logging function calls
// decorator:
function log(message: string) {
return function (method: MethodMirror) {
const original = method.value;
method.value = function (...args) {
console.log(message);
return original.call(this, ...args);
};
};
}
// usage:
@log
function doWork() {
}
// Sample - Interop with private state
// (adapted from https://github.com/wycats/javascript-decorators/blob/master/interop/private-state.md)
// decorator:
function reader(member: MemberMirror) {
const publicName = member.kind === "slot" ? member.name : member.name.slice(1);
member.owner.defineProperty(publicName, {
configurable: true,
get() { return member.getValue(this); },
set(value: any) { member.setValue(this, value); }
});
}
// usage:
class Person {
@reader #first;
@reader #last;
constructor(first, last) {
#first = first;
#last = last;
}
@reader get #fullName() {
return `${#first} ${#last}`;
}
}
// Sample - Deisgn-time type annotations
// decorators:
function type(type: () => Function) {
return function (mirror: Mirror) {
mirror.defineMetadata("design:type", type);
};
}
function returns(type: () => Function) {
return function (mirror: Mirror) {
mirror.defineMetadata("design:returntype", type);
};
}
// usage:
class Vector {
@type(() => Number)
x;
@type(() => Number)
y;
@returns(() => Vector)
add(@type(() => Vector) other) {
const result = new Vector();
result.x = this.x + other.x;
result.y = this.y + other.y;
return result;
}
}
// Sample - Property notification
// utilities:
declare namespace PropertyNotifier {
function notifyPropertyChanging(source: Object, name: string | symbol): void;
function notifyPropertyChanged(source: Object, name: string | symbol): void;
}
// decorator:
function notify(field: FieldMirror) {
const slot = field.owner.defineSlot();
slot.initializer = field.initializer;
field.initializer = undefined;
field.owner.defineProperty(field.name, {
get() {
return slot.get(this);
},
set(value) {
if (value !== slot.get(this)) {
PropertyNotifier.notifyPropertyChanging(this, field.name);
slot.set(this, value);
PropertyNotifier.notifyPropertyChanged(this, field.name);
}
}
});
}
// usage:
class UserViewModel {
@notify firstName;
@notify middleName;
@notify lastName;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment