From 17caeb6b450163ebfc97173b117cf06ce2ec8ca7 Mon Sep 17 00:00:00 2001 From: Steven Sojka Date: Sat, 9 Jun 2018 08:24:12 -0500 Subject: [PATCH] fix(decorators): don't apply instance decorators when accessing from a prototype --- .vscode/settings.json | 5 ++++- src/bind.spec.ts | 19 +++++++++++++++++++ src/factory/DecoratorFactory.ts | 14 +++++++++++++- src/utils.ts | 1 + src/utils/isPrototypeAccess.ts | 5 +++++ 5 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 src/utils/isPrototypeAccess.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 3662b37..cb3f663 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "cSpell.words": [ + "applicate" + ] } \ No newline at end of file diff --git a/src/bind.spec.ts b/src/bind.spec.ts index a9e6fe6..ca004b5 100644 --- a/src/bind.spec.ts +++ b/src/bind.spec.ts @@ -60,4 +60,23 @@ describe('bind', () => { myClass2.fn.call(null); expect(context).to.equal(myClass2); }); + + it('should not bind when accessed on the prototype', () => { + let context; + + class MyClass { + @Bind + fn() { + context = this; + } + } + + MyClass.prototype.fn(); + + const myClass = new MyClass(); + + myClass.fn.call(null); + + expect(context).to.equal(myClass); + }); }); diff --git a/src/factory/DecoratorFactory.ts b/src/factory/DecoratorFactory.ts index 20791cb..3bacb5b 100644 --- a/src/factory/DecoratorFactory.ts +++ b/src/factory/DecoratorFactory.ts @@ -6,7 +6,12 @@ import { InstanceChainContext } from './common'; import { DecoratorConfig } from './DecoratorConfig'; -import { copyMetadata, bind, isMethodOrPropertyDecoratorArgs } from '../utils'; +import { + copyMetadata, + bind, + isMethodOrPropertyDecoratorArgs, + isPrototypeAccess +} from '../utils'; export type GenericDecorator = (...args: any[]) => LodashDecorator; @@ -60,6 +65,7 @@ export class InternalDecoratorFactory { const isSetter = isFirstInstance && isFunction(set); const isMethod = isFirstInstance && isFunction(value); const isProperty = isFirstInstance && !isGetter && !isSetter && !isMethod; + const baseValue = isGetter ? get : isMethod ? value : undefined; chainData.properties.push(name); chainData.fns.push((fn: Function, instance: any, context: InstanceChainContext) => { @@ -132,6 +138,12 @@ export class InternalDecoratorFactory { } descriptor.get = function() { + // Check for direct access on the prototype. + // MyClass.prototype.fn <-- This should not apply the decorator. + if (isPrototypeAccess(this, target)) { + return baseValue; + } + applyDecorator(this); const descriptor = Object.getOwnPropertyDescriptor(this, name)!; diff --git a/src/utils.ts b/src/utils.ts index e82baba..39563d6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,3 +7,4 @@ export * from './utils/bind'; export * from './utils/wrapConstructor'; export * from './utils/assignAll'; export * from './utils/isDecoratorArgs'; +export * from './utils/isPrototypeAccess'; diff --git a/src/utils/isPrototypeAccess.ts b/src/utils/isPrototypeAccess.ts new file mode 100644 index 0000000..9c61281 --- /dev/null +++ b/src/utils/isPrototypeAccess.ts @@ -0,0 +1,5 @@ +export function isPrototypeAccess(context: object, target: object): boolean { + return context === target + || (context.constructor !== target.constructor + && Object.getPrototypeOf(this).constructor === target.constructor); +}