[typespec-ts] Rename reserved-word operations using singularized group name - #5154
Conversation
|
All changed packages have been documented.
Show changes
|
There was a problem hiding this comment.
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 +@fixmefallback for top-level (non-grouped) operations. - Thread operation-group
prefixesthrough 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
commit: |
|
You can try these changes here
|
2b728b3 to
3ff135a
Compare
📦 Package size report✅ No notable package size changes compared to the base branch. 13 package(s) with no notable change
Packed = gzipped |
There was a problem hiding this comment.
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
Conversationsgroup containing bothdeleteanddeleteConversationmaps both todeleteConversation;buildOperationFilesthen 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: falsebypasses this branch.getMethodHierarchiesMapprefixes grouped method names first (for example,deletebecomesConversations_deleteatsrc/utils/operation-util.ts:669-675), so this check no longer sees a reserved word; additionally, the classic operation-group builder prefersoriNameover the newpropertyNameatclassical-operation-helpers.ts:365-375. That configuration therefore still exposesdeletepublicly and generatesconversationsDeleteinternally instead of the promiseddeleteConversation. 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
classicClientsnapshot (including the explicit@clientNamecase andhierarchy-client: false) so the public name and its call into the renamed API function are verified. The scenario runner already supportsts 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
Jeff Fisher (xirzec)
left a comment
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
ah, a handy dependency that everyone uses but hasn't been updated in 8 years. Probably this is fine.
…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
3ff135a to
61bd3be
Compare
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@fixmedoc comment on a$-guarded name.For example, given an operation
deletein theConversationsoperation group: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
deletemethod is acceptable and suppressed the@fixmea while back (see autorest.typescript#3749), anddeletehas been generated that way going back to HLC. Renaming it now would be a breaking change for shipped mgmt SDKs, so ARM keeps emitting plaindeletewith no rename and no@fixme.Backwards-compat escape hatch via
@clientNameThe rename itself is a breaking change for any data-plane library that already shipped with the guarded
deletename. To give those libraries an out, an explicit@clientNameon the operation is now treated as an intentional choice: we honor the requested name verbatim and skip both the rename and the@fixme.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
deleteConversation) and its_deleteConversationSend/_deleteConversationDeserializehelpers, as well as the public method exposed on clients and operation groups.ConversationsDeleteOptionalParams) is intentionally left unchanged.pluralize.singular, which correctly handles cases likeKnowledgeBases→KnowledgeBase,Conversations→Conversation,Categories→Category.$delete) and@fixmeguidance, since there's no group name to disambiguate with — unless a@clientNameoverride is present, in which case the escape hatch above applies. This is a very rare case.Validation
modular-unit: 661/661.test-next: 254/254.