@typespec/emitter-framework 0.9.0-dev.0 → 0.9.0-dev.10
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.
- package/dist/src/core/components/output.d.ts +4 -4
- package/dist/src/core/components/output.d.ts.map +1 -1
- package/dist/src/core/components/output.js +3 -3
- package/dist/src/core/context/name-policy-context.d.ts +2 -2
- package/dist/src/core/context/name-policy-context.d.ts.map +1 -1
- package/dist/src/core/context/tsp-context.d.ts +2 -2
- package/dist/src/core/context/tsp-context.d.ts.map +1 -1
- package/dist/src/core/transport-name-policy.d.ts +1 -1
- package/dist/src/core/transport-name-policy.d.ts.map +1 -1
- package/dist/src/core/write-output.d.ts +2 -2
- package/dist/src/core/write-output.d.ts.map +1 -1
- package/dist/src/csharp/components/class/declaration.d.ts +13 -0
- package/dist/src/csharp/components/class/declaration.d.ts.map +1 -0
- package/dist/src/csharp/components/class/declaration.js +91 -0
- package/dist/src/csharp/components/class/declaration.test.d.ts +2 -0
- package/dist/src/csharp/components/class/declaration.test.d.ts.map +1 -0
- package/dist/src/csharp/components/class/declaration.test.js +377 -0
- package/dist/src/csharp/components/enum/declaration.d.ts +9 -0
- package/dist/src/csharp/components/enum/declaration.d.ts.map +1 -0
- package/dist/src/csharp/components/enum/declaration.js +55 -0
- package/dist/src/csharp/components/enum/declaration.test.d.ts +2 -0
- package/dist/src/csharp/components/enum/declaration.test.d.ts.map +1 -0
- package/dist/src/csharp/components/enum/declaration.test.js +309 -0
- package/dist/src/csharp/components/index.d.ts +5 -0
- package/dist/src/csharp/components/index.d.ts.map +1 -0
- package/dist/src/csharp/components/index.js +4 -0
- package/dist/src/csharp/components/property/property.d.ts +15 -0
- package/dist/src/csharp/components/property/property.d.ts.map +1 -0
- package/dist/src/csharp/components/property/property.js +85 -0
- package/dist/src/csharp/components/property/property.test.d.ts +2 -0
- package/dist/src/csharp/components/property/property.test.d.ts.map +1 -0
- package/dist/src/csharp/components/property/property.test.js +117 -0
- package/dist/src/csharp/components/type-expression.d.ts +11 -0
- package/dist/src/csharp/components/type-expression.d.ts.map +1 -0
- package/dist/src/csharp/components/type-expression.js +143 -0
- package/dist/src/csharp/components/type-expression.test.d.ts +2 -0
- package/dist/src/csharp/components/type-expression.test.d.ts.map +1 -0
- package/dist/src/csharp/components/type-expression.test.js +128 -0
- package/dist/src/csharp/components/utils/doc-comments.d.ts +14 -0
- package/dist/src/csharp/components/utils/doc-comments.d.ts.map +1 -0
- package/dist/src/csharp/components/utils/doc-comments.js +67 -0
- package/dist/src/csharp/components/utils/refkey.d.ts +23 -0
- package/dist/src/csharp/components/utils/refkey.d.ts.map +1 -0
- package/dist/src/csharp/components/utils/refkey.js +35 -0
- package/dist/src/csharp/index.d.ts +2 -0
- package/dist/src/csharp/index.d.ts.map +1 -0
- package/dist/src/csharp/index.js +1 -0
- package/dist/src/testing/index.d.ts +1 -1
- package/dist/src/testing/index.d.ts.map +1 -1
- package/dist/src/testing/scenario-test/code-block-expectation.d.ts +33 -0
- package/dist/src/testing/scenario-test/code-block-expectation.d.ts.map +1 -0
- package/dist/src/testing/scenario-test/code-block-expectation.js +69 -0
- package/dist/src/testing/scenario-test/code-block-expectation.test.d.ts +2 -0
- package/dist/src/testing/scenario-test/code-block-expectation.test.d.ts.map +1 -0
- package/dist/src/testing/scenario-test/code-block-expectation.test.js +80 -0
- package/dist/src/testing/scenario-test/harness.d.ts +3 -3
- package/dist/src/testing/scenario-test/harness.d.ts.map +1 -1
- package/dist/src/testing/scenario-test/harness.js +69 -158
- package/dist/src/testing/scenario-test/index.d.ts +0 -1
- package/dist/src/testing/scenario-test/index.d.ts.map +1 -1
- package/dist/src/testing/scenario-test/index.js +1 -2
- package/dist/src/testing/scenario-test/snippet-extractor.d.ts +1 -1
- package/dist/src/testing/scenario-test/snippet-extractor.js +1 -1
- package/dist/src/typescript/components/array-expression.d.ts +2 -3
- package/dist/src/typescript/components/array-expression.d.ts.map +1 -1
- package/dist/src/typescript/components/array-expression.js +2 -2
- package/dist/src/typescript/components/arrow-function.d.ts +2 -2
- package/dist/src/typescript/components/arrow-function.d.ts.map +1 -1
- package/dist/src/typescript/components/arrow-function.js +2 -1
- package/dist/src/typescript/components/class-method.d.ts +1 -1
- package/dist/src/typescript/components/class-method.d.ts.map +1 -1
- package/dist/src/typescript/components/enum-declaration.d.ts +6 -6
- package/dist/src/typescript/components/enum-declaration.d.ts.map +1 -1
- package/dist/src/typescript/components/enum-declaration.js +2 -2
- package/dist/src/typescript/components/function-declaration.d.ts +1 -1
- package/dist/src/typescript/components/function-declaration.d.ts.map +1 -1
- package/dist/src/typescript/components/function-expression.d.ts +2 -2
- package/dist/src/typescript/components/function-expression.d.ts.map +1 -1
- package/dist/src/typescript/components/function-expression.js +2 -1
- package/dist/src/typescript/components/function-type.d.ts +2 -2
- package/dist/src/typescript/components/function-type.d.ts.map +1 -1
- package/dist/src/typescript/components/function-type.js +2 -1
- package/dist/src/typescript/components/interface-declaration.d.ts +4 -4
- package/dist/src/typescript/components/interface-declaration.d.ts.map +1 -1
- package/dist/src/typescript/components/interface-declaration.js +3 -4
- package/dist/src/typescript/components/interface-member.d.ts +2 -2
- package/dist/src/typescript/components/interface-member.d.ts.map +1 -1
- package/dist/src/typescript/components/interface-method.d.ts +2 -2
- package/dist/src/typescript/components/interface-method.d.ts.map +1 -1
- package/dist/src/typescript/components/record-expression.d.ts +1 -1
- package/dist/src/typescript/components/record-expression.d.ts.map +1 -1
- package/dist/src/typescript/components/type-alias-declaration.d.ts +1 -1
- package/dist/src/typescript/components/type-alias-declaration.d.ts.map +1 -1
- package/dist/src/typescript/components/type-declaration.d.ts +1 -1
- package/dist/src/typescript/components/type-declaration.d.ts.map +1 -1
- package/dist/src/typescript/components/type-expression.d.ts +1 -1
- package/dist/src/typescript/components/type-expression.d.ts.map +1 -1
- package/dist/src/typescript/components/type-transform.d.ts +2 -2
- package/dist/src/typescript/components/type-transform.d.ts.map +1 -1
- package/dist/src/typescript/components/union-declaration.d.ts +2 -2
- package/dist/src/typescript/components/union-declaration.d.ts.map +1 -1
- package/dist/src/typescript/components/union-expression.d.ts +3 -4
- package/dist/src/typescript/components/union-expression.d.ts.map +1 -1
- package/dist/src/typescript/components/union-expression.js +3 -3
- package/dist/src/typescript/components/value-expression.d.ts +2 -2
- package/dist/src/typescript/components/value-expression.d.ts.map +1 -1
- package/dist/src/typescript/utils/operation.d.ts +2 -2
- package/dist/src/typescript/utils/operation.d.ts.map +1 -1
- package/dist/test/test-host.d.ts +2 -0
- package/dist/test/test-host.d.ts.map +1 -0
- package/dist/test/test-host.js +5 -0
- package/dist/test/testing/snippet-extractor-csharp.test.js +3 -3
- package/dist/test/testing/snippet-extractor-java.test.js +3 -3
- package/dist/test/testing/snippet-extractor-python.test.js +2 -2
- package/dist/test/testing/snippet-extractor-typescript.test.js +3 -3
- package/dist/test/typescript/components/member-expression.test.js +5 -5
- package/dist/test/typescript/test-host.d.ts +1 -1
- package/dist/test/typescript/test-host.d.ts.map +1 -1
- package/dist/test/utils.d.ts +2 -2
- package/dist/test/utils.d.ts.map +1 -1
- package/dist/test/vitest.setup.d.ts +2 -0
- package/dist/test/vitest.setup.d.ts.map +1 -0
- package/dist/test/vitest.setup.js +1 -0
- package/package.json +30 -8
- package/src/core/components/output.tsx +9 -5
- package/src/core/context/name-policy-context.ts +2 -2
- package/src/core/context/tsp-context.ts +2 -2
- package/src/core/transport-name-policy.ts +1 -1
- package/src/core/write-output.ts +2 -2
- package/src/csharp/components/class/declaration.test.tsx +339 -0
- package/src/csharp/components/class/declaration.tsx +86 -0
- package/src/csharp/components/enum/declaration.test.tsx +267 -0
- package/src/csharp/components/enum/declaration.tsx +56 -0
- package/src/csharp/components/index.ts +4 -0
- package/src/csharp/components/property/property.test.tsx +97 -0
- package/src/csharp/components/property/property.tsx +62 -0
- package/src/csharp/components/type-expression.test.tsx +133 -0
- package/src/csharp/components/type-expression.tsx +119 -0
- package/src/csharp/components/utils/doc-comments.tsx +58 -0
- package/src/csharp/components/utils/refkey.ts +36 -0
- package/src/csharp/index.ts +1 -0
- package/src/testing/index.ts +1 -1
- package/src/testing/scenario-test/code-block-expectation.test.ts +95 -0
- package/src/testing/scenario-test/code-block-expectation.ts +115 -0
- package/src/testing/scenario-test/harness.ts +92 -237
- package/src/testing/scenario-test/index.ts +0 -1
- package/src/testing/scenario-test/snippet-extractor.ts +1 -1
- package/src/typescript/components/array-expression.tsx +3 -3
- package/src/typescript/components/arrow-function.tsx +2 -2
- package/src/typescript/components/class-method.tsx +1 -1
- package/src/typescript/components/enum-declaration.tsx +6 -6
- package/src/typescript/components/function-declaration.tsx +1 -1
- package/src/typescript/components/function-expression.tsx +2 -2
- package/src/typescript/components/function-type.tsx +2 -2
- package/src/typescript/components/interface-declaration.tsx +11 -12
- package/src/typescript/components/interface-member.tsx +2 -2
- package/src/typescript/components/interface-method.tsx +2 -2
- package/src/typescript/components/record-expression.tsx +1 -1
- package/src/typescript/components/type-alias-declaration.tsx +1 -1
- package/src/typescript/components/type-declaration.tsx +1 -1
- package/src/typescript/components/type-expression.tsx +2 -2
- package/src/typescript/components/type-transform.tsx +3 -3
- package/src/typescript/components/union-declaration.tsx +2 -2
- package/src/typescript/components/union-expression.tsx +12 -7
- package/src/typescript/components/value-expression.tsx +2 -2
- package/src/typescript/utils/operation.ts +2 -2
- package/test/test-host.ts +4 -0
- package/test/testing/snippet-extractor-csharp.test.ts +4 -4
- package/test/testing/snippet-extractor-java.test.ts +4 -4
- package/test/testing/snippet-extractor-python.test.ts +3 -3
- package/test/testing/snippet-extractor-typescript.test.ts +4 -4
- package/test/typescript/components/arrow-function.test.tsx +2 -2
- package/test/typescript/components/enum-declaration.test.tsx +1 -1
- package/test/typescript/components/function-declaration.test.tsx +1 -1
- package/test/typescript/components/function-expression.test.tsx +2 -2
- package/test/typescript/components/function-type.test.tsx +2 -2
- package/test/typescript/components/interface-declaration.test.tsx +1 -1
- package/test/typescript/components/interface-method.test.tsx +2 -2
- package/test/typescript/components/member-expression.test.tsx +10 -10
- package/test/typescript/components/type-alias-declaration.test.tsx +1 -1
- package/test/typescript/components/type-transform.test.tsx +2 -2
- package/test/typescript/components/union-declaration.test.tsx +2 -2
- package/test/typescript/components/value-expression.test.tsx +6 -6
- package/test/typescript/test-host.ts +1 -1
- package/test/utils.ts +2 -2
- package/test/vitest.setup.ts +1 -0
- package/tsconfig.json +3 -1
- package/vitest.config.ts +2 -1
- package/dist/src/testing/scenario-test/test-host.d.ts +0 -8
- package/dist/src/testing/scenario-test/test-host.d.ts.map +0 -1
- package/dist/src/testing/scenario-test/test-host.js +0 -49
- package/src/testing/scenario-test/test-host.ts +0 -83
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useTsp } from "#core/context/tsp-context.js";
|
|
2
|
+
import { type Children, For } from "@alloy-js/core";
|
|
3
|
+
import * as cs from "@alloy-js/csharp";
|
|
4
|
+
import type { Enum, Union } from "@typespec/compiler";
|
|
5
|
+
import { reportDiagnostic } from "../../../lib.js";
|
|
6
|
+
import { getDocComments } from "../utils/doc-comments.jsx";
|
|
7
|
+
import { declarationRefkeys, efRefkey } from "../utils/refkey.js";
|
|
8
|
+
|
|
9
|
+
export interface EnumDeclarationProps extends Omit<cs.EnumDeclarationProps, "name"> {
|
|
10
|
+
name?: string;
|
|
11
|
+
type: Union | Enum;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function EnumDeclaration(props: EnumDeclarationProps): Children {
|
|
15
|
+
const { $ } = useTsp();
|
|
16
|
+
let type: Enum;
|
|
17
|
+
if ($.union.is(props.type)) {
|
|
18
|
+
if (!$.union.isValidEnum(props.type)) {
|
|
19
|
+
throw new Error("The provided union type cannot be represented as an enum");
|
|
20
|
+
}
|
|
21
|
+
type = $.enum.createFromUnion(props.type);
|
|
22
|
+
} else {
|
|
23
|
+
type = props.type;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!props.type.name) {
|
|
27
|
+
reportDiagnostic($.program, { code: "type-declaration-missing-name", target: props.type });
|
|
28
|
+
}
|
|
29
|
+
const refkeys = declarationRefkeys(props.refkey, props.type)[0]; // TODO: support multiple refkeys for declarations in alloy
|
|
30
|
+
const name = props.name ?? cs.useCSharpNamePolicy().getName(props.type.name!, "enum");
|
|
31
|
+
const members = Array.from(type.members.entries());
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<>
|
|
35
|
+
<cs.EnumDeclaration name={name} refkey={refkeys} {...props}>
|
|
36
|
+
<For each={members} joiner={",\n"}>
|
|
37
|
+
{([key, value]) => {
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
<cs.DocWhen doc={getDocComments($, value)} />
|
|
41
|
+
<cs.EnumMember
|
|
42
|
+
name={cs.useCSharpNamePolicy().getName(key, "enum-member")}
|
|
43
|
+
refkey={
|
|
44
|
+
$.union.is(props.type)
|
|
45
|
+
? efRefkey(props.type.variants.get(key))
|
|
46
|
+
: efRefkey(value)
|
|
47
|
+
}
|
|
48
|
+
/>
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
}}
|
|
52
|
+
</For>
|
|
53
|
+
</cs.EnumDeclaration>
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Tester } from "#test/test-host.js";
|
|
2
|
+
import { type Children } from "@alloy-js/core";
|
|
3
|
+
import { ClassDeclaration, createCSharpNamePolicy, Namespace, SourceFile } from "@alloy-js/csharp";
|
|
4
|
+
import { t, type TesterInstance } from "@typespec/compiler/testing";
|
|
5
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { Output } from "../../../core/components/output.jsx";
|
|
7
|
+
import { Property } from "./property.jsx";
|
|
8
|
+
|
|
9
|
+
let tester: TesterInstance;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
tester = await Tester.createInstance();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function Wrapper(props: { children: Children }) {
|
|
16
|
+
const policy = createCSharpNamePolicy();
|
|
17
|
+
return (
|
|
18
|
+
<Output program={tester.program} namePolicy={policy}>
|
|
19
|
+
<Namespace name="TestNamespace">
|
|
20
|
+
<SourceFile path="test.cs">
|
|
21
|
+
<ClassDeclaration name="Test">{props.children}</ClassDeclaration>
|
|
22
|
+
</SourceFile>
|
|
23
|
+
</Namespace>
|
|
24
|
+
</Output>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
it("maps prop: string | null to nullable property", async () => {
|
|
29
|
+
const { prop1 } = await tester.compile(t.code`
|
|
30
|
+
model TestModel {
|
|
31
|
+
${t.modelProperty("prop1")}: string | null;
|
|
32
|
+
}
|
|
33
|
+
`);
|
|
34
|
+
|
|
35
|
+
expect(
|
|
36
|
+
<Wrapper>
|
|
37
|
+
<Property type={prop1} />
|
|
38
|
+
</Wrapper>,
|
|
39
|
+
).toRenderTo(`
|
|
40
|
+
namespace TestNamespace
|
|
41
|
+
{
|
|
42
|
+
class Test
|
|
43
|
+
{
|
|
44
|
+
public required string? Prop1 { get; set; }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("jsonAttributes", () => {
|
|
51
|
+
it("adds [JsonNameAttribute]", async () => {
|
|
52
|
+
const { prop1 } = await tester.compile(t.code`
|
|
53
|
+
model TestModel {
|
|
54
|
+
${t.modelProperty("prop1")}: string;
|
|
55
|
+
}
|
|
56
|
+
`);
|
|
57
|
+
|
|
58
|
+
expect(
|
|
59
|
+
<Wrapper>
|
|
60
|
+
<Property type={prop1} jsonAttributes />
|
|
61
|
+
</Wrapper>,
|
|
62
|
+
).toRenderTo(`
|
|
63
|
+
namespace TestNamespace
|
|
64
|
+
{
|
|
65
|
+
class Test
|
|
66
|
+
{
|
|
67
|
+
[System.Text.Json.JsonPropertyName("prop1")]
|
|
68
|
+
public required string Prop1 { get; set; }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("adds [JsonNameAttribute] respecting encodedName", async () => {
|
|
75
|
+
const { prop1 } = await tester.compile(t.code`
|
|
76
|
+
model TestModel {
|
|
77
|
+
@encodedName("application/json", "prop_1")
|
|
78
|
+
${t.modelProperty("prop1")}: string;
|
|
79
|
+
}
|
|
80
|
+
`);
|
|
81
|
+
|
|
82
|
+
expect(
|
|
83
|
+
<Wrapper>
|
|
84
|
+
<Property type={prop1} jsonAttributes />
|
|
85
|
+
</Wrapper>,
|
|
86
|
+
).toRenderTo(`
|
|
87
|
+
namespace TestNamespace
|
|
88
|
+
{
|
|
89
|
+
class Test
|
|
90
|
+
{
|
|
91
|
+
[System.Text.Json.JsonPropertyName("prop_1")]
|
|
92
|
+
public required string Prop1 { get; set; }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
`);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type Children } from "@alloy-js/core";
|
|
2
|
+
import * as cs from "@alloy-js/csharp";
|
|
3
|
+
import { Attribute } from "@alloy-js/csharp";
|
|
4
|
+
import { type ModelProperty, resolveEncodedName, type Type } from "@typespec/compiler";
|
|
5
|
+
import { useTsp } from "../../../core/index.js";
|
|
6
|
+
import { TypeExpression } from "../type-expression.jsx";
|
|
7
|
+
import { getDocComments } from "../utils/doc-comments.jsx";
|
|
8
|
+
|
|
9
|
+
export interface PropertyProps {
|
|
10
|
+
type: ModelProperty;
|
|
11
|
+
/** If set the property will add the json serialization attributes(using System.Text.Json). */
|
|
12
|
+
jsonAttributes?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create a C# property declaration from a TypeSpec property type.
|
|
17
|
+
*/
|
|
18
|
+
export function Property(props: PropertyProps): Children {
|
|
19
|
+
const result = preprocessPropertyType(props.type.type);
|
|
20
|
+
const { $ } = useTsp();
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<cs.Property
|
|
24
|
+
name={props.type.name}
|
|
25
|
+
type={<TypeExpression type={result.type} />}
|
|
26
|
+
public
|
|
27
|
+
required={!props.type.optional}
|
|
28
|
+
nullable={result.nullable}
|
|
29
|
+
doc={getDocComments($, props.type)}
|
|
30
|
+
attributes={props.jsonAttributes ? [<JsonNameAttribute type={props.type} />] : undefined}
|
|
31
|
+
get
|
|
32
|
+
set
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface JsonNameAttributeProps {
|
|
38
|
+
type: ModelProperty;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function JsonNameAttribute(props: JsonNameAttributeProps): Children {
|
|
42
|
+
const { program } = useTsp();
|
|
43
|
+
const jsonName = resolveEncodedName(program, props.type, "application/json");
|
|
44
|
+
return <Attribute name="System.Text.Json.JsonPropertyName" args={[JSON.stringify(jsonName)]} />;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function preprocessPropertyType(type: Type): { type: Type; nullable: boolean } {
|
|
48
|
+
const { $ } = useTsp();
|
|
49
|
+
|
|
50
|
+
if (type.kind === "Union") {
|
|
51
|
+
const variants = type.variants;
|
|
52
|
+
const nonNullVariant = [...variants.values()].find((v) => v.type !== $.intrinsic.null);
|
|
53
|
+
const nullVariant = [...variants.values()].find((v) => v.type !== $.intrinsic.null);
|
|
54
|
+
if (nonNullVariant && nullVariant && variants.size === 2) {
|
|
55
|
+
return { type: nonNullVariant.type, nullable: true };
|
|
56
|
+
} else {
|
|
57
|
+
return { type, nullable: false };
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
return { type, nullable: false };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { type Children, render } from "@alloy-js/core";
|
|
2
|
+
import { d } from "@alloy-js/core/testing";
|
|
3
|
+
import { Namespace, SourceFile } from "@alloy-js/csharp";
|
|
4
|
+
import type { Model, ModelProperty } from "@typespec/compiler";
|
|
5
|
+
import type { BasicTestRunner } from "@typespec/compiler/testing";
|
|
6
|
+
import { beforeEach, describe, it } from "vitest";
|
|
7
|
+
import { createEmitterFrameworkTestRunner } from "../../../test/typescript/test-host.js";
|
|
8
|
+
import { assertFileContents } from "../../../test/utils.js";
|
|
9
|
+
import { Output } from "../../core/index.js";
|
|
10
|
+
import { ClassDeclaration } from "./class/declaration.js";
|
|
11
|
+
import { TypeExpression } from "./type-expression.jsx";
|
|
12
|
+
|
|
13
|
+
let runner: BasicTestRunner;
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
runner = await createEmitterFrameworkTestRunner();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function Wrapper(props: { children: Children }) {
|
|
20
|
+
return (
|
|
21
|
+
<Output program={runner.program}>
|
|
22
|
+
<Namespace name="TestNamespace">
|
|
23
|
+
<SourceFile path="test.ts">{props.children}</SourceFile>
|
|
24
|
+
</Namespace>
|
|
25
|
+
</Output>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function compileType(ref: string) {
|
|
30
|
+
const { test } = await runner.compile(`
|
|
31
|
+
model Test {
|
|
32
|
+
@test test: ${ref};
|
|
33
|
+
}
|
|
34
|
+
`);
|
|
35
|
+
|
|
36
|
+
return (test as ModelProperty).type;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("map scalar to c# built-in types", () => {
|
|
40
|
+
it.each([
|
|
41
|
+
["string", "string"],
|
|
42
|
+
["int32", "int"],
|
|
43
|
+
["int64", "long"],
|
|
44
|
+
])("%s => %s", async (tspType, csType) => {
|
|
45
|
+
const type = await compileType(tspType);
|
|
46
|
+
const res = render(
|
|
47
|
+
<Wrapper>
|
|
48
|
+
<TypeExpression type={type} />
|
|
49
|
+
</Wrapper>,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
assertFileContents(
|
|
53
|
+
res,
|
|
54
|
+
d`
|
|
55
|
+
namespace TestNamespace
|
|
56
|
+
{
|
|
57
|
+
${csType}
|
|
58
|
+
}
|
|
59
|
+
`,
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("maps array to c# array", async () => {
|
|
65
|
+
const type = await compileType("int32[]");
|
|
66
|
+
const res = render(
|
|
67
|
+
<Wrapper>
|
|
68
|
+
<TypeExpression type={type} />
|
|
69
|
+
</Wrapper>,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
assertFileContents(
|
|
73
|
+
res,
|
|
74
|
+
d`
|
|
75
|
+
namespace TestNamespace
|
|
76
|
+
{
|
|
77
|
+
int[]
|
|
78
|
+
}
|
|
79
|
+
`,
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("Record map to IDictionary", () => {
|
|
84
|
+
it("for primitive types", async () => {
|
|
85
|
+
const type = await compileType("Record<int32>");
|
|
86
|
+
const res = render(
|
|
87
|
+
<Wrapper>
|
|
88
|
+
<TypeExpression type={type} />
|
|
89
|
+
</Wrapper>,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
assertFileContents(
|
|
93
|
+
res,
|
|
94
|
+
d`
|
|
95
|
+
namespace TestNamespace
|
|
96
|
+
{
|
|
97
|
+
IDictionary<string, int>
|
|
98
|
+
}
|
|
99
|
+
`,
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("for models", async () => {
|
|
104
|
+
const { test, Pet } = (await runner.compile(`
|
|
105
|
+
model Test {
|
|
106
|
+
@test test: Record<Pet>;
|
|
107
|
+
}
|
|
108
|
+
@test model Pet {}
|
|
109
|
+
`)) as { test: ModelProperty; Pet: Model };
|
|
110
|
+
|
|
111
|
+
const res = render(
|
|
112
|
+
<Wrapper>
|
|
113
|
+
<ClassDeclaration type={Pet} />
|
|
114
|
+
<hbr />
|
|
115
|
+
<TypeExpression type={test.type} />
|
|
116
|
+
</Wrapper>,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
assertFileContents(
|
|
120
|
+
res,
|
|
121
|
+
d`
|
|
122
|
+
namespace TestNamespace
|
|
123
|
+
{
|
|
124
|
+
class Pet
|
|
125
|
+
{
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
IDictionary<string, Pet>
|
|
129
|
+
}
|
|
130
|
+
`,
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { type Children, code } from "@alloy-js/core";
|
|
2
|
+
import { Reference } from "@alloy-js/csharp";
|
|
3
|
+
import { getTypeName, type IntrinsicType, type Scalar, type Type } from "@typespec/compiler";
|
|
4
|
+
import type { Typekit } from "@typespec/compiler/typekit";
|
|
5
|
+
import { useTsp } from "../../core/index.js";
|
|
6
|
+
import { reportTypescriptDiagnostic } from "../../typescript/lib.js";
|
|
7
|
+
import { efRefkey } from "./utils/refkey.js";
|
|
8
|
+
|
|
9
|
+
export interface TypeExpressionProps {
|
|
10
|
+
type: Type;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function TypeExpression(props: TypeExpressionProps): Children {
|
|
14
|
+
const { $ } = useTsp();
|
|
15
|
+
if (isDeclaration($, props.type)) {
|
|
16
|
+
return <Reference refkey={efRefkey(props.type)} />;
|
|
17
|
+
}
|
|
18
|
+
if ($.scalar.is(props.type)) {
|
|
19
|
+
return getScalarIntrinsicExpression($, props.type);
|
|
20
|
+
} else if ($.array.is(props.type)) {
|
|
21
|
+
return code`${(<TypeExpression type={props.type.indexer.value} />)}[]`;
|
|
22
|
+
} else if ($.record.is(props.type)) {
|
|
23
|
+
return code`IDictionary<string, ${(<TypeExpression type={props.type.indexer.value} />)}>`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Unsupported type for TypeExpression: ${props.type.kind} (${getTypeName(props.type)})`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const intrinsicNameToCSharpType = new Map<string, string | null>([
|
|
32
|
+
// Core types
|
|
33
|
+
["unknown", "object"], // Matches C#'s `object`
|
|
34
|
+
["string", "string"], // Matches C#'s `string`
|
|
35
|
+
["boolean", "bool"], // Matches C#'s `bool`
|
|
36
|
+
["null", "null"], // Matches C#'s `null`
|
|
37
|
+
["void", "void"], // Matches C#'s `void`
|
|
38
|
+
["never", null], // No direct equivalent in C#
|
|
39
|
+
["bytes", "byte[]"], // Matches C#'s `byte[]`
|
|
40
|
+
|
|
41
|
+
// Numeric types
|
|
42
|
+
["numeric", "decimal"], // Parent type for all numeric types, use most precise
|
|
43
|
+
["integer", "int"], // Broad integer category, maps to `int`
|
|
44
|
+
["float", "float"], // Broad float category, maps to `float`
|
|
45
|
+
["decimal", "decimal"], // Broad decimal category, maps to `decimal`
|
|
46
|
+
["decimal128", "decimal"], // C#'s decimal is 128-bit
|
|
47
|
+
["int64", "long"], // 64-bit signed integer
|
|
48
|
+
["int32", "int"], // 32-bit signed integer
|
|
49
|
+
["int16", "short"], // 16-bit signed integer
|
|
50
|
+
["int8", "sbyte"], // 8-bit signed integer
|
|
51
|
+
["safeint", "int"], // Safe integer, use int as default
|
|
52
|
+
["uint64", "ulong"], // 64-bit unsigned integer
|
|
53
|
+
["uint32", "uint"], // 32-bit unsigned integer
|
|
54
|
+
["uint16", "ushort"], // 16-bit unsigned integer
|
|
55
|
+
["uint8", "byte"], // 8-bit unsigned integer
|
|
56
|
+
["float32", "float"], // 32-bit floating point
|
|
57
|
+
["float64", "double"], // 64-bit floating point
|
|
58
|
+
|
|
59
|
+
// Date and time types
|
|
60
|
+
["plainDate", "DateOnly"], // Use .NET 6+ DateOnly for plain calendar dates
|
|
61
|
+
["plainTime", "TimeOnly"], // Use .NET 6+ TimeOnly for plain clock times
|
|
62
|
+
["utcDateTime", "DateTimeOffset"], // Use DateTimeOffset for UTC date-times
|
|
63
|
+
["offsetDateTime", "DateTimeOffset"], // Use DateTimeOffset for timezone-specific date-times
|
|
64
|
+
["duration", "TimeSpan"], // Duration as TimeSpan
|
|
65
|
+
|
|
66
|
+
// String types
|
|
67
|
+
["url", "Uri"], // Matches C#'s `Uri`
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
export function getScalarIntrinsicExpression(
|
|
71
|
+
$: Typekit,
|
|
72
|
+
type: Scalar | IntrinsicType,
|
|
73
|
+
): string | null {
|
|
74
|
+
let intrinsicName: string;
|
|
75
|
+
|
|
76
|
+
if ($.scalar.isUtcDateTime(type) || $.scalar.extendsUtcDateTime(type)) {
|
|
77
|
+
return "DateTimeOffset";
|
|
78
|
+
}
|
|
79
|
+
if ($.scalar.is(type)) {
|
|
80
|
+
intrinsicName = $.scalar.getStdBase(type)?.name ?? "";
|
|
81
|
+
} else {
|
|
82
|
+
intrinsicName = type.name;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const csType = intrinsicNameToCSharpType.get(intrinsicName);
|
|
86
|
+
|
|
87
|
+
if (!csType) {
|
|
88
|
+
reportTypescriptDiagnostic($.program, { code: "typescript-unsupported-scalar", target: type });
|
|
89
|
+
return "object"; // Fallback to object if unsupported
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return csType;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isDeclaration($: Typekit, type: Type): boolean {
|
|
96
|
+
switch (type.kind) {
|
|
97
|
+
case "Namespace":
|
|
98
|
+
case "Interface":
|
|
99
|
+
case "Enum":
|
|
100
|
+
case "Operation":
|
|
101
|
+
case "EnumMember":
|
|
102
|
+
return true;
|
|
103
|
+
case "UnionVariant":
|
|
104
|
+
return false;
|
|
105
|
+
|
|
106
|
+
case "Model":
|
|
107
|
+
if ($.array.is(type) || $.record.is(type)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Boolean(type.name);
|
|
112
|
+
case "Union":
|
|
113
|
+
return Boolean(type.name);
|
|
114
|
+
default:
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export { intrinsicNameToCSharpType };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type Children, List } from "@alloy-js/core";
|
|
2
|
+
import * as cs from "@alloy-js/csharp";
|
|
3
|
+
import { getReturnsDoc, type Type } from "@typespec/compiler";
|
|
4
|
+
import type { Typekit } from "@typespec/compiler/typekit";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Helper to render a doc string for a given TypeSpec type.
|
|
8
|
+
*
|
|
9
|
+
* This is not a JSX component as it needs to return undefined if there is no doc.
|
|
10
|
+
*
|
|
11
|
+
* @param $ The Typekit instance
|
|
12
|
+
* @param type The TypeSpec type to generate documentation for
|
|
13
|
+
* @returns A DocSummary component containing the rendered doc string, or undefined if no doc is available.
|
|
14
|
+
*/
|
|
15
|
+
export function getDocComments($: Typekit, type: Type): Children {
|
|
16
|
+
const typeDoc = $.type.getDoc(type);
|
|
17
|
+
if (!typeDoc) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const docElements: Children[] = [];
|
|
22
|
+
|
|
23
|
+
// Add main type documentation
|
|
24
|
+
docElements.push(
|
|
25
|
+
<cs.DocSummary>
|
|
26
|
+
<cs.DocFromMarkdown markdown={typeDoc} />
|
|
27
|
+
</cs.DocSummary>,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Add operation-specific documentation if applicable
|
|
31
|
+
if ($.operation.is(type)) {
|
|
32
|
+
// Add parameter documentation
|
|
33
|
+
const paramDocs = [];
|
|
34
|
+
for (const param of type.parameters.properties.values()) {
|
|
35
|
+
const paramDoc = $.type.getDoc(param);
|
|
36
|
+
if (paramDoc) {
|
|
37
|
+
paramDocs.push(
|
|
38
|
+
<cs.DocParam name={param.name}>
|
|
39
|
+
<cs.DocFromMarkdown markdown={paramDoc} />
|
|
40
|
+
</cs.DocParam>,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
docElements.push(...paramDocs);
|
|
45
|
+
|
|
46
|
+
// Add return documentation
|
|
47
|
+
const returnDoc = getReturnsDoc($.program, type);
|
|
48
|
+
if (returnDoc) {
|
|
49
|
+
docElements.push(
|
|
50
|
+
<cs.DocReturns>
|
|
51
|
+
<cs.DocFromMarkdown markdown={returnDoc} />
|
|
52
|
+
</cs.DocReturns>,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return <List doubleHardline>{docElements}</List>;
|
|
58
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { refkey as ayRefkey, type Refkey } from "@alloy-js/core";
|
|
2
|
+
|
|
3
|
+
const refKeyPrefix = Symbol.for("emitter-framework:csharp");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A wrapper around `refkey` that uses a custom symbol to avoid collisions with
|
|
7
|
+
* other libraries that use `refkey`.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
*
|
|
11
|
+
* The underlying refkey function is called with the {@link refKeyPrefix} symbol as the first argument.
|
|
12
|
+
*
|
|
13
|
+
* @param args The parameters of the refkey.
|
|
14
|
+
* @returns A refkey object that can be used to identify the value.
|
|
15
|
+
*/
|
|
16
|
+
export function efRefkey(...args: unknown[]): Refkey {
|
|
17
|
+
if (args.length === 0) {
|
|
18
|
+
return ayRefkey(); // Generates a unique refkey
|
|
19
|
+
}
|
|
20
|
+
return ayRefkey(refKeyPrefix, ...args);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a refkey for a declaration by combining the provided refkey with an internal
|
|
25
|
+
* refkey generated from the provided arguments.
|
|
26
|
+
*
|
|
27
|
+
* @param refkey The refkey provided by the user to be passed as is.
|
|
28
|
+
* @param args The parameters of the refkey.
|
|
29
|
+
* @returns An array of refkeys that can be passed to an Alloy declaration.
|
|
30
|
+
*/
|
|
31
|
+
export function declarationRefkeys(refkey?: Refkey | Refkey[], ...args: unknown[]): Refkey[] {
|
|
32
|
+
if (refkey) {
|
|
33
|
+
return [refkey, efRefkey(...args)].flat();
|
|
34
|
+
}
|
|
35
|
+
return [efRefkey(...args)];
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./components/index.js";
|
package/src/testing/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolvePath } from "@typespec/compiler";
|
|
2
|
-
import { createTestLibrary, TypeSpecTestLibrary } from "@typespec/compiler/testing";
|
|
2
|
+
import { createTestLibrary, type TypeSpecTestLibrary } from "@typespec/compiler/testing";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
|
|
5
5
|
export const EmitterFrameworkTestLibrary: TypeSpecTestLibrary = createTestLibrary({
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { d } from "@alloy-js/core/testing";
|
|
2
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
3
|
+
import { getExcerptForQuery, parseCodeBlockHeading } from "./code-block-expectation.js";
|
|
4
|
+
import {
|
|
5
|
+
createSnippetExtractor,
|
|
6
|
+
createTypeScriptExtractorConfig,
|
|
7
|
+
type SnippetExtractor,
|
|
8
|
+
} from "./snippet-extractor.js";
|
|
9
|
+
|
|
10
|
+
describe("parseCodeBlockHeading", () => {
|
|
11
|
+
it("parse whole file expectation", () => {
|
|
12
|
+
expect(parseCodeBlockHeading("ts path/to/file.ts")).toEqual({
|
|
13
|
+
lang: "ts",
|
|
14
|
+
file: "path/to/file.ts",
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("throws error when no file is provided", () => {
|
|
19
|
+
expect(() => parseCodeBlockHeading("ts")).toThrow(
|
|
20
|
+
'Invalid code block heading: "ts". Missing file path. Expected format: "<lang> <path>"',
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("parse parse with type and name", () => {
|
|
25
|
+
expect(parseCodeBlockHeading("ts path/to/file.ts interface foo")).toEqual({
|
|
26
|
+
lang: "ts",
|
|
27
|
+
file: "path/to/file.ts",
|
|
28
|
+
query: {
|
|
29
|
+
type: "interface",
|
|
30
|
+
name: "foo",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("throws error when using type but no name is provided", () => {
|
|
36
|
+
expect(() => parseCodeBlockHeading("ts path/to/file.ts interface")).toThrow(
|
|
37
|
+
'Invalid code block heading: "ts path/to/file.ts interface". Missing name when using type. Expected format: "<lang> <path> [type] [name]"',
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("getExcerptForQuery", () => {
|
|
43
|
+
let snippetExtractor: SnippetExtractor;
|
|
44
|
+
beforeAll(async () => {
|
|
45
|
+
const tsExtractorConfig = await createTypeScriptExtractorConfig();
|
|
46
|
+
snippetExtractor = createSnippetExtractor(tsExtractorConfig);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("gets a whole file", async () => {
|
|
50
|
+
const expectation = {
|
|
51
|
+
lang: "ts",
|
|
52
|
+
file: "file.ts",
|
|
53
|
+
};
|
|
54
|
+
const outputs = {
|
|
55
|
+
"file.ts": d`
|
|
56
|
+
interface bar {
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
interface foo {
|
|
60
|
+
bar: string;
|
|
61
|
+
}
|
|
62
|
+
`,
|
|
63
|
+
};
|
|
64
|
+
const excerpt = getExcerptForQuery(snippetExtractor, expectation, outputs);
|
|
65
|
+
expect(excerpt).toBe(outputs["file.ts"]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("gets an interface for typescript", async () => {
|
|
69
|
+
const expectation = {
|
|
70
|
+
lang: "ts",
|
|
71
|
+
file: "file.ts",
|
|
72
|
+
query: {
|
|
73
|
+
type: "interface",
|
|
74
|
+
name: "foo",
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
const outputs = {
|
|
78
|
+
"file.ts": d`
|
|
79
|
+
interface bar {
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface foo {
|
|
84
|
+
bar: string;
|
|
85
|
+
}
|
|
86
|
+
`,
|
|
87
|
+
};
|
|
88
|
+
const excerpt = getExcerptForQuery(snippetExtractor, expectation, outputs);
|
|
89
|
+
expect(excerpt).toBe(d`
|
|
90
|
+
interface foo {
|
|
91
|
+
bar: string;
|
|
92
|
+
}
|
|
93
|
+
`);
|
|
94
|
+
});
|
|
95
|
+
});
|