From 6eaa02559c98a7a83fd1dc832bfbe55d051220c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:43:20 +0000 Subject: [PATCH 1/9] Initial plan From f000704660cb40e122711c7c6cb54b2af1268e80 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:47:56 +0000 Subject: [PATCH 2/9] Add empty-client diagnostic and tests Co-authored-by: iscai-msft <43154838+iscai-msft@users.noreply.github.com> --- .../src/clients.ts | 12 ++++ .../typespec-client-generator-core/src/lib.ts | 6 ++ .../test/decorators/client.test.ts | 58 +++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/packages/typespec-client-generator-core/src/clients.ts b/packages/typespec-client-generator-core/src/clients.ts index e14beea859..0f7f6a5f57 100644 --- a/packages/typespec-client-generator-core/src/clients.ts +++ b/packages/typespec-client-generator-core/src/clients.ts @@ -217,6 +217,18 @@ export function createSdkClientType(context, client, sdkClientType), ); + // Check if the client is empty (has no methods) + if (sdkClientType.methods.length === 0 && client.type) { + diagnostics.add( + createDiagnostic({ + code: "empty-client", + target: client.type, + format: { + name: sdkClientType.name, + }, + }), + ); + } addDefaultClientParameters(context, sdkClientType); // update initialization model properties diff --git a/packages/typespec-client-generator-core/src/lib.ts b/packages/typespec-client-generator-core/src/lib.ts index 6c5f9c3476..1de893dfe7 100644 --- a/packages/typespec-client-generator-core/src/lib.ts +++ b/packages/typespec-client-generator-core/src/lib.ts @@ -141,6 +141,12 @@ export const $lib = createTypeSpecLibrary({ default: paramMessage`Client "${"name"}" is not inside a service namespace. Use @client({service: MyServiceNS})`, }, }, + "empty-client": { + severity: "warning", + messages: { + default: paramMessage`Client "${"name"}" has no operations. Clients should contain at least one operation.`, + }, + }, "union-null": { severity: "warning", messages: { diff --git a/packages/typespec-client-generator-core/test/decorators/client.test.ts b/packages/typespec-client-generator-core/test/decorators/client.test.ts index a7626d516c..1bf1812c21 100644 --- a/packages/typespec-client-generator-core/test/decorators/client.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/client.test.ts @@ -1461,3 +1461,61 @@ it("operations under namespace or interface without @client or @operationGroup", const operationGroup = operationGroups[0]; strictEqual(listOperationsInOperationGroup(runner.context, operationGroup).length, 1); }); + +describe("empty client diagnostic", () => { + it("should emit diagnostic for empty client", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + @operationGroup + interface SubClient { + op test(): void; + } + } + `); + + expectDiagnostics(diagnostics, { + code: "@azure-tools/typespec-client-generator-core/empty-client", + }); + }); + + it("should not emit diagnostic for client with operations", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + op test(): void; + } + `); + + expectDiagnosticEmpty(diagnostics); + }); + + it("should emit diagnostic for empty interface client", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService; + + @client({service: MyService}) + interface MyClient { + } + `); + + expectDiagnostics(diagnostics, { + code: "@azure-tools/typespec-client-generator-core/empty-client", + }); + }); + + it("should not emit diagnostic for interface client with operations", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService; + + @client({service: MyService}) + interface MyClient { + op test(): void; + } + `); + + expectDiagnosticEmpty(diagnostics); + }); +}); From d6e538c27b78dc65004dd87937af0796a463c9a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:50:05 +0000 Subject: [PATCH 3/9] Add clarifying comment for client.type check Co-authored-by: iscai-msft <43154838+iscai-msft@users.noreply.github.com> --- packages/typespec-client-generator-core/src/clients.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/typespec-client-generator-core/src/clients.ts b/packages/typespec-client-generator-core/src/clients.ts index 0f7f6a5f57..074ed8c7be 100644 --- a/packages/typespec-client-generator-core/src/clients.ts +++ b/packages/typespec-client-generator-core/src/clients.ts @@ -218,6 +218,7 @@ export function createSdkClientType(context, client, sdkClientType), ); // Check if the client is empty (has no methods) + // Only emit diagnostic if client.type is defined (client has a source TypeSpec type to attach the diagnostic to) if (sdkClientType.methods.length === 0 && client.type) { diagnostics.add( createDiagnostic({ From b4c9017d139529f8b2888e8d0014f2fae425ca32 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 20:46:51 +0000 Subject: [PATCH 4/9] Add changeset for empty-client linter feature Co-authored-by: iscai-msft <43154838+iscai-msft@users.noreply.github.com> --- ...dd-linter-rules-for-empty-clients-2026-0-13-20-44-50.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/copilot-add-linter-rules-for-empty-clients-2026-0-13-20-44-50.md diff --git a/.chronus/changes/copilot-add-linter-rules-for-empty-clients-2026-0-13-20-44-50.md b/.chronus/changes/copilot-add-linter-rules-for-empty-clients-2026-0-13-20-44-50.md new file mode 100644 index 0000000000..c5fb84118f --- /dev/null +++ b/.chronus/changes/copilot-add-linter-rules-for-empty-clients-2026-0-13-20-44-50.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +Add linter warning for empty clients (clients with no operations) \ No newline at end of file From 0fa1138d6065f70425918f45738fbe8f4ee61cf9 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 20 Jan 2026 15:48:16 -0500 Subject: [PATCH 5/9] basic working --- .../src/clients.ts | 22 ++++--- .../test/decorators/client.test.ts | 60 +++++++++++++------ .../test/rules/require-client-suffix.test.ts | 13 +++- 3 files changed, 62 insertions(+), 33 deletions(-) diff --git a/packages/typespec-client-generator-core/src/clients.ts b/packages/typespec-client-generator-core/src/clients.ts index 074ed8c7be..adc4deae82 100644 --- a/packages/typespec-client-generator-core/src/clients.ts +++ b/packages/typespec-client-generator-core/src/clients.ts @@ -33,7 +33,7 @@ import { isSubscriptionId, updateWithApiVersionInformation, } from "./internal-utils.js"; -import { createDiagnostic } from "./lib.js"; +import { createDiagnostic, reportDiagnostic } from "./lib.js"; import { createSdkMethods, getSdkMethodParameter } from "./methods.js"; import { getCrossLanguageDefinitionId } from "./public-utils.js"; import { getSdkBuiltInType, getSdkCredentialParameter, getTypeSpecBuiltInType } from "./types.js"; @@ -217,18 +217,16 @@ export function createSdkClientType(context, client, sdkClientType), ); - // Check if the client is empty (has no methods) + // Check if the client is empty (has no methods and no children with operations) // Only emit diagnostic if client.type is defined (client has a source TypeSpec type to attach the diagnostic to) - if (sdkClientType.methods.length === 0 && client.type) { - diagnostics.add( - createDiagnostic({ - code: "empty-client", - target: client.type, - format: { - name: sdkClientType.name, - }, - }), - ); + if (sdkClientType.methods.length === 0 && !sdkClientType.children?.length && client.type) { + reportDiagnostic(context.program, { + code: "empty-client", + target: client.type, + format: { + name: sdkClientType.name, + }, + }); } addDefaultClientParameters(context, sdkClientType); // update initialization model properties diff --git a/packages/typespec-client-generator-core/test/decorators/client.test.ts b/packages/typespec-client-generator-core/test/decorators/client.test.ts index 1bf1812c21..a0d902ade2 100644 --- a/packages/typespec-client-generator-core/test/decorators/client.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/client.test.ts @@ -78,7 +78,9 @@ describe("@client", () => { ` @client @service - @test namespace MyService; + @test namespace MyService { + op test(): void; + } `, ) .toEmitDiagnostics([ @@ -96,7 +98,9 @@ describe("@client", () => { ` @client({name: "MySDK"}) @service - @test namespace MyService; + @test namespace MyService { + op test(): void; + } `, ) .toEmitDiagnostics([ @@ -474,7 +478,9 @@ describe("@operationGroup", () => { @service(#{ title: "DeviceUpdateClient", }) - namespace Azure.IoT.DeviceUpdate; + namespace Azure.IoT.DeviceUpdate { + op test(): void; + } `, ` @client({name: "DeviceUpdateClient", service: Azure.IoT.DeviceUpdate}, "python") @@ -482,10 +488,12 @@ describe("@operationGroup", () => { @operationGroup("java") interface SubClientOnlyForJava { + op javaOp(): void; } @operationGroup("python") interface SubClientOnlyForPython { + op pythonOp(): void; } `, ]; @@ -514,7 +522,7 @@ describe("@operationGroup", () => { strictEqual(listOperationGroups(runner.context, client).length, 1); } - // csharp should have no client + // csharp should have one client (default from service namespace since no explicit @client for csharp) { const runner = await createSdkTestRunner({ emitterName: "@azure-tools/typespec-csharp" }); const [_, diagnostics] = await runner.compileAndDiagnoseWithCustomization( @@ -522,8 +530,9 @@ describe("@operationGroup", () => { testCode[1], ); expectDiagnosticEmpty(diagnostics); - const client = listClients(runner.context); - strictEqual(client.length, 0); + const clients = listClients(runner.context); + strictEqual(clients.length, 1); + strictEqual(listOperationGroups(runner.context, clients[0]).length, 0); } }); }); @@ -1463,14 +1472,11 @@ it("operations under namespace or interface without @client or @operationGroup", }); describe("empty client diagnostic", () => { - it("should emit diagnostic for empty client", async () => { + it("should emit diagnostic for empty namespace client", async () => { const diagnostics = await runner.diagnose(` + @client @service namespace MyService { - @operationGroup - interface SubClient { - op test(): void; - } } `); @@ -1490,13 +1496,29 @@ describe("empty client diagnostic", () => { expectDiagnosticEmpty(diagnostics); }); + it("should not emit diagnostic for client with operation groups", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + @operationGroup + interface SubClient { + op test(): void; + } + } + `); + + expectDiagnosticEmpty(diagnostics); + }); + it("should emit diagnostic for empty interface client", async () => { const diagnostics = await runner.diagnose(` @service - namespace MyService; + namespace MyService { + op serviceOp(): void; - @client({service: MyService}) - interface MyClient { + @client({service: MyService}) + interface MyClient { + } } `); @@ -1508,11 +1530,13 @@ describe("empty client diagnostic", () => { it("should not emit diagnostic for interface client with operations", async () => { const diagnostics = await runner.diagnose(` @service - namespace MyService; + namespace MyService { + @route("/service") op serviceOp(): void; - @client({service: MyService}) - interface MyClient { - op test(): void; + @client({service: MyService}) + interface MyClient { + @route("/test") op test(): void; + } } `); diff --git a/packages/typespec-client-generator-core/test/rules/require-client-suffix.test.ts b/packages/typespec-client-generator-core/test/rules/require-client-suffix.test.ts index ae44dc4528..f350b4cc1e 100644 --- a/packages/typespec-client-generator-core/test/rules/require-client-suffix.test.ts +++ b/packages/typespec-client-generator-core/test/rules/require-client-suffix.test.ts @@ -25,7 +25,9 @@ it("namespace doesn't end in client", async () => { ` @client @service - namespace MyService; + namespace MyService { + op test(): void; + } `, ) .toEmitDiagnostics([ @@ -43,7 +45,9 @@ it("explicit client name doesn't ends with Client", async () => { ` @client({name: "MySDK"}) @service - namespace MyService; + namespace MyService { + op test(): void; + } `, ) .toEmitDiagnostics([ @@ -60,11 +64,14 @@ it("interface", async () => { .expect( ` @service - namespace MyService; + namespace MyService { + op serviceOp(): void; + } namespace MyCustomizations { @client({service: MyService}) interface MyInterface { + op test(): void; }; } `, From 281da761344f34173244cee1b29914db5be8a6cc Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 20 Jan 2026 15:55:18 -0500 Subject: [PATCH 6/9] add test with client location --- .../test/decorators/client.test.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/packages/typespec-client-generator-core/test/decorators/client.test.ts b/packages/typespec-client-generator-core/test/decorators/client.test.ts index a0d902ade2..eef829d8f4 100644 --- a/packages/typespec-client-generator-core/test/decorators/client.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/client.test.ts @@ -1542,4 +1542,52 @@ describe("empty client diagnostic", () => { expectDiagnosticEmpty(diagnostics); }); + + it("should not emit diagnostic when operation is moved into client via @clientLocation", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + interface Source { + @route("/moved") + @clientLocation(Target) + op movedOp(): void; + } + + interface Target { + } + } + `); + + expectDiagnosticEmpty(diagnostics); + }); + + it("should not emit diagnostic when operation is moved into root client via @clientLocation", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + interface Source { + @route("/moved") + @clientLocation(MyService) + op movedOp(): void; + } + } + `); + + expectDiagnosticEmpty(diagnostics); + }); + + it("should not emit diagnostic when operation is moved into new operation group via @clientLocation string", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + interface Source { + @route("/moved") + @clientLocation("NewTarget") + op movedOp(): void; + } + } + `); + + expectDiagnosticEmpty(diagnostics); + }); }); From 0a022675e6daa4dc2745cc988f2159fe2ae5e894 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 20 Jan 2026 16:04:00 -0500 Subject: [PATCH 7/9] add tests with client location --- .../src/cache.ts | 21 +++++++++ .../test/decorators/client.test.ts | 44 ++++++++++++++++--- .../test/validations/types.test.ts | 14 ++++-- 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/packages/typespec-client-generator-core/src/cache.ts b/packages/typespec-client-generator-core/src/cache.ts index 74dddd9d9f..16ac7ae4eb 100644 --- a/packages/typespec-client-generator-core/src/cache.ts +++ b/packages/typespec-client-generator-core/src/cache.ts @@ -307,6 +307,27 @@ export function prepareClientAndOperationCache(context: TCGCContext): void { const hasOperations = context.__clientToOperationsCache!.get(group)!.length > 0; const hasSubGroups = group.subOperationGroups?.length > 0; + // If the group is empty but originally had operations that were moved via @clientLocation, + // emit an empty-client diagnostic + if (!hasOperations && !hasSubGroups && group.type) { + const originalOperations = [...group.type.operations.values()]; + const hasMovedOperations = originalOperations.some((op) => { + const clientLocation = getClientLocation(context, op); + return clientLocation !== undefined && clientLocation !== group.type; + }); + if (hasMovedOperations) { + const name = + group.kind === "SdkClient" ? group.name : getLibraryName(context, group.type); + reportDiagnostic(context.program, { + code: "empty-client", + target: group.type, + format: { + name, + }, + }); + } + } + return hasOperations || hasSubGroups; }; diff --git a/packages/typespec-client-generator-core/test/decorators/client.test.ts b/packages/typespec-client-generator-core/test/decorators/client.test.ts index eef829d8f4..820f118de3 100644 --- a/packages/typespec-client-generator-core/test/decorators/client.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/client.test.ts @@ -1543,11 +1543,16 @@ describe("empty client diagnostic", () => { expectDiagnosticEmpty(diagnostics); }); - it("should not emit diagnostic when operation is moved into client via @clientLocation", async () => { + it("should not emit diagnostic for target when operation is moved into it via @clientLocation", async () => { + // Target was originally empty but receives an operation via @clientLocation + // Source still has an operation, so no empty-client diagnostic const diagnostics = await runner.diagnose(` @service namespace MyService { interface Source { + @route("/stay") + op stayOp(): void; + @route("/moved") @clientLocation(Target) op movedOp(): void; @@ -1561,7 +1566,30 @@ describe("empty client diagnostic", () => { expectDiagnosticEmpty(diagnostics); }); - it("should not emit diagnostic when operation is moved into root client via @clientLocation", async () => { + it("should emit diagnostic when @clientLocation moves all operations out of a client", async () => { + const diagnostics = await runner.diagnose(` + @service + namespace MyService { + interface Source { + @route("/moved") + @clientLocation(Target) + op movedOp(): void; + } + + interface Target { + @route("/target") + op targetOp(): void; + } + } + `); + + expectDiagnostics(diagnostics, { + code: "@azure-tools/typespec-client-generator-core/empty-client", + message: `Client "Source" has no operations. Clients should contain at least one operation.`, + }); + }); + + it("should emit diagnostic when @clientLocation moves all operations to root client", async () => { const diagnostics = await runner.diagnose(` @service namespace MyService { @@ -1573,10 +1601,13 @@ describe("empty client diagnostic", () => { } `); - expectDiagnosticEmpty(diagnostics); + expectDiagnostics(diagnostics, { + code: "@azure-tools/typespec-client-generator-core/empty-client", + message: `Client "Source" has no operations. Clients should contain at least one operation.`, + }); }); - it("should not emit diagnostic when operation is moved into new operation group via @clientLocation string", async () => { + it("should emit diagnostic when @clientLocation moves all operations to new operation group", async () => { const diagnostics = await runner.diagnose(` @service namespace MyService { @@ -1588,6 +1619,9 @@ describe("empty client diagnostic", () => { } `); - expectDiagnosticEmpty(diagnostics); + expectDiagnostics(diagnostics, { + code: "@azure-tools/typespec-client-generator-core/empty-client", + message: `Client "Source" has no operations. Clients should contain at least one operation.`, + }); }); }); diff --git a/packages/typespec-client-generator-core/test/validations/types.test.ts b/packages/typespec-client-generator-core/test/validations/types.test.ts index ff1ad53f13..7eeec23284 100644 --- a/packages/typespec-client-generator-core/test/validations/types.test.ts +++ b/packages/typespec-client-generator-core/test/validations/types.test.ts @@ -13,7 +13,7 @@ it("no duplicate operation with @clientLocation", async () => { ` @service namespace StorageService; - + interface StorageTasks { @clientLocation("StorageTasksReport") @route("/list") @@ -27,7 +27,11 @@ it("no duplicate operation with @clientLocation", async () => { `, ); - expectDiagnosticEmpty(diagnostics); + // StorageTasks becomes empty because all operations are moved out via @clientLocation + expectDiagnostics(diagnostics, { + code: "@azure-tools/typespec-client-generator-core/empty-client", + message: `Client "StorageTasks" has no operations. Clients should contain at least one operation.`, + }); }); it("no duplicate operation with @clientLocation another", async () => { @@ -128,7 +132,7 @@ it("duplicate operation with @clientLocation to new clients", async () => { ` @service namespace Contoso.WidgetManager; - + interface A { @clientLocation("B") @route("/a") @@ -152,6 +156,10 @@ it("duplicate operation with @clientLocation to new clients", async () => { code: "@azure-tools/typespec-client-generator-core/duplicate-client-name", message: 'Client name: "a" is duplicated in language scope: "AllScopes"', }, + { + code: "@azure-tools/typespec-client-generator-core/empty-client", + message: `Client "A" has no operations. Clients should contain at least one operation.`, + }, ]); }); From bffaaa3e8bd06eab4125b753b352d65eba79afe7 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Thu, 22 Jan 2026 15:13:04 -0500 Subject: [PATCH 8/9] address comments --- .../src/cache.ts | 21 ------------------- .../src/clients.ts | 2 +- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/packages/typespec-client-generator-core/src/cache.ts b/packages/typespec-client-generator-core/src/cache.ts index 16ac7ae4eb..74dddd9d9f 100644 --- a/packages/typespec-client-generator-core/src/cache.ts +++ b/packages/typespec-client-generator-core/src/cache.ts @@ -307,27 +307,6 @@ export function prepareClientAndOperationCache(context: TCGCContext): void { const hasOperations = context.__clientToOperationsCache!.get(group)!.length > 0; const hasSubGroups = group.subOperationGroups?.length > 0; - // If the group is empty but originally had operations that were moved via @clientLocation, - // emit an empty-client diagnostic - if (!hasOperations && !hasSubGroups && group.type) { - const originalOperations = [...group.type.operations.values()]; - const hasMovedOperations = originalOperations.some((op) => { - const clientLocation = getClientLocation(context, op); - return clientLocation !== undefined && clientLocation !== group.type; - }); - if (hasMovedOperations) { - const name = - group.kind === "SdkClient" ? group.name : getLibraryName(context, group.type); - reportDiagnostic(context.program, { - code: "empty-client", - target: group.type, - format: { - name, - }, - }); - } - } - return hasOperations || hasSubGroups; }; diff --git a/packages/typespec-client-generator-core/src/clients.ts b/packages/typespec-client-generator-core/src/clients.ts index adc4deae82..066eeee9a2 100644 --- a/packages/typespec-client-generator-core/src/clients.ts +++ b/packages/typespec-client-generator-core/src/clients.ts @@ -219,7 +219,7 @@ export function createSdkClientType Date: Thu, 22 Jan 2026 15:23:55 -0500 Subject: [PATCH 9/9] clean up to only throw for explicit clients with no ops or children --- .../src/clients.ts | 4 +-- .../test/decorators/client.test.ts | 25 ++++++++----------- .../test/validations/types.test.ts | 11 +++----- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/packages/typespec-client-generator-core/src/clients.ts b/packages/typespec-client-generator-core/src/clients.ts index 066eeee9a2..fe2d5983a3 100644 --- a/packages/typespec-client-generator-core/src/clients.ts +++ b/packages/typespec-client-generator-core/src/clients.ts @@ -217,9 +217,9 @@ export function createSdkClientType(context, client, sdkClientType), ); - // Check if the client is empty (has no methods and no children with operations) + // Check if the client is empty (has no methods and no children) // Only emit diagnostic if client.type is defined (client has a source TypeSpec type to attach the diagnostic to) - if (sdkClientType.methods.length === 0 && client.type) { + if (sdkClientType.methods.length === 0 && !sdkClientType.children?.length && client.type) { reportDiagnostic(context.program, { code: "empty-client", target: client.type, diff --git a/packages/typespec-client-generator-core/test/decorators/client.test.ts b/packages/typespec-client-generator-core/test/decorators/client.test.ts index 820f118de3..cfec379c82 100644 --- a/packages/typespec-client-generator-core/test/decorators/client.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/client.test.ts @@ -1566,7 +1566,9 @@ describe("empty client diagnostic", () => { expectDiagnosticEmpty(diagnostics); }); - it("should emit diagnostic when @clientLocation moves all operations out of a client", async () => { + it("should not emit diagnostic when @clientLocation moves all operations out of a non-explicit client", async () => { + // When all operations are moved out of a client that wasn't explicitly defined with @client/@operationGroup, + // the client is simply removed (not warned about) since it was auto-generated const diagnostics = await runner.diagnose(` @service namespace MyService { @@ -1583,13 +1585,11 @@ describe("empty client diagnostic", () => { } `); - expectDiagnostics(diagnostics, { - code: "@azure-tools/typespec-client-generator-core/empty-client", - message: `Client "Source" has no operations. Clients should contain at least one operation.`, - }); + expectDiagnosticEmpty(diagnostics); }); - it("should emit diagnostic when @clientLocation moves all operations to root client", async () => { + it("should not emit diagnostic when @clientLocation moves all operations to root client", async () => { + // Source interface becomes empty and is removed since it wasn't explicitly defined const diagnostics = await runner.diagnose(` @service namespace MyService { @@ -1601,13 +1601,11 @@ describe("empty client diagnostic", () => { } `); - expectDiagnostics(diagnostics, { - code: "@azure-tools/typespec-client-generator-core/empty-client", - message: `Client "Source" has no operations. Clients should contain at least one operation.`, - }); + expectDiagnosticEmpty(diagnostics); }); - it("should emit diagnostic when @clientLocation moves all operations to new operation group", async () => { + it("should not emit diagnostic when @clientLocation moves all operations to new operation group", async () => { + // Source interface becomes empty and is removed since it wasn't explicitly defined const diagnostics = await runner.diagnose(` @service namespace MyService { @@ -1619,9 +1617,6 @@ describe("empty client diagnostic", () => { } `); - expectDiagnostics(diagnostics, { - code: "@azure-tools/typespec-client-generator-core/empty-client", - message: `Client "Source" has no operations. Clients should contain at least one operation.`, - }); + expectDiagnosticEmpty(diagnostics); }); }); diff --git a/packages/typespec-client-generator-core/test/validations/types.test.ts b/packages/typespec-client-generator-core/test/validations/types.test.ts index 7eeec23284..264e53dcc3 100644 --- a/packages/typespec-client-generator-core/test/validations/types.test.ts +++ b/packages/typespec-client-generator-core/test/validations/types.test.ts @@ -28,10 +28,8 @@ it("no duplicate operation with @clientLocation", async () => { ); // StorageTasks becomes empty because all operations are moved out via @clientLocation - expectDiagnostics(diagnostics, { - code: "@azure-tools/typespec-client-generator-core/empty-client", - message: `Client "StorageTasks" has no operations. Clients should contain at least one operation.`, - }); + // Since it wasn't explicitly defined with @client/@operationGroup, it's simply removed (no diagnostic) + expectDiagnosticEmpty(diagnostics); }); it("no duplicate operation with @clientLocation another", async () => { @@ -146,6 +144,7 @@ it("duplicate operation with @clientLocation to new clients", async () => { `, ); + // A becomes empty and is removed since it wasn't explicitly defined with @client/@operationGroup expectDiagnostics(diagnostics, [ { code: "@azure-tools/typespec-client-generator-core/duplicate-client-name", @@ -156,10 +155,6 @@ it("duplicate operation with @clientLocation to new clients", async () => { code: "@azure-tools/typespec-client-generator-core/duplicate-client-name", message: 'Client name: "a" is duplicated in language scope: "AllScopes"', }, - { - code: "@azure-tools/typespec-client-generator-core/empty-client", - message: `Client "A" has no operations. Clients should contain at least one operation.`, - }, ]); });