ast-utils.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. "use strict";
  2. /**
  3. * @license
  4. * Copyright Google LLC All Rights Reserved.
  5. *
  6. * Use of this source code is governed by an MIT-style license that can be
  7. * found in the LICENSE file at https://angular.dev/license
  8. */
  9. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
  10. if (k2 === undefined) k2 = k;
  11. var desc = Object.getOwnPropertyDescriptor(m, k);
  12. if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
  13. desc = { enumerable: true, get: function() { return m[k]; } };
  14. }
  15. Object.defineProperty(o, k2, desc);
  16. }) : (function(o, m, k, k2) {
  17. if (k2 === undefined) k2 = k;
  18. o[k2] = m[k];
  19. }));
  20. var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
  21. Object.defineProperty(o, "default", { enumerable: true, value: v });
  22. }) : function(o, v) {
  23. o["default"] = v;
  24. });
  25. var __importStar = (this && this.__importStar) || (function () {
  26. var ownKeys = function(o) {
  27. ownKeys = Object.getOwnPropertyNames || function (o) {
  28. var ar = [];
  29. for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
  30. return ar;
  31. };
  32. return ownKeys(o);
  33. };
  34. return function (mod) {
  35. if (mod && mod.__esModule) return mod;
  36. var result = {};
  37. if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
  38. __setModuleDefault(result, mod);
  39. return result;
  40. };
  41. })();
  42. Object.defineProperty(exports, "__esModule", { value: true });
  43. exports.insertImport = insertImport;
  44. exports.findNodes = findNodes;
  45. exports.getSourceNodes = getSourceNodes;
  46. exports.findNode = findNode;
  47. exports.insertAfterLastOccurrence = insertAfterLastOccurrence;
  48. exports.getDecoratorMetadata = getDecoratorMetadata;
  49. exports.getMetadataField = getMetadataField;
  50. exports.addSymbolToNgModuleMetadata = addSymbolToNgModuleMetadata;
  51. exports.addDeclarationToModule = addDeclarationToModule;
  52. exports.addImportToModule = addImportToModule;
  53. exports.addProviderToModule = addProviderToModule;
  54. exports.addExportToModule = addExportToModule;
  55. exports.addBootstrapToModule = addBootstrapToModule;
  56. exports.isImported = isImported;
  57. exports.getRouterModuleDeclaration = getRouterModuleDeclaration;
  58. exports.addRouteDeclarationToModule = addRouteDeclarationToModule;
  59. exports.hasTopLevelIdentifier = hasTopLevelIdentifier;
  60. const core_1 = require("@angular-devkit/core");
  61. const ts = __importStar(require("../third_party/github.com/Microsoft/TypeScript/lib/typescript"));
  62. const change_1 = require("./change");
  63. const eol_1 = require("./eol");
  64. /**
  65. * Add Import `import { symbolName } from fileName` if the import doesn't exit
  66. * already. Assumes fileToEdit can be resolved and accessed.
  67. * @param fileToEdit File we want to add import to.
  68. * @param symbolName Item to import.
  69. * @param fileName Path to the file.
  70. * @param isDefault If true, import follows style for importing default exports.
  71. * @param alias Alias that the symbol should be inserted under.
  72. * @return Change
  73. */
  74. function insertImport(source, fileToEdit, symbolName, fileName, isDefault = false, alias) {
  75. const rootNode = source;
  76. const allImports = findNodes(rootNode, ts.isImportDeclaration);
  77. const importExpression = alias ? `${symbolName} as ${alias}` : symbolName;
  78. // get nodes that map to import statements from the file fileName
  79. const relevantImports = allImports.filter((node) => {
  80. return ts.isStringLiteralLike(node.moduleSpecifier) && node.moduleSpecifier.text === fileName;
  81. });
  82. if (relevantImports.length > 0) {
  83. const hasNamespaceImport = relevantImports.some((node) => {
  84. return node.importClause?.namedBindings?.kind === ts.SyntaxKind.NamespaceImport;
  85. });
  86. // if imports * from fileName, don't add symbolName
  87. if (hasNamespaceImport) {
  88. return new change_1.NoopChange();
  89. }
  90. const imports = relevantImports.flatMap((node) => {
  91. return node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)
  92. ? node.importClause.namedBindings.elements
  93. : [];
  94. });
  95. // insert import if it's not there
  96. if (!imports.some((node) => (node.propertyName || node.name).text === symbolName)) {
  97. const fallbackPos = findNodes(relevantImports[0], ts.SyntaxKind.CloseBraceToken)[0].getStart() ||
  98. findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();
  99. return insertAfterLastOccurrence(imports, `, ${importExpression}`, fileToEdit, fallbackPos);
  100. }
  101. return new change_1.NoopChange();
  102. }
  103. // no such import declaration exists
  104. const useStrict = findNodes(rootNode, ts.isStringLiteral).filter((n) => n.text === 'use strict');
  105. let fallbackPos = 0;
  106. if (useStrict.length > 0) {
  107. fallbackPos = useStrict[0].end;
  108. }
  109. const open = isDefault ? '' : '{ ';
  110. const close = isDefault ? '' : ' }';
  111. const eol = (0, eol_1.getEOL)(rootNode.getText());
  112. // if there are no imports or 'use strict' statement, insert import at beginning of file
  113. const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;
  114. const separator = insertAtBeginning ? '' : `;${eol}`;
  115. const toInsert = `${separator}import ${open}${importExpression}${close}` +
  116. ` from '${fileName}'${insertAtBeginning ? `;${eol}` : ''}`;
  117. return insertAfterLastOccurrence(allImports, toInsert, fileToEdit, fallbackPos, ts.SyntaxKind.StringLiteral);
  118. }
  119. function findNodes(node, kindOrGuard, max = Infinity, recursive = false) {
  120. if (!node || max == 0) {
  121. return [];
  122. }
  123. const test = typeof kindOrGuard === 'function'
  124. ? kindOrGuard
  125. : (node) => node.kind === kindOrGuard;
  126. const arr = [];
  127. if (test(node)) {
  128. arr.push(node);
  129. max--;
  130. }
  131. if (max > 0 && (recursive || !test(node))) {
  132. for (const child of node.getChildren()) {
  133. findNodes(child, test, max, recursive).forEach((node) => {
  134. if (max > 0) {
  135. arr.push(node);
  136. }
  137. max--;
  138. });
  139. if (max <= 0) {
  140. break;
  141. }
  142. }
  143. }
  144. return arr;
  145. }
  146. /**
  147. * Get all the nodes from a source.
  148. * @param sourceFile The source file object.
  149. * @returns {Array<ts.Node>} An array of all the nodes in the source.
  150. */
  151. function getSourceNodes(sourceFile) {
  152. const nodes = [sourceFile];
  153. const result = [];
  154. while (nodes.length > 0) {
  155. const node = nodes.shift();
  156. if (node) {
  157. result.push(node);
  158. if (node.getChildCount(sourceFile) >= 0) {
  159. nodes.unshift(...node.getChildren());
  160. }
  161. }
  162. }
  163. return result;
  164. }
  165. function findNode(node, kind, text) {
  166. if (node.kind === kind && node.getText() === text) {
  167. return node;
  168. }
  169. let foundNode = null;
  170. ts.forEachChild(node, (childNode) => {
  171. foundNode = foundNode || findNode(childNode, kind, text);
  172. });
  173. return foundNode;
  174. }
  175. /**
  176. * Helper for sorting nodes.
  177. * @return function to sort nodes in increasing order of position in sourceFile
  178. */
  179. function nodesByPosition(first, second) {
  180. return first.getStart() - second.getStart();
  181. }
  182. /**
  183. * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`
  184. * or after the last of occurence of `syntaxKind` if the last occurence is a sub child
  185. * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.
  186. *
  187. * @param nodes insert after the last occurence of nodes
  188. * @param toInsert string to insert
  189. * @param file file to insert changes into
  190. * @param fallbackPos position to insert if toInsert happens to be the first occurence
  191. * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after
  192. * @return Change instance
  193. * @throw Error if toInsert is first occurence but fall back is not set
  194. */
  195. function insertAfterLastOccurrence(nodes, toInsert, file, fallbackPos, syntaxKind) {
  196. let lastItem;
  197. for (const node of nodes) {
  198. if (!lastItem || lastItem.getStart() < node.getStart()) {
  199. lastItem = node;
  200. }
  201. }
  202. if (syntaxKind && lastItem) {
  203. lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();
  204. }
  205. if (!lastItem && fallbackPos == undefined) {
  206. throw new Error(`tried to insert ${toInsert} as first occurence with no fallback position`);
  207. }
  208. const lastItemPosition = lastItem ? lastItem.getEnd() : fallbackPos;
  209. return new change_1.InsertChange(file, lastItemPosition, toInsert);
  210. }
  211. function _angularImportsFromNode(node) {
  212. const ms = node.moduleSpecifier;
  213. let modulePath;
  214. switch (ms.kind) {
  215. case ts.SyntaxKind.StringLiteral:
  216. modulePath = ms.text;
  217. break;
  218. default:
  219. return {};
  220. }
  221. if (!modulePath.startsWith('@angular/')) {
  222. return {};
  223. }
  224. if (node.importClause) {
  225. if (node.importClause.name) {
  226. // This is of the form `import Name from 'path'`. Ignore.
  227. return {};
  228. }
  229. else if (node.importClause.namedBindings) {
  230. const nb = node.importClause.namedBindings;
  231. if (nb.kind == ts.SyntaxKind.NamespaceImport) {
  232. // This is of the form `import * as name from 'path'`. Return `name.`.
  233. return {
  234. [nb.name.text + '.']: modulePath,
  235. };
  236. }
  237. else {
  238. // This is of the form `import {a,b,c} from 'path'`
  239. const namedImports = nb;
  240. return namedImports.elements
  241. .map((is) => (is.propertyName ? is.propertyName.text : is.name.text))
  242. .reduce((acc, curr) => {
  243. acc[curr] = modulePath;
  244. return acc;
  245. }, {});
  246. }
  247. }
  248. return {};
  249. }
  250. else {
  251. // This is of the form `import 'path';`. Nothing to do.
  252. return {};
  253. }
  254. }
  255. function getDecoratorMetadata(source, identifier, module) {
  256. const angularImports = findNodes(source, ts.isImportDeclaration)
  257. .map((node) => _angularImportsFromNode(node))
  258. .reduce((acc, current) => {
  259. for (const key of Object.keys(current)) {
  260. acc[key] = current[key];
  261. }
  262. return acc;
  263. }, {});
  264. return getSourceNodes(source)
  265. .filter((node) => {
  266. return (node.kind == ts.SyntaxKind.Decorator &&
  267. node.expression.kind == ts.SyntaxKind.CallExpression);
  268. })
  269. .map((node) => node.expression)
  270. .filter((expr) => {
  271. if (expr.expression.kind == ts.SyntaxKind.Identifier) {
  272. const id = expr.expression;
  273. return id.text == identifier && angularImports[id.text] === module;
  274. }
  275. else if (expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression) {
  276. // This covers foo.NgModule when importing * as foo.
  277. const paExpr = expr.expression;
  278. // If the left expression is not an identifier, just give up at that point.
  279. if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {
  280. return false;
  281. }
  282. const id = paExpr.name.text;
  283. const moduleId = paExpr.expression.text;
  284. return id === identifier && angularImports[moduleId + '.'] === module;
  285. }
  286. return false;
  287. })
  288. .filter((expr) => expr.arguments[0] && expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression)
  289. .map((expr) => expr.arguments[0]);
  290. }
  291. function getMetadataField(node, metadataField) {
  292. return (node.properties
  293. .filter(ts.isPropertyAssignment)
  294. // Filter out every fields that's not "metadataField". Also handles string literals
  295. // (but not expressions).
  296. .filter(({ name }) => {
  297. return (ts.isIdentifier(name) || ts.isStringLiteral(name)) && name.text === metadataField;
  298. }));
  299. }
  300. function addSymbolToNgModuleMetadata(source, ngModulePath, metadataField, symbolName, importPath = null) {
  301. const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');
  302. const node = nodes[0];
  303. // Find the decorator declaration.
  304. if (!node || !ts.isObjectLiteralExpression(node)) {
  305. return [];
  306. }
  307. // Get all the children property assignment of object literals.
  308. const matchingProperties = getMetadataField(node, metadataField);
  309. if (matchingProperties.length == 0) {
  310. // We haven't found the field in the metadata declaration. Insert a new field.
  311. let position;
  312. let toInsert;
  313. if (node.properties.length == 0) {
  314. position = node.getEnd() - 1;
  315. toInsert = `\n ${metadataField}: [\n${core_1.tags.indentBy(4) `${symbolName}`}\n ]\n`;
  316. }
  317. else {
  318. const childNode = node.properties[node.properties.length - 1];
  319. position = childNode.getEnd();
  320. // Get the indentation of the last element, if any.
  321. const text = childNode.getFullText(source);
  322. const matches = text.match(/^(\r?\n)(\s*)/);
  323. if (matches) {
  324. toInsert =
  325. `,${matches[0]}${metadataField}: [${matches[1]}` +
  326. `${core_1.tags.indentBy(matches[2].length + 2) `${symbolName}`}${matches[0]}]`;
  327. }
  328. else {
  329. toInsert = `, ${metadataField}: [${symbolName}]`;
  330. }
  331. }
  332. if (importPath !== null) {
  333. return [
  334. new change_1.InsertChange(ngModulePath, position, toInsert),
  335. insertImport(source, ngModulePath, symbolName.replace(/\..*$/, ''), importPath),
  336. ];
  337. }
  338. else {
  339. return [new change_1.InsertChange(ngModulePath, position, toInsert)];
  340. }
  341. }
  342. const assignment = matchingProperties[0];
  343. // If it's not an array, nothing we can do really.
  344. if (!ts.isPropertyAssignment(assignment) ||
  345. !ts.isArrayLiteralExpression(assignment.initializer)) {
  346. return [];
  347. }
  348. let expression;
  349. const assignmentInit = assignment.initializer;
  350. const elements = assignmentInit.elements;
  351. if (elements.length) {
  352. const symbolsArray = elements.map((node) => core_1.tags.oneLine `${node.getText()}`);
  353. if (symbolsArray.includes(core_1.tags.oneLine `${symbolName}`)) {
  354. return [];
  355. }
  356. expression = elements[elements.length - 1];
  357. }
  358. else {
  359. expression = assignmentInit;
  360. }
  361. let toInsert;
  362. let position = expression.getEnd();
  363. if (ts.isArrayLiteralExpression(expression)) {
  364. // We found the field but it's empty. Insert it just before the `]`.
  365. position--;
  366. toInsert = `\n${core_1.tags.indentBy(4) `${symbolName}`}\n `;
  367. }
  368. else {
  369. // Get the indentation of the last element, if any.
  370. const text = expression.getFullText(source);
  371. const matches = text.match(/^(\r?\n)(\s*)/);
  372. if (matches) {
  373. toInsert = `,${matches[1]}${core_1.tags.indentBy(matches[2].length) `${symbolName}`}`;
  374. }
  375. else {
  376. toInsert = `, ${symbolName}`;
  377. }
  378. }
  379. if (importPath !== null) {
  380. return [
  381. new change_1.InsertChange(ngModulePath, position, toInsert),
  382. insertImport(source, ngModulePath, symbolName.replace(/\..*$/, ''), importPath),
  383. ];
  384. }
  385. return [new change_1.InsertChange(ngModulePath, position, toInsert)];
  386. }
  387. /**
  388. * Custom function to insert a declaration (component, pipe, directive)
  389. * into NgModule declarations. It also imports the component.
  390. */
  391. function addDeclarationToModule(source, modulePath, classifiedName, importPath) {
  392. return addSymbolToNgModuleMetadata(source, modulePath, 'declarations', classifiedName, importPath);
  393. }
  394. /**
  395. * Custom function to insert an NgModule into NgModule imports. It also imports the module.
  396. */
  397. function addImportToModule(source, modulePath, classifiedName, importPath) {
  398. return addSymbolToNgModuleMetadata(source, modulePath, 'imports', classifiedName, importPath);
  399. }
  400. /**
  401. * Custom function to insert a provider into NgModule. It also imports it.
  402. */
  403. function addProviderToModule(source, modulePath, classifiedName, importPath) {
  404. return addSymbolToNgModuleMetadata(source, modulePath, 'providers', classifiedName, importPath);
  405. }
  406. /**
  407. * Custom function to insert an export into NgModule. It also imports it.
  408. */
  409. function addExportToModule(source, modulePath, classifiedName, importPath) {
  410. return addSymbolToNgModuleMetadata(source, modulePath, 'exports', classifiedName, importPath);
  411. }
  412. /**
  413. * Custom function to insert an export into NgModule. It also imports it.
  414. */
  415. function addBootstrapToModule(source, modulePath, classifiedName, importPath) {
  416. return addSymbolToNgModuleMetadata(source, modulePath, 'bootstrap', classifiedName, importPath);
  417. }
  418. /**
  419. * Determine if an import already exists.
  420. */
  421. function isImported(source, classifiedName, importPath) {
  422. const allNodes = getSourceNodes(source);
  423. const matchingNodes = allNodes
  424. .filter(ts.isImportDeclaration)
  425. .filter((imp) => ts.isStringLiteral(imp.moduleSpecifier) && imp.moduleSpecifier.text === importPath)
  426. .filter((imp) => {
  427. if (!imp.importClause) {
  428. return false;
  429. }
  430. const nodes = findNodes(imp.importClause, ts.isImportSpecifier).filter((n) => n.getText() === classifiedName);
  431. return nodes.length > 0;
  432. });
  433. return matchingNodes.length > 0;
  434. }
  435. /**
  436. * Returns the RouterModule declaration from NgModule metadata, if any.
  437. */
  438. function getRouterModuleDeclaration(source) {
  439. const result = getDecoratorMetadata(source, 'NgModule', '@angular/core');
  440. const node = result[0];
  441. if (!node || !ts.isObjectLiteralExpression(node)) {
  442. return undefined;
  443. }
  444. const matchingProperties = getMetadataField(node, 'imports');
  445. if (!matchingProperties) {
  446. return;
  447. }
  448. const assignment = matchingProperties[0];
  449. if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {
  450. return;
  451. }
  452. const arrLiteral = assignment.initializer;
  453. return arrLiteral.elements
  454. .filter((el) => el.kind === ts.SyntaxKind.CallExpression)
  455. .find((el) => el.getText().startsWith('RouterModule'));
  456. }
  457. /**
  458. * Adds a new route declaration to a router module (i.e. has a RouterModule declaration)
  459. */
  460. function addRouteDeclarationToModule(source, fileToAdd, routeLiteral) {
  461. const routerModuleExpr = getRouterModuleDeclaration(source);
  462. if (!routerModuleExpr) {
  463. throw new Error(`Couldn't find a route declaration in ${fileToAdd}.\n` +
  464. `Use the '--module' option to specify a different routing module.`);
  465. }
  466. const scopeConfigMethodArgs = routerModuleExpr.arguments;
  467. if (!scopeConfigMethodArgs.length) {
  468. const { line } = source.getLineAndCharacterOfPosition(routerModuleExpr.getStart());
  469. throw new Error(`The router module method doesn't have arguments ` + `at line ${line} in ${fileToAdd}`);
  470. }
  471. let routesArr;
  472. const routesArg = scopeConfigMethodArgs[0];
  473. // Check if the route declarations array is
  474. // an inlined argument of RouterModule or a standalone variable
  475. if (ts.isArrayLiteralExpression(routesArg)) {
  476. routesArr = routesArg;
  477. }
  478. else {
  479. const routesVarName = routesArg.getText();
  480. let routesVar;
  481. if (routesArg.kind === ts.SyntaxKind.Identifier) {
  482. routesVar = source.statements.filter(ts.isVariableStatement).find((v) => {
  483. return v.declarationList.declarations[0].name.getText() === routesVarName;
  484. });
  485. }
  486. if (!routesVar) {
  487. const { line } = source.getLineAndCharacterOfPosition(routesArg.getStart());
  488. throw new Error(`No route declaration array was found that corresponds ` +
  489. `to router module at line ${line} in ${fileToAdd}`);
  490. }
  491. routesArr = findNodes(routesVar, ts.SyntaxKind.ArrayLiteralExpression, 1)[0];
  492. }
  493. const occurrencesCount = routesArr.elements.length;
  494. const text = routesArr.getFullText(source);
  495. let route = routeLiteral;
  496. let insertPos = routesArr.elements.pos;
  497. if (occurrencesCount > 0) {
  498. const lastRouteLiteral = [...routesArr.elements].pop();
  499. const lastRouteIsWildcard = ts.isObjectLiteralExpression(lastRouteLiteral) &&
  500. lastRouteLiteral.properties.some((n) => ts.isPropertyAssignment(n) &&
  501. ts.isIdentifier(n.name) &&
  502. n.name.text === 'path' &&
  503. ts.isStringLiteral(n.initializer) &&
  504. n.initializer.text === '**');
  505. const indentation = text.match(/\r?\n(\r?)\s*/) || [];
  506. const routeText = `${indentation[0] || ' '}${routeLiteral}`;
  507. // Add the new route before the wildcard route
  508. // otherwise we'll always redirect to the wildcard route
  509. if (lastRouteIsWildcard) {
  510. insertPos = lastRouteLiteral.pos;
  511. route = `${routeText},`;
  512. }
  513. else {
  514. insertPos = lastRouteLiteral.end;
  515. route = `,${routeText}`;
  516. }
  517. }
  518. return new change_1.InsertChange(fileToAdd, insertPos, route);
  519. }
  520. /** Asserts if the specified node is a named declaration (e.g. class, interface). */
  521. function isNamedNode(node) {
  522. return !!node.name && ts.isIdentifier(node.name);
  523. }
  524. /**
  525. * Determines if a SourceFile has a top-level declaration whose name matches a specific symbol.
  526. * Can be used to avoid conflicts when inserting new imports into a file.
  527. * @param sourceFile File in which to search.
  528. * @param symbolName Name of the symbol to search for.
  529. * @param skipModule Path of the module that the symbol may have been imported from. Used to
  530. * avoid false positives where the same symbol we're looking for may have been imported.
  531. */
  532. function hasTopLevelIdentifier(sourceFile, symbolName, skipModule = null) {
  533. for (const node of sourceFile.statements) {
  534. if (isNamedNode(node) && node.name.text === symbolName) {
  535. return true;
  536. }
  537. if (ts.isVariableStatement(node) &&
  538. node.declarationList.declarations.some((decl) => {
  539. return isNamedNode(decl) && decl.name.text === symbolName;
  540. })) {
  541. return true;
  542. }
  543. if (ts.isImportDeclaration(node) &&
  544. ts.isStringLiteralLike(node.moduleSpecifier) &&
  545. node.moduleSpecifier.text !== skipModule &&
  546. node.importClause?.namedBindings &&
  547. ts.isNamedImports(node.importClause.namedBindings) &&
  548. node.importClause.namedBindings.elements.some((el) => el.name.text === symbolName)) {
  549. return true;
  550. }
  551. }
  552. return false;
  553. }