The type checker had to do extensive work in resolving the
`NodePath.get` method call for the `NodePath` that had an intersection
type of `ts.VariableDeclarator&{init:t.Expression}`. The `NodePath.get`
method is typed using a conditional type which became expensive to
compute with this intersection type. As a workaround, the original
`init` property is explicitly omitted which avoids the performance
cliff. This brings down the compile time by 15s.
PR Close #39707
		
	
			
		
			
				
	
	
		
			124 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			124 lines
		
	
	
		
			4.2 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);
 | |
|     });
 | |
|   });
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * The type of a variable declarator that is known to have an initializer.
 | |
|  *
 | |
|  * Note: the `init` property is explicitly omitted to workaround a performance cliff in the
 | |
|  * TypeScript type checker.
 | |
|  */
 | |
| type InitializedVariableDeclarator = Omit<t.VariableDeclarator, 'init'>&{init: t.Expression};
 | |
| 
 | |
| function findVarDeclaration(
 | |
|     file: t.File, varName: string): NodePath<InitializedVariableDeclarator> {
 | |
|   let varDecl: NodePath<t.VariableDeclarator>|undefined = undefined;
 | |
|   traverse(file, {
 | |
|     VariableDeclarator: (path: NodePath<t.VariableDeclarator>) => {
 | |
|       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;
 | |
| }
 |