fix(ivy): ngcc - fixes to support compiling Material library (#26403)

1) The `DecorationAnalyzer now analyzes all source files, rather than just
the entry-point files, which fixes #26183.
2) The `DecoratorAnalyzer` now runs all the `handler.analyze()`  calls
across the whole entry-point *before* running `handler.compile()`. This
ensures that dependencies between the decorated classes *within* an
entry-point are known to the handlers when running the compile process.
3) The `Renderer` now does the transformation of the typings (.d.ts) files
which allows us to support packages that only have flat format
entry-points better, and is faster, since we won't parse `.d.ts` files twice.

PR Close #26403
This commit is contained in:
Pete Bacon Darwin
2018-10-16 08:56:54 +01:00
committed by Kara Erickson
parent dff10085e8
commit 030d43b9f3
17 changed files with 536 additions and 417 deletions

View File

@ -27,6 +27,34 @@ const TEST_PROGRAM = {
`
};
const INTERNAL_COMPONENT_PROGRAM = [
{
name: 'entrypoint.js',
contents: `
import {Component, NgModule} from '@angular/core';
import {ImportedComponent} from './component';
export class LocalComponent {}
LocalComponent.decorators = [{type: Component}];
export class MyModule {}
MyModule.decorators = [{type: NgModule, args: [{
declarations: [ImportedComponent, LocalComponent],
exports: [ImportedComponent, LocalComponent],
},] }];
`
},
{
name: 'component.js',
contents: `
import {Component} from '@angular/core';
export class ImportedComponent {}
ImportedComponent.decorators = [{type: Component}];
`,
isRoot: false,
}
];
function createTestHandler() {
const handler = jasmine.createSpyObj<DecoratorHandler<any, any>>('TestDecoratorHandler', [
'detect',
@ -79,9 +107,9 @@ describe('DecorationAnalyzer', () => {
it('should return an object containing the classes that were analyzed', () => {
const file = program.getSourceFile(TEST_PROGRAM.name) !;
const analysis = result.get(file) !;
expect(analysis.analyzedClasses.length).toEqual(1);
expect(analysis.analyzedClasses[0].name).toEqual('MyComponent');
const compiledFile = result.get(file) !;
expect(compiledFile.compiledClasses.length).toEqual(1);
expect(compiledFile.compiledClasses[0].name).toEqual('MyComponent');
});
it('should analyze and compile the classes that are detected', () => {
@ -91,5 +119,41 @@ describe('DecorationAnalyzer', () => {
expect(testHandler.compile).toHaveBeenCalledTimes(1);
expect(testHandler.compile.calls.allArgs()[0][1]).toEqual('Component');
});
describe('internal components', () => {
// The problem of exposing the type of these internal components in the .d.ts typing files
// is not yet solved.
it('should analyze an internally imported component, which is not publicly exported from the entry-point',
() => {
const program = makeProgram(...INTERNAL_COMPONENT_PROGRAM);
const analyzer = new DecorationAnalyzer(
program.getTypeChecker(), new Esm2015ReflectionHost(false, program.getTypeChecker()),
[''], false);
const testHandler = createTestHandler();
analyzer.handlers = [testHandler];
const result = analyzer.analyzeProgram(program);
const file = program.getSourceFile('component.js') !;
const analysis = result.get(file) !;
expect(analysis).toBeDefined();
const ImportedComponent =
analysis.compiledClasses.find(f => f.name === 'ImportedComponent') !;
expect(ImportedComponent).toBeDefined();
});
it('should analyze an internally defined component, which is not exported at all', () => {
const program = makeProgram(...INTERNAL_COMPONENT_PROGRAM);
const analyzer = new DecorationAnalyzer(
program.getTypeChecker(), new Esm2015ReflectionHost(false, program.getTypeChecker()),
[''], false);
const testHandler = createTestHandler();
analyzer.handlers = [testHandler];
const result = analyzer.analyzeProgram(program);
const file = program.getSourceFile('entrypoint.js') !;
const analysis = result.get(file) !;
expect(analysis).toBeDefined();
const LocalComponent = analysis.compiledClasses.find(f => f.name === 'LocalComponent') !;
expect(LocalComponent).toBeDefined();
});
});
});
});

View File

@ -6,10 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as ts from 'typescript';
import {ClassMemberKind, Import} from '../../../ngtsc/host';
import {DtsMapper} from '../../src/host/dts_mapper';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {getDeclaration, makeProgram} from '../helpers/utils';
@ -399,6 +397,7 @@ const DECORATED_FILES = [
name: '/primary.js',
contents: `
import {Directive} from '@angular/core';
import {D} from '/secondary';
class A {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] }
@ -411,7 +410,6 @@ const DECORATED_FILES = [
];
class C {}
export { A, x, C };
export { D } from '/secondary';
`
},
{
@ -422,7 +420,7 @@ const DECORATED_FILES = [
D.decorators = [
{ type: Directive, args: [{ selector: '[d]' }] }
];
export { D };
export {D};
`
}
];
@ -439,13 +437,38 @@ const ARITY_CLASSES = [
{
name: '/typings/class.d.ts',
contents: `
export class NoTypeParam {}
export class OneTypeParam<T> {}
export class TwoTypeParams<T, K> {}
export declare class NoTypeParam {}
export declare class OneTypeParam<T> {}
export declare class TwoTypeParams<T, K> {}
`,
},
];
const TYPINGS_SRC_FILES = [
{name: '/src/index.js', contents: `export * from './class1'; export * from './class2';`},
{name: '/src/class1.js', contents: 'export class Class1 {}\nexport class MissingClass1 {}'},
{name: '/src/class2.js', contents: 'export class Class2 {}'},
{name: '/src/missing-class.js', contents: 'export class MissingClass2 {}'}, {
name: '/src/flat-file.js',
contents:
'export class Class1 {}\nexport class MissingClass1 {}\nexport class MissingClass2 {}\class Class3 {}\nexport {Class3 as xClass3};',
}
];
const TYPINGS_DTS_FILES = [
{name: '/typings/index.d.ts', contents: `export * from './class1'; export * from './class2';`},
{
name: '/typings/class1.d.ts',
contents: `export declare class Class1 {}\nexport declare class OtherClass {}`
},
{
name: '/typings/class2.d.ts',
contents:
`export declare class Class2 {}\nexport declare interface SomeInterface {}\nexport {Class3 as xClass3} from './class3';`
},
{name: '/typings/class3.d.ts', contents: `export declare class Class3 {}`},
];
describe('Fesm2015ReflectionHost', () => {
describe('getDecoratorsOfDeclaration()', () => {
@ -1186,12 +1209,10 @@ describe('Fesm2015ReflectionHost', () => {
describe('getGenericArityOfClass()', () => {
it('should properly count type parameters', () => {
// Mock out reading the `d.ts` file from disk
const readFileSyncSpy = spyOn(fs, 'readFileSync').and.returnValue(ARITY_CLASSES[1].contents);
const dtsProgram = makeProgram(ARITY_CLASSES[1]);
const program = makeProgram(ARITY_CLASSES[0]);
const dtsMapper = new DtsMapper('/src', '/typings');
const host = new Esm2015ReflectionHost(false, program.getTypeChecker(), dtsMapper);
const host = new Esm2015ReflectionHost(
false, program.getTypeChecker(), ARITY_CLASSES[1].name, dtsProgram);
const noTypeParamClass =
getDeclaration(program, '/src/class.js', 'NoTypeParam', ts.isClassDeclaration);
expect(host.getGenericArityOfClass(noTypeParamClass)).toBe(0);
@ -1217,30 +1238,95 @@ describe('Fesm2015ReflectionHost', () => {
});
});
describe('findDecoratedFiles()', () => {
it('should return an array of objects for each file that has exported and decorated classes',
() => {
const program = makeProgram(...DECORATED_FILES);
const host = new Esm2015ReflectionHost(false, program.getTypeChecker());
const primaryFile = program.getSourceFile(DECORATED_FILES[0].name) !;
const secondaryFile = program.getSourceFile(DECORATED_FILES[1].name) !;
const decoratedFiles = host.findDecoratedFiles(primaryFile);
describe('findDecoratedClasses()', () => {
it('should return an array of all decorated classes in the given source file', () => {
const program = makeProgram(...DECORATED_FILES);
const host = new Esm2015ReflectionHost(false, program.getTypeChecker());
const primaryFile = program.getSourceFile(DECORATED_FILES[0].name) !;
const secondaryFile = program.getSourceFile(DECORATED_FILES[1].name) !;
expect(decoratedFiles.size).toEqual(2);
const primaryDecoratedClasses = host.findDecoratedClasses(primaryFile);
expect(primaryDecoratedClasses.length).toEqual(2);
const classA = primaryDecoratedClasses.find(c => c.name === 'A') !;
expect(ts.isClassDeclaration(classA.declaration)).toBeTruthy();
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
// Note that `B` is not exported from `primary.js`
const classB = primaryDecoratedClasses.find(c => c.name === 'B') !;
expect(ts.isClassDeclaration(classB.declaration)).toBeTruthy();
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
const primary = decoratedFiles.get(primaryFile) !;
expect(primary.decoratedClasses.length).toEqual(1);
const classA = primary.decoratedClasses.find(c => c.name === 'A') !;
expect(classA.name).toEqual('A');
expect(ts.isClassDeclaration(classA.declaration)).toBeTruthy();
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
const secondary = decoratedFiles.get(secondaryFile) !;
expect(secondary.decoratedClasses.length).toEqual(1);
const classD = secondary.decoratedClasses.find(c => c.name === 'D') !;
expect(classD.name).toEqual('D');
expect(ts.isClassDeclaration(classD.declaration)).toBeTruthy();
expect(classD.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
});
const secondaryDecoratedClasses = host.findDecoratedClasses(secondaryFile) !;
expect(secondaryDecoratedClasses.length).toEqual(1);
// Note that `D` is exported from `secondary.js` but not exported from `primary.js`
const classD = secondaryDecoratedClasses.find(c => c.name === 'D') !;
expect(classD.name).toEqual('D');
expect(ts.isClassDeclaration(classD.declaration)).toBeTruthy();
expect(classD.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
});
});
describe('getDtsDeclarationsOfClass()', () => {
it('should find the dts declaration that has the same relative path to the source file', () => {
const srcProgram = makeProgram(...TYPINGS_SRC_FILES);
const dtsProgram = makeProgram(...TYPINGS_DTS_FILES);
const class1 = getDeclaration(srcProgram, '/src/class1.js', 'Class1', ts.isClassDeclaration);
const host = new Esm2015ReflectionHost(
false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram);
const dtsDeclaration = host.getDtsDeclarationOfClass(class1);
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class1.d.ts');
});
it('should throw an error if there is no matching class in the matching dts file', () => {
const srcProgram = makeProgram(...TYPINGS_SRC_FILES);
const dtsProgram = makeProgram(...TYPINGS_DTS_FILES);
const missingClass =
getDeclaration(srcProgram, '/src/class1.js', 'MissingClass1', ts.isClassDeclaration);
const host = new Esm2015ReflectionHost(
false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram);
expect(() => host.getDtsDeclarationOfClass(missingClass))
.toThrowError(
'Unable to find matching typings (.d.ts) declaration for MissingClass1 in /src/class1.js');
});
it('should throw an error if there is no matching dts file', () => {
const srcProgram = makeProgram(...TYPINGS_SRC_FILES);
const dtsProgram = makeProgram(...TYPINGS_DTS_FILES);
const missingClass = getDeclaration(
srcProgram, '/src/missing-class.js', 'MissingClass2', ts.isClassDeclaration);
const host = new Esm2015ReflectionHost(
false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram);
expect(() => host.getDtsDeclarationOfClass(missingClass))
.toThrowError(
'Unable to find matching typings (.d.ts) declaration for MissingClass2 in /src/missing-class.js');
});
it('should find the dts file that contains a matching class declaration, even if the source files do not match',
() => {
const srcProgram = makeProgram(...TYPINGS_SRC_FILES);
const dtsProgram = makeProgram(...TYPINGS_DTS_FILES);
const class1 =
getDeclaration(srcProgram, '/src/flat-file.js', 'Class1', ts.isClassDeclaration);
const host = new Esm2015ReflectionHost(
false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram);
const dtsDeclaration = host.getDtsDeclarationOfClass(class1);
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class1.d.ts');
});
it('should find aliased exports', () => {
const srcProgram = makeProgram(...TYPINGS_SRC_FILES);
const dtsProgram = makeProgram(...TYPINGS_DTS_FILES);
const class3 =
getDeclaration(srcProgram, '/src/flat-file.js', 'Class3', ts.isClassDeclaration);
const host = new Esm2015ReflectionHost(
false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram);
const dtsDeclaration = host.getDtsDeclarationOfClass(class3);
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class3.d.ts');
});
});
});

View File

@ -432,6 +432,7 @@ const DECORATED_FILES = [
name: '/primary.js',
contents: `
import {Directive} from '@angular/core';
import { D } from '/secondary';
var A = (function() {
function A() {}
A.decorators = [
@ -453,7 +454,6 @@ const DECORATED_FILES = [
return C;
});
export { A, x, C };
export { D } from '/secondary';
`
},
{
@ -1252,26 +1252,27 @@ describe('Esm5ReflectionHost', () => {
});
});
describe('fileDecoratedFiles()', () => {
it('should return an array of objects for each file that has exported and decorated classes',
() => {
const program = makeProgram(...DECORATED_FILES);
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
const primary = program.getSourceFile(DECORATED_FILES[0].name) !;
const decoratedFiles = host.findDecoratedFiles(primary);
expect(decoratedFiles.size).toEqual(2);
const primaryClasses = decoratedFiles.get(primary) !.decoratedClasses;
expect(primaryClasses.length).toEqual(1);
const classA = primaryClasses.find(c => c.name === 'A') !;
expect(classA.name).toEqual('A');
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
describe('findDecoratedClasses()', () => {
it('should return an array of all decorated classes in the given source file', () => {
const program = makeProgram(...DECORATED_FILES);
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
const primary = program.getSourceFile(DECORATED_FILES[0].name) !;
const secondary = program.getSourceFile(DECORATED_FILES[1].name) !;
const secondaryClasses = decoratedFiles.get(secondary) !.decoratedClasses;
expect(secondaryClasses.length).toEqual(1);
const classD = secondaryClasses.find(c => c.name === 'D') !;
expect(classD.name).toEqual('D');
expect(classD.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
});
const primaryDecoratedClasses = host.findDecoratedClasses(primary);
expect(primaryDecoratedClasses.length).toEqual(2);
const classA = primaryDecoratedClasses.find(c => c.name === 'A') !;
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
// Note that `B` is not exported from `primary.js`
const classB = primaryDecoratedClasses.find(c => c.name === 'B') !;
expect(classB.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
const secondary = program.getSourceFile(DECORATED_FILES[1].name) !;
const secondaryDecoratedClasses = host.findDecoratedClasses(secondary);
expect(secondaryDecoratedClasses.length).toEqual(1);
// Note that `D` is exported from `secondary.js` but not exported from `primary.js`
const classD = secondaryDecoratedClasses.find(c => c.name === 'D') !;
expect(classD.name).toEqual('D');
expect(classD.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
});
});
});

View File

@ -12,7 +12,6 @@ import MagicString from 'magic-string';
import {makeProgram} from '../helpers/utils';
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {DtsMapper} from '../../src/host/dts_mapper';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {EsmRenderer} from '../../src/rendering/esm_renderer';
@ -24,7 +23,7 @@ function setup(file: {name: string, contents: string}, transformDts: boolean = f
const decorationAnalyses =
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
const renderer = new EsmRenderer(host, false, null, dir, dir, null);
const renderer = new EsmRenderer(host, false, null, dir, dir);
return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses};
}
@ -161,8 +160,9 @@ export class A {}`);
it('should insert the definitions directly after the class declaration', () => {
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = decorationAnalyses.get(sourceFile) !.analyzedClasses[0];
renderer.addDefinitions(output, analyzedClass, 'SOME DEFINITION TEXT');
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT');
expect(output.toString()).toContain(`
export class A {}
SOME DEFINITION TEXT
@ -179,9 +179,9 @@ A.decorators = [
() => {
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
const decorator = analyzedClass.decorators[0];
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
const decorator = compiledClass.decorators[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
renderer.removeDecorators(output, decoratorsToRemove);
@ -198,9 +198,9 @@ A.decorators = [
() => {
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
const decorator = analyzedClass.decorators[0];
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
const decorator = compiledClass.decorators[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
renderer.removeDecorators(output, decoratorsToRemove);
@ -217,9 +217,9 @@ A.decorators = [
() => {
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
const decorator = analyzedClass.decorators[0];
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
const decorator = compiledClass.decorators[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
renderer.removeDecorators(output, decoratorsToRemove);
@ -238,9 +238,9 @@ A.decorators = [
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
renderer.removeDecorators(output, decoratorsToRemove);
@ -255,9 +255,9 @@ A.decorators = [
() => {
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
renderer.removeDecorators(output, decoratorsToRemove);
@ -273,9 +273,9 @@ A.decorators = [
() => {
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
renderer.removeDecorators(output, decoratorsToRemove);

View File

@ -20,7 +20,7 @@ function setup(file: {name: string, contents: string}) {
const decorationAnalyses =
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
const renderer = new EsmRenderer(host, false, null, '', '', null);
const renderer = new EsmRenderer(host, false, null, '', '');
return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses};
}
@ -182,8 +182,9 @@ var A = (function() {`);
it('should insert the definitions directly after the class declaration', () => {
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = decorationAnalyses.get(sourceFile) !.analyzedClasses[0];
renderer.addDefinitions(output, analyzedClass, 'SOME DEFINITION TEXT');
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT');
expect(output.toString()).toContain(`
function A() {}
SOME DEFINITION TEXT
@ -199,9 +200,9 @@ SOME DEFINITION TEXT
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
const decorator = analyzedClass.decorators[0];
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
const decorator = compiledClass.decorators[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
renderer.removeDecorators(output, decoratorsToRemove);
@ -217,9 +218,9 @@ SOME DEFINITION TEXT
() => {
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
const decorator = analyzedClass.decorators[0];
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
const decorator = compiledClass.decorators[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
renderer.removeDecorators(output, decoratorsToRemove);
@ -236,9 +237,9 @@ SOME DEFINITION TEXT
() => {
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
const decorator = analyzedClass.decorators[0];
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
const decorator = compiledClass.decorators[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
renderer.removeDecorators(output, decoratorsToRemove);
@ -257,9 +258,9 @@ SOME DEFINITION TEXT
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
renderer.removeDecorators(output, decoratorsToRemove);
@ -274,9 +275,9 @@ SOME DEFINITION TEXT
() => {
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
renderer.removeDecorators(output, decoratorsToRemove);
@ -292,9 +293,9 @@ SOME DEFINITION TEXT
() => {
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
const compiledClass =
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
renderer.removeDecorators(output, decoratorsToRemove);

View File

@ -11,20 +11,20 @@ import * as ts from 'typescript';
import MagicString from 'magic-string';
import {fromObject, generateMapFileComment} from 'convert-source-map';
import {makeProgram} from '../helpers/utils';
import {AnalyzedClass, DecorationAnalyzer, DecorationAnalyses} from '../../src/analysis/decoration_analyzer';
import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {Renderer} from '../../src/rendering/renderer';
class TestRenderer extends Renderer {
constructor(host: Esm2015ReflectionHost) { super(host, false, null, '/src', '/dist', null); }
constructor(host: Esm2015ReflectionHost) { super(host, false, null, '/src', '/dist'); }
addImports(output: MagicString, imports: {name: string, as: string}[]) {
output.prepend('\n// ADD IMPORTS\n');
}
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
output.prepend('\n// ADD CONSTANTS\n');
}
addDefinitions(output: MagicString, analyzedClass: AnalyzedClass, definitions: string) {
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string) {
output.prepend('\n// ADD DEFINITIONS\n');
}
removeDecorators(output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>) {