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,818 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Property Access Tracking
|
|
3
|
+
*
|
|
4
|
+
* Track property accesses on HTTP client response data to infer types.
|
|
5
|
+
* This enables consumer schema inference from how response data is used.
|
|
6
|
+
*
|
|
7
|
+
* @module patterns/http-clients/property-access
|
|
8
|
+
* @see .context/ADR-P2-3-HTTP-CLIENT-TRACING.md
|
|
9
|
+
*/
|
|
10
|
+
import { Node, SyntaxKind } from 'ts-morph';
|
|
11
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
12
|
+
* 🔍 Public API
|
|
13
|
+
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
14
|
+
/**
|
|
15
|
+
* Track all property accesses on a variable assigned from a call expression.
|
|
16
|
+
*
|
|
17
|
+
* Handles:
|
|
18
|
+
* - Direct access: `response.data`
|
|
19
|
+
* - Chained access: `response.data.user.name`
|
|
20
|
+
* - Destructuring: `const { data } = response`
|
|
21
|
+
* - Optional chaining: `response?.data?.user`
|
|
22
|
+
*
|
|
23
|
+
* @param callNode - The call expression node
|
|
24
|
+
* @param variableName - Optional variable name to track (derived if not provided)
|
|
25
|
+
* @returns Array of property accesses found
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const accesses = trackPropertyAccesses(fetchCall);
|
|
30
|
+
* // [{ path: 'data.user.name', segments: ['data', 'user', 'name'] }]
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function trackPropertyAccesses(callNode, variableName) {
|
|
34
|
+
const accesses = [];
|
|
35
|
+
if (!Node.isCallExpression(callNode)) {
|
|
36
|
+
return accesses;
|
|
37
|
+
}
|
|
38
|
+
// First, find the innermost fetch/axios call in the chain
|
|
39
|
+
const rootCall = findRootCallInChain(callNode);
|
|
40
|
+
// Find the variable name if not provided
|
|
41
|
+
const varName = variableName || findVariableName(rootCall);
|
|
42
|
+
// Check for destructuring patterns first
|
|
43
|
+
const destructuringAccesses = findDestructuringFromCall(rootCall);
|
|
44
|
+
for (const access of destructuringAccesses) {
|
|
45
|
+
addUniqueAccess(accesses, access);
|
|
46
|
+
}
|
|
47
|
+
// If we have a variable name, track direct accesses on it
|
|
48
|
+
if (varName) {
|
|
49
|
+
// Get the source file to search for usages
|
|
50
|
+
const sourceFile = rootCall.getSourceFile();
|
|
51
|
+
// Find all identifiers with this name in the file
|
|
52
|
+
const identifiers = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier);
|
|
53
|
+
for (const id of identifiers) {
|
|
54
|
+
if (id.getText() !== varName)
|
|
55
|
+
continue;
|
|
56
|
+
// Skip the declaration itself
|
|
57
|
+
const parent = id.getParent();
|
|
58
|
+
if (Node.isVariableDeclaration(parent))
|
|
59
|
+
continue;
|
|
60
|
+
// Check if this is a property access
|
|
61
|
+
const propertyAccess = findFullPropertyAccessChain(id, varName);
|
|
62
|
+
if (propertyAccess) {
|
|
63
|
+
addUniqueAccess(accesses, propertyAccess);
|
|
64
|
+
}
|
|
65
|
+
// Check for element access (array index)
|
|
66
|
+
const elementAccess = findElementAccessChain(id, varName);
|
|
67
|
+
if (elementAccess) {
|
|
68
|
+
addUniqueAccess(accesses, elementAccess);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Track accesses in chained .then() callbacks starting from root
|
|
73
|
+
const chainedAccesses = trackChainedAccesses(rootCall);
|
|
74
|
+
for (const access of chainedAccesses) {
|
|
75
|
+
addUniqueAccess(accesses, access);
|
|
76
|
+
}
|
|
77
|
+
// Find ALL .then() callbacks in the entire call tree and extract accesses
|
|
78
|
+
const allThenAccesses = findAllThenCallbackAccesses(callNode);
|
|
79
|
+
for (const access of allThenAccesses) {
|
|
80
|
+
addUniqueAccess(accesses, access);
|
|
81
|
+
}
|
|
82
|
+
return accesses;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build an inferred type structure from property accesses.
|
|
86
|
+
*
|
|
87
|
+
* @param accesses - Property accesses observed
|
|
88
|
+
* @returns Nested object representing the inferred type structure
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const accesses = [{ path: 'user.name' }, { path: 'user.email' }];
|
|
93
|
+
* const type = buildTypeFromAccesses(accesses);
|
|
94
|
+
* // { user: { name: 'unknown', email: 'unknown' } }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function buildTypeFromAccesses(accesses) {
|
|
98
|
+
const result = {};
|
|
99
|
+
for (const access of accesses) {
|
|
100
|
+
let current = result;
|
|
101
|
+
const segments = access.segments;
|
|
102
|
+
for (let i = 0; i < segments.length; i++) {
|
|
103
|
+
const segment = segments[i];
|
|
104
|
+
// Handle array access segments
|
|
105
|
+
if (segment.startsWith('[') && segment.endsWith(']')) {
|
|
106
|
+
// Mark current level as array
|
|
107
|
+
if (!current._isArray) {
|
|
108
|
+
current._isArray = true;
|
|
109
|
+
current._element = {};
|
|
110
|
+
}
|
|
111
|
+
// Continue building inside element
|
|
112
|
+
if (i < segments.length - 1) {
|
|
113
|
+
current = current._element;
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (i === segments.length - 1) {
|
|
118
|
+
// Leaf node - mark as unknown type
|
|
119
|
+
if (!(segment in current)) {
|
|
120
|
+
current[segment] = 'unknown';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// Intermediate node - ensure it's an object
|
|
125
|
+
if (!(segment in current) || typeof current[segment] !== 'object') {
|
|
126
|
+
current[segment] = {};
|
|
127
|
+
}
|
|
128
|
+
current = current[segment];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
135
|
+
* 🔗 Chain Navigation
|
|
136
|
+
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
137
|
+
/**
|
|
138
|
+
* Find the root call expression in a method chain.
|
|
139
|
+
*
|
|
140
|
+
* For `fetch().then().then()`, returns `fetch()`.
|
|
141
|
+
*
|
|
142
|
+
* @internal
|
|
143
|
+
*/
|
|
144
|
+
function findRootCallInChain(callNode) {
|
|
145
|
+
if (!Node.isCallExpression(callNode)) {
|
|
146
|
+
return callNode;
|
|
147
|
+
}
|
|
148
|
+
const expr = callNode.getExpression();
|
|
149
|
+
// If it's a property access like .then(), walk down
|
|
150
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
151
|
+
const innerExpr = expr.getExpression();
|
|
152
|
+
if (Node.isCallExpression(innerExpr)) {
|
|
153
|
+
return findRootCallInChain(innerExpr);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return callNode;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Collect all call expressions in a method chain.
|
|
160
|
+
*
|
|
161
|
+
* @internal
|
|
162
|
+
*/
|
|
163
|
+
function collectChainedCalls(startNode) {
|
|
164
|
+
const calls = [];
|
|
165
|
+
// Walk up through the chain collecting all call expressions
|
|
166
|
+
let current = startNode;
|
|
167
|
+
while (current) {
|
|
168
|
+
const parent = current.getParent();
|
|
169
|
+
if (!parent)
|
|
170
|
+
break;
|
|
171
|
+
if (Node.isPropertyAccessExpression(parent)) {
|
|
172
|
+
const grandparent = parent.getParent();
|
|
173
|
+
if (grandparent && Node.isCallExpression(grandparent)) {
|
|
174
|
+
calls.push(grandparent);
|
|
175
|
+
current = grandparent;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
return calls;
|
|
182
|
+
}
|
|
183
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
184
|
+
* 🎯 .then() Callback Tracking
|
|
185
|
+
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
186
|
+
/**
|
|
187
|
+
* Find all .then() callbacks in a call tree and extract property accesses from them.
|
|
188
|
+
*
|
|
189
|
+
* @internal
|
|
190
|
+
*/
|
|
191
|
+
function findAllThenCallbackAccesses(callNode) {
|
|
192
|
+
const accesses = [];
|
|
193
|
+
// Recursively find all .then() calls in the entire tree
|
|
194
|
+
findThenCallsRecursive(callNode, accesses);
|
|
195
|
+
// Also check the source file for any .then() calls we might have missed
|
|
196
|
+
const sourceFile = callNode.getSourceFile();
|
|
197
|
+
sourceFile.forEachDescendant((node) => {
|
|
198
|
+
if (Node.isCallExpression(node)) {
|
|
199
|
+
findThenCallsRecursive(node, accesses);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
return accesses;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Recursively find .then() callbacks and extract property accesses.
|
|
206
|
+
*
|
|
207
|
+
* @internal
|
|
208
|
+
*/
|
|
209
|
+
function findThenCallsRecursive(node, accesses) {
|
|
210
|
+
if (!Node.isCallExpression(node))
|
|
211
|
+
return;
|
|
212
|
+
const expr = node.getExpression();
|
|
213
|
+
// Check if this is a .then() call
|
|
214
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
215
|
+
const methodName = expr.getName();
|
|
216
|
+
if (methodName === 'then' || methodName === 'catch' || methodName === 'finally') {
|
|
217
|
+
// Get the callback argument
|
|
218
|
+
const args = node.getArguments();
|
|
219
|
+
if (args.length > 0) {
|
|
220
|
+
const callback = args[0];
|
|
221
|
+
if (Node.isArrowFunction(callback) || Node.isFunctionExpression(callback)) {
|
|
222
|
+
const params = callback.getParameters();
|
|
223
|
+
if (params.length > 0) {
|
|
224
|
+
const paramName = params[0].getName();
|
|
225
|
+
// Extract property accesses from the callback
|
|
226
|
+
extractPropertyAccessesFromCallback(callback, paramName, accesses);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Extract property accesses from a callback function.
|
|
235
|
+
*
|
|
236
|
+
* @internal
|
|
237
|
+
*/
|
|
238
|
+
function extractPropertyAccessesFromCallback(callback, paramName, accesses) {
|
|
239
|
+
// Get the body of the callback
|
|
240
|
+
let body;
|
|
241
|
+
if (Node.isArrowFunction(callback)) {
|
|
242
|
+
body = callback.getBody();
|
|
243
|
+
}
|
|
244
|
+
else if (Node.isFunctionExpression(callback)) {
|
|
245
|
+
body = callback.getBody();
|
|
246
|
+
}
|
|
247
|
+
if (!body)
|
|
248
|
+
return;
|
|
249
|
+
// For expression bodies like `data => data.name`, body IS the expression
|
|
250
|
+
if (Node.isPropertyAccessExpression(body)) {
|
|
251
|
+
// Check if root is the param
|
|
252
|
+
let root = body.getExpression();
|
|
253
|
+
while (Node.isPropertyAccessExpression(root)) {
|
|
254
|
+
root = root.getExpression();
|
|
255
|
+
}
|
|
256
|
+
if (Node.isIdentifier(root) && root.getText() === paramName) {
|
|
257
|
+
const chain = extractPropertyChain(body);
|
|
258
|
+
if (chain.length > 0) {
|
|
259
|
+
addUniqueAccess(accesses, {
|
|
260
|
+
path: chain.join('.'),
|
|
261
|
+
segments: chain,
|
|
262
|
+
location: getLocation(body),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// For block bodies like `data => { console.log(data.name); }`
|
|
268
|
+
// Find all property access expressions
|
|
269
|
+
body.forEachDescendant((descendant) => {
|
|
270
|
+
if (Node.isPropertyAccessExpression(descendant)) {
|
|
271
|
+
// Check if the root expression is the param identifier
|
|
272
|
+
let root = descendant.getExpression();
|
|
273
|
+
while (Node.isPropertyAccessExpression(root)) {
|
|
274
|
+
root = root.getExpression();
|
|
275
|
+
}
|
|
276
|
+
if (Node.isIdentifier(root) && root.getText() === paramName) {
|
|
277
|
+
const chain = extractPropertyChain(descendant);
|
|
278
|
+
if (chain.length > 0) {
|
|
279
|
+
addUniqueAccess(accesses, {
|
|
280
|
+
path: chain.join('.'),
|
|
281
|
+
segments: chain,
|
|
282
|
+
location: getLocation(descendant),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Extract property chain from a property access expression.
|
|
291
|
+
*
|
|
292
|
+
* For `data.user.name`, returns `['user', 'name']`.
|
|
293
|
+
*
|
|
294
|
+
* @internal
|
|
295
|
+
*/
|
|
296
|
+
function extractPropertyChain(propAccess) {
|
|
297
|
+
const chain = [];
|
|
298
|
+
let current = propAccess;
|
|
299
|
+
// Walk up through chained property accesses
|
|
300
|
+
while (current && Node.isPropertyAccessExpression(current)) {
|
|
301
|
+
const parent = current.getParent();
|
|
302
|
+
if (Node.isPropertyAccessExpression(parent) && parent.getExpression() === current) {
|
|
303
|
+
// This property access is the base of another - continue up
|
|
304
|
+
current = parent;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// Now walk down collecting names
|
|
311
|
+
while (current && Node.isPropertyAccessExpression(current)) {
|
|
312
|
+
chain.push(current.getName());
|
|
313
|
+
const expr = current.getExpression();
|
|
314
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
315
|
+
current = expr;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return chain.reverse();
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Track accesses in all .then() callbacks in the chain.
|
|
325
|
+
*
|
|
326
|
+
* @internal
|
|
327
|
+
*/
|
|
328
|
+
function trackChainedAccesses(callNode) {
|
|
329
|
+
const accesses = [];
|
|
330
|
+
// Collect all .then() calls in the chain starting from this node and going up
|
|
331
|
+
const chainedCalls = collectChainedCalls(callNode);
|
|
332
|
+
for (const chainCall of chainedCalls) {
|
|
333
|
+
if (!Node.isCallExpression(chainCall))
|
|
334
|
+
continue;
|
|
335
|
+
const expr = chainCall.getExpression();
|
|
336
|
+
if (!Node.isPropertyAccessExpression(expr))
|
|
337
|
+
continue;
|
|
338
|
+
const methodName = expr.getName();
|
|
339
|
+
if (methodName === 'then' || methodName === 'catch' || methodName === 'finally') {
|
|
340
|
+
// Get the callback argument
|
|
341
|
+
const args = chainCall.getArguments();
|
|
342
|
+
if (args.length > 0) {
|
|
343
|
+
const callback = args[0];
|
|
344
|
+
// Track accesses inside the callback
|
|
345
|
+
if (Node.isArrowFunction(callback) || Node.isFunctionExpression(callback)) {
|
|
346
|
+
const params = callback.getParameters();
|
|
347
|
+
if (params.length > 0) {
|
|
348
|
+
const paramName = params[0].getName();
|
|
349
|
+
const body = callback.getBody();
|
|
350
|
+
if (body) {
|
|
351
|
+
const callbackAccesses = trackAccessesInCallbackBody(body, paramName);
|
|
352
|
+
for (const access of callbackAccesses) {
|
|
353
|
+
addUniqueAccess(accesses, access);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Also check if the callNode itself is a .then() call and process it
|
|
362
|
+
if (Node.isCallExpression(callNode)) {
|
|
363
|
+
const expr = callNode.getExpression();
|
|
364
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
365
|
+
const methodName = expr.getName();
|
|
366
|
+
if (methodName === 'then' || methodName === 'catch' || methodName === 'finally') {
|
|
367
|
+
const args = callNode.getArguments();
|
|
368
|
+
if (args.length > 0) {
|
|
369
|
+
const callback = args[0];
|
|
370
|
+
if (Node.isArrowFunction(callback) || Node.isFunctionExpression(callback)) {
|
|
371
|
+
const params = callback.getParameters();
|
|
372
|
+
if (params.length > 0) {
|
|
373
|
+
const paramName = params[0].getName();
|
|
374
|
+
const body = callback.getBody();
|
|
375
|
+
if (body) {
|
|
376
|
+
const callbackAccesses = trackAccessesInCallbackBody(body, paramName);
|
|
377
|
+
for (const access of callbackAccesses) {
|
|
378
|
+
addUniqueAccess(accesses, access);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return accesses;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Track property accesses in a callback body for a parameter.
|
|
391
|
+
*
|
|
392
|
+
* @internal
|
|
393
|
+
*/
|
|
394
|
+
function trackAccessesInCallbackBody(body, paramName) {
|
|
395
|
+
const accesses = [];
|
|
396
|
+
if (!body)
|
|
397
|
+
return accesses;
|
|
398
|
+
// Also check if body itself is a property access (expression body)
|
|
399
|
+
if (Node.isPropertyAccessExpression(body)) {
|
|
400
|
+
const rootIdentifier = getRootIdentifier(body);
|
|
401
|
+
if (rootIdentifier && rootIdentifier.getText() === paramName) {
|
|
402
|
+
const chain = buildFullChainFromRoot(body, paramName);
|
|
403
|
+
if (chain.length > 0) {
|
|
404
|
+
const path = chain.join('.');
|
|
405
|
+
addUniqueAccess(accesses, {
|
|
406
|
+
path,
|
|
407
|
+
segments: chain,
|
|
408
|
+
location: getLocation(body),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// Use body.getDescendantsOfKind directly
|
|
414
|
+
const bodyPropAccesses = body.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression);
|
|
415
|
+
for (const node of bodyPropAccesses) {
|
|
416
|
+
// Check if the root is the parameter
|
|
417
|
+
const rootIdentifier = getRootIdentifier(node);
|
|
418
|
+
if (rootIdentifier && rootIdentifier.getText() === paramName) {
|
|
419
|
+
const chain = buildFullChainFromRoot(node, paramName);
|
|
420
|
+
if (chain.length > 0) {
|
|
421
|
+
const path = chain.join('.');
|
|
422
|
+
addUniqueAccess(accesses, {
|
|
423
|
+
path,
|
|
424
|
+
segments: chain,
|
|
425
|
+
location: getLocation(node),
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return accesses;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Track property accesses if the current call is a .then() callback.
|
|
434
|
+
*
|
|
435
|
+
* @internal
|
|
436
|
+
*/
|
|
437
|
+
function trackAccessesInCurrentCall(callNode) {
|
|
438
|
+
const accesses = [];
|
|
439
|
+
if (!Node.isCallExpression(callNode)) {
|
|
440
|
+
return accesses;
|
|
441
|
+
}
|
|
442
|
+
const expr = callNode.getExpression();
|
|
443
|
+
if (!Node.isPropertyAccessExpression(expr)) {
|
|
444
|
+
return accesses;
|
|
445
|
+
}
|
|
446
|
+
const methodName = expr.getName();
|
|
447
|
+
if (methodName !== 'then' && methodName !== 'catch' && methodName !== 'finally') {
|
|
448
|
+
return accesses;
|
|
449
|
+
}
|
|
450
|
+
// Get the callback argument
|
|
451
|
+
const args = callNode.getArguments();
|
|
452
|
+
if (args.length > 0) {
|
|
453
|
+
const callback = args[0];
|
|
454
|
+
if (Node.isArrowFunction(callback) || Node.isFunctionExpression(callback)) {
|
|
455
|
+
const params = callback.getParameters();
|
|
456
|
+
if (params.length > 0) {
|
|
457
|
+
const paramName = params[0].getName();
|
|
458
|
+
// Find property accesses directly by searching the source file
|
|
459
|
+
// within the callback's text range
|
|
460
|
+
const sourceFile = callNode.getSourceFile();
|
|
461
|
+
const callbackStart = callback.getStart();
|
|
462
|
+
const callbackEnd = callback.getEnd();
|
|
463
|
+
// Get all property access expressions in the source file
|
|
464
|
+
const allPropertyAccesses = sourceFile.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression);
|
|
465
|
+
for (const propAccess of allPropertyAccesses) {
|
|
466
|
+
// Check if this property access is within the callback
|
|
467
|
+
const propStart = propAccess.getStart();
|
|
468
|
+
if (propStart < callbackStart || propStart > callbackEnd) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
// Check if root is the parameter
|
|
472
|
+
const rootId = getRootIdentifier(propAccess);
|
|
473
|
+
if (rootId && rootId.getText() === paramName) {
|
|
474
|
+
const chain = buildFullChainFromRoot(propAccess, paramName);
|
|
475
|
+
if (chain.length > 0) {
|
|
476
|
+
const path = chain.join('.');
|
|
477
|
+
addUniqueAccess(accesses, {
|
|
478
|
+
path,
|
|
479
|
+
segments: chain,
|
|
480
|
+
location: getLocation(propAccess),
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return accesses;
|
|
489
|
+
}
|
|
490
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
491
|
+
* 📦 Destructuring Support
|
|
492
|
+
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
493
|
+
/**
|
|
494
|
+
* Find destructuring patterns from a call expression.
|
|
495
|
+
*
|
|
496
|
+
* Handles:
|
|
497
|
+
* - `const { name, email } = await fetch()`
|
|
498
|
+
* - `const { user: { name } } = await fetch()` (nested)
|
|
499
|
+
*
|
|
500
|
+
* @internal
|
|
501
|
+
*/
|
|
502
|
+
function findDestructuringFromCall(callNode) {
|
|
503
|
+
const accesses = [];
|
|
504
|
+
// Walk up to find variable declaration
|
|
505
|
+
let current = callNode;
|
|
506
|
+
while (current) {
|
|
507
|
+
const parent = current.getParent();
|
|
508
|
+
if (!parent)
|
|
509
|
+
break;
|
|
510
|
+
if (Node.isVariableDeclaration(parent)) {
|
|
511
|
+
const nameNode = parent.getNameNode();
|
|
512
|
+
if (Node.isObjectBindingPattern(nameNode)) {
|
|
513
|
+
// Extract all bound properties
|
|
514
|
+
extractBindingPatternAccesses(nameNode, [], accesses);
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
// Skip await and parentheses
|
|
519
|
+
if (Node.isAwaitExpression(parent) || Node.isParenthesizedExpression(parent)) {
|
|
520
|
+
current = parent;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
// Handle chained calls
|
|
524
|
+
if (Node.isCallExpression(parent) || Node.isPropertyAccessExpression(parent)) {
|
|
525
|
+
current = parent;
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
return accesses;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Extract property accesses from an object binding pattern.
|
|
534
|
+
*
|
|
535
|
+
* @internal
|
|
536
|
+
*/
|
|
537
|
+
function extractBindingPatternAccesses(pattern, pathPrefix, accesses) {
|
|
538
|
+
if (!Node.isObjectBindingPattern(pattern))
|
|
539
|
+
return;
|
|
540
|
+
const elements = pattern.getElements();
|
|
541
|
+
for (const elem of elements) {
|
|
542
|
+
const nameNode = elem.getNameNode();
|
|
543
|
+
const propertyName = elem.getPropertyNameNode();
|
|
544
|
+
// Get the property name being accessed
|
|
545
|
+
let propName;
|
|
546
|
+
if (propertyName && Node.isIdentifier(propertyName)) {
|
|
547
|
+
propName = propertyName.getText();
|
|
548
|
+
}
|
|
549
|
+
else if (Node.isIdentifier(nameNode)) {
|
|
550
|
+
propName = nameNode.getText();
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
const fullPath = [...pathPrefix, propName];
|
|
556
|
+
// Check if the name node is another binding pattern (nested destructuring)
|
|
557
|
+
if (Node.isObjectBindingPattern(nameNode)) {
|
|
558
|
+
// Recursively extract nested patterns
|
|
559
|
+
extractBindingPatternAccesses(nameNode, fullPath, accesses);
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
// Leaf node - add the access
|
|
563
|
+
accesses.push({
|
|
564
|
+
path: fullPath.join('.'),
|
|
565
|
+
segments: fullPath,
|
|
566
|
+
location: getLocation(elem),
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
572
|
+
* 🔗 Property Chain Building
|
|
573
|
+
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
574
|
+
/**
|
|
575
|
+
* Find the variable name from a call expression's context.
|
|
576
|
+
*
|
|
577
|
+
* @internal
|
|
578
|
+
*/
|
|
579
|
+
function findVariableName(callNode) {
|
|
580
|
+
let current = callNode;
|
|
581
|
+
while (current) {
|
|
582
|
+
const parent = current.getParent();
|
|
583
|
+
if (!parent)
|
|
584
|
+
break;
|
|
585
|
+
// Found variable declaration
|
|
586
|
+
if (Node.isVariableDeclaration(parent)) {
|
|
587
|
+
const nameNode = parent.getNameNode();
|
|
588
|
+
if (Node.isIdentifier(nameNode)) {
|
|
589
|
+
return nameNode.getText();
|
|
590
|
+
}
|
|
591
|
+
// Handle destructuring pattern - we need to track the original object
|
|
592
|
+
if (Node.isObjectBindingPattern(nameNode)) {
|
|
593
|
+
// Return undefined to trigger destructuring tracking
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
// Skip await and parentheses
|
|
599
|
+
if (Node.isAwaitExpression(parent) || Node.isParenthesizedExpression(parent)) {
|
|
600
|
+
current = parent;
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
// Handle chained calls
|
|
604
|
+
if (Node.isCallExpression(parent) || Node.isPropertyAccessExpression(parent)) {
|
|
605
|
+
current = parent;
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
return undefined;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Get the root identifier from a property access chain.
|
|
614
|
+
*
|
|
615
|
+
* @internal
|
|
616
|
+
*/
|
|
617
|
+
function getRootIdentifier(node) {
|
|
618
|
+
let current = node;
|
|
619
|
+
while (Node.isPropertyAccessExpression(current)) {
|
|
620
|
+
current = current.getExpression();
|
|
621
|
+
}
|
|
622
|
+
if (Node.isIdentifier(current)) {
|
|
623
|
+
return current;
|
|
624
|
+
}
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Build the full chain from root, excluding root name.
|
|
629
|
+
*
|
|
630
|
+
* @internal
|
|
631
|
+
*/
|
|
632
|
+
function buildFullChainFromRoot(node, rootName) {
|
|
633
|
+
if (!Node.isPropertyAccessExpression(node))
|
|
634
|
+
return [];
|
|
635
|
+
// Collect the entire chain from innermost to outermost
|
|
636
|
+
const chain = [];
|
|
637
|
+
let current = node;
|
|
638
|
+
// Walk up to find the outermost property access
|
|
639
|
+
while (current) {
|
|
640
|
+
const parent = current.getParent();
|
|
641
|
+
if (parent && Node.isPropertyAccessExpression(parent) && parent.getExpression() === current) {
|
|
642
|
+
current = parent;
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
// Now walk down collecting property names
|
|
649
|
+
let walkNode = current;
|
|
650
|
+
while (walkNode && Node.isPropertyAccessExpression(walkNode)) {
|
|
651
|
+
chain.push(walkNode.getName());
|
|
652
|
+
const expr = walkNode.getExpression();
|
|
653
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
654
|
+
walkNode = expr;
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return chain.reverse();
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Find the full property access chain starting from an identifier.
|
|
664
|
+
*
|
|
665
|
+
* For `data.user.email`, returns `{ path: 'user.email', segments: ['user', 'email'] }`.
|
|
666
|
+
*
|
|
667
|
+
* @internal
|
|
668
|
+
*/
|
|
669
|
+
function findFullPropertyAccessChain(identifier, rootName) {
|
|
670
|
+
// The identifier must be the start of a property access chain
|
|
671
|
+
const parent = identifier.getParent();
|
|
672
|
+
if (!parent)
|
|
673
|
+
return undefined;
|
|
674
|
+
// Check for property access expression where identifier is the object
|
|
675
|
+
if (Node.isPropertyAccessExpression(parent) && parent.getExpression() === identifier) {
|
|
676
|
+
// Walk up to find the outermost property access
|
|
677
|
+
let outermost = parent;
|
|
678
|
+
let current = parent;
|
|
679
|
+
while (current) {
|
|
680
|
+
const currentParent = current.getParent();
|
|
681
|
+
if (currentParent && Node.isPropertyAccessExpression(currentParent) &&
|
|
682
|
+
currentParent.getExpression() === current) {
|
|
683
|
+
outermost = currentParent;
|
|
684
|
+
current = currentParent;
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
// Build the chain from identifier to outermost
|
|
691
|
+
const chain = buildChainFromIdentifier(outermost, rootName);
|
|
692
|
+
if (chain.length > 0) {
|
|
693
|
+
return {
|
|
694
|
+
path: chain.join('.'),
|
|
695
|
+
segments: chain,
|
|
696
|
+
location: getLocation(outermost),
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return undefined;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Build chain from a property access expression, excluding the root identifier.
|
|
704
|
+
*
|
|
705
|
+
* @internal
|
|
706
|
+
*/
|
|
707
|
+
function buildChainFromIdentifier(node, rootName) {
|
|
708
|
+
const chain = [];
|
|
709
|
+
let current = node;
|
|
710
|
+
while (current && Node.isPropertyAccessExpression(current)) {
|
|
711
|
+
chain.unshift(current.getName());
|
|
712
|
+
const expr = current.getExpression();
|
|
713
|
+
if (Node.isIdentifier(expr)) {
|
|
714
|
+
if (expr.getText() === rootName) {
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
719
|
+
current = expr;
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return chain;
|
|
726
|
+
}
|
|
727
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
728
|
+
* 📊 Element Access (Array Indexing)
|
|
729
|
+
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
730
|
+
/**
|
|
731
|
+
* Find element access chain (array indexing).
|
|
732
|
+
*
|
|
733
|
+
* For `data[0].name`, returns appropriate access.
|
|
734
|
+
*
|
|
735
|
+
* @internal
|
|
736
|
+
*/
|
|
737
|
+
function findElementAccessChain(identifier, rootName) {
|
|
738
|
+
const parent = identifier.getParent();
|
|
739
|
+
if (!parent)
|
|
740
|
+
return undefined;
|
|
741
|
+
// Check for element access expression (data[0])
|
|
742
|
+
if (Node.isElementAccessExpression(parent) && parent.getExpression() === identifier) {
|
|
743
|
+
const argExpr = parent.getArgumentExpression();
|
|
744
|
+
if (!argExpr)
|
|
745
|
+
return undefined;
|
|
746
|
+
const indexText = argExpr.getText();
|
|
747
|
+
// Check if there's property access after the element access
|
|
748
|
+
const grandparent = parent.getParent();
|
|
749
|
+
if (grandparent && Node.isPropertyAccessExpression(grandparent) &&
|
|
750
|
+
grandparent.getExpression() === parent) {
|
|
751
|
+
// data[0].name
|
|
752
|
+
const propChain = buildChainFromElementAccess(grandparent, parent);
|
|
753
|
+
const chain = [`[${indexText}]`, ...propChain];
|
|
754
|
+
return {
|
|
755
|
+
path: chain.join('.'),
|
|
756
|
+
segments: chain,
|
|
757
|
+
location: getLocation(grandparent),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
// Just data[0]
|
|
761
|
+
return {
|
|
762
|
+
path: `[${indexText}]`,
|
|
763
|
+
segments: [`[${indexText}]`],
|
|
764
|
+
location: getLocation(parent),
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
return undefined;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Build chain from property access after element access.
|
|
771
|
+
*
|
|
772
|
+
* @internal
|
|
773
|
+
*/
|
|
774
|
+
function buildChainFromElementAccess(node, elementAccess) {
|
|
775
|
+
const chain = [];
|
|
776
|
+
let current = node;
|
|
777
|
+
while (current && Node.isPropertyAccessExpression(current)) {
|
|
778
|
+
chain.unshift(current.getName());
|
|
779
|
+
const expr = current.getExpression();
|
|
780
|
+
if (expr === elementAccess) {
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
784
|
+
current = expr;
|
|
785
|
+
}
|
|
786
|
+
else {
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return chain.reverse();
|
|
791
|
+
}
|
|
792
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
793
|
+
* 🛠️ Utility Functions
|
|
794
|
+
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
795
|
+
/**
|
|
796
|
+
* Add access if not already present (deduplication).
|
|
797
|
+
*
|
|
798
|
+
* @internal
|
|
799
|
+
*/
|
|
800
|
+
function addUniqueAccess(accesses, access) {
|
|
801
|
+
const existing = accesses.find(a => a.path === access.path);
|
|
802
|
+
if (!existing) {
|
|
803
|
+
accesses.push(access);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Get source location for a node.
|
|
808
|
+
*
|
|
809
|
+
* @internal
|
|
810
|
+
*/
|
|
811
|
+
function getLocation(node) {
|
|
812
|
+
const sourceFile = node.getSourceFile();
|
|
813
|
+
return {
|
|
814
|
+
file: sourceFile.getFilePath(),
|
|
815
|
+
line: node.getStartLineNumber(),
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
//# sourceMappingURL=property-access.js.map
|