Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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-update-for-patch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@azure-tools/typespec-azure-rulesets"
---

Register the TCGC `use-update-for-patch` lint rule as disabled in the client SDK ruleset.
7 changes: 7 additions & 0 deletions .chronus/changes/tcgc-use-update-for-patch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-client-generator-core"
---

Add the opt-in `use-update-for-patch` lint rule to require common SDK method names for ARM PATCH operations to start with `update`, honoring common client name overrides.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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-update-for-patch": false,
},
disable: {
"@azure-tools/typespec-client-generator-core/use-create-for-put":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,18 @@ describe("expect all rules to be defined", () => {
ok(!$linter.ruleSets?.[rulesetName].enable?.[ruleName]);
}
});

it("keeps PATCH SDK naming guidance opt-in", () => {
const ruleName = "@azure-tools/typespec-client-generator-core/use-update-for-patch";
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].enable?.[
"@azure-tools/typespec-azure-resource-manager/use-update-for-patch"
],
undefined,
);
}
});
});
1 change: 1 addition & 0 deletions packages/typespec-client-generator-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,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-update-for-patch`](https://azure.github.io/typespec-azure/docs/libraries/typespec-client-generator-core/rules/use-update-for-patch) | ARM PATCH SDK method names should use 'update' as the verb prefix. |
| [`@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
2 changes: 2 additions & 0 deletions packages/typespec-client-generator-core/src/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ 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 { useUnionHierarchyRule } from "./rules/use-union-hierarchy.js";
import { useUpdateForPatchRule } from "./rules/use-update-for-patch.js";

const rules = [
useCreateForPutRule,
useUpdateForPatchRule,
requireClientSuffixRule,
propertyNameConflictRule,
csharpNoUrlSuffixRule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
Use an SDK method name beginning with `update` for ARM PATCH operations, such as
`update`, `updateTags`, or `updateWidget`. This applies even when the operation uses
a standard ARM patch template, independently of its response status codes.

The rule checks the common TCGC SDK method name case-insensitively on concrete HTTP
endpoints. When enabled, it does not require `@armProviderNamespace`; select it for
ARM APIs, not data-plane APIs with different naming guidance. It is disabled by
default in the `client-sdk` ruleset.

Name resolution uses TCGC's `getLibraryName`, including an unscoped `@clientName`
override, `exact` client names, and the usual `@friendlyName` fallback.
Emitter-scoped overrides and OpenAPI `@operationId` values do not define the common
SDK method name and are ignored.

The source program is checked, including operations removed in later versions.
Historical names supplied through `@renamedFrom` are not reconstructed.

#### ❌ Incorrect

```typespec
@armResourceOperations
interface Widgets {
modify is ArmResourcePatchSync<Widget, Properties = WidgetProperties>;
}
```

#### ✅ Correct

```typespec
@armResourceOperations
interface Widgets {
update is ArmResourcePatchSync<Widget, Properties = WidgetProperties>;
}
```

These examples assume an ARM service, a tracked resource named `Widget`, and its
property model `WidgetProperties`.

## Impact

- **Area:** SDK

Consistent update-method naming makes partial updates easier to discover in
generated clients. Renaming a method after an SDK has shipped may introduce a
breaking change to its public API. Review compatibility before changing an
existing SDK method name.

An unscoped `@clientName` can provide the correct SDK name without renaming the
TypeSpec operation:

```typespec
@armResourceOperations
interface Widgets {
@Azure.ClientGenerator.Core.clientName("updateWidget")
modify is ArmResourcePatchSync<Widget, Properties = WidgetProperties>;
}
```

Import `@azure-tools/typespec-client-generator-core` to use `@clientName`.

## Suppression

Suppression is acceptable when preserving an already-shipped SDK method name
requires an exception approved by the API reviewer. For new operations, fix the
SDK method name instead, either by renaming the operation or using `@clientName`.

```typespec
@armResourceOperations
interface Widgets {
#suppress "@azure-tools/typespec-client-generator-core/use-update-for-patch" "Preserve the already-shipped SDK method name."
modify is ArmResourcePatchSync<Widget, Properties = WidgetProperties>;
}
```

## LintDiff Equivalent

This rule enforces the ARM SDK method-naming intent of
[`PatchInOperationName`](https://github.com/Azure/azure-openapi-validator/blob/aec54e95e52338d0afa911a92958541d5624c105/docs/patch-in-operation-name.md).
It follows TCGC's `use-create-for-put` common SDK naming pattern rather than
reproducing Swagger `operationId` group-name exemptions or checking only authored
TypeSpec names. Common SDK name overrides intentionally affect this rule; no
emitter or version projection is used.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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";

export const useUpdateForPatchRule = createRule({
name: "use-update-for-patch",
description: "ARM PATCH SDK method names should use 'update' as the verb prefix.",
severity: "warning",
url: "https://azure.github.io/typespec-azure/docs/libraries/typespec-client-generator-core/rules/use-update-for-patch",
docs: fileRef.fromPackageRoot("src/rules/use-update-for-patch.md"),
messages: {
default: paramMessage`PATCH SDK method name '${"operationName"}' should start with 'update'. 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);
for (const service of services) {
for (const { operation, verb } of service.operations) {
if (verb !== "patch") {
continue;
}

// Check the common SDK name, leaving emitter-specific overrides to emitter rules.
const operationName = getLibraryName(tcgcContext, operation, AllScopes);
if (operationName.toLowerCase().startsWith("update")) {
continue;
}

context.reportDiagnostic({
target: operation,
format: { operationName },
});
}
}
},
};
},
});
Loading
Loading