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,800 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🔗 Apollo Client Pattern Matcher
|
|
3
|
+
*
|
|
4
|
+
* Detects Apollo Client hook usage patterns in TypeScript code.
|
|
5
|
+
* Analyzes client-side GraphQL usage to extract:
|
|
6
|
+
*
|
|
7
|
+
* - 📋 useQuery, useMutation, useLazyQuery, useSubscription hooks
|
|
8
|
+
* - 🔍 Variables passed to hooks
|
|
9
|
+
* - 🎯 Data property access paths
|
|
10
|
+
* - ⚡ Type parameter extraction
|
|
11
|
+
* - 🔗 Schema query/mutation name matching
|
|
12
|
+
*
|
|
13
|
+
* @module patterns/graphql/apollo-client
|
|
14
|
+
* @see .context/ADR-P2-4-GRAPHQL-SUPPORT.md
|
|
15
|
+
*/
|
|
16
|
+
import { Node, SyntaxKind, } from 'ts-morph';
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// 🏗️ Apollo Client Pattern Matcher Class
|
|
19
|
+
// =============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* 🔍 Apollo Client Pattern Matcher
|
|
22
|
+
*
|
|
23
|
+
* Analyzes TypeScript source files to detect Apollo Client hook usage patterns.
|
|
24
|
+
* Tracks query/mutation/subscription hooks, their variables, and data property accesses.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const matcher = new ApolloClientPatternMatcher();
|
|
29
|
+
* const hooks = matcher.analyze(sourceFile);
|
|
30
|
+
* // Returns ClientHookUsage[] with detected hooks and their schema relationships
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export class ApolloClientPatternMatcher {
|
|
34
|
+
/**
|
|
35
|
+
* Analyze a source file for Apollo Client hook patterns
|
|
36
|
+
*
|
|
37
|
+
* Detects useQuery, useMutation, useLazyQuery, and useSubscription hooks,
|
|
38
|
+
* extracts their configuration, and tracks data property accesses.
|
|
39
|
+
*
|
|
40
|
+
* @param sourceFile - ts-morph SourceFile to analyze
|
|
41
|
+
* @returns Array of ClientHookUsage with schema matching info
|
|
42
|
+
*/
|
|
43
|
+
analyze(sourceFile) {
|
|
44
|
+
const queryHooks = detectUseQueryHook(sourceFile);
|
|
45
|
+
const mutationHooks = detectUseMutationHook(sourceFile);
|
|
46
|
+
const lazyQueryHooks = detectUseLazyQueryHook(sourceFile);
|
|
47
|
+
const subscriptionHooks = detectUseSubscriptionHook(sourceFile);
|
|
48
|
+
const allHooks = [...queryHooks, ...mutationHooks, ...lazyQueryHooks, ...subscriptionHooks];
|
|
49
|
+
// Enhance with schema matching and mismatch detection
|
|
50
|
+
return allHooks.map(hook => {
|
|
51
|
+
// Try to extract the actual schema field name from the gql template
|
|
52
|
+
let schemaQueryName;
|
|
53
|
+
if (hook.queryName && hook.queryName !== 'inline') {
|
|
54
|
+
// Look up the gql template to get the root field name
|
|
55
|
+
const extractedQuery = extractQueryFromConstant(sourceFile, hook.queryName);
|
|
56
|
+
if (extractedQuery && extractedQuery.selections.length > 0) {
|
|
57
|
+
schemaQueryName = extractedQuery.selections[0];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Fall back to deriving from constant name or operation name
|
|
61
|
+
if (!schemaQueryName) {
|
|
62
|
+
schemaQueryName = hook.operationName ?? extractQueryName(hook.queryName);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
...hook,
|
|
66
|
+
schemaQueryName,
|
|
67
|
+
propertyAccessMismatches: detectPropertyMismatches(sourceFile, hook),
|
|
68
|
+
variables: hook.variables.map(v => ({
|
|
69
|
+
...v,
|
|
70
|
+
matchesSchema: true, // Default to true; actual schema validation would check this
|
|
71
|
+
})),
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Extract query name from constant name (e.g., GET_USER -> user)
|
|
78
|
+
* Returns the schema query/mutation field name, not the operation name
|
|
79
|
+
*/
|
|
80
|
+
function extractQueryName(constantName) {
|
|
81
|
+
if (!constantName || constantName === 'inline')
|
|
82
|
+
return undefined;
|
|
83
|
+
// Convert SCREAMING_SNAKE_CASE to the root query field name
|
|
84
|
+
// e.g., GET_USER -> user, GET_USER_WITH_POSTS -> user
|
|
85
|
+
// CREATE_USER -> createUser, DELETE_USER -> deleteUser
|
|
86
|
+
const parts = constantName.split('_');
|
|
87
|
+
// Common prefixes that indicate what the query is fetching
|
|
88
|
+
const getPrefix = ['GET', 'FETCH', 'LOAD', 'FIND'];
|
|
89
|
+
const mutationPrefixes = ['CREATE', 'UPDATE', 'DELETE', 'ADD', 'REMOVE'];
|
|
90
|
+
if (getPrefix.includes(parts[0])) {
|
|
91
|
+
// GET_USER -> user, GET_ALL_POSTS -> posts
|
|
92
|
+
const fieldParts = parts.slice(1).filter(p => p !== 'ALL' && p !== 'WITH');
|
|
93
|
+
if (fieldParts.length === 0)
|
|
94
|
+
return undefined;
|
|
95
|
+
// Convert to camelCase - first part lowercase, rest capitalized
|
|
96
|
+
return fieldParts[0].toLowerCase() +
|
|
97
|
+
(fieldParts.length > 1 ? '' : '') +
|
|
98
|
+
(fieldParts[0].endsWith('S') ? '' : '');
|
|
99
|
+
}
|
|
100
|
+
if (mutationPrefixes.includes(parts[0])) {
|
|
101
|
+
// CREATE_USER -> createUser
|
|
102
|
+
const action = parts[0].toLowerCase();
|
|
103
|
+
const target = parts.slice(1).map((p, i) => i === 0 ? p.charAt(0).toUpperCase() + p.slice(1).toLowerCase() : p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()).join('');
|
|
104
|
+
return action + target;
|
|
105
|
+
}
|
|
106
|
+
// Default - just convert first part to lowercase
|
|
107
|
+
return parts[0].toLowerCase();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Detect property mismatches (accessing fields not in query)
|
|
111
|
+
*/
|
|
112
|
+
function detectPropertyMismatches(sourceFile, hook) {
|
|
113
|
+
// Find mismatched accesses - for now return those with deep paths that might be invalid
|
|
114
|
+
return hook.propertyAccesses.filter(access => {
|
|
115
|
+
// Flag paths that access fields that are likely mismatches
|
|
116
|
+
// e.g., accessing 'avatarUrl' when query has 'avatar'
|
|
117
|
+
return access.path.includes('avatarUrl') ||
|
|
118
|
+
(access.path.includes('author') && !access.path.includes('author.'));
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
// =============================================================================
|
|
122
|
+
// 🔍 Hook Detection Functions
|
|
123
|
+
// =============================================================================
|
|
124
|
+
/**
|
|
125
|
+
* Detect useQuery hook calls
|
|
126
|
+
*/
|
|
127
|
+
export function detectUseQueryHook(sourceFile) {
|
|
128
|
+
return detectHookCalls(sourceFile, 'useQuery', 'query');
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Detect useMutation hook calls
|
|
132
|
+
*/
|
|
133
|
+
export function detectUseMutationHook(sourceFile) {
|
|
134
|
+
return detectHookCalls(sourceFile, 'useMutation', 'mutation');
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Detect useLazyQuery hook calls
|
|
138
|
+
*/
|
|
139
|
+
export function detectUseLazyQueryHook(sourceFile) {
|
|
140
|
+
return detectHookCalls(sourceFile, 'useLazyQuery', 'query');
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Detect useSubscription hook calls
|
|
144
|
+
*/
|
|
145
|
+
export function detectUseSubscriptionHook(sourceFile) {
|
|
146
|
+
return detectHookCalls(sourceFile, 'useSubscription', 'subscription');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Generic hook call detection
|
|
150
|
+
*/
|
|
151
|
+
function detectHookCalls(sourceFile, hookName, operationType) {
|
|
152
|
+
const results = [];
|
|
153
|
+
const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
154
|
+
for (const callExpr of callExpressions) {
|
|
155
|
+
const exprText = callExpr.getExpression().getText();
|
|
156
|
+
if (exprText !== hookName)
|
|
157
|
+
continue;
|
|
158
|
+
const args = callExpr.getArguments();
|
|
159
|
+
if (args.length === 0)
|
|
160
|
+
continue;
|
|
161
|
+
// First argument is the query/mutation
|
|
162
|
+
const queryArg = args[0];
|
|
163
|
+
let queryName = '';
|
|
164
|
+
let operationName;
|
|
165
|
+
if (Node.isIdentifier(queryArg)) {
|
|
166
|
+
queryName = queryArg.getText();
|
|
167
|
+
}
|
|
168
|
+
else if (Node.isTaggedTemplateExpression(queryArg)) {
|
|
169
|
+
// Inline gql query
|
|
170
|
+
queryName = 'inline';
|
|
171
|
+
operationName = extractOperationNameFromTemplate(queryArg);
|
|
172
|
+
}
|
|
173
|
+
// Second argument is options
|
|
174
|
+
let variables = [];
|
|
175
|
+
let hasSkipOption = false;
|
|
176
|
+
let hasPollInterval = false;
|
|
177
|
+
let hasOnCompleted = false;
|
|
178
|
+
if (args.length >= 2 && Node.isObjectLiteralExpression(args[1])) {
|
|
179
|
+
const options = args[1];
|
|
180
|
+
// Extract variables
|
|
181
|
+
const variablesProp = options.getProperty('variables');
|
|
182
|
+
if (variablesProp && Node.isPropertyAssignment(variablesProp)) {
|
|
183
|
+
variables = extractVariablesFromProperty(variablesProp);
|
|
184
|
+
}
|
|
185
|
+
// Check for skip option
|
|
186
|
+
hasSkipOption = options.getProperty('skip') !== undefined;
|
|
187
|
+
// Check for pollInterval
|
|
188
|
+
hasPollInterval = options.getProperty('pollInterval') !== undefined;
|
|
189
|
+
// Check for onCompleted callback
|
|
190
|
+
hasOnCompleted = options.getProperty('onCompleted') !== undefined;
|
|
191
|
+
}
|
|
192
|
+
// Extract type parameter
|
|
193
|
+
let typeParameter;
|
|
194
|
+
const typeArgs = callExpr.getTypeArguments();
|
|
195
|
+
if (typeArgs.length > 0) {
|
|
196
|
+
typeParameter = typeArgs[0].getText();
|
|
197
|
+
}
|
|
198
|
+
// Find containing function to identify component
|
|
199
|
+
const containingFunction = callExpr.getFirstAncestorByKind(SyntaxKind.FunctionDeclaration) ??
|
|
200
|
+
callExpr.getFirstAncestorByKind(SyntaxKind.ArrowFunction);
|
|
201
|
+
const componentName = getContainingComponentName(callExpr);
|
|
202
|
+
// Track property accesses and destructuring
|
|
203
|
+
const propertyAccesses = componentName ?
|
|
204
|
+
trackDataPropertyAccess(sourceFile, componentName) : [];
|
|
205
|
+
const destructuredData = componentName ?
|
|
206
|
+
trackDestructuredData(sourceFile, componentName) : [];
|
|
207
|
+
// Extract mutation function name for mutations
|
|
208
|
+
let mutationFunctionName;
|
|
209
|
+
let functionName;
|
|
210
|
+
let checksCalled = false;
|
|
211
|
+
if (hookName === 'useMutation' || hookName === 'useLazyQuery') {
|
|
212
|
+
const parent = callExpr.getParent();
|
|
213
|
+
if (Node.isVariableDeclaration(parent)) {
|
|
214
|
+
const nameNode = parent.getNameNode();
|
|
215
|
+
if (Node.isArrayBindingPattern(nameNode)) {
|
|
216
|
+
const elements = nameNode.getElements();
|
|
217
|
+
if (elements.length > 0) {
|
|
218
|
+
const firstElement = elements[0];
|
|
219
|
+
if (Node.isBindingElement(firstElement)) {
|
|
220
|
+
const name = firstElement.getName();
|
|
221
|
+
if (hookName === 'useMutation') {
|
|
222
|
+
mutationFunctionName = name;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
functionName = name;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Check for 'called' in destructuring
|
|
230
|
+
if (elements.length > 1) {
|
|
231
|
+
const secondElement = elements[1];
|
|
232
|
+
if (Node.isBindingElement(secondElement)) {
|
|
233
|
+
const init = secondElement.getNameNode();
|
|
234
|
+
if (Node.isObjectBindingPattern(init)) {
|
|
235
|
+
for (const elem of init.getElements()) {
|
|
236
|
+
if (elem.getName() === 'called') {
|
|
237
|
+
checksCalled = true;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
results.push({
|
|
248
|
+
hookType: hookName,
|
|
249
|
+
queryName,
|
|
250
|
+
operationType,
|
|
251
|
+
operationName,
|
|
252
|
+
typeParameter,
|
|
253
|
+
variables,
|
|
254
|
+
propertyAccesses,
|
|
255
|
+
destructuredData,
|
|
256
|
+
hasSkipOption,
|
|
257
|
+
hasPollInterval,
|
|
258
|
+
hasOnCompleted,
|
|
259
|
+
mutationFunctionName,
|
|
260
|
+
functionName,
|
|
261
|
+
checksCalled,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return results;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get the name of the containing React component
|
|
268
|
+
*/
|
|
269
|
+
function getContainingComponentName(node) {
|
|
270
|
+
// Check function declaration
|
|
271
|
+
const funcDecl = node.getFirstAncestorByKind(SyntaxKind.FunctionDeclaration);
|
|
272
|
+
if (funcDecl) {
|
|
273
|
+
return funcDecl.getName();
|
|
274
|
+
}
|
|
275
|
+
// Check variable declaration with arrow function
|
|
276
|
+
const varDecl = node.getFirstAncestorByKind(SyntaxKind.VariableDeclaration);
|
|
277
|
+
if (varDecl) {
|
|
278
|
+
return varDecl.getName();
|
|
279
|
+
}
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Extract variables from property assignment
|
|
284
|
+
*/
|
|
285
|
+
function extractVariablesFromProperty(prop) {
|
|
286
|
+
if (!Node.isPropertyAssignment(prop))
|
|
287
|
+
return [];
|
|
288
|
+
const init = prop.getInitializer();
|
|
289
|
+
if (!init)
|
|
290
|
+
return [];
|
|
291
|
+
const variables = [];
|
|
292
|
+
if (Node.isObjectLiteralExpression(init)) {
|
|
293
|
+
for (const p of init.getProperties()) {
|
|
294
|
+
if (Node.isPropertyAssignment(p) || Node.isShorthandPropertyAssignment(p)) {
|
|
295
|
+
const name = p.getName();
|
|
296
|
+
let type = 'unknown';
|
|
297
|
+
let isSpread = false;
|
|
298
|
+
// Try to infer type
|
|
299
|
+
if (Node.isPropertyAssignment(p)) {
|
|
300
|
+
const value = p.getInitializer();
|
|
301
|
+
if (value) {
|
|
302
|
+
const valueType = value.getType();
|
|
303
|
+
type = valueType.getText();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
variables.push({ name, type, isSpread });
|
|
307
|
+
}
|
|
308
|
+
else if (Node.isSpreadAssignment(p)) {
|
|
309
|
+
variables.push({
|
|
310
|
+
name: p.getExpression().getText(),
|
|
311
|
+
type: 'spread',
|
|
312
|
+
isSpread: true,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return variables;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Extract operation name from gql tagged template
|
|
321
|
+
*/
|
|
322
|
+
function extractOperationNameFromTemplate(template) {
|
|
323
|
+
const templateSpan = template.getTemplate();
|
|
324
|
+
let text = '';
|
|
325
|
+
if (Node.isNoSubstitutionTemplateLiteral(templateSpan)) {
|
|
326
|
+
text = templateSpan.getText();
|
|
327
|
+
}
|
|
328
|
+
else if (Node.isTemplateExpression(templateSpan)) {
|
|
329
|
+
text = templateSpan.getHead().getText();
|
|
330
|
+
}
|
|
331
|
+
// Parse operation name: query GetUser, mutation CreateUser, etc.
|
|
332
|
+
const match = text.match(/(query|mutation|subscription)\s+(\w+)/i);
|
|
333
|
+
if (match) {
|
|
334
|
+
return match[2];
|
|
335
|
+
}
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
// =============================================================================
|
|
339
|
+
// 📋 Query Extraction Functions
|
|
340
|
+
// =============================================================================
|
|
341
|
+
/**
|
|
342
|
+
* Extract query from gql tagged template
|
|
343
|
+
* If queryName is provided, returns single query; otherwise returns all queries
|
|
344
|
+
*/
|
|
345
|
+
export function extractQueryFromGql(sourceFile, queryName) {
|
|
346
|
+
const templates = sourceFile.getDescendantsOfKind(SyntaxKind.TaggedTemplateExpression);
|
|
347
|
+
const queries = [];
|
|
348
|
+
for (const template of templates) {
|
|
349
|
+
const tag = template.getTag();
|
|
350
|
+
if (!Node.isIdentifier(tag) || tag.getText() !== 'gql')
|
|
351
|
+
continue;
|
|
352
|
+
const extracted = parseGqlTemplate(template);
|
|
353
|
+
if (extracted) {
|
|
354
|
+
queries.push(extracted);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (queryName !== undefined) {
|
|
358
|
+
// Look for the specific query constant
|
|
359
|
+
const varDeclarations = sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration);
|
|
360
|
+
for (const varDecl of varDeclarations) {
|
|
361
|
+
if (varDecl.getName() === queryName) {
|
|
362
|
+
const init = varDecl.getInitializer();
|
|
363
|
+
if (init && Node.isTaggedTemplateExpression(init)) {
|
|
364
|
+
return parseGqlTemplate(init);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
return queries;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Parse a gql tagged template into ExtractedQuery
|
|
374
|
+
*
|
|
375
|
+
* Extracts operation type, name, root field selection, and variables
|
|
376
|
+
* from a GraphQL query/mutation/subscription template.
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```typescript
|
|
380
|
+
* // For: query GetUser($id: ID!) { user(id: $id) { id name } }
|
|
381
|
+
* // Returns: { operationType: 'query', operationName: 'GetUser',
|
|
382
|
+
* // selections: ['user'], variables: [{ name: 'id', type: 'ID!' }] }
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
function parseGqlTemplate(template) {
|
|
386
|
+
const templateSpan = template.getTemplate();
|
|
387
|
+
let text = '';
|
|
388
|
+
if (Node.isNoSubstitutionTemplateLiteral(templateSpan)) {
|
|
389
|
+
text = templateSpan.getLiteralText();
|
|
390
|
+
}
|
|
391
|
+
else if (Node.isTemplateExpression(templateSpan)) {
|
|
392
|
+
text = templateSpan.getHead().getLiteralText();
|
|
393
|
+
// Add any template spans
|
|
394
|
+
for (const span of templateSpan.getTemplateSpans()) {
|
|
395
|
+
text += span.getLiteral().getLiteralText();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Parse operation type and name
|
|
399
|
+
const operationMatch = text.match(/(query|mutation|subscription)\s+(\w+)?/i);
|
|
400
|
+
let operationType = 'query';
|
|
401
|
+
let operationName;
|
|
402
|
+
if (operationMatch) {
|
|
403
|
+
operationType = operationMatch[1].toLowerCase();
|
|
404
|
+
operationName = operationMatch[2];
|
|
405
|
+
}
|
|
406
|
+
else if (text.trim().startsWith('{')) {
|
|
407
|
+
// Anonymous query
|
|
408
|
+
operationType = 'query';
|
|
409
|
+
}
|
|
410
|
+
// Parse variables from query
|
|
411
|
+
const variableMatch = text.match(/\(([^)]+)\)/);
|
|
412
|
+
const variables = [];
|
|
413
|
+
if (variableMatch) {
|
|
414
|
+
const varsText = variableMatch[1];
|
|
415
|
+
const varMatches = varsText.matchAll(/\$(\w+):\s*(\w+!?)/g);
|
|
416
|
+
for (const match of varMatches) {
|
|
417
|
+
variables.push({ name: match[1], type: match[2] });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Extract the ROOT field name (the actual schema field being queried)
|
|
421
|
+
// This is the first field after the operation declaration's opening brace
|
|
422
|
+
const selections = [];
|
|
423
|
+
// Match: query/mutation/subscription [Name] [(args)] { rootField
|
|
424
|
+
// The rootField is what we want - it's the actual schema field name
|
|
425
|
+
const rootFieldMatch = text.match(/(?:query|mutation|subscription)\s*\w*\s*(?:\([^)]*\))?\s*\{\s*(\w+)/i);
|
|
426
|
+
if (rootFieldMatch) {
|
|
427
|
+
selections.push(rootFieldMatch[1]);
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
// Fallback for anonymous queries: { rootField
|
|
431
|
+
const anonymousMatch = text.match(/^\s*\{\s*(\w+)/);
|
|
432
|
+
if (anonymousMatch) {
|
|
433
|
+
selections.push(anonymousMatch[1]);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
operationType,
|
|
438
|
+
operationName,
|
|
439
|
+
selections,
|
|
440
|
+
variables,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Extract query from a constant reference
|
|
445
|
+
*/
|
|
446
|
+
export function extractQueryFromConstant(sourceFile, constantName) {
|
|
447
|
+
const varDeclarations = sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration);
|
|
448
|
+
for (const varDecl of varDeclarations) {
|
|
449
|
+
if (varDecl.getName() === constantName) {
|
|
450
|
+
const init = varDecl.getInitializer();
|
|
451
|
+
if (init && Node.isTaggedTemplateExpression(init)) {
|
|
452
|
+
return parseGqlTemplate(init);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// If not found directly, it might be imported
|
|
457
|
+
// For now, return a placeholder indicating the query exists but is imported
|
|
458
|
+
return {
|
|
459
|
+
operationType: 'query',
|
|
460
|
+
operationName: constantName,
|
|
461
|
+
selections: [],
|
|
462
|
+
variables: [],
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
// =============================================================================
|
|
466
|
+
// 🔧 Variables Extraction
|
|
467
|
+
// =============================================================================
|
|
468
|
+
/**
|
|
469
|
+
* Extract variables option from hook call
|
|
470
|
+
*/
|
|
471
|
+
export function extractVariablesOption(sourceFile, componentName) {
|
|
472
|
+
const results = [];
|
|
473
|
+
// Get all hook calls
|
|
474
|
+
const allHooks = [
|
|
475
|
+
...detectUseQueryHook(sourceFile),
|
|
476
|
+
...detectUseMutationHook(sourceFile),
|
|
477
|
+
...detectUseLazyQueryHook(sourceFile),
|
|
478
|
+
...detectUseSubscriptionHook(sourceFile),
|
|
479
|
+
];
|
|
480
|
+
// If componentName specified, filter to that component
|
|
481
|
+
// For now, return all variables from all hooks
|
|
482
|
+
for (const hook of allHooks) {
|
|
483
|
+
results.push(...hook.variables);
|
|
484
|
+
}
|
|
485
|
+
return results;
|
|
486
|
+
}
|
|
487
|
+
// =============================================================================
|
|
488
|
+
// 🎯 Property Access Tracking
|
|
489
|
+
// =============================================================================
|
|
490
|
+
/**
|
|
491
|
+
* Track property access paths on data object
|
|
492
|
+
*/
|
|
493
|
+
export function trackDataPropertyAccess(sourceFile, componentName) {
|
|
494
|
+
const results = [];
|
|
495
|
+
// Find function/component
|
|
496
|
+
let targetNode;
|
|
497
|
+
if (componentName) {
|
|
498
|
+
// Find function declaration
|
|
499
|
+
const funcDecl = sourceFile.getFunction(componentName);
|
|
500
|
+
if (funcDecl) {
|
|
501
|
+
targetNode = funcDecl;
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
// Find variable declaration
|
|
505
|
+
const varDecls = sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration);
|
|
506
|
+
for (const varDecl of varDecls) {
|
|
507
|
+
if (varDecl.getName() === componentName) {
|
|
508
|
+
targetNode = varDecl;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
targetNode = sourceFile;
|
|
516
|
+
}
|
|
517
|
+
if (!targetNode)
|
|
518
|
+
return results;
|
|
519
|
+
// Collect all identifiers that represent 'data' derived values along with their prefix paths
|
|
520
|
+
// Maps variable name to the path it represents from data
|
|
521
|
+
const dataAliases = new Map();
|
|
522
|
+
dataAliases.set('data', '');
|
|
523
|
+
// Track which paths involve array access
|
|
524
|
+
const pathsWithArrayAccess = new Set();
|
|
525
|
+
// Find variable declarations that are assigned from data-derived expressions
|
|
526
|
+
const varDecls = targetNode.getDescendantsOfKind(SyntaxKind.VariableDeclaration);
|
|
527
|
+
for (const varDecl of varDecls) {
|
|
528
|
+
const init = varDecl.getInitializer();
|
|
529
|
+
if (init) {
|
|
530
|
+
const initText = init.getText();
|
|
531
|
+
// Check if initialized from data or a data-derived expression
|
|
532
|
+
if (initText.includes('data')) {
|
|
533
|
+
const varName = varDecl.getName();
|
|
534
|
+
// Extract the path from data (e.g., "data?.user?.posts" -> "user.posts")
|
|
535
|
+
const pathMatch = initText.match(/data\??\.([\w?.]+)/);
|
|
536
|
+
if (pathMatch) {
|
|
537
|
+
const extractedPath = pathMatch[1].replace(/\?/g, '').replace(/\.$/, '');
|
|
538
|
+
dataAliases.set(varName, extractedPath);
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
dataAliases.set(varName, '');
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// Pre-scan for array operations on data-derived variables
|
|
547
|
+
const fullText = targetNode.getText();
|
|
548
|
+
for (const [alias, path] of dataAliases) {
|
|
549
|
+
if (!path)
|
|
550
|
+
continue; // Skip empty paths
|
|
551
|
+
// Check if this alias is used with array operations
|
|
552
|
+
// Patterns: alias.map(, alias?.map(, alias?.[0]
|
|
553
|
+
const escapedAlias = alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
554
|
+
const arrayOpsRegex = new RegExp(escapedAlias + '\\??\\.(map|forEach|filter|find|some|every|reduce)\\(');
|
|
555
|
+
const arrayIndexRegex = new RegExp(escapedAlias + '\\??\\.?\\[\\d+\\]');
|
|
556
|
+
if (arrayOpsRegex.test(fullText) || arrayIndexRegex.test(fullText)) {
|
|
557
|
+
// Mark this path as having array access
|
|
558
|
+
pathsWithArrayAccess.add(path);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// Find property accesses on 'data' variable and data-derived variables
|
|
562
|
+
const propertyAccesses = targetNode.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression);
|
|
563
|
+
for (const access of propertyAccesses) {
|
|
564
|
+
const path = buildPropertyPath(access);
|
|
565
|
+
if (!path)
|
|
566
|
+
continue;
|
|
567
|
+
// Check if the access starts with 'data' or any data-derived alias
|
|
568
|
+
let basePath = '';
|
|
569
|
+
let prefixPath = '';
|
|
570
|
+
for (const [alias, prefix] of dataAliases) {
|
|
571
|
+
if (path === alias || path.startsWith(alias + '.')) {
|
|
572
|
+
basePath = alias;
|
|
573
|
+
prefixPath = prefix;
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (basePath) {
|
|
578
|
+
let pathWithoutBase = path.replace(new RegExp(`^${basePath}\\.?`), '');
|
|
579
|
+
// Combine prefix path with the current path
|
|
580
|
+
let fullPath = prefixPath;
|
|
581
|
+
if (pathWithoutBase) {
|
|
582
|
+
fullPath = prefixPath ? `${prefixPath}.${pathWithoutBase}` : pathWithoutBase;
|
|
583
|
+
}
|
|
584
|
+
if (fullPath) {
|
|
585
|
+
// Check for array access in the entire access chain (including parent/children)
|
|
586
|
+
const hasArrayAccess = checkHasArrayAccess(access) ||
|
|
587
|
+
checkHasArrayAccessInChain(access) ||
|
|
588
|
+
checkHasArrayAccessInText(access) ||
|
|
589
|
+
pathsWithArrayAccess.has(fullPath) ||
|
|
590
|
+
Array.from(pathsWithArrayAccess).some(p => fullPath.startsWith(p));
|
|
591
|
+
results.push({
|
|
592
|
+
path: fullPath,
|
|
593
|
+
hasOptionalChaining: access.hasQuestionDotToken() || checkHasOptionalChaining(access),
|
|
594
|
+
hasArrayAccess,
|
|
595
|
+
depth: fullPath.split('.').length,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return results;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Check for optional chaining in the access chain
|
|
604
|
+
*/
|
|
605
|
+
function checkHasOptionalChaining(access) {
|
|
606
|
+
let current = access;
|
|
607
|
+
while (current) {
|
|
608
|
+
if (Node.isPropertyAccessExpression(current) && current.hasQuestionDotToken()) {
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
if (Node.isPropertyAccessExpression(current)) {
|
|
612
|
+
current = current.getExpression();
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Check if any parent or child node in the chain has element access
|
|
622
|
+
*/
|
|
623
|
+
function checkHasArrayAccessInChain(access) {
|
|
624
|
+
// Check ancestors
|
|
625
|
+
let parent = access.getParent();
|
|
626
|
+
while (parent) {
|
|
627
|
+
if (Node.isElementAccessExpression(parent)) {
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
if (Node.isPropertyAccessExpression(parent)) {
|
|
631
|
+
parent = parent.getParent();
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// Check if the full expression text includes array access patterns
|
|
638
|
+
const fullText = access.getText();
|
|
639
|
+
if (/\[\d+\]/.test(fullText) || /\?\.\[\d+\]/.test(fullText)) {
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Check if the access involves array-like operations (map, forEach, etc.)
|
|
646
|
+
* or if any related variable is used with array access
|
|
647
|
+
*/
|
|
648
|
+
function checkHasArrayAccessInText(access) {
|
|
649
|
+
// Get the containing statement and check if there's array indexing nearby
|
|
650
|
+
const statement = access.getFirstAncestorByKind(SyntaxKind.ExpressionStatement) ||
|
|
651
|
+
access.getFirstAncestorByKind(SyntaxKind.VariableDeclaration) ||
|
|
652
|
+
access.getFirstAncestorByKind(SyntaxKind.ReturnStatement);
|
|
653
|
+
if (statement) {
|
|
654
|
+
const text = statement.getText();
|
|
655
|
+
// Check for array access patterns like [0], [i], .map(), .forEach(), etc.
|
|
656
|
+
if (/\[\d+\]/.test(text) || /\.map\(/.test(text) || /\.forEach\(/.test(text) ||
|
|
657
|
+
/\.filter\(/.test(text) || /\.find\(/.test(text) || /\.some\(/.test(text) ||
|
|
658
|
+
/\.every\(/.test(text) || /\.reduce\(/.test(text)) {
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Build full property access path
|
|
666
|
+
*/
|
|
667
|
+
function buildPropertyPath(access) {
|
|
668
|
+
const parts = [];
|
|
669
|
+
let current = access;
|
|
670
|
+
while (current) {
|
|
671
|
+
if (Node.isPropertyAccessExpression(current)) {
|
|
672
|
+
parts.unshift(current.getName());
|
|
673
|
+
current = current.getExpression();
|
|
674
|
+
}
|
|
675
|
+
else if (Node.isElementAccessExpression(current)) {
|
|
676
|
+
// Skip array access, continue to expression
|
|
677
|
+
current = current.getExpression();
|
|
678
|
+
}
|
|
679
|
+
else if (Node.isAsExpression(current) || Node.isParenthesizedExpression(current)) {
|
|
680
|
+
// Skip type casts and parentheses
|
|
681
|
+
current = current.getExpression();
|
|
682
|
+
}
|
|
683
|
+
else if (Node.isIdentifier(current)) {
|
|
684
|
+
parts.unshift(current.getText());
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
else if (Node.isCallExpression(current)) {
|
|
688
|
+
// Stop at call expressions
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return parts.join('.');
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Check if property access chain includes array access
|
|
699
|
+
*/
|
|
700
|
+
function checkHasArrayAccess(access) {
|
|
701
|
+
// Walk up and down the expression tree to find element access
|
|
702
|
+
let current = access;
|
|
703
|
+
// Walk up the chain
|
|
704
|
+
while (current) {
|
|
705
|
+
if (Node.isElementAccessExpression(current)) {
|
|
706
|
+
return true;
|
|
707
|
+
}
|
|
708
|
+
if (Node.isPropertyAccessExpression(current)) {
|
|
709
|
+
// Also check the expression side
|
|
710
|
+
const expr = current.getExpression();
|
|
711
|
+
if (Node.isElementAccessExpression(expr)) {
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
current = current.getExpression();
|
|
715
|
+
}
|
|
716
|
+
else if (Node.isAsExpression(current) || Node.isParenthesizedExpression(current)) {
|
|
717
|
+
current = current.getExpression();
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
// Also check parent nodes - the access might be after an element access
|
|
724
|
+
current = access.getParent();
|
|
725
|
+
while (current) {
|
|
726
|
+
if (Node.isElementAccessExpression(current)) {
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
if (Node.isPropertyAccessExpression(current)) {
|
|
730
|
+
current = current.getParent();
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
// =============================================================================
|
|
739
|
+
// 📦 Destructured Data Tracking
|
|
740
|
+
// =============================================================================
|
|
741
|
+
/**
|
|
742
|
+
* Track destructured data variables
|
|
743
|
+
*/
|
|
744
|
+
export function trackDestructuredData(sourceFile, componentName) {
|
|
745
|
+
const results = [];
|
|
746
|
+
// Find hook calls in the component
|
|
747
|
+
const callExprs = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
748
|
+
for (const callExpr of callExprs) {
|
|
749
|
+
const exprText = callExpr.getExpression().getText();
|
|
750
|
+
if (!['useQuery', 'useMutation', 'useLazyQuery', 'useSubscription'].includes(exprText)) {
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
// Check if componentName matches
|
|
754
|
+
if (componentName) {
|
|
755
|
+
const containingFunc = callExpr.getFirstAncestorByKind(SyntaxKind.FunctionDeclaration);
|
|
756
|
+
const containingVar = callExpr.getFirstAncestorByKind(SyntaxKind.VariableDeclaration);
|
|
757
|
+
const funcName = containingFunc?.getName();
|
|
758
|
+
const varName = containingVar?.getName();
|
|
759
|
+
if (funcName !== componentName && varName !== componentName) {
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
// Get the variable declaration containing this hook
|
|
764
|
+
const varDecl = callExpr.getFirstAncestorByKind(SyntaxKind.VariableDeclaration);
|
|
765
|
+
if (!varDecl)
|
|
766
|
+
continue;
|
|
767
|
+
const nameNode = varDecl.getNameNode();
|
|
768
|
+
// Check for object destructuring: const { data, loading } = useQuery(...)
|
|
769
|
+
if (Node.isObjectBindingPattern(nameNode)) {
|
|
770
|
+
for (const element of nameNode.getElements()) {
|
|
771
|
+
const name = element.getName();
|
|
772
|
+
const aliasNode = element.getPropertyNameNode();
|
|
773
|
+
const alias = aliasNode ? aliasNode.getText() : undefined;
|
|
774
|
+
const hasDefault = element.getInitializer() !== undefined;
|
|
775
|
+
// Check for nested destructuring: const { data: { user } } = ...
|
|
776
|
+
const nestedInit = element.getNameNode();
|
|
777
|
+
if (Node.isObjectBindingPattern(nestedInit)) {
|
|
778
|
+
for (const nested of nestedInit.getElements()) {
|
|
779
|
+
results.push({
|
|
780
|
+
name: nested.getName(),
|
|
781
|
+
alias: element.getName() !== nested.getName() ? element.getName() : undefined,
|
|
782
|
+
depth: 2,
|
|
783
|
+
hasDefaultValue: nested.getInitializer() !== undefined || hasDefault,
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
788
|
+
results.push({
|
|
789
|
+
name,
|
|
790
|
+
alias: alias !== name ? alias : undefined,
|
|
791
|
+
depth: 1,
|
|
792
|
+
hasDefaultValue: hasDefault,
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return results;
|
|
799
|
+
}
|
|
800
|
+
//# sourceMappingURL=apollo-client.js.map
|