c-cpp.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. import type { Node as SyntaxNode } from 'web-tree-sitter';
  2. import { getChildByField, getNodeText } from '../tree-sitter-helpers';
  3. import type { LanguageExtractor } from '../tree-sitter-types';
  4. /**
  5. * Find the function NAME's `qualified_identifier` (`Foo::bar`) inside a
  6. * declarator, skipping the `parameter_list` — a parameter with a qualified type
  7. * (`const std::string& x`) must NOT be mistaken for the method name. Without the
  8. * skip, a plain free function `std::string TableFileName(const std::string&...)`
  9. * was named `string` (from the parameter type), so calls to it never resolved
  10. * and its file looked like nothing depended on it.
  11. */
  12. function findDeclaratorQualifiedId(declarator: SyntaxNode): SyntaxNode | undefined {
  13. const queue: SyntaxNode[] = [declarator];
  14. while (queue.length > 0) {
  15. const current = queue.shift()!;
  16. if (current.type === 'qualified_identifier') return current;
  17. for (let i = 0; i < current.namedChildCount; i++) {
  18. const child = current.namedChild(i);
  19. // Don't descend into parameters or the trailing return type — their types
  20. // (`const std::string&`, `-> std::string`) aren't the function name.
  21. if (child && child.type !== 'parameter_list' && child.type !== 'trailing_return_type') {
  22. queue.push(child);
  23. }
  24. }
  25. }
  26. return undefined;
  27. }
  28. function extractCppQualifiedMethodName(node: SyntaxNode, source: string): string | undefined {
  29. const declarator = getChildByField(node, 'declarator');
  30. if (!declarator) return undefined;
  31. const qid = findDeclaratorQualifiedId(declarator);
  32. if (!qid) return undefined;
  33. const parts = getNodeText(qid, source).trim().split('::').filter(Boolean);
  34. return parts[parts.length - 1];
  35. }
  36. function extractCppReceiverType(node: SyntaxNode, source: string): string | undefined {
  37. const declarator = getChildByField(node, 'declarator');
  38. if (!declarator) return undefined;
  39. const qid = findDeclaratorQualifiedId(declarator);
  40. if (!qid) return undefined;
  41. const parts = getNodeText(qid, source).trim().split('::').filter(Boolean);
  42. return parts.length > 1 ? parts.slice(0, -1).join('::') : undefined;
  43. }
  44. /**
  45. * Built-in / non-class return types that can never be a method receiver. We
  46. * store no `returnType` for these so resolution never tries to resolve a method
  47. * on `void` / `int` / etc.
  48. */
  49. const CPP_NON_CLASS_RETURN = new Set([
  50. 'void', 'bool', 'char', 'short', 'int', 'long', 'float', 'double', 'unsigned',
  51. 'signed', 'size_t', 'ssize_t', 'auto', 'wchar_t', 'char8_t', 'char16_t',
  52. 'char32_t', 'int8_t', 'int16_t', 'int32_t', 'int64_t', 'uint8_t', 'uint16_t',
  53. 'uint32_t', 'uint64_t', 'intptr_t', 'uintptr_t', 'nullptr_t',
  54. ]);
  55. /**
  56. * Normalize a C++ return type to the bare class name a method could be called
  57. * on. Unwraps smart-pointer / optional wrappers to their element type
  58. * (`std::unique_ptr<Widget>` → `Widget`) so a factory's `->method()` resolves on
  59. * the pointee. Strips cv-qualifiers, `&`/`*`, namespace qualifiers, and other
  60. * template args. Returns undefined for primitives / void / `auto` / empty.
  61. */
  62. export function normalizeCppReturnType(raw: string): string | undefined {
  63. let t = raw.trim();
  64. if (!t) return undefined;
  65. // Unwrap smart pointers / optional to their pointee (the thing you call `->` on).
  66. const wrapper = t.match(/\b(?:std\s*::\s*)?(?:unique_ptr|shared_ptr|weak_ptr|optional)\s*<\s*([^,>]+?)\s*>/);
  67. if (wrapper && wrapper[1]) t = wrapper[1];
  68. t = t
  69. .replace(/\b(?:const|volatile|typename|struct|class|enum)\b/g, ' ')
  70. .replace(/<[^>]*>/g, ' ')
  71. .replace(/[*&]+/g, ' ')
  72. .replace(/\s+/g, ' ')
  73. .trim();
  74. if (!t) return undefined;
  75. const last = t.split('::').filter(Boolean).pop();
  76. if (!last) return undefined;
  77. if (CPP_NON_CLASS_RETURN.has(last)) return undefined;
  78. if (!/^[A-Za-z_]\w*$/.test(last)) return undefined;
  79. return last;
  80. }
  81. /**
  82. * Strip C++ template arguments from a base-type reference name so it matches the
  83. * bare class/struct the template was DEFINED as. `template<typename T> class
  84. * Base { … }` is indexed as a node named `Base`, but a derived class
  85. * `class D : public Base<int>` records its base as the full `Base<int>` (and
  86. * `class Q : public ns::Tpl<int>` as `ns::Tpl<int>`) — neither name-matches
  87. * `Base` / `ns::Tpl`, so the `extends` edge never resolves and the derived class
  88. * looks like it inherits from nothing (#1043).
  89. *
  90. * Removes every balanced `<…>` group regardless of nesting or position, so
  91. * `Base<int>` → `Base`, `ns::Tpl<Foo<int>>` → `ns::Tpl`, and the rare
  92. * `Outer<int>::Inner` → `Outer::Inner`. The remaining qualified head is exactly
  93. * what the non-templated base case already produces, so resolution treats them
  94. * identically. A name with no template args passes through unchanged.
  95. */
  96. export function stripCppTemplateArgs(name: string): string {
  97. if (!name.includes('<')) return name;
  98. let out = '';
  99. let depth = 0;
  100. for (const ch of name) {
  101. if (ch === '<') depth++;
  102. else if (ch === '>') { if (depth > 0) depth--; }
  103. else if (depth === 0) out += ch;
  104. }
  105. return out.trim();
  106. }
  107. /**
  108. * A function/method's return type lives in the `function_definition`'s `type`
  109. * field (`Metrics& Metrics::instance()` → `Metrics`). Constructors, destructors,
  110. * and conversion operators have no `type` field → undefined.
  111. */
  112. function extractCppReturnType(node: SyntaxNode, source: string): string | undefined {
  113. const typeNode = getChildByField(node, 'type');
  114. if (!typeNode) return undefined;
  115. return normalizeCppReturnType(getNodeText(typeNode, source));
  116. }
  117. export const cExtractor: LanguageExtractor = {
  118. // Universal net: recover a real name from any macro-mangled function name.
  119. recoverMangledName: recoverMangledCppName,
  120. functionTypes: ['function_definition'],
  121. classTypes: [],
  122. methodTypes: [],
  123. interfaceTypes: [],
  124. structTypes: ['struct_specifier'],
  125. enumTypes: ['enum_specifier'],
  126. enumMemberTypes: ['enumerator'],
  127. typeAliasTypes: ['type_definition'], // typedef
  128. importTypes: ['preproc_include'],
  129. callTypes: ['call_expression'],
  130. variableTypes: ['declaration'],
  131. nameField: 'declarator',
  132. bodyField: 'body',
  133. paramsField: 'parameters',
  134. // A `const`/`static const` file-scope declaration carries a `type_qualifier`
  135. // child reading "const" — extract those as `constant`, plain globals as
  136. // `variable`.
  137. isConst: (node) =>
  138. node.namedChildren.some(
  139. (c: SyntaxNode) => c.type === 'type_qualifier' && c.text === 'const'
  140. ),
  141. getReturnType: extractCppReturnType,
  142. resolveTypeAliasKind: (node, _source) => {
  143. // C typedef: `typedef enum { ... } name;` or `typedef struct { ... } name;`
  144. // The inner enum_specifier/struct_specifier is anonymous, but we want the typedef name
  145. // to become the enum/struct node name.
  146. for (let i = 0; i < node.namedChildCount; i++) {
  147. const child = node.namedChild(i);
  148. if (!child) continue;
  149. if (child.type === 'enum_specifier' && getChildByField(child, 'body')) return 'enum';
  150. if (child.type === 'struct_specifier' && getChildByField(child, 'body')) return 'struct';
  151. }
  152. return undefined;
  153. },
  154. extractImport: (node, source) => {
  155. const importText = source.substring(node.startIndex, node.endIndex).trim();
  156. // C includes: #include <stdio.h>, #include "myheader.h"
  157. const systemLib = node.namedChildren.find((c: SyntaxNode) => c.type === 'system_lib_string');
  158. if (systemLib) {
  159. return { moduleName: getNodeText(systemLib, source).replace(/^<|>$/g, ''), signature: importText };
  160. }
  161. const stringLiteral = node.namedChildren.find((c: SyntaxNode) => c.type === 'string_literal');
  162. if (stringLiteral) {
  163. const stringContent = stringLiteral.namedChildren.find((c: SyntaxNode) => c.type === 'string_content');
  164. if (stringContent) {
  165. return { moduleName: getNodeText(stringContent, source), signature: importText };
  166. }
  167. }
  168. return null;
  169. },
  170. };
  171. /**
  172. * Detect tree-sitter's misparse of a macro-annotated class/struct, e.g.
  173. * `class MACRO Name { … }` or `class MACRO Name : public Base { … }` (#946).
  174. * Not knowing `MACRO` is a macro, tree-sitter reads `class MACRO` as an
  175. * *elaborated type specifier* (a bodyless `class_specifier`/`struct_specifier`
  176. * whose "type name" is the macro) and the rest as a function: `Name` becomes the
  177. * declarator and the `{ … }` a function body — so the whole declaration surfaces
  178. * as a `function_definition` named after the class, with a line range spanning
  179. * the entire class body. (A base clause, when present, additionally lands in an
  180. * `ERROR` node, but it isn't required — the leading macro alone triggers this.)
  181. *
  182. * Two structural signals pin it down with no risk to genuine code:
  183. * - the `type` field is a *bodyless* class/struct specifier — an elaborated
  184. * type, not a real inline-defined return type like
  185. * `struct P { int x; } makeP() { … }` (which carries a field list); and
  186. * - the declarator is not a `function_declarator` — a real function definition
  187. * always has one, which also leaves the legal-but-rare `class Foo f() { … }`
  188. * (an elaborated return type on a genuine function) alone.
  189. *
  190. * The class body is mangled by the same misparse and is unrecoverable, so —
  191. * matching how macro-prefixed C prototypes are handled — we drop the spurious
  192. * node rather than mint a misleading whole-body `function` that pollutes
  193. * callers/impact and skews kind statistics.
  194. */
  195. function isMacroMisparsedTypeDecl(node: SyntaxNode): boolean {
  196. const typeNode = getChildByField(node, 'type');
  197. if (!typeNode) return false;
  198. if (typeNode.type !== 'class_specifier' && typeNode.type !== 'struct_specifier') return false;
  199. if (typeNode.namedChildren.some((c: SyntaxNode) => c.type === 'field_declaration_list')) return false;
  200. const declarator = getChildByField(node, 'declarator');
  201. if (declarator && declarator.type === 'function_declarator') return false;
  202. return true;
  203. }
  204. /**
  205. * Blank an export/visibility macro in a `class/struct EXPORT_MACRO Name …`
  206. * *definition* header before parsing. Not knowing the macro, tree-sitter reads
  207. * `class EXPORT_MACRO` as an elaborated type specifier and the rest as a
  208. * function, so the whole class — its name, base clause, and members — drops out
  209. * of the index (#946 catches the resulting phantom function but can't recover
  210. * the class), which silently breaks type-hierarchy / inheritance-impact queries
  211. * for effectively every Unreal-Engine (`*_API`), Qt/Boost (`*_EXPORT`), LLVM
  212. * (`*_ABI`), … class. Replacing the macro with equal-length spaces preserves
  213. * every byte offset (and thus line/column), so the declaration then parses as a
  214. * normal class_specifier and the existing extraction emits the node, members,
  215. * and `extends` edge. (#1061, follow-up to #946.)
  216. *
  217. * Matched tightly so it can't touch the same macro used as an ordinary value
  218. * elsewhere (`int x = SOME_API;`): the macro is the ALL-CAPS token sitting
  219. * *between* `class`/`struct` and the type name, and the trailing `[:{]`
  220. * definition-guard fires only when a base clause or body follows — the only
  221. * shape that misparses. That guard also leaves elaborated-type variable
  222. * declarations (`struct FOO var;`, `class FOO obj = …`) untouched, since those
  223. * end in `;` / `=` / `[`, never `:` / `{`. C++-only (wired into cppExtractor),
  224. * so C's heavier use of `struct TAG var;` never reaches it.
  225. */
  226. export function blankCppExportMacros(source: string): string {
  227. if (source.indexOf('class') === -1 && source.indexOf('struct') === -1) return source;
  228. return source.replace(
  229. /\b(class|struct)(\s+)([A-Z][A-Z0-9_]+)(?=\s+[A-Za-z_]\w*(?:\s+final)?\s*[:{])/g,
  230. (_m, kw, ws, macro) => kw + ws + ' '.repeat(macro.length)
  231. );
  232. }
  233. /**
  234. * Blank a known inline-specifier macro sitting in front of a function's return
  235. * type (`FORCEINLINE FString GetName(…)`), before parsing. Not knowing the
  236. * macro, tree-sitter can't reconcile `MACRO <return-type> <name>(` — an extra
  237. * type-like token before the name — and drops into error recovery: the macro
  238. * becomes the return type and, for a non-primitive return, the return type gets
  239. * glued onto the name (`GetName` → `"FString GetName"`), so the function can't
  240. * be found by name and its callers don't link. This is pervasive in Unreal
  241. * Engine (`FORCEINLINE <ret> <name>(…)`) and in vendored third-party libraries
  242. * that define their own inline macro (pugixml's `PUGI__FN`, Godot's
  243. * `_FORCE_INLINE_`, Boost's `BOOST_FORCEINLINE`, …). Replacing the macro with
  244. * equal-length spaces preserves every byte offset (so line/column stay exact)
  245. * and the declaration then parses as an ordinary function — recovering the real
  246. * name AND the return type — mirroring how `blankCppExportMacros` recovers
  247. * macro-annotated classes (#946/#1061).
  248. *
  249. * Matched tightly so it can't touch an ordinary identifier: only the exact,
  250. * curated inline-specifier tokens below (never an arbitrary all-caps token, so a
  251. * real return type like `HRESULT DoIt()` is untouched), and only in specifier
  252. * position — immediately followed by whitespace and the identifier that starts
  253. * the return type or name. That lookahead leaves value/expression uses
  254. * (`x = FORCEINLINE ? …`), string literals, and longer words
  255. * (`FORCEINLINE_SOMETHINGELSE`, word-boundary) alone. To cover a new codebase's
  256. * inline macro, add its exact token to the list.
  257. */
  258. const CPP_INLINE_MACROS = [
  259. // Unreal Engine
  260. 'FORCEINLINE_DEBUGGABLE', 'FORCENOINLINE', 'FORCEINLINE',
  261. // pugixml (ubiquitous vendored XML parser): `#define PUGI__FN inline` before
  262. // the return type, plus `PUGIXML_FUNCTION` (linkage macro) between the return
  263. // type and the name — the blank mechanism handles both positions.
  264. 'PUGI__FN_NO_INLINE', 'PUGI__FN', 'PUGIXML_FUNCTION',
  265. // Godot
  266. '_ALWAYS_INLINE_', '_FORCE_INLINE_',
  267. // Boost
  268. 'BOOST_FORCEINLINE', 'BOOST_NOINLINE',
  269. // Qt (per-method markers + inline)
  270. 'Q_INVOKABLE', 'Q_SCRIPTABLE', 'Q_ALWAYS_INLINE', 'Q_SLOT', 'Q_SIGNAL',
  271. // Folly / Abseil / LLVM / V8 / Eigen / rapidjson
  272. 'FOLLY_ALWAYS_INLINE', 'FOLLY_NOINLINE',
  273. 'ABSL_ATTRIBUTE_ALWAYS_INLINE', 'ABSL_ATTRIBUTE_NOINLINE',
  274. 'LLVM_ATTRIBUTE_ALWAYS_INLINE', 'LLVM_ATTRIBUTE_NOINLINE',
  275. 'V8_INLINE', 'V8_NOINLINE',
  276. 'EIGEN_STRONG_INLINE', 'EIGEN_ALWAYS_INLINE', 'EIGEN_DEVICE_FUNC',
  277. 'RAPIDJSON_FORCEINLINE',
  278. // Common cross-ecosystem inline/attribute hints
  279. 'ALWAYS_INLINE', 'FORCE_INLINE', 'NOINLINE',
  280. ] as const;
  281. // One alternation, longest token first so a longer macro wins over a prefix.
  282. const CPP_INLINE_MACRO_RE = new RegExp(
  283. `\\b(${[...CPP_INLINE_MACROS].sort((a, b) => b.length - a.length).join('|')})\\b(?=\\s+[A-Za-z_])`,
  284. 'g'
  285. );
  286. export function blankCppInlineMacros(source: string): string {
  287. if (!CPP_INLINE_MACROS.some((m) => source.indexOf(m) !== -1)) return source;
  288. return source.replace(CPP_INLINE_MACRO_RE, (m) => ' '.repeat(m.length));
  289. }
  290. // Bare C/C++ type/qualifier tokens that must never be taken as a recovered
  291. // function name (guards `recoverMangledCppName` against the `Ret (name)` idiom,
  292. // where the token before the params is the return type, not the name).
  293. const CPP_PRIMITIVE_NAMES = new Set([
  294. 'bool', 'void', 'int', 'char', 'short', 'long', 'float', 'double', 'unsigned',
  295. 'signed', 'wchar_t', 'char8_t', 'char16_t', 'char32_t', 'char_t', 'size_t',
  296. 'auto', 'const', 'struct', 'class', 'enum', 'union', 'typename',
  297. ]);
  298. /**
  299. * Universal fallback (any macro, no list) for a C/C++ function name still mangled
  300. * because a macro we don't blank sat in front of the return type: `MACRO Ret
  301. * name(…)` / `Ret MACRO name(…)` misparse so the return type is glued onto the
  302. * name ("Ret name", "char_t* to_str(double v)"). Recover the real identifier —
  303. * the token immediately before the parameter list (or the last token). This runs
  304. * AFTER the curated pre-parse blank, so it only ever sees the residual tail that
  305. * blanking didn't already fix cleanly (which also recovers the return type).
  306. *
  307. * Safe by construction: only touches an ALREADY-mangled name — one with an
  308. * internal space that isn't a legit `operator …`/destructor — so a well-formed
  309. * name is returned unchanged. Guarded against the two ways it could mis-pick:
  310. * the `Ret (name)` parenthesized-name idiom (left as-is, ambiguous), and a token
  311. * that is a bare primitive/keyword rather than a real identifier.
  312. */
  313. export function recoverMangledCppName(name: string): string {
  314. if (!/\s/.test(name) || name.startsWith('operator') || name.startsWith('~')) return name;
  315. if (/^\S+\s+\([A-Za-z_]\w*\)/.test(name)) return name; // `Ret (name)` idiom — leave alone
  316. const beforeParams = name.includes('(') ? name.slice(0, name.indexOf('(')) : name;
  317. const tokens = beforeParams.trim().split(/\s+/);
  318. const candidate = tokens[tokens.length - 1];
  319. if (!candidate || !/^[A-Za-z_]\w*$/.test(candidate) || CPP_PRIMITIVE_NAMES.has(candidate)) return name;
  320. return candidate;
  321. }
  322. /** C/C++ source pre-processing before tree-sitter: recover both macro-annotated
  323. * class definitions and macro-prefixed function definitions. Offset-preserving. */
  324. function preParseCppSource(source: string): string {
  325. return blankCppInlineMacros(blankCppExportMacros(source));
  326. }
  327. export const cppExtractor: LanguageExtractor = {
  328. // Recover macro-annotated class/struct definitions (`class MYMODULE_API Foo : Base`,
  329. // #1061/#946) and macro-prefixed functions (`FORCEINLINE FString Foo()`, #1093
  330. // follow-up) that tree-sitter otherwise misparses.
  331. preParse: preParseCppSource,
  332. // Universal net for any macro the curated blank list misses.
  333. recoverMangledName: recoverMangledCppName,
  334. functionTypes: ['function_definition'],
  335. classTypes: ['class_specifier'],
  336. // A bodiless `class_specifier` is a forward declaration (`class Foo;`) or an
  337. // elaborated type reference, not a definition. Skip it so dozens of forward
  338. // decls across headers don't mint phantom `class` nodes that crowd out — and
  339. // get picked as the blast-radius representative over — the single real
  340. // definition, exactly as bodiless struct/enum specifiers are already skipped. (#1093)
  341. skipBodilessClass: true,
  342. methodTypes: ['function_definition'],
  343. interfaceTypes: [],
  344. structTypes: ['struct_specifier'],
  345. enumTypes: ['enum_specifier'],
  346. enumMemberTypes: ['enumerator'],
  347. typeAliasTypes: ['type_definition', 'alias_declaration'], // typedef and using
  348. importTypes: ['preproc_include'],
  349. callTypes: ['call_expression'],
  350. variableTypes: ['declaration'],
  351. nameField: 'declarator',
  352. bodyField: 'body',
  353. paramsField: 'parameters',
  354. resolveName: extractCppQualifiedMethodName,
  355. getReceiverType: extractCppReceiverType,
  356. getReturnType: extractCppReturnType,
  357. getVisibility: (node) => {
  358. // Check for access specifier in parent
  359. const parent = node.parent;
  360. if (parent) {
  361. for (let i = 0; i < parent.childCount; i++) {
  362. const child = parent.child(i);
  363. if (child?.type === 'access_specifier') {
  364. const text = child.text;
  365. if (text.includes('public')) return 'public';
  366. if (text.includes('private')) return 'private';
  367. if (text.includes('protected')) return 'protected';
  368. }
  369. }
  370. }
  371. return undefined;
  372. },
  373. resolveTypeAliasKind: (node, _source) => {
  374. // C++ typedef: `typedef enum { ... } name;` or `typedef struct { ... } name;`
  375. for (let i = 0; i < node.namedChildCount; i++) {
  376. const child = node.namedChild(i);
  377. if (!child) continue;
  378. if (child.type === 'enum_specifier' && getChildByField(child, 'body')) return 'enum';
  379. if (child.type === 'struct_specifier' && getChildByField(child, 'body')) return 'struct';
  380. }
  381. return undefined;
  382. },
  383. isMisparsedFunction: (name, node) => {
  384. // C++ macros like NLOHMANN_JSON_NAMESPACE_BEGIN cause tree-sitter to misparse
  385. // namespace blocks as function_definitions (e.g. name = "namespace detail").
  386. // Also filter C++ keywords that tree-sitter occasionally misinterprets as
  387. // function/method names (e.g. switch statements inside macro-confused scopes).
  388. if (name.startsWith('namespace')) return true;
  389. const cppKeywords = ['switch', 'if', 'for', 'while', 'do', 'case', 'return'];
  390. if (cppKeywords.includes(name)) return true;
  391. // `class MACRO Name : public Base { … }` misparses to a function_definition
  392. // named after the class. `blankCppExportMacros` (preParse) recovers the
  393. // common ALL-CAPS export-macro shape; this drop is the fallback for any
  394. // residual misparse it doesn't blank — still no phantom function (#1061/#946).
  395. return isMacroMisparsedTypeDecl(node);
  396. },
  397. extractImport: (node, source) => {
  398. const importText = source.substring(node.startIndex, node.endIndex).trim();
  399. // C++ includes: #include <iostream>, #include "myheader.h"
  400. const systemLib = node.namedChildren.find((c: SyntaxNode) => c.type === 'system_lib_string');
  401. if (systemLib) {
  402. return { moduleName: getNodeText(systemLib, source).replace(/^<|>$/g, ''), signature: importText };
  403. }
  404. const stringLiteral = node.namedChildren.find((c: SyntaxNode) => c.type === 'string_literal');
  405. if (stringLiteral) {
  406. const stringContent = stringLiteral.namedChildren.find((c: SyntaxNode) => c.type === 'string_content');
  407. if (stringContent) {
  408. return { moduleName: getNodeText(stringContent, source), signature: importText };
  409. }
  410. }
  411. return null;
  412. },
  413. };