mirror of
https://github.com/morluto/rea
synced 2026-10-28 23:11:11 -04:00
feat(javascript): recognize tRPC procedures and GraphQL operations (#1670)
* feat(javascript): recognize tRPC procedures and GraphQL operations Record RPC client syntax as network endpoint candidates in both analyze_javascript_application and analyze_web_bundle, distinguished by mechanism: - trpc:procedure:<method> keeps the static member path of root.<path>.<method>(...), which tRPC proxies preserve through identifier minification. React hooks and TanStack option factories are recognized directly. Vanilla query/mutate need a root bound to createTRPCClient/createTRPCProxyClient, or a tRPC source marker with a router path of at least two segments; subscribe needs the marker and a tRPC observer argument. `this` roots, browser globals and RTK Query `endpoints` chains are never procedures; cache-key helpers are excluded. - trpc🔗<link> records the literal url of documented HTTP links. - graphql:<type> records named operations from string and cooked template literals with a valid operation header, and from precompiled OperationDefinition objects; a graphql-tag loc.source.body copy is not reported twice. Method, callback and factory names follow @trpc/client and React/TanStack integration sources and documentation for 11.19. Carrier segments before the router path, useUtils helpers and minifier-renamed links remain documented limitations. Validation: typecheck; domain tests for recognition and false positives (Apollo, RxJS, stores, RTK Query, browser globals, prose); web bundle analyzer test; end-to-end analysis of a minified fixture through the built MCP server on Windows 11. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(javascript): preserve RPC binding identity and bundled calls * style(reference-source): fix CI formatting --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
@@ -59,6 +59,8 @@ import {
|
||||
type ParsedJavaScriptSource,
|
||||
} from "./javascriptSourceParser.js";
|
||||
|
||||
import { isJavaScriptRpcClientFactory } from "./javascriptRpcEndpoints.js";
|
||||
|
||||
interface BindPatternInput {
|
||||
readonly pattern: t.Node;
|
||||
readonly initializer: t.Node | null;
|
||||
@@ -181,6 +183,40 @@ export function* analyzeParsedJavaScriptSemanticsSteps(
|
||||
};
|
||||
}
|
||||
|
||||
/** Resolve tRPC client roots by lexical identity, excluding overwritten bindings. */
|
||||
export function* classifyParsedJavaScriptRpcClientRootsSteps(
|
||||
file: ParsedJavaScriptSource,
|
||||
): Generator<void, ReadonlySet<t.Node>> {
|
||||
const state = createState(file.program);
|
||||
yield* collectDefinitionsSteps(file.program, state);
|
||||
const roots = new Set<t.Node>();
|
||||
const clients = new Map<string, boolean>();
|
||||
yield* traverseJavaScriptAstSteps(file.program, {
|
||||
enter: (node) => {
|
||||
if (!t.isMemberExpression(node) && !t.isOptionalMemberExpression(node))
|
||||
return;
|
||||
const root = node.object;
|
||||
if (!t.isIdentifier(root)) return;
|
||||
const binding = resolveSemanticBindingState(state, root, root.name);
|
||||
if (binding === undefined) return;
|
||||
let client = clients.get(binding.bindingId);
|
||||
if (client === undefined) {
|
||||
const [initializer] = binding.initializers;
|
||||
client =
|
||||
initializer !== undefined &&
|
||||
initializer.projection.length === 0 &&
|
||||
binding.initializers.length === 1 &&
|
||||
!binding.definitions.some(({ kind }) => kind === "assignment") &&
|
||||
!state.conditionalInitializers.has(initializer.node) &&
|
||||
isJavaScriptRpcClientFactory(initializer.node);
|
||||
clients.set(binding.bindingId, client);
|
||||
}
|
||||
if (client) roots.add(root);
|
||||
},
|
||||
});
|
||||
return roots;
|
||||
}
|
||||
|
||||
/** Receiver identity facts for the overloaded `.open` syntax only. */
|
||||
export type JavaScriptOpenReceiverFact =
|
||||
| "window"
|
||||
|
||||
Reference in New Issue
Block a user