Skip to content

Commit

Permalink
Improve error messages information (microsoft#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
emilioastarita committed Jan 29, 2020
1 parent 4f63bb3 commit 6fbd5e6
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 23 deletions.
3 changes: 1 addition & 2 deletions src/__tests__/auto-injectable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,8 @@ test("@autoInjectable throws a clear error if a dependency can't be resolved.",
class Foo {
constructor(public myBar?: Bar) {}
}

expect(() => new Foo()).toThrow(
/Cannot inject the dependency myBar of Foo constructor. Error: TypeInfo/
/Cannot inject the dependency "myBar" at position #0 of "Foo" constructor\. Reason:\s+TypeInfo/
);
});

Expand Down
42 changes: 42 additions & 0 deletions src/__tests__/errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {instance as globalContainer} from "../dependency-container";
import {inject, injectable} from "../decorators";

afterEach(() => {
globalContainer.reset();
});

test("Error message composition", () => {
class Ok {}

@injectable()
class C {
constructor(public s: any) {}
}

@injectable()
class B {
constructor(public c: C) {}
}

@injectable()
class A {
constructor(public d: Ok, public b: B) {}
}
expect(() => {
globalContainer.resolve(A);
}).toThrow(
/Cannot inject the dependency "b" at position #1 of "A" constructor. Reason:\s+Cannot inject the dependency "c" at position #0 of "B" constructor. Reason:\s+Cannot inject the dependency "s" at position #0 of "C" constructor. Reason:\s+TypeInfo not known for Object/
);
});

test("Param position", () => {
@injectable()
class A {
constructor(@inject("missing") public j: any) {}
}
expect(() => {
globalContainer.resolve(A);
}).toThrow(
/Cannot inject the dependency "j" at position #0 of "A" constructor. Reason:\s+Attempted to resolve unregistered dependency token: "missing"/
);
});
12 changes: 2 additions & 10 deletions src/decorators/auto-injectable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import constructor from "../types/constructor";
import {getParamInfo} from "../reflection-helpers";
import {instance as globalContainer} from "../dependency-container";
import {isTokenDescriptor} from "../providers/injection-token";
import {formatErrorCtor} from "../error-helpers";

/**
* Class decorator factory that replaces the decorated class' constructor with
Expand Down Expand Up @@ -29,16 +30,7 @@ function autoInjectable(): (target: constructor<any>) => any {
return globalContainer.resolve(type);
} catch (e) {
const argIndex = index + args.length;

const [, params = null] =
target.toString().match(/constructor\(([\w, ]+)\)/) || [];
const argName = params
? params.split(",")[argIndex]
: `#${argIndex}`;

throw new Error(
`Cannot inject the dependency ${argName} of ${target.name} constructor. ${e}`
);
throw new Error(formatErrorCtor(target, argIndex, e));
}
})
)
Expand Down
31 changes: 20 additions & 11 deletions src/dependency-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import constructor from "./types/constructor";
import Registry from "./registry";
import Lifecycle from "./types/lifecycle";
import ResolutionContext from "./resolution-context";
import {formatErrorCtor} from "./error-helpers";

export type Registration<T = any> = {
provider: Provider<T>;
Expand Down Expand Up @@ -174,7 +175,7 @@ class InternalDependencyContainer implements DependencyContainer {

if (!registration && isNormalToken(token)) {
throw new Error(
`Attempted to resolve unregistered dependency token: ${token.toString()}`
`Attempted to resolve unregistered dependency token: "${token.toString()}"`
);
}

Expand Down Expand Up @@ -246,7 +247,7 @@ class InternalDependencyContainer implements DependencyContainer {

if (!registrations && isNormalToken(token)) {
throw new Error(
`Attempted to resolve unregistered dependency token: ${token.toString()}`
`Attempted to resolve unregistered dependency token: "${token.toString()}"`
);
}

Expand Down Expand Up @@ -338,20 +339,28 @@ class InternalDependencyContainer implements DependencyContainer {
const paramInfo = typeInfo.get(ctor);

if (!paramInfo || paramInfo.length === 0) {
throw new Error(`TypeInfo not known for ${ctor}`);
throw new Error(`TypeInfo not known for ${ctor.name}`);
}

const params = paramInfo.map(param => {
if (isTokenDescriptor(param)) {
return param.multiple
? this.resolveAll(param.token)
: this.resolve(param.token, context);
}
return this.resolve(param, context);
});
const params = paramInfo.map(this.resolveParams(context, ctor));

return new ctor(...params);
}

private resolveParams<T>(context: ResolutionContext, ctor: constructor<T>) {
return (param: any, idx: number) => {
try {
if (isTokenDescriptor(param)) {
return param.multiple
? this.resolveAll(param.token)
: this.resolve(param.token, context);
}
return this.resolve(param, context);
} catch (e) {
throw new Error(formatErrorCtor(ctor, idx, e));
}
};
}
}

export const instance: DependencyContainer = new InternalDependencyContainer();
Expand Down
27 changes: 27 additions & 0 deletions src/error-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import constructor from "./types/constructor";

function formatDependency(params: string | null, idx: number): string {
if (params === null) {
return `at position #${idx}`;
}
const argName = params.split(",")[idx].trim();
return `"${argName}" at position #${idx}`;
}

function composeErrorMessage(msg: string, e: Error, indent = " "): string {
return [msg, ...e.message.split("\n").map(l => indent + l)].join("\n");
}

export function formatErrorCtor(
ctor: constructor<any>,
paramIdx: number,
error: Error
): string {
const [, params = null] =
ctor.toString().match(/constructor\(([\w, ]+)\)/) || [];
const dep = formatDependency(params, paramIdx);
return composeErrorMessage(
`Cannot inject the dependency ${dep} of "${ctor.name}" constructor. Reason:`,
error
);
}

0 comments on commit 6fbd5e6

Please sign in to comment.