microsoft/typespec

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
5c4ec3fb2397174ca682e1badefb6cbd7c715f38

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

421lines · 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 "templateMapper",
102 "instantiationParameters",
103 "decorators",
104 "projectionBase",
105 "projectionsByName",
106 "projectionSource",
107 "projector",
108 "projections",
109] as const;
110const omittedPropsSet = new Set(omittedProps);
111type OmittedProps = (typeof omittedProps)[number];
112type NamedTypeUIProps<T extends NamedType> = {
113 type: T;
114 properties: Record<Exclude<keyof T, OmittedProps>, "skip" | "ref" | "nested" | "value">;
115 name?: string;
116};
117
118const NamedTypeUI = <T extends NamedType>({ type, name, properties }: NamedTypeUIProps<T>) => {
119 name ??= type.name;
120 const propsUI: TypeUIBaseProperty[] = Object.entries(type)
121 .map(([key, value]) => {
122 if (omittedPropsSet.has(key as any)) {
123 return undefined;
124 }
125 const action = (properties as any)[key] as "skip" | "ref" | "nested";
126 if (action === "skip") {
127 return undefined;
128 }
129
130 const render = (x: any) =>
131 action === "ref" ? <TypeReference type={value} /> : <TypeUI type={x} />;
132 let valueUI;
133 if (value === undefined) {
134 valueUI = value;
135 } else if (value.kind) {
136 valueUI = render(value);
137 } else if (value instanceof Map || Array.isArray(value)) {
138 valueUI = <ItemList items={value} render={render} />;
139 } else {
140 valueUI = value;
141 }
142 return {
143 name: key,
144 value: valueUI,
145 } satisfies TypeUIBaseProperty;
146 })
147 .filter((x): x is TypeUIBaseProperty => Boolean(x));
148 return (
149 <TypeUIBase type={type} name={name} properties={propsUI.concat([getDataProperty(type)])} />
150 );
151};
152
153interface TypeUIProps {
154 type: Type;
155}
156
157const TypeUI: FunctionComponent<TypeUIProps> = ({ type }) => {
158 switch (type.kind) {
159 case "Namespace":
160 return <NamespaceUI type={type} />;
161 case "Interface":
162 return <InterfaceUI type={type} />;
163 case "Operation":
164 return <OperationUI type={type} />;
165 case "Model":
166 return <ModelUI type={type} />;
167 case "Scalar":
168 return <ScalarUI type={type} />;
169 case "ModelProperty":
170 return <ModelPropertyUI type={type} />;
171 case "Union":
172 return <UnionUI type={type} />;
173 case "UnionVariant":
174 return <UnionVariantUI type={type} />;
175 case "Enum":
176 return <EnumUI type={type} />;
177 case "EnumMember":
178 return <EnumMemberUI type={type} />;
179 default:
180 return null;
181 }
182};
183
184const NamespaceUI: FunctionComponent<{ type: Namespace }> = ({ type }) => {
185 const name = getNamespaceFullName(type) || "(global)";
186
187 return (
188 <NamedTypeUI
189 name={name}
190 type={type}
191 properties={{
192 namespaces: "skip",
193 models: "nested",
194 scalars: "nested",
195 interfaces: "nested",
196 operations: "nested",
197 unions: "nested",
198 enums: "nested",
199 decoratorDeclarations: "nested",
200 functionDeclarations: "nested",
201 }}
202 />
203 );
204};
205
206const InterfaceUI: FunctionComponent<{ type: Interface }> = ({ type }) => {
207 return (
208 <NamedTypeUI
209 type={type}
210 properties={{
211 operations: "nested",
212 }}
213 />
214 );
215};
216
217const OperationUI: FunctionComponent<{ type: Operation }> = ({ type }) => {
218 return (
219 <NamedTypeUI
220 type={type}
221 properties={{
222 interface: "skip",
223 parameters: "nested",
224 returnType: "ref",
225 }}
226 />
227 );
228};
229
230function getDataProperty(type: Type): TypeUIBaseProperty {
231 return {
232 name: "data",
233 description: "in program.stateMap()",
234 value: <TypeData type={type} />,
235 };
236}
237
238const ModelUI: FunctionComponent<{ type: Model }> = ({ type }) => {
239 return (
240 <NamedTypeUI
241 type={type}
242 properties={{
243 indexer: "skip",
244 baseModel: "ref",
245 derivedModels: "ref",
246 properties: "nested",
247 }}
248 />
249 );
250};
251
252const ScalarUI: FunctionComponent<{ type: Scalar }> = ({ type }) => {
253 return (
254 <NamedTypeUI
255 type={type}
256 properties={{
257 baseScalar: "ref",
258 derivedScalars: "ref",
259 }}
260 />
261 );
262};
263
264const ModelPropertyUI: FunctionComponent<{ type: ModelProperty }> = ({ type }) => {
265 return (
266 <NamedTypeUI
267 type={type}
268 properties={{
269 model: "skip",
270 type: "ref",
271 optional: "value",
272 sourceProperty: "ref",
273 default: "value",
274 }}
275 />
276 );
277};
278
279const EnumUI: FunctionComponent<{ type: Enum }> = ({ type }) => {
280 return (
281 <NamedTypeUI
282 type={type}
283 properties={{
284 members: "nested",
285 }}
286 />
287 );
288};
289
290const EnumMemberUI: FunctionComponent<{ type: EnumMember }> = ({ type }) => {
291 return (
292 <NamedTypeUI
293 type={type}
294 properties={{
295 enum: "skip",
296 sourceMember: "ref",
297 value: "value",
298 }}
299 />
300 );
301};
302
303const UnionUI: FunctionComponent<{ type: Union }> = ({ type }) => {
304 if (!isNamedUnion(type)) {
305 return <></>;
306 }
307 return (
308 <NamedTypeUI
309 type={type}
310 properties={{
311 expression: "skip",
312 options: "skip",
313 variants: "nested",
314 }}
315 />
316 );
317};
318
319const UnionVariantUI: FunctionComponent<{ type: UnionVariant }> = ({ type }) => {
320 if (typeof type.name === "symbol") {
321 return <></>;
322 }
323 return (
324 <NamedTypeUI
325 type={type as UnionVariant & { name: string }}
326 properties={{
327 union: "skip",
328 type: "ref",
329 }}
330 />
331 );
332};
333
334const NamedTypeRef: FunctionComponent<{ type: NamedType }> = ({ type }) => {
335 const id = getIdForType(type);
336 const href = `#${id}`;
337 return (
338 <a
339 css={{
340 color: "#268bd2",
341 textDecoration: "none",
342
343 "&:hover": {
344 textDecoration: "underline",
345 },
346 }}
347 href={href}
348 title={type.kind + ": " + id}
349 >
350 {getTypeName(type)}
351 </a>
352 );
353};
354const TypeReference: FunctionComponent<{ type: Type }> = ({ type }) => {
355 switch (type.kind) {
356 case "Namespace":
357 case "Operation":
358 case "Interface":
359 case "Enum":
360 case "ModelProperty":
361 case "Scalar":
362 return <NamedTypeRef type={type} />;
363 case "Model":
364 if (type.name === "") {
365 return (
366 <KeyValueSection>
367 <TypeUI type={type} />
368 </KeyValueSection>
369 );
370 } else {
371 return <NamedTypeRef type={type} />;
372 }
373 case "Union":
374 if (isNamedUnion(type)) {
375 return <NamedTypeRef type={type} />;
376 } else {
377 return (
378 <>
379 {[...type.variants.values()].map((variant, i) => {
380 return (
381 <span key={i}>
382 <TypeReference type={variant.type} />
383 {i < type.variants.size - 1 ? " | " : ""}
384 </span>
385 );
386 })}
387 </>
388 );
389 }
390
391 case "TemplateParameter":
392 return <span>Template Param: {type.node.id.sv}</span>;
393 case "String":
394 return <Literal>"{type.value}"</Literal>;
395 case "Number":
396 case "Boolean":
397 return <>{type.value}</>;
398 default:
399 return null;
400 }
401};
402
403const TypeData: FunctionComponent<{ type: Type }> = ({ type }) => {
404 const program = useContext(ProgramContext);
405 const entries = [...program.stateMaps.entries()]
406 .map(([k, v]) => [k, v.get(undefined)?.get(type) as any])
407 .filter(([k, v]) => !!v);
408 if (entries.length === 0) {
409 return null;
410 }
411 return (
412 <KeyValueSection>
413 {entries.map(([k, v], i) => (
414 <div css={{ display: "flex" }} key={i}>
415 <div css={{ color: "#333", marginRight: "5px" }}>{k.toString()}:</div>{" "}
416 <div>{inspect(v)}</div>
417 </div>
418 ))}
419 </KeyValueSection>
420 );
421};