From 3d1a1250d23d7350db1f62826b864cf189946bb3 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 16 Jun 2026 15:18:46 -0400 Subject: [PATCH 1/5] fix path mismatch in override operations --- .../src/http.ts | 1 + .../src/internal-utils.ts | 3 +++ .../test/decorators/override.test.ts | 24 +++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index 5177d91457..94f3725455 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -417,6 +417,7 @@ function getSdkHttpParameters( (segment) => segment[segment.length - 1], ); } + return diagnostics.wrap(retval); } diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 9c7b655fa8..4f1632f73a 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -1091,6 +1091,9 @@ export function compareModelProperties( if (aHasHttpKind && bHasHttpKind) { if (aIsQuery !== bIsQuery || aIsHeader !== bIsHeader || aIsPath !== bIsPath) return false; } + // Return false when one has @path but the other doesn't — path params are structural + // and omitting @path in an override silently breaks URL construction + if (aIsPath !== bIsPath) return false; if ( aIsQuery && bIsQuery && diff --git a/packages/typespec-client-generator-core/test/decorators/override.test.ts b/packages/typespec-client-generator-core/test/decorators/override.test.ts index e2e26cdc63..8db4329c23 100644 --- a/packages/typespec-client-generator-core/test/decorators/override.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/override.test.ts @@ -547,6 +547,30 @@ it("remove optional query param and add secret name", async () => { strictEqual(maxResultsParam.name, "maxresults"); }); +it("reports diagnostic when override parameter is missing @path", async () => { + const [_, diagnostics] = await SimpleBaseTester.compileAndDiagnose( + createClientCustomizationInput( + ` + @service + namespace MyService; + + @route("/items/{itemId}") + op getItem(@path itemId: string): void; + `, + ` + namespace MyCustomizations; + + op getItemOverride(itemId: string): void; + + @@override(MyService.getItem, MyCustomizations.getItemOverride); + `, + ), + ); + expectDiagnostics(diagnostics, { + code: "@azure-tools/typespec-client-generator-core/override-parameters-mismatch", + }); +}); + describe("@clientName", () => { it("original method", async () => { const { program } = await SimpleBaseTester.compile( From f10807827c485a5de37509593ff2357b2868b992 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 16 Jun 2026 15:19:17 -0400 Subject: [PATCH 2/5] add changeset --- .../changes/tcgc-fixPathInOverride-2026-5-16-15-19-10.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/tcgc-fixPathInOverride-2026-5-16-15-19-10.md diff --git a/.chronus/changes/tcgc-fixPathInOverride-2026-5-16-15-19-10.md b/.chronus/changes/tcgc-fixPathInOverride-2026-5-16-15-19-10.md new file mode 100644 index 0000000000..404cc64296 --- /dev/null +++ b/.chronus/changes/tcgc-fixPathInOverride-2026-5-16-15-19-10.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +Verify all `@path` parameters are present in override \ No newline at end of file From 81f2bea62961f231e49b1ba2640970055ef89a54 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Wed, 17 Jun 2026 13:10:57 -0400 Subject: [PATCH 3/5] fix broken tests --- .../src/http.ts | 28 ++++++++++++++++++- .../src/internal-utils.ts | 3 -- .../test/decorators/override.test.ts | 5 ++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index 94f3725455..0decb176a7 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -32,7 +32,12 @@ import { } from "@typespec/http"; import { StreamMetadata, getStreamMetadata } from "@typespec/http/experimental"; import { camelCase } from "change-case"; -import { getResponseAsBool, isInScope, shouldOmitSlashFromEmptyRoute } from "./decorators.js"; +import { + getOverriddenClientMethod, + getResponseAsBool, + isInScope, + shouldOmitSlashFromEmptyRoute, +} from "./decorators.js"; import { CollectionFormat, SdkBodyParameter, @@ -418,6 +423,27 @@ function getSdkHttpParameters( ); } + // Check for override parameters missing @path decorator + if (getOverriddenClientMethod(context, httpOperation.operation)) { + for (const param of retval.parameters) { + if (param.kind === "path" && param.correspondingMethodParams.length > 0) { + const methodParam = param.correspondingMethodParams[0]; + if (methodParam.__raw && !isPathParam(context.program, methodParam.__raw)) { + diagnostics.add( + createDiagnostic({ + code: "override-parameters-mismatch", + target: methodParam.__raw, + format: { + methodName: httpOperation.operation.name, + checkParameter: methodParam.name, + }, + }), + ); + } + } + } + } + return diagnostics.wrap(retval); } diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 4f1632f73a..9c7b655fa8 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -1091,9 +1091,6 @@ export function compareModelProperties( if (aHasHttpKind && bHasHttpKind) { if (aIsQuery !== bIsQuery || aIsHeader !== bIsHeader || aIsPath !== bIsPath) return false; } - // Return false when one has @path but the other doesn't — path params are structural - // and omitting @path in an override silently breaks URL construction - if (aIsPath !== bIsPath) return false; if ( aIsQuery && bIsQuery && diff --git a/packages/typespec-client-generator-core/test/decorators/override.test.ts b/packages/typespec-client-generator-core/test/decorators/override.test.ts index 8db4329c23..fc2029d9f7 100644 --- a/packages/typespec-client-generator-core/test/decorators/override.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/override.test.ts @@ -548,7 +548,7 @@ it("remove optional query param and add secret name", async () => { }); it("reports diagnostic when override parameter is missing @path", async () => { - const [_, diagnostics] = await SimpleBaseTester.compileAndDiagnose( + const { program } = await SimpleBaseTester.compile( createClientCustomizationInput( ` @service @@ -566,7 +566,8 @@ it("reports diagnostic when override parameter is missing @path", async () => { `, ), ); - expectDiagnostics(diagnostics, { + const context = await createSdkContextForTester(program); + expectDiagnostics(context.diagnostics, { code: "@azure-tools/typespec-client-generator-core/override-parameters-mismatch", }); }); From 8c9bc01b4ef9be7c2a103f39fb317fe2f1dc3241 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 22 Jun 2026 13:41:52 -0400 Subject: [PATCH 4/5] move warnign throwing to decorator --- .../src/decorators.ts | 18 ++++++++++++ .../src/http.ts | 28 +------------------ .../test/decorators/override.test.ts | 5 ++-- 3 files changed, 21 insertions(+), 30 deletions(-) diff --git a/packages/typespec-client-generator-core/src/decorators.ts b/packages/typespec-client-generator-core/src/decorators.ts index 9d09ce095a..905f4b288c 100644 --- a/packages/typespec-client-generator-core/src/decorators.ts +++ b/packages/typespec-client-generator-core/src/decorators.ts @@ -32,6 +32,7 @@ import { getServers, isBody, isBodyRoot, + isPathParam, } from "@typespec/http"; import { getVersion, resolveVersions, type Version } from "@typespec/versioning"; import { @@ -749,6 +750,23 @@ export const $override = ( } } + // Warn if original param has @path but override param doesn't, + // unless the override param has @clientLocation (intentional relocation) + if ( + isPathParam(context.program, originalParam) && + !isPathParam(context.program, overrideParams[index]) && + !overrideParams[index].decorators.some((d) => d.decorator.name === "$clientLocation") + ) { + reportDiagnostic(context.program, { + code: "override-parameters-mismatch", + target: context.decoratorTarget, + format: { + methodName: original.name, + checkParameter: overrideParams[index].name, + }, + }); + } + // Apply the alternate type to the original parameter const overrideParam = overrideParams[index]; overrideParam.decorators diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index 0decb176a7..94f3725455 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -32,12 +32,7 @@ import { } from "@typespec/http"; import { StreamMetadata, getStreamMetadata } from "@typespec/http/experimental"; import { camelCase } from "change-case"; -import { - getOverriddenClientMethod, - getResponseAsBool, - isInScope, - shouldOmitSlashFromEmptyRoute, -} from "./decorators.js"; +import { getResponseAsBool, isInScope, shouldOmitSlashFromEmptyRoute } from "./decorators.js"; import { CollectionFormat, SdkBodyParameter, @@ -423,27 +418,6 @@ function getSdkHttpParameters( ); } - // Check for override parameters missing @path decorator - if (getOverriddenClientMethod(context, httpOperation.operation)) { - for (const param of retval.parameters) { - if (param.kind === "path" && param.correspondingMethodParams.length > 0) { - const methodParam = param.correspondingMethodParams[0]; - if (methodParam.__raw && !isPathParam(context.program, methodParam.__raw)) { - diagnostics.add( - createDiagnostic({ - code: "override-parameters-mismatch", - target: methodParam.__raw, - format: { - methodName: httpOperation.operation.name, - checkParameter: methodParam.name, - }, - }), - ); - } - } - } - } - return diagnostics.wrap(retval); } diff --git a/packages/typespec-client-generator-core/test/decorators/override.test.ts b/packages/typespec-client-generator-core/test/decorators/override.test.ts index fc2029d9f7..8db4329c23 100644 --- a/packages/typespec-client-generator-core/test/decorators/override.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/override.test.ts @@ -548,7 +548,7 @@ it("remove optional query param and add secret name", async () => { }); it("reports diagnostic when override parameter is missing @path", async () => { - const { program } = await SimpleBaseTester.compile( + const [_, diagnostics] = await SimpleBaseTester.compileAndDiagnose( createClientCustomizationInput( ` @service @@ -566,8 +566,7 @@ it("reports diagnostic when override parameter is missing @path", async () => { `, ), ); - const context = await createSdkContextForTester(program); - expectDiagnostics(context.diagnostics, { + expectDiagnostics(diagnostics, { code: "@azure-tools/typespec-client-generator-core/override-parameters-mismatch", }); }); From 83eecad07ada0f6263dce8d012493cc2dcfaaf14 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 22 Jun 2026 14:38:38 -0400 Subject: [PATCH 5/5] fix: skip path mismatch warning when override has @clientLocation params When an override operation contains any parameter with @clientLocation, the override is an intentional customization. Other parameters without @path are just pass-throughs and should not trigger the mismatch warning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/typespec-client-generator-core/src/decorators.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/typespec-client-generator-core/src/decorators.ts b/packages/typespec-client-generator-core/src/decorators.ts index 905f4b288c..9c31032c76 100644 --- a/packages/typespec-client-generator-core/src/decorators.ts +++ b/packages/typespec-client-generator-core/src/decorators.ts @@ -751,11 +751,12 @@ export const $override = ( } // Warn if original param has @path but override param doesn't, - // unless the override param has @clientLocation (intentional relocation) + // unless any override param has @clientLocation (indicating an intentional customization + // where non-path params are just pass-throughs) if ( isPathParam(context.program, originalParam) && !isPathParam(context.program, overrideParams[index]) && - !overrideParams[index].decorators.some((d) => d.decorator.name === "$clientLocation") + !overrideParams.some((p) => p.decorators.some((d) => d.decorator.name === "$clientLocation")) ) { reportDiagnostic(context.program, { code: "override-parameters-mismatch",