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:

committed by
Kara Erickson

parent
dff10085e8
commit
030d43b9f3
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -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']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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[]>) {
|
||||
|
Reference in New Issue
Block a user