microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/add-python-test-case-duration

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/asset-emitter/test/typescript-emitter.ts

276lines · modecode

1import {
2 getDoc,
3 type BooleanLiteral,
4 type Enum,
5 type EnumMember,
6 type Interface,
7 type IntrinsicType,
8 type Model,
9 type ModelProperty,
10 type NumericLiteral,
11 type Operation,
12 type Scalar,
13 type StringLiteral,
14 type Tuple,
15 type Type,
16 type Union,
17 type UnionVariant,
18} from "@typespec/compiler";
19import * as prettier from "prettier";
20import { code, Declaration, StringBuilder, type SourceFile } from "../src/index.js";
21import { CodeTypeEmitter, type EmitterOutput } from "../src/type-emitter.js";
22import type { EmittedSourceFile, Scope, SourceFileScope } from "../src/types.js";
23
24export function isArrayType(m: Model) {
25 return m.name === "Array";
26}
27
28export const intrinsicNameToTSType = new Map<string, string>([
29 ["unknown", "unknown"],
30 ["string", "string"],
31 ["int32", "number"],
32 ["int16", "number"],
33 ["float16", "number"],
34 ["float32", "number"],
35 ["int64", "bigint"],
36 ["boolean", "boolean"],
37 ["null", "null"],
38]);
39
40export class TypeScriptInterfaceEmitter extends CodeTypeEmitter {
41 // type literals
42 booleanLiteral(boolean: BooleanLiteral): EmitterOutput<string> {
43 return JSON.stringify(boolean.value);
44 }
45
46 numericLiteral(number: NumericLiteral): EmitterOutput<string> {
47 return JSON.stringify(number.value);
48 }
49
50 stringLiteral(string: StringLiteral): EmitterOutput<string> {
51 return JSON.stringify(string.value);
52 }
53
54 scalarDeclaration(scalar: Scalar, scalarName: string): EmitterOutput<string> {
55 if (!intrinsicNameToTSType.has(scalarName)) {
56 throw new Error("Unknown scalar type " + scalarName);
57 }
58
59 const code = intrinsicNameToTSType.get(scalarName)!;
60 return this.emitter.result.rawCode(code);
61 }
62
63 intrinsic(intrinsic: IntrinsicType, name: string): EmitterOutput<string> {
64 if (!intrinsicNameToTSType.has(name)) {
65 throw new Error("Unknown intrinsic type " + name);
66 }
67
68 const code = intrinsicNameToTSType.get(name)!;
69 return this.emitter.result.rawCode(code);
70 }
71
72 modelLiteral(model: Model): EmitterOutput<string> {
73 return this.emitter.result.rawCode(code`{ ${this.emitter.emitModelProperties(model)}}`);
74 }
75
76 modelDeclaration(model: Model, name: string): EmitterOutput<string> {
77 let extendsClause;
78
79 if (model.baseModel) {
80 extendsClause = code`extends ${this.emitter.emitTypeReference(model.baseModel)}`;
81 } else {
82 extendsClause = "";
83 }
84
85 const comment = getDoc(this.emitter.getProgram(), model);
86 let commentCode = "";
87
88 if (comment) {
89 commentCode = `
90 /**
91 * ${comment}
92 */`;
93 }
94
95 return this.emitter.result.declaration(
96 name,
97 code`${commentCode}\nexport interface ${name} ${extendsClause} {
98 ${this.emitter.emitModelProperties(model)}
99 }`,
100 );
101 }
102
103 modelInstantiation(model: Model, name: string): EmitterOutput<string> {
104 if (this.emitter.getProgram().checker.isStdType(model, "Record")) {
105 const indexerValue = model.indexer!.value;
106 return code`Record<string, ${this.emitter.emitTypeReference(indexerValue)}>`;
107 }
108 return this.modelDeclaration(model, name);
109 }
110
111 modelPropertyLiteral(property: ModelProperty): EmitterOutput<string> {
112 const name = property.name === "_" ? "statusCode" : property.name;
113 const doc = getDoc(this.emitter.getProgram(), property);
114 let docString = "";
115
116 if (doc) {
117 docString = `
118 /**
119 * ${doc}
120 */
121 `;
122 }
123
124 return this.emitter.result.rawCode(
125 code`${docString}${name}${property.optional ? "?" : ""}: ${this.emitter.emitTypeReference(
126 property.type,
127 )}`,
128 );
129 }
130
131 arrayDeclaration(array: Model, name: string, elementType: Type): EmitterOutput<string> {
132 return this.emitter.result.declaration(
133 name,
134 code`interface ${name} extends Array<${this.emitter.emitTypeReference(elementType)}> { };`,
135 );
136 }
137
138 arrayLiteral(array: Model, elementType: Type): EmitterOutput<string> {
139 // we always parenthesize here as prettier will remove the unneeded parens.
140 return this.emitter.result.rawCode(code`(${this.emitter.emitTypeReference(elementType)})[]`);
141 }
142
143 operationDeclaration(operation: Operation, name: string): EmitterOutput<string> {
144 return this.emitter.result.declaration(
145 name,
146 code`interface ${name} {
147 ${this.#operationSignature(operation)}
148 }`,
149 );
150 }
151
152 operationParameters(operation: Operation, parameters: Model): EmitterOutput<string> {
153 const cb = new StringBuilder();
154 for (const prop of parameters.properties.values()) {
155 cb.push(
156 code`${prop.name}${prop.optional ? "?" : ""}: ${this.emitter.emitTypeReference(prop.type)},`,
157 );
158 }
159 return cb;
160 }
161
162 #operationSignature(operation: Operation) {
163 return code`(${this.emitter.emitOperationParameters(
164 operation,
165 )}): ${this.emitter.emitOperationReturnType(operation)}`;
166 }
167
168 operationReturnType(operation: Operation, returnType: Type): EmitterOutput<string> {
169 return this.emitter.emitTypeReference(returnType);
170 }
171
172 interfaceDeclaration(iface: Interface, name: string): EmitterOutput<string> {
173 return this.emitter.result.declaration(
174 name,
175 code`
176 export interface ${name} {
177 ${this.emitter.emitInterfaceOperations(iface)}
178 }
179 `,
180 );
181 }
182
183 interfaceOperationDeclaration(operation: Operation, name: string): EmitterOutput<string> {
184 return code`${name}${this.#operationSignature(operation)}`;
185 }
186
187 enumDeclaration(en: Enum, name: string): EmitterOutput<string> {
188 return this.emitter.result.declaration(
189 name,
190 code`export enum ${name} {
191 ${this.emitter.emitEnumMembers(en)}
192 }`,
193 );
194 }
195
196 enumMember(member: EnumMember): EmitterOutput<string> {
197 // should we just fill in value for you?
198 const value = !member.value ? member.name : member.value;
199
200 return `
201 ${member.name} = ${JSON.stringify(value)}
202 `;
203 }
204
205 enumMemberReference(member: EnumMember): EmitterOutput<string> {
206 return `${this.emitter.emitDeclarationName(member.enum)}.${member.name}`;
207 }
208
209 unionDeclaration(union: Union, name: string): EmitterOutput<string> {
210 return this.emitter.result.declaration(
211 name,
212 code`export type ${name} = ${this.emitter.emitUnionVariants(union)}`,
213 );
214 }
215
216 unionInstantiation(union: Union, name: string): EmitterOutput<string> {
217 return this.unionDeclaration(union, name);
218 }
219
220 unionLiteral(union: Union) {
221 return this.emitter.emitUnionVariants(union);
222 }
223
224 unionVariants(union: Union): EmitterOutput<string> {
225 const builder = new StringBuilder();
226 let i = 0;
227 for (const variant of union.variants.values()) {
228 i++;
229 builder.push(code`${this.emitter.emitType(variant)}${i < union.variants.size ? "|" : ""}`);
230 }
231 return this.emitter.result.rawCode(builder.reduce());
232 }
233
234 unionVariant(variant: UnionVariant): EmitterOutput<string> {
235 return this.emitter.emitTypeReference(variant.type);
236 }
237
238 tupleLiteral(tuple: Tuple): EmitterOutput<string> {
239 return code`[${this.emitter.emitTupleLiteralValues(tuple)}]`;
240 }
241
242 reference(
243 targetDeclaration: Declaration<string>,
244 pathUp: Scope<string>[],
245 pathDown: Scope<string>[],
246 commonScope: Scope<string> | null,
247 ) {
248 if (!commonScope) {
249 const sourceSf = (pathUp[0] as SourceFileScope<string>).sourceFile;
250 const targetSf = (pathDown[0] as SourceFileScope<string>).sourceFile;
251 sourceSf.imports.set(`./${targetSf.path.replace(".js", ".ts")}`, [targetDeclaration.name]);
252 }
253
254 return super.reference(targetDeclaration, pathUp, pathDown, commonScope);
255 }
256
257 async sourceFile(sourceFile: SourceFile<string>): Promise<EmittedSourceFile> {
258 const emittedSourceFile: EmittedSourceFile = {
259 path: sourceFile.path,
260 contents: "",
261 };
262
263 for (const [importPath, typeNames] of sourceFile.imports) {
264 emittedSourceFile.contents += `import {${typeNames.join(",")}} from "${importPath}";\n`;
265 }
266
267 for (const decl of sourceFile.globalScope.declarations) {
268 emittedSourceFile.contents += decl.value + "\n";
269 }
270
271 emittedSourceFile.contents = await prettier.format(emittedSourceFile.contents, {
272 parser: "typescript",
273 });
274 return emittedSourceFile;
275 }
276}
277