forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
/
local.ts
656 lines (590 loc) · 27.1 KB
/
local.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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
/**
* @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 {ExternalExpr, SchemaMetadata} from '@angular/compiler';
import * as ts from 'typescript';
import {ErrorCode, makeDiagnostic, makeRelatedInformation} from '../../diagnostics';
import {AliasingHost, Reexport, Reference, ReferenceEmitter} from '../../imports';
import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
import {ClassDeclaration} from '../../reflection';
import {identifierOfNode, nodeNameForError} from '../../util/src/typescript';
import {ExportScope, ScopeData} from './api';
import {ComponentScopeReader} from './component_scope';
import {DtsModuleScopeResolver} from './dependency';
export interface LocalNgModuleData {
declarations: Reference<ClassDeclaration>[];
imports: Reference<ClassDeclaration>[];
exports: Reference<ClassDeclaration>[];
}
export interface LocalModuleScope extends ExportScope {
compilation: ScopeData;
reexports: Reexport[]|null;
schemas: SchemaMetadata[];
}
/**
* Information about the compilation scope of a registered declaration.
*/
export interface CompilationScope extends ScopeData {
/** The declaration whose compilation scope is described here. */
declaration: ClassDeclaration;
/** The declaration of the NgModule that declares this `declaration`. */
ngModule: ClassDeclaration;
}
/**
* A registry which collects information about NgModules, Directives, Components, and Pipes which
* are local (declared in the ts.Program being compiled), and can produce `LocalModuleScope`s
* which summarize the compilation scope of a component.
*
* This class implements the logic of NgModule declarations, imports, and exports and can produce,
* for a given component, the set of directives and pipes which are "visible" in that component's
* template.
*
* The `LocalModuleScopeRegistry` has two "modes" of operation. During analysis, data for each
* individual NgModule, Directive, Component, and Pipe is added to the registry. No attempt is made
* to traverse or validate the NgModule graph (imports, exports, etc). After analysis, one of
* `getScopeOfModule` or `getScopeForComponent` can be called, which traverses the NgModule graph
* and applies the NgModule logic to generate a `LocalModuleScope`, the full scope for the given
* module or component.
*
* The `LocalModuleScopeRegistry` is also capable of producing `ts.Diagnostic` errors when Angular
* semantics are violated.
*/
export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScopeReader {
/**
* Tracks whether the registry has been asked to produce scopes for a module or component. Once
* this is true, the registry cannot accept registrations of new directives/pipes/modules as it
* would invalidate the cached scope data.
*/
private sealed = false;
/**
* A map of components from the current compilation unit to the NgModule which declared them.
*
* As components and directives are not distinguished at the NgModule level, this map may also
* contain directives. This doesn't cause any problems but isn't useful as there is no concept of
* a directive's compilation scope.
*/
private declarationToModule = new Map<ClassDeclaration, DeclarationData>();
/**
* This maps from the directive/pipe class to a map of data for each NgModule that declares the
* directive/pipe. This data is needed to produce an error for the given class.
*/
private duplicateDeclarations =
new Map<ClassDeclaration, Map<ClassDeclaration, DeclarationData>>();
private moduleToRef = new Map<ClassDeclaration, Reference<ClassDeclaration>>();
/**
* A cache of calculated `LocalModuleScope`s for each NgModule declared in the current program.
*
* A value of `undefined` indicates the scope was invalid and produced errors (therefore,
* diagnostics should exist in the `scopeErrors` map).
*/
private cache = new Map<ClassDeclaration, LocalModuleScope|undefined|null>();
/**
* Tracks whether a given component requires "remote scoping".
*
* Remote scoping is when the set of directives which apply to a given component is set in the
* NgModule's file instead of directly on the component def (which is sometimes needed to get
* around cyclic import issues). This is not used in calculation of `LocalModuleScope`s, but is
* tracked here for convenience.
*/
private remoteScoping = new Set<ClassDeclaration>();
/**
* Tracks errors accumulated in the processing of scopes for each module declaration.
*/
private scopeErrors = new Map<ClassDeclaration, ts.Diagnostic[]>();
/**
* Tracks which NgModules are unreliable due to errors within their declarations.
*
* This provides a unified view of which modules have errors, across all of the different
* diagnostic categories that can be produced. Theoretically this can be inferred from the other
* properties of this class, but is tracked explicitly to simplify the logic.
*/
private taintedModules = new Set<ClassDeclaration>();
constructor(
private localReader: MetadataReader, private dependencyScopeReader: DtsModuleScopeResolver,
private refEmitter: ReferenceEmitter, private aliasingHost: AliasingHost|null) {}
/**
* Add an NgModule's data to the registry.
*/
registerNgModuleMetadata(data: NgModuleMeta): void {
this.assertCollecting();
const ngModule = data.ref.node;
this.moduleToRef.set(data.ref.node, data.ref);
// Iterate over the module's declarations, and add them to declarationToModule. If duplicates
// are found, they're instead tracked in duplicateDeclarations.
for (const decl of data.declarations) {
this.registerDeclarationOfModule(ngModule, decl, data.rawDeclarations);
}
}
registerDirectiveMetadata(directive: DirectiveMeta): void {}
registerPipeMetadata(pipe: PipeMeta): void {}
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null|'error' {
const scope = !this.declarationToModule.has(clazz) ?
null :
this.getScopeOfModule(this.declarationToModule.get(clazz)!.ngModule);
return scope;
}
/**
* If `node` is declared in more than one NgModule (duplicate declaration), then get the
* `DeclarationData` for each offending declaration.
*
* Ordinarily a class is only declared in one NgModule, in which case this function returns
* `null`.
*/
getDuplicateDeclarations(node: ClassDeclaration): DeclarationData[]|null {
if (!this.duplicateDeclarations.has(node)) {
return null;
}
return Array.from(this.duplicateDeclarations.get(node)!.values());
}
/**
* Collects registered data for a module and its directives/pipes and convert it into a full
* `LocalModuleScope`.
*
* This method implements the logic of NgModule imports and exports. It returns the
* `LocalModuleScope` for the given NgModule if one can be produced, `null` if no scope was ever
* defined, or the string `'error'` if the scope contained errors.
*/
getScopeOfModule(clazz: ClassDeclaration): LocalModuleScope|'error'|null {
const scope = this.moduleToRef.has(clazz) ?
this.getScopeOfModuleReference(this.moduleToRef.get(clazz)!) :
null;
// If the NgModule class is marked as tainted, consider it an error.
if (this.taintedModules.has(clazz)) {
return 'error';
}
// Translate undefined -> 'error'.
return scope !== undefined ? scope : 'error';
}
/**
* Retrieves any `ts.Diagnostic`s produced during the calculation of the `LocalModuleScope` for
* the given NgModule, or `null` if no errors were present.
*/
getDiagnosticsOfModule(clazz: ClassDeclaration): ts.Diagnostic[]|null {
// Required to ensure the errors are populated for the given class. If it has been processed
// before, this will be a no-op due to the scope cache.
this.getScopeOfModule(clazz);
if (this.scopeErrors.has(clazz)) {
return this.scopeErrors.get(clazz)!;
} else {
return null;
}
}
/**
* Returns a collection of the compilation scope for each registered declaration.
*/
getCompilationScopes(): CompilationScope[] {
const scopes: CompilationScope[] = [];
this.declarationToModule.forEach((declData, declaration) => {
const scope = this.getScopeOfModule(declData.ngModule);
if (scope !== null && scope !== 'error') {
scopes.push({declaration, ngModule: declData.ngModule, ...scope.compilation});
}
});
return scopes;
}
private registerDeclarationOfModule(
ngModule: ClassDeclaration, decl: Reference<ClassDeclaration>,
rawDeclarations: ts.Expression|null): void {
const declData: DeclarationData = {
ngModule,
ref: decl,
rawDeclarations,
};
// First, check for duplicate declarations of the same directive/pipe.
if (this.duplicateDeclarations.has(decl.node)) {
// This directive/pipe has already been identified as being duplicated. Add this module to the
// map of modules for which a duplicate declaration exists.
this.duplicateDeclarations.get(decl.node)!.set(ngModule, declData);
} else if (
this.declarationToModule.has(decl.node) &&
this.declarationToModule.get(decl.node)!.ngModule !== ngModule) {
// This directive/pipe is already registered as declared in another module. Mark it as a
// duplicate instead.
const duplicateDeclMap = new Map<ClassDeclaration, DeclarationData>();
const firstDeclData = this.declarationToModule.get(decl.node)!;
// Mark both modules as tainted, since their declarations are missing a component.
this.taintedModules.add(firstDeclData.ngModule);
this.taintedModules.add(ngModule);
// Being detected as a duplicate means there are two NgModules (for now) which declare this
// directive/pipe. Add both of them to the duplicate tracking map.
duplicateDeclMap.set(firstDeclData.ngModule, firstDeclData);
duplicateDeclMap.set(ngModule, declData);
this.duplicateDeclarations.set(decl.node, duplicateDeclMap);
// Remove the directive/pipe from `declarationToModule` as it's a duplicate declaration, and
// therefore not valid.
this.declarationToModule.delete(decl.node);
} else {
// This is the first declaration of this directive/pipe, so map it.
this.declarationToModule.set(decl.node, declData);
}
}
/**
* Implementation of `getScopeOfModule` which accepts a reference to a class and differentiates
* between:
*
* * no scope being available (returns `null`)
* * a scope being produced with errors (returns `undefined`).
*/
private getScopeOfModuleReference(ref: Reference<ClassDeclaration>): LocalModuleScope|null
|undefined {
if (this.cache.has(ref.node)) {
return this.cache.get(ref.node);
}
// Seal the registry to protect the integrity of the `LocalModuleScope` cache.
this.sealed = true;
// `ref` should be an NgModule previously added to the registry. If not, a scope for it
// cannot be produced.
const ngModule = this.localReader.getNgModuleMetadata(ref);
if (ngModule === null) {
this.cache.set(ref.node, null);
return null;
}
// Modules which contributed to the compilation scope of this module.
const compilationModules = new Set<ClassDeclaration>([ngModule.ref.node]);
// Modules which contributed to the export scope of this module.
const exportedModules = new Set<ClassDeclaration>([ngModule.ref.node]);
// Errors produced during computation of the scope are recorded here. At the end, if this array
// isn't empty then `undefined` will be cached and returned to indicate this scope is invalid.
const diagnostics: ts.Diagnostic[] = [];
// At this point, the goal is to produce two distinct transitive sets:
// - the directives and pipes which are visible to components declared in the NgModule.
// - the directives and pipes which are exported to any NgModules which import this one.
// Directives and pipes in the compilation scope.
const compilationDirectives = new Map<ts.Declaration, DirectiveMeta>();
const compilationPipes = new Map<ts.Declaration, PipeMeta>();
const declared = new Set<ts.Declaration>();
// Directives and pipes exported to any importing NgModules.
const exportDirectives = new Map<ts.Declaration, DirectiveMeta>();
const exportPipes = new Map<ts.Declaration, PipeMeta>();
// The algorithm is as follows:
// 1) Add all of the directives/pipes from each NgModule imported into the current one to the
// compilation scope.
// 2) Add directives/pipes declared in the NgModule to the compilation scope. At this point, the
// compilation scope is complete.
// 3) For each entry in the NgModule's exports:
// a) Attempt to resolve it as an NgModule with its own exported directives/pipes. If it is
// one, add them to the export scope of this NgModule.
// b) Otherwise, it should be a class in the compilation scope of this NgModule. If it is,
// add it to the export scope.
// c) If it's neither an NgModule nor a directive/pipe in the compilation scope, then this
// is an error.
// 1) process imports.
for (const decl of ngModule.imports) {
const importScope = this.getExportedScope(decl, diagnostics, ref.node, 'import');
if (importScope === null) {
// An import wasn't an NgModule, so record an error.
diagnostics.push(invalidRef(ref.node, decl, 'import'));
continue;
} else if (importScope === undefined) {
// An import was an NgModule but contained errors of its own. Record this as an error too,
// because this scope is always going to be incorrect if one of its imports could not be
// read.
diagnostics.push(invalidTransitiveNgModuleRef(ref.node, decl, 'import'));
continue;
}
for (const directive of importScope.exported.directives) {
compilationDirectives.set(directive.ref.node, directive);
}
for (const pipe of importScope.exported.pipes) {
compilationPipes.set(pipe.ref.node, pipe);
}
for (const importedModule of importScope.exported.ngModules) {
compilationModules.add(importedModule);
}
}
// 2) add declarations.
for (const decl of ngModule.declarations) {
const directive = this.localReader.getDirectiveMetadata(decl);
const pipe = this.localReader.getPipeMetadata(decl);
if (directive !== null) {
compilationDirectives.set(decl.node, {...directive, ref: decl});
} else if (pipe !== null) {
compilationPipes.set(decl.node, {...pipe, ref: decl});
} else {
this.taintedModules.add(ngModule.ref.node);
const errorNode = decl.getOriginForDiagnostics(ngModule.rawDeclarations!);
diagnostics.push(makeDiagnostic(
ErrorCode.NGMODULE_INVALID_DECLARATION, errorNode,
`The class '${decl.node.name.text}' is listed in the declarations ` +
`of the NgModule '${
ngModule.ref.node.name
.text}', but is not a directive, a component, or a pipe. ` +
`Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.`,
[makeRelatedInformation(
decl.node.name, `'${decl.node.name.text}' is declared here.`)]));
continue;
}
declared.add(decl.node);
}
// 3) process exports.
// Exports can contain modules, components, or directives. They're processed differently.
// Modules are straightforward. Directives and pipes from exported modules are added to the
// export maps. Directives/pipes are different - they might be exports of declared types or
// imported types.
for (const decl of ngModule.exports) {
// Attempt to resolve decl as an NgModule.
const importScope = this.getExportedScope(decl, diagnostics, ref.node, 'export');
if (importScope === undefined) {
// An export was an NgModule but contained errors of its own. Record this as an error too,
// because this scope is always going to be incorrect if one of its exports could not be
// read.
diagnostics.push(invalidTransitiveNgModuleRef(ref.node, decl, 'export'));
continue;
} else if (importScope !== null) {
// decl is an NgModule.
for (const directive of importScope.exported.directives) {
exportDirectives.set(directive.ref.node, directive);
}
for (const pipe of importScope.exported.pipes) {
exportPipes.set(pipe.ref.node, pipe);
}
for (const exportedModule of importScope.exported.ngModules) {
exportedModules.add(exportedModule);
}
} else if (compilationDirectives.has(decl.node)) {
// decl is a directive or component in the compilation scope of this NgModule.
const directive = compilationDirectives.get(decl.node)!;
exportDirectives.set(decl.node, directive);
} else if (compilationPipes.has(decl.node)) {
// decl is a pipe in the compilation scope of this NgModule.
const pipe = compilationPipes.get(decl.node)!;
exportPipes.set(decl.node, pipe);
} else {
// decl is an unknown export.
if (this.localReader.getDirectiveMetadata(decl) !== null ||
this.localReader.getPipeMetadata(decl) !== null) {
diagnostics.push(invalidReexport(ref.node, decl));
} else {
diagnostics.push(invalidRef(ref.node, decl, 'export'));
}
continue;
}
}
const exported = {
directives: Array.from(exportDirectives.values()),
pipes: Array.from(exportPipes.values()),
ngModules: Array.from(exportedModules),
};
const reexports = this.getReexports(ngModule, ref, declared, exported, diagnostics);
// Check if this scope had any errors during production.
if (diagnostics.length > 0) {
// Cache undefined, to mark the fact that the scope is invalid.
this.cache.set(ref.node, undefined);
// Save the errors for retrieval.
this.scopeErrors.set(ref.node, diagnostics);
// Mark this module as being tainted.
this.taintedModules.add(ref.node);
return undefined;
}
// Finally, produce the `LocalModuleScope` with both the compilation and export scopes.
const scope = {
compilation: {
directives: Array.from(compilationDirectives.values()),
pipes: Array.from(compilationPipes.values()),
ngModules: Array.from(compilationModules),
},
exported,
reexports,
schemas: ngModule.schemas,
};
this.cache.set(ref.node, scope);
return scope;
}
/**
* Check whether a component requires remote scoping.
*/
getRequiresRemoteScope(node: ClassDeclaration): boolean {
return this.remoteScoping.has(node);
}
/**
* Set a component as requiring remote scoping.
*/
setComponentAsRequiringRemoteScoping(node: ClassDeclaration): void {
this.remoteScoping.add(node);
}
/**
* Look up the `ExportScope` of a given `Reference` to an NgModule.
*
* The NgModule in question may be declared locally in the current ts.Program, or it may be
* declared in a .d.ts file.
*
* @returns `null` if no scope could be found, or `undefined` if an invalid scope
* was found.
*
* May also contribute diagnostics of its own by adding to the given `diagnostics`
* array parameter.
*/
private getExportedScope(
ref: Reference<ClassDeclaration>, diagnostics: ts.Diagnostic[],
ownerForErrors: ts.Declaration, type: 'import'|'export'): ExportScope|null|undefined {
if (ref.node.getSourceFile().isDeclarationFile) {
// The NgModule is declared in a .d.ts file. Resolve it with the `DependencyScopeReader`.
if (!ts.isClassDeclaration(ref.node)) {
// The NgModule is in a .d.ts file but is not declared as a ts.ClassDeclaration. This is an
// error in the .d.ts metadata.
const code = type === 'import' ? ErrorCode.NGMODULE_INVALID_IMPORT :
ErrorCode.NGMODULE_INVALID_EXPORT;
diagnostics.push(makeDiagnostic(
code, identifierOfNode(ref.node) || ref.node,
`Appears in the NgModule.${type}s of ${
nodeNameForError(ownerForErrors)}, but could not be resolved to an NgModule`));
return undefined;
}
return this.dependencyScopeReader.resolve(ref);
} else {
// The NgModule is declared locally in the current program. Resolve it from the registry.
return this.getScopeOfModuleReference(ref);
}
}
private getReexports(
ngModule: NgModuleMeta, ref: Reference<ClassDeclaration>, declared: Set<ts.Declaration>,
exported: {directives: DirectiveMeta[], pipes: PipeMeta[]},
diagnostics: ts.Diagnostic[]): Reexport[]|null {
let reexports: Reexport[]|null = null;
const sourceFile = ref.node.getSourceFile();
if (this.aliasingHost === null) {
return null;
}
reexports = [];
// Track re-exports by symbol name, to produce diagnostics if two alias re-exports would share
// the same name.
const reexportMap = new Map<string, Reference<ClassDeclaration>>();
// Alias ngModuleRef added for readability below.
const ngModuleRef = ref;
const addReexport = (exportRef: Reference<ClassDeclaration>) => {
if (exportRef.node.getSourceFile() === sourceFile) {
return;
}
const isReExport = !declared.has(exportRef.node);
const exportName = this.aliasingHost!.maybeAliasSymbolAs(
exportRef, sourceFile, ngModule.ref.node.name.text, isReExport);
if (exportName === null) {
return;
}
if (!reexportMap.has(exportName)) {
if (exportRef.alias && exportRef.alias instanceof ExternalExpr) {
reexports!.push({
fromModule: exportRef.alias.value.moduleName!,
symbolName: exportRef.alias.value.name!,
asAlias: exportName,
});
} else {
const expr = this.refEmitter.emit(exportRef.cloneWithNoIdentifiers(), sourceFile);
if (!(expr instanceof ExternalExpr) || expr.value.moduleName === null ||
expr.value.name === null) {
throw new Error('Expected ExternalExpr');
}
reexports!.push({
fromModule: expr.value.moduleName,
symbolName: expr.value.name,
asAlias: exportName,
});
}
reexportMap.set(exportName, exportRef);
} else {
// Another re-export already used this name. Produce a diagnostic.
const prevRef = reexportMap.get(exportName)!;
diagnostics.push(reexportCollision(ngModuleRef.node, prevRef, exportRef));
}
};
for (const {ref} of exported.directives) {
addReexport(ref);
}
for (const {ref} of exported.pipes) {
addReexport(ref);
}
return reexports;
}
private assertCollecting(): void {
if (this.sealed) {
throw new Error(`Assertion: LocalModuleScopeRegistry is not COLLECTING`);
}
}
}
/**
* Produce a `ts.Diagnostic` for an invalid import or export from an NgModule.
*/
function invalidRef(
clazz: ts.Declaration, decl: Reference<ts.Declaration>,
type: 'import'|'export'): ts.Diagnostic {
const code =
type === 'import' ? ErrorCode.NGMODULE_INVALID_IMPORT : ErrorCode.NGMODULE_INVALID_EXPORT;
const resolveTarget = type === 'import' ? 'NgModule' : 'NgModule, Component, Directive, or Pipe';
let message =
`Appears in the NgModule.${type}s of ${
nodeNameForError(clazz)}, but could not be resolved to an ${resolveTarget} class.` +
'\n\n';
const library = decl.ownedByModuleGuess !== null ? ` (${decl.ownedByModuleGuess})` : '';
const sf = decl.node.getSourceFile();
// Provide extra context to the error for the user.
if (!sf.isDeclarationFile) {
// This is a file in the user's program.
const annotationType = type === 'import' ? '@NgModule' : 'Angular';
message += `Is it missing an ${annotationType} annotation?`;
} else if (sf.fileName.indexOf('node_modules') !== -1) {
// This file comes from a third-party library in node_modules.
message +=
`This likely means that the library${library} which declares ${decl.debugName} has not ` +
'been processed correctly by ngcc, or is not compatible with Angular Ivy. Check if a ' +
'newer version of the library is available, and update if so. Also consider checking ' +
'with the library\'s authors to see if the library is expected to be compatible with Ivy.';
} else {
// This is a monorepo style local dependency. Unfortunately these are too different to really
// offer much more advice than this.
message += `This likely means that the dependency${library} which declares ${
decl.debugName} has not been processed correctly by ngcc.`;
}
return makeDiagnostic(code, identifierOfNode(decl.node) || decl.node, message);
}
/**
* Produce a `ts.Diagnostic` for an import or export which itself has errors.
*/
function invalidTransitiveNgModuleRef(
clazz: ts.Declaration, decl: Reference<ts.Declaration>,
type: 'import'|'export'): ts.Diagnostic {
const code =
type === 'import' ? ErrorCode.NGMODULE_INVALID_IMPORT : ErrorCode.NGMODULE_INVALID_EXPORT;
return makeDiagnostic(
code, identifierOfNode(decl.node) || decl.node,
`Appears in the NgModule.${type}s of ${nodeNameForError(clazz)}, but itself has errors`);
}
/**
* Produce a `ts.Diagnostic` for an exported directive or pipe which was not declared or imported
* by the NgModule in question.
*/
function invalidReexport(clazz: ts.Declaration, decl: Reference<ts.Declaration>): ts.Diagnostic {
return makeDiagnostic(
ErrorCode.NGMODULE_INVALID_REEXPORT, identifierOfNode(decl.node) || decl.node,
`Present in the NgModule.exports of ${
nodeNameForError(clazz)} but neither declared nor imported`);
}
/**
* Produce a `ts.Diagnostic` for a collision in re-export names between two directives/pipes.
*/
function reexportCollision(
module: ClassDeclaration, refA: Reference<ClassDeclaration>,
refB: Reference<ClassDeclaration>): ts.Diagnostic {
const childMessageText = `This directive/pipe is part of the exports of '${
module.name.text}' and shares the same name as another exported directive/pipe.`;
return makeDiagnostic(
ErrorCode.NGMODULE_REEXPORT_NAME_COLLISION, module.name,
`
There was a name collision between two classes named '${
refA.node.name.text}', which are both part of the exports of '${module.name.text}'.
Angular generates re-exports of an NgModule's exported directives/pipes from the module's source file in certain cases, using the declared name of the class. If two classes of the same name are exported, this automatic naming does not work.
To fix this problem please re-export one or both classes directly from this file.
`.trim(),
[
makeRelatedInformation(refA.node.name, childMessageText),
makeRelatedInformation(refB.node.name, childMessageText),
]);
}
export interface DeclarationData {
ngModule: ClassDeclaration;
ref: Reference;
rawDeclarations: ts.Expression|null;
}