diff --git a/.changeset/new-chairs-wonder.md b/.changeset/new-chairs-wonder.md new file mode 100644 index 00000000..aca5f9e7 --- /dev/null +++ b/.changeset/new-chairs-wonder.md @@ -0,0 +1,5 @@ +--- +"@0no-co/graphqlsp": patch +--- + +fix case where the hover-information would target the wrong TypeScript node by one character diff --git a/packages/graphqlsp/src/ast/token.ts b/packages/graphqlsp/src/ast/token.ts index 79d04e71..9e0dae8f 100644 --- a/packages/graphqlsp/src/ast/token.ts +++ b/packages/graphqlsp/src/ast/token.ts @@ -22,14 +22,14 @@ export const getToken = ( let foundToken: Token | undefined = undefined; for (let line = 0; line < input.length; line++) { - const lPos = cPos; + const lPos = cPos - 1; const stream = new CharacterStream(input[line] + '\n'); while (!stream.eol()) { const token = parser.token(stream, state); const string = stream.current(); if ( - lPos + stream.getStartOfToken() <= cursorPosition && + lPos + stream.getStartOfToken() + 1 <= cursorPosition && lPos + stream.getCurrentPosition() >= cursorPosition ) { foundToken = { diff --git a/test/e2e/fixture-project-tada/fixtures/fragment.ts b/test/e2e/fixture-project-tada/fixtures/fragment.ts index e89928ad..7aea1ca0 100644 --- a/test/e2e/fixture-project-tada/fixtures/fragment.ts +++ b/test/e2e/fixture-project-tada/fixtures/fragment.ts @@ -10,4 +10,14 @@ export const PokemonFields = graphql(` } `); +// prettier-ignore +export const Regression190 = graphql(` +fragment pokemonFields on Pokemon { + id + name + fleeRate + +} +`); + export const Pokemon = () => {}; diff --git a/test/e2e/fixture-project/fixtures/simple.ts b/test/e2e/fixture-project/fixtures/simple.ts index 49ba6b15..c37a71b8 100644 --- a/test/e2e/fixture-project/fixtures/simple.ts +++ b/test/e2e/fixture-project/fixtures/simple.ts @@ -9,5 +9,11 @@ const PostsQuery = gql` } `; +const Regression190 = gql` +query AllPosts { + +} +`; + const sql = (x: string | TemplateStringsArray) => x; const x = sql`'{}'`; diff --git a/test/e2e/graphqlsp.test.ts b/test/e2e/graphqlsp.test.ts index 015ec5a1..4c8292de 100644 --- a/test/e2e/graphqlsp.test.ts +++ b/test/e2e/graphqlsp.test.ts @@ -109,7 +109,7 @@ describe('simple', () => { ]); }, 7500); - it('Gives quick-info when hovering', async () => { + it('Gives quick-info when hovering start (#15)', async () => { server.send({ seq: 9, type: 'request', @@ -117,7 +117,7 @@ describe('simple', () => { arguments: { file: testFile, line: 5, - offset: 7, + offset: 5, }, }); @@ -135,4 +135,76 @@ describe('simple', () => { `Query.posts: [Post]\n\nList out all posts` ); }, 7500); + + it('Handles empty line (#190)', async () => { + server.send({ + seq: 10, + type: 'request', + command: 'completionInfo', + arguments: { + file: testFile, + line: 14, + offset: 3, + includeExternalModuleExports: true, + includeInsertTextCompletions: true, + triggerKind: 1, + }, + }); + + await server.waitForResponse( + response => + response.type === 'response' && response.command === 'completionInfo' + ); + + const res = server.responses + .reverse() + .find( + resp => resp.type === 'response' && resp.command === 'completionInfo' + ); + + expect(res).toBeDefined(); + expect(typeof res?.body.entries).toEqual('object'); + const defaultAttrs = { kind: 'var', kindModifiers: 'declare' }; + expect(res?.body.entries).toEqual([ + { + ...defaultAttrs, + name: 'post', + sortText: '0post', + labelDetails: { detail: ' Post' }, + }, + { + ...defaultAttrs, + name: 'posts', + sortText: '1posts', + labelDetails: { detail: ' [Post]', description: 'List out all posts' }, + }, + { + ...defaultAttrs, + name: '__typename', + sortText: '2__typename', + labelDetails: { + detail: ' String!', + description: 'The name of the current Object type at runtime.', + }, + }, + { + ...defaultAttrs, + name: '__schema', + sortText: '3__schema', + labelDetails: { + detail: ' __Schema!', + description: 'Access the current type schema of this server.', + }, + }, + { + ...defaultAttrs, + name: '__type', + sortText: '4__type', + labelDetails: { + detail: ' __Type', + description: 'Request the type information of a single type.', + }, + }, + ]); + }, 7500); }); diff --git a/test/e2e/tada.test.ts b/test/e2e/tada.test.ts index eca61882..db3237d4 100644 --- a/test/e2e/tada.test.ts +++ b/test/e2e/tada.test.ts @@ -367,4 +367,194 @@ List out all Pokémon, optionally in pages` ] `); }, 30000); + + it('gives quick-info at start of word (#15)', async () => { + server.send({ + seq: 11, + type: 'request', + command: 'quickinfo', + arguments: { + file: outfileCombinations, + line: 7, + offset: 5, + }, + }); + + await server.waitForResponse( + response => + response.type === 'response' && response.command === 'quickinfo' + ); + + const res = server.responses + .reverse() + .find(resp => resp.type === 'response' && resp.command === 'quickinfo'); + + expect(res).toBeDefined(); + expect(typeof res?.body).toEqual('object'); + expect(res?.body.documentation).toEqual(`Pokemon.name: String!`); + }, 30000); + + it('gives suggestions with empty line (#190)', async () => { + server.send({ + seq: 12, + type: 'request', + command: 'completionInfo', + arguments: { + file: outfileCombinations, + line: 19, + offset: 3, + includeExternalModuleExports: true, + includeInsertTextCompletions: true, + triggerKind: 1, + }, + }); + + await server.waitForResponse( + response => + response.type === 'response' && response.command === 'completionInfo' + ); + + const res = server.responses + .reverse() + .find( + resp => resp.type === 'response' && resp.command === 'completionInfo' + ); + + expect(res).toBeDefined(); + expect(typeof res?.body.entries).toEqual('object'); + expect(res?.body.entries).toMatchInlineSnapshot(` + [ + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "detail": " AttacksConnection", + }, + "name": "attacks", + "sortText": "0attacks", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "detail": " [EvolutionRequirement]", + }, + "name": "evolutionRequirements", + "sortText": "2evolutionRequirements", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "detail": " [Pokemon]", + }, + "name": "evolutions", + "sortText": "3evolutions", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "description": "Likelihood of an attempt to catch a Pokémon to fail.", + "detail": " Float", + }, + "name": "fleeRate", + "sortText": "4fleeRate", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "detail": " PokemonDimension", + }, + "name": "height", + "sortText": "5height", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "detail": " ID!", + }, + "name": "id", + "sortText": "6id", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "description": "Maximum combat power a Pokémon may achieve at max level.", + "detail": " Int", + }, + "name": "maxCP", + "sortText": "7maxCP", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "description": "Maximum health points a Pokémon may achieve at max level.", + "detail": " Int", + }, + "name": "maxHP", + "sortText": "8maxHP", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "detail": " String!", + }, + "name": "name", + "sortText": "9name", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "detail": " [PokemonType]", + }, + "name": "resistant", + "sortText": "10resistant", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "detail": " [PokemonType]", + }, + "name": "types", + "sortText": "11types", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "detail": " [PokemonType]", + }, + "name": "weaknesses", + "sortText": "12weaknesses", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "detail": " PokemonDimension", + }, + "name": "weight", + "sortText": "13weight", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "description": "The name of the current Object type at runtime.", + "detail": " String!", + }, + "name": "__typename", + "sortText": "14__typename", + }, + ] + `); + }, 30000); });