Skip to content

Commit

Permalink
feat: Monaco graphql Completion (#1428)
Browse files Browse the repository at this point in the history
  • Loading branch information
rebornix authored Mar 15, 2020
1 parent e0291d3 commit 70e4874
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 535 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
import { Kind, parse, print } from 'graphql';
import { getAutocompleteSuggestions } from './getAutocompleteSuggestions';
import { getHoverInformation } from './getHoverInformation';
import { validateQuery, getRange, SEVERITY } from './getDiagnostics';
import { validateQuery, getRange, DIAGNOSTIC_SEVERITY } from './getDiagnostics';
import {
getDefinitionQueryResultForFragmentSpread,
getDefinitionQueryResultForDefinitionNode,
Expand Down Expand Up @@ -155,7 +155,7 @@ export class GraphQLLanguageService {
const range = getRange(error.locations[0], query);
return [
{
severity: SEVERITY.ERROR,
severity: DIAGNOSTIC_SEVERITY.Error,
message: error.message,
source: 'GraphQL: Syntax',
range,
Expand All @@ -175,7 +175,7 @@ export class GraphQLLanguageService {
);

const dependenciesSource = fragmentDependencies.reduce(
(prev, cur) => `${prev} ${print(cur.definition)}`,
(prev: any, cur: any) => `${prev} ${print(cur.definition)}`,
'',
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
GraphQLType,
GraphQLCompositeType,
GraphQLEnumValue,
Kind,
} from 'graphql';

import {
Expand Down Expand Up @@ -52,6 +53,34 @@ import {
objectValues,
} from './autocompleteUtils';

// TODO@acao,rebornix
// Convert AST token kind to Monaco CompletionItemKind
// Should we take `step` into account similar to how we resolve completion items?
function toCompletionItemKind(
kind: string,
): monaco.languages.CompletionItemKind {
const mItemKind = monaco.languages.CompletionItemKind;

switch (kind) {
case 'Document':
case 'SelectionSet':
case 'Field':
case 'AliasedField':
case 'Arguments':
case 'ObjectValue':
case 'ObjectField':
case 'EnumValue':
case 'ListValue':
case 'ListType':
case 'TypeCondition':
case 'NamedType':
case 'FragmentSpread':
case 'VariableDefinition':
case 'Directive':
return mItemKind.Method;
}
}

/**
* Given GraphQLSchema, queryText, and context of the current position within
* the source text, provide a list of typeahead entries.
Expand Down Expand Up @@ -79,17 +108,17 @@ export function getAutocompleteSuggestions(
// Definition kinds
if (kind === 'Document') {
return hintList(token, [
{ label: 'query' },
{ label: 'mutation' },
{ label: 'subscription' },
{ label: 'fragment' },
{ label: '{' },
{ label: 'query', kind: toCompletionItemKind(kind) },
{ label: 'mutation', kind: toCompletionItemKind(kind) },
{ label: 'subscription', kind: toCompletionItemKind(kind) },
{ label: 'fragment', kind: toCompletionItemKind(kind) },
{ label: '{', kind: toCompletionItemKind(kind) },
]);
}

// Field names
if (kind === 'SelectionSet' || kind === 'Field' || kind === 'AliasedField') {
return getSuggestionsForFieldNames(token, typeInfo, schema);
return getSuggestionsForFieldNames(token, typeInfo, schema, kind);
}

// Argument names
Expand All @@ -102,6 +131,7 @@ export function getAutocompleteSuggestions(
label: argDef.name,
detail: String(argDef.type),
documentation: argDef.description,
kind: toCompletionItemKind(kind),
})),
);
}
Expand All @@ -117,6 +147,7 @@ export function getAutocompleteSuggestions(
label: field.name,
detail: String(field.type),
documentation: field.description,
kind: toCompletionItemKind(kind),
})),
);
}
Expand All @@ -129,7 +160,7 @@ export function getAutocompleteSuggestions(
(kind === 'ObjectField' && step === 2) ||
(kind === 'Argument' && step === 2)
) {
return getSuggestionsForInputValues(token, typeInfo);
return getSuggestionsForInputValues(token, typeInfo, kind);
}

// Fragment type conditions
Expand All @@ -139,12 +170,23 @@ export function getAutocompleteSuggestions(
state.prevState != null &&
state.prevState.kind === 'TypeCondition')
) {
return getSuggestionsForFragmentTypeConditions(token, typeInfo, schema);
return getSuggestionsForFragmentTypeConditions(
token,
typeInfo,
schema,
kind,
);
}

// Fragment spread names
if (kind === 'FragmentSpread' && step === 1) {
return getSuggestionsForFragmentSpread(token, typeInfo, schema, queryText);
return getSuggestionsForFragmentSpread(
token,
typeInfo,
schema,
queryText,
kind,
);
}

// Variable definition types
Expand All @@ -156,12 +198,12 @@ export function getAutocompleteSuggestions(
(state.prevState.kind === 'VariableDefinition' ||
state.prevState.kind === 'ListType'))
) {
return getSuggestionsForVariableDefinition(token, schema);
return getSuggestionsForVariableDefinition(token, schema, kind);
}

// Directive names
if (kind === 'Directive') {
return getSuggestionsForDirective(token, state, schema);
return getSuggestionsForDirective(token, state, schema, kind);
}

return [];
Expand All @@ -172,6 +214,7 @@ function getSuggestionsForFieldNames(
token: ContextToken,
typeInfo: AllTypeInfo,
schema: GraphQLSchema,
kind: string,
): Array<CompletionItem> {
if (typeInfo.parentType) {
const parentType = typeInfo.parentType;
Expand All @@ -194,6 +237,7 @@ function getSuggestionsForFieldNames(
deprecated: field.isDeprecated,
isDeprecated: field.isDeprecated,
deprecationReason: field.deprecationReason,
kind: toCompletionItemKind(kind),
})),
);
}
Expand All @@ -203,6 +247,7 @@ function getSuggestionsForFieldNames(
function getSuggestionsForInputValues(
token: ContextToken,
typeInfo: AllTypeInfo,
kind: string,
): CompletionItem[] {
const namedInputType = getNamedType(typeInfo.inputType as GraphQLType);
if (namedInputType instanceof GraphQLEnumType) {
Expand All @@ -217,6 +262,7 @@ function getSuggestionsForInputValues(
deprecated: value.isDeprecated,
isDeprecated: value.isDeprecated,
deprecationReason: value.deprecationReason,
kind: toCompletionItemKind(kind),
}),
),
);
Expand All @@ -226,12 +272,14 @@ function getSuggestionsForInputValues(
label: 'true',
detail: String(GraphQLBoolean),
documentation: 'Not false.',
kind: toCompletionItemKind(kind),
},

{
label: 'false',
detail: String(GraphQLBoolean),
documentation: 'Not true.',
kind: toCompletionItemKind(kind),
},
]);
}
Expand All @@ -243,6 +291,7 @@ function getSuggestionsForFragmentTypeConditions(
token: ContextToken,
typeInfo: AllTypeInfo,
schema: GraphQLSchema,
kind: string,
): Array<CompletionItem> {
let possibleTypes: GraphQLType[];
if (typeInfo.parentType) {
Expand Down Expand Up @@ -274,6 +323,7 @@ function getSuggestionsForFragmentTypeConditions(
return {
label: String(type),
documentation: (namedType && namedType.description) || '',
kind: toCompletionItemKind(kind),
};
}),
);
Expand All @@ -284,6 +334,7 @@ function getSuggestionsForFragmentSpread(
typeInfo: AllTypeInfo,
schema: GraphQLSchema,
queryText: string,
kind: string,
): Array<CompletionItem> {
const typeMap = schema.getTypeMap();
const defState = getDefinitionState(token.state);
Expand Down Expand Up @@ -316,6 +367,7 @@ function getSuggestionsForFragmentSpread(
label: frag.name.value,
detail: String(typeMap[frag.typeCondition.name.value]),
documentation: `fragment ${frag.name.value} on ${frag.typeCondition.name.value}`,
kind: toCompletionItemKind(kind),
})),
);
}
Expand Down Expand Up @@ -355,6 +407,7 @@ function getFragmentDefinitions(
function getSuggestionsForVariableDefinition(
token: ContextToken,
schema: GraphQLSchema,
kind: string,
): Array<CompletionItem> {
const inputTypeMap = schema.getTypeMap();
const inputTypes = objectValues(inputTypeMap).filter(isInputType);
Expand All @@ -364,6 +417,7 @@ function getSuggestionsForVariableDefinition(
inputTypes.map((type: any) => ({
label: type.name,
documentation: type.description,
kind: toCompletionItemKind(kind),
})),
);
}
Expand All @@ -372,6 +426,7 @@ function getSuggestionsForDirective(
token: ContextToken,
state: State,
schema: GraphQLSchema,
kind: string,
): Array<CompletionItem> {
if (state.prevState && state.prevState.kind) {
const directives = schema
Expand All @@ -382,6 +437,7 @@ function getSuggestionsForDirective(
directives.map(directive => ({
label: directive.name,
documentation: directive.description || '',
kind: toCompletionItemKind(kind),
})),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ import {
import { DiagnosticSeverity, Diagnostic } from 'vscode-languageserver-types';

export const DIAGNOSTIC_SEVERITY = {
ERROR: 1 as DiagnosticSeverity,
WARNING: 2 as DiagnosticSeverity,
INFORMATION: 3 as DiagnosticSeverity,
HINT: 4 as DiagnosticSeverity,
Error: 1 as DiagnosticSeverity,
Warning: 2 as DiagnosticSeverity,
Information: 3 as DiagnosticSeverity,
Hint: 4 as DiagnosticSeverity,
};

export function getDiagnostics(
Expand All @@ -50,7 +50,7 @@ export function getDiagnostics(
const range = getRange(error.locations[0], query);
return [
{
severity: DIAGNOSTIC_SEVERITY.ERROR as DiagnosticSeverity,
severity: DIAGNOSTIC_SEVERITY.Error as DiagnosticSeverity,
message: error.message,
source: 'GraphQL: Syntax',
range,
Expand All @@ -74,15 +74,15 @@ export function validateQuery(

const validationErrorAnnotations = mapCat(
validateWithCustomRules(schema, ast, customRules, isRelayCompatMode),
error => annotations(error, DIAGNOSTIC_SEVERITY.ERROR, 'Validation'),
error => annotations(error, DIAGNOSTIC_SEVERITY.Error, 'Validation'),
);

// Note: findDeprecatedUsages was added in graphql@0.9.0, but we want to
// support older versions of graphql-js.
const deprecationWarningAnnotations = !findDeprecatedUsages
? []
: mapCat(findDeprecatedUsages(schema, ast), error =>
annotations(error, DIAGNOSTIC_SEVERITY.WARNING, 'Deprecation'),
annotations(error, DIAGNOSTIC_SEVERITY.Warning, 'Deprecation'),
);

return validationErrorAnnotations.concat(deprecationWarningAnnotations);
Expand Down
6 changes: 5 additions & 1 deletion packages/graphql-language-service-interface/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ export {
getDefinitionQueryResultForDefinitionNode,
} from './getDefinition';

export { getDiagnostics, validateQuery } from './getDiagnostics';
export {
getDiagnostics,
validateQuery,
DIAGNOSTIC_SEVERITY as DiagnosticSeverity,
} from './getDiagnostics';
export { getOutline } from './getOutline';
export { getHoverInformation } from './getHoverInformation';

Expand Down
5 changes: 3 additions & 2 deletions packages/monaco-graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
"name": "@graphql/monaco-graphql",
"version": "0.0.1",
"dependencies": {
"monaco-editor": "0.20.0",
"monaco-editor-core": "0.20.0",
"graphql-language-service-interface": "2.4.0-alpha.1"
},
"devDependencies": {
"monaco-editor-core": "0.20.0"
}
}
39 changes: 1 addition & 38 deletions packages/monaco-graphql/src/graphqlMode.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as monaco from 'monaco-editor';
import * as monaco from 'monaco-editor-core';

import IRichLanguageConfiguration = monaco.languages.LanguageConfiguration;

Expand Down Expand Up @@ -28,22 +28,6 @@ export function setupMode(defaults: LanguageServiceDefaultsImpl): IDisposable {

disposeAll(providers);

if (modeConfiguration.documentFormattingEdits) {
providers.push(
monaco.languages.registerDocumentFormattingEditProvider(
languageId,
new languageFeatures.DocumentFormattingEditProvider(worker),
),
);
}
if (modeConfiguration.documentRangeFormattingEdits) {
providers.push(
monaco.languages.registerDocumentRangeFormattingEditProvider(
languageId,
new languageFeatures.DocumentRangeFormattingEditProvider(worker),
),
);
}
if (modeConfiguration.completionItems) {
providers.push(
monaco.languages.registerCompletionItemProvider(
Expand All @@ -52,27 +36,6 @@ export function setupMode(defaults: LanguageServiceDefaultsImpl): IDisposable {
),
);
}
if (modeConfiguration.hovers) {
providers.push(
monaco.languages.registerHoverProvider(
languageId,
new languageFeatures.HoverAdapter(worker),
),
);
}
if (modeConfiguration.documentSymbols) {
providers.push(
monaco.languages.registerDocumentSymbolProvider(
languageId,
new languageFeatures.DocumentSymbolAdapter(worker),
),
);
}
if (modeConfiguration.diagnostics) {
providers.push(
new languageFeatures.DiagnosticsAdapter(languageId, worker, defaults),
);
}
}

registerProviders();
Expand Down
Loading

0 comments on commit 70e4874

Please sign in to comment.