First pass at SDL converter

This commit is contained in:
Tim Griesser 2019-02-01 08:48:08 -05:00
parent c744568b52
commit 5008603b66
18 changed files with 695 additions and 114 deletions

View File

@ -8,7 +8,7 @@ sidebar_label: Library Authors
If you are a library author building tools for GraphQL, we expose the core types It is recommended that you have `nexus` specified as a peer dependency rather than a direct dependency of your wrapping plugin, so duplicate copies of the library are not installed.
One example of this pattern is in `nexus-contrib` where functions for creating relay-style connections are constructed:
One example of this pattern would be for creating relay-style connections:
```ts
export const UserConnectionTypes = connectionType("User");
@ -17,21 +17,9 @@ export const UserConnectionTypes = connectionType("User");
Where `connectionType` is really just a wrapper creating two `objectTypes`:
```ts
import { Types } from './nexus';
import { core } from './nexus';
const PageInfo = objectType({
type: 'PageInfo',
definition(t) {
t.boolean('hasNextPage')
t.boolean('hasPreviousPage')
}
});
interface ConnectionTypeConfig {
name: NexusGen['objectNames']
}
export function connectionType(config: ConnectionTypeConfig) {
export function connectionType(type: core.AllOutputTypes) {
const Connection = objectType({
name: `${name}Connection`,
definition(t) {
@ -48,6 +36,13 @@ export function connectionType(config: ConnectionTypeConfig) {
t.field('node', { type: name });
}
});
const PageInfo = objectType({
type: `${type}PageInfo`,
definition(t) {
t.boolean('hasNextPage')
t.boolean('hasPreviousPage')
}
});
return { Connection, Edge, PageInfo }
}
```

View File

@ -3,9 +3,6 @@ import {
GraphQLCompositeType,
GraphQLInputObjectType,
} from "graphql";
import { GenTypesShape } from "../core";
export type MaybeThunk<T> = T | (() => T);
export type BaseScalars = "String" | "Int" | "Float" | "ID" | "Boolean";

View File

@ -1,4 +1,4 @@
import { GetGen, HasGen, GetGen2 } from "../typegenTypeHelpers";
import { GetGen, GetGen2 } from "../typegenTypeHelpers";
import { AllNexusInputTypeDefs } from "./wrapping";
import { NexusTypes, withNexusSymbol } from "./_types";

View File

@ -6,14 +6,30 @@ import {
GraphQLInputObjectType,
GraphQLScalarType,
GraphQLUnionType,
GraphQLField,
GraphQLSchema,
GraphQLNamedType,
isSpecifiedScalarType,
GraphQLField,
isWrappingType,
isNonNullType,
isListType,
GraphQLOutputType,
GraphQLWrappingType,
isScalarType,
isObjectType,
} from "graphql";
import { groupTypes, GroupedTypes } from "./utils";
import { groupTypes, GroupedTypes, isInterfaceField, objValues } from "./utils";
export function convertSDL(sdl: string, commonjs: boolean = false) {
return new SDLConverter(commonjs, sdl).print();
export function convertSDL(
sdl: string,
commonjs: null | boolean = false,
json = JSON
) {
try {
return new SDLConverter(sdl, commonjs, json).print();
} catch (e) {
return `Error Parsing SDL into Schema: ${e.stack}`;
}
}
/**
@ -21,11 +37,16 @@ export function convertSDL(sdl: string, commonjs: boolean = false) {
*/
export class SDLConverter {
protected export: string;
protected schema: GraphQLSchema;
protected schema: GraphQLSchema | null;
protected groupedTypes: GroupedTypes;
constructor(commonjs: boolean = false, sdl: string) {
this.export = commonjs ? "exports." : "export const ";
constructor(
sdl: string,
commonjs: null | boolean = false,
protected json: JSON
) {
this.export =
commonjs === null ? "const " : commonjs ? "exports." : "export const ";
this.schema = buildSchema(sdl);
this.groupedTypes = groupTypes(this.schema);
}
@ -38,7 +59,9 @@ export class SDLConverter {
this.printUnionTypes(),
this.printEnumTypes(),
this.printScalarTypes(),
].join("\n\n");
]
.filter((f) => f)
.join("\n\n");
}
printObjectTypes() {
@ -51,27 +74,98 @@ export class SDLConverter {
}
printObjectType(type: GraphQLObjectType): string {
const implementing = type.getInterfaces().map((i) => i.name);
const implementsInterfaces =
implementing.length > 0
? ` t.implements(${implementing
.map((i) => this.json.stringify(i))
.join(", ")})`
: "";
return this.printBlock([
`${this.export}${type.name} = objectType({`,
` name: "${type.name}"`,
` name: "${type.name}",`,
` definition(t) {`,
implementsInterfaces,
this.printObjectFields(type),
` }`,
`})`,
]);
// if (type.getInterfaces().length > 0) {
// const interfaceNames = type
// .getInterfaces()
// .map((i) => JSON.stringify(i.name))
// .join(", ");
// str.push(` t.implements(${interfaceNames})`);
// }
// Object.keys(type.getFields()).forEach((fieldName) => {
// if (isInterfaceField(type, fieldName)) {
// return;
// }
// eachObj(type.getFields(), (field, key) => {
// getFieldType(field);
// });
// });
// return str.join("\n");
}
printObjectFields(type: GraphQLObjectType | GraphQLInterfaceType) {
return objValues(type.getFields())
.map((field) => {
if (isObjectType(type) && isInterfaceField(type, field.name)) {
return;
}
return this.printField("output", field);
})
.filter((f) => f)
.join("\n");
}
printField(source: "input" | "output", field: GraphQLField<any, any>) {
let fieldType = field.type;
let isNonNull = false;
const list = [];
while (isWrappingType(fieldType)) {
while (isListType(fieldType)) {
fieldType = fieldType.ofType;
if (isNonNullType(fieldType)) {
fieldType = fieldType.ofType;
list.unshift(true);
} else {
list.unshift(false);
}
}
if (isNonNullType(fieldType)) {
isNonNull = true;
fieldType = fieldType.ofType;
}
}
const prefix = list.length === 1 ? `t.list.` : `t.`;
return ` ${prefix}${this.printFieldMethod(
source,
field,
fieldType,
list,
isNonNull
)}`;
}
printFieldMethod(
source: "input" | "output",
field: GraphQLField<any, any>,
type: Exclude<GraphQLOutputType, GraphQLWrappingType>,
list: boolean[],
isNonNull: boolean
) {
const objectMeta: Record<string, any> = {};
if (field.description) {
objectMeta.description = field.description;
}
if (field.deprecationReason) {
objectMeta.deprecation = field.deprecationReason;
}
if (list.length > 1) {
objectMeta.list = list;
}
if (!isNonNull && source === "output") {
objectMeta.nullable = true;
} else if (isNonNull && source === "input") {
objectMeta.required = true;
}
let str = "";
if (isCommonScalar(type)) {
str += `${type.name.toLowerCase()}("${field.name}"`;
} else {
objectMeta.type = type;
str += `field("${field.name}"`;
}
if (Object.keys(objectMeta).length > 0) {
str += `, ${this.json.stringify(objectMeta)}`;
}
return `${str})`;
}
printInterfaceTypes() {
@ -89,13 +183,10 @@ export class SDLConverter {
` name: "${type.name}",`,
this.maybeDescription(type),
` definition(t) {`,
this.printObjectFields(type),
` }`,
`});`,
]);
// eachObj(type.getFields(), (field, key) => {
// getFieldType(field);
// });
// return str.join("\n");
}
printEnumTypes() {
@ -108,19 +199,27 @@ export class SDLConverter {
}
printEnumType(type: GraphQLEnumType): string {
const members = type.getValues().map((val) => {
const { description, name, deprecationReason, value } = val;
if (!description && !deprecationReason && name === value) {
return val.name;
}
return { description, name, deprecated: deprecationReason, value };
});
return this.printBlock([
`${this.export}${type.name} = enumType({`,
` name: "${type.name}",`,
this.maybeDescription(type),
` definition(t) {`,
` }`,
` members: ${this.json.stringify(members)},`,
`});`,
]);
}
printInputObjectTypes() {
if (this.groupedTypes.input.length) {
return this.groupedTypes.input.map((t) => this.printInputObjectType(t));
return this.groupedTypes.input
.map((t) => this.printInputObjectType(t))
.join("\n");
}
return "";
}
@ -159,6 +258,7 @@ export class SDLConverter {
printScalarTypes() {
if (this.groupedTypes.scalar.length) {
return this.groupedTypes.scalar
.filter((s) => !isSpecifiedScalarType(s))
.map((t) => this.printScalarType(t))
.join("\n");
}
@ -168,14 +268,23 @@ export class SDLConverter {
printScalarType(type: GraphQLScalarType): string {
return this.printBlock([
`${this.export}${type.name} = scalarType({`,
` name: ${type.name}",`,
` name: "${type.name}",`,
this.maybeDescription(type),
` definition(t) {`,
` }`,
this.maybeAsNexusType(type),
` serialize() { /* Todo */ },`,
` parseValue() { /* Todo */ },`,
` parseLiteral() { /* Todo */ }`,
`});`,
]);
}
maybeAsNexusType(type: GraphQLScalarType) {
if (isCommonScalar(type)) {
return ` asNexusMethod: "${type.name.toLowerCase()}",`;
}
return null;
}
maybeDescription(type: GraphQLNamedType) {
if (type.description) {
return ` description: ${JSON.stringify(type.description)},`;
@ -184,14 +293,17 @@ export class SDLConverter {
}
printBlock(block: (string | null)[]) {
return block.filter((t) => t !== null).join("\n");
return block.filter((t) => t !== null && t !== "").join("\n");
}
}
const getFieldType = (type: GraphQLField<any, any>) => {
//
};
const getInputFieldType = (type: GraphQLField<any, any>) => {
//
};
function isCommonScalar(field: GraphQLOutputType): boolean {
if (isScalarType(field)) {
return (
isSpecifiedScalarType(field) ||
field.name === "UUID" ||
field.name === "Date"
);
}
return false;
}

View File

@ -27,6 +27,8 @@ export type MaybePromiseDeep<T> = MaybePromise<
? Array<MaybePromiseDeep<U>>
: T[P] extends ReadonlyArray<infer Y>
? ReadonlyArray<MaybePromiseDeep<Y>>
: Date extends T[P]
? MaybePromise<T[P]>
: MaybePromiseDeep<T[P]>
}
>;

View File

@ -1,12 +1,161 @@
import { convertSDL, core } from "..";
import { EXAMPLE_SDL } from "./_sdl";
const { SDLConverter } = core;
describe("SDLConverter", () => {
test("printObjectTypes", () => {});
test("printObjectTypes", () => {
expect(new SDLConverter(EXAMPLE_SDL).printObjectTypes())
.toMatchInlineSnapshot(`
"export const Mutation = objectType({
name: \\"Mutation\\",
definition(t) {
t.field(\\"createPost\\", {\\"type\\":\\"Post\\"})
t.field(\\"registerClick\\", {\\"type\\":\\"Query\\"})
}
})
export const Post = objectType({
name: \\"Post\\",
definition(t) {
t.implements(\\"Node\\")
t.uuid(\\"uuid\\")
t.field(\\"author\\", {\\"type\\":\\"User\\"})
t.float(\\"geo\\", {\\"list\\":[true,true]})
t.float(\\"messyGeo\\", {\\"list\\":[true,false],\\"nullable\\":true})
}
})
export const Query = objectType({
name: \\"Query\\",
definition(t) {
t.field(\\"user\\", {\\"type\\":\\"User\\"})
t.list.field(\\"posts\\", {\\"type\\":\\"Post\\"})
}
})
export const User = objectType({
name: \\"User\\",
definition(t) {
t.implements(\\"Node\\")
t.string(\\"name\\")
t.string(\\"email\\")
t.string(\\"phone\\", {\\"nullable\\":true})
t.list.field(\\"posts\\", {\\"type\\":\\"Post\\"})
t.field(\\"outEnum\\", {\\"nullable\\":true,\\"type\\":\\"SomeEnum\\"})
}
})"
`);
});
test("printEnumTypes", () => {});
test("printScalarTypes", () => {});
test("printInterfaceTypes", () => {});
test("printEnumTypes", () => {
expect(new SDLConverter(EXAMPLE_SDL).printEnumTypes())
.toMatchInlineSnapshot(`
"export const OrderEnum = enumType({
name: \\"OrderEnum\\",
members: [\\"ASC\\",\\"DESC\\"],
});
export const SomeEnum = enumType({
name: \\"SomeEnum\\",
members: [\\"A\\",\\"B\\"],
});"
`);
});
test("printScalarTypes", () => {
expect(new SDLConverter(EXAMPLE_SDL).printScalarTypes())
.toMatchInlineSnapshot(`
"export const UUID = scalarType({
name: \\"UUID\\",
asNexusMethod: \\"uuid\\",
serialize() { /* Todo */ },
parseValue() { /* Todo */ },
parseLiteral() { /* Todo */ }
});"
`);
});
test("printInterfaceTypes", () => {
expect(new SDLConverter(EXAMPLE_SDL).printInterfaceTypes())
.toMatchInlineSnapshot(`
"export const Node = interfaceType({
name: \\"Node\\",
definition(t) {
t.id(\\"id\\")
}
});"
`);
});
});
test("convertSDL", () => {
expect(convertSDL(EXAMPLE_SDL)).toMatchInlineSnapshot(`
"export const Mutation = objectType({
name: \\"Mutation\\",
definition(t) {
t.field(\\"createPost\\", {\\"type\\":\\"Post\\"})
t.field(\\"registerClick\\", {\\"type\\":\\"Query\\"})
}
})
export const Post = objectType({
name: \\"Post\\",
definition(t) {
t.implements(\\"Node\\")
t.uuid(\\"uuid\\")
t.field(\\"author\\", {\\"type\\":\\"User\\"})
t.float(\\"geo\\", {\\"list\\":[true,true]})
t.float(\\"messyGeo\\", {\\"list\\":[true,false],\\"nullable\\":true})
}
})
export const Query = objectType({
name: \\"Query\\",
definition(t) {
t.field(\\"user\\", {\\"type\\":\\"User\\"})
t.list.field(\\"posts\\", {\\"type\\":\\"Post\\"})
}
})
export const User = objectType({
name: \\"User\\",
definition(t) {
t.implements(\\"Node\\")
t.string(\\"name\\")
t.string(\\"email\\")
t.string(\\"phone\\", {\\"nullable\\":true})
t.list.field(\\"posts\\", {\\"type\\":\\"Post\\"})
t.field(\\"outEnum\\", {\\"nullable\\":true,\\"type\\":\\"SomeEnum\\"})
}
})
export const Node = interfaceType({
name: \\"Node\\",
definition(t) {
t.id(\\"id\\")
}
});
export const CreatePostInput = inputObjectType({
name: \\"CreatePostInput\\",
definition(t) {
}
});
export const PostFilters = inputObjectType({
name: \\"PostFilters\\",
definition(t) {
}
});
export const OrderEnum = enumType({
name: \\"OrderEnum\\",
members: [\\"ASC\\",\\"DESC\\"],
});
export const SomeEnum = enumType({
name: \\"SomeEnum\\",
members: [\\"A\\",\\"B\\"],
});
export const UUID = scalarType({
name: \\"UUID\\",
asNexusMethod: \\"uuid\\",
serialize() { /* Todo */ },
parseValue() { /* Todo */ },
parseLiteral() { /* Todo */ }
});"
`);
});

View File

@ -8,6 +8,7 @@
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noUnusedLocals": true,
"skipLibCheck": true,
"declaration": true,
"importHelpers": true,

View File

@ -77,6 +77,7 @@
"links": {
"Getting Started": "Getting Started",
"Playground": "Playground",
"SDL Converter": "SDL Converter",
"Examples": "Examples",
"GitHub": "GitHub"
},

View File

@ -26,6 +26,7 @@
"@babel/preset-env": "^7.1.5",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.1.0",
"@types/json5": "^0.0.30",
"@types/dedent": "^0.7.0",
"@types/fs-extra": "^5.0.4",
"@types/get-port": "^4.0.0",
@ -53,6 +54,7 @@
"typescript": "3.1.6",
"use-debounce": "^0.0.7",
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2"
"webpack-cli": "^3.1.2",
"json5": "^2.1.0"
}
}

View File

@ -0,0 +1,125 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no"
/>
<title>Nexus SDL Converter</title>
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<meta name="msapplication-starturl" content="/" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<link rel="icon" href="img/favicon.png" />
<meta name="twitter:image" content="https://nexus.js.org/img/nexus.png" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.development.js"></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.12.0/graphiql.css"
/>
<link rel="stylesheet" href="css/main.css" />
<link rel="stylesheet" href="separate-css/playground.css" />
</head>
<body class="sideNavVisible separateOnPageNav">
<div class="fixedHeaderContainer">
<div class="headerWrapper wrapper">
<header>
<a href="index.html"
><img class="logo" src="img/nexus.png" alt="Nexus GraphQL" />
<h2 class="headerTitleWithLogo">Nexus GraphQL</h2></a
>
<div class="navigationWrapper navigationSlider">
<nav class="slidingNav">
<ul class="nav-site nav-site-internal">
<li>
<a href="docs/getting-started" target="_self"
>Getting Started</a
>
</li>
<li>
<a href="playground" target="_self">Playground</a>
</li>
<li class="siteNavGroupActive siteNavItemActive">
<a href="converter" target="_self">SDL Converter</a>
</li>
<li>
<a
href="https://github.com/graphql-nexus/nexus/tree/develop/examples"
>Examples</a
>
</li>
<li>
<a
href="https://github.com/graphql-nexus/nexus"
target="_self"
>GitHub</a
>
</li>
<li class="navSearchWrapper reactNavSearchWrapper">
<input
type="text"
id="search_input_react"
placeholder="Search"
title="Search"
/>
</li>
</ul>
</nav>
</div>
</header>
</div>
</div>
<div id="root" class="playground-container"></div>
<script src="playground-dist/converter.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script
async
src="https://www.googletagmanager.com/gtag/js?id=UA-39763513-4"
></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "UA-39763513-4");
</script>
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js"
></script>
<script>
document.addEventListener("keyup", function(e) {
if (e.target !== document.body) {
return;
}
// keyCode for '/' (slash)
if (e.keyCode === 191) {
const search = document.getElementById("search_input_react");
search && search.focus();
}
});
</script>
<script>
var search = docsearch({
apiKey: "80f72460aa3d84abf56948140313b079",
indexName: "graphql-nexus",
inputSelector: "#search_input_react",
algoliaOptions: {},
});
</script>
</body>
</html>

View File

@ -30,8 +30,8 @@
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.12.0/graphiql.css"
/>
<link rel="stylesheet" href="separate-css/playground.css" />
<link rel="stylesheet" href="css/main.css" />
<link rel="stylesheet" href="separate-css/playground.css" />
</head>
<body class="sideNavVisible separateOnPageNav">
<div class="fixedHeaderContainer">
@ -52,6 +52,9 @@
<li class="siteNavGroupActive siteNavItemActive">
<a href="playground" target="_self">Playground</a>
</li>
<li>
<a href="converter" target="_self">SDL Converter</a>
</li>
<li>
<a
href="https://github.com/graphql-nexus/nexus/tree/develop/examples"

View File

@ -0,0 +1,87 @@
import React, { useEffect, useRef, useState } from "react";
import * as monaco from "monaco-editor";
import ReactDOM from "react-dom";
import debounce from "lodash.debounce";
import { convertSDL } from "nexus";
import json5 from "json5";
import { EXAMPLE_SDL } from "../../tests/_sdl";
import "./monaco-graphql";
const COMMON_CONFIG: monaco.editor.IEditorConstructionOptions = {
minimap: {
enabled: false,
},
scrollBeyondLastLine: false,
lineNumbersMinChars: 3,
};
const COMMON_READONLY_CONFIG: monaco.editor.IEditorConstructionOptions = {
...COMMON_CONFIG,
readOnly: true,
contextmenu: false,
renderLineHighlight: "none",
};
function monacoRef() {
return useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
}
export function Converter() {
const [content, setContent] = useState(
[`## WIP, not all types supported yet!!`, `# Paste your SDL here:`]
.concat(EXAMPLE_SDL)
.join("\n")
);
const [sdlDiv, outputDiv] = [
useRef<null | HTMLDivElement>(null),
useRef<null | HTMLDivElement>(null),
];
const [sdlEditorRef, outputEditorRef] = [monacoRef(), monacoRef()];
useEffect(() => {
if (sdlDiv.current && outputDiv.current) {
const sdlEditor = monaco.editor.create(sdlDiv.current, {
language: "graphql",
model: monaco.editor.createModel(
content,
"graphql",
monaco.Uri.file("sdl.graphql")
),
...COMMON_CONFIG,
});
const outputEditor = monaco.editor.create(outputDiv.current, {
language: "typescript",
...COMMON_READONLY_CONFIG,
});
sdlEditorRef.current = sdlEditor;
outputEditorRef.current = outputEditor;
const debouncedChange = debounce(() => {
setContent(sdlEditor.getValue());
}, 100);
sdlEditor.onDidChangeModelContent(debouncedChange as any);
return () => sdlEditor.dispose();
}
}, []);
useEffect(() => {
const converted = convertSDL(content, null, json5 as JSON);
if (outputEditorRef.current) {
outputEditorRef.current.setValue(converted);
}
}, [content]);
return (
<div className="editors-container">
<div className="editors">
<div
style={{ flexBasis: "50%", height: "100%", flexDirection: "column" }}
>
<div ref={sdlDiv} style={{ height: "100%" }} />
</div>
<div
style={{ flexBasis: "50%", height: "100%", flexDirection: "column" }}
>
<div ref={outputDiv} style={{ height: "100%" }} />
</div>
</div>
</div>
);
}
ReactDOM.render(<Converter />, document.getElementById("root"));

View File

@ -282,6 +282,7 @@ function getCurrentSchema(code: string): SchemaOrError {
return val;
}
const singleton = {
core,
objectType(obj: any) {
return add(objectType(obj));
},
@ -303,6 +304,7 @@ function getCurrentSchema(code: string): SchemaOrError {
};
try {
const fn = new Function(
"core",
"objectType",
"interfaceType",
"inputObjectType",
@ -321,6 +323,7 @@ function getCurrentSchema(code: string): SchemaOrError {
`
);
fn(
singleton.core,
singleton.objectType,
singleton.interfaceType,
singleton.inputObjectType,

View File

@ -1,4 +1,5 @@
import * as monaco from "monaco-editor";
import "./monaco-graphql";
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2016,
@ -93,6 +94,7 @@ import * as nexus from 'nexus'
// Re-export these so we can use globally in the sandbox
// while still preserving the typegen
declare global {
declare const core: typeof nexus.core;
declare const arg: typeof nexus.arg;
declare const intArg: typeof nexus.intArg;
declare const stringArg: typeof nexus.stringArg;
@ -110,47 +112,3 @@ declare global {
`,
"file:///sandbox-globals.ts"
);
monaco.languages.register({ id: "graphql" });
// https://code.visualstudio.com/blogs/2017/02/08/syntax-highlighting-optimizations
monaco.languages.setMonarchTokensProvider("graphql", {
ignoreCase: true,
// @ts-ignore
keywords: [
"type",
"input",
"scalar",
"enum",
"union",
"implements",
"interface",
"directive",
],
tokenizer: {
root: [
{ include: "@whitespace" },
[
/[a-z_$][\w$]*/,
{
cases: {
"@keywords": "keyword",
"@default": "identifier",
},
},
],
],
whitespace: [
[/\s+/, "white"],
[/(^#.*$)/, "comment"],
[/(""".*""")/, "string"],
[/""".*$/, "string", "@endDblDocString"],
],
endDocString: [[/.*$/, "string"]],
endDblDocString: [
[/\\"/, "string"],
[/.*"""/, "string", "@popall"],
[/.*$/, "string"],
],
},
});

View File

@ -0,0 +1,132 @@
import * as monaco from "monaco-editor";
monaco.languages.register({ id: "graphql" });
// https://code.visualstudio.com/blogs/2017/02/08/syntax-highlighting-optimizations
monaco.languages.setMonarchTokensProvider("graphql", {
// Set defaultToken to invalid to see what you do not tokenize yet
defaultToken: "invalid",
tokenPostfix: ".gql",
keywords: [
"null",
"true",
"false",
"query",
"mutation",
"subscription",
"extend",
"schema",
"directive",
"scalar",
"type",
"interface",
"union",
"enum",
"input",
"implements",
"fragment",
"on",
],
typeKeywords: ["Int", "Float", "String", "Boolean", "ID"],
directiveLocations: [
"SCHEMA",
"SCALAR",
"OBJECT",
"FIELD_DEFINITION",
"ARGUMENT_DEFINITION",
"INTERFACE",
"UNION",
"ENUM",
"ENUM_VALUE",
"INPUT_OBJECT",
"INPUT_FIELD_DEFINITION",
"QUERY",
"MUTATION",
"SUBSCRIPTION",
"FIELD",
"FRAGMENT_DEFINITION",
"FRAGMENT_SPREAD",
"INLINE_FRAGMENT",
"VARIABLE_DEFINITION",
],
operators: ["=", "!", "?", ":", "&", "|"],
// we include these common regular expressions
symbols: /[=!?:&|]+/,
// https://facebook.github.io/graphql/draft/#sec-String-Value
escapes: /\\(?:["\\\/bfnrt]|u[0-9A-Fa-f]{4})/,
// The main tokenizer for our languages
tokenizer: {
root: [
// identifiers and keywords
[
/[a-z_$][\w$]*/,
{
cases: {
"@keywords": "keyword",
"@default": "identifier",
},
},
],
[
/[A-Z][\w\$]*/,
{
cases: {
"@typeKeywords": "keyword",
"@default": "type.identifier",
},
},
], // to show class names nicely
// whitespace
{ include: "@whitespace" },
// delimiters and operators
[/[{}()\[\]]/, "@brackets"],
[/@symbols/, { cases: { "@operators": "operator", "@default": "" } }],
// @ annotations.
// As an example, we emit a debugging log message on these tokens.
// Note: message are supressed during the first load -- change some lines to see them.
[
/@\s*[a-zA-Z_\$][\w\$]*/,
{ token: "annotation", log: "annotation token: $0" },
],
// numbers
[/\d*\.\d+([eE][\-+]?\d+)?/, "number.float"],
[/0[xX][0-9a-fA-F]+/, "number.hex"],
[/\d+/, "number"],
// delimiter: after number because of .\d floats
[/[;,.]/, "delimiter"],
[/"""/, { token: "string", next: "@mlstring", nextEmbedded: "markdown" }],
// strings
[/"([^"\\]|\\.)*$/, "string.invalid"], // non-teminated string
[/"/, { token: "string.quote", bracket: "@open", next: "@string" }],
],
mlstring: [
[/[^"]+/, "string"],
// @ts-ignore
['"""', { token: "string", next: "@pop", nextEmbedded: "@pop" }],
],
string: [
[/[^\\"]+/, "string"],
[/@escapes/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/"/, { token: "string.quote", bracket: "@close", next: "@pop" }],
],
whitespace: [[/[ \t\r\n]+/, ""], [/#.*$/, "comment"]],
},
});

View File

@ -31,6 +31,7 @@ const siteConfig = {
headerLinks: [
{ doc: "getting-started", label: "Getting Started" },
{ page: "playground", label: "Playground" },
{ page: "converter", label: "SDL Converter" },
{
href: "https://github.com/graphql-nexus/nexus/tree/develop/examples",
label: "Examples",

View File

@ -12,6 +12,7 @@ module.exports = {
devtool: "source-map",
entry: {
playground: "./playground/index.tsx",
converter: "./playground/Converter.tsx",
},
output: {
publicPath: "/playground-dist/",

View File

@ -793,6 +793,11 @@
"@types/orchestrator" "*"
"@types/vinyl" "*"
"@types/json5@^0.0.30":
version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.30.tgz#44cb52f32a809734ca562e685c6473b5754a7818"
integrity sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==
"@types/lodash.debounce@^4.0.4":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.4.tgz#a3d082628ef1bb1964c2b6a2d4f45acae7209e7a"
@ -4394,6 +4399,13 @@ json5@^0.5.0:
version "0.5.1"
resolved "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
json5@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850"
integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==
dependencies:
minimist "^1.2.0"
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"