From c2a75fc3cdb40a49f039fda1d8dfc4001c1f4d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Marie=20De=20Mey?= Date: Thu, 7 Nov 2024 14:23:42 +0200 Subject: [PATCH] more edge cases --- src/diamond.ts | 14 ++++++------ src/seclude.ts | 12 +++++------ test/abcd.test.ts | 9 ++++++++ test/edge-cases.test.ts | 48 +++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 6 +----- 5 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/diamond.ts b/src/diamond.ts index 4822134..0652cf4 100644 --- a/src/diamond.ts +++ b/src/diamond.ts @@ -18,10 +18,6 @@ type BuildingStrategy = { }[] let buildingDiamond: { built: object - // TODO: Not a map, should be a stack where each elements are popped one by one, we know the order - // It could allow 'Inconsistent diamond hierarchy' to become a warning? - // (the error would be checked when exiting the stack) - // Also, we might have undetected conflict (2 objects ending up in the same `this`) strategy: BuildingStrategy } | null = null @@ -35,7 +31,7 @@ const diamondHandler: { return pd ? 'value' in pd ? pd.value - : 'get' in pd + : pd.get ? pd.get!.call(receiver) : undefined : ({} as any)[p] @@ -126,7 +122,6 @@ This happens if a diamond creates another instance of the same diamond in the co // Useless in most cases, but if that object was given out as a reference, it can still // be interacted with for (const p of Object.getOwnPropertyNames(temp)) delete temp[p] - // TODO: test this fake "head" Object.setPrototypeOf( temp, new Proxy(locallyStoredDiamond.built, emptySecludedProxyHandler) @@ -138,7 +133,12 @@ This happens if a diamond creates another instance of the same diamond in the co buildingDiamond = bdRestore ?? null } lastDiamondProperties = Object.getOwnPropertyDescriptors(locallyStoredDiamond.built) - // Value used by `this` on `super(...)` return + if ( + locallyStoredDiamond.built !== this && + Object.getOwnPropertyNames(this).length + Object.getOwnPropertySymbols(this).length > 0 + ) + throw new Error('Temporary object must not have own properties or symbols') + // @ts-expect-error `Symbol.toStringTag` // biome-ignore lint/correctness/noConstructorReturn: This is the whole purpose of this library return locallyStoredDiamond.built diff --git a/src/seclude.ts b/src/seclude.ts index ca009cb..a17295f 100644 --- a/src/seclude.ts +++ b/src/seclude.ts @@ -146,11 +146,11 @@ export function Seclude { + get(target, p, receiver) { switch (p) { case 'constructor': return fakeCtor @@ -169,7 +169,7 @@ export function Seclude { + set(target, p, value, receiver) { const actor = whoAmI(receiver) if (p in target.prototype) { const pd = nextInLine(target, p)! - if ('set' in pd) { + if (pd.set) { pd.set!.call(actor.private, value) return true } diff --git a/test/abcd.test.ts b/test/abcd.test.ts index 88e0bdc..e0c22a1 100644 --- a/test/abcd.test.ts +++ b/test/abcd.test.ts @@ -63,6 +63,7 @@ beforeEach(() => { test('call orders', () => { const obj = new D('o') expect(builtA instanceof A).toBe(true) + expect(builtA === obj).toBe(false) expect(logs()).toEqual([ '[class=A] construct A', '[class=D] construct B', @@ -78,4 +79,12 @@ test('call orders', () => { '[class=D] func B', '[class=D] func A', ]) + expect(builtA?.func(0)).toBe(6) + expect(builtA?.arg).toBe('oDCB') + expect(logs()).toEqual([ + '[class=D] func D', + '[class=D] func C', + '[class=D] func B', + '[class=D] func A', + ]) }) diff --git a/test/edge-cases.test.ts b/test/edge-cases.test.ts index 2da5168..fdbd97d 100644 --- a/test/edge-cases.test.ts +++ b/test/edge-cases.test.ts @@ -1,4 +1,5 @@ import Diamond, { Seclude } from '../src' +import { log, logs } from './logger' test('toStringTags', () => { class X {} @@ -42,3 +43,50 @@ describe('before super', () => { expect(() => new C()).toThrow() }) }) + +describe('unplanned access', () => { + test('undeclared', () => { + class X {} + const S = Seclude(X as any, ['undeclared']) as any + const s = new S() + expect(s.undeclared).toBeUndefined() + expect(S(s).undeclared).toBeUndefined() + s.undeclared = 1 + S(s).undeclared = 2 + expect(s.undeclared).toBe(1) + expect(S(s).undeclared).toBe(2) + }) + test('X-only', () => { + class WriteOnly { + set value(v: number) { + log('set', v) + } + } + class ReadOnly { + get value() { + return 0 + } + } + let x: any + logs() + x = new (Seclude(WriteOnly))() + expect(x.value).toBeUndefined() + x.value = 5 + expect(logs()).toEqual(['set 5']) + x = new (Seclude(ReadOnly))() + expect(x.value).toBe(0) + expect(() => { + x.value = 5 + }).toThrow() + logs() + x = new (Diamond(WriteOnly))() + expect(x.value).toBeUndefined() + x.value = 5 + expect(logs()).toEqual(['set 5']) + x = new (Diamond(ReadOnly))() + expect(x.value).toBe(0) + expect(() => { + x.value = 5 + }).toThrow() + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 6746a52..e487b94 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,10 +10,6 @@ "forceConsistentCasingInFileNames": true, "strictNullChecks": true, "strictBindCallApply": false, - "baseUrl": ".", - "paths": { - "tslib": ["./node_modules/tslib/tslib.d.ts"], - "~/*": ["./src/*"] - } + "baseUrl": "." } }