From fff69234aa7aa6fe888e74bef2bdf5122904094a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 19:53:39 +0000 Subject: [PATCH 1/7] Initial plan From be839f8f8e004545ee5d5174833c3e99beb86a31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 20:07:04 +0000 Subject: [PATCH 2/7] Add diagnostic and validation logic for unused client initialization parameters Co-authored-by: iscai-msft <43154838+iscai-msft@users.noreply.github.com> --- .../typespec-client-generator-core/src/lib.ts | 6 + .../src/package.ts | 120 +++++++++++++ .../decorators/client-initialization.test.ts | 163 ++++++++++++++++++ 3 files changed, 289 insertions(+) diff --git a/packages/typespec-client-generator-core/src/lib.ts b/packages/typespec-client-generator-core/src/lib.ts index 6c5f9c3476..cbb79f0e68 100644 --- a/packages/typespec-client-generator-core/src/lib.ts +++ b/packages/typespec-client-generator-core/src/lib.ts @@ -490,6 +490,12 @@ export const $lib = createTypeSpecLibrary({ default: "All services must have the same server and auth definitions.", }, }, + "unused-client-initialization-parameter": { + severity: "warning", + messages: { + default: paramMessage`Client initialization parameter '${"parameterName"}' is not used in any operations of client '${"clientName"}' or its sub-clients.`, + }, + }, }, emitter: { options: TCGCEmitterOptionsSchema, diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index da6c710407..9890617746 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -19,6 +19,7 @@ import { getActualClientType, getTypeDecorators, } from "./internal-utils.js"; +import { createDiagnostic } from "./lib.js"; import { getLicenseInfo } from "./license.js"; import { getCrossLanguagePackageId, getNamespaceFromType } from "./public-utils.js"; import { getAllReferencedTypes, handleAllTypes } from "./types.js"; @@ -52,6 +53,7 @@ export function createSdkPackage( }, }; organizeNamespaces(context, sdkPackage); + diagnostics.pipe(validateClientInitializationParameters(context, sdkPackage)); return diagnostics.wrap(sdkPackage); } @@ -145,3 +147,121 @@ function populateApiVersionInformation(context: TCGCContext): void { } } } + +/** + * Validates that all client initialization parameters are actually used in at least one operation + * of the client or its sub-clients. + */ +function validateClientInitializationParameters( + context: TCGCContext, + sdkPackage: SdkPackage, +): readonly Diagnostic[] { + const diagnostics: Diagnostic[] = []; + + // Process all top-level clients + for (const client of sdkPackage.clients) { + diagnostics.push(...validateClientInitializationParametersRecursive(context, client)); + } + + return diagnostics; +} + +/** + * Recursively validates client initialization parameters for a client and all its children. + */ +function validateClientInitializationParametersRecursive< + TServiceOperation extends SdkServiceOperation, +>(context: TCGCContext, client: SdkClientType): Diagnostic[] { + const diagnostics: Diagnostic[] = []; + + // Get the raw entity (Namespace or Interface) to report diagnostics on + const target = client.__raw.type; + if (!target) { + // If there's no target, skip validation for this client + return diagnostics; + } + + // Collect all operation parameters from this client and all sub-clients + const allOperationParameterNames = new Set(); + collectOperationParameterNames(client, allOperationParameterNames); + + // Check each client initialization parameter + for (const param of client.clientInitialization.parameters) { + // Skip built-in parameters like endpoint and credential + if (param.kind === "endpoint" || param.kind === "credential") { + continue; + } + + // Check if this parameter is used in any operation + if (!allOperationParameterNames.has(param.name)) { + diagnostics.push( + createDiagnostic({ + code: "unused-client-initialization-parameter", + target: target, + format: { + parameterName: param.name, + clientName: client.name, + }, + }), + ); + } + } + + // Recursively validate sub-clients + if (client.children) { + for (const child of client.children) { + diagnostics.push(...validateClientInitializationParametersRecursive(context, child)); + } + } + + return diagnostics; +} + +/** + * Collects all parameter names used in operations of a client and all its sub-clients. + */ +function collectOperationParameterNames( + client: SdkClientType, + parameterNames: Set, +): void { + // Collect parameters from all methods in this client + for (const method of client.methods) { + if (method.kind === "clientaccessor") { + // Client accessors don't have operations, skip them + continue; + } + + // Check operation parameters + if (method.operation && method.operation.kind === "http") { + for (const param of method.operation.parameters) { + // Check correspondingMethodParams to find the client initialization parameter + if (param.correspondingMethodParams) { + for (const methodParam of param.correspondingMethodParams) { + if (methodParam.kind === "method" && methodParam.onClient) { + parameterNames.add(methodParam.name); + } + } + } + } + + // Also check body parameter + if (method.operation.bodyParam) { + const bodyParam = method.operation.bodyParam; + if (bodyParam.correspondingMethodParams) { + for (const methodParam of bodyParam.correspondingMethodParams) { + if (methodParam.kind === "method" && methodParam.onClient) { + parameterNames.add(methodParam.name); + } + } + } + } + } + } + + // Recursively collect from sub-clients + if (client.children) { + for (const child of client.children) { + collectOperationParameterNames(child, parameterNames); + } + } +} diff --git a/packages/typespec-client-generator-core/test/decorators/client-initialization.test.ts b/packages/typespec-client-generator-core/test/decorators/client-initialization.test.ts index d0eddc2fac..c2d7d32bed 100644 --- a/packages/typespec-client-generator-core/test/decorators/client-initialization.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/client-initialization.test.ts @@ -705,3 +705,166 @@ it("wrong initializedBy value type", async () => { code: "invalid-argument", }); }); + +it("should warn on unused client initialization parameter", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + model ClientInitialization { + unusedParam: string; + } + + @@clientInitialization(MyService, {parameters: ClientInitialization}); + + @route("/test") + op testOp(@query query: string): void; + } + `); + + expectDiagnostics(diagnostics, { + code: "@azure-tools/typespec-client-generator-core/unused-client-initialization-parameter", + }); +}); + +it("should not warn when client initialization parameter is used", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + model ClientInitialization { + blobName: string; + } + + @@clientInitialization(MyService, {parameters: ClientInitialization}); + + @route("/test") + op testOp(@path blobName: string): void; + } + `); + + expectDiagnostics(diagnostics, []); +}); + +it("should warn on multiple unused client initialization parameters", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + model ClientInitialization { + unusedParam1: string; + unusedParam2: string; + usedParam: string; + } + + @@clientInitialization(MyService, {parameters: ClientInitialization}); + + @route("/test") + op testOp(@path usedParam: string): void; + } + `); + + expectDiagnostics(diagnostics, [ + { + code: "@azure-tools/typespec-client-generator-core/unused-client-initialization-parameter", + }, + { + code: "@azure-tools/typespec-client-generator-core/unused-client-initialization-parameter", + }, + ]); +}); + +it("should not warn when parameter is used in subclient with @operationGroup", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + model ClientInitialization { + blobName: string; + } + + @@clientInitialization(MyService, {parameters: ClientInitialization}); + + @operationGroup + interface SubClient { + @route("/blob") + op download(@path blobName: string): void; + } + + @route("/main") + op mainOp(@query query: string): void; + } + `); + + expectDiagnostics(diagnostics, []); +}); + +it("should not warn when parameter is used in subclient with @client", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + model ClientInitialization { + blobName: string; + } + + @@clientInitialization(MyService, {parameters: ClientInitialization}); + + @client({name: "SubClient"}) + namespace SubNamespace { + @route("/blob") + op download(@path blobName: string): void; + } + + @route("/main") + op mainOp(@query query: string): void; + } + `); + + expectDiagnostics(diagnostics, []); +}); + +it("should warn when parameter is not used in subclient operations", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + model ClientInitialization { + unusedParam: string; + } + + @@clientInitialization(MyService, {parameters: ClientInitialization}); + + @operationGroup + interface SubClient { + @route("/blob") + op download(@query query: string): void; + } + + @route("/main") + op mainOp(@query query: string): void; + } + `); + + expectDiagnostics(diagnostics, { + code: "@azure-tools/typespec-client-generator-core/unused-client-initialization-parameter", + }); +}); + +it("should work with @clientLocation decorator", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + model ClientInitialization { + blobName: string; + } + + @@clientInitialization(MyService, {parameters: ClientInitialization}); + + model BlobParams { + @path blobName: string; + } + + @route("/blob") + op download(...BlobParams): void; + + @@clientLocation(download, MyService); + } + `); + + expectDiagnostics(diagnostics, []); +}); From 206ed725203cce82526ef2be3941907b08fd1928 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 20:10:13 +0000 Subject: [PATCH 3/7] Add changeset for unused client initialization parameter warning Co-authored-by: iscai-msft <43154838+iscai-msft@users.noreply.github.com> --- .../changes/warn-unused-client-init-params-2026-01-13.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/warn-unused-client-init-params-2026-01-13.md diff --git a/.chronus/changes/warn-unused-client-init-params-2026-01-13.md b/.chronus/changes/warn-unused-client-init-params-2026-01-13.md new file mode 100644 index 0000000000..9bd75dd05a --- /dev/null +++ b/.chronus/changes/warn-unused-client-init-params-2026-01-13.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +Add warning diagnostic for unused client initialization parameters. If `clientInitialization.parameters` contains values that aren't used in any routes (client or its sub-clients), a diagnostic warning is now produced. From 3a28190dc1a93deb2ad042f027b76c1ca705d4ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 21:14:33 +0000 Subject: [PATCH 4/7] Fix build failures: use reportDiagnostic and fix validation logic Co-authored-by: iscai-msft <43154838+iscai-msft@users.noreply.github.com> --- .../src/package.ts | 64 ++++++------------- 1 file changed, 19 insertions(+), 45 deletions(-) diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 9890617746..25ded564f5 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -19,7 +19,7 @@ import { getActualClientType, getTypeDecorators, } from "./internal-utils.js"; -import { createDiagnostic } from "./lib.js"; +import { reportDiagnostic } from "./lib.js"; import { getLicenseInfo } from "./license.js"; import { getCrossLanguagePackageId, getNamespaceFromType } from "./public-utils.js"; import { getAllReferencedTypes, handleAllTypes } from "./types.js"; @@ -53,7 +53,7 @@ export function createSdkPackage( }, }; organizeNamespaces(context, sdkPackage); - diagnostics.pipe(validateClientInitializationParameters(context, sdkPackage)); + validateClientInitializationParameters(context, sdkPackage); return diagnostics.wrap(sdkPackage); } @@ -155,32 +155,19 @@ function populateApiVersionInformation(context: TCGCContext): void { function validateClientInitializationParameters( context: TCGCContext, sdkPackage: SdkPackage, -): readonly Diagnostic[] { - const diagnostics: Diagnostic[] = []; - - // Process all top-level clients +): void { + // Process all top-level clients - don't recurse since each client already checks its sub-clients for (const client of sdkPackage.clients) { - diagnostics.push(...validateClientInitializationParametersRecursive(context, client)); + validateClientInitializationParametersForClient(context, client); } - - return diagnostics; } /** - * Recursively validates client initialization parameters for a client and all its children. + * Validates client initialization parameters for a single client (including its sub-clients' operations). */ -function validateClientInitializationParametersRecursive< +function validateClientInitializationParametersForClient< TServiceOperation extends SdkServiceOperation, ->(context: TCGCContext, client: SdkClientType): Diagnostic[] { - const diagnostics: Diagnostic[] = []; - - // Get the raw entity (Namespace or Interface) to report diagnostics on - const target = client.__raw.type; - if (!target) { - // If there's no target, skip validation for this client - return diagnostics; - } - +>(context: TCGCContext, client: SdkClientType): void { // Collect all operation parameters from this client and all sub-clients const allOperationParameterNames = new Set(); collectOperationParameterNames(client, allOperationParameterNames); @@ -194,27 +181,19 @@ function validateClientInitializationParametersRecursive< // Check if this parameter is used in any operation if (!allOperationParameterNames.has(param.name)) { - diagnostics.push( - createDiagnostic({ - code: "unused-client-initialization-parameter", - target: target, - format: { - parameterName: param.name, - clientName: client.name, - }, - }), - ); - } - } - - // Recursively validate sub-clients - if (client.children) { - for (const child of client.children) { - diagnostics.push(...validateClientInitializationParametersRecursive(context, child)); + // Get the raw entity (Namespace or Interface) to report diagnostics on + // Use the raw type if available, otherwise use the raw model from clientInitialization + const target = client.__raw.type || client.clientInitialization.__raw || context.program.getGlobalNamespaceType(); + reportDiagnostic(context.program, { + code: "unused-client-initialization-parameter", + target: target, + format: { + parameterName: param.name, + clientName: client.name, + }, + }); } } - - return diagnostics; } /** @@ -226,11 +205,6 @@ function collectOperationParameterNames Date: Mon, 26 Jan 2026 15:30:35 -0500 Subject: [PATCH 5/7] update tests --- .../decorators/client-initialization.test.ts | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/typespec-client-generator-core/test/decorators/client-initialization.test.ts b/packages/typespec-client-generator-core/test/decorators/client-initialization.test.ts index 0a42fff028..e8d4a135a0 100644 --- a/packages/typespec-client-generator-core/test/decorators/client-initialization.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/client-initialization.test.ts @@ -731,7 +731,7 @@ it("wrong initializedBy value type", async () => { }); it("should warn on unused client initialization parameter", async () => { - const diagnostics = await runner.diagnose(` + const { program } = await SimpleTester.compile(` @service namespace MyService { model ClientInitialization { @@ -744,14 +744,15 @@ it("should warn on unused client initialization parameter", async () => { op testOp(@query query: string): void; } `); + await createSdkContextForTester(program); - expectDiagnostics(diagnostics, { + expectDiagnostics(program.diagnostics, { code: "@azure-tools/typespec-client-generator-core/unused-client-initialization-parameter", }); }); it("should not warn when client initialization parameter is used", async () => { - const diagnostics = await runner.diagnose(` + const { program } = await SimpleTester.compile(` @service namespace MyService { model ClientInitialization { @@ -764,12 +765,13 @@ it("should not warn when client initialization parameter is used", async () => { op testOp(@path blobName: string): void; } `); + const context = await createSdkContextForTester(program); - expectDiagnostics(diagnostics, []); + expectDiagnostics(context.diagnostics, []); }); it("should warn on multiple unused client initialization parameters", async () => { - const diagnostics = await runner.diagnose(` + const { program } = await SimpleTester.compile(` @service namespace MyService { model ClientInitialization { @@ -784,8 +786,9 @@ it("should warn on multiple unused client initialization parameters", async () = op testOp(@path usedParam: string): void; } `); + await createSdkContextForTester(program); - expectDiagnostics(diagnostics, [ + expectDiagnostics(program.diagnostics, [ { code: "@azure-tools/typespec-client-generator-core/unused-client-initialization-parameter", }, @@ -796,7 +799,7 @@ it("should warn on multiple unused client initialization parameters", async () = }); it("should not warn when parameter is used in subclient with @operationGroup", async () => { - const diagnostics = await runner.diagnose(` + const { program } = await SimpleTester.compile(` @service namespace MyService { model ClientInitialization { @@ -815,12 +818,13 @@ it("should not warn when parameter is used in subclient with @operationGroup", a op mainOp(@query query: string): void; } `); + const context = await createSdkContextForTester(program); - expectDiagnostics(diagnostics, []); + expectDiagnostics(context.diagnostics, []); }); it("should not warn when parameter is used in subclient with @client", async () => { - const diagnostics = await runner.diagnose(` + const { program } = await SimpleTester.compile(` @service namespace MyService { model ClientInitialization { @@ -839,12 +843,13 @@ it("should not warn when parameter is used in subclient with @client", async () op mainOp(@query query: string): void; } `); + const context = await createSdkContextForTester(program); - expectDiagnostics(diagnostics, []); + expectDiagnostics(context.diagnostics, []); }); it("should warn when parameter is not used in subclient operations", async () => { - const diagnostics = await runner.diagnose(` + const { program } = await SimpleTester.compile(` @service namespace MyService { model ClientInitialization { @@ -863,14 +868,15 @@ it("should warn when parameter is not used in subclient operations", async () => op mainOp(@query query: string): void; } `); + await createSdkContextForTester(program); - expectDiagnostics(diagnostics, { + expectDiagnostics(program.diagnostics, { code: "@azure-tools/typespec-client-generator-core/unused-client-initialization-parameter", }); }); it("should work with @clientLocation decorator", async () => { - const diagnostics = await runner.diagnose(` + const { program } = await SimpleTester.compile(` @service namespace MyService { model ClientInitialization { @@ -889,6 +895,7 @@ it("should work with @clientLocation decorator", async () => { @@clientLocation(download, MyService); } `); + const context = await createSdkContextForTester(program); - expectDiagnostics(diagnostics, []); + expectDiagnostics(context.diagnostics, []); }); From 5a2c0bbac94f136546d0d2b0838600878018970b Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 26 Jan 2026 16:04:57 -0500 Subject: [PATCH 6/7] use methodParameterSegments instead --- .../src/package.ts | 27 ++++++++------ .../test/decorators/client-location.test.ts | 36 +++++++++---------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index cacc6dbc33..ae27951b8c 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -203,7 +203,10 @@ function validateClientInitializationParametersForClient< if (!allOperationParameterNames.has(param.name)) { // Get the raw entity (Namespace or Interface) to report diagnostics on // Use the raw type if available, otherwise use the raw model from clientInitialization - const target = client.__raw.type || client.clientInitialization.__raw || context.program.getGlobalNamespaceType(); + const target = + client.__raw.type || + client.clientInitialization.__raw || + context.program.getGlobalNamespaceType(); reportDiagnostic(context.program, { code: "unused-client-initialization-parameter", target: target, @@ -228,11 +231,13 @@ function collectOperationParameterNames { strictEqual(a2Method.parameters.length, 0); strictEqual(a2Method.operation.parameters.length, 1); strictEqual(a2Method.operation.parameters[0].name, "apiVersion"); - strictEqual(a2Method.operation.parameters[0].correspondingMethodParams.length, 1); + strictEqual(a2Method.operation.parameters[0].methodParameterSegments.length, 1); strictEqual( - a2Method.operation.parameters[0].correspondingMethodParams[0], + a2Method.operation.parameters[0].methodParameterSegments[0][0], bClientApiVersionParam, ); }); @@ -382,9 +382,9 @@ describe("Operation", () => { strictEqual(a2Method.parameters.length, 0); strictEqual(a2Method.operation.parameters.length, 1); strictEqual(a2Method.operation.parameters[0].name, "apiVersion"); - strictEqual(a2Method.operation.parameters[0].correspondingMethodParams.length, 1); + strictEqual(a2Method.operation.parameters[0].methodParameterSegments.length, 1); strictEqual( - a2Method.operation.parameters[0].correspondingMethodParams[0], + a2Method.operation.parameters[0].methodParameterSegments[0][0], bClientApiVersionParam, ); }); @@ -428,9 +428,9 @@ describe("Operation", () => { strictEqual(a2Method.parameters.length, 0); strictEqual(a2Method.operation.parameters.length, 1); strictEqual(a2Method.operation.parameters[0].name, "apiVersion"); - strictEqual(a2Method.operation.parameters[0].correspondingMethodParams.length, 1); + strictEqual(a2Method.operation.parameters[0].methodParameterSegments.length, 1); strictEqual( - a2Method.operation.parameters[0].correspondingMethodParams[0], + a2Method.operation.parameters[0].methodParameterSegments[0][0], rootClientApiVersionParam, ); }); @@ -524,8 +524,8 @@ describe("Parameter", () => { // But the HTTP operation should still reference the client parameter const httpApiKeyParam = aMethod.operation.parameters.find((p) => p.name === "apiKey"); ok(httpApiKeyParam); - strictEqual(httpApiKeyParam.correspondingMethodParams.length, 1); - strictEqual(httpApiKeyParam.correspondingMethodParams[0], clientApiKeyParam); + strictEqual(httpApiKeyParam.methodParameterSegments.length, 1); + strictEqual(httpApiKeyParam.methodParameterSegments[0][0], clientApiKeyParam); }); it("detect parameter name conflict when moving to client", async () => { @@ -628,8 +628,8 @@ describe("Parameter", () => { strictEqual(testOperation.parameters.length, 3); const subIdOperationParam = testOperation.parameters.find((p) => p.name === "subscriptionId"); ok(subIdOperationParam); - strictEqual(subIdOperationParam.correspondingMethodParams.length, 1); - strictEqual(subIdOperationParam.correspondingMethodParams[0], subIdMethodParam); + strictEqual(subIdOperationParam.methodParameterSegments.length, 1); + strictEqual(subIdOperationParam.methodParameterSegments[0][0], subIdMethodParam); ok(testOperation.parameters.some((p) => p.name === "contentType")); ok(testOperation.parameters.some((p) => p.name === "apiVersion")); }); @@ -696,8 +696,8 @@ describe("Parameter", () => { strictEqual(getOperation.parameters.length, 4); const subIdOperationParam = getOperation.parameters.find((p) => p.name === "subscriptionId"); ok(subIdOperationParam); - strictEqual(subIdOperationParam.correspondingMethodParams.length, 1); - strictEqual(subIdOperationParam.correspondingMethodParams[0], subIdMethodParam); + strictEqual(subIdOperationParam.methodParameterSegments.length, 1); + strictEqual(subIdOperationParam.methodParameterSegments[0][0], subIdMethodParam); const putMethod = client.methods.find((m) => m.name === "put"); ok(putMethod); @@ -713,8 +713,8 @@ describe("Parameter", () => { strictEqual(putOperation.parameters.length, 5); const putSubIdOperationParam = putOperation.parameters.find((p) => p.name === "subscriptionId"); ok(putSubIdOperationParam); - strictEqual(putSubIdOperationParam.correspondingMethodParams.length, 1); - strictEqual(putSubIdOperationParam.correspondingMethodParams[0], subIdMethodParam); + strictEqual(putSubIdOperationParam.methodParameterSegments.length, 1); + strictEqual(putSubIdOperationParam.methodParameterSegments[0][0], subIdMethodParam); const deleteMethod = client.methods.find((m) => m.name === "delete"); ok(deleteMethod); @@ -728,8 +728,8 @@ describe("Parameter", () => { (p) => p.name === "subscriptionId", ); ok(deleteSubIdOperationParam); - strictEqual(deleteSubIdOperationParam.correspondingMethodParams.length, 1); - strictEqual(deleteSubIdOperationParam.correspondingMethodParams[0], subIdClientParam); + strictEqual(deleteSubIdOperationParam.methodParameterSegments.length, 1); + strictEqual(deleteSubIdOperationParam.methodParameterSegments[0][0], subIdClientParam); }); it("move to `@clientInitialization` for grandparent client", async () => { @@ -915,7 +915,7 @@ describe("Parameter", () => { ok(subIdParam); const subIdMethodParam = method.parameters.find((p) => p.name === "subscriptionId"); ok(subIdMethodParam); - strictEqual(subIdParam.correspondingMethodParams.length, 1); - strictEqual(subIdParam.correspondingMethodParams[0], subIdMethodParam); + strictEqual(subIdParam.methodParameterSegments.length, 1); + strictEqual(subIdParam.methodParameterSegments[0][0], subIdMethodParam); }); }); From 3169c5a33d37434f5fbb4fbd20bba41d0eebc7ec Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 27 Jan 2026 11:51:34 -0500 Subject: [PATCH 7/7] only check for client init params and add verification for --- .../src/package.ts | 25 +++++++---- .../decorators/client-initialization.test.ts | 42 +++++++++++++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index ae27951b8c..5219280aeb 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -188,17 +188,28 @@ function validateClientInitializationParameters(context: TCGCContext, client: SdkClientType): void { + // Only validate when there's a customized @clientInitialization decorator with parameters + // Skip validation for default/auto-generated client initialization + if (!client.clientInitialization.__raw) { + return; + } + + // Get custom parameters to validate (exclude built-in parameters like endpoint and credential) + const customParams = client.clientInitialization.parameters.filter( + (param) => param.kind !== "endpoint" && param.kind !== "credential", + ); + + // Skip expensive operation parameter collection if there are no custom parameters to validate + if (customParams.length === 0) { + return; + } + // Collect all operation parameters from this client and all sub-clients const allOperationParameterNames = new Set(); collectOperationParameterNames(client, allOperationParameterNames); - // Check each client initialization parameter - for (const param of client.clientInitialization.parameters) { - // Skip built-in parameters like endpoint and credential - if (param.kind === "endpoint" || param.kind === "credential") { - continue; - } - + // Check each custom client initialization parameter + for (const param of customParams) { // Check if this parameter is used in any operation if (!allOperationParameterNames.has(param.name)) { // Get the raw entity (Namespace or Interface) to report diagnostics on diff --git a/packages/typespec-client-generator-core/test/decorators/client-initialization.test.ts b/packages/typespec-client-generator-core/test/decorators/client-initialization.test.ts index e8d4a135a0..1814a3b294 100644 --- a/packages/typespec-client-generator-core/test/decorators/client-initialization.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/client-initialization.test.ts @@ -899,3 +899,45 @@ it("should work with @clientLocation decorator", async () => { expectDiagnostics(context.diagnostics, []); }); + +it("should not warn when client initialization parameter is used via @paramAlias", async () => { + const { program } = await SimpleTester.compile(` + @service + namespace MyService { + model ClientInitialization { + @paramAlias("blob") + blobName: string; + } + + @@clientInitialization(MyService, {parameters: ClientInitialization}); + + @route("/download") + op download(@path blob: string): void; + } + `); + const context = await createSdkContextForTester(program); + + expectDiagnostics(context.diagnostics, []); +}); + +it("should warn when client initialization parameter with @paramAlias is not used", async () => { + const { program } = await SimpleTester.compile(` + @service + namespace MyService { + model ClientInitialization { + @paramAlias("blob") + blobName: string; + } + + @@clientInitialization(MyService, {parameters: ClientInitialization}); + + @route("/test") + op testOp(@query query: string): void; + } + `); + await createSdkContextForTester(program); + + expectDiagnostics(program.diagnostics, { + code: "@azure-tools/typespec-client-generator-core/unused-client-initialization-parameter", + }); +});