From 7a40234fb77a7c2d7434eee7f736b1acb560b481 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 29 Jul 2024 12:24:04 +0200 Subject: [PATCH] refactor(migrations): optimize some of the import utilities (#57179) Makes a few optimizations in the utilities we use for dealing with imports in migrations. I didn't end up using these in the inject migration, but they should still come in handy. Includes: 1. Exiting `isReferenceToImport` early when the node being checked is an identifier and it doesn't match the identifier of the import. This saves us some type checker calls. 2. Adds the ability to pass a single string to `getImportSpecifiers`. This saves us unnecessary arrays and for loops. PR Close #57179 --- .../schematics/utils/typescript/imports.ts | 40 ++++++++++++------- .../schematics/utils/typescript/symbol.ts | 7 ++++ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/packages/core/schematics/utils/typescript/imports.ts b/packages/core/schematics/utils/typescript/imports.ts index 75fd966821f..a10c9cfd61f 100644 --- a/packages/core/schematics/utils/typescript/imports.ts +++ b/packages/core/schematics/utils/typescript/imports.ts @@ -67,28 +67,40 @@ export function getImportSpecifier( moduleName: string | RegExp, specifierName: string, ): ts.ImportSpecifier | null { - return getImportSpecifiers(sourceFile, moduleName, [specifierName])[0] ?? null; + return getImportSpecifiers(sourceFile, moduleName, specifierName)[0] ?? null; } export function getImportSpecifiers( sourceFile: ts.SourceFile, moduleName: string | RegExp, - specifierNames: string[], + specifierOrSpecifiers: string | string[], ): ts.ImportSpecifier[] { const matches: ts.ImportSpecifier[] = []; for (const node of sourceFile.statements) { - if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) { - const isMatch = - typeof moduleName === 'string' - ? node.moduleSpecifier.text === moduleName - : moduleName.test(node.moduleSpecifier.text); - const namedBindings = node.importClause?.namedBindings; - if (isMatch && namedBindings && ts.isNamedImports(namedBindings)) { - for (const specifierName of specifierNames) { - const match = findImportSpecifier(namedBindings.elements, specifierName); - if (match) { - matches.push(match); - } + if (!ts.isImportDeclaration(node) || !ts.isStringLiteral(node.moduleSpecifier)) { + continue; + } + + const namedBindings = node.importClause?.namedBindings; + const isMatch = + typeof moduleName === 'string' + ? node.moduleSpecifier.text === moduleName + : moduleName.test(node.moduleSpecifier.text); + + if (!isMatch || !namedBindings || !ts.isNamedImports(namedBindings)) { + continue; + } + + if (typeof specifierOrSpecifiers === 'string') { + const match = findImportSpecifier(namedBindings.elements, specifierOrSpecifiers); + if (match) { + matches.push(match); + } + } else { + for (const specifierName of specifierOrSpecifiers) { + const match = findImportSpecifier(namedBindings.elements, specifierName); + if (match) { + matches.push(match); } } } diff --git a/packages/core/schematics/utils/typescript/symbol.ts b/packages/core/schematics/utils/typescript/symbol.ts index 82228e92697..c497f6c972a 100644 --- a/packages/core/schematics/utils/typescript/symbol.ts +++ b/packages/core/schematics/utils/typescript/symbol.ts @@ -27,6 +27,13 @@ export function isReferenceToImport( node: ts.Node, importSpecifier: ts.ImportSpecifier, ): boolean { + // If this function is called on an identifier (should be most cases), we can quickly rule out + // non-matches by comparing the identifier's string and the local name of the import specifier + // which saves us some calls to the type checker. + if (ts.isIdentifier(node) && node.text !== importSpecifier.name.text) { + return false; + } + const nodeSymbol = typeChecker.getTypeAtLocation(node).getSymbol(); const importSymbol = typeChecker.getTypeAtLocation(importSpecifier).getSymbol(); return (