feat(ivy): produce diagnostics for missing exports, incorrect entrypoint (#27743)
This commit adds tracking of modules, directives, and pipes which are made visible to consumers through NgModules exported from the package entrypoint. ngtsc will now produce a diagnostic if such classes are not themselves exported via the entrypoint (as this is a requirement for downstream consumers to use them with Ivy). To accomplish this, a graph of references is created and populated via the ReferencesRegistry. Symbols exported via the package entrypoint are compared against the graph to determine if any publicly visible symbols are not properly exported. Diagnostics are produced for each one which also show the path by which they become visible. This commit also introduces a diagnostic (instead of a hard compiler crash) if an entrypoint file cannot be correctly determined. PR Close #27743
This commit is contained in:

committed by
Kara Erickson

parent
ac157170c8
commit
0b9094ec63
@ -12,9 +12,11 @@ import * as ts from 'typescript';
|
||||
import * as api from '../transformers/api';
|
||||
import {nocollapseHack} from '../transformers/nocollapse_hack';
|
||||
|
||||
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, NoopReferencesRegistry, PipeDecoratorHandler, ResourceLoader, SelectorScopeRegistry} from './annotations';
|
||||
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, NoopReferencesRegistry, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader, SelectorScopeRegistry} from './annotations';
|
||||
import {BaseDefDecoratorHandler} from './annotations/src/base_def';
|
||||
import {FlatIndexGenerator} from './entry_point';
|
||||
import {ErrorCode, ngErrorCode} from './diagnostics';
|
||||
import {FlatIndexGenerator, ReferenceGraph, checkForPrivateExports, findFlatIndexEntryPoint} from './entry_point';
|
||||
import {Reference} from './imports';
|
||||
import {PartialEvaluator} from './partial_evaluator';
|
||||
import {TypeScriptReflectionHost} from './reflection';
|
||||
import {FileResourceLoader, HostResourceLoader} from './resource_loader';
|
||||
@ -22,6 +24,7 @@ import {FactoryGenerator, FactoryInfo, GeneratedShimsHostWrapper, ShimGenerator,
|
||||
import {ivySwitchTransform} from './switch';
|
||||
import {IvyCompilation, ivyTransformFactory} from './transform';
|
||||
import {TypeCheckContext, TypeCheckProgramHost} from './typecheck';
|
||||
import {isDtsPath} from './util/src/typescript';
|
||||
|
||||
export class NgtscProgram implements api.Program {
|
||||
private tsProgram: ts.Program;
|
||||
@ -35,6 +38,11 @@ export class NgtscProgram implements api.Program {
|
||||
private _isCore: boolean|undefined = undefined;
|
||||
private rootDirs: string[];
|
||||
private closureCompilerEnabled: boolean;
|
||||
private entryPoint: ts.SourceFile|null;
|
||||
private exportReferenceGraph: ReferenceGraph|null = null;
|
||||
private flatIndexGenerator: FlatIndexGenerator|null = null;
|
||||
|
||||
private constructionDiagnostics: ts.Diagnostic[] = [];
|
||||
|
||||
|
||||
constructor(
|
||||
@ -79,14 +87,10 @@ export class NgtscProgram implements api.Program {
|
||||
generators.push(summaryGenerator, factoryGenerator);
|
||||
}
|
||||
|
||||
let entryPoint: string|null = null;
|
||||
if (options.flatModuleOutFile !== undefined) {
|
||||
const flatModuleId = options.flatModuleId || null;
|
||||
const flatIndexGenerator =
|
||||
FlatIndexGenerator.forRootFiles(options.flatModuleOutFile, rootNames, flatModuleId);
|
||||
if (flatIndexGenerator !== null) {
|
||||
generators.push(flatIndexGenerator);
|
||||
rootFiles.push(flatIndexGenerator.flatIndexPath);
|
||||
} else {
|
||||
entryPoint = findFlatIndexEntryPoint(rootNames);
|
||||
if (entryPoint === null) {
|
||||
// This error message talks specifically about having a single .ts file in "files". However
|
||||
// the actual logic is a bit more permissive. If a single file exists, that will be taken,
|
||||
// otherwise the highest level (shortest path) "index.ts" file will be used as the flat
|
||||
@ -95,8 +99,21 @@ export class NgtscProgram implements api.Program {
|
||||
//
|
||||
// The user is not informed about the "index.ts" option as this behavior is deprecated -
|
||||
// an explicit entrypoint should always be specified.
|
||||
throw new Error(
|
||||
'Angular compiler option "flatModuleIndex" requires one and only one .ts file in the "files" field.');
|
||||
this.constructionDiagnostics.push({
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
code: ngErrorCode(ErrorCode.CONFIG_FLAT_MODULE_NO_INDEX),
|
||||
file: undefined,
|
||||
start: undefined,
|
||||
length: undefined,
|
||||
messageText:
|
||||
'Angular compiler option "flatModuleOutFile" requires one and only one .ts file in the "files" field.',
|
||||
});
|
||||
} else {
|
||||
const flatModuleId = options.flatModuleId || null;
|
||||
this.flatIndexGenerator =
|
||||
new FlatIndexGenerator(entryPoint, options.flatModuleOutFile, flatModuleId);
|
||||
generators.push(this.flatIndexGenerator);
|
||||
rootFiles.push(this.flatIndexGenerator.flatIndexPath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,6 +123,8 @@ export class NgtscProgram implements api.Program {
|
||||
|
||||
this.tsProgram =
|
||||
ts.createProgram(rootFiles, options, this.host, oldProgram && oldProgram.getTsProgram());
|
||||
|
||||
this.entryPoint = entryPoint !== null ? this.tsProgram.getSourceFile(entryPoint) || null : null;
|
||||
}
|
||||
|
||||
getTsProgram(): ts.Program { return this.tsProgram; }
|
||||
@ -116,8 +135,8 @@ export class NgtscProgram implements api.Program {
|
||||
}
|
||||
|
||||
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
||||
undefined): ReadonlyArray<api.Diagnostic> {
|
||||
return [];
|
||||
undefined): ReadonlyArray<ts.Diagnostic|api.Diagnostic> {
|
||||
return this.constructionDiagnostics;
|
||||
}
|
||||
|
||||
getTsSyntacticDiagnostics(
|
||||
@ -147,6 +166,10 @@ export class NgtscProgram implements api.Program {
|
||||
compilation.typeCheck(ctx);
|
||||
diagnostics.push(...this.compileTypeCheckProgram(ctx));
|
||||
}
|
||||
if (this.entryPoint !== null && this.exportReferenceGraph !== null) {
|
||||
diagnostics.push(...checkForPrivateExports(
|
||||
this.entryPoint, this.tsProgram.getTypeChecker(), this.exportReferenceGraph));
|
||||
}
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
@ -253,7 +276,17 @@ export class NgtscProgram implements api.Program {
|
||||
const checker = this.tsProgram.getTypeChecker();
|
||||
const evaluator = new PartialEvaluator(this.reflector, checker);
|
||||
const scopeRegistry = new SelectorScopeRegistry(checker, this.reflector);
|
||||
const referencesRegistry = new NoopReferencesRegistry();
|
||||
|
||||
// If a flat module entrypoint was specified, then track references via a `ReferenceGraph` in
|
||||
// order to produce proper diagnostics for incorrectly exported directives/pipes/etc. If there
|
||||
// is no flat module entrypoint then don't pay the cost of tracking references.
|
||||
let referencesRegistry: ReferencesRegistry;
|
||||
if (this.entryPoint !== null) {
|
||||
this.exportReferenceGraph = new ReferenceGraph();
|
||||
referencesRegistry = new ReferenceGraphAdapter(this.exportReferenceGraph);
|
||||
} else {
|
||||
referencesRegistry = new NoopReferencesRegistry();
|
||||
}
|
||||
|
||||
// Set up the IvyCompilation, which manages state for the Ivy transformer.
|
||||
const handlers = [
|
||||
@ -355,3 +388,21 @@ function isAngularCorePackage(program: ts.Program): boolean {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export class ReferenceGraphAdapter implements ReferencesRegistry {
|
||||
constructor(private graph: ReferenceGraph) {}
|
||||
|
||||
add(source: ts.Declaration, ...references: Reference<ts.Declaration>[]): void {
|
||||
for (const {node} of references) {
|
||||
let sourceFile = node.getSourceFile();
|
||||
if (sourceFile === undefined) {
|
||||
sourceFile = ts.getOriginalNode(node).getSourceFile();
|
||||
}
|
||||
|
||||
// Only record local references (not references into .d.ts files).
|
||||
if (sourceFile === undefined || !isDtsPath(sourceFile.fileName)) {
|
||||
this.graph.add(source, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user