Skip to content

Commit 629a733

Browse files
committed
feat: more outline/symbol support for schema definition types
1 parent d9a2e06 commit 629a733

File tree

4 files changed

+120
-45
lines changed

4 files changed

+120
-45
lines changed

packages/graphql-language-service-interface/src/GraphQLLanguageService.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,27 @@ const KIND_TO_SYMBOL_KIND: { [key: string]: SymbolKind } = {
8181
OperationDefinition: SymbolKind.Class,
8282
FragmentDefinition: SymbolKind.Class,
8383
FragmentSpread: SymbolKind.Struct,
84-
ObjectType: SymbolKind.Class,
85-
InputType: SymbolKind.Class,
84+
ObjectTypeDefinition: SymbolKind.Class,
85+
EnumTypeDefinition: SymbolKind.Enum,
86+
InputObjectTypeDefinition: SymbolKind.Class,
87+
InputValueDefinition: SymbolKind.Field,
88+
FieldDefinition: SymbolKind.Field,
89+
InterfaceTypeDefinition: SymbolKind.Interface,
90+
Document: SymbolKind.File,
91+
FieldWithArguments: SymbolKind.Method,
8692
};
8793

94+
function getKind(tree: OutlineTree) {
95+
if (
96+
tree.kind === 'FieldDefinition' &&
97+
tree.children &&
98+
tree.children.length > 0
99+
) {
100+
return KIND_TO_SYMBOL_KIND.FieldWithArguments;
101+
}
102+
return KIND_TO_SYMBOL_KIND[tree.kind];
103+
}
104+
88105
export class GraphQLLanguageService {
89106
_graphQLCache: GraphQLCache;
90107
_graphQLConfig: GraphQLConfig;
@@ -295,6 +312,7 @@ export class GraphQLLanguageService {
295312

296313
const output: Array<SymbolInformation> = [];
297314
const input = outline.outlineTrees.map((tree: OutlineTree) => [null, tree]);
315+
298316
while (input.length > 0) {
299317
const res = input.pop();
300318
if (!res) {
@@ -304,10 +322,11 @@ export class GraphQLLanguageService {
304322
if (!tree) {
305323
return [];
306324
}
325+
307326
output.push({
308327
// @ts-ignore
309328
name: tree.representativeName,
310-
kind: KIND_TO_SYMBOL_KIND[tree.kind],
329+
kind: getKind(tree),
311330
location: {
312331
uri: filePath,
313332
range: {

packages/graphql-language-service-interface/src/getOutline.ts

Lines changed: 94 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ import {
2222
FragmentDefinitionNode,
2323
SelectionSetNode,
2424
SelectionNode,
25+
InterfaceTypeDefinitionNode,
26+
ObjectTypeDefinitionNode,
27+
EnumTypeDefinitionNode,
2528
DefinitionNode,
29+
InputValueDefinitionNode,
30+
FieldDefinitionNode,
2631
} from 'graphql';
2732
import { offsetToPosition, Position } from 'graphql-language-service-utils';
2833

@@ -37,28 +42,39 @@ const OUTLINEABLE_KINDS = {
3742
FragmentDefinition: true,
3843
FragmentSpread: true,
3944
InlineFragment: true,
45+
ObjectTypeDefinition: true,
46+
InputObjectTypeDefinition: true,
47+
InterfaceTypeDefinition: true,
48+
EnumTypeDefinition: true,
49+
InputValueDefinition: true,
50+
FieldDefinition: true,
4051
};
4152

42-
type OutlineTreeResult = {
43-
representativeName: string;
44-
startPosition: Position;
45-
endPosition: Position;
46-
children: SelectionSetNode[] | [];
47-
tokenizedText: TextToken[];
48-
};
53+
export type OutlineableKinds = keyof typeof OUTLINEABLE_KINDS;
4954

50-
type OutlineTreeConverterType = {
51-
[name: string]: (
52-
node: any,
53-
) =>
54-
| OutlineTreeResult
55-
| SelectionSetNode
56-
| readonly DefinitionNode[]
57-
| readonly SelectionNode[]
58-
| string;
59-
};
55+
// type OutlineableNodes = FieldNode | OperationDefinitionNode | DocumentNode | SelectionSetNode | NameNode | FragmentDefinitionNode | FragmentSpreadNode |InlineFragmentNode | ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode
56+
57+
type OutlineTreeResult =
58+
| {
59+
representativeName: string;
60+
startPosition: Position;
61+
endPosition: Position;
62+
children: SelectionSetNode[] | [];
63+
tokenizedText: TextToken[];
64+
}
65+
| string
66+
| readonly DefinitionNode[]
67+
| readonly SelectionNode[]
68+
| FieldNode[]
69+
| SelectionSetNode;
70+
71+
type OutlineTreeConverterType = Partial<
72+
{
73+
[key in OutlineableKinds]: (node: any) => OutlineTreeResult;
74+
}
75+
>;
6076

61-
export function getOutline(queryText: string): Outline | null | undefined {
77+
export function getOutline(queryText: string): Outline | null {
6278
let ast;
6379
try {
6480
ast = parse(queryText);
@@ -69,10 +85,8 @@ export function getOutline(queryText: string): Outline | null | undefined {
6985
const visitorFns = outlineTreeConverter(queryText);
7086
const outlineTrees = visit(ast, {
7187
leave(node) {
72-
if (
73-
OUTLINEABLE_KINDS.hasOwnProperty(node.kind) &&
74-
visitorFns[node.kind]
75-
) {
88+
if (visitorFns !== undefined && node.kind in visitorFns) {
89+
// @ts-ignore
7690
return visitorFns[node.kind](node);
7791
}
7892
return null;
@@ -85,23 +99,22 @@ export function getOutline(queryText: string): Outline | null | undefined {
8599
function outlineTreeConverter(docText: string): OutlineTreeConverterType {
86100
// TODO: couldn't find a type that would work for all cases here,
87101
// however the inference is not broken by this at least
88-
const meta = (node: any) => ({
89-
representativeName: node.name,
90-
startPosition: offsetToPosition(docText, node.loc.start),
91-
endPosition: offsetToPosition(docText, node.loc.end),
92-
kind: node.kind,
93-
children: node.selectionSet || [],
94-
});
102+
const meta = (node: any) => {
103+
return {
104+
representativeName: node.name,
105+
startPosition: offsetToPosition(docText, node.loc.start),
106+
endPosition: offsetToPosition(docText, node.loc.end),
107+
kind: node.kind,
108+
children: node.selectionSet || node.fields || node.arguments || [],
109+
};
110+
};
95111

96112
return {
97113
Field: (node: FieldNode) => {
98114
const tokenizedText = node.alias
99-
? [
100-
buildToken('plain', (node.alias as unknown) as string),
101-
buildToken('plain', ': '),
102-
]
115+
? [buildToken('plain', node.alias), buildToken('plain', ': ')]
103116
: [];
104-
tokenizedText.push(buildToken('plain', (node.name as unknown) as string));
117+
tokenizedText.push(buildToken('plain', node.name));
105118
return { tokenizedText, ...meta(node) };
106119
},
107120
OperationDefinition: (node: OperationDefinitionNode) => ({
@@ -123,24 +136,67 @@ function outlineTreeConverter(docText: string): OutlineTreeConverterType {
123136
tokenizedText: [
124137
buildToken('keyword', 'fragment'),
125138
buildToken('whitespace', ' '),
126-
buildToken('class-name', (node.name as unknown) as string),
139+
buildToken('class-name', node.name),
140+
],
141+
...meta(node),
142+
}),
143+
InterfaceTypeDefinition: (node: InterfaceTypeDefinitionNode) => ({
144+
tokenizedText: [
145+
buildToken('keyword', 'interface'),
146+
buildToken('whitespace', ' '),
147+
buildToken('class-name', node.name),
148+
],
149+
...meta(node),
150+
}),
151+
EnumTypeDefinition: (node: EnumTypeDefinitionNode) => ({
152+
tokenizedText: [
153+
buildToken('keyword', 'enum'),
154+
buildToken('whitespace', ' '),
155+
buildToken('class-name', node.name),
156+
],
157+
...meta(node),
158+
}),
159+
ObjectTypeDefinition: (node: ObjectTypeDefinitionNode) => ({
160+
tokenizedText: [
161+
buildToken('keyword', 'type'),
162+
buildToken('whitespace', ' '),
163+
buildToken('class-name', node.name),
164+
],
165+
...meta(node),
166+
}),
167+
InputObjectTypeDefinition: (node: ObjectTypeDefinitionNode) => ({
168+
tokenizedText: [
169+
buildToken('keyword', 'input'),
170+
buildToken('whitespace', ' '),
171+
buildToken('class-name', node.name),
127172
],
128173
...meta(node),
129174
}),
130-
131175
FragmentSpread: (node: FragmentSpreadNode) => ({
132176
tokenizedText: [
133177
buildToken('plain', '...'),
134-
buildToken('class-name', (node.name as unknown) as string),
178+
buildToken('class-name', node.name),
135179
],
136180
...meta(node),
137181
}),
182+
InputValueDefinition: (node: InputValueDefinitionNode) => {
183+
return {
184+
tokenizedText: [buildToken('plain', node.name)],
185+
...meta(node),
186+
};
187+
},
188+
FieldDefinition: (node: FieldDefinitionNode) => {
189+
return {
190+
tokenizedText: [buildToken('plain', node.name)],
191+
...meta(node),
192+
};
193+
},
138194

139195
InlineFragment: (node: InlineFragmentNode) => node.selectionSet,
140196
};
141197
}
142198

143-
function buildToken(kind: TokenKind, value: string | undefined): TextToken {
199+
function buildToken(kind: TokenKind, value: string | NameNode): TextToken {
144200
return { kind, value };
145201
}
146202

packages/graphql-language-service-server/src/__tests__/MessageProcessor-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe('MessageProcessor', () => {
7676
outlineTrees: [
7777
{
7878
representativeName: 'item',
79-
kind: SymbolKind.Field,
79+
kind: 'Field',
8080
startPosition: { line: 1, character: 2 },
8181
endPosition: { line: 1, character: 4 },
8282
children: [],

packages/graphql-language-service-types/src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
Diagnostic as DiagnosticType,
1111
Position as PositionType,
1212
CompletionItem as CompletionItemType,
13-
SymbolKind,
1413
} from 'vscode-languageserver-protocol';
1514
import { GraphQLSchema, KindEnum } from 'graphql';
1615

@@ -20,6 +19,7 @@ import {
2019
FragmentDefinitionNode,
2120
NamedTypeNode,
2221
TypeDefinitionNode,
22+
NameNode,
2323
} from 'graphql/language';
2424
import {
2525
GraphQLArgument,
@@ -319,7 +319,7 @@ export type TokenKind =
319319
| 'type';
320320
export type TextToken = {
321321
kind: TokenKind;
322-
value: string | undefined;
322+
value: string | NameNode;
323323
};
324324

325325
export type TokenizedText = TextToken[];
@@ -328,7 +328,7 @@ export type OutlineTree = {
328328
plainText?: string;
329329
tokenizedText?: TokenizedText;
330330
representativeName?: string;
331-
kind: SymbolKind;
331+
kind: string;
332332
startPosition: Position;
333333
endPosition?: Position;
334334
children: OutlineTree[];

0 commit comments

Comments
 (0)