1 | const _bindAll = require('lodash.bindall');
|
2 | const _keys = require('lodash.keys');
|
3 | const CallChain = require('./CallChain');
|
4 |
|
5 | class DependencyError {
|
6 | constructor(callChain, error, errorObject) {
|
7 | this.callChain = callChain;
|
8 | this.error = error;
|
9 | this.errorObject = errorObject;
|
10 | }
|
11 |
|
12 | print(logger) {
|
13 | logger.error(
|
14 | `${this.error} ${this.callChain.getHighlightedName()} in `,
|
15 | this.callChain.getPath(true)
|
16 | );
|
17 | if (this.errorObject) {
|
18 | logger.error(this.errorObject);
|
19 | logger.error(this.errorObject.stack);
|
20 | }
|
21 | }
|
22 |
|
23 | static cyclic(callChain) {
|
24 | return new DependencyError(callChain, 'Cyclic Dependency');
|
25 | }
|
26 |
|
27 | static missingDependency(callChain) {
|
28 | return new DependencyError(callChain, 'Missing Dependency');
|
29 | }
|
30 |
|
31 | static exception(callChain, exception) {
|
32 | return new DependencyError(callChain, 'Exception in Dependency', exception);
|
33 | }
|
34 | }
|
35 |
|
36 | class DependencyWarning {
|
37 | constructor(callChain, name, warningObject) {
|
38 | this.callChain = callChain;
|
39 | this.name = name;
|
40 | this.warningObject = warningObject;
|
41 | }
|
42 |
|
43 | print(logger) {
|
44 | return logger.warn(
|
45 | `${this.name} ${this.callChain.getHighlightedName()} in `,
|
46 | this.callChain.getPath(true)
|
47 | );
|
48 | }
|
49 |
|
50 | static unused(callChain) {
|
51 | return new DependencyWarning(callChain, 'Unused dependency');
|
52 | }
|
53 | }
|
54 |
|
55 | class DependencyResolver {
|
56 | constructor(container, name, logger) {
|
57 | _bindAll(this, 'resolveDependencies', 'resolveRegex');
|
58 | this.container = container;
|
59 | this.name = name;
|
60 | this.logger = logger;
|
61 | this.errors = [];
|
62 | this.warnings = [];
|
63 | this.resolvingFinished = false;
|
64 | }
|
65 |
|
66 | resolveArray(deps, callChain) {
|
67 | let name;
|
68 | const instances = [];
|
69 | for (name of deps) {
|
70 | instances.push(this.resolveDependencies(name, callChain));
|
71 | }
|
72 | return instances;
|
73 | }
|
74 |
|
75 | resolveMap(deps, callChain) {
|
76 | let name;
|
77 | const instances = {};
|
78 | for (name of deps) {
|
79 | instances[name] = this.resolveDependencies(name, callChain);
|
80 | }
|
81 | return instances;
|
82 | }
|
83 |
|
84 | resolveRegex(regex) {
|
85 | const deps = _keys(this.container.dependencies)
|
86 | .filter((key) => {
|
87 | return regex.test(key) && key !== '$injector' && key !== this.container.privateInjectorName();
|
88 | });
|
89 | return this.resolveMap(deps);
|
90 | }
|
91 |
|
92 | addInjectorDependency() {
|
93 | this.$injector = this.container.addDependency('$injector', {
|
94 | get: (name) => {
|
95 | this.checkResolvingFinished(`cannot use $injector.get('${name}') asynchronously`);
|
96 | return this.resolveDependencies(name, this.currentCallChain);
|
97 | },
|
98 | getRegex: (regex) => {
|
99 | this.checkResolvingFinished(`cannot use $injector.getRegex(${regex}) asynchronously`);
|
100 | return this.resolveRegex(regex, this.currentCallChain);
|
101 | },
|
102 | getMap: (deps) => {
|
103 | this.checkResolvingFinished('cannot use $injector.getArray() asynchronously');
|
104 | return this.resolveMap(deps, this.currentCallChain);
|
105 | },
|
106 | getAll: () => {
|
107 | this.checkResolvingFinished('cannot use $injector.getAll() asynchronously');
|
108 | return this.resolveRegex(/.+/, this.currentCallChain);
|
109 | }
|
110 | }, true).instance;
|
111 |
|
112 | return this.$injector;
|
113 | }
|
114 |
|
115 | checkResolvingFinished(message) {
|
116 | if (this.resolvingFinished) {
|
117 | throw new Error(message);
|
118 | }
|
119 | }
|
120 |
|
121 | resolveDependencies(name, chain) {
|
122 | const callChain = this.currentCallChain = chain ? chain.add(name) : CallChain.create(name);
|
123 | const dep = this.container.getDependency(name);
|
124 | if (dep) {
|
125 | if (dep.instance) {
|
126 | return dep.instance;
|
127 | }
|
128 |
|
129 | if (callChain.hasCyclic()) {
|
130 | return this.errors.push(DependencyError.cyclic(callChain));
|
131 | }
|
132 |
|
133 | const instances = this.resolveArray(dep.dependencies, callChain);
|
134 |
|
135 | try {
|
136 | let dependency;
|
137 | const dependencies = dep.dependencies;
|
138 | for (dependency of dependencies) {
|
139 | if (dep.fn.toString().split(dependency).length <= 2) {
|
140 | this.warnings.push(DependencyWarning.unused(callChain.add(dependency)));
|
141 | }
|
142 | }
|
143 | dep.instance = dep.fn.apply(null, instances);
|
144 | } catch (error) {
|
145 | this.errors.push(DependencyError.exception(callChain, error));
|
146 | }
|
147 |
|
148 | return dep.instance;
|
149 | }
|
150 |
|
151 | this.errors.push(DependencyError.missingDependency(callChain));
|
152 | return null;
|
153 | }
|
154 |
|
155 | printErrors() {
|
156 | let error;
|
157 | const results = [];
|
158 | for (error of this.errors) {
|
159 | results.push(error.print(this.logger));
|
160 | }
|
161 | return results;
|
162 | }
|
163 |
|
164 | printWarnings() {
|
165 | let warning;
|
166 | const results = [];
|
167 | for (warning of this.warnings) {
|
168 | results.push(warning.print(this.logger));
|
169 | }
|
170 | return results;
|
171 | }
|
172 |
|
173 | throwError() {
|
174 | throw new Error('Resolver encountered errors');
|
175 | }
|
176 |
|
177 | resolve() {
|
178 | this.addInjectorDependency();
|
179 | this.dependency = this.resolveDependencies(this.name);
|
180 | this.printWarnings();
|
181 | if (this.errors.length > 0) {
|
182 | this.printErrors();
|
183 | this.throwError();
|
184 | }
|
185 | this.resolvingFinished = true;
|
186 | return this.dependency;
|
187 | }
|
188 |
|
189 | static resolve(container, name, logger) {
|
190 | const resolver = new DependencyResolver(container, name, logger);
|
191 | resolver.resolve();
|
192 | return resolver;
|
193 | }
|
194 | }
|
195 |
|
196 | module.exports = DependencyResolver;
|