Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@azure-tools/typespec-client-generator-core"
---

Fix "Cannot read properties of undefined" crash in the `inconsistent-multiple-service-dependency` validation when a service merged into a multi-service client does not specify a version for a depended library (e.g. its latest service version has no matching `@useDependency` entry). The validation now falls back to the latest version of the depended library, matching the behavior of downstream emitters.
17 changes: 15 additions & 2 deletions packages/typespec-client-generator-core/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
isBody,
isBodyRoot,
} from "@typespec/http";
import { resolveVersions } from "@typespec/versioning";
import { getVersion, resolveVersions, type Version } from "@typespec/versioning";
import {
AccessDecorator,
AlternateTypeDecorator,
Expand Down Expand Up @@ -274,8 +274,21 @@ function validateMultipleServiceDependencyVersions(
for (const [depNs, depVersion] of resolutions[resolutions.length - 1].versions) {
// Ignore versions of the merged services themselves.
if (serviceSet.has(depNs)) continue;
// When the service does not specify a version for the depended library
// (e.g. the latest service version has no `@useDependency` mapping for it),
// fall back to the latest version of the depended library, matching the
// behavior expected by downstream emitters.
let resolvedDepVersion: Version | undefined = depVersion;
if (resolvedDepVersion === undefined) {
const depVersionMap = getVersion(program, depNs);
const allDepVersions = depVersionMap?.getVersions();
if (allDepVersions && allDepVersions.length > 0) {
resolvedDepVersion = allDepVersions[allDepVersions.length - 1];
}
}
if (resolvedDepVersion === undefined) continue;
const versions = depVersions.get(depNs) ?? new Set<string>();
versions.add(depVersion.value ?? depVersion.name);
versions.add(resolvedDepVersion.value ?? resolvedDepVersion.name);
depVersions.set(depNs, versions);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2196,6 +2196,63 @@ it("no warning when multiple services share the same dependency version", async
expectDiagnostics(diagnostics, []);
});

it("no crash when a merged service does not specify a version for the depended library", async () => {
const [{ program }, diagnostics] = await SimpleBaseTester.compileAndDiagnose(
createClientCustomizationInput(
`
@versioned(LibVersions)
namespace SharedLib {
enum LibVersions {
v1: "v1",
v2: "v2",
}
}

@service
@versioned(VersionsA)
namespace ServiceA {
enum VersionsA {
@useDependency(SharedLib.LibVersions.v2)
av1,
}
op a(): void;
}
@service
@versioned(VersionsB)
namespace ServiceB {
enum VersionsB {
@useDependency(SharedLib.LibVersions.v2)
bv1,
bv2,
}
op b(): void;
}`,
`
@client(
{
name: "CombineClient",
service: [ServiceA, ServiceB],
autoMergeService: true,
}
)
namespace CombineClient {}
`,
),
);
await createSdkContextForTester(program);
// ServiceA explicitly uses SharedLib.v2; ServiceB does not specify a version
// so the validation falls back to SharedLib's latest version (v2). Both
// resolve to the same version, so no inconsistency warning is reported.
expectDiagnostics(
diagnostics.filter(
(d) =>
d.code ===
"@azure-tools/typespec-client-generator-core/inconsistent-multiple-service-dependency",
),
[],
);
});

it("multiple clients from single service", async () => {
const { program } = await SimpleBaseTester.compile(
createClientCustomizationInput(
Expand Down
Loading