Skip to content

Commit

Permalink
feat: Symbol support for single document (#1244)
Browse files Browse the repository at this point in the history
* document symbols
* more outline/symbol support for schema definition types
* support interface definitions, full enum values for symbols
  • Loading branch information
acao authored Feb 16, 2020
1 parent 0f99f17 commit f729f9a
Show file tree
Hide file tree
Showing 12 changed files with 2,666 additions and 2,902 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ name: Node.JS CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
name: lint & test
runs-on: ubuntu-16.04
steps:
- uses: actions/checkout@v1
- uses: bahmutov/npm-install@v1
- run: yarn ci
e2e:
name: cypress
runs-on: ubuntu-16.04
steps:
- uses: actions/checkout@v1
Expand Down
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@
"@commitlint/config-lerna-scopes": "^8.1.0",
"@strictsoftware/typedoc-plugin-monorepo": "^0.2.1",
"@testing-library/jest-dom": "^5.1.1",
"@types/codemirror": "^0.0.84",
"@types/codemirror": "^0.0.85",
"@types/fetch-mock": "^7.3.2",
"@types/jest": "^25.1.1",
"@types/node": "^13.7.1",
"@typescript-eslint/eslint-plugin": "^2.16.0",
"@typescript-eslint/eslint-plugin": "^2.19.2",
"@typescript-eslint/parser": "^2.18.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^25.1.0",
Expand All @@ -86,17 +86,17 @@
"conventional-changelog-conventionalcommits": "^4.1.0",
"copy": "^0.3.2",
"cross-env": "^7.0.0",
"cypress": "^4.0.1",
"cypress": "^4.0.2",
"eslint": "^6.8.0",
"eslint-config-prettier": "6.10.0",
"eslint-plugin-babel": "5.3.0",
"eslint-plugin-cypress": "^2.7.0",
"eslint-plugin-cypress": "^2.10.1",
"eslint-plugin-flowtype": "4.6.0",
"eslint-plugin-import": "^2.20.0",
"eslint-plugin-jest": "^23.1.1",
"eslint-plugin-prefer-object-spread": "1.2.1",
"eslint-plugin-react": "7.18.1",
"fetch-mock": "^6.0.0",
"eslint-plugin-react": "7.18.3",
"fetch-mock": "6.5.2",
"flow-bin": "^0.118.0",
"graphql": "^14.6.0",
"husky": "^4.0.7",
Expand All @@ -108,9 +108,9 @@
"mkdirp": "^1.0.3",
"mocha": "7.0.1",
"prettier": "^1.18.2",
"rimraf": "^3.0.1",
"rimraf": "^3.0.2",
"ts-jest": "^25.2.0",
"typedoc": "^0.15.6",
"typedoc": "^0.15.1",
"typescript": "^3.6.3"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
DocumentNode,
FragmentSpreadNode,
FragmentDefinitionNode,
OperationDefinitionNode,
TypeDefinitionNode,
NamedTypeNode,
ValidationRule,
Expand All @@ -26,10 +25,16 @@ import {
GraphQLProjectConfig,
Uri,
Position,
Outline,
OutlineTree,
} from 'graphql-language-service-types';

// import { Position } from 'graphql-language-service-utils';
import { Hover, DiagnosticSeverity } from 'vscode-languageserver-types';
import {
Hover,
SymbolInformation,
SymbolKind,
} from 'vscode-languageserver-types';

import { Kind, parse, print } from 'graphql';
import { getAutocompleteSuggestions } from './getAutocompleteSuggestions';
Expand All @@ -41,6 +46,8 @@ import {
getDefinitionQueryResultForNamedType,
} from './getDefinition';

import { getOutline } from './getOutline';

import {
getASTNodeAtPosition,
requireFile,
Expand All @@ -67,6 +74,33 @@ const {
NAMED_TYPE,
} = 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,
FieldWithArguments: SymbolKind.Method,
};

function getKind(tree: OutlineTree) {
if (
tree.kind === 'FieldDefinition' &&
tree.children &&
tree.children.length > 0
) {
return KIND_TO_SYMBOL_KIND.FieldWithArguments;
}
return KIND_TO_SYMBOL_KIND[tree.kind];
}

export class GraphQLLanguageService {
_graphQLCache: GraphQLCache;
_graphQLConfig: GraphQLConfig;
Expand All @@ -84,7 +118,7 @@ export class GraphQLLanguageService {
throw Error(`No config found for uri: ${uri}`);
}

async getDiagnostics(
public async getDiagnostics(
query: string,
uri: Uri,
isRelayCompatMode?: boolean,
Expand Down Expand Up @@ -123,7 +157,7 @@ export class GraphQLLanguageService {
const range = getRange(error.locations[0], query);
return [
{
severity: SEVERITY.ERROR as DiagnosticSeverity,
severity: SEVERITY.ERROR,
message: error.message,
source: 'GraphQL: Syntax',
range,
Expand Down Expand Up @@ -187,7 +221,7 @@ export class GraphQLLanguageService {
return validateQuery(validationAst, schema, customRules, isRelayCompatMode);
}

async getAutocompleteSuggestions(
public async getAutocompleteSuggestions(
query: string,
position: Position,
filePath: Uri,
Expand All @@ -203,7 +237,7 @@ export class GraphQLLanguageService {
return [];
}

async getHoverInformation(
public async getHoverInformation(
query: string,
position: Position,
filePath: Uri,
Expand All @@ -219,7 +253,7 @@ export class GraphQLLanguageService {
return '';
}

async getDefinition(
public async getDefinition(
query: string,
position: Position,
filePath: Uri,
Expand Down Expand Up @@ -250,7 +284,7 @@ export class GraphQLLanguageService {
return getDefinitionQueryResultForDefinitionNode(
filePath,
query,
node as FragmentDefinitionNode | OperationDefinitionNode,
node,
);

case NAMED_TYPE:
Expand All @@ -266,6 +300,55 @@ export class GraphQLLanguageService {
return null;
}

public async getDocumentSymbols(
document: string,
filePath: Uri,
): Promise<SymbolInformation[]> {
const outline = await this.getOutline(document);
if (!outline) {
return [];
}

const output: Array<SymbolInformation> = [];
const input = outline.outlineTrees.map((tree: OutlineTree) => [null, tree]);

while (input.length > 0) {
const res = input.pop();
if (!res) {
return [];
}
const [parent, tree] = res;
if (!tree) {
return [];
}

output.push({
// @ts-ignore
name: tree.representativeName,
kind: getKind(tree),
location: {
uri: filePath,
range: {
start: tree.startPosition,
// @ts-ignore
end: tree.endPosition,
},
},
containerName: parent ? parent.representativeName : undefined,
});
input.push(...tree.children.map(child => [tree, child]));
}
return output;
}
//
// public async getReferences(
// document: string,
// position: Position,
// filePath: Uri,
// ): Promise<Location[]> {
//
// }

async _getDefinitionForNamedType(
query: string,
ast: DocumentNode,
Expand All @@ -287,7 +370,8 @@ export class GraphQLLanguageService {
definition.kind === OBJECT_TYPE_DEFINITION ||
definition.kind === INPUT_OBJECT_TYPE_DEFINITION ||
definition.kind === ENUM_TYPE_DEFINITION ||
definition.kind === SCALAR_TYPE_DEFINITION,
definition.kind === SCALAR_TYPE_DEFINITION ||
definition.kind === INTERFACE_TYPE_DEFINITION,
);

const typeCastedDefs = (localObjectTypeDefinitions as any) as Array<
Expand Down Expand Up @@ -351,4 +435,7 @@ export class GraphQLLanguageService {

return result;
}
async getOutline(query: string): Promise<Outline | null | undefined> {
return getOutline(query);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
*
*/

import { Position } from 'graphql-language-service-types';
import { join } from 'path';
import * as fs from 'fs';
import { buildSchema } from 'graphql';

import { GraphQLConfig } from 'graphql-config';
import { GraphQLLanguageService } from '../GraphQLLanguageService';
import { SymbolKind } from 'vscode-languageserver-protocol';
import { Position } from 'graphql-language-service-utils';

const MOCK_CONFIG = {
schemaPath: './__schema__/StarWarsSchema.graphql',
Expand Down Expand Up @@ -110,4 +111,29 @@ describe('GraphQLLanguageService', () => {
'String\n\nThe `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.',
);
});

it('runs document symbol requests as expected', async () => {
const validQuery = `
query OperationExample {
item(episode: EMPIRE){
...testFragment
}
}
`;

const result = await languageService.getDocumentSymbols(
validQuery,
'file://file.graphql',
);

expect(result).not.toBeUndefined();
expect(result.length).toEqual(3);
// expect(result[0].name).toEqual('item');
expect(result[1].name).toEqual('item');
expect(result[1].kind).toEqual(SymbolKind.Field);
expect(result[1].location.range.start.line).toEqual(2);
expect(result[1].location.range.start.character).toEqual(4);
expect(result[1].location.range.end.line).toEqual(4);
expect(result[1].location.range.end.character).toEqual(5);
});
});
Loading

0 comments on commit f729f9a

Please sign in to comment.