Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add linter selection to playground #2386

Merged
merged 11 commits into from
Sep 11, 2023
2 changes: 1 addition & 1 deletion .github/workflows/tryit-comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
`Changes in this PR will be published to the following url to try(check status of TypeSpec Pull Request Try It pipeline for publish status):`,
`Playground: https://cadlplayground.z22.web.core.windows.net/prs/${prNumber}/`,
"",
`Website: https://cadlwebsite.z1.web.core.windows.net/prs/${prNumber}/`,
`Website: https://tspwebsitepr.z5.web.core.windows.net/prs/${prNumber}/`,

].join("\n")
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@typespec/compiler",
"comment": "Expose `CompilerOptions` TypeScript type",
"type": "none"
}
],
"packageName": "@typespec/compiler"
}
2 changes: 1 addition & 1 deletion eng/pipelines/pr-tryit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
inlineScript: |
az storage blob upload-batch \
--destination \$web \
--account-name "cadlwebsite" \
--account-name "tspwebsitepr" \
--destination-path $(TYPESPEC_WEBSITE_BASE_PATH) \
--source "./packages/website/build/" \
--overwrite
Expand Down
2 changes: 1 addition & 1 deletion eng/scripts/create-tryit-comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ async function main() {
`<!-- ${TRY_ID_COMMENT_IDENTIFIER} -->`,
`You can try these changes at https://cadlplayground.z22.web.core.windows.net${folderName}/prs/${prNumber}/`,
"",
`Check the website changes at https://cadlwebsite.z1.web.core.windows.net${folderName}/prs/${prNumber}/`,
`Check the website changes at https://tspwebsitepr.z5.web.core.windows.net${folderName}/prs/${prNumber}/`,
].join("\n");
await writeComment(repo, prNumber, comment, ghAuth);
}
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export {
} from "./library.js";
export * from "./module-resolver.js";
export * from "./node-host.js";
export * from "./options.js";
export * from "./parser.js";
export * from "./path-utils.js";
export * from "./program.js";
Expand Down
12 changes: 10 additions & 2 deletions packages/playground-website/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,16 @@ const config = definePlaygroundViteConfig({
filename: "samples/unions.tsp",
preferredEmitter: "@typespec/openapi3",
},
"HTTP service": { filename: "samples/http.tsp", preferredEmitter: "@typespec/openapi3" },
"REST framework": { filename: "samples/rest.tsp", preferredEmitter: "@typespec/openapi3" },
"HTTP service": {
filename: "samples/http.tsp",
preferredEmitter: "@typespec/openapi3",
compilerOptions: { linterRuleSet: { extends: ["@typespec/http/all"] } },
},
"REST framework": {
filename: "samples/rest.tsp",
preferredEmitter: "@typespec/openapi3",
compilerOptions: { linterRuleSet: { extends: ["@typespec/http/all"] } },
},
"Protobuf Kiosk": {
filename: "samples/kiosk.tsp",
preferredEmitter: "@typespec/protobuf",
Expand Down
2 changes: 1 addition & 1 deletion packages/playground/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ export { registerMonacoDefaultWorkers } from "./monaco-worker.js";
export { registerMonacoLanguage } from "./services.js";
export { createUrlStateStorage } from "./state-storage.js";
export { PlaygroundSample } from "./types.js";
export { filterEmitters } from "./utils.js";
export { resolveLibraries as filterEmitters } from "./utils.js";
24 changes: 13 additions & 11 deletions packages/playground/src/react/editor-command-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,23 @@ import {
Tooltip,
} from "@fluentui/react-components";
import { Bug16Regular, Save16Regular, Settings24Regular } from "@fluentui/react-icons";
import { CompilerOptions } from "@typespec/compiler";
import { FunctionComponent } from "react";
import { PlaygroundSample } from "../types.js";
import { EmitterDropdown } from "./emitter-dropdown.js";
import { OutputSettings } from "./output-settings.js";
import { SamplesDropdown } from "./samples-dropdown.js";
import { EmitterOptions } from "./types.js";
import { CompilerSettings } from "./settings/compiler-settings.js";
import { PlaygroundTspLibrary } from "./types.js";

export interface EditorCommandBarProps {
documentationUrl?: string;
saveCode: () => Promise<void> | void;
newIssue?: () => Promise<void> | void;
emitters: string[];
libraries: PlaygroundTspLibrary[];
selectedEmitter: string;
onSelectedEmitterChange: (emitter: string) => void;
emitterOptions: EmitterOptions;
onEmitterOptionsChange: (options: EmitterOptions) => void;
compilerOptions: CompilerOptions;
onCompilerOptionsChange: (options: CompilerOptions) => void;

samples?: Record<string, PlaygroundSample>;
selectedSampleName: string;
Expand All @@ -35,11 +36,11 @@ export const EditorCommandBar: FunctionComponent<EditorCommandBarProps> = ({
documentationUrl,
saveCode,
newIssue,
emitters,
libraries,
selectedEmitter,
onSelectedEmitterChange,
emitterOptions,
onEmitterOptionsChange,
compilerOptions: emitterOptions,
onCompilerOptionsChange,
samples,
selectedSampleName,
onSelectedSampleNameChange,
Expand Down Expand Up @@ -73,7 +74,7 @@ export const EditorCommandBar: FunctionComponent<EditorCommandBarProps> = ({
/>
)}
<EmitterDropdown
emitters={emitters}
emitters={libraries.filter((x) => x.isEmitter).map((x) => x.name)}
onSelectedEmitterChange={onSelectedEmitterChange}
selectedEmitter={selectedEmitter}
/>
Expand All @@ -83,10 +84,11 @@ export const EditorCommandBar: FunctionComponent<EditorCommandBarProps> = ({
</DialogTrigger>
<DialogSurface>
<DialogBody>
<OutputSettings
<CompilerSettings
libraries={libraries}
selectedEmitter={selectedEmitter}
options={emitterOptions}
optionsChanged={onEmitterOptionsChange}
onOptionsChanged={onCompilerOptionsChange}
/>
</DialogBody>
</DialogSurface>
Expand Down
15 changes: 15 additions & 0 deletions packages/playground/src/react/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,18 @@ export function useControllableValue<TValue>(

return [currentValue, setValueOrCallOnChange] as any;
}

export function useAsyncMemo<T>(
callback: () => Promise<T>,
defaultValue: T,
deps?: React.DependencyList
): T {
const [value, setValue] = useState<T>(defaultValue);
useEffect(() => {
callback()
.then(setValue)
// eslint-disable-next-line no-console
.catch(() => console.error("Failed to load async memo"));
}, deps);
return value;
}
60 changes: 36 additions & 24 deletions packages/playground/src/react/playground.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CompilerOptions } from "@typespec/compiler";
import debounce from "debounce";
import { KeyCode, KeyMod, MarkerSeverity, Uri, editor } from "monaco-editor";
import { FunctionComponent, useCallback, useEffect, useMemo, useState } from "react";
Expand All @@ -7,12 +8,13 @@ import { BrowserHost } from "../browser-host.js";
import { importTypeSpecCompiler } from "../core.js";
import { getMarkerLocation } from "../services.js";
import { PlaygroundSample } from "../types.js";
import { resolveLibraries } from "../utils.js";
import { EditorCommandBar } from "./editor-command-bar.js";
import { useMonacoModel } from "./editor.js";
import { Footer } from "./footer.js";
import { useControllableValue } from "./hooks.js";
import { useAsyncMemo, useControllableValue } from "./hooks.js";
import { OutputView } from "./output-view.js";
import { CompilationState, EmitterOptions, FileOutputViewer } from "./types.js";
import { CompilationState, FileOutputViewer } from "./types.js";
import { TypeSpecEditor } from "./typespec-editor.js";

export interface PlaygroundProps {
Expand All @@ -21,8 +23,8 @@ export interface PlaygroundProps {
/** Default emitter if leaving this unmanaged. */
defaultContent?: string;

/** List of available emitters */
emitters: string[];
/** List of available libraries */
libraries: string[];

/** Emitter to use */
emitter?: string;
Expand All @@ -32,11 +34,11 @@ export interface PlaygroundProps {
onEmitterChange?: (emitter: string) => void;

/** Emitter options */
emitterOptions?: EmitterOptions;
compilerOptions?: CompilerOptions;
/** Default emitter options if leaving this unmanaged. */
defaultEmitterOptions?: EmitterOptions;
defaultCompilerOptions?: CompilerOptions;
/** Callback when emitter options change */
onEmitterOptionsChange?: (emitter: EmitterOptions) => void;
onCompilerOptionsChange?: (emitter: CompilerOptions) => void;

/** Samples available */
samples?: Record<string, PlaygroundSample>;
Expand Down Expand Up @@ -65,7 +67,7 @@ export interface PlaygroundSaveData {
emitter: string;

/** Emitter options. */
options?: EmitterOptions;
options?: CompilerOptions;

/** If a sample is selected and the content hasn't changed since. */
sampleName?: string;
Expand All @@ -85,10 +87,10 @@ export const Playground: FunctionComponent<PlaygroundProps> = (props) => {
props.defaultEmitter,
props.onEmitterChange
);
const [emitterOptions, onEmitterOptionsChange] = useControllableValue(
props.emitterOptions,
props.defaultEmitterOptions ?? {},
props.onEmitterOptionsChange
const [compilerOptions, onCompilerOptionsChange] = useControllableValue(
props.compilerOptions,
props.defaultCompilerOptions ?? {},
props.onCompilerOptionsChange
);
const [selectedSampleName, onSelectedSampleNameChange] = useControllableValue(
props.sampleName,
Expand All @@ -107,7 +109,7 @@ export const Playground: FunctionComponent<PlaygroundProps> = (props) => {
setContent(content);
const typespecCompiler = await importTypeSpecCompiler();

const state = await compile(host, content, selectedEmitter, emitterOptions);
const state = await compile(host, content, selectedEmitter, compilerOptions);
setCompilationState(state);
if ("program" in state) {
const markers: editor.IMarkerData[] = state.program.diagnostics.map((diag) => ({
Expand All @@ -121,7 +123,7 @@ export const Playground: FunctionComponent<PlaygroundProps> = (props) => {
} else {
editor.setModelMarkers(typespecModel, "owner", []);
}
}, [host, selectedEmitter, emitterOptions, typespecModel, setContent]);
}, [host, selectedEmitter, compilerOptions, typespecModel, setContent]);

const updateTypeSpec = useCallback(
(value: string) => {
Expand All @@ -143,6 +145,9 @@ export const Playground: FunctionComponent<PlaygroundProps> = (props) => {
if (config.preferredEmitter) {
onSelectedEmitterChange(config.preferredEmitter);
}
if (config.compilerOptions) {
onCompilerOptionsChange(config.compilerOptions);
}
}
}
}, [updateTypeSpec, selectedSampleName]);
Expand All @@ -165,15 +170,15 @@ export const Playground: FunctionComponent<PlaygroundProps> = (props) => {
onSave({
content: typespecModel.getValue(),
emitter: selectedEmitter,
options: emitterOptions,
options: compilerOptions,
sampleName: isSampleUntouched ? selectedSampleName : undefined,
});
}
}, [
typespecModel,
onSave,
selectedEmitter,
emitterOptions,
compilerOptions,
selectedSampleName,
isSampleUntouched,
]);
Expand All @@ -193,6 +198,12 @@ export const Playground: FunctionComponent<PlaygroundProps> = (props) => {
[saveCode]
);

const libraries = useAsyncMemo(
async () => resolveLibraries(props.libraries),
[],
[props.libraries]
);

return (
<div
css={{
Expand All @@ -208,11 +219,11 @@ export const Playground: FunctionComponent<PlaygroundProps> = (props) => {
>
<div css={{ gridArea: "typespeceditor", width: "100%", height: "100%", overflow: "hidden" }}>
<EditorCommandBar
emitters={props.emitters}
libraries={libraries}
selectedEmitter={selectedEmitter}
onSelectedEmitterChange={onSelectedEmitterChange}
emitterOptions={emitterOptions}
onEmitterOptionsChange={onEmitterOptionsChange}
compilerOptions={compilerOptions}
onCompilerOptionsChange={onCompilerOptionsChange}
samples={props.samples}
selectedSampleName={selectedSampleName}
onSelectedSampleNameChange={onSelectedSampleNameChange}
Expand Down Expand Up @@ -247,22 +258,23 @@ async function compile(
host: BrowserHost,
content: string,
selectedEmitter: string,
emittersOptions: Record<string, Record<string, unknown>>
options: CompilerOptions
): Promise<CompilationState> {
await host.writeFile("main.tsp", content);
await emptyOutputDir(host);
try {
const typespecCompiler = await importTypeSpecCompiler();
const program = await typespecCompiler.compile(host, "main.tsp", {
outputDir: "tsp-output",
emit: [selectedEmitter],
...options,
options: {
...emittersOptions,
...options.options,
[selectedEmitter]: {
...emittersOptions[selectedEmitter],
...options.options?.[selectedEmitter],
"emitter-output-dir": "tsp-output",
},
},
outputDir: "tsp-output",
emit: [selectedEmitter],
});
const outputFiles = await findOutputFiles(host);
return { program, outputFiles };
Expand Down
Loading
Loading