This commit adds the basic building blocks for linking partial declarations.
In particular it provides a generic `FileLinker` class that delegates to
a set of (not yet implemented) `PartialLinker` classes.
The Babel plugin makes use of this `FileLinker` providing concrete classes
for `AstHost` and `AstFactory` that work with Babel AST. It can be created
with the following code:
```ts
const plugin = createEs2015LinkerPlugin({ /* options */ });
```
PR Close #39116
		
	
			
		
			
				
	
	
		
			117 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			117 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @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 {parse} from '@babel/parser';
 | |
| import traverse, {NodePath} from '@babel/traverse';
 | |
| import * as t from '@babel/types';
 | |
| import {BabelDeclarationScope} from '../src/babel_declaration_scope';
 | |
| 
 | |
| 
 | |
| describe('BabelDeclarationScope', () => {
 | |
|   describe('getConstantScopeRef()', () => {
 | |
|     it('should return a path to the ES module where the expression was imported', () => {
 | |
|       const ast = parse(
 | |
|           [
 | |
|             'import * as core from \'@angular/core\';',
 | |
|             'function foo() {',
 | |
|             '  var TEST = core;',
 | |
|             '}',
 | |
|           ].join('\n'),
 | |
|           {sourceType: 'module'});
 | |
|       const nodePath = findVarDeclaration(ast, 'TEST');
 | |
|       const scope = new BabelDeclarationScope(nodePath.scope);
 | |
|       const constantScope = scope.getConstantScopeRef(nodePath.get('init').node);
 | |
|       expect(constantScope).not.toBe(null);
 | |
|       expect(constantScope!.node).toBe(ast.program);
 | |
|     });
 | |
| 
 | |
|     it('should return a path to the ES Module where the expression is declared', () => {
 | |
|       const ast = parse(
 | |
|           [
 | |
|             'var core;',
 | |
|             'export function foo() {',
 | |
|             '  var TEST = core;',
 | |
|             '}',
 | |
|           ].join('\n'),
 | |
|           {sourceType: 'module'});
 | |
|       const nodePath = findVarDeclaration(ast, 'TEST');
 | |
|       const scope = new BabelDeclarationScope(nodePath.scope);
 | |
|       const constantScope = scope.getConstantScopeRef(nodePath.get('init').node);
 | |
|       expect(constantScope).not.toBe(null);
 | |
|       expect(constantScope!.node).toBe(ast.program);
 | |
|     });
 | |
| 
 | |
|     it('should return null if the file is not an ES module', () => {
 | |
|       const ast = parse(
 | |
|           [
 | |
|             'var core;',
 | |
|             'function foo() {',
 | |
|             '  var TEST = core;',
 | |
|             '}',
 | |
|           ].join('\n'),
 | |
|           {sourceType: 'script'});
 | |
|       const nodePath = findVarDeclaration(ast, 'TEST');
 | |
|       const scope = new BabelDeclarationScope(nodePath.scope);
 | |
|       const constantScope = scope.getConstantScopeRef(nodePath.get('init').node);
 | |
|       expect(constantScope).toBe(null);
 | |
|     });
 | |
| 
 | |
|     it('should return the IIFE factory function where the expression is a parameter', () => {
 | |
|       const ast = parse(
 | |
|           [
 | |
|             'var core;',
 | |
|             '(function(core) {',
 | |
|             '  var BLOCK = \'block\';',
 | |
|             '  function foo() {',
 | |
|             '    var TEST = core;',
 | |
|             '  }',
 | |
|             '})(core);',
 | |
|           ].join('\n'),
 | |
|           {sourceType: 'script'});
 | |
|       const nodePath = findVarDeclaration(ast, 'TEST');
 | |
|       const fnPath = findFirstFunction(ast);
 | |
|       const scope = new BabelDeclarationScope(nodePath.scope);
 | |
|       const constantScope = scope.getConstantScopeRef(nodePath.get('init').node);
 | |
|       expect(constantScope).not.toBe(null);
 | |
|       expect(constantScope!.isFunction()).toBe(true);
 | |
|       expect(constantScope!.node).toEqual(fnPath.node);
 | |
|     });
 | |
|   });
 | |
| });
 | |
| 
 | |
| function findVarDeclaration(
 | |
|     file: t.File, varName: string): NodePath<t.VariableDeclarator&{init: t.Expression}> {
 | |
|   let varDecl: NodePath<t.VariableDeclarator>|undefined = undefined;
 | |
|   traverse(file, {
 | |
|     VariableDeclarator: (path) => {
 | |
|       const id = path.get('id');
 | |
|       if (id.isIdentifier() && id.node.name === varName && path.get('init') !== null) {
 | |
|         varDecl = path;
 | |
|         path.stop();
 | |
|       }
 | |
|     }
 | |
|   });
 | |
|   if (varDecl === undefined) {
 | |
|     throw new Error(`TEST BUG: expected to find variable declaration for ${varName}.`);
 | |
|   }
 | |
|   return varDecl;
 | |
| }
 | |
| 
 | |
| function findFirstFunction(file: t.File): NodePath<t.Function> {
 | |
|   let fn: NodePath<t.Function>|undefined = undefined;
 | |
|   traverse(file, {
 | |
|     Function: (path) => {
 | |
|       fn = path;
 | |
|       path.stop();
 | |
|     }
 | |
|   });
 | |
|   if (fn === undefined) {
 | |
|     throw new Error(`TEST BUG: expected to find a function.`);
 | |
|   }
 | |
|   return fn;
 | |
| }
 |