microsoft/typespec

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
e07d86c87a444d4145b98d85fa416eca819e251c

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

384lines · modecode

1import {
2 Enum,
3 EnumMember,
4 Interface,
5 Model,
6 ModelProperty,
7 Namespace,
8 Operation,
9 Program,
10 Type,
11 Union,
12} from "@cadl-lang/compiler";
13import React, { FunctionComponent, ReactElement, useContext } from "react";
14import ReactDOMServer from "react-dom/server";
15import { ServerStyleSheet } from "styled-components";
16import { Item, Literal, styled } from "./common.js";
17import { inspect } from "./inspect.js";
18
19function expandNamespaces(namespace: Namespace): Namespace[] {
20 return [namespace, ...[...namespace.namespaces.values()].flatMap(expandNamespaces)];
21}
22
23const ProgramContext = React.createContext<Program>({} as any);
24
25export function renderProgram(program: Program) {
26 const sheet = new ServerStyleSheet();
27 const html = ReactDOMServer.renderToString(
28 sheet.collectStyles(<CadlProgramViewer program={program} />)
29 );
30 const styleTags = sheet.getStyleTags();
31 return styleTags + "\n" + html;
32}
33
34export interface CadlProgramViewerProps {
35 program: Program;
36}
37
38const ProgramViewerStyle = styled.div`
39 font-family: monospace;
40 background-color: #f3f3f3;
41
42 ul {
43 margin: 0;
44 padding-left: 20px;
45 overflow: auto;
46 }
47
48 li {
49 margin: 0;
50 list-style: none;
51 position: relative;
52 }
53`;
54
55export const CadlProgramViewer: FunctionComponent<CadlProgramViewerProps> = ({ program }) => {
56 const root = program.checker!.getGlobalNamespaceType();
57 const namespaces = expandNamespaces(root);
58 return (
59 <ProgramContext.Provider value={program}>
60 <ProgramViewerStyle>
61 <ul>
62 {namespaces.map((namespace) => (
63 <li key={program.checker!.getNamespaceString(namespace)}>
64 <Namespace type={namespace} />
65 </li>
66 ))}
67 </ul>
68 </ProgramViewerStyle>
69 </ProgramContext.Provider>
70 );
71};
72
73export interface TypeUIProperty {
74 name: string;
75 value: any;
76 description?: string;
77}
78export interface TypeUIProps {
79 type: Type;
80 name: string;
81 /**
82 * Alternate id
83 * @default getIdForType(type)
84 */
85 id?: string;
86 properties: TypeUIProperty[];
87}
88
89const PropName = styled.span`
90 color: #9c5d27;
91`;
92const PropValue = styled.span``;
93
94const TypeType = styled.span`
95 display: inline;
96 color: #7a3e9d;
97 margin-right: 5px;
98`;
99
100const TypeName = styled.span`
101 display: inline;
102 color: #333333;
103`;
104export const TypeUI: FunctionComponent<TypeUIProps> = (props) => {
105 const program = useContext(ProgramContext);
106 const id = props.id ?? getIdForType(program, props.type);
107 const properties = props.properties.map((prop) => {
108 return (
109 <li key={prop.name}>
110 <PropName title={prop.description}>{prop.name}</PropName>:{" "}
111 <PropValue>{prop.value}</PropValue>
112 </li>
113 );
114 });
115 return (
116 <div>
117 <div id={id}>
118 <TypeType>{props.type.kind}</TypeType>
119 <TypeName>{props.name}</TypeName>
120 </div>
121 <ul>{properties}</ul>
122 </div>
123 );
124};
125
126export interface ItemListProps<T> {
127 items: Map<string, T> | T[];
128 render: (t: T) => ReactElement<any, any> | null;
129}
130
131export const ItemList = <T extends object>(props: ItemListProps<T>) => {
132 if (Array.isArray(props.items)) {
133 if (props.items.length === 0) {
134 return <>{"[]"}</>;
135 }
136 } else {
137 if (props.items.size === 0) {
138 return <>{"{}"}</>;
139 }
140 }
141 return (
142 <ul>
143 {[...props.items.entries()].map(([k, v]) => (
144 <li key={k}>{props.render(v)}</li>
145 ))}
146 </ul>
147 );
148};
149
150const Namespace: FunctionComponent<{ type: Namespace }> = ({ type }) => {
151 const program = useContext(ProgramContext);
152 const name = program.checker!.getNamespaceString(type) || "<root>";
153
154 const properties = [
155 {
156 name: "enums",
157 value: <ItemList items={type.enums} render={(x) => <Enum type={x} />} />,
158 },
159 {
160 name: "models",
161 value: <ItemList items={type.models} render={(x) => <Model type={x} />} />,
162 },
163 {
164 name: "interfaces",
165 value: <ItemList items={type.interfaces} render={(x) => <Interface type={x} />} />,
166 },
167 {
168 name: "operations",
169 value: <ItemList items={type.operations} render={(x) => <Operation type={x} />} />,
170 },
171 {
172 name: "unions",
173 value: <ItemList items={type.unions} render={(x) => <Union type={x} />} />,
174 },
175 ];
176 return <TypeUI type={type} name={name} properties={properties} />;
177};
178
179const Interface: FunctionComponent<{ type: Interface }> = ({ type }) => {
180 const properties = [
181 {
182 name: "operations",
183 value: <ItemList items={type.operations} render={(x) => <Operation type={x} />} />,
184 },
185 getDataProperty(type),
186 ];
187 return <TypeUI type={type} name={type.name} properties={properties} />;
188};
189
190const Operation: FunctionComponent<{ type: Operation }> = ({ type }) => {
191 const properties = [
192 {
193 name: "parameters",
194 value: <Model type={type.parameters} />,
195 },
196 {
197 name: "returnType",
198 value: <TypeReference type={type.returnType} />,
199 },
200 getDataProperty(type),
201 ];
202 return <TypeUI type={type} name={type.name} properties={properties} />;
203};
204
205function getDataProperty(type: Type): TypeUIProperty {
206 return {
207 name: "data",
208 description: "in program.stateMap()",
209 value: <TypeData type={type} />,
210 };
211}
212const Model: FunctionComponent<{ type: Model }> = ({ type }) => {
213 const program = useContext(ProgramContext);
214 const id = getIdForType(program, type);
215 const properties = [
216 {
217 name: "properties",
218 value: <ItemList items={type.properties} render={(x) => <ModelProperty type={x} />} />,
219 },
220 getDataProperty(type),
221 ];
222 return <TypeUI type={type} name={type.name} id={id} properties={properties} />;
223};
224
225const ModelProperty: FunctionComponent<{ type: ModelProperty }> = ({ type }) => {
226 const program = useContext(ProgramContext);
227 const id = getIdForType(program, type);
228 const properties = [
229 {
230 name: "type",
231 value: <TypeReference type={type.type} />,
232 },
233 {
234 name: "optional",
235 value: type.optional,
236 },
237 getDataProperty(type),
238 ];
239 return <TypeUI type={type} name={type.name} id={id} properties={properties} />;
240};
241
242const Enum: FunctionComponent<{ type: Enum }> = ({ type }) => {
243 const program = useContext(ProgramContext);
244 const id = getIdForType(program, type);
245 const properties = [
246 {
247 name: "members",
248 value: <ItemList items={type.members} render={(x) => <EnumMember type={x} />} />,
249 },
250 getDataProperty(type),
251 ];
252 return <TypeUI type={type} name={type.name} id={id} properties={properties} />;
253};
254
255const EnumMember: FunctionComponent<{ type: EnumMember }> = ({ type }) => {
256 const program = useContext(ProgramContext);
257 const id = getIdForType(program, type);
258 const properties = [
259 {
260 name: "value",
261 value: type.value,
262 },
263 getDataProperty(type),
264 ];
265 return <TypeUI type={type} name={type.name} id={id} properties={properties} />;
266};
267
268const Union: FunctionComponent<{ type: Union }> = ({ type }) => {
269 const program = useContext(ProgramContext);
270
271 return (
272 <Item title={type.name ?? "<unamed union>"} id={getIdForType(program, type)}>
273 <TypeData type={type} />
274
275 <UnionOptions type={type} />
276 </Item>
277 );
278};
279
280const UnionOptions: FunctionComponent<{ type: Union }> = ({ type }) => {
281 if (type.options.length === 0) {
282 return <div></div>;
283 }
284 return (
285 <ul>
286 {[...type.options.entries()].map(([k, v]) => (
287 <li key={k}>
288 <TypeReference type={v} />
289 </li>
290 ))}
291 </ul>
292 );
293};
294
295function getIdForType(program: Program, type: Type) {
296 switch (type.kind) {
297 case "Namespace":
298 return program.checker!.getNamespaceString(type);
299 case "Model":
300 case "Enum":
301 case "Union":
302 case "Operation":
303 case "Interface":
304 return `${program.checker!.getNamespaceString(type.namespace)}.${type.name}`;
305 default:
306 return undefined;
307 }
308}
309
310const TypeRef = styled.a`
311 color: #268bd2;
312 text-decoration: none;
313
314 &:hover {
315 text-decoration: underline;
316 }
317`;
318
319const TypeReference: FunctionComponent<{ type: Type }> = ({ type }) => {
320 const program = useContext(ProgramContext);
321 switch (type.kind) {
322 case "Namespace":
323 case "Operation":
324 case "Interface":
325 case "Enum":
326 case "Model":
327 const id = getIdForType(program, type);
328 const href = `#${id}`;
329 return (
330 <TypeRef href={href} title={type.kind + ": " + id}>
331 {type.name}
332 </TypeRef>
333 );
334 case "Union":
335 return (
336 <>
337 {type.options.map((x, i) => {
338 return (
339 <span key={i}>
340 <TypeReference type={x} />
341 {i < type.options.length - 1 ? " | " : ""}
342 </span>
343 );
344 })}
345 </>
346 );
347 case "TemplateParameter":
348 return <span>Template Param: {type.node.id.sv}</span>;
349 case "String":
350 return <Literal>"{type.value}"</Literal>;
351 case "Number":
352 case "Boolean":
353 return <>{type.value}</>;
354 default:
355 return null;
356 }
357};
358
359const TypeDataEntry = styled.div`
360 display: flex;
361`;
362const TypeDataKey = styled.div`
363 color: #333;
364 margin-right: 5px;
365`;
366const TypeDataValue = styled.div``;
367const TypeData: FunctionComponent<{ type: Type }> = ({ type }) => {
368 const program = useContext(ProgramContext);
369 const entries = [...program.stateMaps.entries()]
370 .map(([k, v]) => [k, v.get(undefined)?.get(type) as any])
371 .filter(([k, v]) => !!v);
372 if (entries.length === 0) {
373 return null;
374 }
375 return (
376 <ul>
377 {entries.map(([k, v], i) => (
378 <TypeDataEntry key={i}>
379 <TypeDataKey>{k.toString()}:</TypeDataKey> <TypeDataValue>{inspect(v)}</TypeDataValue>
380 </TypeDataEntry>
381 ))}
382 </ul>
383 );
384};
385