@alloy-js/csharp 0.23.0-dev.3 → 0.23.0-dev.4

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.23.0-dev.3",
3
+ "version": "0.23.0-dev.4",
4
4
  "description": "Alloy components for CSharp language.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,7 +36,7 @@
36
36
  "author": "jhendrix@microsoft.com",
37
37
  "license": "MIT",
38
38
  "dependencies": {
39
- "@alloy-js/core": "~0.22.0 || >= 0.23.0-dev.8",
39
+ "@alloy-js/core": "~0.22.0 || >= 0.23.0-dev.10",
40
40
  "@alloy-js/msbuild": "~0.22.0 || >= 0.23.0-dev.1",
41
41
  "change-case": "^5.4.4",
42
42
  "marked": "^16.1.1",
@@ -45,7 +45,7 @@
45
45
  "devDependencies": {
46
46
  "@alloy-js/cli": "~0.22.0 || >= 0.23.0-dev.3",
47
47
  "@alloy-js/rollup-plugin": "~0.1.0 || >= 0.1.1-dev.1",
48
- "@alloy-js/typescript": "~0.22.0 || >= 0.23.0-dev.4",
48
+ "@alloy-js/typescript": "~0.22.0 || >= 0.23.0-dev.5",
49
49
  "@microsoft/api-extractor": "~7.52.8",
50
50
  "@rollup/plugin-typescript": "^12.1.2",
51
51
  "@types/js-yaml": "^4.0.9",
@@ -1,76 +1,126 @@
1
1
  import {
2
2
  Children,
3
- childrenArray,
4
- ComponentDefinition,
5
3
  computed,
4
+ createAccessExpression,
6
5
  For,
7
- isComponentCreator,
8
6
  Refkeyable,
9
7
  Show,
10
- takeSymbols,
11
8
  } from "@alloy-js/core";
12
9
  import { CSharpSymbol } from "../../symbols/csharp.js";
13
- import {
14
- childrenToPartDescriptors,
15
- isArgsPart,
16
- isIdPart,
17
- PartDescriptor,
18
- PartDescriptorWithArgs,
19
- PartDescriptorWithId,
20
- PartDescriptorWithIndex,
21
- } from "./part-descriptors.js";
10
+ import { normalizeAttributeName } from "./part-descriptors.js";
22
11
 
23
12
  export interface AccessExpressionProps {
24
13
  children: Children;
25
14
  }
26
15
 
27
- export function AccessExpression(props: AccessExpressionProps) {
28
- const children = flattenAccessExpression(childrenArray(() => props.children));
29
- const parts = childrenToPartDescriptors(children);
16
+ type CSharpPartDescriptor = {
17
+ id: Children | undefined;
18
+ indexerArgs: Children[];
19
+ conditional: boolean;
20
+ nullable: boolean;
21
+ args: Children[] | undefined;
22
+ typeArgs: Children[] | undefined;
23
+ };
30
24
 
31
- // any symbols emitted from the children won't be relevant to parent scopes.
32
- takeSymbols();
25
+ const exclusiveParts: (keyof AccessExpressionPartProps)[] = [
26
+ "children",
27
+ "args",
28
+ "refkey",
29
+ "symbol",
30
+ "id",
31
+ ];
32
+
33
+ const { Expression, Part, registerOuterComponent } = createAccessExpression<
34
+ AccessExpressionPartProps,
35
+ CSharpPartDescriptor
36
+ >({
37
+ createDescriptor(partProps, sym, first) {
38
+ const foundProps = exclusiveParts.filter((key) => key in partProps);
39
+ if (foundProps.length > 1) {
40
+ throw new Error(
41
+ `Only one of ${foundProps.join(", ")} can be used for a MemberExpression part at a time`,
42
+ );
43
+ }
33
44
 
34
- if (parts.length === 0) {
35
- return <></>;
36
- }
45
+ let id: Children | undefined;
46
+ if (
47
+ partProps.args ||
48
+ partProps.index !== undefined ||
49
+ partProps.indexerArgs
50
+ ) {
51
+ id = undefined;
52
+ } else if (partProps.children !== undefined) {
53
+ id = partProps.children;
54
+ } else if (first && partProps.refkey) {
55
+ id = partProps.refkey;
56
+ } else if (partProps.id !== undefined) {
57
+ id = normalizeIfAttribute(partProps.id, partProps.attribute);
58
+ } else if (sym) {
59
+ id = normalizeIfAttribute(escapeId(sym.name), partProps.attribute);
60
+ } else {
61
+ id = "<unresolved symbol>";
62
+ }
37
63
 
38
- const isCallChain = computed(() => {
39
- let callCount = 0;
40
- for (const part of parts) {
41
- if (isArgsPart(part)) callCount++;
64
+ let indexerArgs: Children[] = [];
65
+ if (partProps.indexerArgs) {
66
+ indexerArgs = partProps.indexerArgs;
67
+ } else if (partProps.index !== undefined) {
68
+ indexerArgs = [partProps.index];
42
69
  }
43
70
 
44
- return callCount > 1;
45
- });
71
+ return {
72
+ id,
73
+ indexerArgs,
74
+ conditional: !!partProps.conditional,
75
+ nullable:
76
+ partProps.nullable ? true
77
+ : sym ? (sym as CSharpSymbol).isNullable
78
+ : false,
79
+ args:
80
+ partProps.args === true ? []
81
+ : Array.isArray(partProps.args) ? partProps.args
82
+ : undefined,
83
+ typeArgs: partProps.typeArgs,
84
+ };
85
+ },
46
86
 
47
- // construct a member expression from the parts. When a part is nullish,
48
- // and there is a subsequent part, we use `?.` instead of `.`. accessStyle determines
49
- // whether we use dot or bracket notation.
87
+ getBase(part) {
88
+ if (part.id !== undefined) {
89
+ return (
90
+ <>
91
+ {part.id}
92
+ <TypeArgs args={part.typeArgs} />
93
+ </>
94
+ );
95
+ }
96
+ return part.indexerArgs;
97
+ },
98
+
99
+ formatPart(part, prevPart, inCallChain) {
100
+ if (part.args !== undefined) {
101
+ return formatCallExpr(part);
102
+ } else if (part.id !== undefined) {
103
+ return formatMemberAccess(prevPart, part, inCallChain);
104
+ } else {
105
+ return formatElementAccess(prevPart, part);
106
+ }
107
+ },
50
108
 
51
- return computed(() => {
52
- return isCallChain.value ?
53
- formatCallChain(parts)
54
- : formatNonCallChain(parts);
55
- });
56
- }
109
+ isCallPart(part) {
110
+ return part.args !== undefined;
111
+ },
112
+ });
57
113
 
58
114
  /**
59
- * Flattens nested access expressions into a single array of parts.
115
+ * Create a C# access expression from parts. Each part can be a member access,
116
+ * element access, or invocation. Supports conditional access (`?.`), generic
117
+ * type arguments, and call chain formatting.
60
118
  */
61
- function flattenAccessExpression(children: Children[]): Children[] {
62
- const flattened: Children[] = [];
63
- for (const child of children) {
64
- if (isComponentCreator(child, AccessExpression)) {
65
- flattened.push(
66
- ...flattenAccessExpression(childrenArray(() => child.props.children)),
67
- );
68
- } else {
69
- flattened.push(child);
70
- }
71
- }
72
- return flattened;
119
+ export function AccessExpression(props: AccessExpressionProps) {
120
+ return Expression(props);
73
121
  }
122
+ AccessExpression.Part = Part;
123
+ registerOuterComponent(AccessExpression);
74
124
 
75
125
  export interface AccessExpressionPartProps {
76
126
  children?: Children;
@@ -130,170 +180,19 @@ export interface AccessExpressionPartProps {
130
180
  attribute?: boolean;
131
181
  }
132
182
 
133
- AccessExpression.Part = function (props: AccessExpressionPartProps) {
134
- /** renders nothing, the parent AccessExpression will use these args */
135
- };
136
-
137
- /**
138
- * Formatting of call chains (i.e. member expressions which have more than one
139
- * call in them). The general approach is that line breaks occur after each
140
- * call, and there is only one call per line. When there are non-call elements,
141
- * they occur prior to the call part. The first part of the member expression
142
- * contains all but the last non-call part.
143
- *
144
- * The following is an example of proper formatting:
145
- *
146
- * ```ts
147
- * z.dummy // all but the last non-call part for the first element
148
- * .object({ // the first call part with line break after
149
- * a: 1,
150
- * })
151
- * .dummy.partial() // the next call part with non-call parts before it
152
- * ```
153
- */
154
- function formatCallChain(parts: PartDescriptor[]): Children {
155
- return computed(() => {
156
- const expression: Children[] = [];
157
-
158
- // break the expression into parts.
159
- const chunks: PartDescriptor[][] = [];
160
-
161
- // the first part is all the non-call parts
162
- let partIndex = 0;
163
-
164
- function pushPart() {
165
- const part = parts[partIndex];
166
- if (!part) throw new Error("No part to push");
167
- chunks.at(-1)!.push(part);
168
- partIndex++;
169
- }
170
-
171
- function pushChunk() {
172
- chunks.push([]);
173
- }
174
-
175
- // For the first chunk, take all the non-call parts except the last one
176
- // and put them in a chunk.
177
- pushChunk();
178
- while (
179
- partIndex < parts.length &&
180
- (partIndex === parts.length - 1 ||
181
- chunks.at(-1)!.length === 0 ||
182
- !isArgsPart(parts[partIndex + 1]))
183
- ) {
184
- pushPart();
185
- if (isArgsPart(chunks.at(-1)!.at(-1)!)) {
186
- // the first segment always ends after we see a call
187
- // if we happen to take one
188
- break;
189
- }
190
- }
191
-
192
- // then for all remaining parts, collect all the non-call parts and end with
193
- // a call chunk
194
- while (partIndex < parts.length) {
195
- pushChunk();
196
- while (partIndex < parts.length && !isArgsPart(parts[partIndex])) {
197
- pushPart();
198
- }
199
- while (partIndex < parts.length && isArgsPart(parts[partIndex])) {
200
- pushPart();
201
- }
202
- }
203
-
204
- for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
205
- const chunk = chunks[chunkIndex];
206
- const chunkExpression = [];
207
- for (let partIndex = 0; partIndex < chunk.length; partIndex++) {
208
- if (chunkIndex === 0 && partIndex === 0) {
209
- // first part is just gonna be the id
210
- const firstPart =
211
- isIdPart(chunk[0]) ?
212
- chunk[0].id
213
- : (chunk[0] as PartDescriptorWithIndex).indexerArgs;
214
- chunkExpression.push(firstPart);
215
- continue;
216
- }
217
- const part = chunk[partIndex];
218
- const prevPart =
219
- partIndex === 0 ?
220
- chunks[chunkIndex - 1].at(-1)!
221
- : chunk[partIndex - 1];
222
-
223
- if (isArgsPart(part)) {
224
- // For parts with only args (no name), append function call directly with appropriate nullish operator
225
- chunkExpression.push(formatCallExpr(prevPart, part));
226
- } else if (isIdPart(part)) {
227
- chunkExpression.push(formatMemberAccess(prevPart, part, true));
228
- } else {
229
- // bracket notation - don't include the dot
230
- chunkExpression.push(formatElementAccess(prevPart, part));
231
- }
232
- }
233
-
234
- expression.push(
235
- chunkIndex === 0 ? chunkExpression : (
236
- <>
237
- <sbr />
238
- {chunkExpression}
239
- </>
240
- ),
241
- );
242
- }
243
-
244
- return (
245
- <group>
246
- <indent>{expression}</indent>
247
- </group>
248
- );
249
- });
250
- }
251
-
252
- function formatNonCallChain(parts: PartDescriptor[]): Children {
253
- return computed(() => {
254
- const expression: Children[] = [];
255
-
256
- for (let i = 0; i < parts.length; i++) {
257
- const part = parts[i];
258
- const base =
259
- isIdPart(part) ?
260
- part.id
261
- : (part as PartDescriptorWithIndex).indexerArgs;
262
- if (i === 0) {
263
- expression.push(base, <TypeArgs args={(part as any).typeArgs} />);
264
- } else {
265
- // Determine if we should use nullish operator from previous part
266
- const prevPart = parts[i - 1];
267
-
268
- if (isArgsPart(part)) {
269
- // For parts with only args (no name), append function call directly with appropriate nullish operator
270
- expression.push(formatCallExpr(prevPart, part));
271
- } else if (isIdPart(part)) {
272
- expression.push(formatMemberAccess(prevPart, part));
273
- } else {
274
- // bracket notation - don't include the dot
275
- expression.push(formatElementAccess(prevPart, part));
276
- }
277
- }
278
- }
279
-
280
- return expression;
281
- });
282
- }
183
+ // --- Formatting helpers ---
283
184
 
284
185
  function formatElementAccess(
285
- prevPart: PartDescriptor,
286
- part: PartDescriptorWithIndex,
186
+ prevPart: CSharpPartDescriptor,
187
+ part: CSharpPartDescriptor,
287
188
  ) {
189
+ const indexerArgs = computed(() => part.indexerArgs);
288
190
  return (
289
191
  <group>
290
- {part.conditional || ("nullable" in prevPart && prevPart.nullable) ?
291
- "?"
292
- : ""}
293
- [
192
+ {part.conditional || prevPart.nullable ? "?" : ""}[
294
193
  <indent>
295
194
  <sbr />
296
- <For each={part.indexerArgs} comma line>
195
+ <For each={indexerArgs} comma line>
297
196
  {(arg) => arg}
298
197
  </For>
299
198
  </indent>
@@ -303,41 +202,34 @@ function formatElementAccess(
303
202
  }
304
203
 
305
204
  function formatMemberAccess(
306
- prevPart: PartDescriptor,
307
- part: PartDescriptorWithId,
308
- noIndent = false,
205
+ prevPart: CSharpPartDescriptor,
206
+ part: CSharpPartDescriptor,
207
+ noIndent: boolean,
309
208
  ) {
310
- let Wrapping: ComponentDefinition<{ children: Children }>;
209
+ const content = (
210
+ <>
211
+ {part.conditional || prevPart.nullable ? "?." : "."}
212
+ {part.id}
213
+ <TypeArgs args={part.typeArgs} />
214
+ </>
215
+ );
216
+
311
217
  if (noIndent) {
312
- Wrapping = function (props) {
313
- return (
314
- <group>
315
- <sbr />
316
- {props.children}
317
- </group>
318
- );
319
- };
320
- } else {
321
- Wrapping = function (props) {
322
- return (
323
- <group>
324
- <indent>
325
- <sbr />
326
- {props.children}
327
- </indent>
328
- </group>
329
- );
330
- };
218
+ return (
219
+ <group>
220
+ <sbr />
221
+ {content}
222
+ </group>
223
+ );
331
224
  }
332
225
 
333
226
  return (
334
- <Wrapping>
335
- {part.conditional || ("nullable" in prevPart && prevPart.nullable) ?
336
- "?."
337
- : "."}
338
- {isIdPart(part) ? part.id : (part as PartDescriptorWithIndex).indexerArgs}
339
- <TypeArgs args={part.typeArgs} />
340
- </Wrapping>
227
+ <group>
228
+ <indent>
229
+ <sbr />
230
+ {content}
231
+ </indent>
232
+ </group>
341
233
  );
342
234
  }
343
235
 
@@ -359,17 +251,15 @@ function TypeArgs(props: { args?: Children[] }) {
359
251
  );
360
252
  }
361
253
 
362
- function formatCallExpr(
363
- prevPart: PartDescriptor,
364
- part: PartDescriptorWithArgs,
365
- ) {
254
+ function formatCallExpr(part: CSharpPartDescriptor) {
255
+ const args = computed(() => part.args ?? []);
366
256
  return (
367
257
  <group>
368
- (<Show when={part.args.length <= 1}>{part.args[0]}</Show>
369
- <Show when={part.args.length > 1}>
258
+ (<Show when={args.value.length <= 1}>{args.value[0]}</Show>
259
+ <Show when={args.value.length > 1}>
370
260
  <indent>
371
261
  <sbr />
372
- <For each={part.args} comma line>
262
+ <For each={args} comma line>
373
263
  {(arg) => arg}
374
264
  </For>
375
265
  </indent>
@@ -379,3 +269,16 @@ function formatCallExpr(
379
269
  </group>
380
270
  );
381
271
  }
272
+
273
+ // --- Utilities ---
274
+
275
+ function escapeId(id: string) {
276
+ return id.replace(/"/g, '\\"');
277
+ }
278
+
279
+ function normalizeIfAttribute(id: string, isAttribute?: boolean) {
280
+ if (isAttribute) {
281
+ return normalizeAttributeName(id);
282
+ }
283
+ return id;
284
+ }
@@ -1,189 +1,3 @@
1
- import {
2
- Children,
3
- computed,
4
- isComponentCreator,
5
- reactive,
6
- ref,
7
- symbolForRefkey,
8
- ToRefs,
9
- } from "@alloy-js/core";
10
- import { CSharpSymbol } from "../../index.js";
11
- import {
12
- AccessExpression,
13
- AccessExpressionPartProps,
14
- } from "./access-expression.jsx";
15
-
16
- export interface PartDescriptorWithId extends PartDescriptorBase {
17
- /**
18
- * The identifier of the access expression part. Will use member access, so must be a valid
19
- * C# identifier.
20
- */
21
- id: Children;
22
- conditional: boolean;
23
- typeArgs?: Children[];
24
- }
25
-
26
- export function isIdPart(part: PartDescriptor): part is PartDescriptorWithId {
27
- return "id" in part && part.id !== undefined;
28
- }
29
-
30
- export interface PartDescriptorWithIndex extends PartDescriptorBase {
31
- /**
32
- * The index of the access expression part. Will use element access.
33
- */
34
- indexerArgs: Children[];
35
- conditional: boolean;
36
- }
37
-
38
- export function isIndexPart(
39
- part: PartDescriptor,
40
- ): part is PartDescriptorWithIndex {
41
- return "indexerArgs" in part && part.indexerArgs !== undefined;
42
- }
43
-
44
- export interface PartDescriptorWithArgs extends PartDescriptorBase {
45
- args: Children[];
46
- }
47
-
48
- export function isArgsPart(
49
- part: PartDescriptor,
50
- ): part is PartDescriptorWithArgs {
51
- return "args" in part && part.args !== undefined;
52
- }
53
-
54
- export interface PartDescriptorBase {
55
- nullable: boolean;
56
- }
57
-
58
- export type PartDescriptor =
59
- | PartDescriptorWithId
60
- | PartDescriptorWithIndex
61
- | PartDescriptorWithArgs;
62
-
63
- /**
64
- * Build part descriptors from the children of a MemberExpression.
65
- */
66
- export function childrenToPartDescriptors(
67
- children: Children[],
68
- ): PartDescriptor[] {
69
- const parts: PartDescriptor[] = [];
70
- for (const child of children) {
71
- if (!isComponentCreator(child, AccessExpression.Part)) {
72
- // we ignore non-parts
73
- continue;
74
- }
75
-
76
- parts.push(
77
- createPartDescriptorFromProps(child.props, child === children[0]),
78
- );
79
- }
80
-
81
- return parts;
82
- }
83
-
84
- const exclusiveParts: (keyof AccessExpressionPartProps)[] = [
85
- "children",
86
- "args",
87
- "refkey",
88
- "symbol",
89
- "id",
90
- ];
91
- /**
92
- * Creates a reactive part descriptor from the given part props.
93
- *
94
- * @param partProps The props for the part.
95
- * @param first Whether this is the first part in the expression. Refkeys are
96
- * handled specially for the first part.
97
- */
98
- function createPartDescriptorFromProps(
99
- partProps: AccessExpressionPartProps,
100
- first: boolean,
101
- ) {
102
- const foundProps = exclusiveParts.filter((key) => {
103
- return key in partProps;
104
- });
105
-
106
- if (foundProps.length > 1) {
107
- throw new Error(
108
- `Only one of ${foundProps.join(", ")} can be used for a MemberExpression part at a time`,
109
- );
110
- }
111
-
112
- const symbolSource = computed(() => {
113
- if (partProps.refkey) {
114
- return symbolForRefkey(partProps.refkey).value as CSharpSymbol;
115
- } else if (partProps.symbol) {
116
- return partProps.symbol;
117
- } else {
118
- return undefined;
119
- }
120
- });
121
-
122
- const part: ToRefs<PartDescriptor> = {
123
- id: computed(() => {
124
- if (partProps.args || partProps.index || partProps.indexerArgs) {
125
- return undefined;
126
- } else if (partProps.children !== undefined) {
127
- return partProps.children;
128
- } else if (first && partProps.refkey) {
129
- return partProps.refkey;
130
- } else if (partProps.id !== undefined) {
131
- return normalizeIfAttribute(partProps.id, partProps.attribute);
132
- } else if (symbolSource.value) {
133
- return normalizeIfAttribute(
134
- escapeId(symbolSource.value.name),
135
- partProps.attribute,
136
- );
137
- } else {
138
- return "<unresolved symbol>";
139
- }
140
- }),
141
- indexerArgs: computed(() => {
142
- if (partProps.indexerArgs) {
143
- return partProps.indexerArgs;
144
- }
145
-
146
- if (partProps.index !== undefined) {
147
- return [partProps.index];
148
- }
149
-
150
- return [];
151
- }),
152
- conditional: computed(() => {
153
- return !!partProps.conditional;
154
- }),
155
- nullable: computed(() => {
156
- if (partProps.nullable) {
157
- return true;
158
- }
159
-
160
- if (symbolSource.value) {
161
- return symbolSource.value.isNullable;
162
- }
163
-
164
- return false;
165
- }),
166
- args: ref<any>(partProps.args === true ? [] : partProps.args),
167
- typeArgs: ref<any>(partProps.typeArgs),
168
- };
169
-
170
- return reactive(part);
171
- }
172
-
173
- /**
174
- * replaces quotes with escaped quotes
175
- */
176
- function escapeId(id: string) {
177
- return id.replace(/"/g, '\\"');
178
- }
179
-
180
- function normalizeIfAttribute(id: string, isAttribute?: boolean) {
181
- if (isAttribute) {
182
- return normalizeAttributeName(id);
183
- }
184
- return id;
185
- }
186
-
187
1
  /**
188
2
  * Normalize attribute name by removing the "Attribute" suffix if present.
189
3
  * @example
@@ -17,7 +17,10 @@ function renderTokens(tokens: Token[]) {
17
17
  {tokens.map((token, index) => (
18
18
  <>
19
19
  <DocFromMarkedToken token={token} />
20
- {token.type === "paragraph" && index !== tokens.length - 1 && <br />}
20
+ {
21
+ /*@once*/ token.type === "paragraph" &&
22
+ index !== tokens.length - 1 && <br />
23
+ }
21
24
  </>
22
25
  ))}
23
26
  </>