From e45ba17cb2f13e5a79d3e87b0f30ef92ec55d861 Mon Sep 17 00:00:00 2001 From: Samuel Date: Mon, 20 Nov 2023 13:28:59 +0100 Subject: [PATCH] Create a lint diagnostic from invalid schema (#3463) * Create a lint diagnostic from invalid schema * Create two-doors-shave.md * add config option to show error on invalid schema --- .changeset/two-doors-shave.md | 5 ++ packages/cm6-graphql/src/interfaces.ts | 1 + packages/cm6-graphql/src/lint.ts | 99 ++++++++++++++++---------- packages/cm6-graphql/src/state.ts | 9 ++- 4 files changed, 75 insertions(+), 39 deletions(-) create mode 100644 .changeset/two-doors-shave.md diff --git a/.changeset/two-doors-shave.md b/.changeset/two-doors-shave.md new file mode 100644 index 00000000000..e4b8a6f315b --- /dev/null +++ b/.changeset/two-doors-shave.md @@ -0,0 +1,5 @@ +--- +"cm6-graphql": patch +--- + +Create a lint diagnostic from invalid schema diff --git a/packages/cm6-graphql/src/interfaces.ts b/packages/cm6-graphql/src/interfaces.ts index 0bf59cf6936..f4128928dba 100644 --- a/packages/cm6-graphql/src/interfaces.ts +++ b/packages/cm6-graphql/src/interfaces.ts @@ -4,6 +4,7 @@ import { GraphQLSchema } from 'graphql'; import { ContextToken, CompletionItem } from 'graphql-language-service'; import { Position } from './helpers'; export interface GqlExtensionsOptions { + showErrorOnInvalidSchema?: boolean; onShowInDocs?: (field?: string, type?: string, parentType?: string) => void; onFillAllFields?: ( view: EditorView, diff --git a/packages/cm6-graphql/src/lint.ts b/packages/cm6-graphql/src/lint.ts index 22cc1a06c35..e7bfbd0aa2e 100644 --- a/packages/cm6-graphql/src/lint.ts +++ b/packages/cm6-graphql/src/lint.ts @@ -1,55 +1,78 @@ import { Diagnostic, linter } from '@codemirror/lint'; import { getDiagnostics } from 'graphql-language-service'; import { Position, posToOffset } from './helpers'; -import { getSchema, optionsStateField, schemaStateField } from './state'; +import { + getOpts, + getSchema, + optionsStateField, + schemaStateField, +} from './state'; import { Extension } from '@codemirror/state'; +import { validateSchema } from 'graphql'; const SEVERITY = ['error', 'warning', 'info'] as const; export const lint: Extension = linter( view => { - try { - const schema = getSchema(view.state); - if (!schema) { + const schema = getSchema(view.state); + const options = getOpts(view.state); + if (!schema) { + return []; + } + const validationErrors = validateSchema(schema); + if (validationErrors.length) { + if (!options?.showErrorOnInvalidSchema) { return []; } - const results = getDiagnostics(view.state.doc.toString(), schema); - return results - .map((item): Diagnostic | null => { - if (!item.severity || !item.source) { - return null; - } + const combinedError = validationErrors.map(error => { + return error.message; + }); - const calculatedFrom = posToOffset( - view.state.doc, - new Position(item.range.start.line, item.range.start.character), - ); - const from = Math.max( - 0, - Math.min(calculatedFrom, view.state.doc.length), - ); - const calculatedRo = posToOffset( - view.state.doc, - new Position(item.range.end.line, item.range.end.character - 1), - ); - const to = Math.min( - Math.max(from + 1, calculatedRo), - view.state.doc.length, - ); - return { - from, - to: from === to ? to + 1 : to, - severity: SEVERITY[item.severity - 1], - // source: item.source, // TODO: - message: item.message, - actions: [], // TODO: - }; - }) - .filter((_): _ is Diagnostic => Boolean(_)); - } catch { - return []; + return [ + { + from: 0, + to: view.state.doc.length, + severity: 'error', + message: combinedError.join('\n'), + actions: [], // TODO: + }, + ]; } + const results = getDiagnostics(view.state.doc.toString(), schema); + + return results + .map((item): Diagnostic | null => { + if (!item.severity || !item.source) { + return null; + } + + const calculatedFrom = posToOffset( + view.state.doc, + new Position(item.range.start.line, item.range.start.character), + ); + const from = Math.max( + 0, + Math.min(calculatedFrom, view.state.doc.length), + ); + const calculatedRo = posToOffset( + view.state.doc, + new Position(item.range.end.line, item.range.end.character - 1), + ); + const to = Math.min( + Math.max(from + 1, calculatedRo), + view.state.doc.length, + ); + return { + from, + to: from === to ? to + 1 : to, + severity: SEVERITY[item.severity - 1], + // source: item.source, // TODO: + message: item.message, + actions: [], // TODO: + }; + }) + .filter((_): _ is Diagnostic => Boolean(_)); }, { needsRefresh(vu) { diff --git a/packages/cm6-graphql/src/state.ts b/packages/cm6-graphql/src/state.ts index b7aef3f3946..1fc811a21ac 100644 --- a/packages/cm6-graphql/src/state.ts +++ b/packages/cm6-graphql/src/state.ts @@ -49,7 +49,14 @@ export const getOpts = (state: EditorState) => { return state.field(optionsStateField); }; +const defaultOpts: GqlExtensionsOptions = { + showErrorOnInvalidSchema: true, +}; + export const stateExtensions = ( schema?: GraphQLSchema, opts?: GqlExtensionsOptions, -) => [schemaStateField.init(() => schema), optionsStateField.init(() => opts)]; +) => [ + schemaStateField.init(() => schema), + optionsStateField.init(() => ({ ...defaultOpts, ...opts })), +];