Skip to content

Commit 963575c

Browse files
authored
Feat/refactor closure transformer (commontoolsinc#2109)
* fixes for type registry handling in synthetic derive calls * fix: check typeRegistry when inferring map element types Synthetic derive calls from ComputedTransformer now have proper type information, fixing map element type inference for filter chains. - inferArrayElementType checks typeRegistry for synthetic nodes - ComputedTransformer registers types after visiting children - Fixes filter-map-chain and method-chains tests * phase 3 finished - recipe builder and schema factory * Address cubic review bot's comments * Rebase onto main: Port schema injection logic to strategies and update fixtures
1 parent a9a4bda commit 963575c

37 files changed

+2587
-2265
lines changed

packages/ts-transformers/src/closures/capture-collector.ts

Lines changed: 430 additions & 0 deletions
Large diffs are not rendered by default.

packages/ts-transformers/src/closures/strategies/derive-strategy.ts

Lines changed: 424 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import ts from "typescript";
2+
import type { TransformationContext } from "../../core/mod.ts";
3+
import type { ClosureTransformationStrategy } from "./strategy.ts";
4+
import { isEventHandlerJsxAttribute } from "../../ast/mod.ts";
5+
import { CaptureCollector } from "../capture-collector.ts";
6+
import { unwrapArrowFunction } from "../utils/ast-helpers.ts";
7+
import { buildCapturePropertyAssignments } from "./map-strategy.ts";
8+
import { RecipeBuilder } from "../utils/recipe-builder.ts";
9+
import { SchemaFactory } from "../utils/schema-factory.ts";
10+
11+
export class HandlerStrategy implements ClosureTransformationStrategy {
12+
canTransform(
13+
node: ts.Node,
14+
_context: TransformationContext,
15+
): boolean {
16+
if (ts.isJsxAttribute(node)) {
17+
return isEventHandlerJsxAttribute(node.name);
18+
}
19+
return false;
20+
}
21+
22+
transform(
23+
node: ts.Node,
24+
context: TransformationContext,
25+
visitor: ts.Visitor,
26+
): ts.Node | undefined {
27+
if (ts.isJsxAttribute(node)) {
28+
return transformHandlerJsxAttribute(node, context, visitor);
29+
}
30+
return undefined;
31+
}
32+
}
33+
34+
export function transformHandlerJsxAttribute(
35+
attribute: ts.JsxAttribute,
36+
context: TransformationContext,
37+
visitor: ts.Visitor,
38+
): ts.JsxAttribute | undefined {
39+
const initializer = attribute.initializer;
40+
if (!initializer || !ts.isJsxExpression(initializer)) {
41+
return undefined;
42+
}
43+
44+
const expression = initializer.expression;
45+
if (!expression) {
46+
return undefined;
47+
}
48+
49+
const callback = unwrapArrowFunction(expression);
50+
if (!callback) {
51+
return undefined;
52+
}
53+
54+
const transformedBody = ts.visitNode(
55+
callback.body,
56+
visitor,
57+
) as ts.ConciseBody;
58+
59+
const collector = new CaptureCollector(context.checker);
60+
const { captureTree } = collector.analyze(callback);
61+
62+
// Initialize RecipeBuilder
63+
const builder = new RecipeBuilder(context);
64+
builder.setCaptureTree(captureTree);
65+
66+
// Register capture names as used to avoid collision with event parameter
67+
// MOVED: This logic is now handled inside RecipeBuilder.buildHandlerCallback
68+
// to prevent captures from colliding with themselves.
69+
70+
// Build the new callback using buildHandlerCallback
71+
const handlerCallback = builder.buildHandlerCallback(
72+
callback,
73+
transformedBody,
74+
"__ct_handler_event",
75+
"__ct_handler_params",
76+
);
77+
78+
const { factory } = context;
79+
80+
// Build type information for handler params using SchemaFactory
81+
const schemaFactory = new SchemaFactory(context);
82+
const eventTypeNode = schemaFactory.createHandlerEventSchema(callback);
83+
const stateTypeNode = schemaFactory.createHandlerStateSchema(
84+
captureTree,
85+
callback.parameters[1] as ts.ParameterDeclaration | undefined,
86+
);
87+
88+
const handlerExpr = context.ctHelpers.getHelperExpr("handler");
89+
const handlerCall = factory.createCallExpression(
90+
handlerExpr,
91+
[eventTypeNode, stateTypeNode],
92+
[handlerCallback],
93+
);
94+
95+
const paramProperties = buildCapturePropertyAssignments(captureTree, factory);
96+
97+
const paramsObject = factory.createObjectLiteralExpression(
98+
paramProperties,
99+
paramProperties.length > 0,
100+
);
101+
102+
const finalCall = factory.createCallExpression(
103+
handlerCall,
104+
undefined,
105+
[paramsObject],
106+
);
107+
108+
const newInitializer = factory.createJsxExpression(
109+
initializer.dotDotDotToken,
110+
finalCall,
111+
);
112+
113+
return factory.createJsxAttribute(attribute.name, newInitializer);
114+
}
115+
116+
/**
117+
* Transform explicit handler() calls.
118+
* This is less common than JSX attributes but supported.
119+
*/
120+
export function transformHandlerCall(
121+
_node: ts.CallExpression,
122+
_context: TransformationContext,
123+
_visitor: ts.Visitor,
124+
): ts.CallExpression | undefined {
125+
// Implementation for explicit handler calls
126+
// Currently the transformer only handles JSX attributes explicitly
127+
// But we can add support here if needed.
128+
// For now, return undefined as the original transformer didn't have a dedicated
129+
// transformHandlerCall function (it was handled via JSX attribute visitor).
130+
return undefined;
131+
}

0 commit comments

Comments
 (0)