Skip to content

Commit 6e4977d

Browse files
authored
Add support for namekeys for Object Properties. (#321)
1 parent 1f7a1de commit 6e4977d

File tree

5 files changed

+65
-21
lines changed

5 files changed

+65
-21
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "@alloy-js/typescript"
5+
---
6+
7+
The PropertyName component properly handles reactive values for its name prop.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: feature
3+
packages:
4+
- "@alloy-js/typescript"
5+
---
6+
7+
The ObjectProperty component now accepts namekeys.

packages/typescript/src/components/ObjectExpression.tsx

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import {
44
computed,
55
emitSymbol,
66
For,
7+
isNamekey,
78
Match,
89
moveTakenMembersTo,
10+
Namekey,
911
Refkey,
1012
Switch,
1113
takeSymbols,
@@ -83,7 +85,7 @@ export function ObjectExpression(props: ObjectExpressionProps) {
8385
}
8486

8587
export interface ObjectPropertyProps {
86-
name?: string;
88+
name?: string | Namekey;
8789
nameExpression?: Children;
8890
value?: Children;
8991
jsValue?: unknown;
@@ -92,22 +94,12 @@ export interface ObjectPropertyProps {
9294
}
9395

9496
export function ObjectProperty(props: ObjectPropertyProps) {
95-
let name;
96-
let symbolName = props.name;
97-
if (props.name) {
98-
const namer = useTSNamePolicy();
99-
symbolName = namer.getName(props.name, "object-member-data");
100-
name = <PropertyName name={symbolName} />;
101-
} else if (props.nameExpression) {
102-
name = <>[{props.nameExpression}]</>;
103-
} else {
104-
throw new Error("ObjectProperty either a name or a nameExpression.");
105-
}
106-
10797
let sym = undefined;
108-
if (props.refkey && props.name) {
109-
sym = createStaticMemberSymbol(symbolName!, {
98+
99+
if (isNamekey(props.name) || (props.refkey && props.name)) {
100+
sym = createStaticMemberSymbol(props.name, {
110101
refkeys: props.refkey,
102+
namePolicy: useTSNamePolicy().for("object-member-data"),
111103
});
112104

113105
moveTakenMembersTo(sym);
@@ -117,6 +109,18 @@ export function ObjectProperty(props: ObjectPropertyProps) {
117109
takeSymbols();
118110
}
119111

112+
let name: Children;
113+
if (sym) {
114+
name = <PropertyName name={sym.name} />;
115+
} else if (props.name) {
116+
// can't be a namekey if we get here
117+
name = <PropertyName name={props.name as string} />;
118+
} else if (props.nameExpression) {
119+
name = <>[{props.nameExpression}]</>;
120+
} else {
121+
throw new Error("ObjectProperty either a name or a nameExpression.");
122+
}
123+
120124
let value;
121125
if (props.value) {
122126
value = props.value;

packages/typescript/src/components/PropertyName.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MemberDeclarationContext, useContext } from "@alloy-js/core";
1+
import { MemberDeclarationContext, memo, useContext } from "@alloy-js/core";
22
import { TSOutputSymbol } from "../symbols/ts-output-symbol.js";
33
import { isValidJSIdentifier } from "../utils.js";
44

@@ -23,10 +23,12 @@ export interface PropertyNameProps {
2323
*/
2424
export function PropertyName(props: PropertyNameProps) {
2525
if (props.name) {
26-
if (props.private) {
27-
return "#" + props.name;
28-
}
29-
return quoteIfNeeded(props.name);
26+
return memo(() => {
27+
if (props.private) {
28+
return "#" + props.name;
29+
}
30+
return quoteIfNeeded(props.name!);
31+
});
3032
} else {
3133
const declSymbol = useContext(MemberDeclarationContext) as TSOutputSymbol;
3234
if (!declSymbol) {

packages/typescript/test/object.test.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { List } from "@alloy-js/core";
1+
import { List, namekey } from "@alloy-js/core";
22
import { d } from "@alloy-js/core/testing";
33
import { expect, it } from "vitest";
44
import {
5+
CommaList,
56
ObjectExpression,
67
ObjectProperty,
78
ObjectSpreadProperty,
@@ -61,3 +62,26 @@ it("Works with both children and jsvalue", () => {
6162
}
6263
`);
6364
});
65+
66+
it("Handles name conflicts and namekeys", () => {
67+
const nk1 = namekey("a");
68+
const nk2 = namekey("a");
69+
const nk3 = namekey("a", { ignoreNameConflict: true });
70+
71+
const comp = (
72+
<ObjectExpression>
73+
<CommaList>
74+
<ObjectProperty name={nk1} value={3} />
75+
<ObjectProperty name={nk2} value={1} />
76+
<ObjectProperty name={nk3} value={1} />
77+
</CommaList>
78+
</ObjectExpression>
79+
);
80+
expect(toSourceText(comp)).toBe(d`
81+
{
82+
a: 3,
83+
a_2: 1,
84+
a: 1,
85+
}
86+
`);
87+
});

0 commit comments

Comments
 (0)