Skip to content

Add arm-no-path-casing-conflicts linter rule#4379

Draft
Copilot wants to merge 11 commits intomainfrom
copilot/add-arm-no-path-casing-rule
Draft

Add arm-no-path-casing-conflicts linter rule#4379
Copilot wants to merge 11 commits intomainfrom
copilot/add-arm-no-path-casing-rule

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 5, 2026

arm-no-path-casing-conflicts linter rule

  • Implement rule + custom codefix
  • Register in packages/typespec-azure-resource-manager/src/linter.ts
  • Enable in @azure-tools/typespec-azure-rulesets resource-manager ruleset
  • Tests for diagnostic, identical paths no-op, unrelated paths, codefix, internal-namespace skip
  • Include path parameter names in case-insensitive comparison ({scope} vs {Scope} collide; {scope} vs {resourceUri} do not)
  • Tests covering bucketing for /{resourceUri}/... vs /{scope}/... (different buckets) and /{scope}/... vs /{Scope}/... (same bucket)
  • Test that paths differing only by parameter name casing emit a diagnostic
  • Codefix test: Bars resource now uses ArmResourceDeleteSync<Bar> so the post-fix code compiles (different verb on same path as Foos.get is ArmResourceRead<Foo>)
  • New test: two @route operations differing only by static-segment casing — one @delete, one @get — emits a diagnostic
  • Docs page + linter index entry
  • Chronus changeset
  • Merged latest main; pnpm format clean; PR diff against main contains only the 8 files for this change
  • validate-rules-defined test passes (3/3); ARM rule tests pass (9/9)

@microsoft-github-policy-service microsoft-github-policy-service Bot added lib:azure-resource-manager Issues for @azure-tools/typespec-azure-core library meta:website TypeSpec.io updates labels May 5, 2026
* Replace `{paramName}` placeholders with `{}` so that paths only differing
* by parameter name still get bucketed together.
*/
function normalizePath(path: string): string {
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 unnecessary and duplicative. We already have a candidate rule for paths that differ by parameter name, her we want paths that differ only by casing of static path segments.

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.

Updated in e8fdf16: parameter-name-only casing differences no longer trigger this rule. Bucketing and comparison now operate on static segments only (parameters normalized to {}), so paths that differ only by parameter name casing are skipped and left to the separate candidate rule. Test and docs updated accordingly.

* a CodeFix that lowercases the matching `@segment("...")` decorator string
* literals reachable from this operation.
*
* If any differing token is a path parameter (e.g. `{ResourceName}` vs
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.

We should just leave this comment out.

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.

Removed in e8fdf16.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

⚡ Benchmark Results

Comparing [22730c5] against baseline [19a619e]

Metric Baseline Current Change
total 🔴 619.9ms 🔴 851.2ms +37.3% 🔴
loader 🟢 114.2ms 🟢 174.1ms +52.4% 🔴
resolver 🟢 16.1ms 🟢 17.5ms +8.4% 🔴
checker 🟢 145.8ms 🟡 203.5ms +39.6% 🔴
validation 🟢 32.7ms 🟢 43.2ms +32.2% 🔴
 ↳ validation/@azure-tools/typespec-azure-core 🟢 4.7ms 🟢 6.6ms +41.7% 🔴
 ↳ validation/@typespec/http 🟢 4.1ms 🟢 6.3ms +52.2% 🔴
 ↳ validation/@typespec/rest 🟢 0.4ms 🟢 0.5ms +14.8% 🔴
 ↳ validation/@typespec/versioning 🔴 21.5ms 🔴 28.3ms +31.4% 🔴
 ↳ validation/compiler 🟢 1.9ms 🟢 1.5ms -20.1% 🟢
linter 🟢 111.7ms 🟢 142.5ms +27.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/auth-required 🟢 0.0ms 🟢 0.0ms +4.5%
 ↳ linter/@azure-tools/typespec-azure-core/bad-record-type 🟢 0.2ms 🟢 0.2ms +38.3% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/byos 🟢 4.8ms 🟢 6.1ms +25.9% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/casing-style 🟢 0.5ms 🟢 0.6ms +23.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/composition-over-inheritance 🟢 0.1ms 🟢 0.1ms -6.7% 🟢
 ↳ linter/@azure-tools/typespec-azure-core/documentation-required 🟢 0.7ms 🟢 0.8ms +18.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/friendly-name 🟢 0.5ms 🟢 0.6ms +25.7% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/key-visibility-required 🟢 0.1ms 🟢 0.2ms +23.4% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/known-encoding 🟢 0.2ms 🟢 0.2ms +32.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/long-running-polling-operation-required 🟢 0.3ms 🟢 0.3ms +24.9% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-case-mismatch 🟢 0.2ms 🟢 0.2ms +25.3% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-closed-literal-union 🟢 0.2ms 🟢 0.3ms +17.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-enum 🟢 0.0ms 🟢 0.0ms +14.7% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-error-status-codes 🟢 0.1ms 🟢 0.1ms +9.9% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-explicit-routes-resource-ops 🟢 0.1ms 🟢 0.1ms +17.4% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-format 🟢 0.4ms 🟢 0.5ms +25.9% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-generic-numeric 🟢 0.4ms 🟢 0.4ms +5.0% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-header-explode 🟡 14.6ms 🔴 21.6ms +47.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-legacy-usage 🟢 0.9ms 🟢 1.1ms +27.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-multiple-discriminator 🟢 0.1ms 🟢 0.1ms +37.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-nullable 🟢 0.2ms 🟢 0.2ms +38.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-offsetdatetime 🟢 1.0ms 🟢 1.2ms +23.9% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-openapi 🟢 1.7ms 🟢 1.9ms +7.8% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-private-usage 🟢 1.5ms 🟢 1.8ms +18.8% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-query-explode 🟡 14.8ms 🔴 20.6ms +39.1% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-response-body 🟡 16.3ms 🔴 25.2ms +54.7% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-rest-library-interfaces 🟢 0.0ms 🟢 0.0ms -10.4% 🟢
 ↳ linter/@azure-tools/typespec-azure-core/no-route-parameter-name-mismatch 🟢 4.2ms 🟢 5.4ms +26.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-rpc-path-params 🟢 0.2ms 🟢 0.2ms +24.0% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-string-discriminator 🟢 0.0ms 🟢 0.0ms +20.1% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-unknown 🟢 0.1ms 🟢 0.2ms +27.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-unnamed-union 🟢 0.3ms 🟢 0.3ms +23.0% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/operation-missing-api-version 🟢 0.1ms 🟢 0.2ms +9.9% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/request-body-problem 🟢 0.2ms 🟢 0.2ms +15.1% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/require-versioned 🟢 0.0ms 🟢 0.0ms -2.4%
 ↳ linter/@azure-tools/typespec-azure-core/response-schema-problem 🔴 23.8ms 🔴 22.8ms -3.9%
 ↳ linter/@azure-tools/typespec-azure-core/rpc-operation-request-body 🟢 0.2ms 🟢 0.3ms +46.7% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/spread-discriminated-model 🟢 0.2ms 🟢 0.2ms +7.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/use-standard-names 🟢 3.5ms 🟢 5.7ms +62.8% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/use-standard-operations 🟢 0.1ms 🟢 0.1ms +39.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-common-types-version 🟢 4.6ms 🟢 6.8ms +46.1% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-custom-resource-no-key 🟢 0.1ms 🟢 0.1ms +41.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-custom-resource-usage-discourage 🟢 0.1ms 🟢 0.1ms +52.7% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-delete-operation-response-codes 🟢 4.8ms 🟢 8.6ms +79.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-no-record 🟢 0.3ms 🟢 0.4ms +10.4% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-post-operation-response-codes 🟢 0.5ms 🟢 0.8ms +66.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-put-operation-response-codes 🟢 0.0ms 🟢 0.0ms +17.3% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-action-no-segment 🟢 0.2ms 🟢 0.2ms +11.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-duplicate-property 🟢 0.1ms 🟢 0.1ms +37.9% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-interface-requires-decorator 🟢 0.0ms 🟢 0.0ms -0.7%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-action-verb 🟢 0.1ms 🟢 0.1ms +5.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-envelope-property 🟢 0.1ms 🟢 0.1ms +20.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-version-format 🟢 0.1ms 🟢 0.1ms +15.3% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-key-invalid-chars 🟢 0.2ms 🟢 0.3ms +42.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-name-pattern 🟢 0.0ms 🟢 0.0ms -1.5%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-operation 🟢 0.2ms 🟢 0.2ms +9.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-operation-response 🟢 6.3ms 🟢 5.5ms -12.8% 🟢
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-patch 🟢 0.3ms 🟢 0.4ms +16.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-path-segment-invalid-chars 🟢 0.2ms 🟢 0.2ms +24.7% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-provisioning-state 🟢 0.1ms 🟢 0.1ms +15.7% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/beyond-nesting-levels 🟢 0.1ms 🟢 0.1ms +15.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/empty-updateable-properties 🟢 0.1ms 🟢 0.2ms +29.2% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/improper-subscription-list-operation 🟢 0.0ms 🟢 0.0ms -1.6%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/lro-location-header 🟡 13.8ms 🟡 15.9ms +14.9% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint 🟢 0.0ms 🟢 0.0ms +20.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers 🟢 0.3ms 🟢 0.3ms +4.2%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/no-empty-model 🟢 0.1ms 🟢 0.1ms +24.7% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/no-resource-delete-operation 🟢 0.1ms 🟢 0.2ms +30.0% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/no-response-body 🔴 20.2ms 🔴 25.6ms +26.7% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/patch-envelope 🟢 0.1ms 🟢 0.1ms +26.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/resource-name 🟢 0.1ms 🟢 0.2ms +34.4% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/secret-prop 🟢 2.4ms 🟢 2.4ms -0.2%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/unsupported-type 🟢 0.3ms 🟢 0.4ms +25.3% 🔴
 ↳ linter/@azure-tools/typespec-client-generator-core/property-name-conflict 🟢 0.8ms 🟢 1.1ms +35.0% 🔴
 ↳ linter/@azure-tools/typespec-client-generator-core/require-client-suffix 🟢 0.2ms 🟢 0.2ms +3.1%
emit 🟢 199.4ms 🟡 270.4ms +35.6% 🔴
 ↳ emit/@azure-tools/typespec-autorest 🟢 126.3ms 🟢 162.2ms +28.5% 🔴
 ↳ emit/@typespec/openapi3 🟢 109.6ms 🟢 162.1ms +47.9% 🔴
 ↳ emit/@typespec/openapi3/compute 🟢 98.5ms 🟢 143.0ms +45.3% 🔴
 ↳ emit/@typespec/openapi3/write 🟢 11.1ms 🟢 19.0ms +71.1% 🔴

Averaged across 3 specs (azure-arm-resource-manager, azure-core-dataplane, azure-full).
Threshold: changes > ±5% are highlighted.

Copilot AI requested a review from markcowl May 6, 2026 02:32
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 6, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@azure-tools/typespec-azure-resource-manager@4379
npm i https://pkg.pr.new/@azure-tools/typespec-azure-rulesets@4379

commit: 7787b92

@markcowl
Copy link
Copy Markdown
Member

markcowl commented May 6, 2026

@copilot Please pull the latest from main, rebuild and rebuild the docs for typespec-azure-resource-manager, fix any merge conflicts, verify that all files are correctly formatted and only the changes associated with this PR are in the pr diff

Copilot AI and others added 2 commits May 6, 2026 19:31
…th-casing-rule

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

Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/55aa9445-8282-4c42-9865-eead8e407804

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

Copilot AI commented May 6, 2026

@copilot Please pull the latest from main, rebuild and rebuild the docs for typespec-azure-resource-manager, fix any merge conflicts, verify that all files are correctly formatted and only the changes...

Done in fe958be. Merged latest main (resolved the autogenerated linter.md conflict), reran pnpm install, pnpm -r --filter "@azure-tools/typespec-azure-resource-manager..." build, pnpm regen-docs for typespec-azure-resource-manager (updated README.md and reference/linter.md for the new rule), and pnpm format. Tests pass; PR diff against main contains only the 7 files for this change (rule, test, linter wiring, rule docs page, linter index entry, README entry, changeset).

@markcowl
Copy link
Copy Markdown
Member

markcowl commented May 7, 2026

@copilot tests are failiong because the new rule hasn't been added to the azure-resource-manager ruleset. Please pull the latest from main, fix the issue, ensure all tests are passing and all files are correctly formatted, and the pr diff contains only changes from this pr.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 7, 2026

@copilot tests are failiong because the new rule hasn't been added to the azure-resource-manager ruleset. Please pull the latest from main, fix the issue, ensure all tests are passing and all files a...

Done in 5100b66. Added @azure-tools/typespec-azure-resource-manager/arm-no-path-casing-conflicts: true to the resource-manager ruleset in packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts. The previously failing @azure-tools/typespec-azure-rulesets validate-rules-defined test now passes (3/3). pnpm format is clean and the PR diff against main contains only the 8 files related to this change.

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, add tests to cover the suggested changes, then implement the changes. Ensure that all tests pass, all files are correctly formatted, all samples can be built cleanly and only the changes associated with this PR are in the PR diff.

* Replace `{paramName}` placeholders with `{}` so that path-parameter naming
* differences are ignored when comparing paths.
*/
function normalizeParameters(path: string): string {
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.

I previously told you to remove this. You should include the path parameters in your comparison

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 formulate tests to verify that paths like:

/{resourceUri}/providers/Microsoft.Foo/widgets

and

/{scope}/providers/microsoft.foo/widgets

are in different buckets

However:

/{scope}/providers/microsoft.foo/widgets
and

/{Scope}/providers/Microsoft.Foo/widgets

should be in the same bucket

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.

Done in 0e7eafa. Removed the normalizeParameters helper so path parameter names are now part of the case-insensitive bucket key (op.path.toLowerCase()). Added two tests covering the requested cases: /{resourceUri}/providers/Microsoft.Foo/widgets vs /{scope}/providers/microsoft.foo/widgets end up in different buckets (no diagnostic), while /{scope}/providers/microsoft.foo/widgets vs /{Scope}/providers/Microsoft.Foo/widgets share a bucket and emit a diagnostic. Updated the existing parameter-name-casing test to assert a diagnostic accordingly. ARM rule tests pass 8/8, rulesets validate-rules-defined 3/3, samples build & tests 68/68, pnpm format clean.

Copilot AI and others added 2 commits May 7, 2026 19:02
…th-casing-rule

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

Copilot AI commented May 7, 2026

@copilot This rule is being applied in too many cases where the intent of the user is not clear. We should only try to determine if the finalResult matches the author's intended response when one of...

This PR (arm-no-path-casing-conflicts) is purely about detecting ARM operation paths that differ only by character casing — it doesn't look at responses, finalResult, LRO templates, or 200/202/204 combinations at all. The feedback you've described (matching finalResult against the author's intended response, ArmResourceActionAsync* templates, ArmAcceptedLroResponse, low-level 202+LRO-headers operations) sounds like it's intended for a different rule/PR. Could you confirm — was this comment meant for another PR, or is there a separate rule you'd like me to add here? I also restored .github/workflows/ci.yml to match main in 7787b92 (it had drifted via the shallow merge); the PR diff is now exactly the 8 files for this rule.

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 suggested changes, ensure that all tests pass, all files are formatted correctly, and only the changes associated with this PR are in the PR diff

name: string;
}

model Bar is ProxyResource<FooProperties> {
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 fixed version here will not compile, since the same operation verb and path are used. Instead, make one of the operations be a delete and the other a read

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.

Fixed in ee903fb. Changed Bars to delete is ArmResourceDeleteSync<Bar> (in both before and after blocks) so the post-fix snippet compiles — Foos.get and Bars.delete share the same path after the codefix but use different verbs.

});
});

it("applies a codefix that lowercases the offending @segment value", async () => {
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.

Also add two operations that use @route with different casing (one delete and one get)

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.

Added in ee903fb: a new test "emits a diagnostic for two @route operations (delete and get) that differ only by static-segment casing" with a @delete op on /providers/Microsoft.Contoso/foos/{name} and a @get op on /providers/Microsoft.Contoso/Foos/{name} asserting the casing diagnostic is emitted.

copilot-swe-agent[bot] and others added 2 commits May 7, 2026 21:51
…th-casing-rule

Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Copilot AI requested a review from markcowl May 7, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants