diff --git a/src/structy/export/BaseStruct.js b/src/structy/export/BaseStruct.js index b38f417b..8653f765 100644 --- a/src/structy/export/BaseStruct.js +++ b/src/structy/export/BaseStruct.js @@ -35,9 +35,10 @@ export class BaseStruct { /** * Checks to see if a property name is allowed. This is called by the base * class for any encountered property which doesn't have a corresponding - * `_prop_*()` method. If it returns `true`, then the property-value pair is - * passed to {@link #_impl_extraProperty} for further processing. The default - * (base class) implementation always returns `false`. + * `_prop_*()` method and which doesn't already exist in the class (shadowing + * a pre-existing property isn't allowed). If it returns `true`, then the + * property-value pair is passed to {@link #_impl_extraProperty} for further + * processing. The default (base class) implementation always returns `false`. * * @param {string} name Property name. * @returns {boolean} `true` if `name` is allowed on the instance, or `false` @@ -147,7 +148,9 @@ export class BaseStruct { const disallowed = []; for (const name of leftovers) { - if (this._impl_allowExtraProperty(name)) { + if (Reflect.has(this, name)) { + throw new Error(`Extra property would shadow pre-existing property: ${name}`); + } else if (this._impl_allowExtraProperty(name)) { const value = this._impl_extraProperty(name, rawObject[name]); if (value === undefined) { throw new Error(`Extra property checker \`_impl_extraProperty()\` did not return a value. Maybe missing a \`return\`?`); diff --git a/src/structy/tests/BaseStruct.test.js b/src/structy/tests/BaseStruct.test.js index dbbc06de..3ff64d6f 100644 --- a/src/structy/tests/BaseStruct.test.js +++ b/src/structy/tests/BaseStruct.test.js @@ -330,6 +330,10 @@ describe('using a subclass which allows extra properties', () => { class SomeStruct extends BaseStruct { // @defaultConstructor + get yesNo() { + return 'maybe'; + } + _impl_allowExtraProperty(name) { return name.startsWith('yes'); } @@ -370,5 +374,10 @@ describe('using a subclass which allows extra properties', () => { const arg = { yesUndefined: 123 }; expect(() => new SomeStruct(arg)).toThrow(/did not return/); }); + + test('throws given an extra property which would shadow a pre-existing property', () => { + const arg = { yesNo: 12345 }; + expect(() => new SomeStruct(arg)).toThrow(/would shadow/); + }); }); });