refactor(ivy): ngcc - `DecorationAnalyzer` acts on whole program (#26082)

PR Close #26082
This commit is contained in:
Pete Bacon Darwin 2018-09-27 18:22:36 +01:00 committed by Miško Hevery
parent 9562324ea4
commit f7b17a4784
2 changed files with 37 additions and 39 deletions

View File

@ -30,6 +30,9 @@ export interface DecorationAnalysis {
constantPool: ConstantPool; constantPool: ConstantPool;
} }
export type DecorationAnalyses = Map<ts.SourceFile, DecorationAnalysis>;
export const DecorationAnalyses = Map;
export interface MatchingHandler<A, M> { export interface MatchingHandler<A, M> {
handler: DecoratorHandler<A, M>; handler: DecoratorHandler<A, M>;
match: M; match: M;
@ -63,12 +66,30 @@ export class DecorationAnalyzer {
private typeChecker: ts.TypeChecker, private host: NgccReflectionHost, private typeChecker: ts.TypeChecker, private host: NgccReflectionHost,
private rootDirs: string[], private isCore: boolean) {} private rootDirs: string[], private isCore: boolean) {}
/**
* Analyze a program to find all the decorated files should be transformed.
* @param program The program whose files should be analysed.
* @returns a map of the source files to the analysis for those files.
*/
analyzeProgram(program: ts.Program): DecorationAnalyses {
const analyzedFiles = new DecorationAnalyses();
program.getRootFileNames().forEach(fileName => {
const entryPoint = program.getSourceFile(fileName) !;
const decoratedFiles = this.host.findDecoratedFiles(entryPoint);
decoratedFiles.forEach(
decoratedFile =>
analyzedFiles.set(decoratedFile.sourceFile, this.analyzeFile(decoratedFile)));
});
return analyzedFiles;
}
/** /**
* Analyze a decorated file to generate the information about decorated classes that * Analyze a decorated file to generate the information about decorated classes that
* should be converted to use ivy definitions. * should be converted to use ivy definitions.
* @param file The file to be analysed for decorated classes. * @param file The file to be analysed for decorated classes.
* @returns the analysis of the file
*/ */
analyzeFile(file: DecoratedFile): DecorationAnalysis { protected analyzeFile(file: DecoratedFile): DecorationAnalysis {
const constantPool = new ConstantPool(); const constantPool = new ConstantPool();
const analyzedClasses = const analyzedClasses =
file.decoratedClasses.map(clazz => this.analyzeClass(constantPool, clazz)) file.decoratedClasses.map(clazz => this.analyzeClass(constantPool, clazz))

View File

@ -9,23 +9,21 @@ import * as ts from 'typescript';
import {Decorator} from '../../../ngtsc/host'; import {Decorator} from '../../../ngtsc/host';
import {DecoratorHandler} from '../../../ngtsc/transform'; import {DecoratorHandler} from '../../../ngtsc/transform';
import {DecorationAnalysis, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {DecorationAnalyses, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {DecoratedClass} from '../../src/host/decorated_class';
import {DecoratedFile} from '../../src/host/decorated_file';
import {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host'; import {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host';
import {getDeclaration, makeProgram} from '../helpers/utils'; import {makeProgram} from '../helpers/utils';
const TEST_PROGRAM = { const TEST_PROGRAM = {
name: 'test.js', name: 'test.js',
contents: ` contents: `
import {Component, Injectable} from '@angular/core'; import {Component, Injectable} from '@angular/core';
@Component()
export class MyComponent {} export class MyComponent {}
MyComponent.decorators = [{type: Component}];
@Injectable()
export class MyService {} export class MyService {}
MyService.decorators = [{type: Injectable}];
` `
}; };
@ -50,49 +48,26 @@ function createTestHandler() {
return handler; return handler;
} }
function createParsedFile(program: ts.Program) {
const file = new DecoratedFile(program.getSourceFile('test.js') !);
const componentClass = getDeclaration(program, 'test.js', 'MyComponent', ts.isClassDeclaration);
file.decoratedClasses.push(
new DecoratedClass('MyComponent', {} as any, [{
name: 'Component',
import: {from: '@angular/core', name: 'Component'},
node: null as any,
args: null
}]));
const serviceClass = getDeclaration(program, 'test.js', 'MyService', ts.isClassDeclaration);
file.decoratedClasses.push(
new DecoratedClass('MyService', {} as any, [{
name: 'Injectable',
import: {from: '@angular/core', name: 'Injectable'},
node: null as any,
args: null
}]));
return file;
}
describe('DecorationAnalyzer', () => { describe('DecorationAnalyzer', () => {
describe('analyzeFile()', () => { describe('analyzeProgram()', () => {
let program: ts.Program; let program: ts.Program;
let testHandler: jasmine.SpyObj<DecoratorHandler<any, any>>; let testHandler: jasmine.SpyObj<DecoratorHandler<any, any>>;
let result: DecorationAnalysis; let result: DecorationAnalyses;
beforeEach(() => { beforeEach(() => {
program = makeProgram(TEST_PROGRAM); program = makeProgram(TEST_PROGRAM);
const file = createParsedFile(program);
const analyzer = new DecorationAnalyzer( const analyzer = new DecorationAnalyzer(
program.getTypeChecker(), new Fesm2015ReflectionHost(false, program.getTypeChecker()), program.getTypeChecker(), new Fesm2015ReflectionHost(false, program.getTypeChecker()),
[''], false); [''], false);
testHandler = createTestHandler(); testHandler = createTestHandler();
analyzer.handlers = [testHandler]; analyzer.handlers = [testHandler];
result = analyzer.analyzeFile(file); result = analyzer.analyzeProgram(program);
}); });
it('should return an object containing a reference to the original source file', it('should return an object containing a reference to the original source file', () => {
() => { expect(result.sourceFile).toBe(program.getSourceFile('test.js') !); }); const file = program.getSourceFile(TEST_PROGRAM.name) !;
expect(result.get(file) !.sourceFile).toBe(file);
});
it('should call detect on the decorator handlers with each class from the parsed file', () => { it('should call detect on the decorator handlers with each class from the parsed file', () => {
expect(testHandler.detect).toHaveBeenCalledTimes(2); expect(testHandler.detect).toHaveBeenCalledTimes(2);
@ -103,8 +78,10 @@ describe('DecorationAnalyzer', () => {
}); });
it('should return an object containing the classes that were analyzed', () => { it('should return an object containing the classes that were analyzed', () => {
expect(result.analyzedClasses.length).toEqual(1); const file = program.getSourceFile(TEST_PROGRAM.name) !;
expect(result.analyzedClasses[0].name).toEqual('MyComponent'); const analysis = result.get(file) !;
expect(analysis.analyzedClasses.length).toEqual(1);
expect(analysis.analyzedClasses[0].name).toEqual('MyComponent');
}); });
it('should analyze and compile the classes that are detected', () => { it('should analyze and compile the classes that are detected', () => {