@@ -10,13 +10,13 @@ import ts from 'typescript';
10
10
11
11
import { ErrorCode , FatalDiagnosticError , makeDiagnostic , makeRelatedInformation } from '../../../diagnostics' ;
12
12
import { Reference } from '../../../imports' ;
13
- import { InjectableClassRegistry , MetadataReader } from '../../../metadata' ;
14
13
import { describeResolvedType , DynamicValue , PartialEvaluator , ResolvedValue , traceDynamicValue } from '../../../partial_evaluator' ;
15
14
import { ClassDeclaration , ReflectionHost } from '../../../reflection' ;
16
15
import { DeclarationData , LocalModuleScopeRegistry } from '../../../scope' ;
17
16
import { identifierOfNode } from '../../../util/src/typescript' ;
18
17
19
- import { readBaseClass } from './util' ;
18
+ import { InjectableClassRegistry } from './injectable_registry' ;
19
+ import { isAbstractClassDeclaration , readBaseClass } from './util' ;
20
20
21
21
22
22
/**
@@ -102,7 +102,10 @@ export function getProviderDiagnostics(
102
102
const diagnostics : ts . Diagnostic [ ] = [ ] ;
103
103
104
104
for ( const provider of providerClasses ) {
105
- if ( registry . isInjectable ( provider . node ) ) {
105
+ const injectableMeta = registry . getInjectableMeta ( provider . node ) ;
106
+ if ( injectableMeta !== null ) {
107
+ // The provided type is recognized as injectable, so we don't report a diagnostic for this
108
+ // provider.
106
109
continue ;
107
110
}
108
111
@@ -124,9 +127,9 @@ Either add the @Injectable() decorator to '${
124
127
}
125
128
126
129
export function getDirectiveDiagnostics (
127
- node : ClassDeclaration , reader : MetadataReader , evaluator : PartialEvaluator ,
128
- reflector : ReflectionHost , scopeRegistry : LocalModuleScopeRegistry ,
129
- kind : string ) : ts . Diagnostic [ ] | null {
130
+ node : ClassDeclaration , injectableRegistry : InjectableClassRegistry ,
131
+ evaluator : PartialEvaluator , reflector : ReflectionHost , scopeRegistry : LocalModuleScopeRegistry ,
132
+ strictInjectionParameters : boolean , kind : 'Directive' | 'Component' ) : ts . Diagnostic [ ] | null {
130
133
let diagnostics : ts . Diagnostic [ ] | null = [ ] ;
131
134
132
135
const addDiagnostics = ( more : ts . Diagnostic | ts . Diagnostic [ ] | null ) => {
@@ -147,7 +150,8 @@ export function getDirectiveDiagnostics(
147
150
addDiagnostics ( makeDuplicateDeclarationError ( node , duplicateDeclarations , kind ) ) ;
148
151
}
149
152
150
- addDiagnostics ( checkInheritanceOfDirective ( node , reader , reflector , evaluator ) ) ;
153
+ addDiagnostics ( checkInheritanceOfInjectable (
154
+ node , injectableRegistry , reflector , evaluator , strictInjectionParameters , kind ) ) ;
151
155
return diagnostics ;
152
156
}
153
157
@@ -159,9 +163,42 @@ export function getUndecoratedClassWithAngularFeaturesDiagnostic(node: ClassDecl
159
163
`Angular decorator.` ) ;
160
164
}
161
165
162
- export function checkInheritanceOfDirective (
163
- node : ClassDeclaration , reader : MetadataReader , reflector : ReflectionHost ,
164
- evaluator : PartialEvaluator ) : ts . Diagnostic | null {
166
+ export function checkInheritanceOfInjectable (
167
+ node : ClassDeclaration , injectableRegistry : InjectableClassRegistry , reflector : ReflectionHost ,
168
+ evaluator : PartialEvaluator , strictInjectionParameters : boolean ,
169
+ kind : 'Directive' | 'Component' | 'Pipe' | 'Injectable' ) : ts . Diagnostic | null {
170
+ const classWithCtor = findInheritedCtor ( node , injectableRegistry , reflector , evaluator ) ;
171
+ if ( classWithCtor === null || classWithCtor . isCtorValid ) {
172
+ // The class does not inherit a constructor, or the inherited constructor is compatible
173
+ // with DI; no need to report a diagnostic.
174
+ return null ;
175
+ }
176
+
177
+ if ( ! classWithCtor . isDecorated ) {
178
+ // The inherited constructor exists in a class that does not have an Angular decorator.
179
+ // This is an error, as there won't be a factory definition available for DI to invoke
180
+ // the constructor.
181
+ return getInheritedUndecoratedCtorDiagnostic ( node , classWithCtor . ref , kind ) ;
182
+ }
183
+
184
+ if ( ! strictInjectionParameters || isAbstractClassDeclaration ( node ) ) {
185
+ // An invalid constructor is only reported as error under `strictInjectionParameters` and
186
+ // only for concrete classes; follow the same exclusions for derived types.
187
+ return null ;
188
+ }
189
+
190
+ return getInheritedInvalidCtorDiagnostic ( node , classWithCtor . ref , kind ) ;
191
+ }
192
+
193
+ interface ClassWithCtor {
194
+ ref : Reference < ClassDeclaration > ;
195
+ isCtorValid : boolean ;
196
+ isDecorated : boolean ;
197
+ }
198
+
199
+ export function findInheritedCtor (
200
+ node : ClassDeclaration , injectableRegistry : InjectableClassRegistry , reflector : ReflectionHost ,
201
+ evaluator : PartialEvaluator ) : ClassWithCtor | null {
165
202
if ( ! reflector . isClass ( node ) || reflector . getConstructorParameters ( node ) !== null ) {
166
203
// We should skip nodes that aren't classes. If a constructor exists, then no base class
167
204
// definition is required on the runtime side - it's legal to inherit from any class.
@@ -178,44 +215,64 @@ export function checkInheritanceOfDirective(
178
215
return null ;
179
216
}
180
217
181
- // We can skip the base class if it has metadata.
182
- const baseClassMeta = reader . getDirectiveMetadata ( baseClass ) ;
183
- if ( baseClassMeta !== null ) {
184
- return null ;
185
- }
186
-
187
- // If the base class has a blank constructor we can skip it since it can't be using DI.
188
- const baseClassConstructorParams = reflector . getConstructorParameters ( baseClass . node ) ;
189
- const newParentClass = readBaseClass ( baseClass . node , reflector , evaluator ) ;
190
-
191
- if ( baseClassConstructorParams !== null && baseClassConstructorParams . length > 0 ) {
192
- // This class has a non-trivial constructor, that's an error!
193
- return getInheritedUndecoratedCtorDiagnostic ( node , baseClass , reader ) ;
194
- } else if ( baseClassConstructorParams !== null || newParentClass === null ) {
195
- // This class has a trivial constructor, or no constructor + is the
196
- // top of the inheritance chain, so it's okay.
197
- return null ;
218
+ const injectableMeta = injectableRegistry . getInjectableMeta ( baseClass . node ) ;
219
+ if ( injectableMeta !== null ) {
220
+ if ( injectableMeta . ctorDeps !== null ) {
221
+ // The class has an Angular decorator with a constructor.
222
+ return {
223
+ ref : baseClass ,
224
+ isCtorValid : injectableMeta . ctorDeps !== 'invalid' ,
225
+ isDecorated : true ,
226
+ } ;
227
+ }
228
+ } else {
229
+ const baseClassConstructorParams = reflector . getConstructorParameters ( baseClass . node ) ;
230
+ if ( baseClassConstructorParams !== null ) {
231
+ // The class is not decorated, but it does have constructor. An undecorated class is only
232
+ // allowed to have a constructor without parameters, otherwise it is invalid.
233
+ return {
234
+ ref : baseClass ,
235
+ isCtorValid : baseClassConstructorParams . length === 0 ,
236
+ isDecorated : false ,
237
+ } ;
238
+ }
198
239
}
199
240
200
241
// Go up the chain and continue
201
- baseClass = newParentClass ;
242
+ baseClass = readBaseClass ( baseClass . node , reflector , evaluator ) ;
202
243
}
203
244
204
245
return null ;
205
246
}
206
247
248
+ function getInheritedInvalidCtorDiagnostic (
249
+ node : ClassDeclaration , baseClass : Reference ,
250
+ kind : 'Directive' | 'Component' | 'Pipe' | 'Injectable' ) {
251
+ const baseClassName = baseClass . debugName ;
252
+
253
+ return makeDiagnostic (
254
+ ErrorCode . INJECTABLE_INHERITS_INVALID_CONSTRUCTOR , node . name ,
255
+ `The ${ kind . toLowerCase ( ) } ${ node . name . text } inherits its constructor from ${
256
+ baseClassName } , ` +
257
+ `but the latter has a constructor parameter that is not compatible with dependency injection. ` +
258
+ `Either add an explicit constructor to ${ node . name . text } or change ${
259
+ baseClassName } 's constructor to ` +
260
+ `use parameters that are valid for DI.` ) ;
261
+ }
262
+
207
263
function getInheritedUndecoratedCtorDiagnostic (
208
- node : ClassDeclaration , baseClass : Reference , reader : MetadataReader ) {
209
- const subclassMeta = reader . getDirectiveMetadata ( new Reference ( node ) ) ! ;
210
- const dirOrComp = subclassMeta . isComponent ? 'Component' : 'Directive' ;
264
+ node : ClassDeclaration , baseClass : Reference ,
265
+ kind : 'Directive' | 'Component' | 'Pipe' | 'Injectable' ) {
211
266
const baseClassName = baseClass . debugName ;
267
+ const baseNeedsDecorator =
268
+ kind === 'Component' || kind === 'Directive' ? 'Directive' : 'Injectable' ;
212
269
213
270
return makeDiagnostic (
214
271
ErrorCode . DIRECTIVE_INHERITS_UNDECORATED_CTOR , node . name ,
215
- `The ${ dirOrComp . toLowerCase ( ) } ${ node . name . text } inherits its constructor from ${
272
+ `The ${ kind . toLowerCase ( ) } ${ node . name . text } inherits its constructor from ${
216
273
baseClassName } , ` +
217
274
`but the latter does not have an Angular decorator of its own. Dependency injection will not be able to ` +
218
- `resolve the parameters of ${
219
- baseClassName } 's constructor. Either add a @Directive decorator ` +
275
+ `resolve the parameters of ${ baseClassName } 's constructor. Either add a @ ${
276
+ baseNeedsDecorator } decorator ` +
220
277
`to ${ baseClassName } , or add an explicit constructor to ${ node . name . text } .` ) ;
221
278
}
0 commit comments