Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3e7122b
Add ARM use-update-for-patch lint rule
msyyc Oct 10, 2026
5d131d4
Move PATCH SDK naming guidance to TCGC common names
msyyc Oct 10, 2026
abeb615
feat(tcgc): add opt-in DELETE SDK naming rule
msyyc Oct 12, 2026
ef1e589
Address promotion review feedback
msyyc Oct 12, 2026
6475b75
Use ARM templates in DELETE naming rule documentation
msyyc Oct 13, 2026
e3785aa
Merge branch 'main' into promote-lintdiff-delete-in-operation-name
msyyc Oct 13, 2026
2af91d5
refactor(tcgc): share PATCH and PUT SDK verb-prefix checks
msyyc Oct 14, 2026
6f0eb98
refactor(tcgc): share DELETE and PUT SDK verb-prefix checks
msyyc Oct 14, 2026
5a9015b
test(tcgc): remove PATCH version and suppression-only fixtures
msyyc Oct 14, 2026
bdb8b3f
test(tcgc): remove DELETE suppression-only fixture
msyyc Oct 14, 2026
fee3ceb
Merge branch 'main' into promote-lintdiff-patch-in-operation-name
msyyc Oct 14, 2026
30ae8a4
Merge branch 'main' into promote-lintdiff-delete-in-operation-name
msyyc Oct 14, 2026
6fcf466
Share standalone ARM fixtures and isolate DELETE dependency proof
msyyc Oct 15, 2026
90e1e7d
Move native SDK linter emitter isolation to package integration tests
msyyc Oct 15, 2026
ed35661
Merge branch 'main' into promote-lintdiff-patch-in-operation-name
msyyc Oct 15, 2026
862100d
Merge branch 'main' into promote-lintdiff-delete-in-operation-name
msyyc Oct 15, 2026
380a163
Merge branch 'main' into promote-lintdiff-delete-in-operation-name
msyyc Oct 16, 2026
6b9be23
test(tcgc): simplify naming suites and package isolation
msyyc Oct 19, 2026
50c4b74
test(tcgc): simplify DELETE groups and package isolation
msyyc Oct 19, 2026
5fc361c
Merge branch 'main' into promote-lintdiff-delete-in-operation-name
msyyc Oct 19, 2026
8710828
Merge main and preserve rule inventory assertions for #5665
msyyc Oct 19, 2026
d121756
Consolidate PUT, PATCH, and DELETE SDK naming rules
msyyc Oct 20, 2026
e0ee4cd
Merge remote-tracking branch 'origin/main' into promote-lintdiff-dele…
Copilot Oct 20, 2026
a93d695
test(tcgc): remove redundant package isolation guard
msyyc Oct 20, 2026
e564326
Merge branch 'main' into promote-lintdiff-delete-in-operation-name
msyyc Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .chronus/changes/register-use-correct-verb.md
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.
7 changes: 7 additions & 0 deletions .chronus/changes/use-correct-verb.md
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.
2 changes: 1 addition & 1 deletion eng/scripts/doc-updater/knowledge/tcgc.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ namespace (@clientNamespace), naming (@clientName), overload, structure (@client
## SDK Method Naming Rules

- `get-operation-name` checks the common TCGC SDK name of concrete GET operations and requires a `get` or `list` prefix. It honors unscoped `@clientName`, ignores emitter-scoped overrides and OpenAPI operation IDs, and skips template declarations/artifacts and non-GET operations.
- `use-create-for-put` checks concrete PUT endpoints and requires the common TCGC SDK name to start with `create`, case-insensitively. It uses the same common-name resolution, applies without requiring ARM provider metadata, and is disabled by default in the `client-sdk` ruleset.
- `use-correct-verb` checks concrete PUT, PATCH, and DELETE endpoints and requires the common TCGC SDK name to start with `create`, `update`, and `delete`, respectively, case-insensitively. It replaces `use-create-for-put`, uses the same common-name resolution, applies without requiring ARM provider metadata, and is disabled by default in the `client-sdk` ruleset.
- Linter-only naming rules do not alter the generated client graph or wire behavior, so their unit tests and generated rule reference pages are the appropriate coverage; do not add Spector carrier scenarios for them.

## isExactName Property (May 2026)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ export default {
"@azure-tools/typespec-client-generator-core/csharp-model-suffix": true,
"@azure-tools/typespec-client-generator-core/use-union-hierarchy": true,
"@azure-tools/typespec-client-generator-core/get-operation-name": false,
"@azure-tools/typespec-client-generator-core/use-correct-verb": false,
},
disable: {
"@azure-tools/typespec-client-generator-core/use-create-for-put":
"Opt-in ARM naming guidance; data-plane PUT methods may use other verbs.",
"@azure-tools/typespec-client-generator-core/csharp-use-standard-acronyms":
"Disabled until the C# emitter handles standard acronym casing.",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,33 @@ describe("expect all rules to be defined", () => {
ruleset.enable?.["@azure-tools/typespec-client-generator-core/get-operation-name"],
false,
);
strictEqual(
ruleset.enable?.["@azure-tools/typespec-client-generator-core/use-correct-verb"],
false,
);
ok(
ruleset.disable?.["@azure-tools/typespec-client-generator-core/csharp-use-standard-acronyms"],
);
});

it("keeps PUT SDK naming guidance opt-in", () => {
const ruleName = "@azure-tools/typespec-client-generator-core/use-create-for-put";
ok($linter.ruleSets?.["client-sdk"].disable?.[ruleName]);
for (const rulesetName of ["data-plane", "resource-manager", "client-sdk"]) {
ok(!$linter.ruleSets?.[rulesetName].enable?.[ruleName]);
it("keeps combined SDK naming guidance opt-in", () => {
const ruleName = "@azure-tools/typespec-client-generator-core/use-correct-verb";
strictEqual($linter.ruleSets?.["client-sdk"].enable?.[ruleName], false);
for (const rulesetName of ["data-plane", "resource-manager"]) {
strictEqual($linter.ruleSets?.[rulesetName].enable?.[ruleName], undefined);
strictEqual($linter.ruleSets?.[rulesetName].disable?.[ruleName], undefined);
}
for (const oldName of ["use-create-for-put", "use-update-for-patch", "use-delete-for-delete"]) {
for (const ruleset of Object.values($linter.ruleSets ?? {})) {
strictEqual(
ruleset.enable?.[`@azure-tools/typespec-client-generator-core/${oldName}`],
undefined,
);
strictEqual(
ruleset.disable?.[`@azure-tools/typespec-client-generator-core/${oldName}`],
undefined,
);
}
}
});

Expand Down
2 changes: 1 addition & 1 deletion packages/typespec-client-generator-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Available ruleSets:

| Name | Description |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`@azure-tools/typespec-client-generator-core/use-create-for-put`](https://azure.github.io/typespec-azure/docs/libraries/typespec-client-generator-core/rules/use-create-for-put) | ARM PUT SDK method names should use 'create' as the verb prefix. |
| [`@azure-tools/typespec-client-generator-core/use-correct-verb`](https://azure.github.io/typespec-azure/docs/libraries/typespec-client-generator-core/rules/use-correct-verb) | PUT, PATCH, and DELETE SDK method names should start with 'create', 'update', and 'delete', respectively. |
| [`@azure-tools/typespec-client-generator-core/require-client-suffix`](https://azure.github.io/typespec-azure/docs/libraries/typespec-client-generator-core/rules/require-client-suffix) | Client names should end with 'Client'. |
| [`@azure-tools/typespec-client-generator-core/property-name-conflict`](https://azure.github.io/typespec-azure/docs/libraries/typespec-client-generator-core/rules/property-name-conflict) | Avoid naming conflicts between a property and a model of the same name. |
| [`@azure-tools/typespec-client-generator-core/csharp-no-url-suffix`](https://azure.github.io/typespec-azure/docs/libraries/typespec-client-generator-core/rules/csharp-no-url-suffix) | Properties ending with 'Url' should use 'Uri' suffix instead to follow .NET naming conventions. |
Expand Down
4 changes: 2 additions & 2 deletions packages/typespec-client-generator-core/src/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { csharpUseStandardAcronymsRule } from "./rules/csharp-use-standard-acron
import { getOperationNameRule } from "./rules/get-operation-name.rule.js";
import { propertyNameConflictRule } from "./rules/property-name-conflict.rule.js";
import { requireClientSuffixRule } from "./rules/require-client-suffix.rule.js";
import { useCreateForPutRule } from "./rules/use-create-for-put.js";
import { useCorrectVerbRule } from "./rules/use-correct-verb.js";
import { useUnionHierarchyRule } from "./rules/use-union-hierarchy.js";

const rules = [
useCreateForPutRule,
useCorrectVerbRule,
requireClientSuffixRule,
propertyNameConflictRule,
csharpNoUrlSuffixRule,
Expand Down
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:

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.

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/remove versus create*/update*/delete* obvious, and a small @clientName fix where useful. You can assume Widget and 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.


```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);

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.

Could this use the native operation listener instead of enumerating every service from root?

A parent service recursively includes its nested service's operations, and getAllHttpServices also returns the nested service separately. This currently produces two identical DELETE-prefix warnings for the single operation in:

@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.

Loading
Loading