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.
- package/LICENSE +21 -0
- package/README.md +1662 -0
- package/dist/adapters/bootstrap.d.ts +29 -0
- package/dist/adapters/bootstrap.d.ts.map +1 -0
- package/dist/adapters/bootstrap.js +46 -0
- package/dist/adapters/bootstrap.js.map +1 -0
- package/dist/adapters/errors.d.ts +94 -0
- package/dist/adapters/errors.d.ts.map +1 -0
- package/dist/adapters/errors.js +107 -0
- package/dist/adapters/errors.js.map +1 -0
- package/dist/adapters/graphql/index.d.ts +9 -0
- package/dist/adapters/graphql/index.d.ts.map +1 -0
- package/dist/adapters/graphql/index.js +9 -0
- package/dist/adapters/graphql/index.js.map +1 -0
- package/dist/adapters/graphql/sdl-parser.d.ts +74 -0
- package/dist/adapters/graphql/sdl-parser.d.ts.map +1 -0
- package/dist/adapters/graphql/sdl-parser.js +559 -0
- package/dist/adapters/graphql/sdl-parser.js.map +1 -0
- package/dist/adapters/grpc/adapter.d.ts +76 -0
- package/dist/adapters/grpc/adapter.d.ts.map +1 -0
- package/dist/adapters/grpc/adapter.js +362 -0
- package/dist/adapters/grpc/adapter.js.map +1 -0
- package/dist/adapters/grpc/index.d.ts +10 -0
- package/dist/adapters/grpc/index.d.ts.map +1 -0
- package/dist/adapters/grpc/index.js +12 -0
- package/dist/adapters/grpc/index.js.map +1 -0
- package/dist/adapters/grpc/proto-parser.d.ts +76 -0
- package/dist/adapters/grpc/proto-parser.d.ts.map +1 -0
- package/dist/adapters/grpc/proto-parser.js +523 -0
- package/dist/adapters/grpc/proto-parser.js.map +1 -0
- package/dist/adapters/grpc/type-converter.d.ts +43 -0
- package/dist/adapters/grpc/type-converter.d.ts.map +1 -0
- package/dist/adapters/grpc/type-converter.js +270 -0
- package/dist/adapters/grpc/type-converter.js.map +1 -0
- package/dist/adapters/grpc/types.d.ts +85 -0
- package/dist/adapters/grpc/types.d.ts.map +1 -0
- package/dist/adapters/grpc/types.js +7 -0
- package/dist/adapters/grpc/types.js.map +1 -0
- package/dist/adapters/index.d.ts +39 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +50 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/mcp.d.ts +23 -0
- package/dist/adapters/mcp.d.ts.map +1 -0
- package/dist/adapters/mcp.js +293 -0
- package/dist/adapters/mcp.js.map +1 -0
- package/dist/adapters/openapi/adapter.d.ts +213 -0
- package/dist/adapters/openapi/adapter.d.ts.map +1 -0
- package/dist/adapters/openapi/adapter.js +557 -0
- package/dist/adapters/openapi/adapter.js.map +1 -0
- package/dist/adapters/openapi/convert.d.ts +120 -0
- package/dist/adapters/openapi/convert.d.ts.map +1 -0
- package/dist/adapters/openapi/convert.js +363 -0
- package/dist/adapters/openapi/convert.js.map +1 -0
- package/dist/adapters/openapi/index.d.ts +39 -0
- package/dist/adapters/openapi/index.d.ts.map +1 -0
- package/dist/adapters/openapi/index.js +48 -0
- package/dist/adapters/openapi/index.js.map +1 -0
- package/dist/adapters/openapi/parser.d.ts +95 -0
- package/dist/adapters/openapi/parser.d.ts.map +1 -0
- package/dist/adapters/openapi/parser.js +171 -0
- package/dist/adapters/openapi/parser.js.map +1 -0
- package/dist/adapters/registry.d.ts +116 -0
- package/dist/adapters/registry.d.ts.map +1 -0
- package/dist/adapters/registry.js +246 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/adapters/trpc/adapter.d.ts +159 -0
- package/dist/adapters/trpc/adapter.d.ts.map +1 -0
- package/dist/adapters/trpc/adapter.js +223 -0
- package/dist/adapters/trpc/adapter.js.map +1 -0
- package/dist/adapters/trpc/extractor.d.ts +218 -0
- package/dist/adapters/trpc/extractor.d.ts.map +1 -0
- package/dist/adapters/trpc/extractor.js +708 -0
- package/dist/adapters/trpc/extractor.js.map +1 -0
- package/dist/adapters/trpc/index.d.ts +31 -0
- package/dist/adapters/trpc/index.d.ts.map +1 -0
- package/dist/adapters/trpc/index.js +40 -0
- package/dist/adapters/trpc/index.js.map +1 -0
- package/dist/adapters/trpc/parser.d.ts +119 -0
- package/dist/adapters/trpc/parser.d.ts.map +1 -0
- package/dist/adapters/trpc/parser.js +128 -0
- package/dist/adapters/trpc/parser.js.map +1 -0
- package/dist/compare/index.d.ts +33 -0
- package/dist/compare/index.d.ts.map +1 -0
- package/dist/compare/index.js +261 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/core/types.d.ts +188 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +9 -0
- package/dist/core/types.js.map +1 -0
- package/dist/extract/index.d.ts +26 -0
- package/dist/extract/index.d.ts.map +1 -0
- package/dist/extract/index.js +44 -0
- package/dist/extract/index.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +674 -0
- package/dist/index.js.map +1 -0
- package/dist/languages/base.d.ts +57 -0
- package/dist/languages/base.d.ts.map +1 -0
- package/dist/languages/base.js +6 -0
- package/dist/languages/base.js.map +1 -0
- package/dist/languages/bootstrap.d.ts +10 -0
- package/dist/languages/bootstrap.d.ts.map +1 -0
- package/dist/languages/bootstrap.js +25 -0
- package/dist/languages/bootstrap.js.map +1 -0
- package/dist/languages/go/handlers/chi.d.ts +24 -0
- package/dist/languages/go/handlers/chi.d.ts.map +1 -0
- package/dist/languages/go/handlers/chi.js +205 -0
- package/dist/languages/go/handlers/chi.js.map +1 -0
- package/dist/languages/go/handlers/gin.d.ts +24 -0
- package/dist/languages/go/handlers/gin.d.ts.map +1 -0
- package/dist/languages/go/handlers/gin.js +156 -0
- package/dist/languages/go/handlers/gin.js.map +1 -0
- package/dist/languages/go/handlers/stdlib.d.ts +19 -0
- package/dist/languages/go/handlers/stdlib.d.ts.map +1 -0
- package/dist/languages/go/handlers/stdlib.js +112 -0
- package/dist/languages/go/handlers/stdlib.js.map +1 -0
- package/dist/languages/go/index.d.ts +18 -0
- package/dist/languages/go/index.d.ts.map +1 -0
- package/dist/languages/go/index.js +20 -0
- package/dist/languages/go/index.js.map +1 -0
- package/dist/languages/go/parser.d.ts +33 -0
- package/dist/languages/go/parser.d.ts.map +1 -0
- package/dist/languages/go/parser.js +95 -0
- package/dist/languages/go/parser.js.map +1 -0
- package/dist/languages/go/struct-extractor.d.ts +59 -0
- package/dist/languages/go/struct-extractor.d.ts.map +1 -0
- package/dist/languages/go/struct-extractor.js +483 -0
- package/dist/languages/go/struct-extractor.js.map +1 -0
- package/dist/languages/go/tag-parser.d.ts +62 -0
- package/dist/languages/go/tag-parser.d.ts.map +1 -0
- package/dist/languages/go/tag-parser.js +108 -0
- package/dist/languages/go/tag-parser.js.map +1 -0
- package/dist/languages/go/type-converter.d.ts +32 -0
- package/dist/languages/go/type-converter.d.ts.map +1 -0
- package/dist/languages/go/type-converter.js +226 -0
- package/dist/languages/go/type-converter.js.map +1 -0
- package/dist/languages/go/types.d.ts +153 -0
- package/dist/languages/go/types.d.ts.map +1 -0
- package/dist/languages/go/types.js +6 -0
- package/dist/languages/go/types.js.map +1 -0
- package/dist/languages/import-resolver.d.ts +645 -0
- package/dist/languages/import-resolver.d.ts.map +1 -0
- package/dist/languages/import-resolver.js +1278 -0
- package/dist/languages/import-resolver.js.map +1 -0
- package/dist/languages/index.d.ts +34 -0
- package/dist/languages/index.d.ts.map +1 -0
- package/dist/languages/index.js +93 -0
- package/dist/languages/index.js.map +1 -0
- package/dist/languages/json-schema.d.ts +40 -0
- package/dist/languages/json-schema.d.ts.map +1 -0
- package/dist/languages/json-schema.js +188 -0
- package/dist/languages/json-schema.js.map +1 -0
- package/dist/languages/python-ast/index.d.ts +8 -0
- package/dist/languages/python-ast/index.d.ts.map +1 -0
- package/dist/languages/python-ast/index.js +7 -0
- package/dist/languages/python-ast/index.js.map +1 -0
- package/dist/languages/python-ast/parser.d.ts +174 -0
- package/dist/languages/python-ast/parser.d.ts.map +1 -0
- package/dist/languages/python-ast/parser.js +1205 -0
- package/dist/languages/python-ast/parser.js.map +1 -0
- package/dist/languages/python-ast/type-resolver.d.ts +75 -0
- package/dist/languages/python-ast/type-resolver.d.ts.map +1 -0
- package/dist/languages/python-ast/type-resolver.js +421 -0
- package/dist/languages/python-ast/type-resolver.js.map +1 -0
- package/dist/languages/python-ast/types.d.ts +216 -0
- package/dist/languages/python-ast/types.d.ts.map +1 -0
- package/dist/languages/python-ast/types.js +6 -0
- package/dist/languages/python-ast/types.js.map +1 -0
- package/dist/languages/python.d.ts +55 -0
- package/dist/languages/python.d.ts.map +1 -0
- package/dist/languages/python.js +311 -0
- package/dist/languages/python.js.map +1 -0
- package/dist/languages/typescript.d.ts +272 -0
- package/dist/languages/typescript.d.ts.map +1 -0
- package/dist/languages/typescript.js +1381 -0
- package/dist/languages/typescript.js.map +1 -0
- package/dist/patterns/base.d.ts +146 -0
- package/dist/patterns/base.d.ts.map +1 -0
- package/dist/patterns/base.js +89 -0
- package/dist/patterns/base.js.map +1 -0
- package/dist/patterns/errors.d.ts +172 -0
- package/dist/patterns/errors.d.ts.map +1 -0
- package/dist/patterns/errors.js +185 -0
- package/dist/patterns/errors.js.map +1 -0
- package/dist/patterns/extractors.d.ts +170 -0
- package/dist/patterns/extractors.d.ts.map +1 -0
- package/dist/patterns/extractors.js +305 -0
- package/dist/patterns/extractors.js.map +1 -0
- package/dist/patterns/graphql/apollo-client.d.ts +80 -0
- package/dist/patterns/graphql/apollo-client.d.ts.map +1 -0
- package/dist/patterns/graphql/apollo-client.js +800 -0
- package/dist/patterns/graphql/apollo-client.js.map +1 -0
- package/dist/patterns/graphql/apollo-server.d.ts +55 -0
- package/dist/patterns/graphql/apollo-server.d.ts.map +1 -0
- package/dist/patterns/graphql/apollo-server.js +523 -0
- package/dist/patterns/graphql/apollo-server.js.map +1 -0
- package/dist/patterns/graphql/index.d.ts +11 -0
- package/dist/patterns/graphql/index.d.ts.map +1 -0
- package/dist/patterns/graphql/index.js +12 -0
- package/dist/patterns/graphql/index.js.map +1 -0
- package/dist/patterns/graphql/types.d.ts +213 -0
- package/dist/patterns/graphql/types.d.ts.map +1 -0
- package/dist/patterns/graphql/types.js +16 -0
- package/dist/patterns/graphql/types.js.map +1 -0
- package/dist/patterns/http-clients/axios.d.ts +148 -0
- package/dist/patterns/http-clients/axios.d.ts.map +1 -0
- package/dist/patterns/http-clients/axios.js +652 -0
- package/dist/patterns/http-clients/axios.js.map +1 -0
- package/dist/patterns/http-clients/fetch.d.ts +88 -0
- package/dist/patterns/http-clients/fetch.d.ts.map +1 -0
- package/dist/patterns/http-clients/fetch.js +364 -0
- package/dist/patterns/http-clients/fetch.js.map +1 -0
- package/dist/patterns/http-clients/index.d.ts +36 -0
- package/dist/patterns/http-clients/index.d.ts.map +1 -0
- package/dist/patterns/http-clients/index.js +50 -0
- package/dist/patterns/http-clients/index.js.map +1 -0
- package/dist/patterns/http-clients/property-access.d.ts +46 -0
- package/dist/patterns/http-clients/property-access.d.ts.map +1 -0
- package/dist/patterns/http-clients/property-access.js +818 -0
- package/dist/patterns/http-clients/property-access.js.map +1 -0
- package/dist/patterns/http-clients/type-inference.d.ts +48 -0
- package/dist/patterns/http-clients/type-inference.d.ts.map +1 -0
- package/dist/patterns/http-clients/type-inference.js +293 -0
- package/dist/patterns/http-clients/type-inference.js.map +1 -0
- package/dist/patterns/http-clients/types.d.ts +168 -0
- package/dist/patterns/http-clients/types.d.ts.map +1 -0
- package/dist/patterns/http-clients/types.js +10 -0
- package/dist/patterns/http-clients/types.js.map +1 -0
- package/dist/patterns/http-clients/url-extractor.d.ts +53 -0
- package/dist/patterns/http-clients/url-extractor.d.ts.map +1 -0
- package/dist/patterns/http-clients/url-extractor.js +338 -0
- package/dist/patterns/http-clients/url-extractor.js.map +1 -0
- package/dist/patterns/index.d.ts +44 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +49 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/patterns/python/aiohttp.d.ts +21 -0
- package/dist/patterns/python/aiohttp.d.ts.map +1 -0
- package/dist/patterns/python/aiohttp.js +188 -0
- package/dist/patterns/python/aiohttp.js.map +1 -0
- package/dist/patterns/python/httpx.d.ts +20 -0
- package/dist/patterns/python/httpx.d.ts.map +1 -0
- package/dist/patterns/python/httpx.js +183 -0
- package/dist/patterns/python/httpx.js.map +1 -0
- package/dist/patterns/python/index.d.ts +32 -0
- package/dist/patterns/python/index.d.ts.map +1 -0
- package/dist/patterns/python/index.js +63 -0
- package/dist/patterns/python/index.js.map +1 -0
- package/dist/patterns/python/property-access.d.ts +27 -0
- package/dist/patterns/python/property-access.d.ts.map +1 -0
- package/dist/patterns/python/property-access.js +132 -0
- package/dist/patterns/python/property-access.js.map +1 -0
- package/dist/patterns/python/requests.d.ts +19 -0
- package/dist/patterns/python/requests.d.ts.map +1 -0
- package/dist/patterns/python/requests.js +239 -0
- package/dist/patterns/python/requests.js.map +1 -0
- package/dist/patterns/python/types.d.ts +95 -0
- package/dist/patterns/python/types.d.ts.map +1 -0
- package/dist/patterns/python/types.js +43 -0
- package/dist/patterns/python/types.js.map +1 -0
- package/dist/patterns/registry.d.ts +181 -0
- package/dist/patterns/registry.d.ts.map +1 -0
- package/dist/patterns/registry.js +304 -0
- package/dist/patterns/registry.js.map +1 -0
- package/dist/patterns/rest/express.d.ts +78 -0
- package/dist/patterns/rest/express.d.ts.map +1 -0
- package/dist/patterns/rest/express.js +289 -0
- package/dist/patterns/rest/express.js.map +1 -0
- package/dist/patterns/rest/fastify.d.ts +93 -0
- package/dist/patterns/rest/fastify.d.ts.map +1 -0
- package/dist/patterns/rest/fastify.js +420 -0
- package/dist/patterns/rest/fastify.js.map +1 -0
- package/dist/patterns/rest/index.d.ts +31 -0
- package/dist/patterns/rest/index.d.ts.map +1 -0
- package/dist/patterns/rest/index.js +45 -0
- package/dist/patterns/rest/index.js.map +1 -0
- package/dist/patterns/rest/middleware.d.ts +25 -0
- package/dist/patterns/rest/middleware.d.ts.map +1 -0
- package/dist/patterns/rest/middleware.js +219 -0
- package/dist/patterns/rest/middleware.js.map +1 -0
- package/dist/patterns/rest/path-parser.d.ts +50 -0
- package/dist/patterns/rest/path-parser.d.ts.map +1 -0
- package/dist/patterns/rest/path-parser.js +137 -0
- package/dist/patterns/rest/path-parser.js.map +1 -0
- package/dist/patterns/rest/response-inference.d.ts +44 -0
- package/dist/patterns/rest/response-inference.d.ts.map +1 -0
- package/dist/patterns/rest/response-inference.js +218 -0
- package/dist/patterns/rest/response-inference.js.map +1 -0
- package/dist/patterns/rest/types.d.ts +102 -0
- package/dist/patterns/rest/types.d.ts.map +1 -0
- package/dist/patterns/rest/types.js +10 -0
- package/dist/patterns/rest/types.js.map +1 -0
- package/dist/patterns/types.d.ts +105 -0
- package/dist/patterns/types.d.ts.map +1 -0
- package/dist/patterns/types.js +11 -0
- package/dist/patterns/types.js.map +1 -0
- package/dist/report/index.d.ts +11 -0
- package/dist/report/index.d.ts.map +1 -0
- package/dist/report/index.js +55 -0
- package/dist/report/index.js.map +1 -0
- package/dist/tools/contract-comments.d.ts +48 -0
- package/dist/tools/contract-comments.d.ts.map +1 -0
- package/dist/tools/contract-comments.js +130 -0
- package/dist/tools/contract-comments.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/scaffold.d.ts +38 -0
- package/dist/tools/scaffold.d.ts.map +1 -0
- package/dist/tools/scaffold.js +373 -0
- package/dist/tools/scaffold.js.map +1 -0
- package/dist/trace/index.d.ts +28 -0
- package/dist/trace/index.d.ts.map +1 -0
- package/dist/trace/index.js +45 -0
- package/dist/trace/index.js.map +1 -0
- package/dist/types.d.ts +135 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/dist/watch/cache.d.ts +41 -0
- package/dist/watch/cache.d.ts.map +1 -0
- package/dist/watch/cache.js +230 -0
- package/dist/watch/cache.js.map +1 -0
- package/dist/watch/index.d.ts +9 -0
- package/dist/watch/index.d.ts.map +1 -0
- package/dist/watch/index.js +7 -0
- package/dist/watch/index.js.map +1 -0
- package/dist/watch/project.d.ts +128 -0
- package/dist/watch/project.d.ts.map +1 -0
- package/dist/watch/project.js +152 -0
- package/dist/watch/project.js.map +1 -0
- package/dist/watch/watcher.d.ts +76 -0
- package/dist/watch/watcher.d.ts.map +1 -0
- package/dist/watch/watcher.js +235 -0
- package/dist/watch/watcher.js.map +1 -0
- 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
|