Skip to content

[typespec-ts] Rename reserved-word operations using singularized group name - #5154

Open
Maor Leger (maorleger) wants to merge 3 commits into
Azure:mainfrom
maorleger:typespec-ts/rename-reserved-word-operations-by-group
Open

[typespec-ts] Rename reserved-word operations using singularized group name#5154
Maor Leger (maorleger) wants to merge 3 commits into
Azure:mainfrom
maorleger:typespec-ts/rename-reserved-word-operations-by-group

Conversation

@maorleger

@maorleger Maor Leger (maorleger) commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

Reserved-word operations (e.g. delete) that belong to an operation group are now renamed by suffixing the singularized operation group name, instead of emitting a @fixme doc comment on a $-guarded name.

For example, given an operation delete in the Conversations operation group:

// Before
export interface ConversationsOperations {
  /**
   *  @fixme delete is a reserved word that cannot be used as an operation name.
   *         Please add @clientName(...) to the operation to override the generated name.
   */
  delete: (conversationName: string, options?: ConversationsDeleteOptionalParams) => Promise<void>;
}

// After
export interface ConversationsOperations {
  deleteConversation: (conversationName: string, options?: ConversationsDeleteOptionalParams) => Promise<void>;
}

This mirrors the manual in-SDK customization done in Azure/azure-sdk-for-js#39499, applying it automatically in the emitter.

Data plane only (mgmt is intentionally untouched)

For mgmt plane we already decided a bare delete method is acceptable and suppressed the @fixme a while back (see autorest.typescript#3749), and delete has been generated that way going back to HLC. Renaming it now would be a breaking change for shipped mgmt SDKs, so ARM keeps emitting plain delete with no rename and no @fixme.

Backwards-compat escape hatch via @clientName

The rename itself is a breaking change for any data-plane library that already shipped with the guarded delete name. To give those libraries an out, an explicit @clientName on the operation is now treated as an intentional choice: we honor the requested name verbatim and skip both the rename and the @fixme.

@route("/conversations")
interface Conversations {
  // Keep `delete` as the generated method name instead of `deleteConversation`.
  @delete
  @clientName("delete", "javascript")
  delete(@path conversationName: string): void;
}

In that case the public method keeps the reserved word (delete), while the generated API-layer function stays guarded ($delete) because a reserved word isn't a valid function binding in JS.

Details

  • The rename is applied consistently to the API-layer function (deleteConversation) and its _deleteConversationSend / _deleteConversationDeserialize helpers, as well as the public method exposed on clients and operation groups.
  • The options parameter type (e.g. ConversationsDeleteOptionalParams) is intentionally left unchanged.
  • Singularization uses pluralize.singular, which correctly handles cases like KnowledgeBasesKnowledgeBase, ConversationsConversation, CategoriesCategory.
  • Operations without an operation group (e.g. top-level operations) continue to fall back to the previous guarded name ($delete) and @fixme guidance, since there's no group name to disambiguate with — unless a @clientName override is present, in which case the escape hatch above applies. This is a very rare case.

Validation

  • Build, lint, and format: clean.
  • modular-unit: 661/661.
  • test-next: 254/254.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @azure-tools/typespec-ts
Show changes

@azure-tools/typespec-ts - feature ✏️

Rename reserved-word operations (e.g. delete) that belong to an operation group by suffixing the singularized group name instead of emitting a @fixme. For example, Conversations.delete is now generated as deleteConversation rather than $delete with a fixme doc comment. Operations without an operation group continue to fall back to the previous guarded name and @fixme guidance.,> ,> An explicit @clientName override opts out of this renaming: when a reserved-word operation carries a @clientName, the emitter keeps the requested public method name (e.g. delete) and does not disambiguate it with the operation group or emit a @fixme. This provides a backwards-compatibility escape hatch for already-shipped libraries. The generated API-layer function stays guarded ($delete) because a reserved word is not a valid JavaScript function binding, while the public surface preserves the original name.,> ,> tsp,> @route("/conversations"),> interface Conversations {,> // Keep `delete` as the generated method name instead of `deleteConversation`.,> @delete,> @clientName("delete", "javascript"),> delete(@path conversationName: string): void;,> },>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the @azure-tools/typespec-ts modular emitter so dataplane operations whose names are JavaScript reserved words (e.g., delete) are renamed when they belong to an operation group by suffixing the singularized operation-group name (e.g., deleteConversation), instead of relying on $-guarded names plus @fixme guidance.

Changes:

  • Update operation naming to disambiguate reserved-word dataplane operations using the innermost operation group name (singularized via pluralize.singular), while preserving the existing $-guard + @fixme fallback for top-level (non-grouped) operations.
  • Thread operation-group prefixes through modular operation generation helpers so the API-layer function, helpers, and public method stay consistent.
  • Add pluralize (+ typings) dependency and expand the modular-unit scenario coverage for grouped reserved-word operations.
Show a summary per file
File Description
pnpm-lock.yaml Locks newly added pluralize and @types/pluralize dependencies.
packages/typespec-ts/package.json Adds pluralize runtime dependency and @types/pluralize dev dependency for the emitter.
packages/typespec-ts/src/modular/helpers/naming-helpers.ts Enhances getOperationName to disambiguate reserved-word operations using singularized operation-group name and returns both name and propertyName.
packages/typespec-ts/src/modular/helpers/operation-helpers.ts Threads prefixes into getOperationName call sites and updates exception/header deserialization helpers to keep naming consistent.
packages/typespec-ts/src/modular/build-operations.ts Passes operation-group prefixes into header/exception header generation and LRO deserialization detail mapping.
packages/typespec-ts/test/util/emit-util.ts Wires enable-operation-group and hierarchy-client options into the modular operations test emitter helper.
packages/typespec-ts/test/modular-unit/scenarios/api-operations/reservedWordOperations.md Adds a scenario validating grouped delete is emitted as deleteConversation (no $-guard + fixme).
.chronus/changes/rename-reserved-word-operations-by-group-2026-08-04-17-30-00.md Adds a changelog entry describing the feature behavior and fallback behavior.

Review details

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
  • Files reviewed: 7/8 changed files
  • Comments generated: 0
  • Review effort level: Lite

@pkg-pr-new

pkg-pr-new Bot commented Aug 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@azure-tools/typespec-ts@5154

commit: 61bd3be

@azure-sdk-automation

Copy link
Copy Markdown
Contributor

You can try these changes here

🛝 Playground 🌐 Website

@maorleger
Maor Leger (maorleger) force-pushed the typespec-ts/rename-reserved-word-operations-by-group branch from 2b728b3 to 3ff135a Compare August 7, 2026 15:26
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📦 Package size report

✅ No notable package size changes compared to the base branch.

13 package(s) with no notable change
Package Packed (base → head) Δ Packed Unpacked (base → head) Δ Unpacked
@azure-tools/typespec-ts 524.67 KB → 526.32 KB +1.66 KB (+0.3%) 2.53 MB → 2.53 MB +6.59 KB (+0.3%)
@azure-tools/typespec-java 13.51 MB → 13.51 MB -35 B (-0.0%) 15.03 MB → 15.03 MB
@azure-tools/azure-http-specs 146.21 KB → 146.21 KB 1.16 MB → 1.16 MB
@azure-tools/typespec-autorest 80.93 KB → 80.93 KB 395.06 KB → 395.06 KB
@azure-tools/typespec-autorest-canonical 7.42 KB → 7.42 KB 26.00 KB → 26.00 KB
@azure-tools/typespec-azure-core 128.11 KB → 128.11 KB 696.78 KB → 696.78 KB
@azure-tools/typespec-azure-portal-core 42.40 KB → 42.40 KB 192.87 KB → 192.87 KB
@azure-tools/typespec-azure-resource-manager 171.29 KB → 171.29 KB 1.04 MB → 1.04 MB
@azure-tools/typespec-azure-rulesets 5.15 KB → 5.15 KB 31.73 KB → 31.73 KB
@azure-tools/typespec-client-generator-core 226.23 KB → 226.23 KB 1.22 MB → 1.22 MB
@azure-tools/typespec-go 244.29 KB → 244.29 KB 1.24 MB → 1.24 MB
@azure-tools/typespec-metadata 15.91 KB → 15.91 KB 62.26 KB → 62.26 KB
@azure-tools/typespec-python 46.21 KB → 46.21 KB 180.83 KB → 180.83 KB

Packed = gzipped .tgz published to npm. Unpacked = total extracted size. 🆕 added, 🗑️ removed. Packages from the core/ submodule are not included.
🔴 grew · 🟢 shrank — only changes of at least 512 B and 0.5% are marked.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (3)

packages/typespec-ts/src/modular/helpers/naming-helpers.ts:49

  • The synthesized name can collide with a real sibling operation. For example, a Conversations group containing both delete and deleteConversation maps both to deleteConversation; buildOperationFiles then emits duplicate function implementations/helpers, and the classic interface emits duplicate properties, producing invalid generated TypeScript. Detect sibling-name collisions before applying this rename and either choose a non-conflicting fallback or report a diagnostic requiring @clientName.
      const disambiguated = normalizeName(`${operation.name}_${suffix}`, NameType.Method);

packages/typespec-ts/src/modular/helpers/naming-helpers.ts:44

  • hierarchy-client: false bypasses this branch. getMethodHierarchiesMap prefixes grouped method names first (for example, delete becomes Conversations_delete at src/utils/operation-util.ts:669-675), so this check no longer sees a reserved word; additionally, the classic operation-group builder prefers oriName over the new propertyName at classical-operation-helpers.ts:365-375. That configuration therefore still exposes delete publicly and generates conversationsDelete internally instead of the promised deleteConversation. Preserve/check the unprefixed effective name and ensure the disambiguated property name wins in the classic surface.

This issue also appears on line 49 of the same file.

  if (isReservedName(operation.name, NameType.Method) && isDataplane && !hasClientNameOverride) {

packages/typespec-ts/test/modular-unit/scenarios/api-operations/reservedWordOperations.md:178

  • This scenario only snapshots the API operations file, while the PR also changes the separately generated classic client/operation-group public method. Please add a classicClient snapshot (including the explicit @clientName case and hierarchy-client: false) so the public name and its call into the renamed API function are verified. The scenario runner already supports ts classicClient; its emitter helper may also need the same option/TCGC wiring added above.
```yaml
enable-operation-group: true
</details>


- **Files reviewed:** 7/8 changed files
- **Comments generated:** 0 new
- **Review effort level:** Balanced

@xirzec Jeff Fisher (xirzec) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable to me, though please get Jialin Huang (@JialinHuang803) to take a look as well

if (!innermostGroup) {
return undefined;
}
return pluralize.singular(normalizeName(innermostGroup, NameType.Interface));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, a handy dependency that everyone uses but hasn't been updated in 8 years. Probably this is fine.

Maor Leger (maorleger) and others added 3 commits August 7, 2026 19:12
…p name

Reserved-word operations (e.g. `delete`) that belong to an operation group
are now renamed by suffixing the singularized operation group name instead of
emitting a `@fixme`. For example, `Conversations.delete` is generated as
`deleteConversation` rather than `$delete` with a fixme doc comment. This
renames the API-layer function, its send/deserialize helpers, and the public
method consistently while keeping the options type (e.g.
`ConversationsDeleteOptionalParams`) unchanged.

Operations without an operation group continue to fall back to the previous
guarded name and `@fixme` guidance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f7b5077-9928-4c7b-95b7-c62edbc5d4aa
…override is set

An explicit @clientName on a reserved-word data-plane operation is treated as an
intentional naming choice: the emitter keeps the requested public method name
(e.g. delete) and skips the reserved-word disambiguation and the @fixme. The
generated API-layer function stays guarded ($delete) since a reserved word is
not a valid JavaScript function binding. This is a backwards-compat escape hatch
for already-shipped libraries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5725cbfb-0e47-4b72-b651-194ffc2bc750
Merge the @clientName escape-hatch changelog entry into the existing
reserved-word rename changeset since both are feature changes to the same
package in the same PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5725cbfb-0e47-4b72-b651-194ffc2bc750
@maorleger
Maor Leger (maorleger) force-pushed the typespec-ts/rename-reserved-word-operations-by-group branch from 3ff135a to 61bd3be Compare August 7, 2026 19:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:typescript Issues for @azure-tools/typespec-ts emitter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants