feat(ivy): implement listLazyRoutes() for ngtsc (#27697)

This commit uses the NgModuleRouteAnalyzer introduced previously to
implement listLazyRoutes() for NgtscProgram. Currently this implementation
is limited to listing routes globally and cannot list routes for a given lazy
module. Testing seems to indicate that the CLI uses the global form, but this
should be verified.

Jira issue: FW-629

PR Close #27697
This commit is contained in:
Alex Rickabaugh 2018-11-16 17:56:18 +01:00
parent 41b2499f17
commit 1964be0b17
6 changed files with 72 additions and 10 deletions

View File

@ -29,6 +29,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/imports", "//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/partial_evaluator", "//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/routing",
"//packages/compiler-cli/src/ngtsc/shims", "//packages/compiler-cli/src/ngtsc/shims",
"//packages/compiler-cli/src/ngtsc/switch", "//packages/compiler-cli/src/ngtsc/switch",
"//packages/compiler-cli/src/ngtsc/transform", "//packages/compiler-cli/src/ngtsc/transform",

View File

@ -76,7 +76,7 @@ export class DecorationAnalyzer {
new InjectableDecoratorHandler(this.reflectionHost, this.isCore), new InjectableDecoratorHandler(this.reflectionHost, this.isCore),
new NgModuleDecoratorHandler( new NgModuleDecoratorHandler(
this.reflectionHost, this.evaluator, this.scopeRegistry, this.referencesRegistry, this.reflectionHost, this.evaluator, this.scopeRegistry, this.referencesRegistry,
this.isCore), this.isCore, /* routeAnalyzer */ null),
new PipeDecoratorHandler(this.reflectionHost, this.evaluator, this.scopeRegistry, this.isCore), new PipeDecoratorHandler(this.reflectionHost, this.evaluator, this.scopeRegistry, this.isCore),
]; ];

View File

@ -14,6 +14,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/imports", "//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/partial_evaluator", "//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/routing",
"//packages/compiler-cli/src/ngtsc/transform", "//packages/compiler-cli/src/ngtsc/transform",
"//packages/compiler-cli/src/ngtsc/typecheck", "//packages/compiler-cli/src/ngtsc/typecheck",
"@ngdeps//@types/node", "@ngdeps//@types/node",

View File

@ -13,6 +13,7 @@ import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {Reference, ResolvedReference} from '../../imports'; import {Reference, ResolvedReference} from '../../imports';
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator'; import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
import {Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection'; import {Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
import {NgModuleRouteAnalyzer} from '../../routing';
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
import {generateSetClassMetadataCall} from './metadata'; import {generateSetClassMetadataCall} from './metadata';
@ -35,7 +36,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
constructor( constructor(
private reflector: ReflectionHost, private evaluator: PartialEvaluator, private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private scopeRegistry: SelectorScopeRegistry, private referencesRegistry: ReferencesRegistry, private scopeRegistry: SelectorScopeRegistry, private referencesRegistry: ReferencesRegistry,
private isCore: boolean) {} private isCore: boolean, private routeAnalyzer: NgModuleRouteAnalyzer|null) {}
detect(node: ts.Declaration, decorators: Decorator[]|null): Decorator|undefined { detect(node: ts.Declaration, decorators: Decorator[]|null): Decorator|undefined {
if (!decorators) { if (!decorators) {
@ -77,18 +78,20 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
declarations = this.resolveTypeList(expr, declarationMeta, 'declarations'); declarations = this.resolveTypeList(expr, declarationMeta, 'declarations');
} }
let imports: Reference<ts.Declaration>[] = []; let imports: Reference<ts.Declaration>[] = [];
let rawImports: ts.Expression|null = null;
if (ngModule.has('imports')) { if (ngModule.has('imports')) {
const expr = ngModule.get('imports') !; rawImports = ngModule.get('imports') !;
const importsMeta = this.evaluator.evaluate( const importsMeta = this.evaluator.evaluate(
expr, ref => this._extractModuleFromModuleWithProvidersFn(ref.node)); rawImports, ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
imports = this.resolveTypeList(expr, importsMeta, 'imports'); imports = this.resolveTypeList(rawImports, importsMeta, 'imports');
} }
let exports: Reference<ts.Declaration>[] = []; let exports: Reference<ts.Declaration>[] = [];
let rawExports: ts.Expression|null = null;
if (ngModule.has('exports')) { if (ngModule.has('exports')) {
const expr = ngModule.get('exports') !; rawExports = ngModule.get('exports') !;
const exportsMeta = this.evaluator.evaluate( const exportsMeta = this.evaluator.evaluate(
expr, ref => this._extractModuleFromModuleWithProvidersFn(ref.node)); rawExports, ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
exports = this.resolveTypeList(expr, exportsMeta, 'exports'); exports = this.resolveTypeList(rawExports, exportsMeta, 'exports');
this.referencesRegistry.add(node, ...exports); this.referencesRegistry.add(node, ...exports);
} }
let bootstrap: Reference<ts.Declaration>[] = []; let bootstrap: Reference<ts.Declaration>[] = [];
@ -123,6 +126,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
const providers: Expression = ngModule.has('providers') ? const providers: Expression = ngModule.has('providers') ?
new WrappedNodeExpr(ngModule.get('providers') !) : new WrappedNodeExpr(ngModule.get('providers') !) :
new LiteralArrayExpr([]); new LiteralArrayExpr([]);
const rawProviders = ngModule.has('providers') ? ngModule.get('providers') ! : null;
const injectorImports: WrappedNodeExpr<ts.Expression>[] = []; const injectorImports: WrappedNodeExpr<ts.Expression>[] = [];
if (ngModule.has('imports')) { if (ngModule.has('imports')) {
@ -132,6 +136,11 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
injectorImports.push(new WrappedNodeExpr(ngModule.get('exports') !)); injectorImports.push(new WrappedNodeExpr(ngModule.get('exports') !));
} }
if (this.routeAnalyzer !== null) {
this.routeAnalyzer.add(
node.getSourceFile(), node.name !.text, rawImports, rawExports, rawProviders);
}
const ngInjectorDef: R3InjectorMetadata = { const ngInjectorDef: R3InjectorMetadata = {
name: node.name !.text, name: node.name !.text,
type: new WrappedNodeExpr(node.name !), type: new WrappedNodeExpr(node.name !),

View File

@ -20,6 +20,7 @@ import {ImportRewriter, ModuleResolver, NoopImportRewriter, R3SymbolsImportRewri
import {PartialEvaluator} from './partial_evaluator'; import {PartialEvaluator} from './partial_evaluator';
import {TypeScriptReflectionHost} from './reflection'; import {TypeScriptReflectionHost} from './reflection';
import {HostResourceLoader} from './resource_loader'; import {HostResourceLoader} from './resource_loader';
import {NgModuleRouteAnalyzer} from './routing';
import {FactoryGenerator, FactoryInfo, GeneratedShimsHostWrapper, ShimGenerator, SummaryGenerator, generatedFactoryTransform} from './shims'; import {FactoryGenerator, FactoryInfo, GeneratedShimsHostWrapper, ShimGenerator, SummaryGenerator, generatedFactoryTransform} from './shims';
import {ivySwitchTransform} from './switch'; import {ivySwitchTransform} from './switch';
import {IvyCompilation, ivyTransformFactory} from './transform'; import {IvyCompilation, ivyTransformFactory} from './transform';
@ -43,6 +44,7 @@ export class NgtscProgram implements api.Program {
private entryPoint: ts.SourceFile|null; private entryPoint: ts.SourceFile|null;
private exportReferenceGraph: ReferenceGraph|null = null; private exportReferenceGraph: ReferenceGraph|null = null;
private flatIndexGenerator: FlatIndexGenerator|null = null; private flatIndexGenerator: FlatIndexGenerator|null = null;
private routeAnalyzer: NgModuleRouteAnalyzer|null = null;
private constructionDiagnostics: ts.Diagnostic[] = []; private constructionDiagnostics: ts.Diagnostic[] = [];
private moduleResolver: ModuleResolver; private moduleResolver: ModuleResolver;
@ -184,7 +186,14 @@ export class NgtscProgram implements api.Program {
.filter((result): result is Promise<void> => result !== undefined)); .filter((result): result is Promise<void> => result !== undefined));
} }
listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] { return []; } listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] {
if (entryRoute !== undefined) {
throw new Error(
`Listing specific routes is unsupported for now (got query for ${entryRoute})`);
}
this.ensureAnalyzed();
return this.routeAnalyzer !.listLazyRoutes();
}
getLibrarySummaries(): Map<string, api.LibrarySummary> { getLibrarySummaries(): Map<string, api.LibrarySummary> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
@ -290,6 +299,8 @@ export class NgtscProgram implements api.Program {
referencesRegistry = new NoopReferencesRegistry(); referencesRegistry = new NoopReferencesRegistry();
} }
this.routeAnalyzer = new NgModuleRouteAnalyzer(this.moduleResolver, evaluator);
// Set up the IvyCompilation, which manages state for the Ivy transformer. // Set up the IvyCompilation, which manages state for the Ivy transformer.
const handlers = [ const handlers = [
new BaseDefDecoratorHandler(this.reflector, evaluator), new BaseDefDecoratorHandler(this.reflector, evaluator),
@ -300,7 +311,8 @@ export class NgtscProgram implements api.Program {
new DirectiveDecoratorHandler(this.reflector, evaluator, scopeRegistry, this.isCore), new DirectiveDecoratorHandler(this.reflector, evaluator, scopeRegistry, this.isCore),
new InjectableDecoratorHandler(this.reflector, this.isCore), new InjectableDecoratorHandler(this.reflector, this.isCore),
new NgModuleDecoratorHandler( new NgModuleDecoratorHandler(
this.reflector, evaluator, scopeRegistry, referencesRegistry, this.isCore), this.reflector, evaluator, scopeRegistry, referencesRegistry, this.isCore,
this.routeAnalyzer),
new PipeDecoratorHandler(this.reflector, evaluator, scopeRegistry, this.isCore), new PipeDecoratorHandler(this.reflector, evaluator, scopeRegistry, this.isCore),
]; ];

View File

@ -1949,6 +1949,45 @@ describe('ngtsc behavioral tests', () => {
expect(trim(jsContents)).toContain(trim(hostBindingsFn)); expect(trim(jsContents)).toContain(trim(hostBindingsFn));
}); });
}); });
it('should detect all lazy routes', () => {
env.tsconfig();
env.write('test.ts', `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [
RouterModule.forChild([
{path: '', loadChildren: './lazy#LazyModule'},
]),
],
})
export class TestModule {}
`);
env.write('lazy.ts', `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({})
export class LazyModule {}
`);
env.write('node_modules/@angular/router/index.d.ts', `
import {ModuleWithProviders} from '@angular/core';
export declare var ROUTES;
export declare class RouterModule {
static forRoot(arg1: any, arg2: any): ModuleWithProviders<RouterModule>;
static forChild(arg1: any): ModuleWithProviders<RouterModule>;
}
`);
const routes = env.driveRoutes();
expect(routes.length).toBe(1);
expect(routes[0].route).toEqual('./lazy#LazyModule');
expect(routes[0].module.filePath.endsWith('/test.ts')).toBe(true);
expect(routes[0].referencedModule.filePath.endsWith('/lazy.ts')).toBe(true);
});
}); });
function expectTokenAtPosition<T extends ts.Node>( function expectTokenAtPosition<T extends ts.Node>(