Mirror: TypeScript LSP plugin that finds GraphQL documents in your code and provides diagnostics, auto-complete and hover-information.

fix: support suggestions in type-conditions (#262)

Changed files
+127 -7
.changeset
packages
graphqlsp
test
e2e
fixture-project-tada
+5
.changeset/chilly-coins-end.md
···
+
---
+
'@0no-co/graphqlsp': patch
+
---
+
+
Fix type-condition suggestions
+17 -2
packages/graphqlsp/src/ast/token.ts
···
let cPos = template.getStart() + 1;
let foundToken: Token | undefined = undefined;
+
let prevToken: Token | undefined = undefined;
for (let line = 0; line < input.length; line++) {
+
if (foundToken) continue;
const lPos = cPos - 1;
const stream = new CharacterStream(input[line] + '\n');
while (!stream.eol()) {
···
lPos + stream.getStartOfToken() + 1 <= cursorPosition &&
lPos + stream.getCurrentPosition() >= cursorPosition
) {
-
foundToken = {
+
foundToken = prevToken
+
? prevToken
+
: {
+
line,
+
start: stream.getStartOfToken() + 1,
+
end: stream.getCurrentPosition(),
+
string,
+
state,
+
tokenKind: token,
+
};
+
break;
+
} else if (string === 'on') {
+
prevToken = {
line,
start: stream.getStartOfToken() + 1,
end: stream.getCurrentPosition(),
···
state,
tokenKind: token,
};
-
break;
+
} else {
+
prevToken = undefined;
}
}
+23 -5
packages/graphqlsp/src/autoComplete.ts
···
) as Array<FragmentDefinitionNode>;
} catch (e) {}
-
let suggestions = getAutocompleteSuggestions(schema, queryText, cursor);
-
let spreadSuggestions = getSuggestionsForFragmentSpread(
-
token,
-
getTypeInfo(schema, token.state),
+
const isOnTypeCondition =
+
token.string === 'on' && token.state.kind === 'TypeCondition';
+
let suggestions = getAutocompleteSuggestions(
schema,
queryText,
-
fragments
+
cursor,
+
isOnTypeCondition
+
? {
+
...token,
+
state: {
+
...token.state,
+
step: 1,
+
},
+
type: null,
+
}
+
: undefined
);
+
let spreadSuggestions = !isOnTypeCondition
+
? getSuggestionsForFragmentSpread(
+
token,
+
getTypeInfo(schema, token.state),
+
schema,
+
queryText,
+
fragments
+
)
+
: [];
const state =
token.state.kind === 'Invalid' ? token.state.prevState : token.state;
+17
test/e2e/fixture-project-tada/fixtures/type-condition.ts
···
+
import { graphql } from './graphql';
+
import { PokemonFields } from './fragment';
+
+
// prettier-ignore
+
const x = graphql(`
+
query Pok($limit: Int!) {
+
pokemons(limit: $limit) {
+
id
+
name
+
fleeRate
+
classification
+
...pokemonFields
+
__typename
+
... on
+
}
+
}
+
`, [PokemonFields]);
+4
test/e2e/fixture-project-tada/introspection.d.ts
···
{
"kind": "SCALAR",
"name": "Boolean"
+
},
+
{
+
"kind": "SCALAR",
+
"name": "Any"
}
],
"directives": []
+61
test/e2e/tada.test.ts
···
const projectPath = path.resolve(__dirname, 'fixture-project-tada');
describe('Fragment + operations', () => {
const outfileCombo = path.join(projectPath, 'simple.ts');
+
const outfileTypeCondition = path.join(projectPath, 'type-condition.ts');
const outfileUnusedFragment = path.join(projectPath, 'unused-fragment.ts');
const outfileCombinations = path.join(projectPath, 'fragment.ts');
···
server.sendCommand('open', {
file: outfileCombo,
+
fileContent: '// empty',
+
scriptKindName: 'TS',
+
} satisfies ts.server.protocol.OpenRequestArgs);
+
server.sendCommand('open', {
+
file: outfileTypeCondition,
fileContent: '// empty',
scriptKindName: 'TS',
} satisfies ts.server.protocol.OpenRequestArgs);
···
),
},
{
+
file: outfileTypeCondition,
+
fileContent: fs.readFileSync(
+
path.join(projectPath, 'fixtures/type-condition.ts'),
+
'utf-8'
+
),
+
},
+
{
file: outfileCombo,
fileContent: fs.readFileSync(
path.join(projectPath, 'fixtures/simple.ts'),
···
tmpfile: outfileCombo,
} satisfies ts.server.protocol.SavetoRequestArgs);
server.sendCommand('saveto', {
+
file: outfileTypeCondition,
+
tmpfile: outfileTypeCondition,
+
} satisfies ts.server.protocol.SavetoRequestArgs);
+
server.sendCommand('saveto', {
file: outfileCombinations,
tmpfile: outfileCombinations,
} satisfies ts.server.protocol.SavetoRequestArgs);
···
fs.unlinkSync(outfileUnusedFragment);
fs.unlinkSync(outfileCombinations);
fs.unlinkSync(outfileCombo);
+
fs.unlinkSync(outfileTypeCondition);
} catch {}
});
···
},
"name": "__typename",
"sortText": "14__typename",
+
},
+
]
+
`);
+
}, 30000);
+
+
it('gives suggestions for type-conditions (#261)', async () => {
+
server.send({
+
seq: 13,
+
type: 'request',
+
command: 'completionInfo',
+
arguments: {
+
file: outfileTypeCondition,
+
line: 14,
+
offset: 14,
+
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": {
+
"description": "",
+
},
+
"name": "Pokemon",
+
"sortText": "0",
},
]
`);