Skip to content

Commit 0417d6e

Browse files
authored
Fix map closure destructured alias handling and add regression fixtures (#1973)
* Fix map closure destructured alias handling and add regression fixtures * Avoid recomputing destructured computed keys and restore numeric-key fixture coverage * fix(transform): preserve directive prologues when prepending computed initializers * fix: properly deduplicate captured variables with unique suffixes
1 parent b62e7c1 commit 0417d6e

File tree

3 files changed

+150
-24
lines changed

3 files changed

+150
-24
lines changed

packages/ts-transformers/src/closures/transformer.ts

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -906,11 +906,26 @@ function transformDestructuredProperties(
906906
if (statements.length === 0) return currentBody;
907907

908908
if (ts.isBlock(currentBody)) {
909+
// Find where directive prologues end (e.g., "use strict")
910+
// Directives must be string literal expression statements at the start
911+
let directiveEnd = 0;
912+
for (const stmt of currentBody.statements) {
913+
if (
914+
ts.isExpressionStatement(stmt) &&
915+
ts.isStringLiteral(stmt.expression)
916+
) {
917+
directiveEnd++;
918+
} else {
919+
break; // First non-directive = end of prologue
920+
}
921+
}
922+
909923
return factory.updateBlock(
910924
currentBody,
911925
factory.createNodeArray([
912-
...statements,
913-
...currentBody.statements,
926+
...currentBody.statements.slice(0, directiveEnd), // Keep directives first
927+
...statements, // Then our computed initializers
928+
...currentBody.statements.slice(directiveEnd), // Then rest of body
914929
]),
915930
);
916931
}
@@ -945,6 +960,31 @@ function transformDestructuredProperties(
945960
const propertyName = element.propertyName;
946961
usedTempNames.add(alias);
947962

963+
// For computed properties, create the temp variable upfront (not in the factory)
964+
// so multiple uses of the same property share one temp variable
965+
let computedTempIdentifier: ts.Identifier | undefined;
966+
if (propertyName && ts.isComputedPropertyName(propertyName)) {
967+
const tempName = registerTempName(alias);
968+
computedTempIdentifier = factory.createIdentifier(tempName);
969+
970+
computedInitializers.push(
971+
factory.createVariableStatement(
972+
undefined,
973+
factory.createVariableDeclarationList(
974+
[
975+
factory.createVariableDeclaration(
976+
computedTempIdentifier,
977+
undefined,
978+
undefined,
979+
propertyName.expression,
980+
),
981+
],
982+
ts.NodeFlags.Const,
983+
),
984+
),
985+
);
986+
}
987+
948988
destructuredProps.set(alias, () => {
949989
const target = factory.createIdentifier("element");
950990

@@ -969,30 +1009,12 @@ function transformDestructuredProperties(
9691009
return factory.createElementAccessExpression(target, propertyName);
9701010
}
9711011

972-
if (ts.isComputedPropertyName(propertyName)) {
973-
const tempName = registerTempName(alias);
974-
const tempIdentifier = factory.createIdentifier(tempName);
975-
976-
computedInitializers.push(
977-
factory.createVariableStatement(
978-
undefined,
979-
factory.createVariableDeclarationList(
980-
[
981-
factory.createVariableDeclaration(
982-
tempIdentifier,
983-
undefined,
984-
undefined,
985-
propertyName.expression,
986-
),
987-
],
988-
ts.NodeFlags.Const,
989-
),
990-
),
991-
);
992-
1012+
if (
1013+
ts.isComputedPropertyName(propertyName) && computedTempIdentifier
1014+
) {
9931015
return factory.createElementAccessExpression(
9941016
target,
995-
tempIdentifier,
1017+
computedTempIdentifier,
9961018
);
9971019
}
9981020

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import * as __ctHelpers from "commontools";
2+
import { recipe, UI } from "commontools";
3+
const dynamicKey = "value" as const;
4+
interface Item {
5+
value: number;
6+
other: number;
7+
}
8+
interface State {
9+
items: Item[];
10+
}
11+
export default recipe({
12+
$schema: "https://json-schema.org/draft/2020-12/schema",
13+
type: "object",
14+
properties: {
15+
items: {
16+
type: "array",
17+
items: {
18+
$ref: "#/$defs/Item"
19+
}
20+
}
21+
},
22+
required: ["items"],
23+
$defs: {
24+
Item: {
25+
type: "object",
26+
properties: {
27+
value: {
28+
type: "number"
29+
},
30+
other: {
31+
type: "number"
32+
}
33+
},
34+
required: ["value", "other"]
35+
}
36+
}
37+
} as const satisfies __ctHelpers.JSONSchema, (state) => {
38+
return {
39+
[UI]: (<div>
40+
{state.items.mapWithPattern(__ctHelpers.recipe({
41+
$schema: "https://json-schema.org/draft/2020-12/schema",
42+
type: "object",
43+
properties: {
44+
element: {
45+
$ref: "#/$defs/Item"
46+
},
47+
params: {
48+
type: "object",
49+
properties: {}
50+
}
51+
},
52+
required: ["element", "params"],
53+
$defs: {
54+
Item: {
55+
type: "object",
56+
properties: {
57+
value: {
58+
type: "number"
59+
},
60+
other: {
61+
type: "number"
62+
}
63+
},
64+
required: ["value", "other"]
65+
}
66+
}
67+
} as const satisfies __ctHelpers.JSONSchema, ({ element, params: {} }) => {
68+
"use strict";
69+
const __ct_val_key = dynamicKey;
70+
return <span key={__ctHelpers.derive({ element, __ct_val_key }, ({ element: element, __ct_val_key: __ct_val_key }) => element[__ct_val_key])}>{__ctHelpers.derive({ element, __ct_val_key }, ({ element: element, __ct_val_key: __ct_val_key }) => element[__ct_val_key] * 2)}</span>;
71+
}), {})}
72+
</div>),
73+
};
74+
});
75+
// @ts-ignore: Internals
76+
function h(...args: any[]) { return __ctHelpers.h.apply(null, args); }
77+
// @ts-ignore: Internals
78+
h.fragment = __ctHelpers.h.fragment;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// <cts-enable />
2+
import { recipe, UI } from "commontools";
3+
4+
const dynamicKey = "value" as const;
5+
6+
interface Item {
7+
value: number;
8+
other: number;
9+
}
10+
11+
interface State {
12+
items: Item[];
13+
}
14+
15+
export default recipe<State>("MapComputedAliasStrict", (state) => {
16+
return {
17+
[UI]: (
18+
<div>
19+
{state.items.map(({ [dynamicKey]: val }) => {
20+
"use strict";
21+
return <span key={val}>{val * 2}</span>;
22+
})}
23+
</div>
24+
),
25+
};
26+
});

0 commit comments

Comments
 (0)