@typespec/emitter-framework 0.11.0-dev.0 → 0.11.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/src/core/components/index.d.ts +2 -0
  2. package/dist/src/core/components/index.d.ts.map +1 -1
  3. package/dist/src/core/components/index.js +3 -1
  4. package/dist/src/core/components/overrides/component-overrides.d.ts +64 -0
  5. package/dist/src/core/components/overrides/component-overrides.d.ts.map +1 -0
  6. package/dist/src/core/components/overrides/component-overrides.js +40 -0
  7. package/dist/src/core/components/overrides/config.d.ts +28 -0
  8. package/dist/src/core/components/overrides/config.d.ts.map +1 -0
  9. package/dist/src/core/components/overrides/config.js +54 -0
  10. package/dist/src/core/components/overrides/context.d.ts +11 -0
  11. package/dist/src/core/components/overrides/context.d.ts.map +1 -0
  12. package/dist/src/core/components/overrides/context.js +8 -0
  13. package/dist/src/csharp/components/class/declaration.d.ts +1 -1
  14. package/dist/src/csharp/components/class/declaration.d.ts.map +1 -1
  15. package/dist/src/csharp/components/class/declaration.js +4 -3
  16. package/dist/src/csharp/components/class/declaration.test.js +74 -1
  17. package/dist/src/csharp/components/type-expression.d.ts.map +1 -1
  18. package/dist/src/csharp/components/type-expression.js +54 -5
  19. package/dist/src/csharp/components/type-expression.test.js +60 -0
  20. package/dist/src/testing/scenario-test/harness.js +6 -1
  21. package/dist/src/typescript/components/interface-member.d.ts.map +1 -1
  22. package/dist/src/typescript/components/interface-member.js +11 -19
  23. package/dist/src/typescript/components/type-expression.d.ts +0 -1
  24. package/dist/src/typescript/components/type-expression.d.ts.map +1 -1
  25. package/dist/src/typescript/components/type-expression.js +11 -11
  26. package/dist/src/typescript/components/type-transform.d.ts.map +1 -1
  27. package/dist/src/typescript/components/type-transform.js +3 -3
  28. package/dist/test/typescript/components/component-override.test.d.ts +2 -0
  29. package/dist/test/typescript/components/component-override.test.d.ts.map +1 -0
  30. package/dist/test/typescript/components/component-override.test.js +77 -0
  31. package/dist/test/typescript/test-host.d.ts +2 -6
  32. package/dist/test/typescript/test-host.d.ts.map +1 -1
  33. package/dist/test/typescript/test-host.js +4 -15
  34. package/package.json +2 -6
  35. package/src/core/components/index.tsx +2 -0
  36. package/src/core/components/overrides/component-overrides.tsx +134 -0
  37. package/src/core/components/overrides/config.ts +85 -0
  38. package/src/core/components/overrides/context.ts +14 -0
  39. package/src/csharp/components/class/declaration.test.tsx +65 -1
  40. package/src/csharp/components/class/declaration.tsx +7 -3
  41. package/src/csharp/components/type-expression.test.tsx +64 -0
  42. package/src/csharp/components/type-expression.tsx +54 -5
  43. package/src/testing/scenario-test/harness.ts +6 -1
  44. package/src/typescript/components/interface-member.tsx +8 -14
  45. package/src/typescript/components/type-expression.tsx +7 -8
  46. package/src/typescript/components/type-transform.tsx +3 -7
  47. package/test/typescript/components/component-override.test.tsx +71 -0
  48. package/test/typescript/test-host.ts +4 -16
@@ -1,6 +1,15 @@
1
- import { type Children, code } from "@alloy-js/core";
1
+ import { Experimental_OverridableComponent } from "#core/index.js";
2
+ import { code, type Children } from "@alloy-js/core";
2
3
  import { Reference } from "@alloy-js/csharp";
3
- import { getTypeName, type IntrinsicType, type Scalar, type Type } from "@typespec/compiler";
4
+ import {
5
+ getTypeName,
6
+ isNullType,
7
+ isVoidType,
8
+ type IntrinsicType,
9
+ type Scalar,
10
+ type Type,
11
+ type Union,
12
+ } from "@typespec/compiler";
4
13
  import type { Typekit } from "@typespec/compiler/typekit";
5
14
  import { useTsp } from "../../core/index.js";
6
15
  import { reportTypescriptDiagnostic } from "../../typescript/lib.js";
@@ -11,9 +20,19 @@ export interface TypeExpressionProps {
11
20
  }
12
21
 
13
22
  export function TypeExpression(props: TypeExpressionProps): Children {
23
+ if (props.type.kind === "Union") {
24
+ const nullabletype = getNullableUnionInnerType(props.type);
25
+ if (nullabletype) {
26
+ return code`${(<TypeExpression type={nullabletype} />)}?`;
27
+ }
28
+ }
14
29
  const { $ } = useTsp();
15
30
  if (isDeclaration($, props.type)) {
16
- return <Reference refkey={efRefkey(props.type)} />;
31
+ return (
32
+ <Experimental_OverridableComponent reference type={props.type}>
33
+ <Reference refkey={efRefkey(props.type)} />
34
+ </Experimental_OverridableComponent>
35
+ );
17
36
  }
18
37
  if ($.scalar.is(props.type)) {
19
38
  return getScalarIntrinsicExpression($, props.type);
@@ -21,6 +40,15 @@ export function TypeExpression(props: TypeExpressionProps): Children {
21
40
  return code`${(<TypeExpression type={props.type.indexer.value} />)}[]`;
22
41
  } else if ($.record.is(props.type)) {
23
42
  return code`IDictionary<string, ${(<TypeExpression type={props.type.indexer.value} />)}>`;
43
+ } else if ($.literal.isString(props.type)) {
44
+ // c# doesn't have literal types, so we map them to their corresponding C# types in general
45
+ return code`string`;
46
+ } else if ($.literal.isNumeric(props.type)) {
47
+ return Number.isInteger(props.type.value) ? code`int` : code`double`;
48
+ } else if ($.literal.isBoolean(props.type)) {
49
+ return code`bool`;
50
+ } else if (isVoidType(props.type)) {
51
+ return code`void`;
24
52
  }
25
53
 
26
54
  throw new Error(
@@ -28,6 +56,28 @@ export function TypeExpression(props: TypeExpressionProps): Children {
28
56
  );
29
57
  }
30
58
 
59
+ /** Get the inner type if the union is a nullable, otherwise return undefined */
60
+ function getNullableUnionInnerType(u: Union): Type | undefined {
61
+ const isNull = (type: Type) => isNullType(type) || isVoidType(type);
62
+
63
+ if (Array.from(u.variants.values()).some((v) => isNull(v.type))) {
64
+ const { $ } = useTsp();
65
+ const left = Array.from(u.variants.values()).filter((v) => !isNull(v.type));
66
+ if (left.length === 0) {
67
+ // a union only has null or void?
68
+ return $.intrinsic.void;
69
+ } else if (left.length === 1) {
70
+ return left[0].type;
71
+ } else {
72
+ return $.union.create({
73
+ name: u.name,
74
+ variants: left,
75
+ });
76
+ }
77
+ }
78
+ return undefined;
79
+ }
80
+
31
81
  const intrinsicNameToCSharpType = new Map<string, string | null>([
32
82
  // Core types
33
83
  ["unknown", "object"], // Matches C#'s `object`
@@ -107,8 +157,7 @@ function isDeclaration($: Typekit, type: Type): boolean {
107
157
  if ($.array.is(type) || $.record.is(type)) {
108
158
  return false;
109
159
  }
110
-
111
- return Boolean(type.name);
160
+ return true;
112
161
  case "Union":
113
162
  return Boolean(type.name);
114
163
  default:
@@ -198,7 +198,12 @@ function describeScenarios(
198
198
  );
199
199
 
200
200
  if (SCENARIOS_UPDATE) {
201
- testBlock.content = await languageConfiguration.format(result);
201
+ try {
202
+ testBlock.content = await languageConfiguration.format(result);
203
+ } catch {
204
+ // If formatting fails, we still want to update the content
205
+ testBlock.content = result;
206
+ }
202
207
  } else {
203
208
  const expected = await languageConfiguration.format(testBlock.content);
204
209
  const actual = await languageConfiguration.format(result);
@@ -1,7 +1,6 @@
1
1
  import { type Children } from "@alloy-js/core";
2
2
  import * as ts from "@alloy-js/typescript";
3
3
  import { isNeverType, type ModelProperty, type Operation } from "@typespec/compiler";
4
- import { getHttpPart } from "@typespec/http";
5
4
  import { useTsp } from "../../core/context/tsp-context.js";
6
5
  import { InterfaceMethod } from "./interface-method.jsx";
7
6
  import { TypeExpression } from "./type-expression.js";
@@ -23,20 +22,15 @@ export function InterfaceMember(props: InterfaceMemberProps) {
23
22
  return null;
24
23
  }
25
24
 
26
- let unpackedType = props.type.type;
27
- const part = getHttpPart($.program, props.type.type);
28
- if (part) {
29
- unpackedType = part.type;
30
- }
25
+ const unpackedType = props.type.type;
31
26
 
32
- return (
33
- <ts.InterfaceMember
34
- doc={doc}
35
- name={name}
36
- optional={props.optional ?? props.type.optional}
37
- type={<TypeExpression type={unpackedType} />}
38
- />
39
- );
27
+ const interfaceMemberProps = {
28
+ doc,
29
+ name,
30
+ optional: props.optional ?? props.type.optional,
31
+ type: <TypeExpression type={unpackedType} />,
32
+ };
33
+ return <ts.InterfaceMember {...interfaceMemberProps} />;
40
34
  }
41
35
 
42
36
  if ($.operation.is(props.type)) {
@@ -2,7 +2,7 @@ import { For } from "@alloy-js/core";
2
2
  import { Reference, ValueExpression } from "@alloy-js/typescript";
3
3
  import type { IntrinsicType, Model, Scalar, Type } from "@typespec/compiler";
4
4
  import type { Typekit } from "@typespec/compiler/typekit";
5
- import "@typespec/http/experimental/typekit";
5
+ import { Experimental_OverridableComponent } from "../../core/components/overrides/component-overrides.jsx";
6
6
  import { useTsp } from "../../core/context/tsp-context.js";
7
7
  import { reportTypescriptDiagnostic } from "../../typescript/lib.js";
8
8
  import { efRefkey } from "../utils/refkey.js";
@@ -25,11 +25,15 @@ export interface TypeExpressionProps {
25
25
 
26
26
  export function TypeExpression(props: TypeExpressionProps) {
27
27
  const { $ } = useTsp();
28
- const type = $.httpPart.unpack(props.type);
28
+ const type = props.type;
29
29
  if (!props.noReference && isDeclaration($, type)) {
30
30
  // todo: probably need abstraction around deciding what's a declaration in the output
31
31
  // (it may not correspond to things which are declarations in TypeSpec?)
32
- return <Reference refkey={efRefkey(type)} />;
32
+ return (
33
+ <Experimental_OverridableComponent reference type={type}>
34
+ <Reference refkey={efRefkey(type)} />
35
+ </Experimental_OverridableComponent>
36
+ );
33
37
  //throw new Error("Reference not implemented");
34
38
  }
35
39
 
@@ -69,11 +73,6 @@ export function TypeExpression(props: TypeExpressionProps) {
69
73
  return <RecordExpression elementType={elementType} />;
70
74
  }
71
75
 
72
- if ($.httpPart.is(type)) {
73
- const partType = $.httpPart.unpack(type);
74
- return <TypeExpression type={partType} />;
75
- }
76
-
77
76
  return <InterfaceExpression type={type} />;
78
77
  case "Operation":
79
78
  return <FunctionType type={type} />;
@@ -227,7 +227,7 @@ export function ModelTransformExpression(props: ModelTransformExpressionProps) {
227
227
  {mapJoin(
228
228
  () => modelProperties,
229
229
  (_, property) => {
230
- const unpackedType = $.httpPart.unpack(property.type) ?? property.type;
230
+ const unpackedType = property.type;
231
231
  let targetPropertyName = property.name;
232
232
  let sourcePropertyName = namePolicy.getName(property.name, "interface-member");
233
233
 
@@ -368,9 +368,7 @@ export function TypeTransformCall(props: TypeTransformCallProps): Children {
368
368
  }
369
369
  const transformType = collapsedProperty?.type ?? props.type;
370
370
  if ($.model.is(transformType) && $.array.is(transformType)) {
371
- const unpackedElement =
372
- $.httpPart.unpack($.array.getElementType(transformType)) ??
373
- $.array.getElementType(transformType);
371
+ const unpackedElement = $.array.getElementType(transformType);
374
372
  return (
375
373
  <ts.FunctionCallExpression
376
374
  target={ArraySerializerRefkey}
@@ -380,9 +378,7 @@ export function TypeTransformCall(props: TypeTransformCallProps): Children {
380
378
  }
381
379
 
382
380
  if ($.model.is(transformType) && $.record.is(transformType)) {
383
- const unpackedElement =
384
- $.httpPart.unpack($.record.getElementType(transformType)) ??
385
- $.record.getElementType(transformType);
381
+ const unpackedElement = $.record.getElementType(transformType);
386
382
  return (
387
383
  <ts.FunctionCallExpression
388
384
  target={RecordSerializerRefkey}
@@ -0,0 +1,71 @@
1
+ import {
2
+ Experimental_ComponentOverrides,
3
+ Experimental_ComponentOverridesConfig,
4
+ } from "#core/index.js";
5
+ import { FunctionDeclaration } from "#typescript/index.js";
6
+ import { For, List, type Children } from "@alloy-js/core";
7
+ import { d } from "@alloy-js/core/testing";
8
+ import { SourceFile } from "@alloy-js/typescript";
9
+ import type { Namespace } from "@typespec/compiler";
10
+ import { expect, it } from "vitest";
11
+ import { Output } from "../../../src/core/components/output.jsx";
12
+ import { InterfaceDeclaration } from "../../../src/typescript/components/interface-declaration.jsx";
13
+ import { Tester } from "../../test-host.js";
14
+
15
+ it("uses overridden component", async () => {
16
+ const { program } = await Tester.compile(`
17
+ namespace DemoService;
18
+ model Foo {
19
+ knownProp: string;
20
+ }
21
+
22
+ op foo(): Foo;
23
+
24
+ `);
25
+
26
+ const [namespace] = program.resolveTypeReference("DemoService");
27
+ const models = Array.from((namespace as Namespace).models.values());
28
+ const operations = Array.from((namespace as Namespace).operations.values());
29
+
30
+ expect(
31
+ <Output program={program}>
32
+ <TestClientOverrides>
33
+ <SourceFile path="test.ts">
34
+ <List hardline>
35
+ {models.map((model) => (
36
+ <InterfaceDeclaration export type={model} />
37
+ ))}
38
+ </List>
39
+ <hbr />
40
+ <For each={operations}>
41
+ {(operation) => <FunctionDeclaration export type={operation} />}
42
+ </For>
43
+ </SourceFile>
44
+ </TestClientOverrides>
45
+ </Output>,
46
+ ).toRenderTo(
47
+ d`
48
+ export interface Foo {
49
+ knownProp: string;
50
+ }
51
+ export function foo(): unknown {}
52
+ `,
53
+ );
54
+ });
55
+
56
+ function TestClientOverrides(props: { children?: Children }) {
57
+ const overrides = Experimental_ComponentOverridesConfig().forTypeKind("Model", {
58
+ reference: (props) => {
59
+ if (props.type.name === "Foo") {
60
+ return "unknown";
61
+ } else {
62
+ return props.default;
63
+ }
64
+ },
65
+ });
66
+ return (
67
+ <Experimental_ComponentOverrides overrides={overrides}>
68
+ {props.children}
69
+ </Experimental_ComponentOverrides>
70
+ );
71
+ }
@@ -4,18 +4,9 @@ import {
4
4
  createTestWrapper,
5
5
  expectDiagnosticEmpty,
6
6
  } from "@typespec/compiler/testing";
7
- import { HttpTestLibrary } from "@typespec/http/testing";
8
7
 
9
- export async function createTypespecCliTestHost(
10
- options: { libraries: "Http"[] } = { libraries: [] },
11
- ) {
12
- const libraries = [];
13
- if (options.libraries.includes("Http")) {
14
- libraries.push(HttpTestLibrary);
15
- }
16
- return createTestHost({
17
- libraries,
18
- });
8
+ export async function createTypespecCliTestHost() {
9
+ return createTestHost({});
19
10
  }
20
11
 
21
12
  export async function createEmitterFrameworkTestRunner(options: { autoUsings?: string[] } = {}) {
@@ -25,11 +16,8 @@ export async function createEmitterFrameworkTestRunner(options: { autoUsings?: s
25
16
  });
26
17
  }
27
18
 
28
- export async function getProgram(
29
- code: string,
30
- options: { libraries: "Http"[] } = { libraries: [] },
31
- ): Promise<Program> {
32
- const host = await createTypespecCliTestHost(options);
19
+ export async function getProgram(code: string): Promise<Program> {
20
+ const host = await createTypespecCliTestHost();
33
21
  const wrapper = createTestWrapper(host, {
34
22
  compilerOptions: {
35
23
  noEmit: true,