Skip to content
This repository was archived by the owner on Sep 2, 2020. It is now read-only.

Commit 123825d

Browse files
authored
Add support for 'textDocument/hover' (#217)
* Add support for 'textDocument/hover' Display field type & description information
1 parent 2b96960 commit 123825d

File tree

10 files changed

+453
-14
lines changed

10 files changed

+453
-14
lines changed

packages/interface/src/GraphQLLanguageService.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {
2424
Uri,
2525
} from 'graphql-language-service-types';
2626
import type {Position} from 'graphql-language-service-utils';
27+
import type {Hover} from 'vscode-languageserver-types';
2728

2829
import {
2930
FRAGMENT_DEFINITION,
@@ -46,6 +47,7 @@ import {
4647

4748
import {parse, print} from 'graphql';
4849
import {getAutocompleteSuggestions} from './getAutocompleteSuggestions';
50+
import {getHoverInformation} from './getHoverInformation';
4951
import {validateQuery, getRange, SEVERITY} from './getDiagnostics';
5052
import {
5153
getDefinitionQueryResultForFragmentSpread,
@@ -177,6 +179,22 @@ export class GraphQLLanguageService {
177179
return [];
178180
}
179181

182+
async getHoverInformation(
183+
query: string,
184+
position: Position,
185+
filePath: Uri,
186+
): Promise<Hover.contents> {
187+
const projectConfig = this._graphQLConfig.getConfigForFile(filePath);
188+
const schema = await this._graphQLCache
189+
.getSchema(projectConfig.projectName)
190+
.catch(() => null);
191+
192+
if (schema) {
193+
return getHoverInformation(schema, query, position);
194+
}
195+
return '';
196+
}
197+
180198
async getDefinition(
181199
query: string,
182200
position: Position,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
schema {
2+
query: Query
3+
}
4+
5+
""" This is type documentation for Chicken """
6+
scalar Chicken
7+
8+
""" docs for color """
9+
enum Color {
10+
RED
11+
GREEN
12+
BLUE
13+
}
14+
15+
union UnionType = String | Float | Boolean
16+
17+
interface TestInterface {
18+
id: String!
19+
}
20+
21+
""" This is type documentation for TestType """
22+
type TestType implements TestInterface {
23+
""" This is field documentation for TestType.testField """
24+
testField: String
25+
testDeprecatedField: Float @deprecated(reason: "deprecation reason")
26+
testEnumField: Color
27+
}
28+
29+
type Query {
30+
31+
""" This is field documentation for Query.thing """
32+
thing: TestType
33+
listOfThing: [TestType!]
34+
parameterizedField(id: String!): TestType
35+
cluck: Chicken
36+
unionField: UnionType
37+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* Copyright (c) Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*
8+
* @flow
9+
*/
10+
11+
import type {Hover} from 'vscode-languageserver-types';
12+
13+
import {expect} from 'chai';
14+
import {beforeEach, describe, it} from 'mocha';
15+
import fs from 'fs';
16+
import {buildSchema} from 'graphql';
17+
import {Position} from 'graphql-language-service-utils';
18+
import path from 'path';
19+
20+
import {getHoverInformation} from '../getHoverInformation';
21+
22+
describe('getHoverInformation', () => {
23+
let schema;
24+
beforeEach(async () => {
25+
const schemaIDL = fs.readFileSync(
26+
path.join(__dirname, '__schema__/HoverTestSchema.graphql'),
27+
'utf8',
28+
);
29+
schema = buildSchema(schemaIDL);
30+
});
31+
32+
function testHover(query: string, point: Position): Hover.contents {
33+
return getHoverInformation(schema, query, point);
34+
}
35+
36+
it('provides leaf field information', () => {
37+
const actual = testHover(
38+
'query { thing { testField } }',
39+
new Position(0, 20),
40+
);
41+
expect(actual).to.deep.equal(
42+
'TestType.testField: String\n\n This is field documentation for TestType.testField',
43+
);
44+
});
45+
46+
it('provides aliased field information', () => {
47+
const actual = testHover(
48+
'query { thing { other: testField } }',
49+
new Position(0, 25),
50+
);
51+
expect(actual).to.deep.equal(
52+
'TestType.testField: String\n\n This is field documentation for TestType.testField',
53+
);
54+
});
55+
56+
it('provides intermediate field information', () => {
57+
const actual = testHover(
58+
'query { thing { testField } }',
59+
new Position(0, 10),
60+
);
61+
expect(actual).to.deep.equal(
62+
'Query.thing: TestType\n\n This is field documentation for Query.thing',
63+
);
64+
});
65+
66+
it('provides list field information', () => {
67+
const actual = testHover(
68+
'query { listOfThing { testField } }',
69+
new Position(0, 10),
70+
);
71+
expect(actual).to.deep.equal('Query.listOfThing: [TestType!]');
72+
});
73+
74+
it('provides deprecated field information', () => {
75+
const actual = testHover(
76+
'query { thing { testDeprecatedField } }',
77+
new Position(0, 20),
78+
);
79+
expect(actual).to.deep.equal(
80+
'TestType.testDeprecatedField: Float\n\nDeprecated: deprecation reason',
81+
);
82+
});
83+
84+
it('provides enum field information', () => {
85+
const actual = testHover(
86+
'query { thing { testEnumField } }',
87+
new Position(0, 20),
88+
);
89+
expect(actual).to.deep.equal('TestType.testEnumField: Color');
90+
});
91+
92+
it('provides scalar field information', () => {
93+
const actual = testHover('query { cluck }', new Position(0, 10));
94+
expect(actual).to.deep.equal('Query.cluck: Chicken');
95+
});
96+
97+
it('provides parameter type information', () => {
98+
const actual = testHover(
99+
'query { parameterizedField(id: "foo") { testField } }',
100+
new Position(0, 28),
101+
);
102+
expect(actual).to.deep.equal('Query.parameterizedField(id: String!)');
103+
});
104+
105+
it('provides directive information', () => {
106+
const actual = testHover(
107+
'query { thing { testField @skip(if:true) } }',
108+
new Position(0, 30),
109+
);
110+
expect(actual).to.deep.equal(
111+
'@skip\n\nDirects the executor to skip this field or fragment when the `if` argument is true.',
112+
);
113+
});
114+
115+
it('provides union information', () => {
116+
const actual = testHover('query { unionField }', new Position(0, 12));
117+
expect(actual).to.deep.equal('Query.unionField: UnionType');
118+
});
119+
});

packages/interface/src/getAutocompleteSuggestions.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,10 @@ function getSuggestionsForDirective(
375375
return [];
376376
}
377377

378-
function getTokenAtPosition(queryText: string, cursor: Position): ContextToken {
378+
export function getTokenAtPosition(
379+
queryText: string,
380+
cursor: Position,
381+
): ContextToken {
379382
let styleAtCursor = null;
380383
let stateAtCursor = null;
381384
let stringAtCursor = null;
@@ -513,7 +516,10 @@ function canUseDirective(
513516

514517
// Utility for collecting rich type information given any token's state
515518
// from the graphql-mode parser.
516-
function getTypeInfo(schema: GraphQLSchema, tokenState: State): TypeInfo {
519+
export function getTypeInfo(
520+
schema: GraphQLSchema,
521+
tokenState: State,
522+
): TypeInfo {
517523
let argDef;
518524
let argDefs;
519525
let directiveDef;

0 commit comments

Comments
 (0)