diff --git a/src/diamond.ts b/src/diamond.ts index 0652cf4..b131293 100644 --- a/src/diamond.ts +++ b/src/diamond.ts @@ -111,6 +111,7 @@ export default function Diamond( // Even if `Diamond` managed: property initializers do not go through proxy if (locallyStoredDiamond.built !== temp) { if (fLegs(subs)) + /* istanbul ignore next: internal bug guard */ throw new LateSuperError(`Inconsistent diamond hierarchy under [Diamond<${baseClasses.map((b) => b.name).join(',')}>], for ${this.constructor.name}, This happens if a diamond creates another instance of the same diamond in the constructor before calling \`super(...)\`.`) // import properties from temp object @@ -137,6 +138,7 @@ This happens if a diamond creates another instance of the same diamond in the co locallyStoredDiamond.built !== this && Object.getOwnPropertyNames(this).length + Object.getOwnPropertySymbols(this).length > 0 ) + /* istanbul ignore next: internal bug guard */ throw new Error('Temporary object must not have own properties or symbols') // @ts-expect-error `Symbol.toStringTag` diff --git a/src/seclude.ts b/src/seclude.ts index a17295f..ef37c3a 100644 --- a/src/seclude.ts +++ b/src/seclude.ts @@ -8,8 +8,6 @@ import { secludedPropertyDescriptor, secludedProxyHandler, } from './utils' - -const publicPart = (x: T): T => Object.getPrototypeOf(Object.getPrototypeOf(x)) /** * Internally used for communication between `PropertyCollector` and `Secluded` * This is the "baal" `Secluded` send into the "basket" (stack in case a secluded class inherits another secluded class) @@ -92,11 +90,7 @@ export function Seclude` } - const actor = whoAmI(receiver) - if (p in target.prototype && (!(p in secludedProperties) || actor.domain === 'private')) { + const secluded = assertSecluded(receiver) + if (p in target.prototype && !(p in secludedProperties)) { const pd = nextInLine(target, p)! if (!pd) return Reflect.get(target.prototype, p, receiver) - if (pd.get) return pd.get!.call(actor.private) + if (pd.get) return pd.get!.call(secluded) if ('value' in pd) { const rv = pd.value! return typeof rv === 'function' ? function (this: any, ...args: any) { - return rv.apply(actor.private, args) + return rv.apply(secluded, args) } : rv } // No legacy involved: it was well defined in our classes but `readable: false` ... return undefined } - if (p in secludedProperties && actor.domain === 'private') - // If we arrive here, it means it's private but not set in the private part - return undefined - if (allFLegs.has(actor.public)) return Reflect.get(bottomLeg(target), p, receiver) + // if `receiver` is a diamond, pass the hand to "fLeg" management (the diamond, `bottomLeg`) + if (fLegs(target)) return Reflect.get(bottomLeg(target), p, receiver) // If we arrive here, it means it's public but not set in the public part return undefined }, set(target, p, value, receiver) { - const actor = whoAmI(receiver) + const secluded = assertSecluded(receiver) if (p in target.prototype) { const pd = nextInLine(target, p)! if (pd.set) { - pd.set!.call(actor.private, value) + pd.set!.call(secluded, value) return true } if (!pd.writable) return false } - - if (p in secludedProperties && actor.domain === 'private') { - Object.defineProperty(receiver, p, { - value, - writable: true, - enumerable: true, - configurable: true, - }) - return true - } - if (allFLegs.has(actor.public)) return Reflect.set(bottomLeg(target), p, value, receiver) - Object.defineProperty(actor.public, p, { + if (fLegs(target)) return Reflect.set(bottomLeg(target), p, value, receiver) + Object.defineProperty(receiver, p, { value, writable: true, enumerable: true, @@ -217,7 +191,7 @@ export function Seclude diamond.prototype, + getPrototypeOf: () => diamond.prototype, }) Object.setPrototypeOf(GateKeeper.prototype, fakeCtor.prototype) return GateKeeperProxy as any diff --git a/test/edge-cases.test.ts b/test/edge-cases.test.ts index fdbd97d..01e5ae4 100644 --- a/test/edge-cases.test.ts +++ b/test/edge-cases.test.ts @@ -1,3 +1,4 @@ +import type { Ctor } from 'lib' import Diamond, { Seclude } from '../src' import { log, logs } from './logger' @@ -45,16 +46,22 @@ describe('before super', () => { }) describe('unplanned access', () => { - test('undeclared', () => { - class X {} + test.each([ + ['diamond', class X extends Diamond() {}], + ['simple', class X {}], + ])('undeclared on %s', (_: string, X: Ctor) => { const S = Seclude(X as any, ['undeclared']) as any const s = new S() expect(s.undeclared).toBeUndefined() + expect(s.undeclaredPublic).toBeUndefined() expect(S(s).undeclared).toBeUndefined() s.undeclared = 1 + s.undeclaredPublic = 3 S(s).undeclared = 2 expect(s.undeclared).toBe(1) + expect(s.undeclaredPublic).toBe(3) expect(S(s).undeclared).toBe(2) + expect(S(s).undeclaredPublic).toBe(3) }) test('X-only', () => { class WriteOnly { diff --git a/test/instanceOf.test.ts b/test/instanceOf.test.ts index 769f891..385889c 100644 --- a/test/instanceOf.test.ts +++ b/test/instanceOf.test.ts @@ -1,4 +1,6 @@ +import Diamond from '../src' import D, { Seclude } from '../src' +import { log, logs } from './logger' class A1 {} class A2 extends A1 {} class B1 {} @@ -40,3 +42,20 @@ test('secluded', () => { // Still wondering... Is `MountedPlane` (a `Plane` without `wingSpan`) a `Plane` ? expect(S(s) instanceof X).toBe(true) }) + +test('edge-cases', () => { + logs() + // biome-ignore lint/complexity/noStaticOnlyClass: + class X { + static [Symbol.hasInstance](o: any) { + // NB: 'o' here is the prototype of the given object to `instance of` + log('hasInstance') + return true + } + } + Diamond(X) + // @ts-expect-error + expect(null instanceof X).toBe(false) + expect({} instanceof X).toBe(true) + expect(logs()).toEqual(['hasInstance']) +})