Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Symbol support for single document #1244

Merged
merged 7 commits into from
Feb 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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