diff --git a/Reflect.ts b/Reflect.ts index c8e1414..701c204 100644 --- a/Reflect.ts +++ b/Reflect.ts @@ -554,7 +554,7 @@ namespace Reflect { */ export declare function deleteMetadata(metadataKey: any, target: any, propertyKey: string | symbol): boolean; - (function (this: any, factory: (exporter: (key: K, value: typeof Reflect[K]) => void) => void) { + (function (this: any, factory: (exporter: (key: K, value: typeof Reflect[K]) => void, root: any) => void) { const root = typeof globalThis === "object" ? globalThis : typeof global === "object" ? global : @@ -570,7 +570,7 @@ namespace Reflect { exporter = makeExporter(root.Reflect, exporter); } - factory(exporter); + factory(exporter, root); function makeExporter(target: typeof Reflect, previous?: (key: K, value: typeof Reflect[K]) => void) { return (key: K, value: typeof Reflect[K]) => { @@ -593,7 +593,7 @@ namespace Reflect { return functionThis() || indirectEvalThis(); } }) - (function (exporter) { + (function (exporter, root) { const hasOwn = Object.prototype.hasOwnProperty; // feature test for Symbol support @@ -628,10 +628,6 @@ namespace Reflect { const _Set: typeof Set = !usePolyfill && typeof Set === "function" && typeof Set.prototype.entries === "function" ? Set : CreateSetPolyfill(); const _WeakMap: typeof WeakMap = !usePolyfill && typeof WeakMap === "function" ? WeakMap : CreateWeakMapPolyfill(); - // [[Metadata]] internal slot - // https://rbuckton.github.io/reflect-metadata/#ordinary-object-internal-methods-and-internal-slots - const Metadata = new _WeakMap>>(); - function decorate(decorators: ClassDecorator[], target: Function): Function; function decorate(decorators: (PropertyDecorator | MethodDecorator)[], target: any, propertyKey: string | symbol, attributes?: PropertyDescriptor | null): PropertyDescriptor | undefined; function decorate(decorators: (PropertyDecorator | MethodDecorator)[], target: any, propertyKey: string | symbol, attributes: PropertyDescriptor): PropertyDescriptor; @@ -1131,15 +1127,11 @@ namespace Reflect { function deleteMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): boolean { if (!IsObject(target)) throw new TypeError(); if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); - const metadataMap = GetOrCreateMetadataMap(target, propertyKey, /*Create*/ false); - if (IsUndefined(metadataMap)) return false; - if (!metadataMap.delete(metadataKey)) return false; - if (metadataMap.size > 0) return true; - const targetMetadata = Metadata.get(target); - targetMetadata.delete(propertyKey); - if (targetMetadata.size > 0) return true; - Metadata.delete(target); - return true; + if (!IsObject(target)) throw new TypeError(); + if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); + const provider = GetMetadataProvider(target, propertyKey, /*Create*/ false); + if (IsUndefined(provider)) return false; + return provider.OrdinaryDeleteMetadata(metadataKey, target, propertyKey); } exporter("deleteMetadata", deleteMetadata); @@ -1168,26 +1160,6 @@ namespace Reflect { return descriptor; } - // 2.1.1 GetOrCreateMetadataMap(O, P, Create) - // https://rbuckton.github.io/reflect-metadata/#getorcreatemetadatamap - function GetOrCreateMetadataMap(O: any, P: string | symbol | undefined, Create: true): Map; - function GetOrCreateMetadataMap(O: any, P: string | symbol | undefined, Create: false): Map | undefined; - function GetOrCreateMetadataMap(O: any, P: string | symbol | undefined, Create: boolean): Map | undefined { - let targetMetadata = Metadata.get(O); - if (IsUndefined(targetMetadata)) { - if (!Create) return undefined; - targetMetadata = new _Map>(); - Metadata.set(O, targetMetadata); - } - let metadataMap = targetMetadata.get(P); - if (IsUndefined(metadataMap)) { - if (!Create) return undefined; - metadataMap = new _Map(); - targetMetadata.set(P, metadataMap); - } - return metadataMap; - } - // 3.1.1.1 OrdinaryHasMetadata(MetadataKey, O, P) // https://rbuckton.github.io/reflect-metadata/#ordinaryhasmetadata function OrdinaryHasMetadata(MetadataKey: any, O: any, P: string | symbol | undefined): boolean { @@ -1201,9 +1173,9 @@ namespace Reflect { // 3.1.2.1 OrdinaryHasOwnMetadata(MetadataKey, O, P) // https://rbuckton.github.io/reflect-metadata/#ordinaryhasownmetadata function OrdinaryHasOwnMetadata(MetadataKey: any, O: any, P: string | symbol | undefined): boolean { - const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); - if (IsUndefined(metadataMap)) return false; - return ToBoolean(metadataMap.has(MetadataKey)); + const provider = GetMetadataProvider(O, P, /*Create*/ false); + if (IsUndefined(provider)) return false; + return ToBoolean(provider.OrdinaryHasOwnMetadata(MetadataKey, O, P)); } // 3.1.3.1 OrdinaryGetMetadata(MetadataKey, O, P) @@ -1219,16 +1191,16 @@ namespace Reflect { // 3.1.4.1 OrdinaryGetOwnMetadata(MetadataKey, O, P) // https://rbuckton.github.io/reflect-metadata/#ordinarygetownmetadata function OrdinaryGetOwnMetadata(MetadataKey: any, O: any, P: string | symbol | undefined): any { - const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); - if (IsUndefined(metadataMap)) return undefined; - return metadataMap.get(MetadataKey); + const provider = GetMetadataProvider(O, P, /*Create*/ false); + if (IsUndefined(provider)) return; + return provider.OrdinaryGetOwnMetadata(MetadataKey, O, P); } // 3.1.5.1 OrdinaryDefineOwnMetadata(MetadataKey, MetadataValue, O, P) // https://rbuckton.github.io/reflect-metadata/#ordinarydefineownmetadata function OrdinaryDefineOwnMetadata(MetadataKey: any, MetadataValue: any, O: any, P: string | symbol | undefined): void { - const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ true); - metadataMap.set(MetadataKey, MetadataValue); + const provider = GetMetadataProvider(O, P, /*Create*/ true); + provider.OrdinaryDefineOwnMetadata(MetadataKey, MetadataValue, O, P); } // 3.1.6.1 OrdinaryMetadataKeys(O, P) @@ -1262,32 +1234,11 @@ namespace Reflect { // 3.1.7.1 OrdinaryOwnMetadataKeys(O, P) // https://rbuckton.github.io/reflect-metadata/#ordinaryownmetadatakeys function OrdinaryOwnMetadataKeys(O: any, P: string | symbol | undefined): any[] { - const keys: any[] = []; - const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); - if (IsUndefined(metadataMap)) return keys; - const keysObj = metadataMap.keys(); - const iterator = GetIterator(keysObj); - let k = 0; - while (true) { - const next = IteratorStep(iterator); - if (!next) { - keys.length = k; - return keys; - } - const nextValue = IteratorValue(next); - try { - keys[k] = nextValue; - } - catch (e) { - try { - IteratorClose(iterator); - } - finally { - throw e; - } - } - k++; + const provider = GetMetadataProvider(O, P, /*create*/ false); + if (!provider) { + return []; } + return provider.OrdinaryOwnMetadataKeys(O, P); } // 6 ECMAScript Data Typ0es and Values @@ -1534,6 +1485,271 @@ namespace Reflect { return constructor; } + // Global metadata registry + // - Allows `import "reflect-metadata"` and `import "reflect-metadata/no-conflict"` to interoperate. + // - Uses isolated metadata if `Reflect` is frozen before the registry can be installed. + + const registrySymbol = supportsSymbol ? Symbol.for("@reflect-metadata:registry") : undefined; + const metadataRegistry = GetOrCreateMetadataRegistry(); + const metadataProvider = CreateMetadataProvider(metadataRegistry); + + /** + * Creates a registry used to allow multiple `reflect-metadata` providers. + */ + function CreateMetadataRegistry(): MetadataRegistry { + let first: MetadataProvider | undefined; + let second: MetadataProvider | undefined; + let rest: Set | undefined; + const targetProviderMap = new _WeakMap>(); + const registry: MetadataRegistry = { + registerProvider, + getProvider, + setProvider, + }; + return registry; + + function registerProvider(provider: MetadataProvider) { + if (!Object.isExtensible(registry)) { + throw new Error("Cannot add provider to a frozen registry."); + } + switch (true) { + case IsUndefined(first): first = provider; break; + case first === provider: break; + case IsUndefined(second): second = provider; break; + case second === provider: break; + default: + if (rest === undefined) rest = new _Set(); + rest.add(provider); + break; + } + } + + function getProviderNoCache(O: object, P: string | symbol | undefined) { + if (IsUndefined(first)) return undefined; + if (first.isProviderFor(O, P)) return first; + if (IsUndefined(second)) return undefined; + if (second.isProviderFor(O, P)) return first; + if (IsUndefined(rest)) return undefined; + const iterator = GetIterator(rest); + while (true) { + const next = IteratorStep(iterator); + if (!next) { + return undefined; + } + const provider = IteratorValue(next); + if (provider.isProviderFor(O, P)) { + IteratorClose(iterator); + return provider; + } + } + } + + function getProvider(O: object, P: string | symbol | undefined) { + let providerMap = targetProviderMap.get(O); + let provider: MetadataProvider | undefined; + if (!IsUndefined(providerMap)) { + provider = providerMap.get(P); + } + if (!IsUndefined(provider)) { + return provider; + } + + provider = getProviderNoCache(O, P); + if (!IsUndefined(provider)) { + if (IsUndefined(providerMap)) { + providerMap = new _Map(); + targetProviderMap.set(O, providerMap); + } + providerMap.set(P, provider); + } + return provider; + } + + function hasProvider(provider: MetadataProvider) { + return first === provider || second === provider || !IsUndefined(rest) && rest.has(provider); + } + + function setProvider(O: object, P: string | symbol | undefined, provider: MetadataProvider) { + if (!hasProvider(provider)) { + throw new Error("Metadata provider not registered."); + } + const existingProvider = getProvider(O, P); + if (existingProvider !== provider) { + if (!IsUndefined(existingProvider)) { + return false; + } + let providerMap = targetProviderMap.get(O); + if (IsUndefined(providerMap)) { + providerMap = new _Map(); + targetProviderMap.set(O, providerMap); + } + providerMap.set(P, provider); + } + return true; + } + } + + /** + * Gets or creates the shared registry of metadata providers. + */ + function GetOrCreateMetadataRegistry(): MetadataRegistry { + let metadataRegistry: MetadataRegistry | undefined; + if (!IsUndefined(registrySymbol) && IsObject(root.Reflect) && Object.isExtensible(root.Reflect)) { + metadataRegistry = (root.Reflect as any)[registrySymbol] as MetadataRegistry | undefined; + } + if (IsUndefined(metadataRegistry)) { + metadataRegistry = CreateMetadataRegistry(); + } + if (!IsUndefined(registrySymbol) && IsObject(root.Reflect) && Object.isExtensible(root.Reflect)) { + Object.defineProperty(root.Reflect, registrySymbol, { + enumerable: false, + configurable: false, + writable: false, + value: metadataRegistry + }); + } + return metadataRegistry; + } + + function CreateMetadataProvider(registry: MetadataRegistry): MetadataProvider { + // [[Metadata]] internal slot + // https://rbuckton.github.io/reflect-metadata/#ordinary-object-internal-methods-and-internal-slots + const metadata = new _WeakMap>>(); + const provider: MetadataProvider = { + isProviderFor(O, P) { + const targetMetadata = metadata.get(O); + if (IsUndefined(targetMetadata)) return false; + return targetMetadata.has(P); + }, + OrdinaryDefineOwnMetadata: OrdinaryDefineOwnMetadata, + OrdinaryHasOwnMetadata: OrdinaryHasOwnMetadata, + OrdinaryGetOwnMetadata: OrdinaryGetOwnMetadata, + OrdinaryOwnMetadataKeys: OrdinaryOwnMetadataKeys, + OrdinaryDeleteMetadata: OrdinaryDeleteMetadata, + }; + metadataRegistry.registerProvider(provider); + return provider; + + // 2.1.1 GetOrCreateMetadataMap(O, P, Create) + // https://rbuckton.github.io/reflect-metadata/#getorcreatemetadatamap + function GetOrCreateMetadataMap(O: object, P: string | symbol | undefined, Create: true): Map; + function GetOrCreateMetadataMap(O: object, P: string | symbol | undefined, Create: false): Map | undefined; + function GetOrCreateMetadataMap(O: object, P: string | symbol | undefined, Create: boolean) { + let targetMetadata = metadata.get(O); + let createdTargetMetadata = false; + if (IsUndefined(targetMetadata)) { + if (!Create) return undefined; + targetMetadata = new _Map>(); + metadata.set(O, targetMetadata); + createdTargetMetadata = true; + } + let metadataMap = targetMetadata.get(P); + if (IsUndefined(metadataMap)) { + if (!Create) return undefined; + metadataMap = new _Map(); + targetMetadata.set(P, metadataMap); + if (!registry.setProvider(O, P, provider)) { + targetMetadata.delete(P); + if (createdTargetMetadata) { + metadata.delete(O); + } + throw new Error("Wrong provider for target."); + } + } + return metadataMap; + } + + // 3.1.2.1 OrdinaryHasOwnMetadata(MetadataKey, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinaryhasownmetadata + function OrdinaryHasOwnMetadata(MetadataKey: any, O: object, P: string | symbol | undefined): boolean { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return false; + return ToBoolean(metadataMap.has(MetadataKey)); + } + + // 3.1.4.1 OrdinaryGetOwnMetadata(MetadataKey, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinarygetownmetadata + function OrdinaryGetOwnMetadata(MetadataKey: any, O: object, P: string | symbol | undefined): any { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return undefined; + return metadataMap.get(MetadataKey); + } + + // 3.1.5.1 OrdinaryDefineOwnMetadata(MetadataKey, MetadataValue, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinarydefineownmetadata + function OrdinaryDefineOwnMetadata(MetadataKey: any, MetadataValue: any, O: object, P: string | symbol | undefined): void { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ true); + metadataMap.set(MetadataKey, MetadataValue); + } + + // 3.1.7.1 OrdinaryOwnMetadataKeys(O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinaryownmetadatakeys + function OrdinaryOwnMetadataKeys(O: any, P: string | symbol | undefined): any[] { + const keys: any[] = []; + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return keys; + const keysObj = metadataMap.keys(); + const iterator = GetIterator(keysObj); + let k = 0; + while (true) { + const next = IteratorStep(iterator); + if (!next) { + keys.length = k; + return keys; + } + const nextValue = IteratorValue(next); + try { + keys[k] = nextValue; + } + catch (e) { + try { + IteratorClose(iterator); + } + finally { + throw e; + } + } + k++; + } + } + + function OrdinaryDeleteMetadata(MetadataKey: any, O: object, P: string | symbol | undefined): boolean { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return false; + if (!metadataMap.delete(MetadataKey)) return false; + if (metadataMap.size === 0) { + const targetMetadata = metadata.get(O); + if (!IsUndefined(targetMetadata)) { + targetMetadata.delete(P); + if (targetMetadata.size === 0) { + metadata.delete(targetMetadata); + } + } + } + return true; + } + } + + function GetMetadataProvider(O: object, P: string | symbol | undefined, Create: true): MetadataProvider; + function GetMetadataProvider(O: object, P: string | symbol | undefined, Create: false): MetadataProvider | undefined; + /** + * Gets the metadata provider for an object. If the object has no metadata provider and this is for a create operation, + * then this module's metadata provider is assigned to the object. + */ + function GetMetadataProvider(O: object, P: string | symbol | undefined, Create: boolean): MetadataProvider | undefined { + const registeredProvider = metadataRegistry.getProvider(O, P); + if (!IsUndefined(registeredProvider)) { + return registeredProvider; + } + if (Create) { + if (metadataRegistry.setProvider(O, P, metadataProvider)) { + return metadataProvider; + } + throw new Error("Illegal state."); + } + return undefined; + } + // naive Map shim function CreateMapPolyfill(): MapConstructor { const cacheSentinel = {}; diff --git a/ReflectLite.ts b/ReflectLite.ts index 78153af..51ad86d 100644 --- a/ReflectLite.ts +++ b/ReflectLite.ts @@ -545,7 +545,7 @@ namespace Reflect { */ export declare function deleteMetadata(metadataKey: any, target: any, propertyKey: string | symbol): boolean; - (function (this: any, factory: (exporter: (key: K, value: typeof Reflect[K]) => void) => void) { + (function (this: any, factory: (exporter: (key: K, value: typeof Reflect[K]) => void, root: any) => void) { const root = typeof globalThis === "object" ? globalThis : typeof global === "object" ? global : @@ -561,7 +561,7 @@ namespace Reflect { exporter = makeExporter(root.Reflect, exporter); } - factory(exporter); + factory(exporter, root); function makeExporter(target: typeof Reflect, previous?: (key: K, value: typeof Reflect[K]) => void) { return (key: K, value: typeof Reflect[K]) => { @@ -584,7 +584,7 @@ namespace Reflect { return functionThis() || indirectEvalThis(); } }) - (function (exporter) { + (function (exporter, root) { // feature test for Symbol support const supportsSymbol = typeof Symbol === "function"; const toPrimitiveSymbol = supportsSymbol && typeof Symbol.toPrimitive !== "undefined" ? Symbol.toPrimitive : fail("Symbol.toPrimitive not found."); @@ -596,10 +596,6 @@ namespace Reflect { const _Set: typeof Set = typeof Set === "function" && typeof Set.prototype.entries === "function" ? Set : fail("A valid Set constructor could not be found."); const _WeakMap: typeof WeakMap = typeof WeakMap === "function" ? WeakMap : fail("A valid WeakMap constructor could not be found."); - // [[Metadata]] internal slot - // https://rbuckton.github.io/reflect-metadata/#ordinary-object-internal-methods-and-internal-slots - const Metadata = new _WeakMap>>(); - function decorate(decorators: ClassDecorator[], target: Function): Function; function decorate(decorators: (PropertyDecorator | MethodDecorator)[], target: any, propertyKey: string | symbol, attributes?: PropertyDescriptor | null): PropertyDescriptor | undefined; function decorate(decorators: (PropertyDecorator | MethodDecorator)[], target: any, propertyKey: string | symbol, attributes: PropertyDescriptor): PropertyDescriptor; @@ -1099,15 +1095,9 @@ namespace Reflect { function deleteMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): boolean { if (!IsObject(target)) throw new TypeError(); if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); - const metadataMap = GetOrCreateMetadataMap(target, propertyKey, /*Create*/ false); - if (IsUndefined(metadataMap)) return false; - if (!metadataMap.delete(metadataKey)) return false; - if (metadataMap.size > 0) return true; - const targetMetadata = Metadata.get(target); - targetMetadata.delete(propertyKey); - if (targetMetadata.size > 0) return true; - Metadata.delete(target); - return true; + const provider = GetMetadataProvider(target, propertyKey, /*Create*/ false); + if (IsUndefined(provider)) return false; + return provider.OrdinaryDeleteMetadata(metadataKey, target, propertyKey); } exporter("deleteMetadata", deleteMetadata); @@ -1136,26 +1126,6 @@ namespace Reflect { return descriptor; } - // 2.1.1 GetOrCreateMetadataMap(O, P, Create) - // https://rbuckton.github.io/reflect-metadata/#getorcreatemetadatamap - function GetOrCreateMetadataMap(O: any, P: string | symbol | undefined, Create: true): Map; - function GetOrCreateMetadataMap(O: any, P: string | symbol | undefined, Create: false): Map | undefined; - function GetOrCreateMetadataMap(O: any, P: string | symbol | undefined, Create: boolean): Map | undefined { - let targetMetadata = Metadata.get(O); - if (IsUndefined(targetMetadata)) { - if (!Create) return undefined; - targetMetadata = new _Map>(); - Metadata.set(O, targetMetadata); - } - let metadataMap = targetMetadata.get(P); - if (IsUndefined(metadataMap)) { - if (!Create) return undefined; - metadataMap = new _Map(); - targetMetadata.set(P, metadataMap); - } - return metadataMap; - } - // 3.1.1.1 OrdinaryHasMetadata(MetadataKey, O, P) // https://rbuckton.github.io/reflect-metadata/#ordinaryhasmetadata function OrdinaryHasMetadata(MetadataKey: any, O: any, P: string | symbol | undefined): boolean { @@ -1169,9 +1139,9 @@ namespace Reflect { // 3.1.2.1 OrdinaryHasOwnMetadata(MetadataKey, O, P) // https://rbuckton.github.io/reflect-metadata/#ordinaryhasownmetadata function OrdinaryHasOwnMetadata(MetadataKey: any, O: any, P: string | symbol | undefined): boolean { - const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); - if (IsUndefined(metadataMap)) return false; - return ToBoolean(metadataMap.has(MetadataKey)); + const provider = GetMetadataProvider(O, P, /*Create*/ false); + if (IsUndefined(provider)) return false; + return ToBoolean(provider.OrdinaryHasOwnMetadata(MetadataKey, O, P)); } // 3.1.3.1 OrdinaryGetMetadata(MetadataKey, O, P) @@ -1187,16 +1157,16 @@ namespace Reflect { // 3.1.4.1 OrdinaryGetOwnMetadata(MetadataKey, O, P) // https://rbuckton.github.io/reflect-metadata/#ordinarygetownmetadata function OrdinaryGetOwnMetadata(MetadataKey: any, O: any, P: string | symbol | undefined): any { - const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); - if (IsUndefined(metadataMap)) return undefined; - return metadataMap.get(MetadataKey); + const provider = GetMetadataProvider(O, P, /*Create*/ false); + if (IsUndefined(provider)) return; + return provider.OrdinaryGetOwnMetadata(MetadataKey, O, P); } // 3.1.5.1 OrdinaryDefineOwnMetadata(MetadataKey, MetadataValue, O, P) // https://rbuckton.github.io/reflect-metadata/#ordinarydefineownmetadata function OrdinaryDefineOwnMetadata(MetadataKey: any, MetadataValue: any, O: any, P: string | symbol | undefined): void { - const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ true); - metadataMap.set(MetadataKey, MetadataValue); + const provider = GetMetadataProvider(O, P, /*Create*/ true); + provider.OrdinaryDefineOwnMetadata(MetadataKey, MetadataValue, O, P); } // 3.1.6.1 OrdinaryMetadataKeys(O, P) @@ -1230,32 +1200,11 @@ namespace Reflect { // 3.1.7.1 OrdinaryOwnMetadataKeys(O, P) // https://rbuckton.github.io/reflect-metadata/#ordinaryownmetadatakeys function OrdinaryOwnMetadataKeys(O: any, P: string | symbol | undefined): any[] { - const keys: any[] = []; - const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); - if (IsUndefined(metadataMap)) return keys; - const keysObj = metadataMap.keys(); - const iterator = GetIterator(keysObj); - let k = 0; - while (true) { - const next = IteratorStep(iterator); - if (!next) { - keys.length = k; - return keys; - } - const nextValue = IteratorValue(next); - try { - keys[k] = nextValue; - } - catch (e) { - try { - IteratorClose(iterator); - } - finally { - throw e; - } - } - k++; + const provider = GetMetadataProvider(O, P, /*create*/ false); + if (!provider) { + return []; } + return provider.OrdinaryOwnMetadataKeys(O, P); } // 6 ECMAScript Data Typ0es and Values @@ -1501,5 +1450,270 @@ namespace Reflect { function fail(e: any): never { throw e; } + + // Global metadata registry + // - Allows `import "reflect-metadata"` and `import "reflect-metadata/no-conflict"` to interoperate. + // - Uses isolated metadata if `Reflect` is frozen before the registry can be installed. + + const registrySymbol = supportsSymbol ? Symbol.for("@reflect-metadata:registry") : undefined; + const metadataRegistry = GetOrCreateMetadataRegistry(); + const metadataProvider = CreateMetadataProvider(metadataRegistry); + + /** + * Creates a registry used to allow multiple `reflect-metadata` providers. + */ + function CreateMetadataRegistry(): MetadataRegistry { + let first: MetadataProvider | undefined; + let second: MetadataProvider | undefined; + let rest: Set | undefined; + const targetProviderMap = new _WeakMap>(); + const registry: MetadataRegistry = { + registerProvider, + getProvider, + setProvider, + }; + return registry; + + function registerProvider(provider: MetadataProvider) { + if (!Object.isExtensible(registry)) { + throw new Error("Cannot add provider to a frozen registry."); + } + switch (true) { + case IsUndefined(first): first = provider; break; + case first === provider: break; + case IsUndefined(second): second = provider; break; + case second === provider: break; + default: + if (rest === undefined) rest = new _Set(); + rest.add(provider); + break; + } + } + + function getProviderNoCache(O: object, P: string | symbol | undefined) { + if (IsUndefined(first)) return undefined; + if (first.isProviderFor(O, P)) return first; + if (IsUndefined(second)) return undefined; + if (second.isProviderFor(O, P)) return first; + if (IsUndefined(rest)) return undefined; + const iterator = GetIterator(rest); + while (true) { + const next = IteratorStep(iterator); + if (!next) { + return undefined; + } + const provider = IteratorValue(next); + if (provider.isProviderFor(O, P)) { + IteratorClose(iterator); + return provider; + } + } + } + + function getProvider(O: object, P: string | symbol | undefined) { + let providerMap = targetProviderMap.get(O); + let provider: MetadataProvider | undefined; + if (!IsUndefined(providerMap)) { + provider = providerMap.get(P); + } + if (!IsUndefined(provider)) { + return provider; + } + + provider = getProviderNoCache(O, P); + if (!IsUndefined(provider)) { + if (IsUndefined(providerMap)) { + providerMap = new _Map(); + targetProviderMap.set(O, providerMap); + } + providerMap.set(P, provider); + } + return provider; + } + + function hasProvider(provider: MetadataProvider) { + return first === provider || second === provider || !IsUndefined(rest) && rest.has(provider); + } + + function setProvider(O: object, P: string | symbol | undefined, provider: MetadataProvider) { + if (!hasProvider(provider)) { + throw new Error("Metadata provider not registered."); + } + const existingProvider = getProvider(O, P); + if (existingProvider !== provider) { + if (!IsUndefined(existingProvider)) { + return false; + } + let providerMap = targetProviderMap.get(O); + if (IsUndefined(providerMap)) { + providerMap = new _Map(); + targetProviderMap.set(O, providerMap); + } + providerMap.set(P, provider); + } + return true; + } + } + + /** + * Gets or creates the shared registry of metadata providers. + */ + function GetOrCreateMetadataRegistry(): MetadataRegistry { + let metadataRegistry: MetadataRegistry | undefined; + if (!IsUndefined(registrySymbol) && IsObject(root.Reflect) && Object.isExtensible(root.Reflect)) { + metadataRegistry = (root.Reflect as any)[registrySymbol] as MetadataRegistry | undefined; + } + if (IsUndefined(metadataRegistry)) { + metadataRegistry = CreateMetadataRegistry(); + } + if (!IsUndefined(registrySymbol) && IsObject(root.Reflect) && Object.isExtensible(root.Reflect)) { + Object.defineProperty(root.Reflect, registrySymbol, { + enumerable: false, + configurable: false, + writable: false, + value: metadataRegistry + }); + } + return metadataRegistry; + } + + function CreateMetadataProvider(registry: MetadataRegistry): MetadataProvider { + // [[Metadata]] internal slot + // https://rbuckton.github.io/reflect-metadata/#ordinary-object-internal-methods-and-internal-slots + const metadata = new _WeakMap>>(); + const provider: MetadataProvider = { + isProviderFor(O, P) { + const targetMetadata = metadata.get(O); + if (IsUndefined(targetMetadata)) return false; + return targetMetadata.has(P); + }, + OrdinaryDefineOwnMetadata: OrdinaryDefineOwnMetadata, + OrdinaryHasOwnMetadata: OrdinaryHasOwnMetadata, + OrdinaryGetOwnMetadata: OrdinaryGetOwnMetadata, + OrdinaryOwnMetadataKeys: OrdinaryOwnMetadataKeys, + OrdinaryDeleteMetadata: OrdinaryDeleteMetadata, + }; + metadataRegistry.registerProvider(provider); + return provider; + + // 2.1.1 GetOrCreateMetadataMap(O, P, Create) + // https://rbuckton.github.io/reflect-metadata/#getorcreatemetadatamap + function GetOrCreateMetadataMap(O: object, P: string | symbol | undefined, Create: true): Map; + function GetOrCreateMetadataMap(O: object, P: string | symbol | undefined, Create: false): Map | undefined; + function GetOrCreateMetadataMap(O: object, P: string | symbol | undefined, Create: boolean) { + let targetMetadata = metadata.get(O); + let createdTargetMetadata = false; + if (IsUndefined(targetMetadata)) { + if (!Create) return undefined; + targetMetadata = new _Map>(); + metadata.set(O, targetMetadata); + createdTargetMetadata = true; + } + let metadataMap = targetMetadata.get(P); + if (IsUndefined(metadataMap)) { + if (!Create) return undefined; + metadataMap = new _Map(); + targetMetadata.set(P, metadataMap); + if (!registry.setProvider(O, P, provider)) { + targetMetadata.delete(P); + if (createdTargetMetadata) { + metadata.delete(O); + } + throw new Error("Wrong provider for target."); + } + } + return metadataMap; + } + + // 3.1.2.1 OrdinaryHasOwnMetadata(MetadataKey, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinaryhasownmetadata + function OrdinaryHasOwnMetadata(MetadataKey: any, O: object, P: string | symbol | undefined): boolean { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return false; + return ToBoolean(metadataMap.has(MetadataKey)); + } + + // 3.1.4.1 OrdinaryGetOwnMetadata(MetadataKey, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinarygetownmetadata + function OrdinaryGetOwnMetadata(MetadataKey: any, O: object, P: string | symbol | undefined): any { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return undefined; + return metadataMap.get(MetadataKey); + } + + // 3.1.5.1 OrdinaryDefineOwnMetadata(MetadataKey, MetadataValue, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinarydefineownmetadata + function OrdinaryDefineOwnMetadata(MetadataKey: any, MetadataValue: any, O: object, P: string | symbol | undefined): void { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ true); + metadataMap.set(MetadataKey, MetadataValue); + } + + // 3.1.7.1 OrdinaryOwnMetadataKeys(O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinaryownmetadatakeys + function OrdinaryOwnMetadataKeys(O: any, P: string | symbol | undefined): any[] { + const keys: any[] = []; + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return keys; + const keysObj = metadataMap.keys(); + const iterator = GetIterator(keysObj); + let k = 0; + while (true) { + const next = IteratorStep(iterator); + if (!next) { + keys.length = k; + return keys; + } + const nextValue = IteratorValue(next); + try { + keys[k] = nextValue; + } + catch (e) { + try { + IteratorClose(iterator); + } + finally { + throw e; + } + } + k++; + } + } + + function OrdinaryDeleteMetadata(MetadataKey: any, O: object, P: string | symbol | undefined): boolean { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return false; + if (!metadataMap.delete(MetadataKey)) return false; + if (metadataMap.size === 0) { + const targetMetadata = metadata.get(O); + if (!IsUndefined(targetMetadata)) { + targetMetadata.delete(P); + if (targetMetadata.size === 0) { + metadata.delete(targetMetadata); + } + } + } + return true; + } + } + + function GetMetadataProvider(O: object, P: string | symbol | undefined, Create: true): MetadataProvider; + function GetMetadataProvider(O: object, P: string | symbol | undefined, Create: false): MetadataProvider | undefined; + /** + * Gets the metadata provider for an object. If the object has no metadata provider and this is for a create operation, + * then this module's metadata provider is assigned to the object. + */ + function GetMetadataProvider(O: object, P: string | symbol | undefined, Create: boolean): MetadataProvider | undefined { + const registeredProvider = metadataRegistry.getProvider(O, P); + if (!IsUndefined(registeredProvider)) { + return registeredProvider; + } + if (Create) { + if (metadataRegistry.setProvider(O, P, metadataProvider)) { + return metadataProvider; + } + throw new Error("Illegal state."); + } + return undefined; + } }); } diff --git a/ReflectNoConflict.ts b/ReflectNoConflict.ts index 86d1299..9c648eb 100644 --- a/ReflectNoConflict.ts +++ b/ReflectNoConflict.ts @@ -29,11 +29,6 @@ const _Map: typeof Map = typeof Map === "function" && typeof Map.prototype.entri const _Set: typeof Set = typeof Set === "function" && typeof Set.prototype.entries === "function" ? Set : fail("A valid Set constructor could not be found."); const _WeakMap: typeof WeakMap = typeof WeakMap === "function" ? WeakMap : fail("A valid WeakMap constructor could not be found."); -// [[Metadata]] internal slot -// https://rbuckton.github.io/reflect-metadata/#ordinary-object-internal-methods-and-internal-slots -const Metadata = new _WeakMap>>(); -let hasSetMetadata = false; - /** * Applies a set of decorators to a target object. * @param decorators An array of decorators. @@ -337,9 +332,6 @@ export function defineMetadata(metadataKey: any, metadataValue: any, target: any * */ export function defineMetadata(metadataKey: any, metadataValue: any, target: any, propertyKey?: string | symbol): void { - if (!hasSetMetadata && typeof Reflect !== "undefined" && typeof Reflect.defineMetadata === "function" && Reflect.defineMetadata !== defineMetadata) { - return Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey!); - } if (!IsObject(target)) throw new TypeError(); if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); return OrdinaryDefineOwnMetadata(metadataKey, metadataValue, target, propertyKey); @@ -431,9 +423,6 @@ export function hasMetadata(metadataKey: any, target: any, propertyKey: string | * */ export function hasMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): boolean { - if (!hasSetMetadata && typeof Reflect !== "undefined" && typeof Reflect.hasMetadata === "function" && Reflect.hasMetadata !== hasMetadata) { - return Reflect.hasMetadata(metadataKey, target, propertyKey!); - } if (!IsObject(target)) throw new TypeError(); if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); return OrdinaryHasMetadata(metadataKey, target, propertyKey); @@ -525,9 +514,6 @@ export function hasOwnMetadata(metadataKey: any, target: any, propertyKey: strin * */ export function hasOwnMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): boolean { - if (!hasSetMetadata && typeof Reflect !== "undefined" && typeof Reflect.hasOwnMetadata === "function" && Reflect.hasOwnMetadata !== hasOwnMetadata) { - return Reflect.hasOwnMetadata(metadataKey, target, propertyKey!); - } if (!IsObject(target)) throw new TypeError(); if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); return OrdinaryHasOwnMetadata(metadataKey, target, propertyKey); @@ -619,9 +605,6 @@ export function getMetadata(metadataKey: any, target: any, propertyKey: string | * */ export function getMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): any { - if (!hasSetMetadata && typeof Reflect !== "undefined" && typeof Reflect.getMetadata === "function" && Reflect.getMetadata !== getMetadata) { - return Reflect.getMetadata(metadataKey, target, propertyKey!); - } if (!IsObject(target)) throw new TypeError(); if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); return OrdinaryGetMetadata(metadataKey, target, propertyKey); @@ -713,9 +696,6 @@ export function getOwnMetadata(metadataKey: any, target: any, propertyKey: strin * */ export function getOwnMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): any { - if (!hasSetMetadata && typeof Reflect !== "undefined" && typeof Reflect.getOwnMetadata === "function" && Reflect.getOwnMetadata !== getOwnMetadata) { - return Reflect.getOwnMetadata(metadataKey, target, propertyKey!); - } if (!IsObject(target)) throw new TypeError(); if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); return OrdinaryGetOwnMetadata(metadataKey, target, propertyKey); @@ -804,9 +784,6 @@ export function getMetadataKeys(target: any, propertyKey: string | symbol): any[ * */ export function getMetadataKeys(target: any, propertyKey?: string | symbol): any[] { - if (!hasSetMetadata && typeof Reflect !== "undefined" && typeof Reflect.getMetadataKeys === "function" && Reflect.getMetadataKeys !== getMetadataKeys) { - return Reflect.getMetadataKeys(target, propertyKey!); - } if (!IsObject(target)) throw new TypeError(); if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); return OrdinaryMetadataKeys(target, propertyKey); @@ -895,9 +872,6 @@ export function getOwnMetadataKeys(target: any, propertyKey: string | symbol): a * */ export function getOwnMetadataKeys(target: any, propertyKey?: string | symbol): any[] { - if (!hasSetMetadata && typeof Reflect !== "undefined" && typeof Reflect.getOwnMetadataKeys === "function" && Reflect.getOwnMetadataKeys !== getOwnMetadataKeys) { - return Reflect.getOwnMetadataKeys(target, propertyKey!); - } if (!IsObject(target)) throw new TypeError(); if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); return OrdinaryOwnMetadataKeys(target, propertyKey); @@ -989,20 +963,11 @@ export function deleteMetadata(metadataKey: any, target: any, propertyKey: strin * */ export function deleteMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): boolean { - if (!hasSetMetadata && typeof Reflect !== "undefined" && typeof Reflect.deleteMetadata === "function" && Reflect.deleteMetadata !== deleteMetadata) { - return Reflect.deleteMetadata(metadataKey, target, propertyKey!); - } if (!IsObject(target)) throw new TypeError(); if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); - const metadataMap = GetOrCreateMetadataMap(target, propertyKey, /*Create*/ false); - if (IsUndefined(metadataMap)) return false; - if (!metadataMap.delete(metadataKey)) return false; - if (metadataMap.size > 0) return true; - const targetMetadata = Metadata.get(target); - targetMetadata.delete(propertyKey); - if (targetMetadata.size > 0) return true; - Metadata.delete(target); - return true; + const provider = GetMetadataProvider(target, propertyKey, /*Create*/ false); + if (IsUndefined(provider)) return false; + return provider.OrdinaryDeleteMetadata(metadataKey, target, propertyKey); } function DecorateConstructor(decorators: ClassDecorator[], target: Function): Function { @@ -1029,27 +994,6 @@ function DecorateProperty(decorators: MemberDecorator[], target: any, propertyKe return descriptor; } -// 2.1.1 GetOrCreateMetadataMap(O, P, Create) -// https://rbuckton.github.io/reflect-metadata/#getorcreatemetadatamap -function GetOrCreateMetadataMap(O: any, P: string | symbol | undefined, Create: true): Map; -function GetOrCreateMetadataMap(O: any, P: string | symbol | undefined, Create: false): Map | undefined; -function GetOrCreateMetadataMap(O: any, P: string | symbol | undefined, Create: boolean): Map | undefined { - let targetMetadata = Metadata.get(O); - if (IsUndefined(targetMetadata)) { - if (!Create) return undefined; - targetMetadata = new _Map>(); - Metadata.set(O, targetMetadata); - hasSetMetadata = true; - } - let metadataMap = targetMetadata.get(P); - if (IsUndefined(metadataMap)) { - if (!Create) return undefined; - metadataMap = new _Map(); - targetMetadata.set(P, metadataMap); - } - return metadataMap; -} - // 3.1.1.1 OrdinaryHasMetadata(MetadataKey, O, P) // https://rbuckton.github.io/reflect-metadata/#ordinaryhasmetadata function OrdinaryHasMetadata(MetadataKey: any, O: any, P: string | symbol | undefined): boolean { @@ -1063,9 +1007,9 @@ function OrdinaryHasMetadata(MetadataKey: any, O: any, P: string | symbol | unde // 3.1.2.1 OrdinaryHasOwnMetadata(MetadataKey, O, P) // https://rbuckton.github.io/reflect-metadata/#ordinaryhasownmetadata function OrdinaryHasOwnMetadata(MetadataKey: any, O: any, P: string | symbol | undefined): boolean { - const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); - if (IsUndefined(metadataMap)) return false; - return ToBoolean(metadataMap.has(MetadataKey)); + const provider = GetMetadataProvider(O, P, /*Create*/ false); + if (IsUndefined(provider)) return false; + return ToBoolean(provider.OrdinaryHasOwnMetadata(MetadataKey, O, P)); } // 3.1.3.1 OrdinaryGetMetadata(MetadataKey, O, P) @@ -1081,16 +1025,16 @@ function OrdinaryGetMetadata(MetadataKey: any, O: any, P: string | symbol | unde // 3.1.4.1 OrdinaryGetOwnMetadata(MetadataKey, O, P) // https://rbuckton.github.io/reflect-metadata/#ordinarygetownmetadata function OrdinaryGetOwnMetadata(MetadataKey: any, O: any, P: string | symbol | undefined): any { - const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); - if (IsUndefined(metadataMap)) return undefined; - return metadataMap.get(MetadataKey); + const provider = GetMetadataProvider(O, P, /*Create*/ false); + if (IsUndefined(provider)) return; + return provider.OrdinaryGetOwnMetadata(MetadataKey, O, P); } // 3.1.5.1 OrdinaryDefineOwnMetadata(MetadataKey, MetadataValue, O, P) // https://rbuckton.github.io/reflect-metadata/#ordinarydefineownmetadata function OrdinaryDefineOwnMetadata(MetadataKey: any, MetadataValue: any, O: any, P: string | symbol | undefined): void { - const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ true); - metadataMap.set(MetadataKey, MetadataValue); + const provider = GetMetadataProvider(O, P, /*Create*/ true); + provider.OrdinaryDefineOwnMetadata(MetadataKey, MetadataValue, O, P); } // 3.1.6.1 OrdinaryMetadataKeys(O, P) @@ -1124,32 +1068,11 @@ function OrdinaryMetadataKeys(O: any, P: string | symbol | undefined): any[] { // 3.1.7.1 OrdinaryOwnMetadataKeys(O, P) // https://rbuckton.github.io/reflect-metadata/#ordinaryownmetadatakeys function OrdinaryOwnMetadataKeys(O: any, P: string | symbol | undefined): any[] { - const keys: any[] = []; - const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); - if (IsUndefined(metadataMap)) return keys; - const keysObj = metadataMap.keys(); - const iterator = GetIterator(keysObj); - let k = 0; - while (true) { - const next = IteratorStep(iterator); - if (!next) { - keys.length = k; - return keys; - } - const nextValue = IteratorValue(next); - try { - keys[k] = nextValue; - } - catch (e) { - try { - IteratorClose(iterator); - } - finally { - throw e; - } - } - k++; + const provider = GetMetadataProvider(O, P, /*create*/ false); + if (!provider) { + return []; } + return provider.OrdinaryOwnMetadataKeys(O, P); } // 6 ECMAScript Data Typ0es and Values @@ -1395,3 +1318,299 @@ function OrdinaryGetPrototypeOf(O: any): any { function fail(e: any): never { throw e; } + +// Global metadata registry +// - Allows `import "reflect-metadata"` and `import "reflect-metadata/no-conflict"` to interoperate. +// - Uses isolated metadata if `Reflect` is frozen before the registry can be installed. + +const registrySymbol = supportsSymbol ? Symbol.for("@reflect-metadata:registry") : undefined; +const metadataRegistry = GetOrCreateMetadataRegistry(); +const metadataProvider = CreateMetadataProvider(metadataRegistry); + +/** + * Creates a registry used to allow multiple `reflect-metadata` providers. + */ +function CreateMetadataRegistry(): MetadataRegistry { + let first: MetadataProvider | undefined; + let second: MetadataProvider | undefined; + let rest: Set | undefined; + const targetProviderMap = new _WeakMap>(); + const registry: MetadataRegistry = { + registerProvider, + getProvider, + setProvider, + }; + return registry; + + function registerProvider(provider: MetadataProvider) { + if (!Object.isExtensible(registry)) { + throw new Error("Cannot add provider to a frozen registry."); + } + switch (true) { + case IsUndefined(first): first = provider; break; + case first === provider: break; + case IsUndefined(second): second = provider; break; + case second === provider: break; + default: + if (rest === undefined) rest = new _Set(); + rest.add(provider); + break; + } + } + + function getProviderNoCache(O: object, P: string | symbol | undefined) { + if (IsUndefined(first)) return undefined; + if (first.isProviderFor(O, P)) return first; + if (IsUndefined(second)) return undefined; + if (second.isProviderFor(O, P)) return first; + if (IsUndefined(rest)) return undefined; + const iterator = GetIterator(rest); + while (true) { + const next = IteratorStep(iterator); + if (!next) { + return undefined; + } + const provider = IteratorValue(next); + if (provider.isProviderFor(O, P)) { + IteratorClose(iterator); + return provider; + } + } + } + + function getProvider(O: object, P: string | symbol | undefined) { + let providerMap = targetProviderMap.get(O); + let provider: MetadataProvider | undefined; + if (!IsUndefined(providerMap)) { + provider = providerMap.get(P); + } + if (!IsUndefined(provider)) { + return provider; + } + + provider = getProviderNoCache(O, P); + if (!IsUndefined(provider)) { + if (IsUndefined(providerMap)) { + providerMap = new _Map(); + targetProviderMap.set(O, providerMap); + } + providerMap.set(P, provider); + } + return provider; + } + + function hasProvider(provider: MetadataProvider) { + return first === provider || second === provider || !IsUndefined(rest) && rest.has(provider); + } + + function setProvider(O: object, P: string | symbol | undefined, provider: MetadataProvider) { + if (!hasProvider(provider)) { + throw new Error("Metadata provider not registered."); + } + const existingProvider = getProvider(O, P); + if (existingProvider !== provider) { + if (!IsUndefined(existingProvider)) { + return false; + } + let providerMap = targetProviderMap.get(O); + if (IsUndefined(providerMap)) { + providerMap = new _Map(); + targetProviderMap.set(O, providerMap); + } + providerMap.set(P, provider); + } + return true; + } +} + +/** + * Gets or creates the shared registry of metadata providers. + */ +function GetOrCreateMetadataRegistry(): MetadataRegistry { + let metadataRegistry: MetadataRegistry | undefined; + if (!IsUndefined(registrySymbol) && IsObject(Reflect) && Object.isExtensible(Reflect)) { + metadataRegistry = (Reflect as any)[registrySymbol] as MetadataRegistry | undefined; + } + if (IsUndefined(metadataRegistry)) { + metadataRegistry = CreateMetadataRegistry(); + } + if (!IsUndefined(registrySymbol) && IsObject(Reflect) && Object.isExtensible(Reflect)) { + if (typeof Reflect.defineMetadata === "function") { + // `/no-conflict` used in an environment with an older copy of `reflect-metadata`. Add a legacy fallback. + metadataRegistry.registerProvider(CreateFallbackProvider(Reflect)); + } + Object.defineProperty(Reflect, registrySymbol, { + enumerable: false, + configurable: false, + writable: false, + value: metadataRegistry + }); + } + return metadataRegistry; +} + +function CreateMetadataProvider(registry: MetadataRegistry): MetadataProvider { + // [[Metadata]] internal slot + // https://rbuckton.github.io/reflect-metadata/#ordinary-object-internal-methods-and-internal-slots + const metadata = new _WeakMap>>(); + const provider: MetadataProvider = { + isProviderFor(O, P) { + const targetMetadata = metadata.get(O); + if (IsUndefined(targetMetadata)) return false; + return targetMetadata.has(P); + }, + OrdinaryDefineOwnMetadata: OrdinaryDefineOwnMetadata, + OrdinaryHasOwnMetadata: OrdinaryHasOwnMetadata, + OrdinaryGetOwnMetadata: OrdinaryGetOwnMetadata, + OrdinaryOwnMetadataKeys: OrdinaryOwnMetadataKeys, + OrdinaryDeleteMetadata: OrdinaryDeleteMetadata, + }; + metadataRegistry.registerProvider(provider); + return provider; + + // 2.1.1 GetOrCreateMetadataMap(O, P, Create) + // https://rbuckton.github.io/reflect-metadata/#getorcreatemetadatamap + function GetOrCreateMetadataMap(O: object, P: string | symbol | undefined, Create: true): Map; + function GetOrCreateMetadataMap(O: object, P: string | symbol | undefined, Create: false): Map | undefined; + function GetOrCreateMetadataMap(O: object, P: string | symbol | undefined, Create: boolean) { + let targetMetadata = metadata.get(O); + let createdTargetMetadata = false; + if (IsUndefined(targetMetadata)) { + if (!Create) return undefined; + targetMetadata = new _Map>(); + metadata.set(O, targetMetadata); + createdTargetMetadata = true; + } + let metadataMap = targetMetadata.get(P); + if (IsUndefined(metadataMap)) { + if (!Create) return undefined; + metadataMap = new _Map(); + targetMetadata.set(P, metadataMap); + if (!registry.setProvider(O, P, provider)) { + targetMetadata.delete(P); + if (createdTargetMetadata) { + metadata.delete(O); + } + throw new Error("Wrong provider for target."); + } + } + return metadataMap; + } + + // 3.1.2.1 OrdinaryHasOwnMetadata(MetadataKey, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinaryhasownmetadata + function OrdinaryHasOwnMetadata(MetadataKey: any, O: object, P: string | symbol | undefined): boolean { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return false; + return ToBoolean(metadataMap.has(MetadataKey)); + } + + // 3.1.4.1 OrdinaryGetOwnMetadata(MetadataKey, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinarygetownmetadata + function OrdinaryGetOwnMetadata(MetadataKey: any, O: object, P: string | symbol | undefined): any { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return undefined; + return metadataMap.get(MetadataKey); + } + + // 3.1.5.1 OrdinaryDefineOwnMetadata(MetadataKey, MetadataValue, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinarydefineownmetadata + function OrdinaryDefineOwnMetadata(MetadataKey: any, MetadataValue: any, O: object, P: string | symbol | undefined): void { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ true); + metadataMap.set(MetadataKey, MetadataValue); + } + + // 3.1.7.1 OrdinaryOwnMetadataKeys(O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinaryownmetadatakeys + function OrdinaryOwnMetadataKeys(O: any, P: string | symbol | undefined): any[] { + const keys: any[] = []; + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return keys; + const keysObj = metadataMap.keys(); + const iterator = GetIterator(keysObj); + let k = 0; + while (true) { + const next = IteratorStep(iterator); + if (!next) { + keys.length = k; + return keys; + } + const nextValue = IteratorValue(next); + try { + keys[k] = nextValue; + } + catch (e) { + try { + IteratorClose(iterator); + } + finally { + throw e; + } + } + k++; + } + } + + function OrdinaryDeleteMetadata(MetadataKey: any, O: object, P: string | symbol | undefined): boolean { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return false; + if (!metadataMap.delete(MetadataKey)) return false; + if (metadataMap.size === 0) { + const targetMetadata = metadata.get(O); + if (!IsUndefined(targetMetadata)) { + targetMetadata.delete(P); + if (targetMetadata.size === 0) { + metadata.delete(targetMetadata); + } + } + } + return true; + } +} + +function CreateFallbackProvider(reflect: typeof Reflect): MetadataProvider { + const metadataOwner = new _WeakMap>(); + const provider: MetadataProvider = { + isProviderFor(O, P) { + let metadataPropertySet = metadataOwner.get(O); + if (!IsUndefined(metadataPropertySet)) { + return metadataPropertySet.has(P); + } + if (reflect.getOwnMetadataKeys(O, P!).length) { + if (IsUndefined(metadataPropertySet)) { + metadataPropertySet = new _Set(); + metadataOwner.set(O, metadataPropertySet); + } + metadataPropertySet.add(P); + return true; + } + return false; + }, + OrdinaryDefineOwnMetadata: reflect.defineMetadata, + OrdinaryHasOwnMetadata: reflect.hasOwnMetadata, + OrdinaryGetOwnMetadata: reflect.getOwnMetadata, + OrdinaryOwnMetadataKeys: reflect.getOwnMetadataKeys, + OrdinaryDeleteMetadata: reflect.deleteMetadata, + }; + return provider; +} + +function GetMetadataProvider(O: object, P: string | symbol | undefined, Create: true): MetadataProvider; +function GetMetadataProvider(O: object, P: string | symbol | undefined, Create: false): MetadataProvider | undefined; +/** + * Gets the metadata provider for an object. If the object has no metadata provider and this is for a create operation, + * then this module's metadata provider is assigned to the object. + */ +function GetMetadataProvider(O: object, P: string | symbol | undefined, Create: boolean): MetadataProvider | undefined { + const registeredProvider = metadataRegistry.getProvider(O, P); + if (!IsUndefined(registeredProvider)) { + return registeredProvider; + } + if (Create) { + if (metadataRegistry.setProvider(O, P, metadataProvider)) { + return metadataProvider; + } + throw new Error("Illegal state."); + } + return undefined; +} diff --git a/globals.d.ts b/globals.d.ts index 403f574..d0b17ce 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -15,6 +15,7 @@ and limitations under the License. interface SymbolConstructor { (description?: string): symbol; + for(key: string): symbol; readonly iterator: symbol; readonly toPrimitive: symbol; } @@ -89,3 +90,20 @@ interface WeakMapConstructor { declare var Map: MapConstructor; declare var Set: SetConstructor; declare var WeakMap: WeakMapConstructor; + +// NOTE: These are not actually global, just shared between the Reflect*.ts variants + +interface MetadataRegistry { + registerProvider(provider: MetadataProvider): void; + getProvider(O: object, P: string | symbol | undefined): MetadataProvider | undefined; + setProvider(O: object, P: string | symbol | undefined, provider: MetadataProvider): boolean; +} + +interface MetadataProvider { + isProviderFor(O: object, P: string | symbol | undefined): boolean; + OrdinaryDefineOwnMetadata(MetadataKey: any, MetadataValue: any, O: object, P: string | symbol | undefined): void; + OrdinaryDeleteMetadata(MetadataKey: any, O: object, P: string | symbol | undefined): boolean; + OrdinaryHasOwnMetadata(MetadataKey: any, O: object, P: string | symbol | undefined): boolean; + OrdinaryGetOwnMetadata(MetadataKey: any, O: object, P: string | symbol | undefined): any; + OrdinaryOwnMetadataKeys(O: object, P: string | symbol | undefined): any[]; +} diff --git a/gulpfile.js b/gulpfile.js index efe3851..c102f7d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,7 +9,12 @@ const gls = require("gulp-live-server"); const debugProject = tsb.create("tsconfig.json"); const releaseProject = tsb.create("tsconfig-release.json"); -const tests = tsb.create("test/tsconfig.json"); +const tests = { + full: tsb.create("test/full/tsconfig.json"), + lite: tsb.create("test/lite/tsconfig.json"), + "no-conflict": tsb.create("test/no-conflict/tsconfig.json"), + registry: tsb.create("test/registry/tsconfig.json"), +}; let project = debugProject; @@ -30,10 +35,32 @@ gulp.task("build:reflect", () => gulp .pipe(project()) .pipe(gulp.dest("."))); -gulp.task("build:tests", ["build:reflect"], () => gulp - .src(["test/**/*.ts"]) - .pipe(tests()) - .pipe(gulp.dest("test"))); +gulp.task("build:tests:full", ["build:reflect"], () => gulp + .src(["test/full/**/*.ts"]) + .pipe(tests.full()) + .pipe(gulp.dest("test/full"))); + +gulp.task("build:tests:lite", ["build:reflect"], () => gulp + .src(["test/lite/**/*.ts"]) + .pipe(tests.lite()) + .pipe(gulp.dest("test/lite"))); + +gulp.task("build:tests:no-conflict", ["build:reflect"], () => gulp + .src(["test/no-conflict/**/*.ts"]) + .pipe(tests["no-conflict"]()) + .pipe(gulp.dest("test/no-conflict"))); + +gulp.task("build:tests:registry", ["build:reflect"], () => gulp + .src(["test/registry/**/*.ts"]) + .pipe(tests.registry()) + .pipe(gulp.dest("test/registry"))); + +gulp.task("build:tests", [ + "build:tests:full", + "build:tests:lite", + "build:tests:no-conflict", + "build:tests:registry" +]); gulp.task("build:spec", () => gulp .src(["spec.html"]) @@ -55,31 +82,37 @@ gulp.task("use-polyfill", () => { process.env["REFLECT_METADATA_USE_MAP_POLYFILL"] = "true"; }); -gulp.task("test:full", ["build:tests", "no-polyfill"], () => { +gulp.task("test:full", ["build:tests:full", "no-polyfill"], () => { console.log("Running tests w/o polyfill..."); return gulp .src(["test/full/**/*.js"], { read: false }) .pipe(mocha({ reporter: "dot" })); }); -gulp.task("test:lite", ["build:tests", "no-polyfill"], () => { +gulp.task("test:lite", ["build:tests:lite", "no-polyfill"], () => { console.log("Running lite-mode tests w/o polyfill..."); return gulp .src(["test/lite/**/*.js"], { read: false }) .pipe(mocha({ reporter: "dot" })); }); -gulp.task("test:no-conflict", ["build:tests", "no-polyfill"], () => { +gulp.task("test:no-conflict", ["build:tests:no-conflict", "no-polyfill"], () => { console.log("Running no-conflict-mode tests w/o polyfill..."); return gulp .src(["test/no-conflict/**/*.js"], { read: false }) .pipe(mocha({ reporter: "dot" })); }); -gulp.task("test:use-polyfill", ["build:tests", "use-polyfill"], () => { +gulp.task("test:registry", ["build:tests:registry", "no-polyfill"], () => { + console.log("Running registry..."); + return gulp + .src(["test/registry/**/*.js"], { read: false }) + .pipe(mocha({ reporter: "dot" })); +}); +gulp.task("test:use-polyfill", ["build:tests:full", "use-polyfill"], () => { console.log("Running tests w/ polyfill..."); return gulp .src(["test/full/**/*.js"], { read: false }) .pipe(mocha({ reporter: "dot" })); }); -gulp.task("test", sequence("test:full", "test:lite", "test:no-conflict", "test:use-polyfill")); +gulp.task("test", sequence("test:full", "test:lite", "test:no-conflict", "test:registry", "test:use-polyfill")); gulp.task("watch:reflect", () => gulp.watch([ diff --git a/test/tsconfig.json b/test/full/tsconfig.json similarity index 76% rename from test/tsconfig.json rename to test/full/tsconfig.json index 609ebe8..038cdd3 100644 --- a/test/tsconfig.json +++ b/test/full/tsconfig.json @@ -5,7 +5,7 @@ "sourceMap": true, "module": "commonjs", "types": ["node", "mocha"], - "typeRoots": ["../node_modules/@types"], + "typeRoots": ["../../node_modules/@types"], }, "include": [ "**/*.ts" diff --git a/test/lite/tsconfig.json b/test/lite/tsconfig.json new file mode 100644 index 0000000..038cdd3 --- /dev/null +++ b/test/lite/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es5", + "noImplicitAny": true, + "sourceMap": true, + "module": "commonjs", + "types": ["node", "mocha"], + "typeRoots": ["../../node_modules/@types"], + }, + "include": [ + "**/*.ts" + ] +} \ No newline at end of file diff --git a/test/no-conflict/tsconfig.json b/test/no-conflict/tsconfig.json new file mode 100644 index 0000000..038cdd3 --- /dev/null +++ b/test/no-conflict/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es5", + "noImplicitAny": true, + "sourceMap": true, + "module": "commonjs", + "types": ["node", "mocha"], + "typeRoots": ["../../node_modules/@types"], + }, + "include": [ + "**/*.ts" + ] +} \ No newline at end of file diff --git a/test/registry/registry.ts b/test/registry/registry.ts new file mode 100644 index 0000000..6a1d00a --- /dev/null +++ b/test/registry/registry.ts @@ -0,0 +1,31 @@ +/// +/// +const ReflectNoConflict = require("../../ReflectNoConflict"); +require("../../Reflect"); +import { assert } from "chai"; + +describe("MetadataRegistry", () => { + it("defines registry", () => { + const registrySymbol = Symbol.for("@reflect-metadata:registry"); + const registry = (Reflect as any)[registrySymbol] as MetadataRegistry; + assert.isDefined(registry); + }); + it("two registries", () => { + const registrySymbol = Symbol.for("@reflect-metadata:registry"); + const registry = (Reflect as any)[registrySymbol] as MetadataRegistry; + const obj1 = {}; + ReflectNoConflict.defineMetadata("key", "value", obj1); + const obj2 = {}; + Reflect.defineMetadata("key", "value", obj2); + const provider1 = registry.getProvider(obj1, undefined); + const provider2 = registry.getProvider(obj2, undefined); + assert.isDefined(provider1); + assert.isDefined(provider2); + assert.notStrictEqual(provider1, provider2); + }); + it("registries are shared", () => { + const obj = {}; + ReflectNoConflict.defineMetadata("key", "value", obj); + assert.isTrue(Reflect.hasOwnMetadata("key", obj)); + }); +}); diff --git a/test/registry/tsconfig.json b/test/registry/tsconfig.json new file mode 100644 index 0000000..038cdd3 --- /dev/null +++ b/test/registry/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es5", + "noImplicitAny": true, + "sourceMap": true, + "module": "commonjs", + "types": ["node", "mocha"], + "typeRoots": ["../../node_modules/@types"], + }, + "include": [ + "**/*.ts" + ] +} \ No newline at end of file