mnehmos.trace.mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1662 -0
  3. package/dist/adapters/bootstrap.d.ts +29 -0
  4. package/dist/adapters/bootstrap.d.ts.map +1 -0
  5. package/dist/adapters/bootstrap.js +46 -0
  6. package/dist/adapters/bootstrap.js.map +1 -0
  7. package/dist/adapters/errors.d.ts +94 -0
  8. package/dist/adapters/errors.d.ts.map +1 -0
  9. package/dist/adapters/errors.js +107 -0
  10. package/dist/adapters/errors.js.map +1 -0
  11. package/dist/adapters/graphql/index.d.ts +9 -0
  12. package/dist/adapters/graphql/index.d.ts.map +1 -0
  13. package/dist/adapters/graphql/index.js +9 -0
  14. package/dist/adapters/graphql/index.js.map +1 -0
  15. package/dist/adapters/graphql/sdl-parser.d.ts +74 -0
  16. package/dist/adapters/graphql/sdl-parser.d.ts.map +1 -0
  17. package/dist/adapters/graphql/sdl-parser.js +559 -0
  18. package/dist/adapters/graphql/sdl-parser.js.map +1 -0
  19. package/dist/adapters/grpc/adapter.d.ts +76 -0
  20. package/dist/adapters/grpc/adapter.d.ts.map +1 -0
  21. package/dist/adapters/grpc/adapter.js +362 -0
  22. package/dist/adapters/grpc/adapter.js.map +1 -0
  23. package/dist/adapters/grpc/index.d.ts +10 -0
  24. package/dist/adapters/grpc/index.d.ts.map +1 -0
  25. package/dist/adapters/grpc/index.js +12 -0
  26. package/dist/adapters/grpc/index.js.map +1 -0
  27. package/dist/adapters/grpc/proto-parser.d.ts +76 -0
  28. package/dist/adapters/grpc/proto-parser.d.ts.map +1 -0
  29. package/dist/adapters/grpc/proto-parser.js +523 -0
  30. package/dist/adapters/grpc/proto-parser.js.map +1 -0
  31. package/dist/adapters/grpc/type-converter.d.ts +43 -0
  32. package/dist/adapters/grpc/type-converter.d.ts.map +1 -0
  33. package/dist/adapters/grpc/type-converter.js +270 -0
  34. package/dist/adapters/grpc/type-converter.js.map +1 -0
  35. package/dist/adapters/grpc/types.d.ts +85 -0
  36. package/dist/adapters/grpc/types.d.ts.map +1 -0
  37. package/dist/adapters/grpc/types.js +7 -0
  38. package/dist/adapters/grpc/types.js.map +1 -0
  39. package/dist/adapters/index.d.ts +39 -0
  40. package/dist/adapters/index.d.ts.map +1 -0
  41. package/dist/adapters/index.js +50 -0
  42. package/dist/adapters/index.js.map +1 -0
  43. package/dist/adapters/mcp.d.ts +23 -0
  44. package/dist/adapters/mcp.d.ts.map +1 -0
  45. package/dist/adapters/mcp.js +293 -0
  46. package/dist/adapters/mcp.js.map +1 -0
  47. package/dist/adapters/openapi/adapter.d.ts +213 -0
  48. package/dist/adapters/openapi/adapter.d.ts.map +1 -0
  49. package/dist/adapters/openapi/adapter.js +557 -0
  50. package/dist/adapters/openapi/adapter.js.map +1 -0
  51. package/dist/adapters/openapi/convert.d.ts +120 -0
  52. package/dist/adapters/openapi/convert.d.ts.map +1 -0
  53. package/dist/adapters/openapi/convert.js +363 -0
  54. package/dist/adapters/openapi/convert.js.map +1 -0
  55. package/dist/adapters/openapi/index.d.ts +39 -0
  56. package/dist/adapters/openapi/index.d.ts.map +1 -0
  57. package/dist/adapters/openapi/index.js +48 -0
  58. package/dist/adapters/openapi/index.js.map +1 -0
  59. package/dist/adapters/openapi/parser.d.ts +95 -0
  60. package/dist/adapters/openapi/parser.d.ts.map +1 -0
  61. package/dist/adapters/openapi/parser.js +171 -0
  62. package/dist/adapters/openapi/parser.js.map +1 -0
  63. package/dist/adapters/registry.d.ts +116 -0
  64. package/dist/adapters/registry.d.ts.map +1 -0
  65. package/dist/adapters/registry.js +246 -0
  66. package/dist/adapters/registry.js.map +1 -0
  67. package/dist/adapters/trpc/adapter.d.ts +159 -0
  68. package/dist/adapters/trpc/adapter.d.ts.map +1 -0
  69. package/dist/adapters/trpc/adapter.js +223 -0
  70. package/dist/adapters/trpc/adapter.js.map +1 -0
  71. package/dist/adapters/trpc/extractor.d.ts +218 -0
  72. package/dist/adapters/trpc/extractor.d.ts.map +1 -0
  73. package/dist/adapters/trpc/extractor.js +708 -0
  74. package/dist/adapters/trpc/extractor.js.map +1 -0
  75. package/dist/adapters/trpc/index.d.ts +31 -0
  76. package/dist/adapters/trpc/index.d.ts.map +1 -0
  77. package/dist/adapters/trpc/index.js +40 -0
  78. package/dist/adapters/trpc/index.js.map +1 -0
  79. package/dist/adapters/trpc/parser.d.ts +119 -0
  80. package/dist/adapters/trpc/parser.d.ts.map +1 -0
  81. package/dist/adapters/trpc/parser.js +128 -0
  82. package/dist/adapters/trpc/parser.js.map +1 -0
  83. package/dist/compare/index.d.ts +33 -0
  84. package/dist/compare/index.d.ts.map +1 -0
  85. package/dist/compare/index.js +261 -0
  86. package/dist/compare/index.js.map +1 -0
  87. package/dist/core/types.d.ts +188 -0
  88. package/dist/core/types.d.ts.map +1 -0
  89. package/dist/core/types.js +9 -0
  90. package/dist/core/types.js.map +1 -0
  91. package/dist/extract/index.d.ts +26 -0
  92. package/dist/extract/index.d.ts.map +1 -0
  93. package/dist/extract/index.js +44 -0
  94. package/dist/extract/index.js.map +1 -0
  95. package/dist/index.d.ts +9 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +674 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/languages/base.d.ts +57 -0
  100. package/dist/languages/base.d.ts.map +1 -0
  101. package/dist/languages/base.js +6 -0
  102. package/dist/languages/base.js.map +1 -0
  103. package/dist/languages/bootstrap.d.ts +10 -0
  104. package/dist/languages/bootstrap.d.ts.map +1 -0
  105. package/dist/languages/bootstrap.js +25 -0
  106. package/dist/languages/bootstrap.js.map +1 -0
  107. package/dist/languages/go/handlers/chi.d.ts +24 -0
  108. package/dist/languages/go/handlers/chi.d.ts.map +1 -0
  109. package/dist/languages/go/handlers/chi.js +205 -0
  110. package/dist/languages/go/handlers/chi.js.map +1 -0
  111. package/dist/languages/go/handlers/gin.d.ts +24 -0
  112. package/dist/languages/go/handlers/gin.d.ts.map +1 -0
  113. package/dist/languages/go/handlers/gin.js +156 -0
  114. package/dist/languages/go/handlers/gin.js.map +1 -0
  115. package/dist/languages/go/handlers/stdlib.d.ts +19 -0
  116. package/dist/languages/go/handlers/stdlib.d.ts.map +1 -0
  117. package/dist/languages/go/handlers/stdlib.js +112 -0
  118. package/dist/languages/go/handlers/stdlib.js.map +1 -0
  119. package/dist/languages/go/index.d.ts +18 -0
  120. package/dist/languages/go/index.d.ts.map +1 -0
  121. package/dist/languages/go/index.js +20 -0
  122. package/dist/languages/go/index.js.map +1 -0
  123. package/dist/languages/go/parser.d.ts +33 -0
  124. package/dist/languages/go/parser.d.ts.map +1 -0
  125. package/dist/languages/go/parser.js +95 -0
  126. package/dist/languages/go/parser.js.map +1 -0
  127. package/dist/languages/go/struct-extractor.d.ts +59 -0
  128. package/dist/languages/go/struct-extractor.d.ts.map +1 -0
  129. package/dist/languages/go/struct-extractor.js +483 -0
  130. package/dist/languages/go/struct-extractor.js.map +1 -0
  131. package/dist/languages/go/tag-parser.d.ts +62 -0
  132. package/dist/languages/go/tag-parser.d.ts.map +1 -0
  133. package/dist/languages/go/tag-parser.js +108 -0
  134. package/dist/languages/go/tag-parser.js.map +1 -0
  135. package/dist/languages/go/type-converter.d.ts +32 -0
  136. package/dist/languages/go/type-converter.d.ts.map +1 -0
  137. package/dist/languages/go/type-converter.js +226 -0
  138. package/dist/languages/go/type-converter.js.map +1 -0
  139. package/dist/languages/go/types.d.ts +153 -0
  140. package/dist/languages/go/types.d.ts.map +1 -0
  141. package/dist/languages/go/types.js +6 -0
  142. package/dist/languages/go/types.js.map +1 -0
  143. package/dist/languages/import-resolver.d.ts +645 -0
  144. package/dist/languages/import-resolver.d.ts.map +1 -0
  145. package/dist/languages/import-resolver.js +1278 -0
  146. package/dist/languages/import-resolver.js.map +1 -0
  147. package/dist/languages/index.d.ts +34 -0
  148. package/dist/languages/index.d.ts.map +1 -0
  149. package/dist/languages/index.js +93 -0
  150. package/dist/languages/index.js.map +1 -0
  151. package/dist/languages/json-schema.d.ts +40 -0
  152. package/dist/languages/json-schema.d.ts.map +1 -0
  153. package/dist/languages/json-schema.js +188 -0
  154. package/dist/languages/json-schema.js.map +1 -0
  155. package/dist/languages/python-ast/index.d.ts +8 -0
  156. package/dist/languages/python-ast/index.d.ts.map +1 -0
  157. package/dist/languages/python-ast/index.js +7 -0
  158. package/dist/languages/python-ast/index.js.map +1 -0
  159. package/dist/languages/python-ast/parser.d.ts +174 -0
  160. package/dist/languages/python-ast/parser.d.ts.map +1 -0
  161. package/dist/languages/python-ast/parser.js +1205 -0
  162. package/dist/languages/python-ast/parser.js.map +1 -0
  163. package/dist/languages/python-ast/type-resolver.d.ts +75 -0
  164. package/dist/languages/python-ast/type-resolver.d.ts.map +1 -0
  165. package/dist/languages/python-ast/type-resolver.js +421 -0
  166. package/dist/languages/python-ast/type-resolver.js.map +1 -0
  167. package/dist/languages/python-ast/types.d.ts +216 -0
  168. package/dist/languages/python-ast/types.d.ts.map +1 -0
  169. package/dist/languages/python-ast/types.js +6 -0
  170. package/dist/languages/python-ast/types.js.map +1 -0
  171. package/dist/languages/python.d.ts +55 -0
  172. package/dist/languages/python.d.ts.map +1 -0
  173. package/dist/languages/python.js +311 -0
  174. package/dist/languages/python.js.map +1 -0
  175. package/dist/languages/typescript.d.ts +272 -0
  176. package/dist/languages/typescript.d.ts.map +1 -0
  177. package/dist/languages/typescript.js +1381 -0
  178. package/dist/languages/typescript.js.map +1 -0
  179. package/dist/patterns/base.d.ts +146 -0
  180. package/dist/patterns/base.d.ts.map +1 -0
  181. package/dist/patterns/base.js +89 -0
  182. package/dist/patterns/base.js.map +1 -0
  183. package/dist/patterns/errors.d.ts +172 -0
  184. package/dist/patterns/errors.d.ts.map +1 -0
  185. package/dist/patterns/errors.js +185 -0
  186. package/dist/patterns/errors.js.map +1 -0
  187. package/dist/patterns/extractors.d.ts +170 -0
  188. package/dist/patterns/extractors.d.ts.map +1 -0
  189. package/dist/patterns/extractors.js +305 -0
  190. package/dist/patterns/extractors.js.map +1 -0
  191. package/dist/patterns/graphql/apollo-client.d.ts +80 -0
  192. package/dist/patterns/graphql/apollo-client.d.ts.map +1 -0
  193. package/dist/patterns/graphql/apollo-client.js +800 -0
  194. package/dist/patterns/graphql/apollo-client.js.map +1 -0
  195. package/dist/patterns/graphql/apollo-server.d.ts +55 -0
  196. package/dist/patterns/graphql/apollo-server.d.ts.map +1 -0
  197. package/dist/patterns/graphql/apollo-server.js +523 -0
  198. package/dist/patterns/graphql/apollo-server.js.map +1 -0
  199. package/dist/patterns/graphql/index.d.ts +11 -0
  200. package/dist/patterns/graphql/index.d.ts.map +1 -0
  201. package/dist/patterns/graphql/index.js +12 -0
  202. package/dist/patterns/graphql/index.js.map +1 -0
  203. package/dist/patterns/graphql/types.d.ts +213 -0
  204. package/dist/patterns/graphql/types.d.ts.map +1 -0
  205. package/dist/patterns/graphql/types.js +16 -0
  206. package/dist/patterns/graphql/types.js.map +1 -0
  207. package/dist/patterns/http-clients/axios.d.ts +148 -0
  208. package/dist/patterns/http-clients/axios.d.ts.map +1 -0
  209. package/dist/patterns/http-clients/axios.js +652 -0
  210. package/dist/patterns/http-clients/axios.js.map +1 -0
  211. package/dist/patterns/http-clients/fetch.d.ts +88 -0
  212. package/dist/patterns/http-clients/fetch.d.ts.map +1 -0
  213. package/dist/patterns/http-clients/fetch.js +364 -0
  214. package/dist/patterns/http-clients/fetch.js.map +1 -0
  215. package/dist/patterns/http-clients/index.d.ts +36 -0
  216. package/dist/patterns/http-clients/index.d.ts.map +1 -0
  217. package/dist/patterns/http-clients/index.js +50 -0
  218. package/dist/patterns/http-clients/index.js.map +1 -0
  219. package/dist/patterns/http-clients/property-access.d.ts +46 -0
  220. package/dist/patterns/http-clients/property-access.d.ts.map +1 -0
  221. package/dist/patterns/http-clients/property-access.js +818 -0
  222. package/dist/patterns/http-clients/property-access.js.map +1 -0
  223. package/dist/patterns/http-clients/type-inference.d.ts +48 -0
  224. package/dist/patterns/http-clients/type-inference.d.ts.map +1 -0
  225. package/dist/patterns/http-clients/type-inference.js +293 -0
  226. package/dist/patterns/http-clients/type-inference.js.map +1 -0
  227. package/dist/patterns/http-clients/types.d.ts +168 -0
  228. package/dist/patterns/http-clients/types.d.ts.map +1 -0
  229. package/dist/patterns/http-clients/types.js +10 -0
  230. package/dist/patterns/http-clients/types.js.map +1 -0
  231. package/dist/patterns/http-clients/url-extractor.d.ts +53 -0
  232. package/dist/patterns/http-clients/url-extractor.d.ts.map +1 -0
  233. package/dist/patterns/http-clients/url-extractor.js +338 -0
  234. package/dist/patterns/http-clients/url-extractor.js.map +1 -0
  235. package/dist/patterns/index.d.ts +44 -0
  236. package/dist/patterns/index.d.ts.map +1 -0
  237. package/dist/patterns/index.js +49 -0
  238. package/dist/patterns/index.js.map +1 -0
  239. package/dist/patterns/python/aiohttp.d.ts +21 -0
  240. package/dist/patterns/python/aiohttp.d.ts.map +1 -0
  241. package/dist/patterns/python/aiohttp.js +188 -0
  242. package/dist/patterns/python/aiohttp.js.map +1 -0
  243. package/dist/patterns/python/httpx.d.ts +20 -0
  244. package/dist/patterns/python/httpx.d.ts.map +1 -0
  245. package/dist/patterns/python/httpx.js +183 -0
  246. package/dist/patterns/python/httpx.js.map +1 -0
  247. package/dist/patterns/python/index.d.ts +32 -0
  248. package/dist/patterns/python/index.d.ts.map +1 -0
  249. package/dist/patterns/python/index.js +63 -0
  250. package/dist/patterns/python/index.js.map +1 -0
  251. package/dist/patterns/python/property-access.d.ts +27 -0
  252. package/dist/patterns/python/property-access.d.ts.map +1 -0
  253. package/dist/patterns/python/property-access.js +132 -0
  254. package/dist/patterns/python/property-access.js.map +1 -0
  255. package/dist/patterns/python/requests.d.ts +19 -0
  256. package/dist/patterns/python/requests.d.ts.map +1 -0
  257. package/dist/patterns/python/requests.js +239 -0
  258. package/dist/patterns/python/requests.js.map +1 -0
  259. package/dist/patterns/python/types.d.ts +95 -0
  260. package/dist/patterns/python/types.d.ts.map +1 -0
  261. package/dist/patterns/python/types.js +43 -0
  262. package/dist/patterns/python/types.js.map +1 -0
  263. package/dist/patterns/registry.d.ts +181 -0
  264. package/dist/patterns/registry.d.ts.map +1 -0
  265. package/dist/patterns/registry.js +304 -0
  266. package/dist/patterns/registry.js.map +1 -0
  267. package/dist/patterns/rest/express.d.ts +78 -0
  268. package/dist/patterns/rest/express.d.ts.map +1 -0
  269. package/dist/patterns/rest/express.js +289 -0
  270. package/dist/patterns/rest/express.js.map +1 -0
  271. package/dist/patterns/rest/fastify.d.ts +93 -0
  272. package/dist/patterns/rest/fastify.d.ts.map +1 -0
  273. package/dist/patterns/rest/fastify.js +420 -0
  274. package/dist/patterns/rest/fastify.js.map +1 -0
  275. package/dist/patterns/rest/index.d.ts +31 -0
  276. package/dist/patterns/rest/index.d.ts.map +1 -0
  277. package/dist/patterns/rest/index.js +45 -0
  278. package/dist/patterns/rest/index.js.map +1 -0
  279. package/dist/patterns/rest/middleware.d.ts +25 -0
  280. package/dist/patterns/rest/middleware.d.ts.map +1 -0
  281. package/dist/patterns/rest/middleware.js +219 -0
  282. package/dist/patterns/rest/middleware.js.map +1 -0
  283. package/dist/patterns/rest/path-parser.d.ts +50 -0
  284. package/dist/patterns/rest/path-parser.d.ts.map +1 -0
  285. package/dist/patterns/rest/path-parser.js +137 -0
  286. package/dist/patterns/rest/path-parser.js.map +1 -0
  287. package/dist/patterns/rest/response-inference.d.ts +44 -0
  288. package/dist/patterns/rest/response-inference.d.ts.map +1 -0
  289. package/dist/patterns/rest/response-inference.js +218 -0
  290. package/dist/patterns/rest/response-inference.js.map +1 -0
  291. package/dist/patterns/rest/types.d.ts +102 -0
  292. package/dist/patterns/rest/types.d.ts.map +1 -0
  293. package/dist/patterns/rest/types.js +10 -0
  294. package/dist/patterns/rest/types.js.map +1 -0
  295. package/dist/patterns/types.d.ts +105 -0
  296. package/dist/patterns/types.d.ts.map +1 -0
  297. package/dist/patterns/types.js +11 -0
  298. package/dist/patterns/types.js.map +1 -0
  299. package/dist/report/index.d.ts +11 -0
  300. package/dist/report/index.d.ts.map +1 -0
  301. package/dist/report/index.js +55 -0
  302. package/dist/report/index.js.map +1 -0
  303. package/dist/tools/contract-comments.d.ts +48 -0
  304. package/dist/tools/contract-comments.d.ts.map +1 -0
  305. package/dist/tools/contract-comments.js +130 -0
  306. package/dist/tools/contract-comments.js.map +1 -0
  307. package/dist/tools/index.d.ts +6 -0
  308. package/dist/tools/index.d.ts.map +1 -0
  309. package/dist/tools/index.js +6 -0
  310. package/dist/tools/index.js.map +1 -0
  311. package/dist/tools/scaffold.d.ts +38 -0
  312. package/dist/tools/scaffold.d.ts.map +1 -0
  313. package/dist/tools/scaffold.js +373 -0
  314. package/dist/tools/scaffold.js.map +1 -0
  315. package/dist/trace/index.d.ts +28 -0
  316. package/dist/trace/index.d.ts.map +1 -0
  317. package/dist/trace/index.js +45 -0
  318. package/dist/trace/index.js.map +1 -0
  319. package/dist/types.d.ts +135 -0
  320. package/dist/types.d.ts.map +1 -0
  321. package/dist/types.js +22 -0
  322. package/dist/types.js.map +1 -0
  323. package/dist/watch/cache.d.ts +41 -0
  324. package/dist/watch/cache.d.ts.map +1 -0
  325. package/dist/watch/cache.js +230 -0
  326. package/dist/watch/cache.js.map +1 -0
  327. package/dist/watch/index.d.ts +9 -0
  328. package/dist/watch/index.d.ts.map +1 -0
  329. package/dist/watch/index.js +7 -0
  330. package/dist/watch/index.js.map +1 -0
  331. package/dist/watch/project.d.ts +128 -0
  332. package/dist/watch/project.d.ts.map +1 -0
  333. package/dist/watch/project.js +152 -0
  334. package/dist/watch/project.js.map +1 -0
  335. package/dist/watch/watcher.d.ts +76 -0
  336. package/dist/watch/watcher.d.ts.map +1 -0
  337. package/dist/watch/watcher.js +235 -0
  338. package/dist/watch/watcher.js.map +1 -0
  339. package/package.json +70 -0
@@ -0,0 +1,1278 @@
1
+ /**
2
+ * 🔗 Cross-File Import Resolution for TypeScript Projects
3
+ *
4
+ * This module provides comprehensive import resolution capabilities for TypeScript
5
+ * projects, including:
6
+ * - Relative import resolution (./foo, ../bar)
7
+ * - tsconfig.json path alias resolution (@/utils, models)
8
+ * - Re-export chain following (export * from, export { X } from)
9
+ * - Circular reference detection
10
+ * - Import graph building for visualization
11
+ *
12
+ * @module import-resolver
13
+ * @see ADR Reference: .context/ADR-P2-5-IMPORT-RESOLUTION.md
14
+ *
15
+ * @example Basic Usage
16
+ * ```typescript
17
+ * import { ImportResolverImpl } from './import-resolver.js';
18
+ *
19
+ * const resolver = new ImportResolverImpl({
20
+ * tsConfigPath: './tsconfig.json',
21
+ * maxReexportDepth: 10,
22
+ * });
23
+ *
24
+ * // Resolve an import
25
+ * const result = resolver.resolve('./types', '/project/src/index.ts');
26
+ *
27
+ * // Get all exported types from a file
28
+ * const types = resolver.getExportedTypes('/project/src/types/index.ts');
29
+ *
30
+ * // Resolve a specific type reference
31
+ * const typeRef = resolver.resolveTypeRef('User', '/project/src/handlers/user.ts');
32
+ * ```
33
+ */
34
+ import { Project, Node, ts } from 'ts-morph';
35
+ import * as path from 'path';
36
+ import * as fs from 'fs';
37
+ // ═══════════════════════════════════════════════════════════════════════════
38
+ // 📁 Path Utilities
39
+ // ═══════════════════════════════════════════════════════════════════════════
40
+ /**
41
+ * Checks if a file path is a Windows-style path.
42
+ *
43
+ * Detects Windows paths by looking for drive letter patterns like `C:\` or `C:/`.
44
+ *
45
+ * @param filePath - The file path to check
46
+ * @returns `true` if the path contains a Windows drive letter pattern
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * isWindowsPath('C:\\Users\\project\\src'); // true
51
+ * isWindowsPath('/home/user/project'); // false
52
+ * ```
53
+ */
54
+ function isWindowsPath(filePath) {
55
+ // Check for Windows drive letter pattern anywhere in the path (e.g., C:, f:, F:\, f:/)
56
+ // This handles both "C:\..." and "C:/..." formats
57
+ return /[a-zA-Z]:[\\/]/.test(filePath) || /^[a-zA-Z]:/.test(filePath);
58
+ }
59
+ /**
60
+ * Normalizes path separators to use OS-native separators.
61
+ *
62
+ * On Windows, converts forward slashes to backslashes to ensure consistent
63
+ * path handling across the codebase.
64
+ *
65
+ * @param filePath - The file path to normalize
66
+ * @returns The path with OS-native separators
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * // On Windows:
71
+ * normalizePath('C:/Users/project/src'); // 'C:\\Users\\project\\src'
72
+ * ```
73
+ */
74
+ function normalizePath(filePath) {
75
+ // On Windows, always convert forward slashes to backslashes
76
+ if (process.platform === 'win32' || isWindowsPath(filePath)) {
77
+ // Use regex to replace ALL forward slashes in one operation
78
+ return filePath.replace(/\//g, '\\');
79
+ }
80
+ return filePath;
81
+ }
82
+ /**
83
+ * Forces path normalization for output - the final step before returning to caller.
84
+ *
85
+ * This is the last line of defense against mixed slashes in returned paths.
86
+ *
87
+ * @param filePath - The file path to normalize
88
+ * @returns The path with OS-native separators
89
+ */
90
+ function normalizeOutputPath(filePath) {
91
+ if (process.platform === 'win32') {
92
+ return filePath.replace(/\//g, '\\');
93
+ }
94
+ return filePath;
95
+ }
96
+ /**
97
+ * Joins path segments with proper separator based on the first segment.
98
+ *
99
+ * On Windows, ensures the result uses backslashes regardless of input format.
100
+ *
101
+ * @param segments - Path segments to join
102
+ * @returns The joined path with OS-native separators
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * // On Windows:
107
+ * joinPath('C:\\project', 'src', 'index.ts'); // 'C:\\project\\src\\index.ts'
108
+ * ```
109
+ */
110
+ function joinPath(...segments) {
111
+ // Use the first non-empty segment to determine if this is a Windows path
112
+ const firstSegment = segments.find(s => s.length > 0) || '';
113
+ // On Windows (or if path looks like Windows), use backslash
114
+ if (isWindowsPath(firstSegment) || process.platform === 'win32') {
115
+ // Join manually with backslash to avoid path.join using wrong separator
116
+ const normalizedSegments = segments.map(s => s.replace(/\//g, '\\').replace(/\\+$/, ''));
117
+ const result = normalizedSegments.join('\\').replace(/\\+/g, '\\');
118
+ return result;
119
+ }
120
+ // Use path.join for non-Windows
121
+ return path.join(...segments);
122
+ }
123
+ /**
124
+ * LRU (Least Recently Used) Cache for parsed TypeScript files.
125
+ *
126
+ * Provides efficient caching with automatic eviction of least-recently-used
127
+ * entries when the cache reaches capacity.
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * const cache = new FileCache(100);
132
+ * cache.set('/path/to/file.ts', { sourceFile, mtime: Date.now(), cachedAt: Date.now() });
133
+ * const entry = cache.get('/path/to/file.ts');
134
+ * ```
135
+ */
136
+ export class FileCache {
137
+ cache;
138
+ accessOrder;
139
+ maxSize;
140
+ hits = 0;
141
+ misses = 0;
142
+ /**
143
+ * Creates a new FileCache instance.
144
+ *
145
+ * @param maxSize - Maximum number of entries before LRU eviction (default: 500)
146
+ */
147
+ constructor(maxSize = 500) {
148
+ this.cache = new Map();
149
+ this.accessOrder = [];
150
+ this.maxSize = maxSize;
151
+ }
152
+ /**
153
+ * Gets a cached entry, updating its access order.
154
+ *
155
+ * @param filePath - Absolute path to the file
156
+ * @returns Cached entry, or undefined if not cached
157
+ */
158
+ get(filePath) {
159
+ const entry = this.cache.get(filePath);
160
+ if (entry) {
161
+ this.hits++;
162
+ // Move to end of access order (most recently used)
163
+ const idx = this.accessOrder.indexOf(filePath);
164
+ if (idx !== -1) {
165
+ this.accessOrder.splice(idx, 1);
166
+ }
167
+ this.accessOrder.push(filePath);
168
+ return entry;
169
+ }
170
+ this.misses++;
171
+ return undefined;
172
+ }
173
+ /**
174
+ * Sets a cache entry, evicting LRU entries if at capacity.
175
+ *
176
+ * @param filePath - Absolute path to the file
177
+ * @param entry - Cache entry to store
178
+ */
179
+ set(filePath, entry) {
180
+ // If already exists, update and move to end
181
+ if (this.cache.has(filePath)) {
182
+ this.cache.set(filePath, entry);
183
+ const idx = this.accessOrder.indexOf(filePath);
184
+ if (idx !== -1) {
185
+ this.accessOrder.splice(idx, 1);
186
+ }
187
+ this.accessOrder.push(filePath);
188
+ return;
189
+ }
190
+ // Evict LRU if at capacity
191
+ while (this.cache.size >= this.maxSize && this.accessOrder.length > 0) {
192
+ const lruKey = this.accessOrder.shift();
193
+ this.cache.delete(lruKey);
194
+ }
195
+ // Add new entry
196
+ this.cache.set(filePath, entry);
197
+ this.accessOrder.push(filePath);
198
+ }
199
+ /**
200
+ * Invalidates a cache entry for a modified file.
201
+ *
202
+ * @param filePath - Absolute path to the file to invalidate
203
+ */
204
+ invalidate(filePath) {
205
+ this.cache.delete(filePath);
206
+ const idx = this.accessOrder.indexOf(filePath);
207
+ if (idx !== -1) {
208
+ this.accessOrder.splice(idx, 1);
209
+ }
210
+ }
211
+ /**
212
+ * Clears all cached data and resets statistics.
213
+ */
214
+ clear() {
215
+ this.cache.clear();
216
+ this.accessOrder = [];
217
+ this.hits = 0;
218
+ this.misses = 0;
219
+ }
220
+ /**
221
+ * Gets cache performance statistics.
222
+ *
223
+ * @returns Cache statistics including hit rate
224
+ */
225
+ getStats() {
226
+ const total = this.hits + this.misses;
227
+ return {
228
+ size: this.cache.size,
229
+ maxSize: this.maxSize,
230
+ hits: this.hits,
231
+ misses: this.misses,
232
+ hitRate: total === 0 ? 0 : this.hits / total,
233
+ };
234
+ }
235
+ }
236
+ /**
237
+ * Parses tsconfig.json to extract path mapping configuration.
238
+ *
239
+ * @param tsConfigPath - Absolute path to tsconfig.json
240
+ * @returns Parsed path configuration, or null if parsing fails
241
+ * @internal
242
+ */
243
+ function parseTsConfig(tsConfigPath) {
244
+ try {
245
+ if (!fs.existsSync(tsConfigPath)) {
246
+ return null;
247
+ }
248
+ const content = fs.readFileSync(tsConfigPath, 'utf-8');
249
+ const config = JSON.parse(content);
250
+ const compilerOptions = config.compilerOptions || {};
251
+ return {
252
+ baseUrl: compilerOptions.baseUrl || '.',
253
+ paths: compilerOptions.paths || {},
254
+ };
255
+ }
256
+ catch {
257
+ return null;
258
+ }
259
+ }
260
+ // ═══════════════════════════════════════════════════════════════════════════
261
+ // 🔧 Import Resolution Engine Implementation
262
+ // ═══════════════════════════════════════════════════════════════════════════
263
+ /**
264
+ * Implementation of the ImportResolver interface.
265
+ *
266
+ * Provides comprehensive import resolution for TypeScript projects including:
267
+ * - Relative import resolution with extension probing
268
+ * - tsconfig.json path alias resolution
269
+ * - Re-export chain following
270
+ * - Circular reference detection
271
+ * - LRU caching for performance
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * const resolver = new ImportResolverImpl({
276
+ * tsConfigPath: './tsconfig.json',
277
+ * maxReexportDepth: 15,
278
+ * maxCacheSize: 1000,
279
+ * });
280
+ *
281
+ * // Resolve an import
282
+ * const result = resolver.resolve('./types', '/project/src/index.ts');
283
+ * if (result) {
284
+ * console.log(`Resolved to: ${result.filePath}`);
285
+ * }
286
+ *
287
+ * // Get all exported types
288
+ * const types = resolver.getExportedTypes('/project/src/types/index.ts');
289
+ * for (const [name, type] of types) {
290
+ * console.log(`Export: ${name} (${type.kind})`);
291
+ * }
292
+ * ```
293
+ */
294
+ export class ImportResolverImpl {
295
+ config;
296
+ cache;
297
+ project;
298
+ tsConfigPaths = null;
299
+ tsConfigDir = '';
300
+ /**
301
+ * Creates a new ImportResolverImpl instance.
302
+ *
303
+ * @param config - Configuration options for the resolver
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * const resolver = new ImportResolverImpl({
308
+ * tsConfigPath: './tsconfig.json',
309
+ * maxCacheSize: 1000,
310
+ * });
311
+ * ```
312
+ */
313
+ constructor(config = {}) {
314
+ this.config = {
315
+ maxReexportDepth: config.maxReexportDepth ?? 10,
316
+ maxCacheSize: config.maxCacheSize ?? 500,
317
+ includeNodeModules: config.includeNodeModules ?? false,
318
+ tsConfigPath: config.tsConfigPath,
319
+ pathMappings: config.pathMappings,
320
+ baseDir: config.baseDir,
321
+ };
322
+ this.cache = new FileCache(this.config.maxCacheSize);
323
+ // Initialize ts-morph project
324
+ this.project = new Project({
325
+ skipAddingFilesFromTsConfig: true,
326
+ compilerOptions: {
327
+ strict: true,
328
+ target: ts.ScriptTarget.ESNext,
329
+ module: ts.ModuleKind.ESNext,
330
+ },
331
+ });
332
+ // Parse tsconfig if provided
333
+ if (this.config.tsConfigPath) {
334
+ this.tsConfigPaths = parseTsConfig(this.config.tsConfigPath);
335
+ this.tsConfigDir = path.dirname(this.config.tsConfigPath);
336
+ }
337
+ }
338
+ // ─────────────────────────────────────────────────────────────────────────
339
+ // Public API Methods
340
+ // ─────────────────────────────────────────────────────────────────────────
341
+ /**
342
+ * Resolves an import specifier from a source file.
343
+ *
344
+ * Handles relative imports, path aliases, and baseUrl resolution.
345
+ * Returns null for node_modules imports unless `includeNodeModules` is true.
346
+ *
347
+ * @param importPath - The import specifier (e.g., './types', '@/utils')
348
+ * @param fromFile - Absolute path to the file containing the import
349
+ * @returns Resolved import info, or null if unresolvable
350
+ */
351
+ resolve(importPath, fromFile) {
352
+ // Validate fromFile exists
353
+ if (!fs.existsSync(fromFile)) {
354
+ return null;
355
+ }
356
+ // Skip node_modules imports
357
+ if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
358
+ // Check if it's a path alias
359
+ const aliasResolved = this.resolvePathAlias(importPath, fromFile);
360
+ if (aliasResolved) {
361
+ return this.createResolvedImport(aliasResolved, importPath);
362
+ }
363
+ // Check baseUrl resolution
364
+ const baseUrlResolved = this.resolveFromBaseUrl(importPath, fromFile);
365
+ if (baseUrlResolved) {
366
+ return this.createResolvedImport(baseUrlResolved, importPath);
367
+ }
368
+ // Skip HTTP/URL imports
369
+ if (importPath.startsWith('http://') || importPath.startsWith('https://')) {
370
+ return null;
371
+ }
372
+ // Skip node_modules (third-party packages)
373
+ return null;
374
+ }
375
+ // Resolve relative import
376
+ const resolvedPath = this.resolveRelativeImport(importPath, fromFile);
377
+ if (!resolvedPath) {
378
+ return null;
379
+ }
380
+ return this.createResolvedImport(resolvedPath, importPath);
381
+ }
382
+ /**
383
+ * Gets all exported types from a file.
384
+ *
385
+ * Collects direct exports (interfaces, type aliases, enums) and follows
386
+ * re-export chains to gather all publicly available types.
387
+ *
388
+ * @param filePath - Absolute path to the file
389
+ * @returns Map of export name to NormalizedType
390
+ */
391
+ getExportedTypes(filePath) {
392
+ const result = new Map();
393
+ // Check if file exists
394
+ if (!fs.existsSync(filePath)) {
395
+ return result;
396
+ }
397
+ const sourceFile = this.getOrParseFile(filePath);
398
+ if (!sourceFile) {
399
+ return result;
400
+ }
401
+ // Collect direct exports
402
+ this.collectDirectExports(sourceFile, result);
403
+ // Collect re-exports
404
+ this.collectReExports(sourceFile, result, new Set([filePath]), 0);
405
+ return result;
406
+ }
407
+ /**
408
+ * Resolves a type reference following imports and re-exports.
409
+ *
410
+ * Starting from a source file, traces the import chain to find where
411
+ * a type is actually defined, handling re-exports and aliases.
412
+ *
413
+ * @param typeName - Name of the type to resolve
414
+ * @param fromFile - Absolute path to the file using the type
415
+ * @returns Resolved type reference, or null if not found
416
+ */
417
+ resolveTypeRef(typeName, fromFile) {
418
+ // Check if file exists
419
+ if (!fs.existsSync(fromFile)) {
420
+ return null;
421
+ }
422
+ const sourceFile = this.getOrParseFile(fromFile);
423
+ if (!sourceFile) {
424
+ return null;
425
+ }
426
+ // Track visited files for circular detection
427
+ const visited = new Set();
428
+ const reexportChain = [];
429
+ return this.resolveTypeRefInternal(typeName, sourceFile, visited, reexportChain, 0);
430
+ }
431
+ /**
432
+ * Gets the import graph for a file.
433
+ *
434
+ * Builds a tree structure showing all import dependencies, useful for
435
+ * visualization and dependency analysis.
436
+ *
437
+ * @param filePath - Absolute path to the root file
438
+ * @param depth - How deep to traverse (default: 1)
439
+ * @returns Import graph node with children
440
+ */
441
+ getImportGraph(filePath, depth = 1) {
442
+ // Force backslash normalization on input
443
+ let normalizedFilePath = filePath;
444
+ if (process.platform === 'win32' || isWindowsPath(filePath)) {
445
+ normalizedFilePath = filePath.replace(/\//g, '\\');
446
+ }
447
+ const sourceFile = this.getOrParseFile(normalizedFilePath);
448
+ if (!sourceFile) {
449
+ return {
450
+ filePath: normalizedFilePath,
451
+ imports: [],
452
+ exports: [],
453
+ };
454
+ }
455
+ return this.buildImportGraph(sourceFile, depth, new Set());
456
+ }
457
+ /**
458
+ * Clears all cached data.
459
+ *
460
+ * Use after making changes to source files to ensure fresh parsing.
461
+ */
462
+ clearCache() {
463
+ this.cache.clear();
464
+ }
465
+ /**
466
+ * Gets cache performance statistics.
467
+ *
468
+ * @returns Current cache statistics
469
+ */
470
+ getCacheStats() {
471
+ return this.cache.getStats();
472
+ }
473
+ /**
474
+ * Warms the cache by pre-loading files matching patterns.
475
+ *
476
+ * @param patterns - Glob patterns for files to pre-load
477
+ */
478
+ async warmCache(patterns) {
479
+ // For each pattern, add files to the project
480
+ for (const pattern of patterns) {
481
+ this.project.addSourceFilesAtPaths(pattern);
482
+ }
483
+ // Parse and cache each file
484
+ for (const sourceFile of this.project.getSourceFiles()) {
485
+ const filePath = normalizePath(sourceFile.getFilePath());
486
+ try {
487
+ const stats = fs.statSync(filePath);
488
+ this.cache.set(filePath, {
489
+ sourceFile,
490
+ mtime: stats.mtimeMs,
491
+ cachedAt: Date.now(),
492
+ });
493
+ }
494
+ catch {
495
+ // Ignore files that can't be accessed
496
+ }
497
+ }
498
+ }
499
+ // ─────────────────────────────────────────────────────────────────────────
500
+ // Import Resolution Helpers
501
+ // ─────────────────────────────────────────────────────────────────────────
502
+ /**
503
+ * Resolves a relative import path (./foo or ../bar).
504
+ *
505
+ * @param importPath - The relative import specifier
506
+ * @param fromFile - Absolute path to the file containing the import
507
+ * @returns Resolved absolute path, or null if not found
508
+ * @internal
509
+ */
510
+ resolveRelativeImport(importPath, fromFile) {
511
+ // Helper to force Windows path separators
512
+ const forceBackslash = (p) => {
513
+ if (process.platform === 'win32' || isWindowsPath(p)) {
514
+ return p.split('/').join('\\');
515
+ }
516
+ return p;
517
+ };
518
+ // Normalize fromFile first (might have forward slashes from ts-morph)
519
+ const normalizedFromFile = forceBackslash(fromFile);
520
+ const fromDir = forceBackslash(path.dirname(normalizedFromFile));
521
+ // Normalize importPath too - convert forward slashes to backslashes on Windows
522
+ const normalizedImportPath = forceBackslash(importPath);
523
+ const targetBase = forceBackslash(path.resolve(fromDir, normalizedImportPath));
524
+ // Probe extensions in order
525
+ const extensions = ['.ts', '.tsx'];
526
+ // First check if the target already has an extension
527
+ if (fs.existsSync(targetBase) && fs.statSync(targetBase).isFile()) {
528
+ return forceBackslash(targetBase);
529
+ }
530
+ // Probe extensions
531
+ for (const ext of extensions) {
532
+ const candidate = forceBackslash(targetBase + ext);
533
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
534
+ return forceBackslash(candidate);
535
+ }
536
+ }
537
+ // Check if it's a directory with index file
538
+ if (fs.existsSync(targetBase) && fs.statSync(targetBase).isDirectory()) {
539
+ for (const indexName of ['index.ts', 'index.tsx']) {
540
+ // Build index path with backslash on Windows
541
+ const indexPath = targetBase + '\\' + indexName;
542
+ if (fs.existsSync(indexPath)) {
543
+ return indexPath;
544
+ }
545
+ // Try forward slash too (for cross-platform compatibility)
546
+ const indexPathFwd = targetBase + '/' + indexName;
547
+ if (fs.existsSync(indexPathFwd)) {
548
+ return forceBackslash(indexPathFwd);
549
+ }
550
+ }
551
+ }
552
+ // Also check for index files when the target doesn't exist as a directory
553
+ for (const indexName of ['index.ts', 'index.tsx']) {
554
+ // Build index path with backslash on Windows
555
+ const indexPath = targetBase + '\\' + indexName;
556
+ if (fs.existsSync(indexPath)) {
557
+ return indexPath;
558
+ }
559
+ // Try forward slash too (for cross-platform compatibility)
560
+ const indexPathFwd = targetBase + '/' + indexName;
561
+ if (fs.existsSync(indexPathFwd)) {
562
+ return forceBackslash(indexPathFwd);
563
+ }
564
+ }
565
+ return null;
566
+ }
567
+ /**
568
+ * Resolves a path alias from tsconfig.json paths configuration.
569
+ *
570
+ * @param importPath - The import specifier (e.g., '@/utils', 'models')
571
+ * @param fromFile - Absolute path to the file containing the import
572
+ * @returns Resolved absolute path, or null if not a matching alias
573
+ * @internal
574
+ */
575
+ resolvePathAlias(importPath, fromFile) {
576
+ if (!this.tsConfigPaths) {
577
+ return null;
578
+ }
579
+ const { paths } = this.tsConfigPaths;
580
+ for (const [pattern, targets] of Object.entries(paths)) {
581
+ // Handle exact match (e.g., "models" -> ["aliased/models"])
582
+ if (pattern === importPath) {
583
+ for (const target of targets) {
584
+ const resolvedTarget = path.resolve(this.tsConfigDir, this.tsConfigPaths.baseUrl, target);
585
+ const resolved = this.probeFile(resolvedTarget);
586
+ if (resolved) {
587
+ return resolved;
588
+ }
589
+ }
590
+ }
591
+ // Handle wildcard patterns (e.g., "@/*" -> ["./*"])
592
+ if (pattern.endsWith('*')) {
593
+ const prefix = pattern.slice(0, -1); // Remove trailing *
594
+ if (importPath.startsWith(prefix)) {
595
+ const suffix = importPath.slice(prefix.length);
596
+ for (const target of targets) {
597
+ const targetBase = target.slice(0, -1); // Remove trailing * from target
598
+ const resolvedTarget = path.resolve(this.tsConfigDir, this.tsConfigPaths.baseUrl, targetBase + suffix);
599
+ const resolved = this.probeFile(resolvedTarget);
600
+ if (resolved) {
601
+ return resolved;
602
+ }
603
+ }
604
+ }
605
+ }
606
+ }
607
+ return null;
608
+ }
609
+ /**
610
+ * Resolves an import using baseUrl from tsconfig.json.
611
+ *
612
+ * @param importPath - The import specifier
613
+ * @param fromFile - Absolute path to the file containing the import
614
+ * @returns Resolved absolute path, or null if not found
615
+ * @internal
616
+ */
617
+ resolveFromBaseUrl(importPath, fromFile) {
618
+ if (!this.tsConfigPaths) {
619
+ return null;
620
+ }
621
+ const targetBase = path.resolve(this.tsConfigDir, this.tsConfigPaths.baseUrl, importPath);
622
+ return this.probeFile(targetBase);
623
+ }
624
+ /**
625
+ * Probes for a file with various extensions and index files.
626
+ *
627
+ * @param targetBase - Base path to probe
628
+ * @returns Resolved path if found, or null
629
+ * @internal
630
+ */
631
+ probeFile(targetBase) {
632
+ // Check direct file and with extensions
633
+ const extensions = ['', '.ts', '.tsx'];
634
+ for (const ext of extensions) {
635
+ const candidate = normalizePath(targetBase + ext);
636
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
637
+ return candidate;
638
+ }
639
+ }
640
+ // Check for index files in directory
641
+ for (const indexName of ['index.ts', 'index.tsx']) {
642
+ const indexPath = joinPath(targetBase, indexName);
643
+ if (fs.existsSync(indexPath) && fs.statSync(indexPath).isFile()) {
644
+ return indexPath;
645
+ }
646
+ }
647
+ return null;
648
+ }
649
+ /**
650
+ * Creates a ResolvedImport object from a resolved file path.
651
+ *
652
+ * @param filePath - Resolved absolute file path
653
+ * @param originalSpecifier - Original import specifier
654
+ * @returns ResolvedImport object
655
+ * @internal
656
+ */
657
+ createResolvedImport(filePath, originalSpecifier) {
658
+ // Force normalization - use split/join as ultimate fallback
659
+ let normalizedPath = filePath;
660
+ if (process.platform === 'win32' || isWindowsPath(filePath)) {
661
+ normalizedPath = filePath.split('/').join('\\');
662
+ }
663
+ const sourceFile = this.getOrParseFile(normalizedPath);
664
+ return {
665
+ filePath: normalizedPath,
666
+ exportName: '*', // Will be refined when resolving specific exports
667
+ isDefault: false,
668
+ isNamespace: false,
669
+ isTypeOnly: false,
670
+ sourceFile: sourceFile || undefined,
671
+ originalSpecifier,
672
+ };
673
+ }
674
+ // ─────────────────────────────────────────────────────────────────────────
675
+ // File Parsing and Caching
676
+ // ─────────────────────────────────────────────────────────────────────────
677
+ /**
678
+ * Gets or parses a source file, using cache when available.
679
+ *
680
+ * @param filePath - Absolute path to the file
681
+ * @returns Parsed SourceFile, or null if parsing fails
682
+ * @internal
683
+ */
684
+ getOrParseFile(filePath) {
685
+ // Check cache
686
+ const cached = this.cache.get(filePath);
687
+ if (cached) {
688
+ return cached.sourceFile;
689
+ }
690
+ // Check if file exists
691
+ if (!fs.existsSync(filePath)) {
692
+ return null;
693
+ }
694
+ try {
695
+ // Get mtime for cache invalidation
696
+ const stats = fs.statSync(filePath);
697
+ const mtime = stats.mtimeMs;
698
+ // Try to get from project first
699
+ let sourceFile = this.project.getSourceFile(filePath);
700
+ if (!sourceFile) {
701
+ sourceFile = this.project.addSourceFileAtPath(filePath);
702
+ }
703
+ // Cache the parsed file
704
+ this.cache.set(filePath, {
705
+ sourceFile,
706
+ mtime,
707
+ cachedAt: Date.now(),
708
+ });
709
+ return sourceFile;
710
+ }
711
+ catch {
712
+ return null;
713
+ }
714
+ }
715
+ // ─────────────────────────────────────────────────────────────────────────
716
+ // Export Collection
717
+ // ─────────────────────────────────────────────────────────────────────────
718
+ /**
719
+ * Collects directly exported types from a source file.
720
+ *
721
+ * @param sourceFile - The source file to analyze
722
+ * @param result - Map to populate with exported types
723
+ * @internal
724
+ */
725
+ collectDirectExports(sourceFile, result) {
726
+ // Get interfaces
727
+ for (const iface of sourceFile.getInterfaces()) {
728
+ if (iface.isExported()) {
729
+ const name = iface.getName();
730
+ const type = this.convertInterfaceToNormalizedType(iface);
731
+ result.set(name, type);
732
+ }
733
+ }
734
+ // Get type aliases
735
+ for (const typeAlias of sourceFile.getTypeAliases()) {
736
+ if (typeAlias.isExported()) {
737
+ const name = typeAlias.getName();
738
+ const type = this.convertTypeAliasToNormalizedType(typeAlias);
739
+ result.set(name, type);
740
+ }
741
+ }
742
+ // Get enums
743
+ for (const enumDecl of sourceFile.getEnums()) {
744
+ if (enumDecl.isExported()) {
745
+ const name = enumDecl.getName();
746
+ result.set(name, { kind: 'ref', name });
747
+ }
748
+ }
749
+ // Check for default export
750
+ const defaultExport = sourceFile.getDefaultExportSymbol();
751
+ if (defaultExport) {
752
+ result.set('default', { kind: 'ref', name: 'default' });
753
+ }
754
+ }
755
+ /**
756
+ * Collects re-exported types from a source file.
757
+ *
758
+ * Follows `export * from '...'` and `export { X } from '...'` declarations.
759
+ *
760
+ * @param sourceFile - The source file to analyze
761
+ * @param result - Map to populate with re-exported types
762
+ * @param visited - Set of visited file paths for cycle detection
763
+ * @param depth - Current recursion depth
764
+ * @internal
765
+ */
766
+ collectReExports(sourceFile, result, visited, depth) {
767
+ if (depth >= this.config.maxReexportDepth) {
768
+ return;
769
+ }
770
+ const filePath = normalizePath(sourceFile.getFilePath());
771
+ // Find export declarations
772
+ for (const exportDecl of sourceFile.getExportDeclarations()) {
773
+ const moduleSpecifier = exportDecl.getModuleSpecifierValue();
774
+ if (!moduleSpecifier)
775
+ continue;
776
+ // Resolve the module
777
+ const resolvedPath = this.resolveRelativeImport(moduleSpecifier, filePath) ||
778
+ this.resolvePathAlias(moduleSpecifier, filePath);
779
+ if (!resolvedPath || visited.has(resolvedPath))
780
+ continue;
781
+ visited.add(resolvedPath);
782
+ const targetFile = this.getOrParseFile(resolvedPath);
783
+ if (!targetFile)
784
+ continue;
785
+ const namedExports = exportDecl.getNamedExports();
786
+ if (namedExports.length === 0) {
787
+ // export * from '...'
788
+ this.collectDirectExports(targetFile, result);
789
+ this.collectReExports(targetFile, result, visited, depth + 1);
790
+ }
791
+ else {
792
+ // export { X, Y as Z } from '...'
793
+ const targetTypes = new Map();
794
+ this.collectDirectExports(targetFile, targetTypes);
795
+ for (const namedExport of namedExports) {
796
+ const importName = namedExport.getName();
797
+ const exportName = namedExport.getAliasNode()?.getText() || importName;
798
+ const type = targetTypes.get(importName);
799
+ if (type) {
800
+ result.set(exportName, type);
801
+ }
802
+ }
803
+ }
804
+ }
805
+ // Check for default re-export: export { X as default } from '...'
806
+ for (const exportDecl of sourceFile.getExportDeclarations()) {
807
+ const moduleSpecifier = exportDecl.getModuleSpecifierValue();
808
+ if (!moduleSpecifier)
809
+ continue;
810
+ for (const namedExport of exportDecl.getNamedExports()) {
811
+ const alias = namedExport.getAliasNode();
812
+ if (alias && alias.getText() === 'default') {
813
+ const resolvedPath = this.resolveRelativeImport(moduleSpecifier, filePath) ||
814
+ this.resolvePathAlias(moduleSpecifier, filePath);
815
+ if (resolvedPath) {
816
+ result.set('default', { kind: 'ref', name: namedExport.getName() });
817
+ }
818
+ }
819
+ }
820
+ }
821
+ }
822
+ // ─────────────────────────────────────────────────────────────────────────
823
+ // Type Conversion
824
+ // ─────────────────────────────────────────────────────────────────────────
825
+ /**
826
+ * Converts a TypeScript interface to NormalizedType format.
827
+ *
828
+ * @param iface - The interface node to convert
829
+ * @returns NormalizedType representation
830
+ * @internal
831
+ */
832
+ convertInterfaceToNormalizedType(iface) {
833
+ if (!Node.isInterfaceDeclaration(iface)) {
834
+ return { kind: 'unknown' };
835
+ }
836
+ const interfaceName = iface.getName();
837
+ const properties = {};
838
+ const required = [];
839
+ const type = iface.getType();
840
+ // Start with the current interface in visitingTypes to prevent infinite recursion
841
+ const visitingTypes = new Set([interfaceName]);
842
+ for (const prop of type.getProperties()) {
843
+ const propName = prop.getName();
844
+ const propType = prop.getValueDeclaration()
845
+ ? prop.getValueDeclarationOrThrow().getType()
846
+ : prop.getDeclaredType();
847
+ const isOptional = prop.isOptional();
848
+ const normalizedType = this.convertTsTypeToNormalized(propType, visitingTypes);
849
+ properties[propName] = {
850
+ type: normalizedType,
851
+ optional: isOptional,
852
+ nullable: false,
853
+ readonly: false,
854
+ deprecated: false,
855
+ };
856
+ if (!isOptional) {
857
+ required.push(propName);
858
+ }
859
+ }
860
+ const schema = {
861
+ name: interfaceName,
862
+ properties,
863
+ required,
864
+ source: { source: 'typescript', id: `interface:${interfaceName}` },
865
+ };
866
+ return { kind: 'object', schema };
867
+ }
868
+ /**
869
+ * Converts a TypeScript type alias to NormalizedType format.
870
+ *
871
+ * @param typeAlias - The type alias node to convert
872
+ * @returns NormalizedType representation
873
+ * @internal
874
+ */
875
+ convertTypeAliasToNormalizedType(typeAlias) {
876
+ if (!Node.isTypeAliasDeclaration(typeAlias)) {
877
+ return { kind: 'unknown' };
878
+ }
879
+ const aliasType = typeAlias.getType();
880
+ return this.convertTsTypeToNormalized(aliasType, new Set());
881
+ }
882
+ /**
883
+ * Converts a ts-morph Type to NormalizedType format.
884
+ *
885
+ * Handles primitives, literals, arrays, unions, intersections, and objects.
886
+ *
887
+ * @param type - The ts-morph Type to convert
888
+ * @param visitingTypes - Set of type names being visited (for cycle detection)
889
+ * @returns NormalizedType representation
890
+ * @internal
891
+ */
892
+ convertTsTypeToNormalized(type, visitingTypes) {
893
+ // Check for primitives
894
+ if (type.isString()) {
895
+ return { kind: 'primitive', value: 'string' };
896
+ }
897
+ if (type.isNumber()) {
898
+ return { kind: 'primitive', value: 'number' };
899
+ }
900
+ if (type.isBoolean()) {
901
+ return { kind: 'primitive', value: 'boolean' };
902
+ }
903
+ if (type.isNull()) {
904
+ return { kind: 'primitive', value: 'null' };
905
+ }
906
+ // Check for literal types
907
+ if (type.isStringLiteral()) {
908
+ return { kind: 'literal', value: type.getLiteralValue() };
909
+ }
910
+ if (type.isNumberLiteral()) {
911
+ return { kind: 'literal', value: type.getLiteralValue() };
912
+ }
913
+ if (type.isBooleanLiteral()) {
914
+ const text = type.getText();
915
+ return { kind: 'literal', value: text === 'true' };
916
+ }
917
+ // Check for array types
918
+ if (type.isArray()) {
919
+ const elementType = type.getArrayElementType();
920
+ if (elementType) {
921
+ return {
922
+ kind: 'array',
923
+ element: this.convertTsTypeToNormalized(elementType, visitingTypes),
924
+ };
925
+ }
926
+ return { kind: 'array', element: { kind: 'unknown' } };
927
+ }
928
+ // Check for union types
929
+ if (type.isUnion()) {
930
+ const variants = type.getUnionTypes()
931
+ .filter(t => !t.isNull() && !t.isUndefined())
932
+ .map(t => this.convertTsTypeToNormalized(t, visitingTypes));
933
+ if (variants.length === 0) {
934
+ return { kind: 'unknown' };
935
+ }
936
+ if (variants.length === 1) {
937
+ return variants[0];
938
+ }
939
+ return { kind: 'union', variants };
940
+ }
941
+ // Check for intersection types
942
+ if (type.isIntersection()) {
943
+ const members = type.getIntersectionTypes()
944
+ .map(t => this.convertTsTypeToNormalized(t, visitingTypes));
945
+ return { kind: 'intersection', members };
946
+ }
947
+ // Check for object types (interfaces, type literals, etc.)
948
+ if (type.isObject()) {
949
+ const symbol = type.getSymbol();
950
+ if (symbol) {
951
+ const name = symbol.getName();
952
+ // Check for circular reference - already visiting this type
953
+ if (name !== '__type' && visitingTypes.has(name)) {
954
+ return { kind: 'ref', name };
955
+ }
956
+ // Skip built-in types
957
+ if (name === 'Date' || name === 'Array' || name === 'Promise') {
958
+ return { kind: 'ref', name };
959
+ }
960
+ // For named interfaces/type aliases, use ref for nested types
961
+ if (name !== '__type') {
962
+ const declarations = symbol.getDeclarations();
963
+ const isNamedType = declarations && declarations.length > 0 &&
964
+ (Node.isInterfaceDeclaration(declarations[0]) || Node.isTypeAliasDeclaration(declarations[0]));
965
+ if (isNamedType) {
966
+ // Only inline at the top level (visitingTypes empty)
967
+ if (visitingTypes.size > 0) {
968
+ return { kind: 'ref', name };
969
+ }
970
+ // At top level, mark as visiting and proceed to inline
971
+ visitingTypes.add(name);
972
+ }
973
+ }
974
+ }
975
+ const properties = {};
976
+ const required = [];
977
+ for (const prop of type.getProperties()) {
978
+ const propName = prop.getName();
979
+ const propType = prop.getValueDeclaration()
980
+ ? prop.getValueDeclarationOrThrow().getType()
981
+ : prop.getDeclaredType();
982
+ const isOptional = prop.isOptional();
983
+ // Check for circular reference in property type
984
+ const propSymbol = propType.getSymbol();
985
+ if (propSymbol && visitingTypes.has(propSymbol.getName())) {
986
+ properties[propName] = {
987
+ type: { kind: 'ref', name: propSymbol.getName() },
988
+ optional: isOptional,
989
+ nullable: false,
990
+ readonly: false,
991
+ deprecated: false,
992
+ };
993
+ }
994
+ else {
995
+ properties[propName] = {
996
+ type: this.convertTsTypeToNormalized(propType, visitingTypes),
997
+ optional: isOptional,
998
+ nullable: false,
999
+ readonly: false,
1000
+ deprecated: false,
1001
+ };
1002
+ }
1003
+ if (!isOptional) {
1004
+ required.push(propName);
1005
+ }
1006
+ }
1007
+ const schema = {
1008
+ properties,
1009
+ required,
1010
+ source: { source: 'typescript', id: 'inline' },
1011
+ };
1012
+ return { kind: 'object', schema };
1013
+ }
1014
+ // Check for any/unknown
1015
+ if (type.getText() === 'any') {
1016
+ return { kind: 'any' };
1017
+ }
1018
+ return { kind: 'unknown' };
1019
+ }
1020
+ // ─────────────────────────────────────────────────────────────────────────
1021
+ // Type Reference Resolution
1022
+ // ─────────────────────────────────────────────────────────────────────────
1023
+ /**
1024
+ * Internal type reference resolution with cycle detection.
1025
+ *
1026
+ * @param typeName - Name of the type to resolve
1027
+ * @param sourceFile - Current source file being analyzed
1028
+ * @param visited - Set of visited file:type combinations
1029
+ * @param reexportChain - Chain of re-export files traversed
1030
+ * @param depth - Current recursion depth
1031
+ * @returns Resolved type reference, or null if not found
1032
+ * @internal
1033
+ */
1034
+ resolveTypeRefInternal(typeName, sourceFile, visited, reexportChain, depth) {
1035
+ const filePath = normalizePath(String(sourceFile.getFilePath()));
1036
+ // Check for max depth
1037
+ if (depth >= this.config.maxReexportDepth) {
1038
+ return {
1039
+ type: { kind: 'ref', name: typeName },
1040
+ definitionFile: filePath,
1041
+ definitionLine: 1,
1042
+ reexportChain,
1043
+ complete: false,
1044
+ incompleteReason: `Maximum re-export depth (${this.config.maxReexportDepth}) exceeded while resolving '${typeName}' in '${filePath}'`,
1045
+ };
1046
+ }
1047
+ // Check for circular reference
1048
+ if (visited.has(filePath + ':' + typeName)) {
1049
+ return {
1050
+ type: { kind: 'ref', name: typeName },
1051
+ definitionFile: filePath,
1052
+ definitionLine: 1,
1053
+ reexportChain,
1054
+ complete: false,
1055
+ incompleteReason: `Circular reference detected: '${typeName}' in '${filePath}' (chain: ${reexportChain.join(' -> ')})`,
1056
+ };
1057
+ }
1058
+ visited.add(filePath + ':' + typeName);
1059
+ // Check for direct definition in this file
1060
+ const directDef = this.findDirectDefinition(typeName, sourceFile);
1061
+ if (directDef) {
1062
+ return {
1063
+ type: directDef.type,
1064
+ definitionFile: filePath,
1065
+ definitionLine: directDef.line,
1066
+ reexportChain,
1067
+ complete: true,
1068
+ };
1069
+ }
1070
+ // Check imports in this file
1071
+ const importResult = this.findInImports(typeName, sourceFile, visited, reexportChain, depth);
1072
+ if (importResult) {
1073
+ return importResult;
1074
+ }
1075
+ // Check re-exports
1076
+ for (const exportDecl of sourceFile.getExportDeclarations()) {
1077
+ const moduleSpecifier = exportDecl.getModuleSpecifierValue();
1078
+ if (!moduleSpecifier)
1079
+ continue;
1080
+ const namedExports = exportDecl.getNamedExports();
1081
+ // Check if this exports the type we're looking for
1082
+ let targetTypeName = typeName;
1083
+ let found = false;
1084
+ if (namedExports.length === 0) {
1085
+ // export * from '...' - might include our type
1086
+ found = true;
1087
+ }
1088
+ else {
1089
+ // Check named exports
1090
+ for (const namedExport of namedExports) {
1091
+ const exportName = namedExport.getAliasNode()?.getText() || namedExport.getName();
1092
+ if (exportName === typeName) {
1093
+ targetTypeName = namedExport.getName();
1094
+ found = true;
1095
+ break;
1096
+ }
1097
+ }
1098
+ }
1099
+ if (found) {
1100
+ const resolvedPath = this.resolveRelativeImport(moduleSpecifier, filePath) ||
1101
+ this.resolvePathAlias(moduleSpecifier, filePath);
1102
+ if (resolvedPath) {
1103
+ const targetFile = this.getOrParseFile(resolvedPath);
1104
+ if (targetFile) {
1105
+ reexportChain.push(filePath);
1106
+ const result = this.resolveTypeRefInternal(targetTypeName, targetFile, visited, reexportChain, depth + 1);
1107
+ if (result) {
1108
+ return result;
1109
+ }
1110
+ }
1111
+ }
1112
+ }
1113
+ }
1114
+ return null;
1115
+ }
1116
+ /**
1117
+ * Finds a direct type definition in a source file.
1118
+ *
1119
+ * @param typeName - Name of the type to find
1120
+ * @param sourceFile - Source file to search
1121
+ * @returns Type and line number if found, or null
1122
+ * @internal
1123
+ */
1124
+ findDirectDefinition(typeName, sourceFile) {
1125
+ // Check interfaces
1126
+ for (const iface of sourceFile.getInterfaces()) {
1127
+ if (iface.getName() === typeName && iface.isExported()) {
1128
+ return {
1129
+ type: this.convertInterfaceToNormalizedType(iface),
1130
+ line: iface.getStartLineNumber(),
1131
+ };
1132
+ }
1133
+ }
1134
+ // Check type aliases
1135
+ for (const typeAlias of sourceFile.getTypeAliases()) {
1136
+ if (typeAlias.getName() === typeName && typeAlias.isExported()) {
1137
+ return {
1138
+ type: this.convertTypeAliasToNormalizedType(typeAlias),
1139
+ line: typeAlias.getStartLineNumber(),
1140
+ };
1141
+ }
1142
+ }
1143
+ // Check enums
1144
+ for (const enumDecl of sourceFile.getEnums()) {
1145
+ if (enumDecl.getName() === typeName && enumDecl.isExported()) {
1146
+ return {
1147
+ type: { kind: 'ref', name: typeName },
1148
+ line: enumDecl.getStartLineNumber(),
1149
+ };
1150
+ }
1151
+ }
1152
+ return null;
1153
+ }
1154
+ /**
1155
+ * Finds a type in the imports of a source file.
1156
+ *
1157
+ * @param typeName - Name of the type to find
1158
+ * @param sourceFile - Source file containing imports
1159
+ * @param visited - Set of visited file:type combinations
1160
+ * @param reexportChain - Chain of re-export files traversed
1161
+ * @param depth - Current recursion depth
1162
+ * @returns Resolved type reference, or null if not found in imports
1163
+ * @internal
1164
+ */
1165
+ findInImports(typeName, sourceFile, visited, reexportChain, depth) {
1166
+ const filePath = normalizePath(sourceFile.getFilePath());
1167
+ for (const importDecl of sourceFile.getImportDeclarations()) {
1168
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
1169
+ const namedImports = importDecl.getNamedImports();
1170
+ for (const namedImport of namedImports) {
1171
+ const importName = namedImport.getName();
1172
+ const alias = namedImport.getAliasNode()?.getText();
1173
+ const usedName = alias || importName;
1174
+ if (usedName === typeName) {
1175
+ const resolvedPath = this.resolveRelativeImport(moduleSpecifier, filePath) ||
1176
+ this.resolvePathAlias(moduleSpecifier, filePath);
1177
+ if (resolvedPath) {
1178
+ const targetFile = this.getOrParseFile(normalizePath(resolvedPath));
1179
+ if (targetFile) {
1180
+ reexportChain.push(filePath);
1181
+ return this.resolveTypeRefInternal(importName, targetFile, visited, reexportChain, depth + 1);
1182
+ }
1183
+ }
1184
+ }
1185
+ }
1186
+ }
1187
+ return null;
1188
+ }
1189
+ // ─────────────────────────────────────────────────────────────────────────
1190
+ // Import Graph Building
1191
+ // ─────────────────────────────────────────────────────────────────────────
1192
+ /**
1193
+ * Builds an import graph recursively from a source file.
1194
+ *
1195
+ * @param sourceFile - Root source file
1196
+ * @param depth - Remaining depth to traverse
1197
+ * @param visited - Set of visited file paths
1198
+ * @returns Import graph node
1199
+ * @internal
1200
+ */
1201
+ buildImportGraph(sourceFile, depth, visited) {
1202
+ // ts-morph returns paths with forward slashes, convert to backslashes on Windows
1203
+ const rawPath = String(sourceFile.getFilePath());
1204
+ const filePath = process.platform === 'win32' || isWindowsPath(rawPath)
1205
+ ? rawPath.replace(/\//g, '\\')
1206
+ : rawPath;
1207
+ visited.add(filePath);
1208
+ const imports = [];
1209
+ const exports = [];
1210
+ // Collect imports
1211
+ for (const importDecl of sourceFile.getImportDeclarations()) {
1212
+ const specifier = importDecl.getModuleSpecifierValue();
1213
+ const names = importDecl.getNamedImports().map(n => n.getName());
1214
+ const resolvedPath = this.resolveRelativeImport(specifier, filePath) ||
1215
+ this.resolvePathAlias(specifier, filePath);
1216
+ imports.push({
1217
+ specifier,
1218
+ resolved: resolvedPath ? normalizePath(resolvedPath) : null,
1219
+ names,
1220
+ });
1221
+ }
1222
+ // Also collect export declarations with module specifiers
1223
+ for (const exportDecl of sourceFile.getExportDeclarations()) {
1224
+ const moduleSpecifier = exportDecl.getModuleSpecifierValue();
1225
+ if (!moduleSpecifier)
1226
+ continue;
1227
+ const namedExports = exportDecl.getNamedExports();
1228
+ const names = namedExports.length > 0
1229
+ ? namedExports.map(n => n.getName())
1230
+ : ['*']; // Star export
1231
+ const resolvedPath = this.resolveRelativeImport(moduleSpecifier, filePath) ||
1232
+ this.resolvePathAlias(moduleSpecifier, filePath);
1233
+ imports.push({
1234
+ specifier: moduleSpecifier,
1235
+ resolved: resolvedPath ? normalizePath(resolvedPath) : null,
1236
+ names,
1237
+ });
1238
+ }
1239
+ // Collect exports
1240
+ for (const iface of sourceFile.getInterfaces()) {
1241
+ if (iface.isExported()) {
1242
+ exports.push(iface.getName());
1243
+ }
1244
+ }
1245
+ for (const typeAlias of sourceFile.getTypeAliases()) {
1246
+ if (typeAlias.isExported()) {
1247
+ exports.push(typeAlias.getName());
1248
+ }
1249
+ }
1250
+ for (const enumDecl of sourceFile.getEnums()) {
1251
+ if (enumDecl.isExported()) {
1252
+ exports.push(enumDecl.getName());
1253
+ }
1254
+ }
1255
+ const node = {
1256
+ filePath,
1257
+ imports,
1258
+ exports,
1259
+ };
1260
+ // Recurse to children if depth > 0
1261
+ if (depth > 0) {
1262
+ const children = [];
1263
+ for (const imp of imports) {
1264
+ if (imp.resolved && !visited.has(imp.resolved)) {
1265
+ const childFile = this.getOrParseFile(imp.resolved);
1266
+ if (childFile) {
1267
+ children.push(this.buildImportGraph(childFile, depth - 1, visited));
1268
+ }
1269
+ }
1270
+ }
1271
+ if (children.length > 0) {
1272
+ node.children = children;
1273
+ }
1274
+ }
1275
+ return node;
1276
+ }
1277
+ }
1278
+ //# sourceMappingURL=import-resolver.js.map