microsoft/typespec

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
75f10dca9a396ec1221eed3b66f4edc20640adff

Branches

Tags

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

Clone

HTTPS

Download ZIP

packages/html-program-viewer/src/ui.tsx

420lines · modecode

1import {
2 Enum,
3 EnumMember,
4 getNamespaceFullName,
5 getTypeName,
6 Interface,
7 Model,
8 ModelProperty,
9 Namespace,
10 Operation,
11 Program,
12 Scalar,
13 Type,
14 Union,
15 UnionVariant,
16} from "@cadl-lang/compiler";
17import { css } from "@emotion/react";
18import React, { FunctionComponent, ReactElement, useContext } from "react";
19import ReactDOMServer from "react-dom/server";
20import { KeyValueSection, Literal } from "./common.js";
21import { inspect } from "./inspect.js";
22import { TypeUIBase, TypeUIBaseProperty } from "./type-ui-base.js";
23import { getIdForType, isNamedUnion } from "./utils.js";
24
25function expandNamespaces(namespace: Namespace): Namespace[] {
26 return [namespace, ...[...namespace.namespaces.values()].flatMap(expandNamespaces)];
27}
28
29const ProgramContext = React.createContext<Program>({} as any);
30
31export function renderProgram(program: Program) {
32 const html = ReactDOMServer.renderToString(<CadlProgramViewer program={program} />);
33 return html;
34}
35
36export interface CadlProgramViewerProps {
37 program: Program;
38}
39
40const ProgramViewerStyles = css({
41 fontFamily: "monospace",
42 backgroundColor: "#f3f3f3",
43 li: {
44 margin: 0,
45 listStyle: "none",
46 position: "relative",
47 },
48});
49
50export const CadlProgramViewer: FunctionComponent<CadlProgramViewerProps> = ({ program }) => {
51 const root = program.checker!.getGlobalNamespaceType();
52 const namespaces = expandNamespaces(root);
53 return (
54 <ProgramContext.Provider value={program}>
55 <div css={ProgramViewerStyles}>
56 <ul css={{ padding: "0 0 0 10px", margin: 0 }}>
57 {namespaces.map((namespace) => (
58 <li key={getNamespaceFullName(namespace)}>
59 <NamespaceUI type={namespace} />
60 </li>
61 ))}
62 </ul>
63 </div>
64 </ProgramContext.Provider>
65 );
66};
67
68export interface ItemListProps<T> {
69 items: Map<string, T> | T[];
70 render: (t: T) => ReactElement<any, any> | null;
71}
72
73export const ItemList = <T extends object>(props: ItemListProps<T>) => {
74 if (Array.isArray(props.items)) {
75 if (props.items.length === 0) {
76 return <>{"[]"}</>;
77 }
78 } else {
79 if (props.items.size === 0) {
80 return <>{"{}"}</>;
81 }
82 }
83 return (
84 <KeyValueSection>
85 {[...props.items.entries()].map(([k, v]) => (
86 <li key={k}>{props.render(v)}</li>
87 ))}
88 </KeyValueSection>
89 );
90};
91
92type NamedType = Type & { name: string };
93const omittedProps = [
94 "kind",
95 "name",
96 "node",
97 "symbol",
98 "namespace",
99 "templateNode",
100 "templateArguments",
101 "instantiationParameters",
102 "decorators",
103 "projectionBase",
104 "projectionsByName",
105 "projectionSource",
106 "projector",
107 "projections",
108] as const;
109const omittedPropsSet = new Set(omittedProps);
110type OmittedProps = typeof omittedProps[number];
111type NamedTypeUIProps<T extends NamedType> = {
112 type: T;
113 properties: Record<Exclude<keyof T, OmittedProps>, "skip" | "ref" | "nested" | "value">;
114 name?: string;
115};
116
117const NamedTypeUI = <T extends NamedType>({ type, name, properties }: NamedTypeUIProps<T>) => {
118 name ??= type.name;
119 const propsUI: TypeUIBaseProperty[] = Object.entries(type)
120 .map(([key, value]) => {
121 if (omittedPropsSet.has(key as any)) {
122 return undefined;
123 }
124 const action = (properties as any)[key] as "skip" | "ref" | "nested";
125 if (action === "skip") {
126 return undefined;
127 }
128
129 const render = (x: any) =>
130 action === "ref" ? <TypeReference type={value} /> : <TypeUI type={x} />;
131 let valueUI;
132 if (value === undefined) {
133 valueUI = value;
134 } else if (value.kind) {
135 valueUI = render(value);
136 } else if (value instanceof Map || Array.isArray(value)) {
137 valueUI = <ItemList items={value} render={render} />;
138 } else {
139 valueUI = value;
140 }
141 return {
142 name: key,
143 value: valueUI,
144 } satisfies TypeUIBaseProperty;
145 })
146 .filter((x): x is TypeUIBaseProperty => Boolean(x));
147 return (
148 <TypeUIBase type={type} name={name} properties={propsUI.concat([getDataProperty(type)])} />
149 );
150};
151
152interface TypeUIProps {
153 type: Type;
154}
155
156const TypeUI: FunctionComponent<TypeUIProps> = ({ type }) => {
157 switch (type.kind) {
158 case "Namespace":
159 return <NamespaceUI type={type} />;
160 case "Interface":
161 return <InterfaceUI type={type} />;
162 case "Operation":
163 return <OperationUI type={type} />;
164 case "Model":
165 return <ModelUI type={type} />;
166 case "Scalar":
167 return <ScalarUI type={type} />;
168 case "ModelProperty":
169 return <ModelPropertyUI type={type} />;
170 case "Union":
171 return <UnionUI type={type} />;
172 case "UnionVariant":
173 return <UnionVariantUI type={type} />;
174 case "Enum":
175 return <EnumUI type={type} />;
176 case "EnumMember":
177 return <EnumMemberUI type={type} />;
178 default:
179 return null;
180 }
181};
182
183const NamespaceUI: FunctionComponent<{ type: Namespace }> = ({ type }) => {
184 const name = getNamespaceFullName(type) || "(global)";
185
186 return (
187 <NamedTypeUI
188 name={name}
189 type={type}
190 properties={{
191 namespaces: "skip",
192 models: "nested",
193 scalars: "nested",
194 interfaces: "nested",
195 operations: "nested",
196 unions: "nested",
197 enums: "nested",
198 decoratorDeclarations: "nested",
199 functionDeclarations: "nested",
200 }}
201 />
202 );
203};
204
205const InterfaceUI: FunctionComponent<{ type: Interface }> = ({ type }) => {
206 return (
207 <NamedTypeUI
208 type={type}
209 properties={{
210 operations: "nested",
211 }}
212 />
213 );
214};
215
216const OperationUI: FunctionComponent<{ type: Operation }> = ({ type }) => {
217 return (
218 <NamedTypeUI
219 type={type}
220 properties={{
221 interface: "skip",
222 parameters: "nested",
223 returnType: "ref",
224 }}
225 />
226 );
227};
228
229function getDataProperty(type: Type): TypeUIBaseProperty {
230 return {
231 name: "data",
232 description: "in program.stateMap()",
233 value: <TypeData type={type} />,
234 };
235}
236
237const ModelUI: FunctionComponent<{ type: Model }> = ({ type }) => {
238 return (
239 <NamedTypeUI
240 type={type}
241 properties={{
242 indexer: "skip",
243 baseModel: "ref",
244 derivedModels: "ref",
245 properties: "nested",
246 }}
247 />
248 );
249};
250
251const ScalarUI: FunctionComponent<{ type: Scalar }> = ({ type }) => {
252 return (
253 <NamedTypeUI
254 type={type}
255 properties={{
256 baseScalar: "ref",
257 derivedScalars: "ref",
258 }}
259 />
260 );
261};
262
263const ModelPropertyUI: FunctionComponent<{ type: ModelProperty }> = ({ type }) => {
264 return (
265 <NamedTypeUI
266 type={type}
267 properties={{
268 model: "skip",
269 type: "ref",
270 optional: "value",
271 sourceProperty: "ref",
272 default: "value",
273 }}
274 />
275 );
276};
277
278const EnumUI: FunctionComponent<{ type: Enum }> = ({ type }) => {
279 return (
280 <NamedTypeUI
281 type={type}
282 properties={{
283 members: "nested",
284 }}
285 />
286 );
287};
288
289const EnumMemberUI: FunctionComponent<{ type: EnumMember }> = ({ type }) => {
290 return (
291 <NamedTypeUI
292 type={type}
293 properties={{
294 enum: "skip",
295 sourceMember: "ref",
296 value: "value",
297 }}
298 />
299 );
300};
301
302const UnionUI: FunctionComponent<{ type: Union }> = ({ type }) => {
303 if (!isNamedUnion(type)) {
304 return <></>;
305 }
306 return (
307 <NamedTypeUI
308 type={type}
309 properties={{
310 expression: "skip",
311 options: "skip",
312 variants: "nested",
313 }}
314 />
315 );
316};
317
318const UnionVariantUI: FunctionComponent<{ type: UnionVariant }> = ({ type }) => {
319 if (typeof type.name === "symbol") {
320 return <></>;
321 }
322 return (
323 <NamedTypeUI
324 type={type as UnionVariant & { name: string }}
325 properties={{
326 union: "skip",
327 type: "ref",
328 }}
329 />
330 );
331};
332
333const NamedTypeRef: FunctionComponent<{ type: NamedType }> = ({ type }) => {
334 const id = getIdForType(type);
335 const href = `#${id}`;
336 return (
337 <a
338 css={{
339 color: "#268bd2",
340 textDecoration: "none",
341
342 "&:hover": {
343 textDecoration: "underline",
344 },
345 }}
346 href={href}
347 title={type.kind + ": " + id}
348 >
349 {getTypeName(type)}
350 </a>
351 );
352};
353const TypeReference: FunctionComponent<{ type: Type }> = ({ type }) => {
354 switch (type.kind) {
355 case "Namespace":
356 case "Operation":
357 case "Interface":
358 case "Enum":
359 case "ModelProperty":
360 case "Scalar":
361 return <NamedTypeRef type={type} />;
362 case "Model":
363 if (type.name === "") {
364 return (
365 <KeyValueSection>
366 <TypeUI type={type} />
367 </KeyValueSection>
368 );
369 } else {
370 return <NamedTypeRef type={type} />;
371 }
372 case "Union":
373 if (isNamedUnion(type)) {
374 return <NamedTypeRef type={type} />;
375 } else {
376 return (
377 <>
378 {[...type.variants.values()].map((variant, i) => {
379 return (
380 <span key={i}>
381 <TypeReference type={variant.type} />
382 {i < type.variants.size - 1 ? " | " : ""}
383 </span>
384 );
385 })}
386 </>
387 );
388 }
389
390 case "TemplateParameter":
391 return <span>Template Param: {type.node.id.sv}</span>;
392 case "String":
393 return <Literal>"{type.value}"</Literal>;
394 case "Number":
395 case "Boolean":
396 return <>{type.value}</>;
397 default:
398 return null;
399 }
400};
401
402const TypeData: FunctionComponent<{ type: Type }> = ({ type }) => {
403 const program = useContext(ProgramContext);
404 const entries = [...program.stateMaps.entries()]
405 .map(([k, v]) => [k, v.get(undefined)?.get(type) as any])
406 .filter(([k, v]) => !!v);
407 if (entries.length === 0) {
408 return null;
409 }
410 return (
411 <KeyValueSection>
412 {entries.map(([k, v], i) => (
413 <div css={{ display: "flex" }} key={i}>
414 <div css={{ color: "#333", marginRight: "5px" }}>{k.toString()}:</div>{" "}
415 <div>{inspect(v)}</div>
416 </div>
417 ))}
418 </KeyValueSection>
419 );
420};
421