/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {AotSummaryResolver, AotSummaryResolverHost, CompileSummaryKind, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler'; import {METADATA_VERSION} from '@angular/compiler-cli'; import {deserializeSummaries, serializeSummaries} from '@angular/compiler/src/aot/summary_serializer'; import {summaryFileName} from '@angular/compiler/src/aot/util'; import {MockStaticSymbolResolverHost} from './static_symbol_resolver_spec'; import {createMockOutputContext, MockAotSummaryResolverHost} from './summary_resolver_spec'; { describe('summary serializer', () => { let summaryResolver: AotSummaryResolver; let symbolResolver: StaticSymbolResolver; let symbolCache: StaticSymbolCache; let host: MockAotSummaryResolverHost; beforeEach(() => { symbolCache = new StaticSymbolCache(); }); function init( summaries: {[filePath: string]: string} = {}, metadata: {[key: string]: any} = {}) { host = new MockAotSummaryResolverHost(summaries); summaryResolver = new AotSummaryResolver(host, symbolCache); symbolResolver = new StaticSymbolResolver( new MockStaticSymbolResolverHost(metadata), symbolCache, summaryResolver); } describe('summaryFileName', () => { it('should add .ngsummary.json to the filename', () => { init(); expect(summaryFileName('a.ts')).toBe('a.ngsummary.json'); expect(summaryFileName('a.d.ts')).toBe('a.ngsummary.json'); expect(summaryFileName('a.js')).toBe('a.ngsummary.json'); }); }); it('should serialize various data correctly', () => { init(); const serializedData = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [ { symbol: symbolCache.get('/tmp/some_values.ts', 'Values'), metadata: { aNumber: 1, aString: 'hello', anArray: [1, 2], aStaticSymbol: symbolCache.get('/tmp/some_symbol.ts', 'someName'), aStaticSymbolWithMembers: symbolCache.get('/tmp/some_symbol.ts', 'someName', ['someMember']), } }, { symbol: symbolCache.get('/tmp/some_service.ts', 'SomeService'), metadata: { __symbolic: 'class', members: {'aMethod': {__symbolic: 'function'}}, statics: {aStatic: true}, decorators: ['aDecoratorData'] } } ], [{ summary: { summaryKind: CompileSummaryKind.Injectable, type: { reference: symbolCache.get('/tmp/some_service.ts', 'SomeService'), } } as any, metadata: null as any }]); const summaries = deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serializedData.json) .summaries; expect(summaries.length).toBe(2); // Note: change from .ts to .d.ts is expected expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/some_values.d.ts', 'Values')); expect(summaries[0].metadata).toEqual({ aNumber: 1, aString: 'hello', anArray: [1, 2], aStaticSymbol: symbolCache.get('/tmp/some_symbol.d.ts', 'someName'), aStaticSymbolWithMembers: symbolCache.get('/tmp/some_symbol.d.ts', 'someName', ['someMember']) }); expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/some_service.d.ts', 'SomeService')); // serialization should drop class decorators expect(summaries[1].metadata).toEqual({ __symbolic: 'class', members: {aMethod: {__symbolic: 'function'}}, statics: {aStatic: true} }); expect(summaries[1].type!.type.reference) .toBe(symbolCache.get('/tmp/some_service.d.ts', 'SomeService')); }); it('should automatically add exported directives / pipes of NgModules that are not source files', () => { init(); const externalSerialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [ {symbol: symbolCache.get('/tmp/external.ts', 'SomeExternalPipe'), metadata: null}, {symbol: symbolCache.get('/tmp/external.ts', 'SomeExternalDir'), metadata: null}, ], [ { summary: { summaryKind: CompileSummaryKind.Pipe, type: { reference: symbolCache.get('/tmp/external.ts', 'SomeExternalPipe'), } } as any, metadata: null as any }, { summary: { summaryKind: CompileSummaryKind.Directive, type: { reference: symbolCache.get('/tmp/external.ts', 'SomeExternalDir'), }, providers: [], viewProviders: [], } as any, metadata: null as any } ]); init({ '/tmp/external.ngsummary.json': externalSerialized.json, }); const serialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [ {symbol: symbolCache.get('/tmp/some_module.ts', 'SomeModule'), metadata: null}, ], [{ summary: { summaryKind: CompileSummaryKind.NgModule, type: {reference: symbolCache.get('/tmp/some_module.ts', 'SomeModule')}, exportedPipes: [ {reference: symbolCache.get('/tmp/some_pipe.ts', 'SomePipe')}, {reference: symbolCache.get('/tmp/external.d.ts', 'SomeExternalPipe')} ], exportedDirectives: [ {reference: symbolCache.get('/tmp/some_dir.ts', 'SomeDir')}, {reference: symbolCache.get('/tmp/external.d.ts', 'SomeExternalDir')} ], providers: [], modules: [], }, metadata: null as any }]); const summaries = deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json) .summaries; init({ '/tmp/some_module.ngsummary.json': serialized.json, }); const serializedReexport = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [ { symbol: symbolCache.get('/tmp/some_reexport.ts', 'ReexportModule'), metadata: symbolCache.get('/tmp/some_module.d.ts', 'SomeModule') }, ], []); expect(summaries.length).toBe(3); expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/some_module.d.ts', 'SomeModule')); expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/external.d.ts', 'SomeExternalDir')); expect(summaries[2].symbol) .toBe(symbolCache.get('/tmp/external.d.ts', 'SomeExternalPipe')); const reexportSummaries = deserializeSummaries( symbolCache, summaryResolver, 'someFile.d.ts', serializedReexport.json) .summaries; expect(reexportSummaries.length).toBe(4); expect(reexportSummaries[0].symbol) .toBe(symbolCache.get('/tmp/some_reexport.d.ts', 'ReexportModule')); expect(reexportSummaries[1].symbol) .toBe(symbolCache.get('/tmp/some_module.d.ts', 'SomeModule')); expect(reexportSummaries[2].symbol) .toBe(symbolCache.get('/tmp/external.d.ts', 'SomeExternalDir')); expect(reexportSummaries[3].symbol) .toBe(symbolCache.get('/tmp/external.d.ts', 'SomeExternalPipe')); }); it('should automatically add the metadata of referenced symbols that are not in the source files', () => { init(); const externalSerialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [ { symbol: symbolCache.get('/tmp/external.ts', 'PROVIDERS'), metadata: [symbolCache.get('/tmp/external_svc.ts', 'SomeService')] }, { symbol: symbolCache.get('/tmp/external_svc.ts', 'SomeService'), metadata: {__symbolic: 'class'} }, // Note: This is an important usecase when using ng1 and ng2 together via // goog.module. // In these cases, users write the following to get a referrable symbol in metadata // collection: // import UsernameService from 'goog:somePackage.UsernameService'; // export {UsernameService}; { symbol: symbolCache.get('/tmp/external.ts', 'ReexportNonExistent'), metadata: symbolCache.get('/tmp/external.ts', 'NonExistent'), } ], [{ summary: { summaryKind: CompileSummaryKind.Injectable, type: { reference: symbolCache.get('/tmp/external_svc.ts', 'SomeService'), } } as any, metadata: null as any }]); init( { '/tmp/external.ngsummary.json': externalSerialized.json, }, { '/tmp/local.ts': ` export var local = 'a'; `, '/tmp/non_summary.d.ts': {__symbolic: 'module', version: METADATA_VERSION, metadata: {'external': 'b'}} }); const serialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{ symbol: symbolCache.get('/tmp/test.ts', 'main'), metadata: { local: symbolCache.get('/tmp/local.ts', 'local'), external: symbolCache.get('/tmp/external.d.ts', 'PROVIDERS'), externalNonSummary: symbolCache.get('/tmp/non_summary.d.ts', 'external'), reexportNonExistent: symbolCache.get('/tmp/external.ts', 'ReexportNonExistent'), } }], []); const summaries = deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json) .summaries; // Note: local should not show up! expect(summaries.length).toBe(4); expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/test.d.ts', 'main')); expect(summaries[0].metadata).toEqual({ local: symbolCache.get('/tmp/local.d.ts', 'local'), external: symbolCache.get('/tmp/external.d.ts', 'PROVIDERS'), externalNonSummary: symbolCache.get('/tmp/non_summary.d.ts', 'external'), reexportNonExistent: symbolCache.get('/tmp/external.d.ts', 'ReexportNonExistent'), }); expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/external.d.ts', 'PROVIDERS')); expect(summaries[1].metadata).toEqual([symbolCache.get( '/tmp/external_svc.d.ts', 'SomeService')]); // SomService is a transitive dep, but should have been serialized as well. expect(summaries[2].symbol).toBe(symbolCache.get('/tmp/external_svc.d.ts', 'SomeService')); expect(summaries[2].type!.type.reference) .toBe(symbolCache.get('/tmp/external_svc.d.ts', 'SomeService')); // there was no summary for non_summary, but it should have // been serialized as well. expect(summaries[3].symbol).toBe(symbolCache.get('/tmp/non_summary.d.ts', 'external')); expect(summaries[3].metadata).toEqual('b'); }); it('should resolve reexported values in libraries', () => { init(); const externalSerialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [ {symbol: symbolCache.get('/tmp/external.ts', 'value'), metadata: 'someString'}, { symbol: symbolCache.get('/tmp/external.ts', 'reexportValue'), metadata: symbolCache.get('/tmp/external.ts', 'value') }, ], []); init({ '/tmp/external.ngsummary.json': externalSerialized.json, }); const serialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [ { symbol: symbolCache.get('/tmp/test.ts', 'mainValue'), metadata: symbolCache.get('/tmp/external.d.ts', 'reexportValue'), }, ], []); const summaries = deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json) .summaries; expect(summaries.length).toBe(2); expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/test.d.ts', 'mainValue')); expect(summaries[0].metadata).toBe(symbolCache.get('/tmp/external.d.ts', 'value')); expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/external.d.ts', 'value')); expect(summaries[1].metadata).toBe('someString'); }); it('should use existing reexports for "importAs" for symbols of libraries', () => { init(); const externalSerialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [ {symbol: symbolCache.get('/tmp/external.ts', 'value'), metadata: 'aValue'}, { symbol: symbolCache.get('/tmp/external.ts', 'reexportValue'), metadata: symbolCache.get('/tmp/external.ts', 'value') }, ], []); expect(externalSerialized.exportAs).toEqual([]); init({ '/tmp/external.ngsummary.json': externalSerialized.json, }); const serialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{ symbol: symbolCache.get('/tmp/test.ts', 'mainValue'), metadata: symbolCache.get('/tmp/external.d.ts', 'reexportValue'), }], []); expect(serialized.exportAs).toEqual([]); const importAs = deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json) .importAs; expect(importAs).toEqual([{ symbol: symbolCache.get('/tmp/external.d.ts', 'value'), importAs: symbolCache.get('/tmp/test.d.ts', 'mainValue'), }]); }); describe('with resolved symbols', () => { it('should be able to serialize a call', () => { init(); const serialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{ symbol: symbolCache.get('/tmp/test.ts', 'main'), metadata: { __symbolic: 'call', expression: {__symbolic: 'resolved', symbol: symbolCache.get('/tmp/test2.ts', 'ref')} } }], []); expect(serialized.json).not.toContain('error'); }); it('should be able to serialize a call to a method', () => { init(); const serialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{ symbol: symbolCache.get('/tmp/test.ts', 'main'), metadata: { __symbolic: 'call', expression: { __symbolic: 'select', expression: {__symbolic: 'resolved', symbol: symbolCache.get('/tmp/test2.ts', 'ref')}, name: 'foo' } } }], []); expect(serialized.json).not.toContain('error'); }); }); describe('symbol re-exports enabled', () => { it('should not create "importAs" names for ctor arguments which are types of reexported classes in libraries', () => { init(); const externalSerialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [ { symbol: symbolCache.get('/tmp/external.ts', 'type'), metadata: {__symbolic: 'interface'} }, { symbol: symbolCache.get('/tmp/external.ts', 'value'), metadata: {__symbolic: 'class'} }, { symbol: symbolCache.get('/tmp/external.ts', 'reexportClass'), metadata: { __symbolic: 'class', 'members': { '__ctor__': [{ '__symbolic': 'constructor', 'parameters': [ symbolCache.get('/tmp/external.ts', 'type'), symbolCache.get('/tmp/external.ts', 'value'), ] }] } } }, ], [], true); expect(externalSerialized.exportAs).toEqual([]); init({ '/tmp/external.ngsummary.json': externalSerialized.json, }); const serialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{ symbol: symbolCache.get('/tmp/test.ts', 'mainClass'), metadata: symbolCache.get('/tmp/external.d.ts', 'reexportClass'), }], [], true); const importAs = deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json) .importAs; expect(importAs).toEqual([ { symbol: symbolCache.get('/tmp/external.d.ts', 'reexportClass'), importAs: symbolCache.get('/tmp/test.d.ts', 'mainClass'), }, { symbol: symbolCache.get('/tmp/external.d.ts', 'value'), importAs: symbolCache.get('someFile.ngfactory.d.ts', 'value_3'), } ]); }); it('should create reexports in the ngfactory for symbols of libraries', () => { init(); const serialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{ symbol: symbolCache.get('/tmp/test.ts', 'main'), metadata: [ symbolCache.get('/tmp/external.d.ts', 'lib'), symbolCache.get('/tmp/external.d.ts', 'lib', ['someMember']), ] }], [], true); // Note: no entry for the symbol with members! expect(serialized.exportAs).toEqual([ {symbol: symbolCache.get('/tmp/external.d.ts', 'lib'), exportAs: 'lib_1'} ]); const deserialized = deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json); // Note: no entry for the symbol with members! expect(deserialized.importAs).toEqual([{ symbol: symbolCache.get('/tmp/external.d.ts', 'lib'), importAs: symbolCache.get('someFile.ngfactory.d.ts', 'lib_1') }]); }); }); it('should use existing reexports for "importAs" for symbols of libraries', () => { init(); const externalSerialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [ {symbol: symbolCache.get('/tmp/external.ts', 'value'), metadata: 'aValue'}, { symbol: symbolCache.get('/tmp/external.ts', 'reexportValue'), metadata: symbolCache.get('/tmp/external.ts', 'value') }, ], [], false); expect(externalSerialized.exportAs).toEqual([]); init({ '/tmp/external.ngsummary.json': externalSerialized.json, }); const serialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{ symbol: symbolCache.get('/tmp/test.ts', 'mainValue'), metadata: symbolCache.get('/tmp/external.d.ts', 'reexportValue'), }], []); expect(serialized.exportAs).toEqual([]); const importAs = deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json) .importAs; expect(importAs).toEqual([{ symbol: symbolCache.get('/tmp/external.d.ts', 'value'), importAs: symbolCache.get('/tmp/test.d.ts', 'mainValue'), }]); }); it('should not create reexports in the ngfactory for external symbols', () => { init(); const serialized = serializeSummaries( 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{ symbol: symbolCache.get('/tmp/test.ts', 'main'), metadata: [ symbolCache.get('/tmp/external.d.ts', 'lib'), symbolCache.get('/tmp/external.d.ts', 'lib', ['someMember']), ] }], [], false); expect(serialized.exportAs.length).toBe(0, 'Expected no external symbols to be re-exported.'); const deserialized = deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json); expect(deserialized.importAs.length) .toBe(0, 'Expected no symbols that can be imported from a re-exported location'); }); }); }