Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve annotations #1343

Merged
merged 37 commits into from
May 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7b062e2
Merge branch 'master' of https://github.com/inversify/InversifyJS
tonyhallett Aug 24, 2019
2cf47bf
Merge branch 'master' of https://github.com/inversify/InversifyJS
tonyhallett Apr 21, 2021
36310b7
Merge branch 'master' of https://github.com/inversify/InversifyJS
tonyhallett May 1, 2021
57ad651
Merge branch 'master' of https://github.com/inversify/InversifyJS
tonyhallett May 3, 2021
9ec5940
Merge branch 'master' of https://github.com/inversify/InversifyJS
tonyhallett May 7, 2021
ce54f28
Merge branch 'master' of https://github.com/inversify/InversifyJS
tonyhallett May 8, 2021
2073070
refactor to helper createTaggedDecorator which supports all decorator…
tonyhallett May 8, 2021
035d68c
make createTaggedDecorator permit multiple metadata
tonyhallett May 8, 2021
2e1c863
refactor tagParameter and tagProperty
tonyhallett May 8, 2021
dd92dc7
extract _ensureNoMetadataKeyDuplicates
tonyhallett May 8, 2021
08e46e6
change parameter name to indexOrPropertyDescriptor
tonyhallett May 8, 2021
ff16c9a
grammar correct historic test names
tonyhallett May 8, 2021
17a0fa7
use sinon sandbox for spying
tonyhallett May 8, 2021
3fcb065
review
tonyhallett May 9, 2021
2e5a011
wiki
tonyhallett May 9, 2021
6da7ac6
improve typing
tonyhallett May 9, 2021
a0eede0
improve decorator typing
tonyhallett May 9, 2021
c9487b8
throw error for decorated property missing inject / multiInject decor…
tonyhallett May 9, 2021
44c43f0
refactor code climate
tonyhallett May 9, 2021
2e4119f
permit injection for symbol property key
tonyhallett May 9, 2021
ee81df3
move symbol tests to container
tonyhallett May 9, 2021
5703cdf
remove callback from createTaggedDecorator. minor type changes.
tonyhallett May 10, 2021
9c75702
permit lazyserviceidentifier for multiinject
tonyhallett May 10, 2021
7124f67
correct decorator typing
tonyhallett May 10, 2021
0f565da
type target to Object
tonyhallett May 10, 2021
3137069
replace if condition
tonyhallett May 10, 2021
0fc9b09
DecoratorTarget type and throw for annotating static properties
tonyhallett May 11, 2021
b7b7741
DecoratorTarget for js decorate
tonyhallett May 11, 2021
9c39f67
add test for js inject for property
tonyhallett May 11, 2021
2756407
extract getSymbolDescription
tonyhallett May 11, 2021
b325f59
refactor: simplify types
notaphplover May 12, 2021
39f3913
_tagParameterOrProperty on constructor function
tonyhallett May 12, 2021
5a4462e
DecoratorTarget type
tonyhallett May 13, 2021
b266fe1
type predicate cast to type testing for
tonyhallett May 13, 2021
bf99ac1
Prototype type - map properties to include undefined
tonyhallett May 13, 2021
03e7bf9
Merge branch 'master' into improve-annotations
tonyhallett May 14, 2021
a2fac9b
update changelog
tonyhallett May 14, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
123 changes: 86 additions & 37 deletions src/annotation/decorator_utils.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,117 @@
import * as ERROR_MSGS from "../constants/error_msgs";
import * as METADATA_KEY from "../constants/metadata_keys";
import { interfaces } from "../interfaces/interfaces";
import { getFirstArrayDuplicate } from "../utils/js";

function targetIsConstructorFunction<T = Object>(target:DecoratorTarget<T>): target is ConstructorFunction<T>{
tonyhallett marked this conversation as resolved.
Show resolved Hide resolved
return (target as ConstructorFunction<T>).prototype !== undefined;
}

type Prototype<T> = {
[Property in keyof T ]:
T[Property] extends Function?
T[Property] :
T[Property] | undefined
} & {constructor:Function}

interface ConstructorFunction<T = Object>{
new (...args:unknown[]): T,
prototype:Prototype<T>
}

export type DecoratorTarget<T = Object> = ConstructorFunction<T> | Prototype<T>

function _throwIfMethodParameter(parameterName:string | symbol | undefined):void {
if(parameterName !== undefined) {
throw new Error(ERROR_MSGS.INVALID_DECORATOR_OPERATION);
}
}


function tagParameter(
annotationTarget: any,
propertyName: string,
annotationTarget: DecoratorTarget,
parameterName: string | symbol | undefined,
parameterIndex: number,
metadata: interfaces.Metadata
metadata: interfaces.MetadataOrMetadataArray
) {
const metadataKey = METADATA_KEY.TAGGED;
_tagParameterOrProperty(metadataKey, annotationTarget, propertyName, metadata, parameterIndex);
_throwIfMethodParameter(parameterName);
_tagParameterOrProperty(METADATA_KEY.TAGGED, annotationTarget as ConstructorFunction, parameterIndex.toString(), metadata);
}

function tagProperty(
annotationTarget: any,
propertyName: string,
metadata: interfaces.Metadata
annotationTarget: DecoratorTarget,
propertyName: string | symbol,
metadata: interfaces.MetadataOrMetadataArray
) {
const metadataKey = METADATA_KEY.TAGGED_PROP;
_tagParameterOrProperty(metadataKey, annotationTarget.constructor, propertyName, metadata);
if(targetIsConstructorFunction(annotationTarget)) {
throw new Error(ERROR_MSGS.INVALID_DECORATOR_OPERATION);
}
_tagParameterOrProperty(METADATA_KEY.TAGGED_PROP, annotationTarget.constructor, propertyName, metadata);
}

function _ensureNoMetadataKeyDuplicates(metadata: interfaces.MetadataOrMetadataArray):interfaces.Metadata[]{
let metadatas: interfaces.Metadata[] = [];
if(Array.isArray(metadata)){
metadatas = metadata;
const duplicate = getFirstArrayDuplicate(metadatas.map(md => md.key));
if(duplicate !== undefined) {
throw new Error(`${ERROR_MSGS.DUPLICATED_METADATA} ${duplicate.toString()}`);
}
}else{
metadatas = [metadata];
}
return metadatas;
}

function _tagParameterOrProperty(
metadataKey: string,
annotationTarget: any,
propertyName: string,
metadata: interfaces.Metadata,
parameterIndex?: number
annotationTarget: Function,
key: string | symbol,
metadata: interfaces.MetadataOrMetadataArray,
) {
const metadatas: interfaces.Metadata[] = _ensureNoMetadataKeyDuplicates(metadata);

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);
}

let paramsOrPropertiesMetadata:Record<string | symbol, interfaces.Metadata[] | undefined> = {};
// 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 as any];

if (!Array.isArray(paramOrPropertyMetadata)) {
if (paramOrPropertyMetadata === undefined) {
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);
paramsOrPropertiesMetadata[key] = paramOrPropertyMetadata;
paramOrPropertyMetadata.push(...metadatas);
paramsOrPropertiesMetadata[key as any] = paramOrPropertyMetadata;
Reflect.defineMetadata(metadataKey, paramsOrPropertiesMetadata, annotationTarget);

}

function createTaggedDecorator(
metadata: interfaces.MetadataOrMetadataArray,
) {
return (
target: DecoratorTarget,
targetKey?: string | symbol,
indexOrPropertyDescriptor?: number | TypedPropertyDescriptor<unknown>,
) => {
if (typeof indexOrPropertyDescriptor === "number") {
tagParameter(target, targetKey, indexOrPropertyDescriptor, metadata);
} else {
tagProperty(target, targetKey as string | symbol, metadata);
}
};
}

function _decorate(decorators: any[], target: any): void {
Reflect.decorate(decorators, target);
}
Expand All @@ -72,22 +121,22 @@ 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(
tonyhallett marked this conversation as resolved.
Show resolved Hide resolved
decorator: (ClassDecorator | ParameterDecorator | MethodDecorator),
target: any,
parameterIndex?: number | string): void {
target: object,
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);
}
}

export { decorate, tagParameter, tagProperty };
export { decorate, tagParameter, tagProperty, createTaggedDecorator };
35 changes: 2 additions & 33 deletions src/annotation/inject.ts
Original file line number Diff line number Diff line change
@@ -1,37 +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 { tagParameter, tagProperty } from "./decorator_utils";
import { injectBase } from "./inject_base";

export type ServiceIdentifierOrFunc = interfaces.ServiceIdentifier<any> | LazyServiceIdentifer;

export class LazyServiceIdentifer<T = any> {
private _cb: () => interfaces.ServiceIdentifier<T>;
public constructor(cb: () => interfaces.ServiceIdentifier<T>) {
this._cb = cb;
}

public unwrap() {
return this._cb();
}
}

function inject(serviceIdentifier: ServiceIdentifierOrFunc) {
return function(target: any, targetKey: string, index?: number | PropertyDescriptor): void {
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);
}

};
}
const inject = injectBase(METADATA_KEY.INJECT_TAG);

export { inject };
23 changes: 23 additions & 0 deletions src/annotation/inject_base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { UNDEFINED_INJECT_ANNOTATION } from "../constants/error_msgs";
import { Metadata } from "../planning/metadata";
import { createTaggedDecorator, DecoratorTarget } from "./decorator_utils";
import { ServiceIdentifierOrFunc } from "./lazy_service_identifier";

export function injectBase(metadataKey: string) {
return (serviceIdentifier: ServiceIdentifierOrFunc) => {
return (
target: DecoratorTarget,
targetKey?: string | symbol,
indexOrPropertyDescriptor?: number | TypedPropertyDescriptor<unknown>,
) => {
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);
};
}
}
14 changes: 14 additions & 0 deletions src/annotation/lazy_service_identifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { interfaces } from "../interfaces/interfaces";

export type ServiceIdentifierOrFunc = interfaces.ServiceIdentifier<any> | LazyServiceIdentifer;

export class LazyServiceIdentifer<T = any> {
private _cb: () => interfaces.ServiceIdentifier<T>;
public constructor(cb: () => interfaces.ServiceIdentifier<T>) {
this._cb = cb;
}

public unwrap() {
return this._cb();
}
}
18 changes: 2 additions & 16 deletions src/annotation/multi_inject.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
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 { injectBase } from "./inject_base";

function multiInject(serviceIdentifier: interfaces.ServiceIdentifier<any>) {
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);
}

};
}
const multiInject = injectBase(METADATA_KEY.MULTI_INJECT_TAG);

export { multiInject };
11 changes: 2 additions & 9 deletions src/annotation/named.ts
Original file line number Diff line number Diff line change
@@ -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 } 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 };
14 changes: 2 additions & 12 deletions src/annotation/optional.ts
Original file line number Diff line number Diff line change
@@ -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 };
11 changes: 2 additions & 9 deletions src/annotation/tagged.ts
Original file line number Diff line number Diff line change
@@ -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 };
4 changes: 2 additions & 2 deletions src/annotation/target_name.ts
Original file line number Diff line number Diff line change
@@ -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);
};
Expand Down
4 changes: 2 additions & 2 deletions src/annotation/unmanaged.ts
Original file line number Diff line number Diff line change
@@ -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);
};
Expand Down
5 changes: 2 additions & 3 deletions src/interfaces/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,7 @@ namespace interfaces {
setCurrentRequest(request: Request): void;
}

export interface ReflectResult {
notaphplover marked this conversation as resolved.
Show resolved Hide resolved
[key: string]: Metadata[];
}
export type MetadataOrMetadataArray = Metadata | Metadata[];

export interface Metadata {
key: string | number | symbol;
Expand Down Expand Up @@ -160,6 +158,7 @@ namespace interfaces {
serviceIdentifier: ServiceIdentifier<any>;
type: TargetType;
name: QueryableString;
identifier: string | symbol;
metadata: Metadata[];
getNamedTag(): interfaces.Metadata | null;
getCustomTags(): interfaces.Metadata[] | null;
Expand Down
Loading