Skip to content
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
13 changes: 10 additions & 3 deletions packages/emitter-framework/src/testing/scenario-test/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ function describeScenarios(
for (const scenario of scenarioFile.scenarios) {
const isOnly = scenario.title.includes("only:");
const isSkip = scenario.title.includes("skip:");
const isNoFormat = scenario.title.includes("no-format");
const describeFn = isSkip ? describe.skip : isOnly ? describe.only : describe;

let outputFiles: Record<string, string>;
Expand Down Expand Up @@ -199,14 +200,20 @@ function describeScenarios(

if (SCENARIOS_UPDATE) {
try {
testBlock.content = await languageConfiguration.format(result);
testBlock.content = isNoFormat
? result
: await languageConfiguration.format(result);
} catch {
// If formatting fails, we still want to update the content
testBlock.content = result;
}
} else {
const expected = await languageConfiguration.format(testBlock.content);
const actual = await languageConfiguration.format(result);
const expected = isNoFormat
? testBlock.content
: await languageConfiguration.format(testBlock.content);
const actual = isNoFormat
? result
: await languageConfiguration.format(result);
expect(actual).toBe(expected);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export function ClientContextFactoryDeclaration(props: ClientContextFactoryProps
parameters={parameters}
>
{resolvedEndpoint}
<hbr />
return <ts.FunctionCallExpression target={httpRuntimeTemplateLib.getClient} args={[args]} />
</FunctionDeclaration>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Children } from "@alloy-js/core";
import { type Children, List } from "@alloy-js/core";
import * as ts from "@alloy-js/typescript";
import * as cl from "@typespec/http-client";
import { ClientContextDeclaration } from "./client-context-declaration.jsx";
Expand All @@ -15,9 +15,11 @@ export function ClientContext(props: ClientContextProps) {
const fileName = namePolicy.getName(props.client.name + "Context", "variable");
return (
<ts.SourceFile path={`${fileName}.ts`}>
<ClientContextDeclaration client={props.client} />
<ClientContextOptionsDeclaration client={props.client} />
<ClientContextFactoryDeclaration client={props.client} />
<List hardline>
<ClientContextDeclaration client={props.client} />
<ClientContextOptionsDeclaration client={props.client} />
<ClientContextFactoryDeclaration client={props.client} />
</List>
</ts.SourceFile>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function ParametrizedEndpoint(props: ParametrizedEndpointProps) {
{code`
"${props.template}".replace(/{([^}]+)}/g, (_, key) =>
key in ${paramsRef} ? String(params[key]) : (() => { throw new Error(\`Missing parameter: $\{key}\`); })()
);
)
`}
</ts.VarDeclaration>
);
Expand Down
20 changes: 11 additions & 9 deletions packages/http-client-js/src/components/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,15 @@ export function ClientClass(props: ClientClassProps) {
return (
<ts.ClassDeclaration export name={clientName} refkey={clientClassRef}>
<List hardline>
<ts.ClassField
name="context"
jsPrivate
refkey={contextMemberRef}
type={contextDeclarationRef}
/>
<For each={subClients} hardline semicolon>
<>
<ts.ClassField
name="context"
jsPrivate
refkey={contextMemberRef}
type={contextDeclarationRef}
/>;
</>
<For each={subClients} hardline semicolon ender={";"}>
{(subClient) => <SubClientClassField client={subClient} />}
</For>
<ClientConstructor client={props.client} />
Expand Down Expand Up @@ -138,13 +140,13 @@ function ClientConstructor(props: ClientConstructorProps) {
<ts.ClassMethod name="constructor" parameters={constructorParameters}>
{clientContextFieldRef} ={" "}
<ts.FunctionCallExpression target={clientContextFactoryRef} args={args} />;<br />
<For each={subClients} joiner=";" hardline>
<For each={subClients} hardline semicolon ender={";"}>
{(subClient) => {
const subClientFieldRef = getSubClientClassFieldRef(subClient);
const subClientArgs = calculateSubClientArgs(subClient, constructorParameters);
return (
<>
{subClientFieldRef} = <NewClientExpression client={subClient} args={subClientArgs} />;
{subClientFieldRef} = <NewClientExpression client={subClient} args={subClientArgs} />
</>
);
}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Should generate a client with consistent and normal Typescript code no-format

This test verifies that a basic service with multiple routes results in "decently" formatted output.

The goal isn't perfect formatting, just some level of consistency for basic usecases. Prettier or other formatters should be run
to achieve actually good formatting.

- no double semicolons (constructor lines, createWidgetsClientContext, for example)
- the constructor properly separates sub-client assignments with newlines.
- each type declaration in the DemoServiceClient class has semicolons at the end, consistently.
- newlines separate `ClientContext` blocks

## TypeSpec

```tsp
@service(#{ title: "Multi Route Service" })
namespace DemoService;

model Widget {
id: string;
weight: int32;
}

model User {
id: string;
name: string;
}

@error
model Error {
code: int32;
message: string;
}

@route("/widgets")
@tag("Widgets")
interface Widgets {
@get list(): Widget[] | Error;
@get read(@path id: string): Widget | Error;
}

@route("/users")
@tag("Users")
interface Users {
@get list(): User[] | Error;
@get read(@path id: string): User | Error;
}
```

## TypeScript

### Client

It generates a root client with multiple sub-clients, each properly separated by newlines in the constructor.

```ts src/demoServiceClient.ts class DemoServiceClient
export class DemoServiceClient {
#context: DemoServiceClientContext;
widgetsClient: WidgetsClient;
usersClient: UsersClient;
constructor(endpoint: string, options?: DemoServiceClientOptions) {
this.#context = createDemoServiceClientContext(endpoint, options);
this.widgetsClient = new WidgetsClient(endpoint, options);
this.usersClient = new UsersClient(endpoint, options);
}
}
```

It should have interface/function end-curly-braces with a newline before the next one, and no double-semicolons:

```ts src/api/widgetsClient/widgetsClientContext.ts
import { type Client, type ClientOptions, getClient } from "@typespec/ts-http-runtime";

export interface WidgetsClientContext extends Client {}
export interface WidgetsClientOptions extends ClientOptions {
endpoint?: string;
}
export function createWidgetsClientContext(
endpoint: string,
options?: WidgetsClientOptions,
): WidgetsClientContext {
const params: Record<string, any> = {
endpoint: endpoint
};
const resolvedEndpoint = "{endpoint}".replace(/{([^}]+)}/g, (_, key) =>
key in params ? String(params[key]) : (() => { throw new Error(`Missing parameter: ${key}`); })()
);
return getClient(resolvedEndpoint,{
...options
})
}

```