Skip to content

Commit

Permalink
improvement: changes to LSP to prepare for monaco
Browse files Browse the repository at this point in the history
- move GraphQLCache to interface package, prepare for de-nodeification
- graphql-languageservice package exports interface, parser & types (ala vscode)
- remove some unnecessarily duplicate types from gls-types
- improve autocomplete results to return a vscode CompletionItemKind
- Change diagnostic severity case to match other vscode patterns
  • Loading branch information
acao committed Apr 6, 2020
1 parent b8f9af6 commit 87bd8b2
Show file tree
Hide file tree
Showing 46 changed files with 458 additions and 288 deletions.
2 changes: 1 addition & 1 deletion examples/graphiql-webpack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"start": "cross-env NODE_ENV=development webpack-dev-server"
},
"dependencies": {
"graphiql": "file:../../packages/graphiql",
"graphql": "15.0.0",
"graphiql": "1.0.0-alpha.4",
"react": "16.13.1"
},
"devDependencies": {
Expand Down
4 changes: 3 additions & 1 deletion packages/graphql-language-service-interface/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
"peerDependencies": {
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
},
"devDependencies": {
"graphql-language-service-types": "^1.6.0-alpha.5"
},
"dependencies": {
"graphql-language-service-parser": "^1.6.0-alpha.3",
"graphql-language-service-types": "^1.6.0-alpha.5",
"graphql-language-service-utils": "^2.4.0-alpha.5"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import { ASTNode, DocumentNode, DefinitionNode } from 'graphql/language';
import {
CachedContent,
GraphQLCache as GraphQLCacheInterface,
GraphQLFileMetadata,
GraphQLFileInfo,
FragmentInfo,
Expand Down Expand Up @@ -57,7 +56,7 @@ export async function getGraphQLCache(
parser: typeof parseDocument,
extensions?: Array<(config: GraphQLConfig) => GraphQLConfig>,
config?: GraphQLConfig,
): Promise<GraphQLCacheInterface> {
): Promise<GraphQLCache> {
let graphQLConfig =
config ?? ((await loadConfig({ rootDir: configDir })) as GraphQLConfig);
if (extensions && extensions.length > 0) {
Expand All @@ -68,7 +67,7 @@ export async function getGraphQLCache(
return new GraphQLCache(configDir, graphQLConfig, parser);
}

export class GraphQLCache implements GraphQLCacheInterface {
export class GraphQLCache {
_configDir: Uri;
_graphQLFileListCache: Map<Uri, Map<string, GraphQLFileInfo>>;
_graphQLConfig: GraphQLConfig;
Expand Down Expand Up @@ -679,7 +678,7 @@ export class GraphQLCache implements GraphQLCacheInterface {
}

_getProjectName(projectConfig: GraphQLProjectConfig) {
return projectConfig || 'default';
return projectConfig;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,22 @@ import {
TypeDefinitionNode,
NamedTypeNode,
ValidationRule,
GraphQLSchema,
} from 'graphql';

import {
CompletionItem,
DefinitionQueryResult,
Diagnostic,
GraphQLCache,
Uri,
Position,
Outline,
OutlineTree,
ContextToken,
} from 'graphql-language-service-types';

import { GraphQLCache } from './GraphQLCache';

import { GraphQLConfig, GraphQLProjectConfig } from 'graphql-config';
import {
Hover,
Expand All @@ -37,7 +40,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 @@ -73,18 +76,19 @@ const {
} = Kind;

const KIND_TO_SYMBOL_KIND: { [key: string]: SymbolKind } = {
Field: SymbolKind.Field,
OperationDefinition: SymbolKind.Class,
FragmentDefinition: SymbolKind.Class,
FragmentSpread: SymbolKind.Struct,
ObjectTypeDefinition: SymbolKind.Class,
EnumTypeDefinition: SymbolKind.Enum,
EnumValueDefinition: SymbolKind.EnumMember,
InputObjectTypeDefinition: SymbolKind.Class,
InputValueDefinition: SymbolKind.Field,
FieldDefinition: SymbolKind.Field,
InterfaceTypeDefinition: SymbolKind.Interface,
Document: SymbolKind.File,
[Kind.FIELD]: SymbolKind.Field,
[Kind.OPERATION_DEFINITION]: SymbolKind.Class,
[Kind.FRAGMENT_DEFINITION]: SymbolKind.Class,
[Kind.FRAGMENT_SPREAD]: SymbolKind.Struct,
[Kind.OBJECT_TYPE_DEFINITION]: SymbolKind.Class,
[Kind.ENUM_TYPE_DEFINITION]: SymbolKind.Enum,
[Kind.ENUM_VALUE_DEFINITION]: SymbolKind.EnumMember,
[Kind.INPUT_OBJECT_TYPE_DEFINITION]: SymbolKind.Class,
[Kind.INPUT_VALUE_DEFINITION]: SymbolKind.Field,
[Kind.FIELD_DEFINITION]: SymbolKind.Field,
[Kind.INTERFACE_TYPE_DEFINITION]: SymbolKind.Interface,
[Kind.DOCUMENT]: SymbolKind.File,
// novel, for symbols only
FieldWithArguments: SymbolKind.Method,
};

Expand All @@ -102,10 +106,12 @@ function getKind(tree: OutlineTree) {
export class GraphQLLanguageService {
_graphQLCache: GraphQLCache;
_graphQLConfig: GraphQLConfig;
_project: GraphQLProjectConfig;

constructor(cache: GraphQLCache) {
this._graphQLCache = cache;
this._graphQLConfig = cache.getGraphQLConfig();
this._project = this._graphQLConfig.getDefault();
}

getConfigForURI(uri: Uri) {
Expand All @@ -115,6 +121,23 @@ export class GraphQLLanguageService {
}
throw Error(`No config found for uri: ${uri}`);
}
public async getSchema(
projectName?: string,
queryHasExtensions?: boolean,
): Promise<GraphQLSchema | null> {
try {
const schema = projectName
? await this._graphQLCache.getSchema(projectName, queryHasExtensions)
: await this._graphQLConfig.getDefault().getSchema();
return schema;
} catch (err) {
console.warn('no schema found');
return null;
}
}
public async getProject(projectName: string) {
this._project = this._graphQLConfig.getProject(projectName);
}

public async getDiagnostics(
query: string,
Expand Down Expand Up @@ -155,7 +178,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 +198,7 @@ export class GraphQLLanguageService {
);

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

Expand Down Expand Up @@ -208,9 +231,7 @@ export class GraphQLLanguageService {
}
/* eslint-enable no-implicit-coercion */
}
const schema = await this._graphQLCache
.getSchema(projectName, queryHasExtensions)
.catch(() => null);
const schema = await this.getSchema(projectName, queryHasExtensions);

if (!schema) {
return [];
Expand All @@ -223,14 +244,13 @@ export class GraphQLLanguageService {
query: string,
position: Position,
filePath: Uri,
contextToken?: ContextToken,
): Promise<Array<CompletionItem>> {
const projectConfig = this.getConfigForURI(filePath);
const schema = await this._graphQLCache
.getSchema(projectConfig.name)
.catch(() => null);
const schema = await this.getSchema(projectConfig.name);

if (schema) {
return getAutocompleteSuggestions(schema, query, position);
return getAutocompleteSuggestions(schema, query, position, contextToken);
}
return [];
}
Expand All @@ -241,9 +261,7 @@ export class GraphQLLanguageService {
filePath: Uri,
): Promise<Hover['contents']> {
const projectConfig = this.getConfigForURI(filePath);
const schema = await this._graphQLCache
.getSchema(projectConfig.name)
.catch(() => null);
const schema = await this.getSchema(projectConfig.name);

if (schema) {
return getHoverInformation(schema, query, position);
Expand Down Expand Up @@ -321,15 +339,13 @@ export class GraphQLLanguageService {
}

output.push({
// @ts-ignore
name: tree.representativeName,
name: tree.representativeName as string,
kind: getKind(tree),
location: {
uri: filePath,
range: {
start: tree.startPosition,
// @ts-ignore
end: tree.endPosition,
end: tree.endPosition as Position,
},
},
containerName: parent ? parent.representativeName : undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
projects:
default:
schema: '__schema__/StarWarsSchema.graphql'
testWithSchema:
schema:
- __schema__/StarWarsSchema.graphql
- 'directive @customDirective on FRAGMENT_SPREAD'
testWithEndpoint:
schema: https://example.com/graphql
testWithEndpointAndSchema:
schema:
- __schema__/StarWarsSchema.graphql
- https://example.com/graphql
testWithoutSchema:
schema: ''
testWithCustomDirectives:
schema:
- __schema__/StarWarsSchema.graphql
- 'directive @customDirective on FIELD'
testSingularIncludesGlob:
schema: __schema__/StarWarsSchema.graphql
include: __queries__/*.graphql
testMultipleIncludes:
schema: __schema__/StarWarsSchema.graphql
include:
- __queries__/*.graphql
- __fragments__/*.graphql
testNoIncludes:
schema: __schema__/StarWarsSchema.graphql
testBadIncludes:
schema: __schema__/StarWarsSchema.graphql
include: nope.nopeql
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const MOCK_CONFIG = {
filepath: join(__dirname, '.graphqlrc.yml'),
config: {
schema: './__schema__/StarWarsSchema.graphql',
documents: ['./queries/**', '**/*.graphql'],
documents: ['./__queries__/**', '**/*.graphql'],
},
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
type Query { hero(episode: Episode): Character }
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import fs from 'fs';
import { buildSchema, parse, GraphQLSchema } from 'graphql';
import path from 'path';

import { getDiagnostics, validateQuery, SEVERITY } from '../getDiagnostics';
import {
getDiagnostics,
validateQuery,
DIAGNOSTIC_SEVERITY,
} from '../getDiagnostics';

describe('getDiagnostics', () => {
let schema: GraphQLSchema;
Expand All @@ -30,7 +34,7 @@ describe('getDiagnostics', () => {
expect(error.message).toEqual(
'Cannot query field "title" on type "Query".',
);
expect(error.severity).toEqual(SEVERITY.ERROR);
expect(error.severity).toEqual(DIAGNOSTIC_SEVERITY.Error);
expect(error.source).toEqual('GraphQL: Validation');
});

Expand All @@ -43,7 +47,7 @@ describe('getDiagnostics', () => {
// eslint-disable-next-line no-useless-escape
'The field "Query.deprecatedField" is deprecated. Use test instead.',
);
expect(error.severity).toEqual(SEVERITY.WARNING);
expect(error.severity).toEqual(DIAGNOSTIC_SEVERITY.Warning);
expect(error.source).toEqual('GraphQL: Deprecation');
});

Expand Down Expand Up @@ -73,10 +77,9 @@ describe('getDiagnostics', () => {
expect(errors.length).toEqual(1);
const error = errors[0];
expect(error.message).toEqual(
// eslint-disable-next-line no-useless-escape
'Syntax Error: Expected ":", found Name "id".',
);
expect(error.severity).toEqual(SEVERITY.ERROR);
expect(error.severity).toEqual(DIAGNOSTIC_SEVERITY.Error);
expect(error.source).toEqual('GraphQL: Syntax');
});

Expand Down
Loading

0 comments on commit 87bd8b2

Please sign in to comment.