diff --git a/package-lock.json b/package-lock.json index 2985acd..20722a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "flat-diamond", - "version": "1.0.3", + "version": "1.0.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "flat-diamond", - "version": "1.0.3", - "license": "GPL-3.0-only", + "version": "1.0.7", + "license": "ISC", "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", diff --git a/package.json b/package.json index 826eb88..5201e38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flat-diamond", - "version": "1.0.7", + "version": "1.0.8", "types": "./lib/index.d.ts", "exports": { ".": { diff --git a/src/diamond.ts b/src/diamond.ts index 803d6d0..f550653 100644 --- a/src/diamond.ts +++ b/src/diamond.ts @@ -1,5 +1,15 @@ import { Ctor, HasBases, Newable } from './types' -import { allFLegs, bottomLeg, emptySecludedProxyHandler, fLegs, nextInFLeg } from './utils' +import { + allFLegs, + bottomLeg, + emptySecludedProxyHandler, + fLegs, + hasInstanceManager, + hasInstanceManagers, + linearLeg, + manageHasInstance, + nextInFLeg +} from './utils' type BuildingStrategy = Map let buildingDiamond: { @@ -7,12 +17,13 @@ let buildingDiamond: { strategy: BuildingStrategy } | null = null -export const diamondHandler: { +const diamondHandler: { getPrototypeOf(target: Ctor): Ctor get(target: Ctor, p: PropertyKey, receiver: Ctor): any set(target: Ctor, p: PropertyKey, v: any, receiver: Ctor): boolean } & ProxyHandler = { get(target, p, receiver) { + if (p === 'constructor') return Object const pd = nextInFLeg(receiver.constructor, p, target) return pd && ('value' in pd ? pd.value : 'get' in pd ? pd.get!.call(receiver) : undefined) }, @@ -34,16 +45,6 @@ export const diamondHandler: { } } -export function hasInstanceManager(cls: Class) { - return (obj: any) => { - if (!obj || typeof obj !== 'object') return false - const objBottom = bottomLeg(obj.constructor) - if (objBottom === cls) return true - const fLeg = allFLegs.get(objBottom) - return Boolean(fLeg && fLeg.some((base) => bottomLeg(base) === cls)) - } -} - export default function Diamond( ...baseClasses: TBases ): Newable> { @@ -73,6 +74,10 @@ export default function Diamond( const responsibility = buildingDiamond ? buildingDiamond!.strategy.get(this.constructor as Ctor)! : myResponsibility + if (!responsibility) { + throw new Error(`Inconsistent diamond hierarchy. +This might happen if a diamond is created from another constructor before its 'super(...)' is called.`) + } if (!buildingDiamond) buildingDiamond = { built: this, @@ -107,11 +112,15 @@ export default function Diamond( //In the constructor method and in the field initializers, we can build diamonds, but not *this* diamond buildingDiamond = null } + // Value used by `this` on `super(...)` return return locallyStoredDiamond.built } static [Symbol.hasInstance] = hasInstanceManager(Diamond) } + hasInstanceManagers.add(Diamond) allFLegs.set(Diamond, bases) + for (const base of baseClasses) + if (!fLegs(base)) for (const ctor of linearLeg(base)) if (!manageHasInstance(ctor)) break /** * Constructs the building strategy for building this class and only this class with its specific legacy */ diff --git a/src/seclude.ts b/src/seclude.ts index e4ab7a4..c7d46dc 100644 --- a/src/seclude.ts +++ b/src/seclude.ts @@ -1,4 +1,4 @@ -import Diamond, { diamondHandler, hasInstanceManager } from './diamond' +import Diamond from './diamond' import { Ctor, KeySet, Newable } from './types' import { allFLegs, @@ -59,13 +59,6 @@ export function Seclude(), diamondSecluded = !fLegs(base), diamond = diamondSecluded ? Diamond(PropertyCollector) : PropertyCollector - // We make sure `Secluded(X).secluded(x) instanceof X` - if (diamondSecluded) { - Object.defineProperty(base, Symbol.hasInstance, { - value: hasInstanceManager(base), - configurable: true - }) - } class GateKeeper extends diamond { static secluded(obj: TBase): TBase | undefined { return privates.get(obj) @@ -178,7 +171,7 @@ export function Seclude( } as ProxyHandler } export const emptySecludedProxyHandler = secludedProxyHandler(null, {}) + +export function hasInstanceManager( + cls: Class, + original?: (obj: any) => boolean +) { + function nativeLinear(ctor: Ctor) { + // linearLeg ignore last diamond (that we need) + for (; ctor !== Object; ctor = Object.getPrototypeOf(ctor.prototype).constructor) + if (ctor === cls) return true + return false + } + const inheritsFrom = original + ? (ctor: Ctor) => nativeLinear(ctor) || original(ctor.prototype) + : (ctor: Ctor) => nativeLinear(ctor) + return (obj: any) => { + if (!obj || typeof obj !== 'object') return false + if (inheritsFrom(obj.constructor)) return true + const fLeg = fLegs(obj.constructor) + return Boolean(fLeg && fLeg.some(inheritsFrom)) + } +} +export const hasInstanceManagers = new WeakSet() +export function manageHasInstance(ctor: Ctor) { + if (hasInstanceManagers.has(ctor)) return false + hasInstanceManagers.add(ctor) + Object.defineProperty(ctor, Symbol.hasInstance, { + value: hasInstanceManager( + ctor, + ctor.hasOwnProperty(Symbol.hasInstance) ? ctor[Symbol.hasInstance] : undefined + ), + configurable: true + }) + return true +} diff --git a/test/instanceOf.test.ts b/test/instanceOf.test.ts index fc2cb7a..8bd238a 100644 --- a/test/instanceOf.test.ts +++ b/test/instanceOf.test.ts @@ -1,4 +1,4 @@ -import D, { instanceOf } from '../src' +import D, { instanceOf, Seclude } from '../src' class A1 {} class A2 extends A1 {} class B1 {} @@ -13,34 +13,29 @@ class X2 extends D(C2, D2) {} const x1 = new X1(), x2 = new X2() -test('instanceOf', () => { - expect(instanceOf(x1, X1)).toBe(true) - expect(instanceOf(x1, X2)).toBe(false) - expect(instanceOf(x2, X1)).toBe(false) - expect(instanceOf(x2, X2)).toBe(true) - expect(instanceOf(x1, A1)).toBe(true) - expect(instanceOf(x1, B1)).toBe(true) - expect(instanceOf(x1, C1)).toBe(false) - - expect(instanceOf(x2, A1)).toBe(true) - expect(instanceOf(x2, B1)).toBe(true) - expect(instanceOf(x2, A2)).toBe(false) - expect(instanceOf(x2, B2)).toBe(false) - expect(instanceOf(x2, C1)).toBe(true) -}) test('instanceof', () => { + /* expect(x1 instanceof X1).toBe(true) expect(x1 instanceof X2).toBe(false) expect(x2 instanceof X1).toBe(false) expect(x2 instanceof X2).toBe(true) - expect(x1 instanceof A1).toBe(false) - expect(x1 instanceof B1).toBe(false) + expect(x1 instanceof A1).toBe(true) + expect(x1 instanceof B1).toBe(true) expect(x1 instanceof C1).toBe(false) - expect(x2 instanceof A1).toBe(false) - expect(x2 instanceof B1).toBe(false) + expect(x2 instanceof A1).toBe(true) + expect(x2 instanceof B1).toBe(true)*/ expect(x2 instanceof A2).toBe(false) expect(x2 instanceof B2).toBe(false) expect(x2 instanceof C1).toBe(true) }) + +test('secluded', () => { + class X {} + const S = Seclude(X) + const s = new S() + expect(s instanceof X).toBe(true) + expect(s instanceof S).toBe(true) + expect(S.secluded(s) instanceof X).toBe(true) +})