Skip to content

Commit

Permalink
Merge pull request #65 from inversify/fix
Browse files Browse the repository at this point in the history
Implements inversify/InversifyJS/issues/674#issuecomment-349302658
  • Loading branch information
remojansen authored Dec 5, 2017
2 parents cafc953 + 41e3817 commit 35cfdc7
Show file tree
Hide file tree
Showing 12 changed files with 146 additions and 28 deletions.
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,55 @@ class Shuriken implements Weapon {
}
```

### Using @provideFluent multiple times

If you try to apply `@provideFluent` multiple times:

```ts
let container = new Container();
let provideFluent = fluentProvide(container);

const provideSingleton = (identifier: any) => {
return provideFluent(identifier)
.inSingletonScope()
.done();
};

function shouldThrow() {
@provideSingleton("Ninja")
@provideSingleton("SilentNinja")
class Ninja {}
return Ninja;
}
```

The library will throw an exception:

> Cannot apply @provideFluent decorator multiple times but is has been used multiple times in Ninja Please use @done(true) if you are trying to declare multiple bindings!
We throw an exception to ensure that you are are not trying to apply `@fluentProvide` multiple times by mistake.

You can overcome this by passing the `force` argument to `done()`:

```ts
let container = new Container();
let provideFluent = fluentProvide(container);

const provideSingleton = (identifier: any) => {
return provideFluent(identifier)
.inSingletonScope()
.done(true); // IMPORTANT!
};

function shouldThrow() {
@provideSingleton("Ninja")
@provideSingleton("SilentNinja")
class Ninja {}
return Ninja;
}
```


## The auto provide utility
This library includes a small utility apply to add the default `@provide` decorator to all
the public properties of a module:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "inversify-binding-decorators",
"version": "3.1.0",
"version": "3.2.0",
"description": "An utility that allows developers to declare InversifyJS bindings using ES2016 decorators",
"main": "lib/index.js",
"jsnext:main": "es/index.js",
Expand Down
7 changes: 5 additions & 2 deletions src/decorator/provide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ function provide(container: interfaces.Container) {
try {
decorate(injectable(), target);
} catch (e) {
throw new Error(`${e.message} ` +
"Please use @provide(ID, true) if you are trying to declare multiple bindings!");
throw new Error(
"Cannot apply @provide decorator multiple times but is has been used " +
`multiple times in ${target.name} ` +
"Please use @provide(ID, true) if you are trying to declare multiple bindings!"
);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { interfaces as inversifyInterfaces } from "inversify";
namespace interfaces {

export interface ProvideDoneSyntax {
done(): (target: any) => any;
done(force?: boolean): (target: any) => any;
}

export interface ProvideInSyntax<T> extends ProvideDoneSyntax {
Expand Down
30 changes: 25 additions & 5 deletions src/syntax/provide_done_syntax.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import interfaces from "../interfaces/interfaces";
import { decorate, injectable } from "inversify";
import { interfaces as inversifyInterfaces } from "inversify";
import { interfaces as inversifyInterfaces, METADATA_KEY } from "inversify";

class ProvideDoneSyntax<T> implements interfaces.ProvideDoneSyntax {

Expand All @@ -10,10 +10,30 @@ class ProvideDoneSyntax<T> implements interfaces.ProvideDoneSyntax {
this._binding = binding;
}

public done() {
return (target: any) => {
decorate(injectable(), target);
this._binding.implementationType = target;
public done(force?: boolean) {
const that = this;
return function (target: any) {

const isAlreadyDecorated = Reflect.hasOwnMetadata(METADATA_KEY.PARAM_TYPES, target);
const redecorateWithInject = force === true;

if (redecorateWithInject === true && isAlreadyDecorated === false) {
decorate(injectable(), target);
} else if (redecorateWithInject === true && isAlreadyDecorated === true) {
// Do nothing
} else {
try {
decorate(injectable(), target);
} catch (e) {
throw new Error(
"Cannot apply @provideFluent decorator multiple times but is has been used " +
`multiple times in ${target.name} ` +
"Please use @done(true) if you are trying to declare multiple bindings!"
);
}
}

that._binding.implementationType = target;
return target;
};
}
Expand Down
4 changes: 2 additions & 2 deletions src/syntax/provide_in_syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ class ProvideInSyntax<T> implements interfaces.ProvideInSyntax<T> {
return new ProvideWhenOnSyntax(provideWhenSyntax, provideOnSyntax);
}

public done() {
public done(force?: boolean) {
let binding: inversifyInterfaces.Binding<T> = (<any>this._bindingInSyntax)._binding;
let provideDoneSyntax = new ProvideDoneSyntax<T>(binding);
return provideDoneSyntax.done();
return provideDoneSyntax.done(force);
}

}
Expand Down
4 changes: 2 additions & 2 deletions src/syntax/provide_in_when_on_syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ class ProvideInWhenOnSyntax<T> implements interfaces.ProvideInWhenOnSyntax<T> {
return this._provideInSyntax.inTransientScope();
}

public done() {
return this._provideInSyntax.done();
public done(force?: boolean) {
return this._provideInSyntax.done(force);
}

}
Expand Down
4 changes: 2 additions & 2 deletions src/syntax/provide_on_syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class ProvideOnSyntax<T> implements interfaces.ProvideOnSyntax<T> {
return new ProvideWhenSyntax(bindingWhenSyntax, this._provideDoneSyntax);
}

public done() {
return this._provideDoneSyntax.done();
public done(force?: boolean) {
return this._provideDoneSyntax.done(force);
}

}
Expand Down
4 changes: 2 additions & 2 deletions src/syntax/provide_when_on_syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ class ProvideWhenOnSyntax<T> implements interfaces.ProvideWhenOnSyntax<T> {
return this._provideOnSyntax.onActivation(fn);
}

public done() {
return this._provideWhenSyntax.done();
public done(force?: boolean) {
return this._provideWhenSyntax.done(force);
}

}
Expand Down
4 changes: 2 additions & 2 deletions src/syntax/provide_when_syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ class ProvideWhenSyntax<T> implements interfaces.ProvideWhenSyntax<T> {
return new ProvideOnSyntax<T>(bindingOnSyntax, this._provideDoneSyntax);
}

public done() {
return this._provideDoneSyntax.done();
public done(force?: boolean) {
return this._provideDoneSyntax.done(force);
}

}
Expand Down
54 changes: 49 additions & 5 deletions test/decorator/fluent_provide.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@ import "reflect-metadata";

describe("fluentProvide", () => {

let container = new Container();

it("Should return a configurable decorator", () => {

let container = new Container();
let provide = fluentProvide(container);
expect(typeof provide).eqls("function");

});

it("Should return an instance of ProvideInWhenOnSyntax once it is configured", () => {

let container = new Container();
let provide = fluentProvide(container);
let provideInWhenOnSyntax = provide("SomeTypeID");
expect((<any>provideInWhenOnSyntax)._provideInSyntax).not.to.be.eqls(null);
Expand All @@ -24,6 +21,53 @@ describe("fluentProvide", () => {
expect((<any>provideInWhenOnSyntax)._provideInSyntax).not.to.be.eqls(undefined);
expect((<any>provideInWhenOnSyntax)._provideWhenSyntax).not.to.be.eqls(undefined);
expect((<any>provideInWhenOnSyntax)._provideOnSyntax).not.to.be.eqls(undefined);
});

it("Should throw if @fluentProvide is applied more than once without force flag", () => {

let container = new Container();
let provideFluent = fluentProvide(container);

const provideSingleton = (identifier: any) => {
return provideFluent(identifier)
.inSingletonScope()
.done();
};

function shouldThrow() {
@provideSingleton("Ninja")
@provideSingleton("SilentNinja")
class Ninja {}
return Ninja;
}

expect(shouldThrow).to.throw(
"Cannot apply @provideFluent decorator multiple times but is has been used " +
"multiple times in Ninja " +
"Please use @done(true) if you are trying to declare multiple bindings!"
);

});

it("Should work if @provide is applied more than once with force flag", () => {

let container = new Container();
let provideFluent = fluentProvide(container);

const provideSingleton = (identifier: any) => {
return provideFluent(identifier)
.inSingletonScope()
.done(true); // IMPORTANT!
};

function shouldThrow() {
@provideSingleton("Ninja")
@provideSingleton("SilentNinja")
class Ninja {}
return Ninja;
}

expect(shouldThrow).not.to.throw();

});

Expand Down
10 changes: 6 additions & 4 deletions test/decorator/provide.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe("provide", () => {

});

it("Should throw if applied more than once without force flag", () => {
it("Should throw if @provide is applied more than once without force flag", () => {

const myContainer = new Container();
const provide = _provide(myContainer);
Expand All @@ -42,12 +42,14 @@ describe("provide", () => {
}

expect(shouldThrow).to.throw(
"Cannot apply @injectable decorator multiple times. " +
"Please use @provide(ID, true) if you are trying to declare multiple bindings!");
"Cannot apply @provide decorator multiple times but is has been used " +
"multiple times in Ninja " +
"Please use @provide(ID, true) if you are trying to declare multiple bindings!"
);

});

it("Should work if applied more than once with force flag", () => {
it("Should work if @provide is applied more than once with force flag", () => {

const myContainer = new Container();
const provide = _provide(myContainer);
Expand Down

0 comments on commit 35cfdc7

Please sign in to comment.