-
Notifications
You must be signed in to change notification settings - Fork 24.8k
/
di.ts
224 lines (209 loc) Β· 8.25 KB
/
di.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Expression, LiteralExpr, R3DependencyMetadata, WrappedNodeExpr} from '@angular/compiler';
import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError, makeRelatedInformation} from '../../../diagnostics';
import {ClassDeclaration, CtorParameter, Decorator, ReflectionHost, TypeValueReferenceKind, UnavailableValue, ValueUnavailableKind} from '../../../reflection';
import {isAngularCore, valueReferenceToExpression} from './util';
export type ConstructorDeps = {
deps: R3DependencyMetadata[];
}|{
deps: null;
errors: ConstructorDepError[];
};
export interface ConstructorDepError {
index: number;
param: CtorParameter;
reason: UnavailableValue;
}
export function getConstructorDependencies(
clazz: ClassDeclaration, reflector: ReflectionHost, isCore: boolean): ConstructorDeps|null {
const deps: R3DependencyMetadata[] = [];
const errors: ConstructorDepError[] = [];
let ctorParams = reflector.getConstructorParameters(clazz);
if (ctorParams === null) {
if (reflector.hasBaseClass(clazz)) {
return null;
} else {
ctorParams = [];
}
}
ctorParams.forEach((param, idx) => {
let token = valueReferenceToExpression(param.typeValueReference);
let attributeNameType: Expression|null = null;
let optional = false, self = false, skipSelf = false, host = false;
(param.decorators || []).filter(dec => isCore || isAngularCore(dec)).forEach(dec => {
const name = isCore || dec.import === null ? dec.name : dec.import!.name;
if (name === 'Inject') {
if (dec.args === null || dec.args.length !== 1) {
throw new FatalDiagnosticError(
ErrorCode.DECORATOR_ARITY_WRONG, Decorator.nodeForError(dec),
`Unexpected number of arguments to @Inject().`);
}
token = new WrappedNodeExpr(dec.args[0]);
} else if (name === 'Optional') {
optional = true;
} else if (name === 'SkipSelf') {
skipSelf = true;
} else if (name === 'Self') {
self = true;
} else if (name === 'Host') {
host = true;
} else if (name === 'Attribute') {
if (dec.args === null || dec.args.length !== 1) {
throw new FatalDiagnosticError(
ErrorCode.DECORATOR_ARITY_WRONG, Decorator.nodeForError(dec),
`Unexpected number of arguments to @Attribute().`);
}
const attributeName = dec.args[0];
token = new WrappedNodeExpr(attributeName);
if (ts.isStringLiteralLike(attributeName)) {
attributeNameType = new LiteralExpr(attributeName.text);
} else {
attributeNameType =
new WrappedNodeExpr(ts.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword));
}
} else {
throw new FatalDiagnosticError(
ErrorCode.DECORATOR_UNEXPECTED, Decorator.nodeForError(dec),
`Unexpected decorator ${name} on parameter.`);
}
});
if (token === null) {
if (param.typeValueReference.kind !== TypeValueReferenceKind.UNAVAILABLE) {
throw new Error(
'Illegal state: expected value reference to be unavailable if no token is present');
}
errors.push({
index: idx,
param,
reason: param.typeValueReference.reason,
});
} else {
deps.push({token, attributeNameType, optional, self, skipSelf, host});
}
});
if (errors.length === 0) {
return {deps};
} else {
return {deps: null, errors};
}
}
/**
* Convert `ConstructorDeps` into the `R3DependencyMetadata` array for those deps if they're valid,
* or into an `'invalid'` signal if they're not.
*
* This is a companion function to `validateConstructorDependencies` which accepts invalid deps.
*/
export function unwrapConstructorDependencies(deps: ConstructorDeps|null): R3DependencyMetadata[]|
'invalid'|null {
if (deps === null) {
return null;
} else if (deps.deps !== null) {
// These constructor dependencies are valid.
return deps.deps;
} else {
// These deps are invalid.
return 'invalid';
}
}
export function getValidConstructorDependencies(
clazz: ClassDeclaration, reflector: ReflectionHost, isCore: boolean): R3DependencyMetadata[]|
null {
return validateConstructorDependencies(
clazz, getConstructorDependencies(clazz, reflector, isCore));
}
/**
* Validate that `ConstructorDeps` does not have any invalid dependencies and convert them into the
* `R3DependencyMetadata` array if so, or raise a diagnostic if some deps are invalid.
*
* This is a companion function to `unwrapConstructorDependencies` which does not accept invalid
* deps.
*/
export function validateConstructorDependencies(
clazz: ClassDeclaration, deps: ConstructorDeps|null): R3DependencyMetadata[]|null {
if (deps === null) {
return null;
} else if (deps.deps !== null) {
return deps.deps;
} else {
// TODO(alxhub): this cast is necessary because the g3 typescript version doesn't narrow here.
// There is at least one error.
const error = (deps as {errors: ConstructorDepError[]}).errors[0];
throw createUnsuitableInjectionTokenError(clazz, error);
}
}
/**
* Creates a fatal error with diagnostic for an invalid injection token.
* @param clazz The class for which the injection token was unavailable.
* @param error The reason why no valid injection token is available.
*/
function createUnsuitableInjectionTokenError(
clazz: ClassDeclaration, error: ConstructorDepError): FatalDiagnosticError {
const {param, index, reason} = error;
let chainMessage: string|undefined = undefined;
let hints: ts.DiagnosticRelatedInformation[]|undefined = undefined;
switch (reason.kind) {
case ValueUnavailableKind.UNSUPPORTED:
chainMessage = 'Consider using the @Inject decorator to specify an injection token.';
hints = [
makeRelatedInformation(reason.typeNode, 'This type is not supported as injection token.'),
];
break;
case ValueUnavailableKind.NO_VALUE_DECLARATION:
chainMessage = 'Consider using the @Inject decorator to specify an injection token.';
hints = [
makeRelatedInformation(
reason.typeNode,
'This type does not have a value, so it cannot be used as injection token.'),
];
if (reason.decl !== null) {
hints.push(makeRelatedInformation(reason.decl, 'The type is declared here.'));
}
break;
case ValueUnavailableKind.TYPE_ONLY_IMPORT:
chainMessage =
'Consider changing the type-only import to a regular import, or use the @Inject decorator to specify an injection token.';
hints = [
makeRelatedInformation(
reason.typeNode,
'This type is imported using a type-only import, which prevents it from being usable as an injection token.'),
makeRelatedInformation(reason.node, 'The type-only import occurs here.'),
];
break;
case ValueUnavailableKind.NAMESPACE:
chainMessage = 'Consider using the @Inject decorator to specify an injection token.';
hints = [
makeRelatedInformation(
reason.typeNode,
'This type corresponds with a namespace, which cannot be used as injection token.'),
makeRelatedInformation(reason.importClause, 'The namespace import occurs here.'),
];
break;
case ValueUnavailableKind.UNKNOWN_REFERENCE:
chainMessage = 'The type should reference a known declaration.';
hints = [makeRelatedInformation(reason.typeNode, 'This type could not be resolved.')];
break;
case ValueUnavailableKind.MISSING_TYPE:
chainMessage =
'Consider adding a type to the parameter or use the @Inject decorator to specify an injection token.';
break;
}
const chain: ts.DiagnosticMessageChain = {
messageText: `No suitable injection token for parameter '${param.name || index}' of class '${
clazz.name.text}'.`,
category: ts.DiagnosticCategory.Error,
code: 0,
next: [{
messageText: chainMessage,
category: ts.DiagnosticCategory.Message,
code: 0,
}],
};
return new FatalDiagnosticError(ErrorCode.PARAM_MISSING_TOKEN, param.nameNode, chain, hints);
}