Basic html program viewer (#392)
This commit is contained in:
Родитель
95943a9627
Коммит
3fdd7fc561
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@cadl-lang/compiler",
|
||||
"comment": "",
|
||||
"type": "none"
|
||||
}
|
||||
],
|
||||
"packageName": "@cadl-lang/compiler"
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@cadl-lang/html-program-viewer",
|
||||
"comment": "",
|
||||
"type": "none"
|
||||
}
|
||||
],
|
||||
"packageName": "@cadl-lang/html-program-viewer"
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@cadl-lang/rest",
|
||||
"comment": "",
|
||||
"type": "none"
|
||||
}
|
||||
],
|
||||
"packageName": "@cadl-lang/rest"
|
||||
}
|
|
@ -14,6 +14,7 @@ specifiers:
|
|||
'@rush-temp/cadl-vscode': file:./projects/cadl-vscode.tgz
|
||||
'@rush-temp/compiler': file:./projects/compiler.tgz
|
||||
'@rush-temp/eslint-config-cadl': file:./projects/eslint-config-cadl.tgz
|
||||
'@rush-temp/html-program-viewer': file:./projects/html-program-viewer.tgz
|
||||
'@rush-temp/internal-build-utils': file:./projects/internal-build-utils.tgz
|
||||
'@rush-temp/openapi': file:./projects/openapi.tgz
|
||||
'@rush-temp/openapi3': file:./projects/openapi3.tgz
|
||||
|
@ -102,6 +103,7 @@ dependencies:
|
|||
'@rush-temp/cadl-vscode': file:projects/cadl-vscode.tgz
|
||||
'@rush-temp/compiler': file:projects/compiler.tgz
|
||||
'@rush-temp/eslint-config-cadl': file:projects/eslint-config-cadl.tgz_prettier@2.6.2
|
||||
'@rush-temp/html-program-viewer': file:projects/html-program-viewer.tgz
|
||||
'@rush-temp/internal-build-utils': file:projects/internal-build-utils.tgz
|
||||
'@rush-temp/openapi': file:projects/openapi.tgz
|
||||
'@rush-temp/openapi3': file:projects/openapi3.tgz
|
||||
|
@ -5144,6 +5146,28 @@ packages:
|
|||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/html-program-viewer.tgz:
|
||||
resolution: {integrity: sha512-3Zex2bNRi+nTRtbJlMXzl52Bqa3+6EzQpZt5epPpFGJB+eNEPpz1UvDN/XVIxxKBIu9QG+e57UYCWxrj318/zA==, tarball: file:projects/html-program-viewer.tgz}
|
||||
name: '@rush-temp/html-program-viewer'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@types/mocha': 9.1.0
|
||||
'@types/node': 16.0.3
|
||||
'@types/prettier': 2.6.0
|
||||
'@types/react': 18.0.8
|
||||
'@types/react-dom': 18.0.3
|
||||
c8: 7.11.0
|
||||
eslint: 8.13.0
|
||||
mocha: 9.2.2
|
||||
prettier: 2.6.2
|
||||
react: 18.0.0
|
||||
react-dom: 18.0.0_react@18.0.0
|
||||
rimraf: 3.0.2
|
||||
typescript: 4.6.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/internal-build-utils.tgz:
|
||||
resolution: {integrity: sha512-u9OBuT5W8BjtZKm+xGC0lAECOwUokH4HoZkAmEdaYPgXbUpCIu7uGNC3QK847bG4gKf7LjHcoNucnS+BE+FxFA==, tarball: file:projects/internal-build-utils.tgz}
|
||||
name: '@rush-temp/internal-build-utils'
|
||||
|
@ -5267,7 +5291,7 @@ packages:
|
|||
dev: false
|
||||
|
||||
file:projects/samples.tgz:
|
||||
resolution: {integrity: sha512-9NOTc+iv0CahBj7cjFAHL/uNubrt7X1Lb/0OCF7E7eTeiC3LEInTh2RJ9OjCgXQFg5fmHN29+Dc8YRUhTG+smQ==, tarball: file:projects/samples.tgz}
|
||||
resolution: {integrity: sha512-l8VhAvdoml3anddwhZvTspcIQIgY8Io1umiPAhzzl1jbLgI66IZyQONwz5KCvtAQMstajoV9Lbbczv/DpM+rEg==, tarball: file:projects/samples.tgz}
|
||||
name: '@rush-temp/samples'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
|
|
|
@ -152,7 +152,7 @@ export function isNeverType(type: Type): type is NeverType {
|
|||
return type.kind === "Intrinsic" && type.name === "never";
|
||||
}
|
||||
|
||||
const numericTypesKey = Symbol("numericTypes");
|
||||
const numericTypesKey = Symbol("numeric");
|
||||
export function $numeric({ program }: DecoratorContext, target: Type) {
|
||||
if (!isIntrinsic(program, target)) {
|
||||
program.reportDiagnostic(
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"reporter": ["cobertura", "json", "text"]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
require("@cadl-lang/eslint-config-cadl/patch/modern-module-resolution");
|
||||
|
||||
module.exports = {
|
||||
extends: "@cadl-lang/eslint-config-cadl",
|
||||
parserOptions: { tsconfigRootDir: __dirname },
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
timeout: 5000
|
||||
require: source-map-support/register
|
||||
spec: "dist/test/**/*.js"
|
||||
ignore: "dist/test/manual/**/*.js"
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
|
@ -0,0 +1,7 @@
|
|||
# Cadl Html Program viewer emitter.
|
||||
|
||||
## See also
|
||||
|
||||
- [Cadl Getting Started](https://github.com/microsoft/cadl#getting-started)
|
||||
- [Cadl Tutorial](https://github.com/microsoft/cadl/blob/main/docs/tutorial.md)
|
||||
- [Cadl for the OpenAPI Developer](https://github.com/microsoft/cadl/blob/main/docs/cadl-for-openapi-dev.md)
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"name": "@cadl-lang/html-program-viewer",
|
||||
"version": "0.1.0",
|
||||
"author": "Microsoft Corporation",
|
||||
"description": "Cadl library for emitting an html view of the program.",
|
||||
"homepage": "https://github.com/Azure/cadl",
|
||||
"readme": "https://github.com/Azure/cadl/blob/master/README.md",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Azure/cadl.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Azure/cadl/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"cadl"
|
||||
],
|
||||
"type": "module",
|
||||
"main": "dist/src/index.js",
|
||||
"exports": {
|
||||
".": "./dist/src/index.js",
|
||||
"./testing": "./dist/src/testing/index.js"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"./dist/src/index.d.ts"
|
||||
],
|
||||
"testing": [
|
||||
"./dist/src/testing/index.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"cadlMain": "dist/src/index.js",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./dist ./temp",
|
||||
"build": "tsc -p .",
|
||||
"watch": "tsc -p . --watch",
|
||||
"test": "mocha",
|
||||
"test-official": "c8 mocha --forbid-only",
|
||||
"lint": "eslint . --ext .ts --max-warnings=0",
|
||||
"lint:fix": "eslint . --fix --ext .ts"
|
||||
},
|
||||
"files": [
|
||||
"lib/*.cadl",
|
||||
"dist/**",
|
||||
"!dist/test/**"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@cadl-lang/compiler": "~0.30.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"prettier": "~2.6.2",
|
||||
"react": "~18.0.0",
|
||||
"react-dom": "~18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "~9.1.0",
|
||||
"@types/node": "~16.0.3",
|
||||
"@types/prettier": "^2.0.2",
|
||||
"@types/react": "~18.0.5",
|
||||
"@types/react-dom": "~18.0.1",
|
||||
"@cadl-lang/compiler": "~0.31.0",
|
||||
"@cadl-lang/eslint-config-cadl": "~0.3.0",
|
||||
"eslint": "^8.12.0",
|
||||
"mocha": "~9.2.0",
|
||||
"c8": "~7.11.0",
|
||||
"rimraf": "~3.0.2",
|
||||
"typescript": "~4.6.3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import React, { FunctionComponent, PropsWithChildren } from "react";
|
||||
|
||||
export const Group: FunctionComponent<PropsWithChildren<{}>> = ({ children }) => {
|
||||
return <div className="group">{children}</div>;
|
||||
};
|
||||
|
||||
export interface SectionProps {
|
||||
title: string;
|
||||
id?: string;
|
||||
hide?: boolean;
|
||||
}
|
||||
|
||||
export const Section: FunctionComponent<PropsWithChildren<SectionProps>> = ({
|
||||
id,
|
||||
title,
|
||||
hide,
|
||||
children,
|
||||
}) => {
|
||||
if (hide) {
|
||||
return <div></div>;
|
||||
}
|
||||
return (
|
||||
<div className="section" id={id}>
|
||||
<div className="section-title">{title}</div>
|
||||
<div className="section-content">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Item: FunctionComponent<PropsWithChildren<SectionProps>> = ({
|
||||
id,
|
||||
title,
|
||||
hide,
|
||||
children,
|
||||
}) => {
|
||||
if (hide) {
|
||||
return <div></div>;
|
||||
}
|
||||
return (
|
||||
<div className="item" id={id}>
|
||||
<div className="item-title">{title}</div>
|
||||
<div className="item-content">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Literal: FunctionComponent<PropsWithChildren<{}>> = ({ children }) => {
|
||||
return <div className="literal">{children}</div>;
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
import { Program, resolvePath } from "@cadl-lang/compiler";
|
||||
import { readFile } from "fs/promises";
|
||||
import { dirname, resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { renderProgram } from "./ui.js";
|
||||
|
||||
export async function $onEmit(program: Program) {
|
||||
const html = renderProgram(program);
|
||||
const htmlPath = resolvePath(program.compilerOptions.outputPath!, "cadl-program.html");
|
||||
const cssPath = resolvePath(program.compilerOptions.outputPath!, "style.css");
|
||||
await program.host.writeFile(
|
||||
htmlPath,
|
||||
`<!DOCTYPE html><html lang="en"><link rel="stylesheet" href="style.css"><body>${html}</body></html>`
|
||||
);
|
||||
const cssFile = resolve(dirname(fileURLToPath(import.meta.url)), "../../src/style.css");
|
||||
await program.host.writeFile(cssPath, await (await readFile(cssFile)).toString());
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from "./emitter.js";
|
|
@ -0,0 +1,93 @@
|
|||
body {
|
||||
font-family: monospace;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
border: 1px solid #dedede;
|
||||
}
|
||||
|
||||
th,
|
||||
td,
|
||||
caption {
|
||||
border: 1px solid #dedede;
|
||||
}
|
||||
|
||||
.group {
|
||||
border: 1px solid #c5c5c5;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
border: 1px solid #c5c5c5;
|
||||
}
|
||||
|
||||
.section > .section-title {
|
||||
border-bottom: 1px solid #c5c5c5;
|
||||
background-color: #4875ca;
|
||||
color: #f5f5f5;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
.section > .section-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.item {
|
||||
border: 1px solid #c5c5c5;
|
||||
}
|
||||
|
||||
.item > .item-title {
|
||||
border-bottom: 1px solid #c5c5c5;
|
||||
background-color: #dedede;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
.item > .item-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.literal {
|
||||
color: #5da713;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.type-type {
|
||||
display: inline;
|
||||
color: #7A3E9D;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.type-name {
|
||||
display: inline;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.type-ref {
|
||||
color: #268bd2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.type-ref:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.prop {
|
||||
}
|
||||
.prop .prop-name {
|
||||
color: #9C5D27;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { resolvePath } from "@cadl-lang/compiler";
|
||||
import { CadlTestLibrary } from "@cadl-lang/compiler/testing";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
export const ProgramViewerTestLibrary: CadlTestLibrary = {
|
||||
name: "@cadl-lang/html-program-viewer",
|
||||
packageRoot: resolvePath(fileURLToPath(import.meta.url), "../../../../"),
|
||||
files: [
|
||||
{
|
||||
realDir: "",
|
||||
pattern: "package.json",
|
||||
virtualPath: "./node_modules/@cadl-lang/html-program-viewer",
|
||||
},
|
||||
{
|
||||
realDir: "dist/src",
|
||||
pattern: "*.js",
|
||||
virtualPath: "./node_modules/@cadl-lang/html-program-viewer/dist/src",
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,328 @@
|
|||
import {
|
||||
EnumMemberType,
|
||||
EnumType,
|
||||
InterfaceType,
|
||||
ModelType,
|
||||
ModelTypeProperty,
|
||||
NamespaceType,
|
||||
OperationType,
|
||||
Program,
|
||||
Type,
|
||||
UnionType,
|
||||
} from "@cadl-lang/compiler";
|
||||
import React, { FunctionComponent, ReactElement, useContext } from "react";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import { inspect } from "util";
|
||||
import { Item, Literal } from "./common.js";
|
||||
|
||||
function expandNamespaces(namespace: NamespaceType): NamespaceType[] {
|
||||
return [namespace, ...[...namespace.namespaces.values()].flatMap(expandNamespaces)];
|
||||
}
|
||||
|
||||
const ProgramContext = React.createContext<Program>({} as any);
|
||||
|
||||
export function renderProgram(program: Program) {
|
||||
const root = program.checker!.getGlobalNamespaceType();
|
||||
const namespaces = expandNamespaces(root);
|
||||
return ReactDOMServer.renderToString(
|
||||
<ProgramContext.Provider value={program}>
|
||||
<ul>
|
||||
{namespaces.map((namespace) => (
|
||||
<li key={program.checker!.getNamespaceString(namespace)}>
|
||||
<Namespace type={namespace} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</ProgramContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export interface TypeUIProperty {
|
||||
name: string;
|
||||
value: any;
|
||||
description?: string;
|
||||
}
|
||||
export interface TypeUIProps {
|
||||
type: Type;
|
||||
name: string;
|
||||
/**
|
||||
* Alternate id
|
||||
* @default getIdForType(type)
|
||||
*/
|
||||
id?: string;
|
||||
properties: TypeUIProperty[];
|
||||
}
|
||||
|
||||
export const TypeUI: FunctionComponent<TypeUIProps> = (props) => {
|
||||
const program = useContext(ProgramContext);
|
||||
const id = props.id ?? getIdForType(program, props.type);
|
||||
const properties = props.properties.map((prop) => {
|
||||
return (
|
||||
<li key={prop.name} className="prop">
|
||||
<span className="prop-name" title={prop.description}>
|
||||
{prop.name}
|
||||
</span>
|
||||
: <span className="prop-value">{prop.value}</span>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<div id={id}>
|
||||
<span className="type-type">{props.type.kind}</span>
|
||||
<span className="type-name">{props.name}</span>
|
||||
</div>
|
||||
<ul>{properties}</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface ItemListProps<T> {
|
||||
items: Map<string, T> | T[];
|
||||
render: (t: T) => ReactElement<any, any> | null;
|
||||
}
|
||||
|
||||
export const ItemList = <T extends object>(props: ItemListProps<T>) => {
|
||||
if (Array.isArray(props.items)) {
|
||||
if (props.items.length === 0) {
|
||||
return <>{"[]"}</>;
|
||||
}
|
||||
} else {
|
||||
if (props.items.size === 0) {
|
||||
return <>{"{}"}</>;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ul>
|
||||
{[...props.items.entries()].map(([k, v]) => (
|
||||
<li key={k}>{props.render(v)}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
const Namespace: FunctionComponent<{ type: NamespaceType }> = ({ type }) => {
|
||||
const program = useContext(ProgramContext);
|
||||
const name = program.checker!.getNamespaceString(type) || "<root>";
|
||||
|
||||
const properties = [
|
||||
{
|
||||
name: "enums",
|
||||
value: <ItemList items={type.enums} render={(x) => <Enum type={x} />} />,
|
||||
},
|
||||
{
|
||||
name: "models",
|
||||
value: <ItemList items={type.models} render={(x) => <Model type={x} />} />,
|
||||
},
|
||||
{
|
||||
name: "interfaces",
|
||||
value: <ItemList items={type.interfaces} render={(x) => <Interface type={x} />} />,
|
||||
},
|
||||
{
|
||||
name: "operations",
|
||||
value: <ItemList items={type.operations} render={(x) => <Operation type={x} />} />,
|
||||
},
|
||||
{
|
||||
name: "unions",
|
||||
value: <ItemList items={type.unions} render={(x) => <Union type={x} />} />,
|
||||
},
|
||||
];
|
||||
return <TypeUI type={type} name={name} properties={properties} />;
|
||||
};
|
||||
|
||||
const Interface: FunctionComponent<{ type: InterfaceType }> = ({ type }) => {
|
||||
const properties = [
|
||||
{
|
||||
name: "operations",
|
||||
value: <ItemList items={type.operations} render={(x) => <Operation type={x} />} />,
|
||||
},
|
||||
getDataProperty(type),
|
||||
];
|
||||
return <TypeUI type={type} name={type.name} properties={properties} />;
|
||||
};
|
||||
|
||||
const Operation: FunctionComponent<{ type: OperationType }> = ({ type }) => {
|
||||
const properties = [
|
||||
{
|
||||
name: "parameters",
|
||||
value: <Model type={type.parameters} />,
|
||||
},
|
||||
{
|
||||
name: "returnType",
|
||||
value: <TypeReference type={type.returnType} />,
|
||||
},
|
||||
getDataProperty(type),
|
||||
];
|
||||
return <TypeUI type={type} name={type.name} properties={properties} />;
|
||||
};
|
||||
|
||||
function getDataProperty(type: Type): TypeUIProperty {
|
||||
return {
|
||||
name: "data",
|
||||
description: "in program.stateMap()",
|
||||
value: <TypeData type={type} />,
|
||||
};
|
||||
}
|
||||
const Model: FunctionComponent<{ type: ModelType }> = ({ type }) => {
|
||||
const program = useContext(ProgramContext);
|
||||
const id = getIdForType(program, type);
|
||||
const properties = [
|
||||
{
|
||||
name: "properties",
|
||||
value: <ItemList items={type.properties} render={(x) => <ModelProperty type={x} />} />,
|
||||
},
|
||||
getDataProperty(type),
|
||||
];
|
||||
return <TypeUI type={type} name={type.name} id={id} properties={properties} />;
|
||||
};
|
||||
|
||||
const ModelProperty: FunctionComponent<{ type: ModelTypeProperty }> = ({ type }) => {
|
||||
const program = useContext(ProgramContext);
|
||||
const id = getIdForType(program, type);
|
||||
const properties = [
|
||||
{
|
||||
name: "type",
|
||||
value: <TypeReference type={type.type} />,
|
||||
},
|
||||
{
|
||||
name: "optional",
|
||||
value: type.optional,
|
||||
},
|
||||
getDataProperty(type),
|
||||
];
|
||||
return <TypeUI type={type} name={type.name} id={id} properties={properties} />;
|
||||
};
|
||||
|
||||
const Enum: FunctionComponent<{ type: EnumType }> = ({ type }) => {
|
||||
const program = useContext(ProgramContext);
|
||||
const id = getIdForType(program, type);
|
||||
const properties = [
|
||||
{
|
||||
name: "members",
|
||||
value: <ItemList items={type.members} render={(x) => <EnumMember type={x} />} />,
|
||||
},
|
||||
getDataProperty(type),
|
||||
];
|
||||
return <TypeUI type={type} name={type.name} id={id} properties={properties} />;
|
||||
};
|
||||
|
||||
const EnumMember: FunctionComponent<{ type: EnumMemberType }> = ({ type }) => {
|
||||
const program = useContext(ProgramContext);
|
||||
const id = getIdForType(program, type);
|
||||
const properties = [
|
||||
{
|
||||
name: "value",
|
||||
value: type.value,
|
||||
},
|
||||
getDataProperty(type),
|
||||
];
|
||||
return <TypeUI type={type} name={type.name} id={id} properties={properties} />;
|
||||
};
|
||||
|
||||
const Union: FunctionComponent<{ type: UnionType }> = ({ type }) => {
|
||||
const program = useContext(ProgramContext);
|
||||
|
||||
return (
|
||||
<Item title={type.name ?? "<unamed union>"} id={getIdForType(program, type)}>
|
||||
<TypeData type={type} />
|
||||
|
||||
<UnionOptions type={type} />
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
const UnionOptions: FunctionComponent<{ type: UnionType }> = ({ type }) => {
|
||||
if (type.options.length === 0) {
|
||||
return <div></div>;
|
||||
}
|
||||
return (
|
||||
<ul>
|
||||
{[...type.options.entries()].map(([k, v]) => (
|
||||
<li key={k}>
|
||||
<TypeReference type={v} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
function getIdForType(program: Program, type: Type) {
|
||||
switch (type.kind) {
|
||||
case "Namespace":
|
||||
return program.checker!.getNamespaceString(type);
|
||||
case "Model":
|
||||
case "Enum":
|
||||
case "Union":
|
||||
case "Operation":
|
||||
case "Interface":
|
||||
return `${program.checker!.getNamespaceString(type.namespace)}.${type.name}`;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const TypeReference: FunctionComponent<{ type: Type }> = ({ type }) => {
|
||||
const program = useContext(ProgramContext);
|
||||
switch (type.kind) {
|
||||
case "Namespace":
|
||||
case "Operation":
|
||||
case "Interface":
|
||||
case "Enum":
|
||||
case "Model":
|
||||
const id = getIdForType(program, type);
|
||||
const href = `#${id}`;
|
||||
return (
|
||||
<a className="type-ref" href={href} title={type.kind + ": " + id}>
|
||||
{type.name}
|
||||
</a>
|
||||
);
|
||||
case "Array":
|
||||
return (
|
||||
<>
|
||||
<TypeReference type={type.elementType} />
|
||||
{"[]"}
|
||||
</>
|
||||
);
|
||||
case "Union":
|
||||
return (
|
||||
<>
|
||||
{type.options.map((x, i) => {
|
||||
return (
|
||||
<span key={i}>
|
||||
<TypeReference type={x} />
|
||||
{i < type.options.length - 1 ? " | " : ""}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
case "TemplateParameter":
|
||||
return <span>Template Param: {type.node.id.sv}</span>;
|
||||
case "String":
|
||||
return <Literal>"{type.value}"</Literal>;
|
||||
case "Number":
|
||||
case "Boolean":
|
||||
return <>{type.value}</>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const TypeData: FunctionComponent<{ type: Type }> = ({ type }) => {
|
||||
const program = useContext(ProgramContext);
|
||||
const entries = [...program.stateMaps.entries()]
|
||||
.map(([k, v]) => [k, v.get(type)])
|
||||
.filter(([k, v]) => !!v);
|
||||
if (entries.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ul>
|
||||
{entries.map(([k, v], i) => (
|
||||
<li key={i}>
|
||||
{k.toString()}: {inspect(v, { showHidden: false })}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
import { BasicTestRunner } from "@cadl-lang/compiler/testing";
|
||||
import { createViewerTestRunner } from "./test-host.js";
|
||||
|
||||
describe("Smoke tests", () => {
|
||||
let runner: BasicTestRunner;
|
||||
|
||||
beforeEach(async () => {
|
||||
runner = await createViewerTestRunner();
|
||||
});
|
||||
|
||||
it("create html view", async () => {
|
||||
await runner.compile(`op foo(): string;`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
import { createTestHost, createTestWrapper } from "@cadl-lang/compiler/testing";
|
||||
import { ProgramViewerTestLibrary } from "../src/testing/index.js";
|
||||
|
||||
export async function createOpenAPITestHost() {
|
||||
return createTestHost({
|
||||
libraries: [ProgramViewerTestLibrary],
|
||||
});
|
||||
}
|
||||
|
||||
export async function createViewerTestRunner() {
|
||||
const host = await createOpenAPITestHost();
|
||||
return createTestWrapper(host, (code) => code, { emitters: ["@cadl-lang/html-program-viewer"] });
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"references": [
|
||||
{ "path": "../compiler/tsconfig.json" },
|
||||
{ "path": "../rest/tsconfig.json" },
|
||||
{ "path": "../openapi/tsconfig.json" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": ".",
|
||||
"tsBuildInfoFile": "temp/tsconfig.tsbuildinfo",
|
||||
"types": ["node", "mocha"],
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts"]
|
||||
}
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from "@cadl-lang/compiler";
|
||||
import { reportDiagnostic } from "./diagnostics.js";
|
||||
|
||||
const headerFieldsKey = Symbol("headerFields");
|
||||
const headerFieldsKey = Symbol("header");
|
||||
export function $header({ program }: DecoratorContext, entity: Type, headerName?: string) {
|
||||
if (!validateDecoratorTarget(program, entity, "@header", "ModelProperty")) {
|
||||
return;
|
||||
|
@ -35,7 +35,7 @@ export function isHeader(program: Program, entity: Type) {
|
|||
return program.stateMap(headerFieldsKey).has(entity);
|
||||
}
|
||||
|
||||
const queryFieldsKey = Symbol("queryFields");
|
||||
const queryFieldsKey = Symbol("query");
|
||||
export function $query({ program }: DecoratorContext, entity: Type, queryKey?: string) {
|
||||
if (!validateDecoratorTarget(program, entity, "@query", "ModelProperty")) {
|
||||
return;
|
||||
|
@ -59,7 +59,7 @@ export function isQueryParam(program: Program, entity: Type) {
|
|||
return program.stateMap(queryFieldsKey).has(entity);
|
||||
}
|
||||
|
||||
const pathFieldsKey = Symbol("pathFields");
|
||||
const pathFieldsKey = Symbol("path");
|
||||
export function $path({ program }: DecoratorContext, entity: Type, paramName?: string) {
|
||||
if (!validateDecoratorTarget(program, entity, "@path", "ModelProperty")) {
|
||||
return;
|
||||
|
@ -83,7 +83,7 @@ export function isPathParam(program: Program, entity: Type) {
|
|||
return program.stateMap(pathFieldsKey).has(entity);
|
||||
}
|
||||
|
||||
const bodyFieldsKey = Symbol("bodyFields");
|
||||
const bodyFieldsKey = Symbol("body");
|
||||
export function $body({ program }: DecoratorContext, entity: Type) {
|
||||
if (!validateDecoratorTarget(program, entity, "@body", "ModelProperty")) {
|
||||
return;
|
||||
|
@ -214,7 +214,7 @@ export function getStatusCodeDescription(statusCode: string) {
|
|||
|
||||
export type HttpVerb = "get" | "put" | "post" | "patch" | "delete" | "head";
|
||||
|
||||
const operationVerbsKey = Symbol("operationVerbs");
|
||||
const operationVerbsKey = Symbol("verbs");
|
||||
|
||||
function setOperationVerb(program: Program, entity: Type, verb: HttpVerb): void {
|
||||
if (entity.kind === "Operation") {
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
"@cadl-lang/compiler": "~0.31.0",
|
||||
"@cadl-lang/rest": "~0.14.0",
|
||||
"@cadl-lang/openapi": "~0.9.0",
|
||||
"@cadl-lang/openapi3": "~0.11.0"
|
||||
"@cadl-lang/openapi3": "~0.11.0",
|
||||
"@cadl-lang/html-program-viewer": "~0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cadl-lang/internal-build-utils": "~0.2.0",
|
||||
|
|
|
@ -126,6 +126,12 @@
|
|||
"reviewCategory": "production",
|
||||
"shouldPublish": true
|
||||
},
|
||||
{
|
||||
"packageName": "@cadl-lang/html-program-viewer",
|
||||
"projectFolder": "packages/html-program-viewer",
|
||||
"reviewCategory": "production",
|
||||
"shouldPublish": true
|
||||
},
|
||||
{
|
||||
"packageName": "@cadl-lang/versioning",
|
||||
"projectFolder": "packages/versioning",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
{ "path": "packages/cadl-vscode/tsconfig.json" },
|
||||
{ "path": "packages/internal-build-utils/tsconfig.json" },
|
||||
{ "path": "packages/tmlanguage-generator/tsconfig.json" },
|
||||
{ "path": "packages/html-program-viewer/tsconfig.json" },
|
||||
{ "path": "packages/openapi3/tsconfig.json" },
|
||||
{ "path": "packages/bundler/tsconfig.json" },
|
||||
{ "path": "packages/playground/tsconfig.json" }
|
||||
|
|
Загрузка…
Ссылка в новой задаче