Skip to content

Commit 9fa8102

Browse files
authored
Declare helper functions for TypeScript (#327)
This lets you write code like: ```tsx const hi = namekey("hi"); <ts.SourceFile path="test1.ts"> {code` const ${decl(hi)} = 12; console.log(${hi}); `} </ts.SourceFile> ```
1 parent 36ab36a commit 9fa8102

File tree

5 files changed

+195
-3
lines changed

5 files changed

+195
-3
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/core"
5+
---
6+
7+
Fix a bug where tracing would track additional signals.
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+
Introduce `decl`, `declType`, and `declMember` functions to declare a symbol given a namekey and return the new symbol name. Allows avoiding the use of declaration components if desired.

packages/core/src/tracer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ export function trace(
198198
fg: { r: 50, g: 50, b: 50 },
199199
}) +
200200
" " +
201-
cb() +
201+
untrack(cb) +
202202
"\n",
203203
);
204204
}

packages/typescript/src/symbols/index.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { Namekey, OutputScopeOptions } from "@alloy-js/core";
1+
import {
2+
createComponent,
3+
Namekey,
4+
onCleanup,
5+
OutputScopeOptions,
6+
toRef,
7+
} from "@alloy-js/core";
28
import { useLexicalScope, useMemberOwner } from "../utils.js";
39
import { useTSScope } from "./scopes.js";
410
import { TSLexicalScope } from "./ts-lexical-scope.js";
@@ -24,7 +30,7 @@ export function createTypeAndValueSymbol(
2430

2531
export function createTypeSymbol(
2632
name: string | Namekey,
27-
options: CreateTsSymbolOptions,
33+
options: CreateTsSymbolOptions = {},
2834
) {
2935
const scope = useLexicalScope();
3036
if (scope && !scope.types) {
@@ -125,3 +131,46 @@ export function createMemberScope(
125131
const parent = useTSScope();
126132
return new TSMemberScope(name, parent, ownerSymbol, options);
127133
}
134+
135+
export function decl(namekey: Namekey, options?: CreateTsSymbolOptions) {
136+
return createComponent(() => {
137+
const symbol = createValueSymbol(namekey, options);
138+
139+
onCleanup(() => {
140+
symbol.delete();
141+
});
142+
143+
return toRef(symbol, "name");
144+
}, {});
145+
}
146+
147+
export function declType(namekey: Namekey, options?: CreateTsSymbolOptions) {
148+
return createComponent(() => {
149+
const symbol = createTypeSymbol(namekey, options);
150+
151+
onCleanup(() => {
152+
symbol.delete();
153+
});
154+
155+
return toRef(symbol, "name");
156+
}, {});
157+
}
158+
159+
export function declMember(
160+
namekey: Namekey,
161+
locationOptions: { jsPrivate?: boolean; static?: boolean } = {},
162+
options?: CreateTsSymbolOptions,
163+
) {
164+
// Create the symbol inside a component so that we only get these side
165+
// effects when this function is rendered (and not from something like
166+
// children()/childrenArray() invoking the memo) and we get cleanup logic.
167+
return createComponent(() => {
168+
const symbol = createMemberSymbol(namekey, locationOptions, options);
169+
170+
onCleanup(() => {
171+
symbol.delete();
172+
});
173+
174+
return toRef(symbol, "name");
175+
}, {});
176+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { code, namekey, Output } from "@alloy-js/core";
2+
import "@alloy-js/core/testing";
3+
import { d } from "@alloy-js/core/testing";
4+
import { expect, it } from "vitest";
5+
import * as ts from "../src/index.js";
6+
import { decl, declMember, declType } from "../src/symbols/index.js";
7+
8+
it("declares variables", () => {
9+
const hi = namekey("hi");
10+
11+
expect(
12+
<Output>
13+
<ts.SourceFile path="test1.ts">
14+
{code`
15+
const ${decl(hi)} = 12;
16+
console.log(${hi});
17+
`}
18+
</ts.SourceFile>
19+
<ts.SourceFile path="test2.ts">{hi};</ts.SourceFile>
20+
</Output>,
21+
).toRenderTo({
22+
"test1.ts": d`
23+
const hi = 12;
24+
console.log(hi);
25+
`,
26+
"test2.ts": d`
27+
import { hi } from "./test1.js";
28+
29+
hi;
30+
`,
31+
});
32+
});
33+
34+
it("declares types", () => {
35+
const hi = namekey("hi");
36+
37+
expect(
38+
<Output>
39+
<ts.SourceFile path="test1.ts">
40+
{code`
41+
interface ${declType(hi)} {
42+
value: number;
43+
}
44+
`}
45+
</ts.SourceFile>
46+
<ts.SourceFile path="test2.ts">
47+
<ts.VarDeclaration name="test" type={hi} />
48+
</ts.SourceFile>
49+
</Output>,
50+
).toRenderTo({
51+
"test1.ts": d`
52+
interface hi {
53+
value: number;
54+
}
55+
`,
56+
"test2.ts": d`
57+
import type { hi } from "./test1.js";
58+
59+
const test: hi
60+
`,
61+
});
62+
});
63+
64+
it("declares variables", () => {
65+
const hi = namekey("hi");
66+
67+
expect(
68+
<Output>
69+
<ts.SourceFile path="test1.ts">
70+
{code`
71+
interface ${declType(hi)} {
72+
value: number;
73+
}
74+
`}
75+
</ts.SourceFile>
76+
<ts.SourceFile path="test2.ts">
77+
<ts.VarDeclaration name="test" type={hi} />
78+
</ts.SourceFile>
79+
</Output>,
80+
).toRenderTo({
81+
"test1.ts": d`
82+
interface hi {
83+
value: number;
84+
}
85+
`,
86+
"test2.ts": d`
87+
import type { hi } from "./test1.js";
88+
89+
const test: hi
90+
`,
91+
});
92+
});
93+
94+
it("declares members", () => {
95+
const publicMember = namekey("publicMember");
96+
const privateMember = namekey("privateMember");
97+
const staticMethod = namekey("staticMethod");
98+
expect(
99+
<Output>
100+
<ts.SourceFile path="test1.ts">
101+
<ts.ClassDeclaration name="Foo">
102+
{code`
103+
public ${declMember(publicMember)} = 12;
104+
#${declMember(privateMember, { jsPrivate: true })} = 34;
105+
constructor() {
106+
${publicMember} = 24;
107+
${privateMember} = 10;
108+
}
109+
`}
110+
<hbr />
111+
static ${declMember(staticMethod, { static: true })}() {"{}"};
112+
</ts.ClassDeclaration>
113+
<hbr />
114+
{staticMethod}();
115+
</ts.SourceFile>
116+
</Output>,
117+
).toRenderTo(d`
118+
class Foo {
119+
public publicMember = 12;
120+
#privateMember = 34;
121+
constructor() {
122+
this.publicMember = 24;
123+
this.#privateMember = 10;
124+
}
125+
static $staticMethod() {};
126+
}
127+
Foo.staticMethod();
128+
`);
129+
});

0 commit comments

Comments
 (0)