From 2073070bec3d1e13641145c374ed2ef4ee3949cc Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sat, 8 May 2021 15:18:07 +0100 Subject: [PATCH 01/30] refactor to helper createTaggedDecorator which supports all decorators on setters --- src/annotation/decorator_utils.ts | 18 +++++++++++++++++- src/annotation/inject.ts | 15 +++------------ src/annotation/multi_inject.ts | 14 ++------------ src/annotation/named.ts | 11 ++--------- src/annotation/optional.ts | 14 ++------------ src/annotation/tagged.ts | 11 ++--------- 6 files changed, 28 insertions(+), 55 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index eda3573e7..7a915c23a 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -1,6 +1,7 @@ import * as ERROR_MSGS from "../constants/error_msgs"; import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; +import { Metadata } from "../planning/metadata"; function tagParameter( annotationTarget: any, @@ -63,6 +64,21 @@ function _tagParameterOrProperty( } +function createTaggedDecorator( + metadata:Metadata, + callback?:(target: any, targetKey: string, index?: number | PropertyDescriptor) => void) { + return function(target: any, targetKey: string, index?: number | PropertyDescriptor) { + if(callback){ + callback(target, targetKey, index); + } + if (typeof index === "number") { + tagParameter(target, targetKey, index, metadata); + } else { + tagProperty(target, targetKey, metadata); + } + }; +} + function _decorate(decorators: any[], target: any): void { Reflect.decorate(decorators, target); } @@ -90,4 +106,4 @@ function decorate( } } -export { decorate, tagParameter, tagProperty }; +export { decorate, tagParameter, tagProperty, createTaggedDecorator }; diff --git a/src/annotation/inject.ts b/src/annotation/inject.ts index 9e67fa30a..0f6a36b5e 100644 --- a/src/annotation/inject.ts +++ b/src/annotation/inject.ts @@ -2,7 +2,7 @@ import { UNDEFINED_INJECT_ANNOTATION } from "../constants/error_msgs"; import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; import { Metadata } from "../planning/metadata"; -import { tagParameter, tagProperty } from "./decorator_utils"; +import { createTaggedDecorator } from "./decorator_utils"; export type ServiceIdentifierOrFunc = interfaces.ServiceIdentifier | LazyServiceIdentifer; @@ -18,20 +18,11 @@ export class LazyServiceIdentifer { } function inject(serviceIdentifier: ServiceIdentifierOrFunc) { - return function(target: any, targetKey: string, index?: number | PropertyDescriptor): void { + return createTaggedDecorator(new Metadata(METADATA_KEY.INJECT_TAG, serviceIdentifier),target => { if (serviceIdentifier === undefined) { throw new Error(UNDEFINED_INJECT_ANNOTATION(target.name)); } - - const metadata = new Metadata(METADATA_KEY.INJECT_TAG, serviceIdentifier); - - if (typeof index === "number") { - tagParameter(target, targetKey, index, metadata); - } else { - tagProperty(target, targetKey, metadata); - } - - }; + }) } export { inject }; diff --git a/src/annotation/multi_inject.ts b/src/annotation/multi_inject.ts index c479e28c8..016b96966 100644 --- a/src/annotation/multi_inject.ts +++ b/src/annotation/multi_inject.ts @@ -1,20 +1,10 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; import { Metadata } from "../planning/metadata"; -import { tagParameter, tagProperty } from "./decorator_utils"; +import { createTaggedDecorator } from "./decorator_utils"; function multiInject(serviceIdentifier: interfaces.ServiceIdentifier) { - return function(target: any, targetKey: string, index?: number) { - - const metadata = new Metadata(METADATA_KEY.MULTI_INJECT_TAG, serviceIdentifier); - - if (typeof index === "number") { - tagParameter(target, targetKey, index, metadata); - } else { - tagProperty(target, targetKey, metadata); - } - - }; + return createTaggedDecorator(new Metadata(METADATA_KEY.MULTI_INJECT_TAG, serviceIdentifier)); } export { multiInject }; diff --git a/src/annotation/named.ts b/src/annotation/named.ts index 004776efe..87d5e2d1d 100644 --- a/src/annotation/named.ts +++ b/src/annotation/named.ts @@ -1,17 +1,10 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { Metadata } from "../planning/metadata"; -import { tagParameter, tagProperty } from "./decorator_utils"; +import { createTaggedDecorator/* , tagParameter, tagProperty */ } from "./decorator_utils"; // Used to add named metadata which is used to resolve name-based contextual bindings. function named(name: string | number | symbol) { - return function(target: any, targetKey: string, index?: number) { - const metadata = new Metadata(METADATA_KEY.NAMED_TAG, name); - if (typeof index === "number") { - tagParameter(target, targetKey, index, metadata); - } else { - tagProperty(target, targetKey, metadata); - } - }; + return createTaggedDecorator(new Metadata(METADATA_KEY.NAMED_TAG, name)); } export { named }; diff --git a/src/annotation/optional.ts b/src/annotation/optional.ts index 25a766a89..31de6d5ef 100644 --- a/src/annotation/optional.ts +++ b/src/annotation/optional.ts @@ -1,19 +1,9 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { Metadata } from "../planning/metadata"; -import { tagParameter, tagProperty } from "./decorator_utils"; +import { createTaggedDecorator } from "./decorator_utils"; function optional() { - return function(target: any, targetKey: string, index?: number) { - - const metadata = new Metadata(METADATA_KEY.OPTIONAL_TAG, true); - - if (typeof index === "number") { - tagParameter(target, targetKey, index, metadata); - } else { - tagProperty(target, targetKey, metadata); - } - - }; + return createTaggedDecorator(new Metadata(METADATA_KEY.OPTIONAL_TAG, true)); } export { optional }; diff --git a/src/annotation/tagged.ts b/src/annotation/tagged.ts index e5055e731..c5a8786ff 100644 --- a/src/annotation/tagged.ts +++ b/src/annotation/tagged.ts @@ -1,16 +1,9 @@ import { Metadata } from "../planning/metadata"; -import { tagParameter, tagProperty } from "./decorator_utils"; +import { createTaggedDecorator } from "./decorator_utils"; // Used to add custom metadata which is used to resolve metadata-based contextual bindings. function tagged(metadataKey: string | number | symbol, metadataValue: any) { - return function(target: any, targetKey: string, index?: number) { - const metadata = new Metadata(metadataKey, metadataValue); - if (typeof index === "number") { - tagParameter(target, targetKey, index, metadata); - } else { - tagProperty(target, targetKey, metadata); - } - }; + return createTaggedDecorator(new Metadata(metadataKey, metadataValue)); } export { tagged }; From 035d68caf010fc4b2467accba555750ceb973db7 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sat, 8 May 2021 17:14:01 +0100 Subject: [PATCH 02/30] make createTaggedDecorator permit multiple metadata --- src/annotation/decorator_utils.ts | 27 +++++--- src/interfaces/interfaces.ts | 2 + src/inversify.ts | 1 + src/utils/js.ts | 11 ++++ test/annotation/decorator_utils.test.ts | 87 +++++++++++++++++++++++++ 5 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 src/utils/js.ts create mode 100644 test/annotation/decorator_utils.test.ts diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 7a915c23a..4970b1e19 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -1,13 +1,13 @@ import * as ERROR_MSGS from "../constants/error_msgs"; import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; -import { Metadata } from "../planning/metadata"; +import { getArrayDuplicate } from "../utils/js"; function tagParameter( annotationTarget: any, propertyName: string, parameterIndex: number, - metadata: interfaces.Metadata + metadata: interfaces.MetadataOrMetadataArray ) { const metadataKey = METADATA_KEY.TAGGED; _tagParameterOrProperty(metadataKey, annotationTarget, propertyName, metadata, parameterIndex); @@ -16,7 +16,7 @@ function tagParameter( function tagProperty( annotationTarget: any, propertyName: string, - metadata: interfaces.Metadata + metadata: interfaces.MetadataOrMetadataArray ) { const metadataKey = METADATA_KEY.TAGGED_PROP; _tagParameterOrProperty(metadataKey, annotationTarget.constructor, propertyName, metadata); @@ -26,9 +26,19 @@ function _tagParameterOrProperty( metadataKey: string, annotationTarget: any, propertyName: string, - metadata: interfaces.Metadata, + metadata: interfaces.MetadataOrMetadataArray, parameterIndex?: number ) { + let metadatas: interfaces.Metadata[] = []; + if(Array.isArray(metadata)){ + metadatas = metadata; + const duplicate = getArrayDuplicate(metadatas.map(md => md.key)); + if(duplicate !== undefined) { + throw new Error(`${ERROR_MSGS.DUPLICATED_METADATA} ${duplicate.toString()}`); + } + }else{ + metadatas = [metadata]; + } let paramsOrPropertiesMetadata: interfaces.ReflectResult = {}; const isParameterDecorator = (typeof parameterIndex === "number"); @@ -51,22 +61,23 @@ function _tagParameterOrProperty( paramOrPropertyMetadata = []; } else { for (const m of paramOrPropertyMetadata) { - if (m.key === metadata.key) { + if (metadatas.some(md => md.key === m.key)) { throw new Error(`${ERROR_MSGS.DUPLICATED_METADATA} ${m.key.toString()}`); } } } // set metadata - paramOrPropertyMetadata.push(metadata); + paramOrPropertyMetadata.push(...metadatas); paramsOrPropertiesMetadata[key] = paramOrPropertyMetadata; Reflect.defineMetadata(metadataKey, paramsOrPropertiesMetadata, annotationTarget); } function createTaggedDecorator( - metadata:Metadata, - callback?:(target: any, targetKey: string, index?: number | PropertyDescriptor) => void) { + metadata:interfaces.MetadataOrMetadataArray, + callback?:(target: any, targetKey: string, index?: number | PropertyDescriptor) => void +) { return function(target: any, targetKey: string, index?: number | PropertyDescriptor) { if(callback){ callback(target, targetKey, index); diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index 5a92dada7..0a35b466f 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -115,6 +115,8 @@ namespace interfaces { [key: string]: Metadata[]; } + export type MetadataOrMetadataArray = Metadata | Metadata[]; + export interface Metadata { key: string | number | symbol; value: any; diff --git a/src/inversify.ts b/src/inversify.ts index b23403e52..831ea2aab 100644 --- a/src/inversify.ts +++ b/src/inversify.ts @@ -3,6 +3,7 @@ export const METADATA_KEY = keys; export { Container } from "./container/container"; export { BindingScopeEnum, BindingTypeEnum, TargetTypeEnum } from "./constants/literal_types"; export { AsyncContainerModule, ContainerModule } from "./container/container_module"; +export { createTaggedDecorator } from "./annotation/decorator_utils" export { injectable } from "./annotation/injectable"; export { tagged } from "./annotation/tagged"; export { named } from "./annotation/named"; diff --git a/src/utils/js.ts b/src/utils/js.ts new file mode 100644 index 000000000..dc54512e6 --- /dev/null +++ b/src/utils/js.ts @@ -0,0 +1,11 @@ +export function getArrayDuplicate(array:T[]):T | undefined { + const seenValues: any= {} + + for (const entry of array) { + if (seenValues[entry]) { + return entry; + } else { + seenValues[entry] = true + } + } +} \ No newline at end of file diff --git a/test/annotation/decorator_utils.test.ts b/test/annotation/decorator_utils.test.ts new file mode 100644 index 000000000..44a64501f --- /dev/null +++ b/test/annotation/decorator_utils.test.ts @@ -0,0 +1,87 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; +import { createTaggedDecorator, tagParameter, tagProperty } from "../../src/annotation/decorator_utils" +import * as ERROR_MSGS from "../../src/constants/error_msgs"; +import { Container, inject, injectable } from "../../src/inversify"; +describe("createTaggedDecorator", () => { + it("should call the callback when decorated", () => { + class Target {} + const callback = sinon.spy(); + const decorator = createTaggedDecorator({key:"1",value:"2"},callback); + try { + decorator(Target, "key", 1); + }catch(e){ + // + } + expect(callback.calledWithExactly(Target.prototype, "key",1)); + }); + + it("should pass to tagParameter for parameter decorators", () => { + class Target {} + const metadata = {key:"1",value:"2"}; + const decorator = createTaggedDecorator(metadata); + const spiedTagParameter = sinon.spy(tagParameter); + decorator(Target,undefined as any,1); + expect(spiedTagParameter.calledWithExactly(Target, undefined as any, 1, metadata)); + }); + + it("should pass to tagProperty for property decorators", () => { + class Target {} + const metadata = {key:"2",value:"2"}; + const decorator = createTaggedDecorator(metadata); + const spiedTagProperty = sinon.spy(tagProperty); + decorator(Target,"PropertyName"); + expect(spiedTagProperty.calledWithExactly(Target, "PropertyName", metadata)); + }); + + it("should enable constraining to multiple metadata with a single decorator", () => { + function multipleMetadataDecorator(key1Value:string, key2Value: string) { + return createTaggedDecorator([{key:"key1",value:key1Value},{key:"key2",value:key2Value}]); + } + interface Thing{ + type:string + } + + @injectable() + class Thing1 implements Thing{ + type = "Thing1" + } + + @injectable() + class Root { + public thingyType:string; + @multipleMetadataDecorator("Key1Value","Key2Value") + @inject("Thing") + set thingy(thingy:Thing) { + this.thingyType = thingy.type + } + } + + const container = new Container(); + container.bind("Thing").to(Thing1).when(request => { + const metadatas = request.target.metadata; + const key1Metadata = metadatas[1]; + const key2Metadata = metadatas[2]; + return key1Metadata.value === "Key1Value" && key2Metadata.value === "Key2Value"; + }); + container.resolve(Root); + }) +}); + +describe("tagParameter", () => { + it("should throw if multiple metadata with same key", () => { + class Target {} + expect( + () => tagParameter(Target,undefined as any, 1, [{key:"Duplicate",value:"1"},{key:"Duplicate",value:"2"}]) + ).to.throw(`${ERROR_MSGS.DUPLICATED_METADATA} Duplicate`); + }); +}); + +describe("tagProperty", () => { + it("should throw if multiple metadata with same key", () => { + class Target {} + expect( + () => tagProperty(Target,"Property", [{key:"Duplicate",value:"1"},{key:"Duplicate",value:"2"}]) + ).to.throw(`${ERROR_MSGS.DUPLICATED_METADATA} Duplicate`); + }); +}); From 2e1c863e015ee4a4837155a9bb3d144fe049e880 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sat, 8 May 2021 17:17:34 +0100 Subject: [PATCH 03/30] refactor tagParameter and tagProperty --- src/annotation/decorator_utils.ts | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 4970b1e19..6f7e20c3c 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -9,8 +9,10 @@ function tagParameter( parameterIndex: number, metadata: interfaces.MetadataOrMetadataArray ) { - const metadataKey = METADATA_KEY.TAGGED; - _tagParameterOrProperty(metadataKey, annotationTarget, propertyName, metadata, parameterIndex); + if(propertyName !== undefined) { + throw new Error(ERROR_MSGS.INVALID_DECORATOR_OPERATION); + } + _tagParameterOrProperty(METADATA_KEY.TAGGED, annotationTarget, parameterIndex.toString(), metadata); } function tagProperty( @@ -18,16 +20,14 @@ function tagProperty( propertyName: string, metadata: interfaces.MetadataOrMetadataArray ) { - const metadataKey = METADATA_KEY.TAGGED_PROP; - _tagParameterOrProperty(metadataKey, annotationTarget.constructor, propertyName, metadata); + _tagParameterOrProperty(METADATA_KEY.TAGGED_PROP, annotationTarget.constructor, propertyName, metadata); } function _tagParameterOrProperty( metadataKey: string, annotationTarget: any, - propertyName: string, + key: string, metadata: interfaces.MetadataOrMetadataArray, - parameterIndex?: number ) { let metadatas: interfaces.Metadata[] = []; if(Array.isArray(metadata)){ @@ -41,23 +41,15 @@ function _tagParameterOrProperty( } let paramsOrPropertiesMetadata: interfaces.ReflectResult = {}; - const isParameterDecorator = (typeof parameterIndex === "number"); - const key: string = (parameterIndex !== undefined && isParameterDecorator) ? parameterIndex.toString() : propertyName; - - // if the decorator is used as a parameter decorator, the property name must be provided - if (isParameterDecorator && propertyName !== undefined) { - throw new Error(ERROR_MSGS.INVALID_DECORATOR_OPERATION); - } // read metadata if available if (Reflect.hasOwnMetadata(metadataKey, annotationTarget)) { paramsOrPropertiesMetadata = Reflect.getMetadata(metadataKey, annotationTarget); } - // get metadata for the decorated parameter by its index - let paramOrPropertyMetadata: interfaces.Metadata[] = paramsOrPropertiesMetadata[key]; + let paramOrPropertyMetadata: interfaces.Metadata[] | undefined = paramsOrPropertiesMetadata[key]; - if (!Array.isArray(paramOrPropertyMetadata)) { + if (paramOrPropertyMetadata === undefined) { paramOrPropertyMetadata = []; } else { for (const m of paramOrPropertyMetadata) { From dd92dc770820e69902d0fe6fff3543e6de7aeccf Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sat, 8 May 2021 18:42:11 +0100 Subject: [PATCH 04/30] extract _ensureNoMetadataKeyDuplicates --- src/annotation/decorator_utils.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 6f7e20c3c..6f0cd42cf 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -23,12 +23,7 @@ function tagProperty( _tagParameterOrProperty(METADATA_KEY.TAGGED_PROP, annotationTarget.constructor, propertyName, metadata); } -function _tagParameterOrProperty( - metadataKey: string, - annotationTarget: any, - key: string, - metadata: interfaces.MetadataOrMetadataArray, -) { +function _ensureNoMetadataKeyDuplicates(metadata: interfaces.MetadataOrMetadataArray):interfaces.Metadata[]{ let metadatas: interfaces.Metadata[] = []; if(Array.isArray(metadata)){ metadatas = metadata; @@ -39,6 +34,17 @@ function _tagParameterOrProperty( }else{ metadatas = [metadata]; } + return metadatas; +} + + +function _tagParameterOrProperty( + metadataKey: string, + annotationTarget: any, + key: string, + metadata: interfaces.MetadataOrMetadataArray, +) { + const metadatas: interfaces.Metadata[] = _ensureNoMetadataKeyDuplicates(metadata); let paramsOrPropertiesMetadata: interfaces.ReflectResult = {}; From 08e46e613e01eaac7cd4d0e4b78a069d576e8ab7 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sat, 8 May 2021 18:47:04 +0100 Subject: [PATCH 05/30] change parameter name to indexOrPropertyDescriptor --- src/annotation/decorator_utils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 6f0cd42cf..91aad5990 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -74,14 +74,14 @@ function _tagParameterOrProperty( function createTaggedDecorator( metadata:interfaces.MetadataOrMetadataArray, - callback?:(target: any, targetKey: string, index?: number | PropertyDescriptor) => void + callback?:(target: any, targetKey: string, indexOrPropertyDescriptor?: number | PropertyDescriptor) => void ) { - return function(target: any, targetKey: string, index?: number | PropertyDescriptor) { + return function(target: any, targetKey: string, indexOrPropertyDescriptor?: number | PropertyDescriptor) { if(callback){ - callback(target, targetKey, index); + callback(target, targetKey, indexOrPropertyDescriptor); } - if (typeof index === "number") { - tagParameter(target, targetKey, index, metadata); + if (typeof indexOrPropertyDescriptor === "number") { + tagParameter(target, targetKey, indexOrPropertyDescriptor, metadata); } else { tagProperty(target, targetKey, metadata); } From ff16c9ac97b867bd49c4bf3bfcb6c192494c42da Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sat, 8 May 2021 18:49:32 +0100 Subject: [PATCH 06/30] grammar correct historic test names --- test/annotation/inject.test.ts | 2 +- test/inversify.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/annotation/inject.test.ts b/test/annotation/inject.test.ts index 2f1f4f304..e19f73aaa 100644 --- a/test/annotation/inject.test.ts +++ b/test/annotation/inject.test.ts @@ -113,7 +113,7 @@ describe("@inject", () => { }); - it("Should throw when not applayed to a constructor", () => { + it("Should throw when not applied to a constructor", () => { const useDecoratorOnMethodThatIsNotAConstructor = function() { __decorate([ __param(0, inject("Katana")) ], diff --git a/test/inversify.test.ts b/test/inversify.test.ts index c28fdb891..a9d7fefc5 100644 --- a/test/inversify.test.ts +++ b/test/inversify.test.ts @@ -72,7 +72,7 @@ describe("InversifyJS", () => { }); - it("Should be able to to do setter injection and property injection", () => { + it("Should be able to do setter injection and property injection", () => { @injectable() class Shuriken { public throw() { From 17a0fa7042e2b1b45c6f4e47a10d94559a113050 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sat, 8 May 2021 19:06:21 +0100 Subject: [PATCH 07/30] use sinon sandbox for spying --- test/annotation/decorator_utils.test.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/annotation/decorator_utils.test.ts b/test/annotation/decorator_utils.test.ts index 44a64501f..67fd2304f 100644 --- a/test/annotation/decorator_utils.test.ts +++ b/test/annotation/decorator_utils.test.ts @@ -4,6 +4,15 @@ import { createTaggedDecorator, tagParameter, tagProperty } from "../../src/anno import * as ERROR_MSGS from "../../src/constants/error_msgs"; import { Container, inject, injectable } from "../../src/inversify"; describe("createTaggedDecorator", () => { + let sandbox:sinon.SinonSandbox + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + it("should call the callback when decorated", () => { class Target {} const callback = sinon.spy(); @@ -20,7 +29,7 @@ describe("createTaggedDecorator", () => { class Target {} const metadata = {key:"1",value:"2"}; const decorator = createTaggedDecorator(metadata); - const spiedTagParameter = sinon.spy(tagParameter); + const spiedTagParameter = sandbox.spy(tagParameter); decorator(Target,undefined as any,1); expect(spiedTagParameter.calledWithExactly(Target, undefined as any, 1, metadata)); }); @@ -29,7 +38,7 @@ describe("createTaggedDecorator", () => { class Target {} const metadata = {key:"2",value:"2"}; const decorator = createTaggedDecorator(metadata); - const spiedTagProperty = sinon.spy(tagProperty); + const spiedTagProperty = sandbox.spy(tagProperty); decorator(Target,"PropertyName"); expect(spiedTagProperty.calledWithExactly(Target, "PropertyName", metadata)); }); From 3fcb0655b0ac94bab2fde71759e52ab0583cdd5e Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sun, 9 May 2021 09:29:12 +0100 Subject: [PATCH 08/30] review --- src/annotation/decorator_utils.ts | 12 ++++++++---- src/annotation/inject.ts | 4 ++-- src/annotation/multi_inject.ts | 4 ++-- src/annotation/named.ts | 4 ++-- src/annotation/optional.ts | 4 ++-- src/annotation/tagged.ts | 4 ++-- src/utils/js.ts | 10 +++++----- test/annotation/decorator_utils.test.ts | 20 ++++++++++++++------ 8 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 91aad5990..d2e869a74 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -1,7 +1,7 @@ import * as ERROR_MSGS from "../constants/error_msgs"; import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; -import { getArrayDuplicate } from "../utils/js"; +import { getFirstArrayDuplicate } from "../utils/js"; function tagParameter( annotationTarget: any, @@ -27,7 +27,7 @@ function _ensureNoMetadataKeyDuplicates(metadata: interfaces.MetadataOrMetadataA let metadatas: interfaces.Metadata[] = []; if(Array.isArray(metadata)){ metadatas = metadata; - const duplicate = getArrayDuplicate(metadatas.map(md => md.key)); + const duplicate = getFirstArrayDuplicate(metadatas.map(md => md.key)); if(duplicate !== undefined) { throw new Error(`${ERROR_MSGS.DUPLICATED_METADATA} ${duplicate.toString()}`); } @@ -72,7 +72,7 @@ function _tagParameterOrProperty( } -function createTaggedDecorator( +function createTaggedDecoratorInternal( metadata:interfaces.MetadataOrMetadataArray, callback?:(target: any, targetKey: string, indexOrPropertyDescriptor?: number | PropertyDescriptor) => void ) { @@ -88,6 +88,10 @@ function createTaggedDecorator( }; } +function createTaggedDecorator(metadata:interfaces.MetadataOrMetadataArray) { + return createTaggedDecoratorInternal(metadata); +} + function _decorate(decorators: any[], target: any): void { Reflect.decorate(decorators, target); } @@ -115,4 +119,4 @@ function decorate( } } -export { decorate, tagParameter, tagProperty, createTaggedDecorator }; +export { decorate, tagParameter, tagProperty, createTaggedDecoratorInternal, createTaggedDecorator }; diff --git a/src/annotation/inject.ts b/src/annotation/inject.ts index 0f6a36b5e..a35a1b76b 100644 --- a/src/annotation/inject.ts +++ b/src/annotation/inject.ts @@ -2,7 +2,7 @@ import { UNDEFINED_INJECT_ANNOTATION } from "../constants/error_msgs"; import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; import { Metadata } from "../planning/metadata"; -import { createTaggedDecorator } from "./decorator_utils"; +import { createTaggedDecoratorInternal } from "./decorator_utils"; export type ServiceIdentifierOrFunc = interfaces.ServiceIdentifier | LazyServiceIdentifer; @@ -18,7 +18,7 @@ export class LazyServiceIdentifer { } function inject(serviceIdentifier: ServiceIdentifierOrFunc) { - return createTaggedDecorator(new Metadata(METADATA_KEY.INJECT_TAG, serviceIdentifier),target => { + return createTaggedDecoratorInternal(new Metadata(METADATA_KEY.INJECT_TAG, serviceIdentifier),target => { if (serviceIdentifier === undefined) { throw new Error(UNDEFINED_INJECT_ANNOTATION(target.name)); } diff --git a/src/annotation/multi_inject.ts b/src/annotation/multi_inject.ts index 016b96966..3a45c319b 100644 --- a/src/annotation/multi_inject.ts +++ b/src/annotation/multi_inject.ts @@ -1,10 +1,10 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; import { Metadata } from "../planning/metadata"; -import { createTaggedDecorator } from "./decorator_utils"; +import { createTaggedDecoratorInternal } from "./decorator_utils"; function multiInject(serviceIdentifier: interfaces.ServiceIdentifier) { - return createTaggedDecorator(new Metadata(METADATA_KEY.MULTI_INJECT_TAG, serviceIdentifier)); + return createTaggedDecoratorInternal(new Metadata(METADATA_KEY.MULTI_INJECT_TAG, serviceIdentifier)); } export { multiInject }; diff --git a/src/annotation/named.ts b/src/annotation/named.ts index 87d5e2d1d..1127b20d3 100644 --- a/src/annotation/named.ts +++ b/src/annotation/named.ts @@ -1,10 +1,10 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { Metadata } from "../planning/metadata"; -import { createTaggedDecorator/* , tagParameter, tagProperty */ } from "./decorator_utils"; +import { createTaggedDecoratorInternal } from "./decorator_utils"; // Used to add named metadata which is used to resolve name-based contextual bindings. function named(name: string | number | symbol) { - return createTaggedDecorator(new Metadata(METADATA_KEY.NAMED_TAG, name)); + return createTaggedDecoratorInternal(new Metadata(METADATA_KEY.NAMED_TAG, name)); } export { named }; diff --git a/src/annotation/optional.ts b/src/annotation/optional.ts index 31de6d5ef..2082201a8 100644 --- a/src/annotation/optional.ts +++ b/src/annotation/optional.ts @@ -1,9 +1,9 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { Metadata } from "../planning/metadata"; -import { createTaggedDecorator } from "./decorator_utils"; +import { createTaggedDecoratorInternal } from "./decorator_utils"; function optional() { - return createTaggedDecorator(new Metadata(METADATA_KEY.OPTIONAL_TAG, true)); + return createTaggedDecoratorInternal(new Metadata(METADATA_KEY.OPTIONAL_TAG, true)); } export { optional }; diff --git a/src/annotation/tagged.ts b/src/annotation/tagged.ts index c5a8786ff..de3395d74 100644 --- a/src/annotation/tagged.ts +++ b/src/annotation/tagged.ts @@ -1,9 +1,9 @@ import { Metadata } from "../planning/metadata"; -import { createTaggedDecorator } from "./decorator_utils"; +import { createTaggedDecoratorInternal } from "./decorator_utils"; // Used to add custom metadata which is used to resolve metadata-based contextual bindings. function tagged(metadataKey: string | number | symbol, metadataValue: any) { - return createTaggedDecorator(new Metadata(metadataKey, metadataValue)); + return createTaggedDecoratorInternal(new Metadata(metadataKey, metadataValue)); } export { tagged }; diff --git a/src/utils/js.ts b/src/utils/js.ts index dc54512e6..75a03c22c 100644 --- a/src/utils/js.ts +++ b/src/utils/js.ts @@ -1,11 +1,11 @@ -export function getArrayDuplicate(array:T[]):T | undefined { - const seenValues: any= {} +export function getFirstArrayDuplicate(array:T[]):T | undefined { + const seenValues = new Set() for (const entry of array) { - if (seenValues[entry]) { + if (seenValues.has(entry)) { return entry; } else { - seenValues[entry] = true + seenValues.add(entry); } } -} \ No newline at end of file +} diff --git a/test/annotation/decorator_utils.test.ts b/test/annotation/decorator_utils.test.ts index 67fd2304f..2761a0cea 100644 --- a/test/annotation/decorator_utils.test.ts +++ b/test/annotation/decorator_utils.test.ts @@ -1,9 +1,9 @@ import { expect } from "chai"; import * as sinon from "sinon"; -import { createTaggedDecorator, tagParameter, tagProperty } from "../../src/annotation/decorator_utils" +import { createTaggedDecorator, createTaggedDecoratorInternal, tagParameter, tagProperty } from "../../src/annotation/decorator_utils" import * as ERROR_MSGS from "../../src/constants/error_msgs"; import { Container, inject, injectable } from "../../src/inversify"; -describe("createTaggedDecorator", () => { +describe("createTaggedDecoratorInternal", () => { let sandbox:sinon.SinonSandbox beforeEach(function () { sandbox = sinon.createSandbox(); @@ -16,7 +16,7 @@ describe("createTaggedDecorator", () => { it("should call the callback when decorated", () => { class Target {} const callback = sinon.spy(); - const decorator = createTaggedDecorator({key:"1",value:"2"},callback); + const decorator = createTaggedDecoratorInternal({key:"1",value:"2"},callback); try { decorator(Target, "key", 1); }catch(e){ @@ -28,7 +28,7 @@ describe("createTaggedDecorator", () => { it("should pass to tagParameter for parameter decorators", () => { class Target {} const metadata = {key:"1",value:"2"}; - const decorator = createTaggedDecorator(metadata); + const decorator = createTaggedDecoratorInternal(metadata); const spiedTagParameter = sandbox.spy(tagParameter); decorator(Target,undefined as any,1); expect(spiedTagParameter.calledWithExactly(Target, undefined as any, 1, metadata)); @@ -37,7 +37,7 @@ describe("createTaggedDecorator", () => { it("should pass to tagProperty for property decorators", () => { class Target {} const metadata = {key:"2",value:"2"}; - const decorator = createTaggedDecorator(metadata); + const decorator = createTaggedDecoratorInternal(metadata); const spiedTagProperty = sandbox.spy(tagProperty); decorator(Target,"PropertyName"); expect(spiedTagProperty.calledWithExactly(Target, "PropertyName", metadata)); @@ -47,6 +47,7 @@ describe("createTaggedDecorator", () => { function multipleMetadataDecorator(key1Value:string, key2Value: string) { return createTaggedDecorator([{key:"key1",value:key1Value},{key:"key2",value:key2Value}]); } + interface Thing{ type:string } @@ -74,7 +75,14 @@ describe("createTaggedDecorator", () => { return key1Metadata.value === "Key1Value" && key2Metadata.value === "Key2Value"; }); container.resolve(Root); - }) + }); + + it("should be exposed without callback as createTaggedDecorator", () => { + const spy = sandbox.spy(createTaggedDecoratorInternal); + createTaggedDecorator({key:"key",value:"value"}); + expect(spy.calledWithExactly({key:"key",value:"value"})); + }); + }); describe("tagParameter", () => { From 2e5a011488aa94d09c7d2f4c27f8b8dd891f6687 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sun, 9 May 2021 09:29:21 +0100 Subject: [PATCH 09/30] wiki --- wiki/custom_tag_decorators.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/wiki/custom_tag_decorators.md b/wiki/custom_tag_decorators.md index bdfd69e76..e3a1e7cb4 100644 --- a/wiki/custom_tag_decorators.md +++ b/wiki/custom_tag_decorators.md @@ -19,3 +19,23 @@ class Ninja implements Ninja { } } ``` + +If you need to create a reusable decorator for multiple tags: + +```ts +function moodReason(mood:string, reason: string) { + return createTaggedDecorator([{key:"mood",value:mood},{key:"reason",value:reason}]); +} +const happyAndIKnowIt = moodReason("happy","I know it"); +const dontLikeMondays = moodReason("miserable","I don't like Mondays"); + +@injectable() +class MoodyNinja { + public constructor( + @inject("Response") @happyAndIKnowIt clapHands: Response, + @inject("Response") @dontLikeMondays shootWholeDayDown: Response + ) { + //.... + } +} +``` From 6da7ac6996d99ab8b6723202b64e99a5c3069e5b Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sun, 9 May 2021 13:25:46 +0100 Subject: [PATCH 10/30] improve typing --- src/annotation/decorator_utils.ts | 16 +++++++--------- src/interfaces/interfaces.ts | 4 ---- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index d2e869a74..dd2ff22ec 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -5,7 +5,7 @@ import { getFirstArrayDuplicate } from "../utils/js"; function tagParameter( annotationTarget: any, - propertyName: string, + propertyName: string | symbol | undefined, parameterIndex: number, metadata: interfaces.MetadataOrMetadataArray ) { @@ -17,7 +17,7 @@ function tagParameter( function tagProperty( annotationTarget: any, - propertyName: string, + propertyName: string | symbol, metadata: interfaces.MetadataOrMetadataArray ) { _tagParameterOrProperty(METADATA_KEY.TAGGED_PROP, annotationTarget.constructor, propertyName, metadata); @@ -37,17 +37,15 @@ function _ensureNoMetadataKeyDuplicates(metadata: interfaces.MetadataOrMetadataA return metadatas; } - function _tagParameterOrProperty( metadataKey: string, annotationTarget: any, - key: string, + key: string | symbol, metadata: interfaces.MetadataOrMetadataArray, ) { const metadatas: interfaces.Metadata[] = _ensureNoMetadataKeyDuplicates(metadata); - let paramsOrPropertiesMetadata: interfaces.ReflectResult = {}; - + let paramsOrPropertiesMetadata:any = {}; // read metadata if available if (Reflect.hasOwnMetadata(metadataKey, annotationTarget)) { paramsOrPropertiesMetadata = Reflect.getMetadata(metadataKey, annotationTarget); @@ -74,14 +72,14 @@ function _tagParameterOrProperty( function createTaggedDecoratorInternal( metadata:interfaces.MetadataOrMetadataArray, - callback?:(target: any, targetKey: string, indexOrPropertyDescriptor?: number | PropertyDescriptor) => void + callback?:(target: any, targetKey: string | symbol, indexOrPropertyDescriptor?: number | PropertyDescriptor) => void ) { - return function(target: any, targetKey: string, indexOrPropertyDescriptor?: number | PropertyDescriptor) { + return function(target: any, targetKey: string | symbol, indexOrPropertyDescriptor?: number | PropertyDescriptor) { if(callback){ callback(target, targetKey, indexOrPropertyDescriptor); } if (typeof indexOrPropertyDescriptor === "number") { - tagParameter(target, targetKey, indexOrPropertyDescriptor, metadata); + tagParameter(target, targetKey as unknown as string | symbol | undefined, indexOrPropertyDescriptor, metadata); } else { tagProperty(target, targetKey, metadata); } diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index 0a35b466f..fcdf0630e 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -111,10 +111,6 @@ namespace interfaces { setCurrentRequest(request: Request): void; } - export interface ReflectResult { - [key: string]: Metadata[]; - } - export type MetadataOrMetadataArray = Metadata | Metadata[]; export interface Metadata { From a0eede0bc25899d2d957a7b0a3e7072231101fd7 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sun, 9 May 2021 14:08:53 +0100 Subject: [PATCH 11/30] improve decorator typing --- src/annotation/decorator_utils.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index dd2ff22ec..5282c3df9 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -69,12 +69,16 @@ function _tagParameterOrProperty( Reflect.defineMetadata(metadataKey, paramsOrPropertiesMetadata, annotationTarget); } - +type ParameterOrPropertyDecorator = ( + target: any, + targetKey: string | symbol, + indexOrPropertyDescriptor?: number | TypedPropertyDescriptor +) => void; function createTaggedDecoratorInternal( metadata:interfaces.MetadataOrMetadataArray, - callback?:(target: any, targetKey: string | symbol, indexOrPropertyDescriptor?: number | PropertyDescriptor) => void + callback?:ParameterOrPropertyDecorator ) { - return function(target: any, targetKey: string | symbol, indexOrPropertyDescriptor?: number | PropertyDescriptor) { + const decorator:ParameterOrPropertyDecorator = (target, targetKey, indexOrPropertyDescriptor) => { if(callback){ callback(target, targetKey, indexOrPropertyDescriptor); } @@ -84,6 +88,7 @@ function createTaggedDecoratorInternal( tagProperty(target, targetKey, metadata); } }; + return decorator; } function createTaggedDecorator(metadata:interfaces.MetadataOrMetadataArray) { From c9487b803b5c9c6a66cf3341eaecfaf656b6f704 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sun, 9 May 2021 14:10:53 +0100 Subject: [PATCH 12/30] throw error for decorated property missing inject / multiInject decorators --- src/planning/reflection_utils.ts | 11 ++++++++--- test/planning/planner.test.ts | 27 ++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/planning/reflection_utils.ts b/src/planning/reflection_utils.ts index f538f98d1..1fe73b642 100644 --- a/src/planning/reflection_utils.ts +++ b/src/planning/reflection_utils.ts @@ -48,7 +48,7 @@ function getTargets( ); // Target instances that represent properties to be injected - const propertyTargets = getClassPropsAsTargets(metadataReader, func); + const propertyTargets = getClassPropsAsTargets(metadataReader, func, constructorName); const targets = [ ...constructorTargets, @@ -130,7 +130,7 @@ function getConstructorArgsAsTargets( return targets; } -function getClassPropsAsTargets(metadataReader: interfaces.MetadataReader, constructorFunc: Function) { +function getClassPropsAsTargets(metadataReader: interfaces.MetadataReader, constructorFunc: Function, constructorName:string) { const classPropsMetadata = metadataReader.getPropertiesMetadata(constructorFunc); let targets: interfaces.Target[] = []; @@ -149,6 +149,11 @@ function getClassPropsAsTargets(metadataReader: interfaces.MetadataReader, const // Take types to be injected from user-generated metadata const serviceIdentifier = (metadata.inject || metadata.multiInject); + if(serviceIdentifier === undefined) { + const msg = `${ERROR_MSGS.MISSING_INJECTABLE_ANNOTATION} for property ${key} in class ${constructorName}.`; + throw new Error(msg); + + } // The property target const target = new Target(TargetTypeEnum.ClassProperty, targetName, serviceIdentifier); @@ -161,7 +166,7 @@ function getClassPropsAsTargets(metadataReader: interfaces.MetadataReader, const if (baseConstructor !== Object) { - const baseTargets = getClassPropsAsTargets(metadataReader, baseConstructor); + const baseTargets = getClassPropsAsTargets(metadataReader, baseConstructor,constructorName); targets = [ ...targets, diff --git a/test/planning/planner.test.ts b/test/planning/planner.test.ts index 21adb0d32..d8e60898a 100644 --- a/test/planning/planner.test.ts +++ b/test/planning/planner.test.ts @@ -9,6 +9,7 @@ import * as ERROR_MSGS from "../../src/constants/error_msgs"; import { TargetTypeEnum } from "../../src/constants/literal_types"; import { Container } from "../../src/container/container"; import { interfaces } from "../../src/interfaces/interfaces"; +import { named } from "../../src/inversify"; import { MetadataReader } from "../../src/planning/metadata_reader"; import { plan } from "../../src/planning/planner"; @@ -465,7 +466,7 @@ describe("Planner", () => { }); - it("Should be throw when a class has a missing @injectable annotation", () => { + it("Should throw when a class has a missing @injectable annotation", () => { interface Weapon { } @@ -482,6 +483,30 @@ describe("Planner", () => { }); + it("Should throw when apply a metadata decorator without @inject or @multiInject", () => { + @injectable() + class Ninja { + @named("name") + // tslint:disable-next-line: no-empty + set weapon(weapon : Weapon){ + + } + } + interface Weapon { } + + class Katana implements Weapon { } + + const container = new Container(); + container.bind("Weapon").to(Katana); + container.bind(Ninja).toSelf(); + + const throwFunction = () => { + plan(new MetadataReader(), container, false, TargetTypeEnum.Variable, Ninja); + }; + + expect(throwFunction).to.throw(`${ERROR_MSGS.MISSING_INJECTABLE_ANNOTATION} for property weapon in class Ninja.`); + }); + it("Should ignore checking base classes for @injectable when skipBaseClassChecks is set on the container", () => { class Test { } From 44c43f057bc7ecf324b9c630e10074a81a53402c Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sun, 9 May 2021 14:31:09 +0100 Subject: [PATCH 13/30] refactor code climate --- src/planning/reflection_utils.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/planning/reflection_utils.ts b/src/planning/reflection_utils.ts index 1fe73b642..ea79262d6 100644 --- a/src/planning/reflection_utils.ts +++ b/src/planning/reflection_utils.ts @@ -130,6 +130,15 @@ function getConstructorArgsAsTargets( return targets; } +function _getServiceIdentifierForProperty(inject:any,multiInject:any,propertyName:string, className: string):any { + const serviceIdentifier = (inject || multiInject); + if(serviceIdentifier === undefined) { + const msg = `${ERROR_MSGS.MISSING_INJECTABLE_ANNOTATION} for property ${propertyName} in class ${className}.`; + throw new Error(msg); + } + return serviceIdentifier; +} + function getClassPropsAsTargets(metadataReader: interfaces.MetadataReader, constructorFunc: Function, constructorName:string) { const classPropsMetadata = metadataReader.getPropertiesMetadata(constructorFunc); @@ -148,12 +157,7 @@ function getClassPropsAsTargets(metadataReader: interfaces.MetadataReader, const const targetName = metadata.targetName || key; // Take types to be injected from user-generated metadata - const serviceIdentifier = (metadata.inject || metadata.multiInject); - if(serviceIdentifier === undefined) { - const msg = `${ERROR_MSGS.MISSING_INJECTABLE_ANNOTATION} for property ${key} in class ${constructorName}.`; - throw new Error(msg); - - } + const serviceIdentifier = _getServiceIdentifierForProperty(metadata.inject,metadata.multiInject,key,constructorName); // The property target const target = new Target(TargetTypeEnum.ClassProperty, targetName, serviceIdentifier); From 2e4119f04bd3490bba7c869e43753da06c9d067d Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sun, 9 May 2021 16:19:10 +0100 Subject: [PATCH 14/30] permit injection for symbol property key --- src/interfaces/interfaces.ts | 1 + src/planning/reflection_utils.ts | 15 +++++++------ src/planning/target.ts | 8 +++++-- src/resolution/instantiation.ts | 4 ++-- test/inversify.test.ts | 37 ++++++++++++++++++++++++++++++++ 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index fcdf0630e..3b6a3da7d 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -158,6 +158,7 @@ namespace interfaces { serviceIdentifier: ServiceIdentifier; type: TargetType; name: QueryableString; + identifier: string | symbol; metadata: Metadata[]; getNamedTag(): interfaces.Metadata | null; getCustomTags(): interfaces.Metadata[] | null; diff --git a/src/planning/reflection_utils.ts b/src/planning/reflection_utils.ts index ea79262d6..8e4c31277 100644 --- a/src/planning/reflection_utils.ts +++ b/src/planning/reflection_utils.ts @@ -130,10 +130,10 @@ function getConstructorArgsAsTargets( return targets; } -function _getServiceIdentifierForProperty(inject:any,multiInject:any,propertyName:string, className: string):any { +function _getServiceIdentifierForProperty(inject:any,multiInject:any,propertyName:string | symbol, className: string):any { const serviceIdentifier = (inject || multiInject); if(serviceIdentifier === undefined) { - const msg = `${ERROR_MSGS.MISSING_INJECTABLE_ANNOTATION} for property ${propertyName} in class ${className}.`; + const msg = `${ERROR_MSGS.MISSING_INJECTABLE_ANNOTATION} for property ${String(propertyName)} in class ${className}.`; throw new Error(msg); } return serviceIdentifier; @@ -141,9 +141,11 @@ function _getServiceIdentifierForProperty(inject:any,multiInject:any,propertyNam function getClassPropsAsTargets(metadataReader: interfaces.MetadataReader, constructorFunc: Function, constructorName:string) { - const classPropsMetadata = metadataReader.getPropertiesMetadata(constructorFunc); + const classPropsMetadata:any = metadataReader.getPropertiesMetadata(constructorFunc); let targets: interfaces.Target[] = []; - const keys = Object.keys(classPropsMetadata); + const symbolKeys = Object.getOwnPropertySymbols(classPropsMetadata); + const stringKeys:(string | symbol)[] = Object.keys(classPropsMetadata); + const keys:(string | symbol)[] = stringKeys.concat(symbolKeys); for (const key of keys) { @@ -153,14 +155,13 @@ function getClassPropsAsTargets(metadataReader: interfaces.MetadataReader, const // the metadata formatted for easier access const metadata = formatTargetMetadata(classPropsMetadata[key]); - // the name of the property being injected - const targetName = metadata.targetName || key; + const identifier = metadata.targetName || key; // Take types to be injected from user-generated metadata const serviceIdentifier = _getServiceIdentifierForProperty(metadata.inject,metadata.multiInject,key,constructorName); // The property target - const target = new Target(TargetTypeEnum.ClassProperty, targetName, serviceIdentifier); + const target = new Target(TargetTypeEnum.ClassProperty, identifier, serviceIdentifier); target.metadata = targetMetadata; targets.push(target); } diff --git a/src/planning/target.ts b/src/planning/target.ts index bd9d90b1f..c0a88686c 100644 --- a/src/planning/target.ts +++ b/src/planning/target.ts @@ -10,11 +10,13 @@ class Target implements interfaces.Target { public type: interfaces.TargetType; public serviceIdentifier: interfaces.ServiceIdentifier; public name: interfaces.QueryableString; + public identifier: string | symbol; + public key:string | symbol public metadata: Metadata[]; public constructor( type: interfaces.TargetType, - name: string, + identifier: string | symbol, serviceIdentifier: interfaces.ServiceIdentifier, namedOrTagged?: (string | Metadata) ) { @@ -22,7 +24,9 @@ class Target implements interfaces.Target { this.id = id(); this.type = type; this.serviceIdentifier = serviceIdentifier; - this.name = new QueryableString(name || ""); + const queryableName = typeof identifier === 'symbol' ? identifier.toString().slice(7,-1) : identifier; + this.name = new QueryableString(queryableName || ""); + this.identifier = identifier; this.metadata = new Array(); let metadataItem: interfaces.Metadata | null = null; diff --git a/src/resolution/instantiation.ts b/src/resolution/instantiation.ts index 6c3b41a1d..6b659151f 100644 --- a/src/resolution/instantiation.ts +++ b/src/resolution/instantiation.ts @@ -66,9 +66,9 @@ function createInstanceWithInjections( ): T { const instance = new args.constr(...args.constructorInjections); args.propertyRequests.forEach((r: interfaces.Request, index: number) => { - const propertyName = r.target.name.value(); + const property = r.target.identifier; const injection = args.propertyInjections[index]; - (instance as Record)[propertyName] = injection; + (instance as any)[property] = injection; }); return instance } diff --git a/test/inversify.test.ts b/test/inversify.test.ts index a9d7fefc5..8017d5778 100644 --- a/test/inversify.test.ts +++ b/test/inversify.test.ts @@ -110,6 +110,43 @@ describe("InversifyJS", () => { expect(ninja.sneak()).to.eql("hit!"); expect(ninja.fight()).to.eql("cut!"); }); + + it("Should be able to inject when symbol property key ", () => { + const weaponProperty = Symbol(); + interface Weapon {} + @injectable() + class Shuriken implements Weapon { } + @injectable() + class Ninja{ + @inject("Weapon") + [weaponProperty]: Weapon + } + const container = new Container(); + container.bind("Weapon").to(Shuriken); + const myNinja = container.resolve(Ninja); + const weapon = myNinja[weaponProperty]; + expect(weapon).to.be.instanceOf(Shuriken); + }); + + it("Should be possible to constrain to a symbol description", () => { + const throwableWeapon = Symbol("throwable"); + interface Weapon {} + @injectable() + class Shuriken implements Weapon { } + @injectable() + class Ninja{ + @inject("Weapon") + [throwableWeapon]: Weapon + } + const container = new Container(); + container.bind("Weapon").to(Shuriken).when(request => { + return request.target.name.equals("throwable"); + }) + const myNinja = container.resolve(Ninja); + const weapon = myNinja[throwableWeapon]; + expect(weapon).to.be.instanceOf(Shuriken); + }) + it("Should be able to resolve and inject dependencies in VanillaJS", () => { const TYPES = { From ee81df323fcc7cd4ffabe651714e1213cd0628de Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sun, 9 May 2021 16:43:03 +0100 Subject: [PATCH 15/30] move symbol tests to container --- test/container/container.test.ts | 40 +++++++++++++++++++++++++++++++- test/inversify.test.ts | 36 ---------------------------- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/test/container/container.test.ts b/test/container/container.test.ts index 45d93d168..6d60ae997 100644 --- a/test/container/container.test.ts +++ b/test/container/container.test.ts @@ -1,5 +1,6 @@ import { assert, expect } from "chai"; import * as sinon from "sinon"; +import { inject } from "../../src/annotation/inject"; import { injectable } from "../../src/annotation/injectable"; import { postConstruct } from "../../src/annotation/post_construct"; import * as ERROR_MSGS from "../../src/constants/error_msgs"; @@ -1086,5 +1087,42 @@ describe("Container", () => { skipBaseClassChecks: 'Jolene, Jolene, Jolene, Jolene' as unknown as boolean }) ).to.throw(ERROR_MSGS.CONTAINER_OPTIONS_INVALID_SKIP_BASE_CHECK); - }) + }); + + it("Should be able to inject when symbol property key ", () => { + const weaponProperty = Symbol(); + interface Weapon {} + @injectable() + class Shuriken implements Weapon { } + @injectable() + class Ninja{ + @inject("Weapon") + [weaponProperty]: Weapon + } + const container = new Container(); + container.bind("Weapon").to(Shuriken); + const myNinja = container.resolve(Ninja); + const weapon = myNinja[weaponProperty]; + expect(weapon).to.be.instanceOf(Shuriken); + }); + + it("Should be possible to constrain to a symbol description", () => { + const throwableWeapon = Symbol("throwable"); + interface Weapon {} + @injectable() + class Shuriken implements Weapon { } + @injectable() + class Ninja{ + @inject("Weapon") + [throwableWeapon]: Weapon + } + const container = new Container(); + container.bind("Weapon").to(Shuriken).when(request => { + return request.target.name.equals("throwable"); + }) + const myNinja = container.resolve(Ninja); + const weapon = myNinja[throwableWeapon]; + expect(weapon).to.be.instanceOf(Shuriken); + }); + }); diff --git a/test/inversify.test.ts b/test/inversify.test.ts index 8017d5778..386862d15 100644 --- a/test/inversify.test.ts +++ b/test/inversify.test.ts @@ -111,42 +111,6 @@ describe("InversifyJS", () => { expect(ninja.fight()).to.eql("cut!"); }); - it("Should be able to inject when symbol property key ", () => { - const weaponProperty = Symbol(); - interface Weapon {} - @injectable() - class Shuriken implements Weapon { } - @injectable() - class Ninja{ - @inject("Weapon") - [weaponProperty]: Weapon - } - const container = new Container(); - container.bind("Weapon").to(Shuriken); - const myNinja = container.resolve(Ninja); - const weapon = myNinja[weaponProperty]; - expect(weapon).to.be.instanceOf(Shuriken); - }); - - it("Should be possible to constrain to a symbol description", () => { - const throwableWeapon = Symbol("throwable"); - interface Weapon {} - @injectable() - class Shuriken implements Weapon { } - @injectable() - class Ninja{ - @inject("Weapon") - [throwableWeapon]: Weapon - } - const container = new Container(); - container.bind("Weapon").to(Shuriken).when(request => { - return request.target.name.equals("throwable"); - }) - const myNinja = container.resolve(Ninja); - const weapon = myNinja[throwableWeapon]; - expect(weapon).to.be.instanceOf(Shuriken); - }) - it("Should be able to resolve and inject dependencies in VanillaJS", () => { const TYPES = { From 5703cdfcd1414489a7da46978b6a2eb424d1fc84 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Mon, 10 May 2021 09:02:38 +0100 Subject: [PATCH 16/30] remove callback from createTaggedDecorator. minor type changes. --- src/annotation/decorator_utils.ts | 31 +++++++------------------ src/annotation/inject.ts | 9 ++++--- src/annotation/multi_inject.ts | 4 ++-- src/annotation/named.ts | 4 ++-- src/annotation/optional.ts | 4 ++-- src/annotation/tagged.ts | 4 ++-- test/annotation/decorator_utils.test.ts | 26 ++++----------------- 7 files changed, 27 insertions(+), 55 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 5282c3df9..a9579c99d 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -45,13 +45,13 @@ function _tagParameterOrProperty( ) { const metadatas: interfaces.Metadata[] = _ensureNoMetadataKeyDuplicates(metadata); - let paramsOrPropertiesMetadata:any = {}; + let paramsOrPropertiesMetadata:Record = {}; // read metadata if available if (Reflect.hasOwnMetadata(metadataKey, annotationTarget)) { paramsOrPropertiesMetadata = Reflect.getMetadata(metadataKey, annotationTarget); } - let paramOrPropertyMetadata: interfaces.Metadata[] | undefined = paramsOrPropertiesMetadata[key]; + let paramOrPropertyMetadata: interfaces.Metadata[] | undefined = paramsOrPropertiesMetadata[key as any]; if (paramOrPropertyMetadata === undefined) { paramOrPropertyMetadata = []; @@ -65,34 +65,21 @@ function _tagParameterOrProperty( // set metadata paramOrPropertyMetadata.push(...metadatas); - paramsOrPropertiesMetadata[key] = paramOrPropertyMetadata; + paramsOrPropertiesMetadata[key as any] = paramOrPropertyMetadata; Reflect.defineMetadata(metadataKey, paramsOrPropertiesMetadata, annotationTarget); } -type ParameterOrPropertyDecorator = ( - target: any, - targetKey: string | symbol, - indexOrPropertyDescriptor?: number | TypedPropertyDescriptor -) => void; -function createTaggedDecoratorInternal( + +function createTaggedDecorator( metadata:interfaces.MetadataOrMetadataArray, - callback?:ParameterOrPropertyDecorator ) { - const decorator:ParameterOrPropertyDecorator = (target, targetKey, indexOrPropertyDescriptor) => { - if(callback){ - callback(target, targetKey, indexOrPropertyDescriptor); - } + return (target:any, targetKey:string | symbol | undefined, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { if (typeof indexOrPropertyDescriptor === "number") { - tagParameter(target, targetKey as unknown as string | symbol | undefined, indexOrPropertyDescriptor, metadata); + tagParameter(target, targetKey, indexOrPropertyDescriptor, metadata); } else { - tagProperty(target, targetKey, metadata); + tagProperty(target, targetKey as any, metadata); } }; - return decorator; -} - -function createTaggedDecorator(metadata:interfaces.MetadataOrMetadataArray) { - return createTaggedDecoratorInternal(metadata); } function _decorate(decorators: any[], target: any): void { @@ -122,4 +109,4 @@ function decorate( } } -export { decorate, tagParameter, tagProperty, createTaggedDecoratorInternal, createTaggedDecorator }; +export { decorate, tagParameter, tagProperty, createTaggedDecorator }; diff --git a/src/annotation/inject.ts b/src/annotation/inject.ts index a35a1b76b..e7a0e864e 100644 --- a/src/annotation/inject.ts +++ b/src/annotation/inject.ts @@ -2,7 +2,7 @@ import { UNDEFINED_INJECT_ANNOTATION } from "../constants/error_msgs"; import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; import { Metadata } from "../planning/metadata"; -import { createTaggedDecoratorInternal } from "./decorator_utils"; +import { createTaggedDecorator } from "./decorator_utils"; export type ServiceIdentifierOrFunc = interfaces.ServiceIdentifier | LazyServiceIdentifer; @@ -18,11 +18,14 @@ export class LazyServiceIdentifer { } function inject(serviceIdentifier: ServiceIdentifierOrFunc) { - return createTaggedDecoratorInternal(new Metadata(METADATA_KEY.INJECT_TAG, serviceIdentifier),target => { + return (target:any, targetKey:string | symbol, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { if (serviceIdentifier === undefined) { throw new Error(UNDEFINED_INJECT_ANNOTATION(target.name)); } - }) + return createTaggedDecorator( + new Metadata(METADATA_KEY.INJECT_TAG, serviceIdentifier) + )(target, targetKey,indexOrPropertyDescriptor); + }; } export { inject }; diff --git a/src/annotation/multi_inject.ts b/src/annotation/multi_inject.ts index 3a45c319b..016b96966 100644 --- a/src/annotation/multi_inject.ts +++ b/src/annotation/multi_inject.ts @@ -1,10 +1,10 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; import { Metadata } from "../planning/metadata"; -import { createTaggedDecoratorInternal } from "./decorator_utils"; +import { createTaggedDecorator } from "./decorator_utils"; function multiInject(serviceIdentifier: interfaces.ServiceIdentifier) { - return createTaggedDecoratorInternal(new Metadata(METADATA_KEY.MULTI_INJECT_TAG, serviceIdentifier)); + return createTaggedDecorator(new Metadata(METADATA_KEY.MULTI_INJECT_TAG, serviceIdentifier)); } export { multiInject }; diff --git a/src/annotation/named.ts b/src/annotation/named.ts index 1127b20d3..a13a83780 100644 --- a/src/annotation/named.ts +++ b/src/annotation/named.ts @@ -1,10 +1,10 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { Metadata } from "../planning/metadata"; -import { createTaggedDecoratorInternal } from "./decorator_utils"; +import { createTaggedDecorator } from "./decorator_utils"; // Used to add named metadata which is used to resolve name-based contextual bindings. function named(name: string | number | symbol) { - return createTaggedDecoratorInternal(new Metadata(METADATA_KEY.NAMED_TAG, name)); + return createTaggedDecorator(new Metadata(METADATA_KEY.NAMED_TAG, name)); } export { named }; diff --git a/src/annotation/optional.ts b/src/annotation/optional.ts index 2082201a8..31de6d5ef 100644 --- a/src/annotation/optional.ts +++ b/src/annotation/optional.ts @@ -1,9 +1,9 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { Metadata } from "../planning/metadata"; -import { createTaggedDecoratorInternal } from "./decorator_utils"; +import { createTaggedDecorator } from "./decorator_utils"; function optional() { - return createTaggedDecoratorInternal(new Metadata(METADATA_KEY.OPTIONAL_TAG, true)); + return createTaggedDecorator(new Metadata(METADATA_KEY.OPTIONAL_TAG, true)); } export { optional }; diff --git a/src/annotation/tagged.ts b/src/annotation/tagged.ts index de3395d74..c5a8786ff 100644 --- a/src/annotation/tagged.ts +++ b/src/annotation/tagged.ts @@ -1,9 +1,9 @@ import { Metadata } from "../planning/metadata"; -import { createTaggedDecoratorInternal } from "./decorator_utils"; +import { createTaggedDecorator } from "./decorator_utils"; // Used to add custom metadata which is used to resolve metadata-based contextual bindings. function tagged(metadataKey: string | number | symbol, metadataValue: any) { - return createTaggedDecoratorInternal(new Metadata(metadataKey, metadataValue)); + return createTaggedDecorator(new Metadata(metadataKey, metadataValue)); } export { tagged }; diff --git a/test/annotation/decorator_utils.test.ts b/test/annotation/decorator_utils.test.ts index 2761a0cea..a51815d4f 100644 --- a/test/annotation/decorator_utils.test.ts +++ b/test/annotation/decorator_utils.test.ts @@ -1,9 +1,9 @@ import { expect } from "chai"; import * as sinon from "sinon"; -import { createTaggedDecorator, createTaggedDecoratorInternal, tagParameter, tagProperty } from "../../src/annotation/decorator_utils" +import { createTaggedDecorator, tagParameter, tagProperty } from "../../src/annotation/decorator_utils" import * as ERROR_MSGS from "../../src/constants/error_msgs"; import { Container, inject, injectable } from "../../src/inversify"; -describe("createTaggedDecoratorInternal", () => { +describe("createTaggedDecorator", () => { let sandbox:sinon.SinonSandbox beforeEach(function () { sandbox = sinon.createSandbox(); @@ -13,22 +13,10 @@ describe("createTaggedDecoratorInternal", () => { sandbox.restore(); }); - it("should call the callback when decorated", () => { - class Target {} - const callback = sinon.spy(); - const decorator = createTaggedDecoratorInternal({key:"1",value:"2"},callback); - try { - decorator(Target, "key", 1); - }catch(e){ - // - } - expect(callback.calledWithExactly(Target.prototype, "key",1)); - }); - it("should pass to tagParameter for parameter decorators", () => { class Target {} const metadata = {key:"1",value:"2"}; - const decorator = createTaggedDecoratorInternal(metadata); + const decorator = createTaggedDecorator(metadata); const spiedTagParameter = sandbox.spy(tagParameter); decorator(Target,undefined as any,1); expect(spiedTagParameter.calledWithExactly(Target, undefined as any, 1, metadata)); @@ -37,7 +25,7 @@ describe("createTaggedDecoratorInternal", () => { it("should pass to tagProperty for property decorators", () => { class Target {} const metadata = {key:"2",value:"2"}; - const decorator = createTaggedDecoratorInternal(metadata); + const decorator = createTaggedDecorator(metadata); const spiedTagProperty = sandbox.spy(tagProperty); decorator(Target,"PropertyName"); expect(spiedTagProperty.calledWithExactly(Target, "PropertyName", metadata)); @@ -77,12 +65,6 @@ describe("createTaggedDecoratorInternal", () => { container.resolve(Root); }); - it("should be exposed without callback as createTaggedDecorator", () => { - const spy = sandbox.spy(createTaggedDecoratorInternal); - createTaggedDecorator({key:"key",value:"value"}); - expect(spy.calledWithExactly({key:"key",value:"value"})); - }); - }); describe("tagParameter", () => { From 9c7570227be9d65c8bc9a64024f583700f306ea8 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Mon, 10 May 2021 09:27:12 +0100 Subject: [PATCH 17/30] permit lazyserviceidentifier for multiinject --- src/annotation/inject.ts | 29 ++--------------------- src/annotation/inject_base.ts | 17 +++++++++++++ src/annotation/lazy_service_identifier.ts | 14 +++++++++++ src/annotation/multi_inject.ts | 8 ++----- src/inversify.ts | 3 ++- src/planning/reflection_utils.ts | 2 +- test/annotation/inject.test.ts | 3 ++- 7 files changed, 40 insertions(+), 36 deletions(-) create mode 100644 src/annotation/inject_base.ts create mode 100644 src/annotation/lazy_service_identifier.ts diff --git a/src/annotation/inject.ts b/src/annotation/inject.ts index e7a0e864e..7f56543d2 100644 --- a/src/annotation/inject.ts +++ b/src/annotation/inject.ts @@ -1,31 +1,6 @@ -import { UNDEFINED_INJECT_ANNOTATION } from "../constants/error_msgs"; import * as METADATA_KEY from "../constants/metadata_keys"; -import { interfaces } from "../interfaces/interfaces"; -import { Metadata } from "../planning/metadata"; -import { createTaggedDecorator } from "./decorator_utils"; +import { injectBase } from "./inject_base"; -export type ServiceIdentifierOrFunc = interfaces.ServiceIdentifier | LazyServiceIdentifer; - -export class LazyServiceIdentifer { - private _cb: () => interfaces.ServiceIdentifier; - public constructor(cb: () => interfaces.ServiceIdentifier) { - this._cb = cb; - } - - public unwrap() { - return this._cb(); - } -} - -function inject(serviceIdentifier: ServiceIdentifierOrFunc) { - return (target:any, targetKey:string | symbol, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { - if (serviceIdentifier === undefined) { - throw new Error(UNDEFINED_INJECT_ANNOTATION(target.name)); - } - return createTaggedDecorator( - new Metadata(METADATA_KEY.INJECT_TAG, serviceIdentifier) - )(target, targetKey,indexOrPropertyDescriptor); - }; -} +const inject = injectBase(METADATA_KEY.INJECT_TAG); export { inject }; diff --git a/src/annotation/inject_base.ts b/src/annotation/inject_base.ts new file mode 100644 index 000000000..0678b63dc --- /dev/null +++ b/src/annotation/inject_base.ts @@ -0,0 +1,17 @@ +import { UNDEFINED_INJECT_ANNOTATION } from "../constants/error_msgs"; +import { Metadata } from "../planning/metadata"; +import { createTaggedDecorator } from "./decorator_utils"; +import { ServiceIdentifierOrFunc } from "./lazy_service_identifier"; + +export function injectBase(metadataKey:string){ + return (serviceIdentifier: ServiceIdentifierOrFunc) => { + return (target:any, targetKey:string | symbol, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { + if (serviceIdentifier === undefined) { + throw new Error(UNDEFINED_INJECT_ANNOTATION(target.name)); + } + return createTaggedDecorator( + new Metadata(metadataKey, serviceIdentifier) + )(target, targetKey,indexOrPropertyDescriptor); + }; + } +} \ No newline at end of file diff --git a/src/annotation/lazy_service_identifier.ts b/src/annotation/lazy_service_identifier.ts new file mode 100644 index 000000000..8b75dad59 --- /dev/null +++ b/src/annotation/lazy_service_identifier.ts @@ -0,0 +1,14 @@ +import { interfaces } from "../interfaces/interfaces"; + +export type ServiceIdentifierOrFunc = interfaces.ServiceIdentifier | LazyServiceIdentifer; + +export class LazyServiceIdentifer { + private _cb: () => interfaces.ServiceIdentifier; + public constructor(cb: () => interfaces.ServiceIdentifier) { + this._cb = cb; + } + + public unwrap() { + return this._cb(); + } +} diff --git a/src/annotation/multi_inject.ts b/src/annotation/multi_inject.ts index 016b96966..d874ae3ef 100644 --- a/src/annotation/multi_inject.ts +++ b/src/annotation/multi_inject.ts @@ -1,10 +1,6 @@ import * as METADATA_KEY from "../constants/metadata_keys"; -import { interfaces } from "../interfaces/interfaces"; -import { Metadata } from "../planning/metadata"; -import { createTaggedDecorator } from "./decorator_utils"; +import { injectBase } from "./inject_base"; -function multiInject(serviceIdentifier: interfaces.ServiceIdentifier) { - return createTaggedDecorator(new Metadata(METADATA_KEY.MULTI_INJECT_TAG, serviceIdentifier)); -} +const multiInject = injectBase(METADATA_KEY.MULTI_INJECT_TAG); export { multiInject }; diff --git a/src/inversify.ts b/src/inversify.ts index 831ea2aab..8307f4f99 100644 --- a/src/inversify.ts +++ b/src/inversify.ts @@ -7,7 +7,8 @@ export { createTaggedDecorator } from "./annotation/decorator_utils" export { injectable } from "./annotation/injectable"; export { tagged } from "./annotation/tagged"; export { named } from "./annotation/named"; -export { inject, LazyServiceIdentifer } from "./annotation/inject"; +export { inject } from "./annotation/inject"; +export { LazyServiceIdentifer } from "./annotation/lazy_service_identifier" export { optional } from "./annotation/optional"; export { unmanaged } from "./annotation/unmanaged"; export { multiInject } from "./annotation/multi_inject"; diff --git a/src/planning/reflection_utils.ts b/src/planning/reflection_utils.ts index 8e4c31277..8643187a8 100644 --- a/src/planning/reflection_utils.ts +++ b/src/planning/reflection_utils.ts @@ -1,4 +1,4 @@ -import { LazyServiceIdentifer } from "../annotation/inject"; +import { LazyServiceIdentifer } from "../annotation/lazy_service_identifier"; import * as ERROR_MSGS from "../constants/error_msgs"; import { TargetTypeEnum } from "../constants/literal_types"; import * as METADATA_KEY from "../constants/metadata_keys"; diff --git a/test/annotation/inject.test.ts b/test/annotation/inject.test.ts index e19f73aaa..8cb9f4855 100644 --- a/test/annotation/inject.test.ts +++ b/test/annotation/inject.test.ts @@ -3,7 +3,8 @@ declare function __param(paramIndex: number, decorator: ParameterDecorator): Cla import { expect } from "chai"; import { decorate } from "../../src/annotation/decorator_utils"; -import { inject, LazyServiceIdentifer } from "../../src/annotation/inject"; +import { inject } from "../../src/annotation/inject"; +import { LazyServiceIdentifer } from "../../src/annotation/lazy_service_identifier"; import * as ERROR_MSGS from "../../src/constants/error_msgs"; import * as METADATA_KEY from "../../src/constants/metadata_keys"; import { interfaces } from "../../src/interfaces/interfaces"; From 7124f672917ecd4a09d38930cb2043183393619d Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Mon, 10 May 2021 11:20:48 +0100 Subject: [PATCH 18/30] correct decorator typing --- src/annotation/decorator_utils.ts | 2 +- src/annotation/inject_base.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index a9579c99d..c83c73e2b 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -77,7 +77,7 @@ function createTaggedDecorator( if (typeof indexOrPropertyDescriptor === "number") { tagParameter(target, targetKey, indexOrPropertyDescriptor, metadata); } else { - tagProperty(target, targetKey as any, metadata); + tagProperty(target, targetKey as string | symbol, metadata); } }; } diff --git a/src/annotation/inject_base.ts b/src/annotation/inject_base.ts index 0678b63dc..cb3278d8e 100644 --- a/src/annotation/inject_base.ts +++ b/src/annotation/inject_base.ts @@ -5,7 +5,7 @@ import { ServiceIdentifierOrFunc } from "./lazy_service_identifier"; export function injectBase(metadataKey:string){ return (serviceIdentifier: ServiceIdentifierOrFunc) => { - return (target:any, targetKey:string | symbol, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { + return (target:any, targetKey:string | symbol | undefined, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { if (serviceIdentifier === undefined) { throw new Error(UNDEFINED_INJECT_ANNOTATION(target.name)); } @@ -14,4 +14,4 @@ export function injectBase(metadataKey:string){ )(target, targetKey,indexOrPropertyDescriptor); }; } -} \ No newline at end of file +} From 0f565da6a56df9e4767809e0b96d223bf145dd04 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Mon, 10 May 2021 17:28:41 +0100 Subject: [PATCH 19/30] type target to Object --- src/annotation/decorator_utils.ts | 8 ++++---- src/annotation/inject_base.ts | 11 +++++++++-- test/annotation/inject.test.ts | 20 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index c83c73e2b..188492f07 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -4,7 +4,7 @@ import { interfaces } from "../interfaces/interfaces"; import { getFirstArrayDuplicate } from "../utils/js"; function tagParameter( - annotationTarget: any, + annotationTarget: Object, propertyName: string | symbol | undefined, parameterIndex: number, metadata: interfaces.MetadataOrMetadataArray @@ -16,7 +16,7 @@ function tagParameter( } function tagProperty( - annotationTarget: any, + annotationTarget: Object, propertyName: string | symbol, metadata: interfaces.MetadataOrMetadataArray ) { @@ -39,7 +39,7 @@ function _ensureNoMetadataKeyDuplicates(metadata: interfaces.MetadataOrMetadataA function _tagParameterOrProperty( metadataKey: string, - annotationTarget: any, + annotationTarget: Object, key: string | symbol, metadata: interfaces.MetadataOrMetadataArray, ) { @@ -73,7 +73,7 @@ function _tagParameterOrProperty( function createTaggedDecorator( metadata:interfaces.MetadataOrMetadataArray, ) { - return (target:any, targetKey:string | symbol | undefined, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { + return (target:Object, targetKey:string | symbol | undefined, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { if (typeof indexOrPropertyDescriptor === "number") { tagParameter(target, targetKey, indexOrPropertyDescriptor, metadata); } else { diff --git a/src/annotation/inject_base.ts b/src/annotation/inject_base.ts index cb3278d8e..d577aaaef 100644 --- a/src/annotation/inject_base.ts +++ b/src/annotation/inject_base.ts @@ -5,9 +5,16 @@ import { ServiceIdentifierOrFunc } from "./lazy_service_identifier"; export function injectBase(metadataKey:string){ return (serviceIdentifier: ServiceIdentifierOrFunc) => { - return (target:any, targetKey:string | symbol | undefined, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { + return (target: Object, + targetKey:string | symbol | undefined, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { if (serviceIdentifier === undefined) { - throw new Error(UNDEFINED_INJECT_ANNOTATION(target.name)); + let className = ""; + if(typeof target === "function"){ + className = target.name; + }else{ + className = target.constructor.name; + } + throw new Error(UNDEFINED_INJECT_ANNOTATION(className)); } return createTaggedDecorator( new Metadata(metadataKey, serviceIdentifier) diff --git a/test/annotation/inject.test.ts b/test/annotation/inject.test.ts index 8cb9f4855..2ca491c90 100644 --- a/test/annotation/inject.test.ts +++ b/test/annotation/inject.test.ts @@ -8,6 +8,7 @@ import { LazyServiceIdentifer } from "../../src/annotation/lazy_service_identifi import * as ERROR_MSGS from "../../src/constants/error_msgs"; import * as METADATA_KEY from "../../src/constants/metadata_keys"; import { interfaces } from "../../src/interfaces/interfaces"; +import { multiInject } from "../../src/inversify"; interface Katana {} interface Shuriken {} @@ -176,4 +177,23 @@ describe("@inject", () => { }); + it("should throw when applied inject decorator with undefined service identifier to a property", () => { + expect(() => { + //@ts-ignore + class WithUndefinedInject{ + @inject(undefined as any) + property:string + } + }).to.throw(`${ERROR_MSGS.UNDEFINED_INJECT_ANNOTATION("WithUndefinedInject")}`) + }); + + it("should throw when applied multiInject decorator with undefined service identifier to a constructor parameter", () => { + expect(() => { + //@ts-ignore + class WithUndefinedInject{ + constructor(@multiInject(undefined as any) readonly dependency:string[]){} + } + }).to.throw(`${ERROR_MSGS.UNDEFINED_INJECT_ANNOTATION("WithUndefinedInject")}`) + }); + }); From 3137069e7f5157226faf920eec2e641287c9f98e Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Mon, 10 May 2021 17:34:07 +0100 Subject: [PATCH 20/30] replace if condition --- src/annotation/inject_base.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/annotation/inject_base.ts b/src/annotation/inject_base.ts index d577aaaef..10c7f2ca5 100644 --- a/src/annotation/inject_base.ts +++ b/src/annotation/inject_base.ts @@ -8,12 +8,7 @@ export function injectBase(metadataKey:string){ return (target: Object, targetKey:string | symbol | undefined, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { if (serviceIdentifier === undefined) { - let className = ""; - if(typeof target === "function"){ - className = target.name; - }else{ - className = target.constructor.name; - } + const className = typeof target === "function" ? target.name : target.constructor.name; throw new Error(UNDEFINED_INJECT_ANNOTATION(className)); } return createTaggedDecorator( From 0fc9b095c2c0cd6e7e7e658a89cca66e39ad088b Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Tue, 11 May 2021 10:53:36 +0100 Subject: [PATCH 21/30] DecoratorTarget type and throw for annotating static properties --- src/annotation/decorator_utils.ts | 23 ++++++++++++++++++----- src/annotation/inject_base.ts | 4 ++-- test/annotation/decorator_utils.test.ts | 17 +++++++++++++++-- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 188492f07..1ea1f76bd 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -3,8 +3,17 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; import { getFirstArrayDuplicate } from "../utils/js"; +interface ConstructorFunction { + new(...args:any[]):unknown, + readonly prototype:unknown +} +interface Prototype { + constructor: Function; +} +export type DecoratorTarget = Prototype | ConstructorFunction; + function tagParameter( - annotationTarget: Object, + annotationTarget: DecoratorTarget, propertyName: string | symbol | undefined, parameterIndex: number, metadata: interfaces.MetadataOrMetadataArray @@ -12,14 +21,17 @@ function tagParameter( if(propertyName !== undefined) { throw new Error(ERROR_MSGS.INVALID_DECORATOR_OPERATION); } - _tagParameterOrProperty(METADATA_KEY.TAGGED, annotationTarget, parameterIndex.toString(), metadata); + _tagParameterOrProperty(METADATA_KEY.TAGGED, annotationTarget as Function, parameterIndex.toString(), metadata); } function tagProperty( - annotationTarget: Object, + annotationTarget: DecoratorTarget, propertyName: string | symbol, metadata: interfaces.MetadataOrMetadataArray ) { + if(annotationTarget.constructor === Function) { + throw new Error(ERROR_MSGS.INVALID_DECORATOR_OPERATION); + } _tagParameterOrProperty(METADATA_KEY.TAGGED_PROP, annotationTarget.constructor, propertyName, metadata); } @@ -39,7 +51,7 @@ function _ensureNoMetadataKeyDuplicates(metadata: interfaces.MetadataOrMetadataA function _tagParameterOrProperty( metadataKey: string, - annotationTarget: Object, + annotationTarget: Function, key: string | symbol, metadata: interfaces.MetadataOrMetadataArray, ) { @@ -73,7 +85,8 @@ function _tagParameterOrProperty( function createTaggedDecorator( metadata:interfaces.MetadataOrMetadataArray, ) { - return (target:Object, targetKey:string | symbol | undefined, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { + return (target:DecoratorTarget, + targetKey:string | symbol | undefined, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { if (typeof indexOrPropertyDescriptor === "number") { tagParameter(target, targetKey, indexOrPropertyDescriptor, metadata); } else { diff --git a/src/annotation/inject_base.ts b/src/annotation/inject_base.ts index 10c7f2ca5..79d46644d 100644 --- a/src/annotation/inject_base.ts +++ b/src/annotation/inject_base.ts @@ -1,11 +1,11 @@ import { UNDEFINED_INJECT_ANNOTATION } from "../constants/error_msgs"; import { Metadata } from "../planning/metadata"; -import { createTaggedDecorator } from "./decorator_utils"; +import { createTaggedDecorator, DecoratorTarget } from "./decorator_utils"; import { ServiceIdentifierOrFunc } from "./lazy_service_identifier"; export function injectBase(metadataKey:string){ return (serviceIdentifier: ServiceIdentifierOrFunc) => { - return (target: Object, + return (target: DecoratorTarget, targetKey:string | symbol | undefined, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { if (serviceIdentifier === undefined) { const className = typeof target === "function" ? target.name : target.constructor.name; diff --git a/test/annotation/decorator_utils.test.ts b/test/annotation/decorator_utils.test.ts index a51815d4f..9d74cc71a 100644 --- a/test/annotation/decorator_utils.test.ts +++ b/test/annotation/decorator_utils.test.ts @@ -27,7 +27,7 @@ describe("createTaggedDecorator", () => { const metadata = {key:"2",value:"2"}; const decorator = createTaggedDecorator(metadata); const spiedTagProperty = sandbox.spy(tagProperty); - decorator(Target,"PropertyName"); + decorator(Target.prototype,"PropertyName"); expect(spiedTagProperty.calledWithExactly(Target, "PropertyName", metadata)); }); @@ -80,7 +80,20 @@ describe("tagProperty", () => { it("should throw if multiple metadata with same key", () => { class Target {} expect( - () => tagProperty(Target,"Property", [{key:"Duplicate",value:"1"},{key:"Duplicate",value:"2"}]) + () => tagProperty(Target.prototype,"Property", [{key:"Duplicate",value:"1"},{key:"Duplicate",value:"2"}]) ).to.throw(`${ERROR_MSGS.DUPLICATED_METADATA} Duplicate`); }); + + it("should throw for static properties", () => { + class Target {} + + // does not throw + tagProperty(Target.prototype,"Property", {key:"key",value:"value"}) + + expect( + () => tagProperty(Target,"StaticProperty", {key:"key",value:"value"}) + ).to.throw(ERROR_MSGS.INVALID_DECORATOR_OPERATION); + + }); + }); From b7b77416bbd4129881b1e3f416e87175ee1457a8 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Tue, 11 May 2021 12:15:26 +0100 Subject: [PATCH 22/30] DecoratorTarget for js decorate --- src/annotation/decorator_utils.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 1ea1f76bd..08fac55de 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -104,19 +104,19 @@ function _param(paramIndex: number, decorator: ParameterDecorator) { } // Allows VanillaJS developers to use decorators: -// decorate(injectable("Foo", "Bar"), FooBar); +// decorate(injectable(), FooBar); // decorate(targetName("foo", "bar"), FooBar); // decorate(named("foo"), FooBar, 0); // decorate(tagged("bar"), FooBar, 1); function decorate( decorator: (ClassDecorator | ParameterDecorator | MethodDecorator), - target: any, - parameterIndex?: number | string): void { + target: DecoratorTarget, + parameterIndexOrProperty?: number | string): void { - if (typeof parameterIndex === "number") { - _decorate([_param(parameterIndex, decorator as ParameterDecorator)], target); - } else if (typeof parameterIndex === "string") { - Reflect.decorate([decorator as MethodDecorator], target, parameterIndex); + if (typeof parameterIndexOrProperty === "number") { + _decorate([_param(parameterIndexOrProperty, decorator as ParameterDecorator)], target); + } else if (typeof parameterIndexOrProperty === "string") { + Reflect.decorate([decorator as MethodDecorator], target, parameterIndexOrProperty); } else { _decorate([decorator as ClassDecorator], target); } From 9c39f67848eb7ce44c5b3d57adc30522158e3224 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Tue, 11 May 2021 12:16:12 +0100 Subject: [PATCH 23/30] add test for js inject for property --- test/inversify.test.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/inversify.test.ts b/test/inversify.test.ts index 386862d15..24bd296af 100644 --- a/test/inversify.test.ts +++ b/test/inversify.test.ts @@ -116,9 +116,16 @@ describe("InversifyJS", () => { const TYPES = { Katana: "Katana", Ninja: "Ninja", - Shuriken: "Shuriken" + Shuriken: "Shuriken", + Blowgun: "Blowgun" }; + class Blowgun { + public blow() { + return "poison!"; + } + } + class Katana { public hit() { return "cut!"; @@ -135,6 +142,7 @@ describe("InversifyJS", () => { public _katana: Katana; public _shuriken: Shuriken; + public _blowgun: Blowgun; public constructor(katana: Katana, shuriken: Shuriken) { this._katana = katana; @@ -142,23 +150,32 @@ describe("InversifyJS", () => { } public fight() { return this._katana.hit(); } public sneak() { return this._shuriken.throw(); } + public poisonDart() { return this._blowgun.blow();} + + public set blowgun(blowgun:Blowgun) { + this._blowgun = blowgun; + } } decorate(injectable(), Katana); decorate(injectable(), Shuriken); decorate(injectable(), Ninja); + decorate(injectable(), Blowgun); decorate(inject(TYPES.Katana), Ninja, 0); decorate(inject(TYPES.Shuriken), Ninja, 1); + decorate(inject(TYPES.Blowgun), Ninja.prototype, "blowgun"); const container = new Container(); container.bind(TYPES.Ninja).to(Ninja); container.bind(TYPES.Katana).to(Katana); container.bind(TYPES.Shuriken).to(Shuriken); + container.bind(TYPES.Blowgun).to(Blowgun); const ninja = container.get(TYPES.Ninja); expect(ninja.fight()).eql("cut!"); expect(ninja.sneak()).eql("hit!"); + expect(ninja.poisonDart()).eql("poison!"); }); From 2756407573f4972f30c38e3c1d38badfde1cd50c Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Tue, 11 May 2021 12:23:34 +0100 Subject: [PATCH 24/30] extract getSymbolDescription --- src/planning/target.ts | 3 ++- src/utils/serialization.ts | 7 ++++++- test/utils/serialization.test.ts | 10 +++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/planning/target.ts b/src/planning/target.ts index c0a88686c..39e1fbb04 100644 --- a/src/planning/target.ts +++ b/src/planning/target.ts @@ -1,6 +1,7 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; import { id } from "../utils/id"; +import { getSymbolDescription } from "../utils/serialization"; import { Metadata } from "./metadata"; import { QueryableString } from "./queryable_string"; @@ -24,7 +25,7 @@ class Target implements interfaces.Target { this.id = id(); this.type = type; this.serviceIdentifier = serviceIdentifier; - const queryableName = typeof identifier === 'symbol' ? identifier.toString().slice(7,-1) : identifier; + const queryableName = typeof identifier === 'symbol' ? getSymbolDescription(identifier): identifier; this.name = new QueryableString(queryableName || ""); this.identifier = identifier; this.metadata = new Array(); diff --git a/src/utils/serialization.ts b/src/utils/serialization.ts index 206c9f349..de51db0d5 100644 --- a/src/utils/serialization.ts +++ b/src/utils/serialization.ts @@ -134,10 +134,15 @@ function getFunctionName(v: any): string { } } +function getSymbolDescription(symbol:Symbol) { + return symbol.toString().slice(7,-1); +} + export { getFunctionName, getServiceIdentifierAsString, listRegisteredBindingsForServiceIdentifier, listMetadataForTarget, - circularDependencyToException + circularDependencyToException, + getSymbolDescription }; diff --git a/test/utils/serialization.test.ts b/test/utils/serialization.test.ts index 18179085b..e7de8865f 100644 --- a/test/utils/serialization.test.ts +++ b/test/utils/serialization.test.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { TargetTypeEnum } from "../../src/constants/literal_types"; import { Target } from "../../src/planning/target"; -import { getFunctionName, listMetadataForTarget } from "../../src/utils/serialization"; +import { getFunctionName, getSymbolDescription, listMetadataForTarget } from "../../src/utils/serialization"; describe("Serialization", () => { @@ -32,4 +32,12 @@ describe("Serialization", () => { expect(list).to.eql(` ${serviceIdentifier}`); }); + it("Should extract symbol description", () => { + const symbolWithDescription = Symbol("description"); + expect(getSymbolDescription(symbolWithDescription)).to.equal("description"); + + const symbolWithoutDescription = Symbol(); + expect(getSymbolDescription(symbolWithoutDescription)).to.equal(""); + }); + }); From b325f596fc087ef9805aaaa1ce596c15467ca7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Pintos=20L=C3=B3pez?= Date: Wed, 12 May 2021 16:20:26 +0200 Subject: [PATCH 25/30] refactor: simplify types --- src/annotation/decorator_utils.ts | 30 ++++++++++++------------------ src/annotation/inject_base.ts | 14 +++++++++----- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 08fac55de..73bf2107f 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -3,17 +3,8 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; import { getFirstArrayDuplicate } from "../utils/js"; -interface ConstructorFunction { - new(...args:any[]):unknown, - readonly prototype:unknown -} -interface Prototype { - constructor: Function; -} -export type DecoratorTarget = Prototype | ConstructorFunction; - function tagParameter( - annotationTarget: DecoratorTarget, + annotationTarget: object, propertyName: string | symbol | undefined, parameterIndex: number, metadata: interfaces.MetadataOrMetadataArray @@ -21,11 +12,11 @@ function tagParameter( if(propertyName !== undefined) { throw new Error(ERROR_MSGS.INVALID_DECORATOR_OPERATION); } - _tagParameterOrProperty(METADATA_KEY.TAGGED, annotationTarget as Function, parameterIndex.toString(), metadata); + _tagParameterOrProperty(METADATA_KEY.TAGGED, annotationTarget, parameterIndex.toString(), metadata); } function tagProperty( - annotationTarget: DecoratorTarget, + annotationTarget: object, propertyName: string | symbol, metadata: interfaces.MetadataOrMetadataArray ) { @@ -49,9 +40,9 @@ function _ensureNoMetadataKeyDuplicates(metadata: interfaces.MetadataOrMetadataA return metadatas; } -function _tagParameterOrProperty( +function _tagParameterOrProperty( metadataKey: string, - annotationTarget: Function, + annotationTarget: object, key: string | symbol, metadata: interfaces.MetadataOrMetadataArray, ) { @@ -83,10 +74,13 @@ function _tagParameterOrProperty( } function createTaggedDecorator( - metadata:interfaces.MetadataOrMetadataArray, + metadata: interfaces.MetadataOrMetadataArray, ) { - return (target:DecoratorTarget, - targetKey:string | symbol | undefined, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { + return ( + target: object, + targetKey?: string | symbol, + indexOrPropertyDescriptor?: number | TypedPropertyDescriptor, + ) => { if (typeof indexOrPropertyDescriptor === "number") { tagParameter(target, targetKey, indexOrPropertyDescriptor, metadata); } else { @@ -110,7 +104,7 @@ function _param(paramIndex: number, decorator: ParameterDecorator) { // decorate(tagged("bar"), FooBar, 1); function decorate( decorator: (ClassDecorator | ParameterDecorator | MethodDecorator), - target: DecoratorTarget, + target: object, parameterIndexOrProperty?: number | string): void { if (typeof parameterIndexOrProperty === "number") { diff --git a/src/annotation/inject_base.ts b/src/annotation/inject_base.ts index 79d46644d..dd5ec762c 100644 --- a/src/annotation/inject_base.ts +++ b/src/annotation/inject_base.ts @@ -1,19 +1,23 @@ import { UNDEFINED_INJECT_ANNOTATION } from "../constants/error_msgs"; import { Metadata } from "../planning/metadata"; -import { createTaggedDecorator, DecoratorTarget } from "./decorator_utils"; +import { createTaggedDecorator } from "./decorator_utils"; import { ServiceIdentifierOrFunc } from "./lazy_service_identifier"; -export function injectBase(metadataKey:string){ +export function injectBase(metadataKey: string) { return (serviceIdentifier: ServiceIdentifierOrFunc) => { - return (target: DecoratorTarget, - targetKey:string | symbol | undefined, indexOrPropertyDescriptor?:number | TypedPropertyDescriptor) => { + return ( + target: object, + targetKey?: string | symbol, + indexOrPropertyDescriptor?: number | TypedPropertyDescriptor, + ) => { if (serviceIdentifier === undefined) { const className = typeof target === "function" ? target.name : target.constructor.name; + throw new Error(UNDEFINED_INJECT_ANNOTATION(className)); } return createTaggedDecorator( new Metadata(metadataKey, serviceIdentifier) - )(target, targetKey,indexOrPropertyDescriptor); + )(target, targetKey, indexOrPropertyDescriptor); }; } } From 39f3913fc80a7c8eba23ca6b64fefa7456a5d9f8 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Wed, 12 May 2021 21:25:17 +0100 Subject: [PATCH 26/30] _tagParameterOrProperty on constructor function --- src/annotation/decorator_utils.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 73bf2107f..497ee3f9f 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -3,16 +3,20 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; import { getFirstArrayDuplicate } from "../utils/js"; +function _throwIfMethodParameter(parameterName:string | symbol | undefined):void { + if(parameterName !== undefined) { + throw new Error(ERROR_MSGS.INVALID_DECORATOR_OPERATION); + } +} + function tagParameter( annotationTarget: object, - propertyName: string | symbol | undefined, + parameterName: string | symbol | undefined, parameterIndex: number, metadata: interfaces.MetadataOrMetadataArray ) { - if(propertyName !== undefined) { - throw new Error(ERROR_MSGS.INVALID_DECORATOR_OPERATION); - } - _tagParameterOrProperty(METADATA_KEY.TAGGED, annotationTarget, parameterIndex.toString(), metadata); + _throwIfMethodParameter(parameterName); + _tagParameterOrProperty(METADATA_KEY.TAGGED, annotationTarget as Function, parameterIndex.toString(), metadata); } function tagProperty( @@ -42,7 +46,7 @@ function _ensureNoMetadataKeyDuplicates(metadata: interfaces.MetadataOrMetadataA function _tagParameterOrProperty( metadataKey: string, - annotationTarget: object, + annotationTarget: Function, key: string | symbol, metadata: interfaces.MetadataOrMetadataArray, ) { From 5a4462edfdabe1d33f7e38a1a9598a649a527bb4 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Thu, 13 May 2021 09:27:10 +0100 Subject: [PATCH 27/30] DecoratorTarget type --- src/annotation/decorator_utils.ts | 24 ++++++++++++++++++------ src/annotation/inject_base.ts | 4 ++-- src/annotation/target_name.ts | 4 ++-- src/annotation/unmanaged.ts | 4 ++-- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 497ee3f9f..3586ccd0c 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -3,28 +3,40 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { interfaces } from "../interfaces/interfaces"; import { getFirstArrayDuplicate } from "../utils/js"; +function targetIsConstructorFunction(target:DecoratorTarget): target is ConstructorFunction{ + return (target as any).prototype !== undefined; +} + +interface ConstructorFunction{ + new (...args:unknown[]): T, + prototype:T +} + +export type DecoratorTarget = ConstructorFunction | T + function _throwIfMethodParameter(parameterName:string | symbol | undefined):void { if(parameterName !== undefined) { throw new Error(ERROR_MSGS.INVALID_DECORATOR_OPERATION); } } + function tagParameter( - annotationTarget: object, + annotationTarget: DecoratorTarget, parameterName: string | symbol | undefined, parameterIndex: number, metadata: interfaces.MetadataOrMetadataArray ) { _throwIfMethodParameter(parameterName); - _tagParameterOrProperty(METADATA_KEY.TAGGED, annotationTarget as Function, parameterIndex.toString(), metadata); + _tagParameterOrProperty(METADATA_KEY.TAGGED, annotationTarget as ConstructorFunction, parameterIndex.toString(), metadata); } function tagProperty( - annotationTarget: object, + annotationTarget: DecoratorTarget, propertyName: string | symbol, metadata: interfaces.MetadataOrMetadataArray ) { - if(annotationTarget.constructor === Function) { + if(targetIsConstructorFunction(annotationTarget)) { throw new Error(ERROR_MSGS.INVALID_DECORATOR_OPERATION); } _tagParameterOrProperty(METADATA_KEY.TAGGED_PROP, annotationTarget.constructor, propertyName, metadata); @@ -44,7 +56,7 @@ function _ensureNoMetadataKeyDuplicates(metadata: interfaces.MetadataOrMetadataA return metadatas; } -function _tagParameterOrProperty( +function _tagParameterOrProperty( metadataKey: string, annotationTarget: Function, key: string | symbol, @@ -81,7 +93,7 @@ function createTaggedDecorator( metadata: interfaces.MetadataOrMetadataArray, ) { return ( - target: object, + target: DecoratorTarget, targetKey?: string | symbol, indexOrPropertyDescriptor?: number | TypedPropertyDescriptor, ) => { diff --git a/src/annotation/inject_base.ts b/src/annotation/inject_base.ts index dd5ec762c..0f2456f11 100644 --- a/src/annotation/inject_base.ts +++ b/src/annotation/inject_base.ts @@ -1,12 +1,12 @@ import { UNDEFINED_INJECT_ANNOTATION } from "../constants/error_msgs"; import { Metadata } from "../planning/metadata"; -import { createTaggedDecorator } from "./decorator_utils"; +import { createTaggedDecorator, DecoratorTarget } from "./decorator_utils"; import { ServiceIdentifierOrFunc } from "./lazy_service_identifier"; export function injectBase(metadataKey: string) { return (serviceIdentifier: ServiceIdentifierOrFunc) => { return ( - target: object, + target: DecoratorTarget, targetKey?: string | symbol, indexOrPropertyDescriptor?: number | TypedPropertyDescriptor, ) => { diff --git a/src/annotation/target_name.ts b/src/annotation/target_name.ts index 032a1a3f9..3e36bc3a0 100644 --- a/src/annotation/target_name.ts +++ b/src/annotation/target_name.ts @@ -1,9 +1,9 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { Metadata } from "../planning/metadata"; -import { tagParameter } from "./decorator_utils"; +import { tagParameter, DecoratorTarget } from "./decorator_utils"; function targetName(name: string) { - return function(target: any, targetKey: string, index: number) { + return function(target: DecoratorTarget, targetKey: string, index: number) { const metadata = new Metadata(METADATA_KEY.NAME_TAG, name); tagParameter(target, targetKey, index, metadata); }; diff --git a/src/annotation/unmanaged.ts b/src/annotation/unmanaged.ts index 798484568..becb37854 100644 --- a/src/annotation/unmanaged.ts +++ b/src/annotation/unmanaged.ts @@ -1,9 +1,9 @@ import * as METADATA_KEY from "../constants/metadata_keys"; import { Metadata } from "../planning/metadata"; -import { tagParameter } from "./decorator_utils"; +import { tagParameter, DecoratorTarget } from "./decorator_utils"; function unmanaged() { - return function(target: any, targetKey: string, index: number) { + return function(target: DecoratorTarget, targetKey: string, index: number) { const metadata = new Metadata(METADATA_KEY.UNMANAGED_TAG, true); tagParameter(target, targetKey, index, metadata); }; From b266fe159f2f2bc5dbc60c8a1f8b6e97f04bbdc3 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Thu, 13 May 2021 10:44:22 +0100 Subject: [PATCH 28/30] type predicate cast to type testing for Co-authored-by: notaphplover --- src/annotation/decorator_utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 3586ccd0c..0e8e68e32 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -4,7 +4,7 @@ import { interfaces } from "../interfaces/interfaces"; import { getFirstArrayDuplicate } from "../utils/js"; function targetIsConstructorFunction(target:DecoratorTarget): target is ConstructorFunction{ - return (target as any).prototype !== undefined; + return (target as ConstructorFunction).prototype !== undefined; } interface ConstructorFunction{ From bf99ac10b4ec8e1f472db8aac8112797366f5f92 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Thu, 13 May 2021 13:30:56 +0100 Subject: [PATCH 29/30] Prototype type - map properties to include undefined --- src/annotation/decorator_utils.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/annotation/decorator_utils.ts b/src/annotation/decorator_utils.ts index 0e8e68e32..95856fea7 100644 --- a/src/annotation/decorator_utils.ts +++ b/src/annotation/decorator_utils.ts @@ -7,12 +7,19 @@ function targetIsConstructorFunction(target:DecoratorTarget): tar return (target as ConstructorFunction).prototype !== undefined; } +type Prototype = { + [Property in keyof T ]: + T[Property] extends Function? + T[Property] : + T[Property] | undefined +} & {constructor:Function} + interface ConstructorFunction{ new (...args:unknown[]): T, - prototype:T + prototype:Prototype } -export type DecoratorTarget = ConstructorFunction | T +export type DecoratorTarget = ConstructorFunction | Prototype function _throwIfMethodParameter(parameterName:string | symbol | undefined):void { if(parameterName !== undefined) { From a2fac9bf92cbe48c3b0f9ba1ca02137b9148ed1b Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Fri, 14 May 2021 11:52:18 +0100 Subject: [PATCH 30/30] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6191525a..91e41a865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- createTaggedDecorator #1343 - Async bindings #1132 - Async binding resolution (getAllAsync, getAllNamedAsync, getAllTaggedAsync, getAsync, getNamedAsync, getTaggedAsync, rebindAsync, unbindAsync, unbindAllAsync, unloadAsync) #1132 - Global onActivation / onDeactivation #1132 @@ -17,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - @postConstruct can target an asyncronous function #1132 ### Fixed +- only inject decorator can be applied to setters #1342 - Container.resolve should resolve in that container #1338 ## [5.1.1] - 2021-04-25 -Fix pre-publish for build artifacts