From 1964be0b17b7393bea8a8ae2f4f1984836cce24c Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Fri, 16 Nov 2018 17:56:18 +0100 Subject: [PATCH] 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 --- packages/compiler-cli/BUILD.bazel | 1 + .../ngcc/src/analysis/decoration_analyzer.ts | 2 +- .../src/ngtsc/annotations/BUILD.bazel | 1 + .../src/ngtsc/annotations/src/ng_module.ts | 23 +++++++---- packages/compiler-cli/src/ngtsc/program.ts | 16 +++++++- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 39 +++++++++++++++++++ 6 files changed, 72 insertions(+), 10 deletions(-) diff --git a/packages/compiler-cli/BUILD.bazel b/packages/compiler-cli/BUILD.bazel index 43691c1bf0..78390cb36e 100644 --- a/packages/compiler-cli/BUILD.bazel +++ b/packages/compiler-cli/BUILD.bazel @@ -29,6 +29,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/imports", "//packages/compiler-cli/src/ngtsc/partial_evaluator", "//packages/compiler-cli/src/ngtsc/reflection", + "//packages/compiler-cli/src/ngtsc/routing", "//packages/compiler-cli/src/ngtsc/shims", "//packages/compiler-cli/src/ngtsc/switch", "//packages/compiler-cli/src/ngtsc/transform", diff --git a/packages/compiler-cli/src/ngcc/src/analysis/decoration_analyzer.ts b/packages/compiler-cli/src/ngcc/src/analysis/decoration_analyzer.ts index 4961707644..5745516138 100644 --- a/packages/compiler-cli/src/ngcc/src/analysis/decoration_analyzer.ts +++ b/packages/compiler-cli/src/ngcc/src/analysis/decoration_analyzer.ts @@ -76,7 +76,7 @@ export class DecorationAnalyzer { new InjectableDecoratorHandler(this.reflectionHost, this.isCore), new NgModuleDecoratorHandler( this.reflectionHost, this.evaluator, this.scopeRegistry, this.referencesRegistry, - this.isCore), + this.isCore, /* routeAnalyzer */ null), new PipeDecoratorHandler(this.reflectionHost, this.evaluator, this.scopeRegistry, this.isCore), ]; diff --git a/packages/compiler-cli/src/ngtsc/annotations/BUILD.bazel b/packages/compiler-cli/src/ngtsc/annotations/BUILD.bazel index 5cc3cdc8bc..8ccbfcec29 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/annotations/BUILD.bazel @@ -14,6 +14,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/imports", "//packages/compiler-cli/src/ngtsc/partial_evaluator", "//packages/compiler-cli/src/ngtsc/reflection", + "//packages/compiler-cli/src/ngtsc/routing", "//packages/compiler-cli/src/ngtsc/transform", "//packages/compiler-cli/src/ngtsc/typecheck", "@ngdeps//@types/node", diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index 53aa832b04..b19df3c058 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -13,6 +13,7 @@ import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {Reference, ResolvedReference} from '../../imports'; import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator'; import {Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection'; +import {NgModuleRouteAnalyzer} from '../../routing'; import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; import {generateSetClassMetadataCall} from './metadata'; @@ -35,7 +36,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler[] = []; + let rawImports: ts.Expression|null = null; if (ngModule.has('imports')) { - const expr = ngModule.get('imports') !; + rawImports = ngModule.get('imports') !; const importsMeta = this.evaluator.evaluate( - expr, ref => this._extractModuleFromModuleWithProvidersFn(ref.node)); - imports = this.resolveTypeList(expr, importsMeta, 'imports'); + rawImports, ref => this._extractModuleFromModuleWithProvidersFn(ref.node)); + imports = this.resolveTypeList(rawImports, importsMeta, 'imports'); } let exports: Reference[] = []; + let rawExports: ts.Expression|null = null; if (ngModule.has('exports')) { - const expr = ngModule.get('exports') !; + rawExports = ngModule.get('exports') !; const exportsMeta = this.evaluator.evaluate( - expr, ref => this._extractModuleFromModuleWithProvidersFn(ref.node)); - exports = this.resolveTypeList(expr, exportsMeta, 'exports'); + rawExports, ref => this._extractModuleFromModuleWithProvidersFn(ref.node)); + exports = this.resolveTypeList(rawExports, exportsMeta, 'exports'); this.referencesRegistry.add(node, ...exports); } let bootstrap: Reference[] = []; @@ -123,6 +126,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler[] = []; if (ngModule.has('imports')) { @@ -132,6 +136,11 @@ export class NgModuleDecoratorHandler implements DecoratorHandler => 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 { throw new Error('Method not implemented.'); @@ -290,6 +299,8 @@ export class NgtscProgram implements api.Program { referencesRegistry = new NoopReferencesRegistry(); } + this.routeAnalyzer = new NgModuleRouteAnalyzer(this.moduleResolver, evaluator); + // Set up the IvyCompilation, which manages state for the Ivy transformer. const handlers = [ new BaseDefDecoratorHandler(this.reflector, evaluator), @@ -300,7 +311,8 @@ export class NgtscProgram implements api.Program { new DirectiveDecoratorHandler(this.reflector, evaluator, scopeRegistry, this.isCore), new InjectableDecoratorHandler(this.reflector, this.isCore), 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), ]; diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index cff67d3790..02e6b1aac9 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -1949,6 +1949,45 @@ describe('ngtsc behavioral tests', () => { 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; + static forChild(arg1: any): ModuleWithProviders; + } + `); + + 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(