@azure-tools/typespec-ts 0.55.0-dev.10 → 0.55.0-dev.11

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": "@azure-tools/typespec-ts",
3
- "version": "0.55.0-dev.10",
3
+ "version": "0.55.0-dev.11",
4
4
  "description": "An experimental TypeSpec emitter for TypeScript",
5
5
  "main": "dist/src/index.js",
6
6
  "type": "module",
@@ -316,12 +316,36 @@ function prepareExampleParameters(
316
316
 
317
317
  let subscriptionIdValue = `"00000000-0000-0000-0000-000000000000"`;
318
318
  let isSubscriptionIdAdded = false;
319
- // required parameters
319
+
320
+ // Map each HTTP parameter to its method parameter via `methodParameterSegments`
321
+ // and place its example value at that path, yielding one argument per method
322
+ // parameter regardless of flat, nested, or grouped shape. Client- and
323
+ // body-carried roots are emitted by their own sections and skipped here.
324
+ const bodyParam = method.operation.bodyParam;
325
+ let bodyRootName: string | undefined;
326
+ if (
327
+ bodyParam &&
328
+ !isSpreadBodyParameter(bodyParam) &&
329
+ bodyParam.methodParameterSegments.length === 1 &&
330
+ bodyParam.methodParameterSegments[0] !== undefined &&
331
+ bodyParam.methodParameterSegments[0].length >= 1
332
+ ) {
333
+ bodyRootName = bodyParam.methodParameterSegments[0][0]!.name;
334
+ }
335
+
336
+ const methodArguments = new Map<
337
+ string,
338
+ { name: string; isOptional: boolean; value: ValueTreeNode | string }
339
+ >();
320
340
  for (const param of method.operation.parameters) {
321
- if (param.optional === true || param.type.kind === "constant" || param.clientDefaultValue) {
341
+ if (param.type.kind === "constant" || param.clientDefaultValue) {
322
342
  continue;
323
343
  }
324
-
344
+ const path = param.methodParameterSegments[0];
345
+ if (path === undefined || path.length === 0) {
346
+ continue;
347
+ }
348
+ const root = path[0]!;
325
349
  const exampleValue = parameterMap[param.serializedName];
326
350
 
327
351
  // Handle subscriptionId parameter separately for ARM clients
@@ -343,28 +367,27 @@ function prepareExampleParameters(
343
367
  continue;
344
368
  }
345
369
 
370
+ // Roots emitted by the client and body sections.
371
+ if (root.onClient || (bodyRootName !== undefined && root.name === bodyRootName)) {
372
+ continue;
373
+ }
374
+
346
375
  if (!exampleValue || !exampleValue.value) {
347
376
  // report diagnostic if required parameter is missing
348
- reportDiagnostic(dpgContext.program, {
349
- code: "required-sample-parameter",
350
- format: {
351
- exampleName: method.oriName ?? method.name,
352
- paramName: param.name,
353
- },
354
- target: NoTarget,
355
- });
377
+ if (path.length === 1 && !param.optional) {
378
+ reportDiagnostic(dpgContext.program, {
379
+ code: "required-sample-parameter",
380
+ format: {
381
+ exampleName: method.oriName ?? method.name,
382
+ paramName: param.name,
383
+ },
384
+ target: NoTarget,
385
+ });
386
+ }
356
387
  continue;
357
388
  }
358
389
 
359
- result.push(
360
- prepareExampleValue(
361
- dpgContext,
362
- exampleValue.parameter.name,
363
- exampleValue.value,
364
- param.optional,
365
- param.onClient,
366
- ),
367
- );
390
+ placeMethodArgument(methodArguments, path, getParameterValue(dpgContext, exampleValue.value));
368
391
  }
369
392
 
370
393
  // If client-level subscriptionId is needed on the client for this method, then add it
@@ -381,7 +404,6 @@ function prepareExampleParameters(
381
404
  }
382
405
 
383
406
  // required/optional body parameters
384
- const bodyParam = method.operation.bodyParam;
385
407
  const bodySerializeName = bodyParam?.serializedName;
386
408
  const bodyExample = parameterMap[bodySerializeName ?? ""];
387
409
  if (bodyParam && bodyExample && bodyExample.value) {
@@ -441,28 +463,87 @@ function prepareExampleParameters(
441
463
  }
442
464
  }
443
465
  }
444
- // optional parameters
445
- method.operation.parameters
446
- .filter(
447
- (param) =>
448
- param.optional === true && parameterMap[param.serializedName] && !param.clientDefaultValue,
449
- )
450
- .map((param) => parameterMap[param.serializedName]!)
451
- .forEach((param) => {
452
- result.push(
453
- prepareExampleValue(
454
- dpgContext,
455
- param.parameter.name,
456
- param.value,
457
- true,
458
- param.parameter.onClient,
459
- ),
460
- );
466
+ // Emit one argument per top-level method parameter. A normal parameter carries
467
+ // a scalar value; a parameter with nested leaves carries an object literal.
468
+ for (const arg of methodArguments.values()) {
469
+ result.push({
470
+ name: normalizeName(arg.name, NameType.Parameter, true),
471
+ value: serializeNestedValue(arg.value),
472
+ isOptional: arg.isOptional,
473
+ onClient: false,
461
474
  });
475
+ }
462
476
 
463
477
  return result;
464
478
  }
465
479
 
480
+ /** A nested value tree: leaves are TypeScript value expressions, interior nodes are objects. */
481
+ interface ValueTreeNode {
482
+ [key: string]: ValueTreeNode | string;
483
+ }
484
+
485
+ /**
486
+ * Records `leaf` for the method parameter addressed by `path`. `path[0]` is the
487
+ * top-level method parameter (the argument key); a single-segment path stores a
488
+ * scalar, a longer path stores the leaf inside a nested object merged with
489
+ * sibling leaves sharing the same root.
490
+ */
491
+ function placeMethodArgument(
492
+ methodArguments: Map<
493
+ string,
494
+ { name: string; isOptional: boolean; value: ValueTreeNode | string }
495
+ >,
496
+ path: readonly { name: string; optional?: boolean }[],
497
+ leaf: string,
498
+ ): void {
499
+ const root = path[0]!;
500
+ let arg = methodArguments.get(root.name);
501
+ if (!arg) {
502
+ arg = { name: root.name, isOptional: Boolean(root.optional), value: {} };
503
+ methodArguments.set(root.name, arg);
504
+ }
505
+ if (path.length === 1) {
506
+ arg.value = leaf;
507
+ return;
508
+ }
509
+ if (typeof arg.value !== "object") {
510
+ arg.value = {};
511
+ }
512
+ setNestedValue(arg.value, path.slice(1), leaf);
513
+ }
514
+
515
+ /** Places `leaf` at `path` (segments after the root), creating intermediate objects as needed. */
516
+ function setNestedValue(
517
+ root: ValueTreeNode,
518
+ path: readonly { name: string }[],
519
+ leaf: string,
520
+ ): void {
521
+ let node = root;
522
+ for (let i = 0; i < path.length; i++) {
523
+ const key = normalizeName(path[i]!.name, NameType.Property, true);
524
+ if (i === path.length - 1) {
525
+ node[key] = leaf;
526
+ } else {
527
+ const existing = node[key];
528
+ if (typeof existing !== "object" || existing === null) {
529
+ node[key] = {};
530
+ }
531
+ node = node[key] as ValueTreeNode;
532
+ }
533
+ }
534
+ }
535
+
536
+ /** Serializes a {@link ValueTreeNode} into a TypeScript object literal string. */
537
+ function serializeNestedValue(node: ValueTreeNode | string): string {
538
+ if (typeof node === "string") {
539
+ return node;
540
+ }
541
+ const entries = Object.entries(node).map(
542
+ ([key, value]) => `${key}: ${serializeNestedValue(value)}`,
543
+ );
544
+ return `{ ${entries.join(", ")} }`;
545
+ }
546
+
466
547
  function getCredentialExampleValue(
467
548
  _dpgContext: SdkContext,
468
549
  initialization: SdkClientInitializationType,