| 
									
										
										
										
											2020-10-03 19:59:53 +01:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @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 * as o from '@angular/compiler/src/output/output_ast'; | 
					
						
							|  |  |  | import * as ts from 'typescript'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {TypeScriptAstFactory} from '../../../src/ngtsc/translator'; | 
					
						
							|  |  |  | import {AstHost} from '../../src/ast/ast_host'; | 
					
						
							|  |  |  | import {TypeScriptAstHost} from '../../src/ast/typescript/typescript_ast_host'; | 
					
						
							|  |  |  | import {DeclarationScope} from '../../src/file_linker/declaration_scope'; | 
					
						
							|  |  |  | import {FileLinker} from '../../src/file_linker/file_linker'; | 
					
						
							|  |  |  | import {LinkerEnvironment} from '../../src/file_linker/linker_environment'; | 
					
						
							|  |  |  | import {DEFAULT_LINKER_OPTIONS} from '../../src/file_linker/linker_options'; | 
					
						
							|  |  |  | import {PartialDirectiveLinkerVersion1} from '../../src/file_linker/partial_linkers/partial_directive_linker_1'; | 
					
						
							|  |  |  | import {generate} from './helpers'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe('FileLinker', () => { | 
					
						
							|  |  |  |   let factory: TypeScriptAstFactory; | 
					
						
							|  |  |  |   beforeEach(() => factory = new TypeScriptAstFactory()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('isPartialDeclaration()', () => { | 
					
						
							|  |  |  |     it('should return true if the callee is recognized', () => { | 
					
						
							|  |  |  |       const {fileLinker} = createFileLinker(); | 
					
						
							| 
									
										
										
										
											2020-11-03 21:33:57 +01:00
										 |  |  |       expect(fileLinker.isPartialDeclaration('ɵɵngDeclareDirective')).toBe(true); | 
					
						
							|  |  |  |       expect(fileLinker.isPartialDeclaration('ɵɵngDeclareComponent')).toBe(true); | 
					
						
							| 
									
										
										
										
											2020-10-03 19:59:53 +01:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should return false if the callee is not recognized', () => { | 
					
						
							|  |  |  |       const {fileLinker} = createFileLinker(); | 
					
						
							|  |  |  |       expect(fileLinker.isPartialDeclaration('$foo')).toBe(false); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('linkPartialDeclaration()', () => { | 
					
						
							|  |  |  |     it('should throw an error if the function name is not recognised', () => { | 
					
						
							|  |  |  |       const {fileLinker} = createFileLinker(); | 
					
						
							| 
									
										
										
										
											2020-11-25 21:03:02 +00:00
										 |  |  |       const version = factory.createLiteral('0.0.0-PLACEHOLDER'); | 
					
						
							| 
									
										
										
										
											2020-10-03 19:59:53 +01:00
										 |  |  |       const ngImport = factory.createIdentifier('core'); | 
					
						
							|  |  |  |       const declarationArg = factory.createObjectLiteral([ | 
					
						
							|  |  |  |         {propertyName: 'version', quoted: false, value: version}, | 
					
						
							|  |  |  |         {propertyName: 'ngImport', quoted: false, value: ngImport}, | 
					
						
							|  |  |  |       ]); | 
					
						
							|  |  |  |       expect( | 
					
						
							|  |  |  |           () => fileLinker.linkPartialDeclaration( | 
					
						
							|  |  |  |               'foo', [declarationArg], new MockDeclarationScope())) | 
					
						
							|  |  |  |           .toThrowError('Unknown partial declaration function foo.'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should throw an error if the metadata object does not have a `version` property', () => { | 
					
						
							|  |  |  |       const {fileLinker} = createFileLinker(); | 
					
						
							|  |  |  |       const ngImport = factory.createIdentifier('core'); | 
					
						
							|  |  |  |       const declarationArg = factory.createObjectLiteral([ | 
					
						
							|  |  |  |         {propertyName: 'ngImport', quoted: false, value: ngImport}, | 
					
						
							|  |  |  |       ]); | 
					
						
							|  |  |  |       expect( | 
					
						
							|  |  |  |           () => fileLinker.linkPartialDeclaration( | 
					
						
							| 
									
										
										
										
											2020-11-03 21:33:57 +01:00
										 |  |  |               'ɵɵngDeclareDirective', [declarationArg], new MockDeclarationScope())) | 
					
						
							| 
									
										
										
										
											2020-10-03 19:59:53 +01:00
										 |  |  |           .toThrowError(`Expected property 'version' to be present.`); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should throw an error if the metadata object does not have a `ngImport` property', () => { | 
					
						
							|  |  |  |       const {fileLinker} = createFileLinker(); | 
					
						
							|  |  |  |       const ngImport = factory.createIdentifier('core'); | 
					
						
							|  |  |  |       const declarationArg = factory.createObjectLiteral([ | 
					
						
							|  |  |  |         {propertyName: 'version', quoted: false, value: ngImport}, | 
					
						
							|  |  |  |       ]); | 
					
						
							|  |  |  |       expect( | 
					
						
							|  |  |  |           () => fileLinker.linkPartialDeclaration( | 
					
						
							| 
									
										
										
										
											2020-11-03 21:33:57 +01:00
										 |  |  |               'ɵɵngDeclareDirective', [declarationArg], new MockDeclarationScope())) | 
					
						
							| 
									
										
										
										
											2020-10-03 19:59:53 +01:00
										 |  |  |           .toThrowError(`Expected property 'ngImport' to be present.`); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should call `linkPartialDeclaration()` on the appropriate partial compiler', () => { | 
					
						
							|  |  |  |       const {fileLinker} = createFileLinker(); | 
					
						
							|  |  |  |       const compileSpy = spyOn(PartialDirectiveLinkerVersion1.prototype, 'linkPartialDeclaration') | 
					
						
							|  |  |  |                              .and.returnValue(o.literal('compilation result')); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const ngImport = factory.createIdentifier('core'); | 
					
						
							| 
									
										
										
										
											2020-11-25 21:03:02 +00:00
										 |  |  |       const version = factory.createLiteral('0.0.0-PLACEHOLDER'); | 
					
						
							| 
									
										
										
										
											2020-10-03 19:59:53 +01:00
										 |  |  |       const declarationArg = factory.createObjectLiteral([ | 
					
						
							|  |  |  |         {propertyName: 'ngImport', quoted: false, value: ngImport}, | 
					
						
							|  |  |  |         {propertyName: 'version', quoted: false, value: version}, | 
					
						
							|  |  |  |       ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const compilationResult = fileLinker.linkPartialDeclaration( | 
					
						
							| 
									
										
										
										
											2020-11-03 21:33:57 +01:00
										 |  |  |           'ɵɵngDeclareDirective', [declarationArg], new MockDeclarationScope()); | 
					
						
							| 
									
										
										
										
											2020-10-03 19:59:53 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       expect(compilationResult).toEqual(factory.createLiteral('compilation result')); | 
					
						
							|  |  |  |       expect(compileSpy).toHaveBeenCalled(); | 
					
						
							|  |  |  |       expect(compileSpy.calls.mostRecent().args[3].getNode('ngImport')).toBe(ngImport); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('getConstantStatements()', () => { | 
					
						
							|  |  |  |     it('should capture shared constant values', () => { | 
					
						
							|  |  |  |       const {fileLinker} = createFileLinker(); | 
					
						
							|  |  |  |       spyOnLinkPartialDeclarationWithConstants(o.literal('REPLACEMENT')); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Here we use the `core` idenfifier for `ngImport` to trigger the use of a shared scope for
 | 
					
						
							|  |  |  |       // constant statements.
 | 
					
						
							|  |  |  |       const declarationArg = factory.createObjectLiteral([ | 
					
						
							|  |  |  |         {propertyName: 'ngImport', quoted: false, value: factory.createIdentifier('core')}, | 
					
						
							| 
									
										
										
										
											2020-11-25 21:03:02 +00:00
										 |  |  |         {propertyName: 'version', quoted: false, value: factory.createLiteral('0.0.0-PLACEHOLDER')}, | 
					
						
							| 
									
										
										
										
											2020-10-03 19:59:53 +01:00
										 |  |  |       ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const replacement = fileLinker.linkPartialDeclaration( | 
					
						
							| 
									
										
										
										
											2020-11-03 21:33:57 +01:00
										 |  |  |           'ɵɵngDeclareDirective', [declarationArg], new MockDeclarationScope()); | 
					
						
							| 
									
										
										
										
											2020-10-03 19:59:53 +01:00
										 |  |  |       expect(generate(replacement)).toEqual('"REPLACEMENT"'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const results = fileLinker.getConstantStatements(); | 
					
						
							|  |  |  |       expect(results.length).toEqual(1); | 
					
						
							|  |  |  |       const {constantScope, statements} = results[0]; | 
					
						
							|  |  |  |       expect(constantScope).toBe(MockConstantScopeRef.singleton); | 
					
						
							|  |  |  |       expect(statements.map(generate)).toEqual(['const _c0 = [1];']); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should be no shared constant statements to capture when they are emitted into the replacement IIFE', | 
					
						
							|  |  |  |        () => { | 
					
						
							|  |  |  |          const {fileLinker} = createFileLinker(); | 
					
						
							|  |  |  |          spyOnLinkPartialDeclarationWithConstants(o.literal('REPLACEMENT')); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          // Here we use a string literal `"not-a-module"` for `ngImport` to cause constant
 | 
					
						
							|  |  |  |          // statements to be emitted in an IIFE rather than added to the shared constant scope.
 | 
					
						
							|  |  |  |          const declarationArg = factory.createObjectLiteral([ | 
					
						
							|  |  |  |            {propertyName: 'ngImport', quoted: false, value: factory.createLiteral('not-a-module')}, | 
					
						
							| 
									
										
										
										
											2020-11-25 21:03:02 +00:00
										 |  |  |            { | 
					
						
							|  |  |  |              propertyName: 'version', | 
					
						
							|  |  |  |              quoted: false, | 
					
						
							|  |  |  |              value: factory.createLiteral('0.0.0-PLACEHOLDER') | 
					
						
							|  |  |  |            }, | 
					
						
							| 
									
										
										
										
											2020-10-03 19:59:53 +01:00
										 |  |  |          ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          const replacement = fileLinker.linkPartialDeclaration( | 
					
						
							| 
									
										
										
										
											2020-11-03 21:33:57 +01:00
										 |  |  |              'ɵɵngDeclareDirective', [declarationArg], new MockDeclarationScope()); | 
					
						
							| 
									
										
										
										
											2020-10-03 19:59:53 +01:00
										 |  |  |          expect(generate(replacement)) | 
					
						
							|  |  |  |              .toEqual('function () { const _c0 = [1]; return "REPLACEMENT"; }()'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          const results = fileLinker.getConstantStatements(); | 
					
						
							|  |  |  |          expect(results.length).toEqual(0); | 
					
						
							|  |  |  |        }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function createFileLinker(): { | 
					
						
							|  |  |  |     host: AstHost<ts.Expression>, | 
					
						
							|  |  |  |     fileLinker: FileLinker<MockConstantScopeRef, ts.Statement, ts.Expression> | 
					
						
							|  |  |  |   } { | 
					
						
							|  |  |  |     const linkerEnvironment = LinkerEnvironment.create<ts.Statement, ts.Expression>( | 
					
						
							|  |  |  |         new TypeScriptAstHost(), new TypeScriptAstFactory(), DEFAULT_LINKER_OPTIONS); | 
					
						
							|  |  |  |     const fileLinker = new FileLinker<MockConstantScopeRef, ts.Statement, ts.Expression>( | 
					
						
							|  |  |  |         linkerEnvironment, 'test.js', '// test code'); | 
					
						
							|  |  |  |     return {host: linkerEnvironment.host, fileLinker}; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * This mock implementation of `DeclarationScope` will return a singleton instance of | 
					
						
							|  |  |  |  * `MockConstantScopeRef` if the expression is an identifier, or `null` otherwise. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This way we can simulate whether the constants will be shared or inlined into an IIFE. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class MockDeclarationScope implements DeclarationScope<MockConstantScopeRef, ts.Expression> { | 
					
						
							|  |  |  |   getConstantScopeRef(expression: ts.Expression): MockConstantScopeRef|null { | 
					
						
							|  |  |  |     if (ts.isIdentifier(expression)) { | 
					
						
							|  |  |  |       return MockConstantScopeRef.singleton; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class MockConstantScopeRef { | 
					
						
							|  |  |  |   private constructor() {} | 
					
						
							|  |  |  |   static singleton = new MockDeclarationScope(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Spy on the `PartialDirectiveLinkerVersion1.linkPartialDeclaration()` method, triggering | 
					
						
							|  |  |  |  * shared constants to be created. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function spyOnLinkPartialDeclarationWithConstants(replacement: o.Expression) { | 
					
						
							|  |  |  |   let callCount = 0; | 
					
						
							|  |  |  |   spyOn(PartialDirectiveLinkerVersion1.prototype, 'linkPartialDeclaration') | 
					
						
							|  |  |  |       .and.callFake(((sourceUrl, code, constantPool) => { | 
					
						
							|  |  |  |                       const constArray = o.literalArr([o.literal(++callCount)]); | 
					
						
							|  |  |  |                       // We have to add the constant twice or it will not create a shared statement
 | 
					
						
							|  |  |  |                       constantPool.getConstLiteral(constArray); | 
					
						
							|  |  |  |                       constantPool.getConstLiteral(constArray); | 
					
						
							|  |  |  |                       return replacement; | 
					
						
							|  |  |  |                     }) as typeof PartialDirectiveLinkerVersion1.prototype.linkPartialDeclaration); | 
					
						
							|  |  |  | } |