Skip to content

Add arm-resource-required-operations linter rule#4380

Draft
Copilot wants to merge 9 commits intomainfrom
copilot/add-arm-resource-required-operations-rule
Draft

Add arm-resource-required-operations linter rule#4380
Copilot wants to merge 9 commits intomainfrom
copilot/add-arm-resource-required-operations-rule

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 5, 2026

Adds a new singleton-aware arm-resource-required-operations linter rule to @azure-tools/typespec-azure-resource-manager (and registers it in the @azure-tools/typespec-azure-rulesets resource-manager preset). The rule supersedes no-resource-delete-operation; both remain registered for backwards compatibility.

Required-operation matrix

Resource kind Required operations
Singleton (any kind) read, createOrUpdate
Tracked (top-level, resource-group-scoped) read, createOrUpdate, delete, list-by-parent (a list-by-resource-group operation satisfies this), list-by-subscription
Tracked (tenant / location / nested) read, createOrUpdate, delete, list-by-parent
Proxy / Extension read, createOrUpdate, delete, list-by-parent

@armVirtualResource and other "Other"-kind resources are skipped.

Implementation notes

  • Uses resolveArmResources (handles both standard and legacy resource operations) and iterates the returned ResolvedResource entries via a root: listener — called once per program.
  • Reads kind, singleton, and operations directly from each ResolvedResource (no separate getArmResourceKind / isSingletonResource lookups).
  • A single declared resource can produce multiple ResolvedResource entries (for example, ARM operation templates emit an additional path for createOrUpdate when a child resource is nested). Entries are grouped by model identity and a single diagnostic is reported per logical resource using the union of their operations; the canonical entry (shortest resourceType.types) is used for kind/singleton/name.
  • List categorization:
    • For non-tracked resources (Proxy / Extension), any list operation satisfies list-by-parent (path-independent).
    • For tracked resources, paths with /subscriptions/{ and no /resourceGroups/{ and a single /providers/ segment count as list-by-subscription; everything else (by-resource-group, by-location, by-parent) counts as list-by-parent.
  • list-by-subscription is required only for top-level resource-group-scoped tracked resources (parent undefined and instance path contains /resourceGroups/{). Tenant-scoped, location-scoped, and nested tracked resources do not require it.
  • Specialized message IDs (missingGet, missingCreateOrUpdate, missingDelete, missingListByParent, missingListBySubscription) when exactly one operation is missing; otherwise a single default diagnostic listing all missing operations.
  • Codefixes splice templated operation declarations into the @armResourceOperations interface.

Sample updates

Existing samples were updated to satisfy the new rule:

  • legacy/optional-body and legacy/rename-operations: added listBySubscription to the Widget resource (a real omission for resource-group-scoped tracked resources).
  • legacy/nsp and resource-types/nsp: added #suppress for the rule on the NSP-configuration interface (NSP configurations are read-only by design).
  • legacy/polymorphic-resource/proxy.tsp and legacy/polymorphic-resource/extension.tsp: added #suppress for the rule on interfaces that intentionally demonstrate read-only polymorphic proxy/extension variants.

Diff vs main

  • src/rules/arm-resource-required-operations.ts (new)
  • src/linter.ts (register rule)
  • test/rules/arm-resource-required-operations.test.ts (new — 12 tests covering full set, single-missing variants for each op including missingGet, missingCreateOrUpdate, missingDelete, missingListByParent for a tracked resource missing list-by-resource-group, missingListBySubscription for a tracked resource missing list-by-subscription, nested proxy missing list-by-parent, top-level extension valid (path-independent list classification), singletons (valid + missing-read), and @armVirtualResource skip)
  • packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts (register rule)
  • website/.../rules/arm-resource-required-operations.md (new docs page)
  • website/.../rules/no-resource-delete-operation.md (cross-link)
  • website/.../reference/linter.md (index entry)
  • .chronus/changes/arm-resource-required-operations-...md
  • Sample updates in packages/samples/specs/resource-manager/... and corresponding regenerated snapshots under packages/samples/test/output/...

Copilot AI and others added 2 commits May 5, 2026 18:04
…rce-required-operations-rule

Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Copilot AI requested a review from markcowl May 5, 2026 18:13
@markcowl markcowl changed the title Add arm-resource-required-operations rule and extend arm-resource-patch Add arm-resource-required-operations rule May 5, 2026
Copy link
Copy Markdown
Member

@markcowl markcowl left a comment

Choose a reason for hiding this comment

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

Please pull the latest from main, make the changes requested, ensure that all tests are passing, all files are formatted correctly, that samples rebuild correctly and that the PR diff contains only changes associated with this pr

return {
model: (model: Model) => {
if (isInternalTypeSpec(context.program, model)) return;
const armResource = getArmResources(context.program).find((r) => r.typespecType === model);
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.

getArmResources is an expensive operation. We shgoulr un this once for the prgram and filter the results for each resource model.

Also, we should use resolveArmResources instead of getArmResources, as it handles both standard and legacy resource operations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Switched to resolveArmResources, called once per program and cached in a Map<Model, ResolvedResource[]> closure that's reused for every model visit (5b963c9).

): RequiredOperation[] {
const kind = armResource.kind;
if (kind !== "Tracked" && kind !== "Proxy" && kind !== "Extension") {
return [];
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.

All resources that are not singletons should have read, list-by-parent

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Unified the requirements so every non-singleton resource needs read + createOrUpdate + delete + list-by-parent. Tracked resources additionally need list-by-subscription, and a list-by-resource-group operation satisfies the list-by-parent requirement at resource-group scope (5b963c9).

Copilot AI and others added 2 commits May 5, 2026 21:19
…rce-required-operations-rule

Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 5, 2026

Please pull the latest from main, make the changes requested, ensure that all tests are passing, all files are formatted correctly, that samples rebuild correctly and that the PR diff contains only ch...

Merged latest main, applied the requested rule changes, ran pnpm format, and verified the full ARM test suite (300 tests) and rulesets tests pass. PR diff vs main only contains files for the new arm-resource-required-operations rule (5b963c9).

Copilot AI changed the title Add arm-resource-required-operations rule Add arm-resource-required-operations rule May 5, 2026
Copilot AI requested a review from markcowl May 5, 2026 21:32
Copy link
Copy Markdown
Member

@markcowl markcowl left a comment

Choose a reason for hiding this comment

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

Pull the latest from main, make the requested changes, validate that all tests pass and all files are formatted correctly, that your branch is up to date with main and that all changes in the PR diff are part of this PR.

},
create(context) {
// Resolve resources once for the program and reuse for every model visit.
let resourcesByModel: Map<Model, ResolvedResource[]> | undefined;
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.

This is the wrong way to use the result of resolveARmResources. Each ResolvedResource is a resource with its operations and you should use this rather than making other calls to to assemble resource definitions.

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.

The 'model' does not define a resource in some scenarios - some scenarios use a single model to describe multiple resources, so using the returned ResolvedResource array from resolveArmResources uniquely defines each resource, and you should be able to get the resource kind from this as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Restructured to iterate ResolvedResource entries from resolveArmResources via a root: listener and read kind, singleton, and operations directly off each entry — no auxiliary lookups for those. Because a single declared resource can produce multiple ResolvedResource entries (ARM operation templates emit extra paths for createOrUpdate when a nested resource exists), entries are grouped by model identity and one diagnostic is reported per logical resource using the union of their operations (38144d7).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Switched to iterating each ResolvedResource returned by resolveArmResources (root listener). Kind/singleton are now read from the ResolvedResource itself. Multiple entries that the resolver produces for the same model (path-doubling for createOrUpdate under nested resources) are grouped per model and merged into a single logical-resource check, using the canonical (shortest resourceType.types) entry for kind/singleton/name (38144d7).

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 6, 2026

Pull the latest from main, make the requested changes, validate that all tests pass and all files are formatted correctly, that your branch is up to date with main and that all changes in the PR diff ...

Branch is up to date with main (no new upstream commits since the last sync). Applied the requested ResolvedResource-based restructuring, ran pnpm format, and verified the full ARM test suite (300/300) and rulesets tests pass. PR diff vs main only contains files for the new arm-resource-required-operations rule (38144d7).

Copilot AI changed the title Add arm-resource-required-operations rule Add arm-resource-required-operations linter rule May 6, 2026
Copilot AI requested a review from markcowl May 6, 2026 02:41
@markcowl
Copy link
Copy Markdown
Member

markcowl commented May 7, 2026

@copilot there is a merge conflict. Pull the latest from main, merge your changes, validate that all tests pass and all files are formatted correctly, that your branch is up to date with main and that all changes in the PR diff are part of this PR.

…rce-required-operations-rule

Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/f98c08fb-bae8-4462-a33e-47862ea7bfed

Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 7, 2026

@copilot there is a merge conflict. Pull the latest from main, merge your changes, validate that all tests pass and all files are formatted correctly, that your branch is up to date with main and that...

Merged latest main and resolved the conflict in website/.../reference/linter.md (main reformatted rule link paths to ../rules/... style; the new arm-resource-required-operations row was re-added using the same style). Ran pnpm format, full ARM test suite passes (300/300), branch is up to date with main (ede43a4).

@microsoft-github-policy-service microsoft-github-policy-service Bot added the lib:tcgc Issues for @azure-tools/typespec-client-generator-core library label May 7, 2026
Copy link
Copy Markdown
Member

@markcowl markcowl left a comment

Choose a reason for hiding this comment

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

There are merge conflicts. Please pull the latest from main and fix any merge conflicts. Analyze the tests and verify that you have good coverage of the code with this set of tests - if not, add additional tests.

Ensure all tests pass, samples rebuild cleanly, all files are formatted correctly and only the files associated with this pul request are in the pull request diff

Copilot AI and others added 2 commits May 7, 2026 19:34
…rce-required-operations-rule

# Conflicts:
#	core
#	website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md

Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 7, 2026

There are merge conflicts. Please pull the latest from main and fix any merge conflicts. Analyze the tests and verify that you have good coverage of the code with this set of tests - if not, add ad...

Merged latest main (resolved core submodule + linter.md conflicts). Refined the rule's list-categorization heuristic so non-tracked resources accept any list as list-by-parent, and tracked resources only require list-by-subscription when they're top-level resource-group-scoped (tenant/location/nested tracked resources don't). Added 3 more tests covering missingCreateOrUpdate, singleton-missing-read, and top-level extension valid (12 tests total). Updated samples to either declare missing operations (listBySubscription on Widget in legacy/optional-body and legacy/rename-operations) or #suppress the rule on intentionally read-only resources (NSP configurations, polymorphic proxy/extension demos). Full ARM tests (303/303), rulesets (3/3), and sample snapshot tests (68/68) pass; pnpm format clean; PR diff scoped to this rule (545ef74).

Copilot AI requested a review from markcowl May 7, 2026 19:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lib:azure-resource-manager Issues for @azure-tools/typespec-azure-core library lib:tcgc Issues for @azure-tools/typespec-client-generator-core library meta:website TypeSpec.io updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants