@alloy-js/csharp 0.21.0-dev.16 → 0.21.0-dev.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alloy-js/csharp",
3
- "version": "0.21.0-dev.16",
3
+ "version": "0.21.0-dev.18",
4
4
  "description": "Alloy components for CSharp language.",
5
5
  "exports": {
6
6
  ".": {
@@ -1,3 +1,4 @@
1
+ import { namekey } from "@alloy-js/core";
1
2
  import { expect, it } from "vitest";
2
3
  import { Attribute, AttributeList } from "./attributes.jsx";
3
4
 
@@ -7,6 +8,15 @@ it("define attribute", () => {
7
8
  `);
8
9
  });
9
10
 
11
+ it("define attribute whose name ending with 'Attribute'", () => {
12
+ expect(<Attribute name="TestAttribute" />).toRenderTo(`
13
+ [Test]
14
+ `);
15
+ expect(<Attribute name={namekey("TestAttribute")} />).toRenderTo(`
16
+ [Test]
17
+ `);
18
+ });
19
+
10
20
  it("define attribute with single arg", () => {
11
21
  expect(<Attribute name="Test" args={[`"abc"`]} />).toRenderTo(`
12
22
  [Test("abc")]
@@ -3,6 +3,7 @@ import {
3
3
  findKeyedChildren,
4
4
  For,
5
5
  Indent,
6
+ Refkey,
6
7
  taggedComponent,
7
8
  } from "@alloy-js/core";
8
9
 
@@ -54,7 +55,7 @@ function renderAttribute(attr: string | AttributeProps | Children): Children {
54
55
 
55
56
  export interface AttributeProps {
56
57
  /** Attribute name */
57
- name: Children;
58
+ name: string | Refkey;
58
59
 
59
60
  /** Argument */
60
61
  args?: Children[];
@@ -81,7 +82,7 @@ export const Attribute = taggedComponent(
81
82
  (props: AttributeProps) => {
82
83
  return (
83
84
  <group>
84
- [{props.name}
85
+ [{normalizeAttributeName(props.name)}
85
86
  {props.args && props.args.length > 0 && (
86
87
  <>
87
88
  (
@@ -98,3 +99,15 @@ export const Attribute = taggedComponent(
98
99
  );
99
100
  },
100
101
  );
102
+
103
+ function normalizeAttributeName(name: string | Refkey) {
104
+ const nameStr =
105
+ typeof name === "string" ? name
106
+ : typeof name === "object" && "name" in name ? name.name
107
+ : undefined;
108
+
109
+ if (nameStr !== undefined && nameStr.endsWith("Attribute")) {
110
+ return nameStr.substring(0, nameStr.length - "Attribute".length);
111
+ }
112
+ return name;
113
+ }
@@ -148,3 +148,40 @@ it("can attach attributes", () => {
148
148
  }
149
149
  `);
150
150
  });
151
+
152
+ it("can add modifiers: in | out | ref", () => {
153
+ expect(
154
+ <Wrapper>
155
+ <Method
156
+ name="MethodOne"
157
+ parameters={[
158
+ {
159
+ name: "param1",
160
+ type: "T1",
161
+ ref: true,
162
+ },
163
+ {
164
+ name: "param2",
165
+ type: "T2",
166
+ in: true,
167
+ },
168
+ {
169
+ name: "param3",
170
+ type: "T3",
171
+ out: true,
172
+ },
173
+ {
174
+ name: "param4",
175
+ type: "T4",
176
+ refReadonly: true,
177
+ },
178
+ ]}
179
+ />
180
+ </Wrapper>,
181
+ ).toRenderTo(`
182
+ public class TestClass
183
+ {
184
+ void MethodOne(ref T1 param1, in T2 param2, out T3 param3, ref readonly T4 param4) {}
185
+ }
186
+ `);
187
+ });
@@ -22,6 +22,27 @@ export interface ParameterProps {
22
22
 
23
23
  refkey?: Refkey;
24
24
 
25
+ /**
26
+ * Parameter modifier: The argument must be initialized before calling the method. The method can't assign a new value to the parameter. The compiler might create a temporary variable to hold a copy of the argument to in parameters.
27
+ * @see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/method-parameters#in-parameter-modifier
28
+ * */
29
+ in?: boolean;
30
+ /**
31
+ * Parameter modifier: The calling method isn't required to initialize the argument before calling the method. The method must assign a value to the parameter.
32
+ * @see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/method-parameters#out-parameter-modifier
33
+ * */
34
+ out?: boolean;
35
+ /**
36
+ * Parameter modifier: The argument must be initialized before calling the method. The method can assign a new value to the parameter, but isn't required to do so.
37
+ * @see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/method-parameters#ref-parameter-modifier
38
+ */
39
+ ref?: boolean;
40
+ /**
41
+ * Parameter modifier: The argument must be initialized before calling the method. The method can't assign a new value to the parameter.
42
+ * @see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/method-parameters#ref-readonly-modifier
43
+ */
44
+ refReadonly?: boolean;
45
+
25
46
  /**
26
47
  * Define attributes to attach
27
48
  * @example
@@ -48,9 +69,26 @@ export function Parameter(props: ParameterProps) {
48
69
  isNullable: props.optional,
49
70
  });
50
71
 
72
+ // Only one of in, out, ref, ref readonly can be specified
73
+ const modifiers: (keyof ParameterProps)[] = (
74
+ ["in", "out", "ref", "refReadonly"] as (keyof ParameterProps)[]
75
+ ).filter((k) => props[k]);
76
+
77
+ if (modifiers.length > 1) {
78
+ throw new Error(
79
+ `Only one of 'in', 'out', 'ref', 'ref readonly' can be specified for parameter '${
80
+ typeof props.name === "string" ? props.name : props.name.name
81
+ }'`,
82
+ );
83
+ }
84
+ const modifier =
85
+ modifiers.length === 0 ? ""
86
+ : modifiers[0] === "refReadonly" ? "ref readonly "
87
+ : modifiers[0] + " ";
51
88
  return (
52
89
  <Declaration symbol={memberSymbol}>
53
90
  <AttributeList attributes={props.attributes} endline />
91
+ <>{modifier}</>
54
92
  <TypeSlot>{props.type}</TypeSlot>
55
93
  {props.optional ? "?" : ""} <Name />
56
94
  {props.default ? code` = ${props.default}` : ""}
@@ -191,3 +191,23 @@ it("has built-ins available", () => {
191
191
  var directoryName = info.DirectoryName?.Length;
192
192
  `);
193
193
  });
194
+
195
+ it("can render multiple times", () => {
196
+ const System = createLibrary("System", {
197
+ String: {
198
+ kind: "class",
199
+ members: {},
200
+ },
201
+ });
202
+ expect(<TestNamespace>{System.String}</TestNamespace>).toRenderTo(`
203
+ using System;
204
+
205
+ String
206
+ `);
207
+
208
+ expect(<TestNamespace>{System.String}</TestNamespace>).toRenderTo(`
209
+ using System;
210
+
211
+ String
212
+ `);
213
+ });
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  Binder,
3
3
  LibrarySymbolReference,
4
+ namekey,
4
5
  refkey,
5
6
  REFKEYABLE,
6
7
  TO_SYMBOL,
@@ -96,16 +97,15 @@ export type LibraryFrom<T> = {
96
97
  } & LibrarySymbolReference;
97
98
 
98
99
  interface InternalContext {
99
- binder(): Binder;
100
- ownerSymbol(): NamedTypeSymbol;
100
+ ownerSymbol(binder: Binder | undefined): NamedTypeSymbol;
101
101
  }
102
102
 
103
103
  export function createLibrary<T extends Record<string, Descriptor>>(
104
104
  rootNs: string,
105
105
  props: T,
106
106
  ): LibraryFrom<T> {
107
- let binder: Binder | undefined = undefined;
108
- let ownerSymbol: NamespaceSymbol | undefined = undefined;
107
+ const ownerSymbolPerBinder = new WeakMap<Binder, NamespaceSymbol>();
108
+
109
109
  const namespaceNames = rootNs.split(".");
110
110
 
111
111
  return createSymbolEntry(
@@ -115,33 +115,24 @@ export function createLibrary<T extends Record<string, Descriptor>>(
115
115
  members: props,
116
116
  },
117
117
  {
118
- binder() {
119
- if (!binder) {
120
- binder = useBinder();
121
- }
122
- return binder!;
123
- },
124
- ownerSymbol() {
125
- if (ownerSymbol) {
126
- return ownerSymbol;
127
- }
128
-
129
- const binder = this.binder();
130
- ownerSymbol = getGlobalNamespace(binder);
131
- for (const name of namespaceNames.slice(0, -1)) {
132
- if (ownerSymbol!.members.symbolNames.has(name)) {
133
- ownerSymbol = ownerSymbol!.members.symbolNames.get(
134
- name,
135
- )! as NamespaceSymbol;
136
- } else {
137
- ownerSymbol = new NamespaceSymbol(name, ownerSymbol!, {
138
- binder,
139
- refkeys: refkey(),
140
- });
118
+ ownerSymbol(binder: Binder | undefined) {
119
+ return mapGet(ownerSymbolPerBinder, binder, () => {
120
+ let ownerSymbol = getGlobalNamespace(binder);
121
+ for (const name of namespaceNames.slice(0, -1)) {
122
+ if (ownerSymbol!.members.symbolNames.has(name)) {
123
+ ownerSymbol = ownerSymbol!.members.symbolNames.get(
124
+ name,
125
+ )! as NamespaceSymbol;
126
+ } else {
127
+ ownerSymbol = new NamespaceSymbol(namekey(name), ownerSymbol!, {
128
+ binder,
129
+ refkeys: refkey(),
130
+ });
131
+ }
141
132
  }
142
- }
143
133
 
144
- return ownerSymbol;
134
+ return ownerSymbol;
135
+ });
145
136
  },
146
137
  },
147
138
  ) as LibraryFrom<T>;
@@ -152,24 +143,26 @@ function createSymbolEntry(
152
143
  descriptor: Descriptor,
153
144
  context: InternalContext,
154
145
  ): LibrarySymbolReference {
155
- let symbol: CSharpSymbol | undefined = undefined;
146
+ const symbols = new WeakMap<Binder, CSharpSymbol>();
156
147
 
157
- function getSymbol() {
158
- if (symbol) return symbol;
159
-
160
- symbol = createSymbolFromDescriptor(
161
- name,
162
- descriptor,
163
- context,
164
- initializeMembers,
148
+ function getSymbol(binder: Binder | undefined) {
149
+ // We cache symbols per binder to ensure we only create one symbol. We also
150
+ // track an unbound symbol for cases where there is no binder (mostly
151
+ // tests).
152
+ return mapGet(symbols, binder, () =>
153
+ createSymbolFromDescriptor(
154
+ name,
155
+ binder,
156
+ descriptor,
157
+ context,
158
+ initializeMembers,
159
+ ),
165
160
  );
166
- return symbol;
167
161
  }
168
162
 
169
163
  const newContext: InternalContext = {
170
- binder: context.binder,
171
- ownerSymbol() {
172
- return getSymbol() as NamedTypeSymbol;
164
+ ownerSymbol(binder) {
165
+ return getSymbol(binder) as NamedTypeSymbol;
173
166
  },
174
167
  };
175
168
 
@@ -182,10 +175,10 @@ function createSymbolEntry(
182
175
 
183
176
  const obj: LibrarySymbolReference & Record<string, unknown> = {
184
177
  [REFKEYABLE]() {
185
- return getSymbol().refkeys[0];
178
+ return getSymbol(useBinder()).refkeys[0];
186
179
  },
187
180
  [TO_SYMBOL]() {
188
- return getSymbol();
181
+ return getSymbol(useBinder());
189
182
  },
190
183
  };
191
184
 
@@ -209,36 +202,45 @@ function createSymbolEntry(
209
202
 
210
203
  function createSymbolFromDescriptor(
211
204
  name: string,
205
+ binder: Binder | undefined,
212
206
  descriptor: Descriptor,
213
207
  context: InternalContext,
214
208
  lazyMemberInitializer: () => void,
215
209
  ): CSharpSymbol {
216
- const ownerSymbol = context.ownerSymbol();
217
- const binder = context.binder();
210
+ const ownerSymbol = context.ownerSymbol(binder);
218
211
 
219
212
  switch (descriptor.kind) {
220
213
  case "namespace":
221
214
  if (ownerSymbol.members.symbolNames.has(name)) {
222
215
  return ownerSymbol.members.symbolNames.get(name)! as NamespaceSymbol;
223
216
  }
224
- return new NamespaceSymbol(name, ownerSymbol as NamespaceSymbol, {
225
- binder,
226
- refkeys: refkey(),
227
- lazyMemberInitializer,
228
- });
217
+ return new NamespaceSymbol(
218
+ namekey(name),
219
+ ownerSymbol as NamespaceSymbol,
220
+ {
221
+ binder,
222
+ refkeys: refkey(),
223
+ lazyMemberInitializer,
224
+ },
225
+ );
229
226
  case "class":
230
227
  case "enum":
231
228
  case "interface":
232
229
  case "struct":
233
230
  case "record":
234
- return new NamedTypeSymbol(name, ownerSymbol.members, descriptor.kind, {
235
- binder,
236
- refkeys: refkey(),
237
- lazyMemberInitializer,
238
- });
231
+ return new NamedTypeSymbol(
232
+ namekey(name),
233
+ ownerSymbol.members,
234
+ descriptor.kind,
235
+ {
236
+ binder,
237
+ refkeys: refkey(),
238
+ lazyMemberInitializer,
239
+ },
240
+ );
239
241
  case "method":
240
242
  return new MethodSymbol(
241
- name,
243
+ namekey(name),
242
244
  ownerSymbol.members,
243
245
  descriptor.methodKind,
244
246
  {
@@ -248,7 +250,7 @@ function createSymbolFromDescriptor(
248
250
  );
249
251
  case "field":
250
252
  case "property":
251
- return new CSharpSymbol(name, ownerSymbol.members, {
253
+ return new CSharpSymbol(namekey(name), ownerSymbol.members, {
252
254
  binder,
253
255
  refkeys: refkey(),
254
256
  type:
@@ -260,7 +262,7 @@ function createSymbolFromDescriptor(
260
262
  lazyMemberInitializer,
261
263
  });
262
264
  case "generic":
263
- return new CSharpSymbol(name, ownerSymbol.members, {
265
+ return new CSharpSymbol(namekey(name), ownerSymbol.members, {
264
266
  binder,
265
267
  refkeys: refkey(),
266
268
  });
@@ -268,3 +270,37 @@ function createSymbolFromDescriptor(
268
270
  throw "Unsupported";
269
271
  }
270
272
  }
273
+
274
+ const defaultsPerMap = new WeakMap<object, unknown>();
275
+
276
+ function mapGet<T extends WeakKey, V>(
277
+ map: WeakMap<T, V>,
278
+ key: T | undefined,
279
+ ): V | undefined;
280
+ function mapGet<T extends WeakKey, V>(
281
+ map: WeakMap<T, V>,
282
+ key: T | undefined,
283
+ init: () => V,
284
+ ): V;
285
+ function mapGet<T extends WeakKey, V>(
286
+ map: WeakMap<T, V>,
287
+ key: T | undefined,
288
+ init?: () => V,
289
+ ): V | undefined {
290
+ if (key === undefined) {
291
+ // Use a per-map default store when callers request a value for an undefined key.
292
+ let value = defaultsPerMap.get(map as unknown as object) as V | undefined;
293
+ if (value === undefined && init) {
294
+ value = init();
295
+ defaultsPerMap.set(map as unknown as object, value);
296
+ }
297
+ return value;
298
+ }
299
+
300
+ let value = map.get(key);
301
+ if (value === undefined && init) {
302
+ value = init();
303
+ map.set(key, value);
304
+ }
305
+ return value;
306
+ }
package/temp/api.json CHANGED
@@ -1857,10 +1857,14 @@
1857
1857
  "kind": "Content",
1858
1858
  "text": "name: "
1859
1859
  },
1860
+ {
1861
+ "kind": "Content",
1862
+ "text": "string | "
1863
+ },
1860
1864
  {
1861
1865
  "kind": "Reference",
1862
- "text": "Children",
1863
- "canonicalReference": "@alloy-js/core!Children:type"
1866
+ "text": "Refkey",
1867
+ "canonicalReference": "@alloy-js/core!Refkey:type"
1864
1868
  },
1865
1869
  {
1866
1870
  "kind": "Content",
@@ -1873,7 +1877,7 @@
1873
1877
  "name": "name",
1874
1878
  "propertyTypeTokenRange": {
1875
1879
  "startIndex": 1,
1876
- "endIndex": 2
1880
+ "endIndex": 3
1877
1881
  }
1878
1882
  }
1879
1883
  ],
@@ -13690,6 +13694,33 @@
13690
13694
  "endIndex": 2
13691
13695
  }
13692
13696
  },
13697
+ {
13698
+ "kind": "PropertySignature",
13699
+ "canonicalReference": "@alloy-js/csharp!ParameterProps#in:member",
13700
+ "docComment": "/**\n * Parameter modifier: The argument must be initialized before calling the method. The method can't assign a new value to the parameter. The compiler might create a temporary variable to hold a copy of the argument to in parameters.\n *\n * @see\n *\n * https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/method-parameters#in-parameter-modifier\n */\n",
13701
+ "excerptTokens": [
13702
+ {
13703
+ "kind": "Content",
13704
+ "text": "in?: "
13705
+ },
13706
+ {
13707
+ "kind": "Content",
13708
+ "text": "boolean"
13709
+ },
13710
+ {
13711
+ "kind": "Content",
13712
+ "text": ";"
13713
+ }
13714
+ ],
13715
+ "isReadonly": false,
13716
+ "isOptional": true,
13717
+ "releaseTag": "Public",
13718
+ "name": "in",
13719
+ "propertyTypeTokenRange": {
13720
+ "startIndex": 1,
13721
+ "endIndex": 2
13722
+ }
13723
+ },
13693
13724
  {
13694
13725
  "kind": "PropertySignature",
13695
13726
  "canonicalReference": "@alloy-js/csharp!ParameterProps#name:member",
@@ -13749,6 +13780,60 @@
13749
13780
  "endIndex": 2
13750
13781
  }
13751
13782
  },
13783
+ {
13784
+ "kind": "PropertySignature",
13785
+ "canonicalReference": "@alloy-js/csharp!ParameterProps#out:member",
13786
+ "docComment": "/**\n * Parameter modifier: The calling method isn't required to initialize the argument before calling the method. The method must assign a value to the parameter.\n *\n * @see\n *\n * https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/method-parameters#out-parameter-modifier\n */\n",
13787
+ "excerptTokens": [
13788
+ {
13789
+ "kind": "Content",
13790
+ "text": "out?: "
13791
+ },
13792
+ {
13793
+ "kind": "Content",
13794
+ "text": "boolean"
13795
+ },
13796
+ {
13797
+ "kind": "Content",
13798
+ "text": ";"
13799
+ }
13800
+ ],
13801
+ "isReadonly": false,
13802
+ "isOptional": true,
13803
+ "releaseTag": "Public",
13804
+ "name": "out",
13805
+ "propertyTypeTokenRange": {
13806
+ "startIndex": 1,
13807
+ "endIndex": 2
13808
+ }
13809
+ },
13810
+ {
13811
+ "kind": "PropertySignature",
13812
+ "canonicalReference": "@alloy-js/csharp!ParameterProps#ref:member",
13813
+ "docComment": "/**\n * Parameter modifier: The argument must be initialized before calling the method. The method can assign a new value to the parameter, but isn't required to do so.\n *\n * @see\n *\n * https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/method-parameters#ref-parameter-modifier\n */\n",
13814
+ "excerptTokens": [
13815
+ {
13816
+ "kind": "Content",
13817
+ "text": "ref?: "
13818
+ },
13819
+ {
13820
+ "kind": "Content",
13821
+ "text": "boolean"
13822
+ },
13823
+ {
13824
+ "kind": "Content",
13825
+ "text": ";"
13826
+ }
13827
+ ],
13828
+ "isReadonly": false,
13829
+ "isOptional": true,
13830
+ "releaseTag": "Public",
13831
+ "name": "ref",
13832
+ "propertyTypeTokenRange": {
13833
+ "startIndex": 1,
13834
+ "endIndex": 2
13835
+ }
13836
+ },
13752
13837
  {
13753
13838
  "kind": "PropertySignature",
13754
13839
  "canonicalReference": "@alloy-js/csharp!ParameterProps#refkey:member",
@@ -13777,6 +13862,33 @@
13777
13862
  "endIndex": 2
13778
13863
  }
13779
13864
  },
13865
+ {
13866
+ "kind": "PropertySignature",
13867
+ "canonicalReference": "@alloy-js/csharp!ParameterProps#refReadonly:member",
13868
+ "docComment": "/**\n * Parameter modifier: The argument must be initialized before calling the method. The method can't assign a new value to the parameter.\n *\n * @see\n *\n * https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/method-parameters#ref-readonly-modifier\n */\n",
13869
+ "excerptTokens": [
13870
+ {
13871
+ "kind": "Content",
13872
+ "text": "refReadonly?: "
13873
+ },
13874
+ {
13875
+ "kind": "Content",
13876
+ "text": "boolean"
13877
+ },
13878
+ {
13879
+ "kind": "Content",
13880
+ "text": ";"
13881
+ }
13882
+ ],
13883
+ "isReadonly": false,
13884
+ "isOptional": true,
13885
+ "releaseTag": "Public",
13886
+ "name": "refReadonly",
13887
+ "propertyTypeTokenRange": {
13888
+ "startIndex": 1,
13889
+ "endIndex": 2
13890
+ }
13891
+ },
13780
13892
  {
13781
13893
  "kind": "PropertySignature",
13782
13894
  "canonicalReference": "@alloy-js/csharp!ParameterProps#type:member",