Repository navigation
[Swagger Linter Migration] PutInOperationName + PatchInOperationName + DeleteInOperationName -> use-correct-verb #5673
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3e7122b
5d131d4
abeb615
ef1e589
6475b75
e3785aa
2af91d5
6f0eb98
5a9015b
bdb8b3f
fee3ceb
30ae8a4
6fcf466
90e1e7d
ed35661
862100d
380a163
6b9be23
50c4b74
5fc361c
8710828
d121756
e0ee4cd
a93d695
e564326
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| changeKind: internal | ||
| packages: | ||
| - "@azure-tools/typespec-azure-rulesets" | ||
| --- | ||
|
|
||
| Replace the separate SDK verb naming registrations with `@azure-tools/typespec-client-generator-core/use-correct-verb`, disabled by default in the `client-sdk` ruleset. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| changeKind: feature | ||
| packages: | ||
| - "@azure-tools/typespec-client-generator-core" | ||
| --- | ||
|
|
||
| Consolidate `use-create-for-put`, PATCH naming guidance, and DELETE naming guidance into the opt-in `use-correct-verb` lint rule. Common SDK method names must start with `create` for PUT, `update` for PATCH, and `delete` for DELETE, case-insensitively. Replace `use-create-for-put` in linter configuration and suppressions with `use-correct-verb`; enabling it also checks PATCH and DELETE. Renaming an already-shipped SDK method may be a breaking change. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| Use common SDK method names beginning with `create` for PUT, `update` for PATCH, | ||
| and `delete` for DELETE. Prefix matching is case-insensitive. GET, POST, HEAD, | ||
| and other HTTP verbs are outside this rule. | ||
|
|
||
| The rule checks concrete HTTP service endpoints in ARM and data-plane services. | ||
| It does not require ARM provider metadata or check client-only declarations | ||
| outside services. It resolves common names through TCGC, honoring unscoped | ||
| `@clientName`, exact names, and the supported `@friendlyName` fallback. | ||
| Language-specific overrides and explicit OpenAPI operation IDs do not change | ||
| the common name. Diagnostics target the original operation. | ||
|
|
||
| The rule is disabled by default in the `client-sdk` ruleset. It does not select | ||
| an emitter or API-version projection, or reconstruct historical renamed names. | ||
|
|
||
| Use this shared setup before each ARM example: | ||
|
|
||
| ```typespec | ||
| import "@typespec/http"; | ||
| import "@typespec/rest"; | ||
| import "@azure-tools/typespec-azure-resource-manager"; | ||
| import "@azure-tools/typespec-client-generator-core"; | ||
|
|
||
| using TypeSpec.Http; | ||
| using TypeSpec.Rest; | ||
| using Azure.ResourceManager; | ||
| using Azure.ClientGenerator.Core; | ||
|
|
||
| @armProviderNamespace | ||
| @service | ||
| @armCommonTypesVersion(CommonTypes.Versions.v5) | ||
| namespace Example; | ||
|
|
||
| model Widget is TrackedResource<WidgetProperties> { | ||
| @key("widgetName") @segment("widgets") name: string; | ||
| } | ||
|
|
||
| model WidgetProperties { | ||
| provisioningState?: ResourceProvisioningState; | ||
| } | ||
| ``` | ||
|
|
||
| #### ❌ Incorrect | ||
|
|
||
| ```typespec | ||
| @armResourceOperations | ||
| interface Widgets { | ||
| replace is ArmResourceCreateOrReplaceAsync<Widget>; | ||
| modify is ArmResourcePatchSync<Widget, Properties = WidgetProperties>; | ||
| remove is ArmResourceDeleteSync<Widget>; | ||
| } | ||
| ``` | ||
|
|
||
| #### ✅ Correct | ||
|
|
||
| ```typespec | ||
| @armResourceOperations | ||
| interface Widgets { | ||
| createOrUpdate is ArmResourceCreateOrReplaceAsync<Widget>; | ||
| update is ArmResourcePatchSync<Widget, Properties = WidgetProperties>; | ||
| delete is ArmResourceDeleteSync<Widget>; | ||
| } | ||
| ``` | ||
|
|
||
| ## Impact | ||
|
|
||
| - **Area:** SDK | ||
|
|
||
| Renaming an already-shipped SDK method may introduce a breaking change. | ||
| Review compatibility before changing names. An unscoped `@clientName` changes | ||
| the common SDK name without renaming the TypeSpec operation: | ||
|
|
||
| ```typespec | ||
| @armResourceOperations | ||
| interface Widgets { | ||
| @clientName("createOrUpdate") | ||
| replace is ArmResourceCreateOrReplaceAsync<Widget>; | ||
| @clientName("updateWidget") | ||
| modify is ArmResourcePatchSync<Widget, Properties = WidgetProperties>; | ||
| @clientName("deleteWidget") | ||
| remove is ArmResourceDeleteSync<Widget>; | ||
| } | ||
| ``` | ||
|
|
||
| ## Suppression | ||
|
|
||
| For new methods, fix the name or use an unscoped `@clientName`. When preserving | ||
| an already-shipped name requires an API-reviewer-approved exception, suppress | ||
| the warning on that operation with a justification: | ||
|
|
||
| ```typespec | ||
| @armResourceOperations | ||
| interface Widgets { | ||
| #suppress "@azure-tools/typespec-client-generator-core/use-correct-verb" "Preserve the already-shipped SDK method name." | ||
| remove is ArmResourceDeleteSync<Widget>; | ||
| } | ||
| ``` | ||
|
|
||
| ## Migration | ||
|
|
||
| This rule replaces `use-create-for-put` and consolidates the proposed | ||
| `use-update-for-patch` and `use-delete-for-delete` rules. Replace the old name | ||
| in linter configuration and suppressions with `use-correct-verb`. | ||
| Enabling the replacement checks all three verbs, not just PUT. | ||
|
|
||
| ## LintDiff Equivalent | ||
|
|
||
| | Swagger validator | Native SDK naming requirement | | ||
| | -------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | ||
| | [PutInOperationName](https://github.com/Azure/azure-openapi-validator/blob/main/docs/put-in-operation-name.md) | PUT names start with `create`. | | ||
| | [PatchInOperationName](https://github.com/Azure/azure-openapi-validator/blob/main/docs/patch-in-operation-name.md) | PATCH names start with `update`. | | ||
| | [DeleteInOperationName](https://github.com/Azure/azure-openapi-validator/blob/main/docs/delete-in-operation-name.md) | DELETE names start with `delete`. | | ||
|
|
||
| The native rule checks common SDK names rather than serialized `operationId` | ||
| or lintdiff authored names. It intentionally does not reproduce underscore, | ||
| group-prefix, or missing-ID exemptions. No emitted Swagger parity is claimed. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { createRule, fileRef, paramMessage } from "@typespec/compiler"; | ||
| import { getAllHttpServices } from "@typespec/http"; | ||
| import { createTCGCContext } from "../context.js"; | ||
| import { AllScopes } from "../internal-utils.js"; | ||
| import { getLibraryName } from "../public-utils.js"; | ||
|
|
||
| const verbPrefixes = { put: "create", patch: "update", delete: "delete" }; | ||
|
|
||
| export const useCorrectVerbRule = createRule({ | ||
| name: "use-correct-verb", | ||
| description: | ||
| "PUT, PATCH, and DELETE SDK method names should start with 'create', 'update', and 'delete', respectively.", | ||
| severity: "warning", | ||
| url: "https://azure.github.io/typespec-azure/docs/libraries/typespec-client-generator-core/rules/use-correct-verb", | ||
| docs: fileRef.fromPackageRoot("src/rules/use-correct-verb.md"), | ||
| messages: { | ||
| default: paramMessage`${"verb"} SDK method name '${"operationName"}' should start with '${"prefix"}'. Note: If you have already shipped an SDK on top of this spec, fixing this warning may introduce a breaking change.`, | ||
| }, | ||
| create(context) { | ||
| const tcgcContext = createTCGCContext( | ||
| context.program, | ||
| "@azure-tools/typespec-client-generator-core", | ||
| { mutateNamespace: false }, | ||
| ); | ||
|
|
||
| return { | ||
| root: () => { | ||
| const [services] = getAllHttpServices(context.program); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this use the native A parent service recursively includes its nested service's operations, and @service namespace Outer {
@service namespace Inner {
@delete @route("/widgets") op remove(): void;
}
}Resolve the HTTP metadata per eligible operation and check it once through the existing linter traversal. Preserve the current service scope, including the no-explicit-service global fallback, and keep a focused regression expecting one diagnostic for the nested-service case. Copilot-generated comment. |
||
| for (const service of services) { | ||
| for (const { operation, verb } of service.operations) { | ||
| if (verb !== "put" && verb !== "patch" && verb !== "delete") continue; | ||
| const prefix = verbPrefixes[verb]; | ||
| const operationName = getLibraryName(tcgcContext, operation, AllScopes); | ||
| if (!operationName.toLowerCase().startsWith(prefix)) { | ||
| context.reportDiagnostic({ | ||
| target: operation, | ||
| format: { verb: verb.toUpperCase(), operationName, prefix }, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| }; | ||
| }, | ||
| }); | ||
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this shared-setup block. Rule documentation examples do not need to be complete, independently compilable specifications. The imports/usings, service namespace, common-types version and resource/provisioning-state declarations bury the SDK-name problem we want authors to see.
Keep short before/after operation snippets that make
replace/modify/removeversuscreate*/update*/delete*obvious, and a small@clientNamefix where useful. You can assumeWidgetand related resource types are defined. Keep standard ARM templates, but do not add a setup appendix or another shared preamble just to make the examples compile. Apply this focused-example style to the rest of the page too.Copilot-generated comment.