Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[tcgc] add namespace flag #2161

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions packages/typespec-client-generator-core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export async function createSdkContext<
decoratorsAllowList: [...defaultDecoratorsAllowList, ...(options?.additionalDecorators ?? [])],
previewStringRegex: options?.versioning?.previewStringRegex || tcgcContext.previewStringRegex,
disableUsageAccessPropagationToBase: options?.disableUsageAccessPropagationToBase ?? false,
namespace: context.options["namespace"],
};
sdkContext.sdkPackage = diagnostics.pipe(getSdkPackage(sdkContext));
for (const client of sdkContext.sdkPackage.clients) {
Expand Down
19 changes: 17 additions & 2 deletions packages/typespec-client-generator-core/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1113,9 +1113,16 @@ export function getClientNamespace(
entity: Namespace | Interface | Model | Enum | Union,
): string {
const override = getScopedDecoratorData(context, clientNamespaceKey, entity);
if (override) return override;
if (override) {
if (context.namespace && !override.startsWith(context.namespace)) {
if (context.namespace) {
throw new Error("The client namespace needs to correspond with the overwritten namespace.");
}
}
return override;
}
if (!entity.namespace) {
return "";
return context.namespace || "";
}
if (entity.kind === "Namespace") {
return getNamespaceFullNameWithOverride(context, entity);
Expand All @@ -1135,6 +1142,14 @@ function getNamespaceFullNameWithOverride(context: TCGCContext, namespace: Names
segments.unshift(current.name);
current = current.namespace;
}
if (context.namespace) {
// if we override with namespace flag, we should override the global namespace to the namespace flag
const globalNamespaceName = getNamespaceFullName(context.program.getGlobalNamespaceType());
return segments
.join(".")
.replace(globalNamespaceName, context.namespace);
}

return segments.join(".");
}

Expand Down
5 changes: 5 additions & 0 deletions packages/typespec-client-generator-core/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export interface TCGCContext {
previewStringRegex: RegExp;
disableUsageAccessPropagationToBase: boolean;
__pagedResultSet: Set<SdkType>;
namespace?: string;
}

export interface SdkContext<
Expand All @@ -77,6 +78,7 @@ export interface SdkEmitterOptions {
"examples-directory"?: string;
"examples-dir"?: string;
"emitter-name"?: string;
namespace?: string;
}

export interface SdkClient {
Expand Down Expand Up @@ -772,6 +774,9 @@ export type SdkMethod<TServiceOperation extends SdkServiceOperation> =

export interface SdkPackage<TServiceOperation extends SdkServiceOperation> {
name: string;
/**
* @deprecated Look at `.namespaces` instead
*/
rootNamespace: string;
clients: SdkClientType<TServiceOperation>[];
models: SdkModelType[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
BooleanLiteral,
createDiagnosticCollector,
Diagnostic,
Enum,

Check warning on line 5 in packages/typespec-client-generator-core/src/internal-utils.ts

View workflow job for this annotation

GitHub Actions / Lint

'Enum' is defined but never used. Allowed unused vars must match /^_/u
getDeprecationDetails,
getNamespaceFullName,
Interface,
Expand All @@ -27,11 +28,12 @@
HttpOperationResponseContent,
} from "@typespec/http";
import { getAddedOnVersions, getRemovedOnVersions, getVersions } from "@typespec/versioning";
import { getParamAlias } from "./decorators.js";
import { getClientNamespace, getParamAlias } from "./decorators.js";

Check warning on line 31 in packages/typespec-client-generator-core/src/internal-utils.ts

View workflow job for this annotation

GitHub Actions / Lint

'getClientNamespace' is defined but never used. Allowed unused vars must match /^_/u
import {
DecoratorInfo,
SdkBuiltInType,
SdkClient,
SdkContext,

Check warning on line 36 in packages/typespec-client-generator-core/src/internal-utils.ts

View workflow job for this annotation

GitHub Actions / Lint

'SdkContext' is defined but never used. Allowed unused vars must match /^_/u
SdkEnumType,
SdkHttpResponse,
SdkModelPropertyType,
Expand Down Expand Up @@ -83,6 +85,8 @@
}

/**
*
* @deprecated Use `.namespaces` from sdkContext.sdkPackage instead
*
* @param context
* @param namespace If we know explicitly the namespace of the client, pass this in
Expand Down
1 change: 1 addition & 0 deletions packages/typespec-client-generator-core/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const EmitterOptionsSchema: JSONSchemaType<SdkEmitterOptions> = {
"examples-directory": { type: "string", nullable: true },
"examples-dir": { type: "string", nullable: true },
"emitter-name": { type: "string", nullable: true },
namespace: { type: "string", nullable: true },
},
};

Expand Down
26 changes: 16 additions & 10 deletions packages/typespec-client-generator-core/src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {
SdkBodyModelPropertyType,
SdkClient,
SdkClientType,
SdkContext,
SdkEmitterOptions,
SdkEndpointParameter,
SdkEndpointType,
SdkEnumType,
Expand Down Expand Up @@ -807,7 +809,7 @@ function createSdkClientType<TServiceOperation extends SdkServiceOperation>(
summary: getSummary(context.program, client.type),
methods: [],
apiVersions: context.__tspTypeToApiVersions.get(client.type)!,
nameSpace: getClientNamespaceStringHelper(context, client.service)!,
nameSpace: getClientNamespaceStringHelper(context, client.service)!, // eslint-disable-line @typescript-eslint/no-deprecated
clientNamespace: getClientNamespace(context, client.type),
initialization: diagnostics.pipe(getSdkInitializationType(context, client)),
decorators: diagnostics.pipe(getTypeDecorators(context, client.type)),
Expand Down Expand Up @@ -897,17 +899,20 @@ function populateApiVersionInformation(context: TCGCContext): void {
}
}

export function getSdkPackage<TServiceOperation extends SdkServiceOperation>(
context: TCGCContext,
export function getSdkPackage<
TOptions extends Record<string, any> = SdkEmitterOptions,
TServiceOperation extends SdkServiceOperation = SdkHttpOperation,
>(
context: SdkContext<TOptions, TServiceOperation>,
): [SdkPackage<TServiceOperation>, readonly Diagnostic[]] {
const diagnostics = createDiagnosticCollector();
populateApiVersionInformation(context);
diagnostics.pipe(handleAllTypes(context));
const crossLanguagePackageId = diagnostics.pipe(getCrossLanguagePackageId(context));
const allReferencedTypes = getAllReferencedTypes(context);
const sdkPackage: SdkPackage<TServiceOperation> = {
name: getClientNamespaceString(context)!,
rootNamespace: getClientNamespaceString(context)!,
name: getClientNamespaceString(context)!, // eslint-disable-line @typescript-eslint/no-deprecated
rootNamespace: getClientNamespaceString(context)!, // eslint-disable-line @typescript-eslint/no-deprecated
clients: listClients(context).map((c) => diagnostics.pipe(createSdkClientType(context, c))),
models: allReferencedTypes.filter((x): x is SdkModelType => x.kind === "model"),
enums: allReferencedTypes.filter((x): x is SdkEnumType => x.kind === "enum"),
Expand All @@ -917,13 +922,14 @@ export function getSdkPackage<TServiceOperation extends SdkServiceOperation>(
crossLanguagePackageId,
namespaces: [],
};
organizeNamespaces(sdkPackage);
organizeNamespaces(context, sdkPackage);
return diagnostics.wrap(sdkPackage);
}

function organizeNamespaces<TServiceOperation extends SdkServiceOperation>(
sdkPackage: SdkPackage<TServiceOperation>,
) {
function organizeNamespaces<
TOptions extends Record<string, any> = SdkEmitterOptions,
TServiceOperation extends SdkServiceOperation = SdkHttpOperation,
>(context: SdkContext<TOptions, TServiceOperation>, sdkPackage: SdkPackage<TServiceOperation>) {
const clients = [...sdkPackage.clients];
while (clients.length > 0) {
const client = clients.shift()!;
Expand All @@ -944,7 +950,7 @@ function organizeNamespaces<TServiceOperation extends SdkServiceOperation>(
}
}

function getSdkNamespace<TServiceOperation extends SdkServiceOperation>(
function getSdkNamespace<TServiceOperation extends SdkServiceOperation = SdkHttpOperation>(
sdkPackage: SdkPackage<TServiceOperation>,
namespace: string,
) {
Expand Down
3 changes: 3 additions & 0 deletions packages/typespec-client-generator-core/src/public-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,15 @@
/**
* Get the client's namespace for generation. If package-name is passed in config, we return
* that value as our namespace. Otherwise, we default to the TypeSpec service namespace.
*
* @deprecated Access namespace information by iterating through `sdkPackage.namespaces` instead
*
* @param program
* @param context
* @returns
*/
export function getClientNamespaceString(context: TCGCContext): string | undefined {
return getClientNamespaceStringHelper(context, listServices(context.program)[0]?.type);

Check warning on line 114 in packages/typespec-client-generator-core/src/public-utils.ts

View workflow job for this annotation

GitHub Actions / Lint

`getClientNamespaceStringHelper` is deprecated. Use `.namespaces` from sdkContext.sdkPackage instead
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ok, strictEqual } from "assert";
import { beforeEach, describe, it } from "vitest";
import { SdkTestRunner, createSdkTestRunner } from "../test-host.js";
import { SdkEmitterOptions } from "../../src/interfaces.js";
import { SdkTestRunner, createSdkContextTestHelper, createSdkTestRunner } from "../test-host.js";

describe("typespec-client-generator-core: namespaces", () => {
let runner: SdkTestRunner;
Expand Down Expand Up @@ -238,4 +239,77 @@ describe("typespec-client-generator-core: namespaces", () => {
ok(petActionClient);
strictEqual(petActionClient.clientNamespace, "PetStoreRenamed.SubNamespace");
});

it("restructure client with namespace flag", async () => {
await runner.compile(
`
@service({
title: "Pet Store",
})
namespace PetStore;

@route("/feed")
op feed(): void;

@route("/op2")
op pet(): void;
`,
);
const sdkPackage = (
await createSdkContextTestHelper<SdkEmitterOptions>(runner.context.program, {
namespace: "PetStoreRenamed",
})
).sdkPackage;
const foodClient = sdkPackage.clients.find((x) => x.name === "PetStoreRenamedClient");
ok(foodClient);
strictEqual(foodClient.clientNamespace, "PetStoreRenamed");
});

it("restructure client hierarchy with namespace flag, renaming of client name, and client namespace name", async () => {
await runner.compileWithCustomization(
`
@service({
title: "Pet Store",
})
namespace PetStore;

@route("/feed")
op feed(): void;

@route("/op2")
op pet(): void;
`,
`
namespace PetStoreRenamed; // this namespace will be the namespace of the clients and operation groups defined in this customization file

@client({
name: "FoodClient",
service: PetStore
})
interface Client1 {
feed is PetStore.feed
}

@client({
name: "PetActionClient",
service: PetStore
})
@clientNamespace("PetStoreRenamed.SubNamespace") // use @clientNamespace to specify the namespace of the client
interface Client2 {
pet is PetStore.pet
}
`,
);
const sdkPackage = (
await createSdkContextTestHelper<SdkEmitterOptions>(runner.context.program, {
namespace: "PetStoreRenamed",
})
).sdkPackage;
const foodClient = sdkPackage.clients.find((x) => x.name === "FoodClient");
ok(foodClient);
strictEqual(foodClient.clientNamespace, "PetStoreRenamed");
const petActionClient = sdkPackage.clients.find((x) => x.name === "PetActionClient");
ok(petActionClient);
strictEqual(petActionClient.clientNamespace, "PetStoreRenamed.SubNamespace");
});
});
Loading