Skip to content

Commit 089c226

Browse files
committed
fix: third completion context mode, changeset update
1 parent ced343f commit 089c226

File tree

6 files changed

+109
-46
lines changed

6 files changed

+109
-46
lines changed

.changeset/rotten-seahorses-fry.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,23 @@
44
'graphql-language-service-server-cli': minor
55
---
66

7-
Fix many schema and fragment lifecycle issues, for all contexts except for schema updates for url schemas.
7+
Fix many schema and fragment lifecycle issues, not all of them, but many related to cacheing.
88
Note: this makes `cacheSchemaForLookup` enabled by default again for schema first contexts.
99

10-
this fixes multiple cacheing bugs, on writing some in-depth integration coverage for the LSP server.
11-
it also solves several bugs regarding loading config types, and properly restarts the server when there are config changes
10+
This fixes multiple cacheing bugs, upon addomg some in-depth integration test coverage for the LSP server.
11+
It also solves several bugs regarding loading config types, and properly restarts the server and invalidates schema when there are config changes.
1212

1313
### Bugfix Summary
1414

15-
- jump to definition in embedded files offset bug
16-
- cache invalidation for fragments
17-
- schema cache invalidation for schema files
18-
- schema definition lookups & autocomplete crossing into the wrong workspace
15+
- configurable polling updates for network and other code first schema configuration, set to a 30s interval by default. powered by `schemaCacheTTL` which can be configured in the IDE settings (vscode, nvim) or in the graphql config file. (1)
16+
- jump to definition in embedded files offset bug, for both fragments and code files with SDL strings
17+
- cache invalidation for fragments (fragment lookup/autcoomplete data is more accurate, but incomplete/invalid fragments still do not autocomplete or validate, and remember fragment options always filter/validate by the `on` type!)
18+
- schema cache invalidation for schema files - schema updates as you change the SDL files, and the generated file for code first by the `schemaCacheTTL` setting
19+
- schema definition lookups & autocomplete crossing over into the wrong project
20+
21+
**Notes**
22+
23+
1. If possible, configuring for your locally running framework or a registry client to handle schema updates and output to a `schema.graphql` or `introspection.json` will always provide a better experience. many graphql frameworks have this built in! Otherwise, we must use a lazy polling approach.
1924

2025
### Known Bugs Fixed
2126

@@ -24,7 +29,7 @@ it also solves several bugs regarding loading config types, and properly restart
2429
- #3469
2530
- #2422
2631
- #2820
27-
- many others to add here...
32+
- many more!
2833

2934
### Test Improvements
3035

@@ -33,3 +38,9 @@ it also solves several bugs regarding loading config types, and properly restart
3338
- **total increased test coverage of about 25% in the LSP server codebase.**
3439
- many "happy paths" covered for both schema and code first contexts
3540
- many bugs revealed (and their source)
41+
42+
### What's next?
43+
44+
Another stage of the rewrite is already almost ready. This will fix even more bugs and improve memory usage, eliminate redundant parsing and ensure that graphql config's loaders do _all_ of the parsing and heavy lifting, thus honoring all the configs as well. It also significantly reduces the code complexity.
45+
46+
There is also a plan to match Relay LSP's lookup config for either IDE (vscode, nvm, etc) settings as they provide, or by loading modules into your `graphql-config`!

.changeset/silly-yaks-bathe.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
---
2-
'graphiql': patch
32
'graphql-language-service': patch
43
'graphql-language-service-server': patch
54
'graphql-language-service-server-cli': patch
65
'codemirror-graphql': patch
7-
'@graphiql/react': patch
6+
'cm6-graphql': patch
87
'monaco-graphql': patch
8+
'vscode-graphql': patch
99
---
1010

11-
bugfix to completion for SDL type fields
11+
Fixes several issues with Type System (SDL) completion across the ecosystem:
12+
13+
- restores completion for object and input type fields when the schema context is not parseable
14+
- correct top-level completions for either of the unknown, type system or executable definitions. this leads to mixed top level completions when the document is unparseable, but now you are not seemingly restricted to only executable top level definitions
15+
- `.graphqls` ad-hoc standard functionality remains, but is not required, as it is not part of the official spec, and the spec also allows mixed mode documents in theory, and this concept is required when the type is unknown

packages/graphql-language-service/src/interface/__tests__/getAutocompleteSuggestions-test.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,17 @@ describe('getAutocompleteSuggestions', () => {
147147
it('provides correct initial keywords', () => {
148148
expect(testSuggestions('', new Position(0, 0))).toEqual([
149149
{ label: '{' },
150+
{ label: 'extend' },
150151
{ label: 'fragment' },
152+
{ label: 'input' },
153+
{ label: 'interface' },
151154
{ label: 'mutation' },
152155
{ label: 'query' },
156+
{ label: 'scalar' },
157+
{ label: 'schema' },
153158
{ label: 'subscription' },
159+
{ label: 'type' },
160+
{ label: 'union' },
154161
]);
155162

156163
expect(testSuggestions('q', new Position(0, 1))).toEqual([
@@ -159,9 +166,9 @@ describe('getAutocompleteSuggestions', () => {
159166
]);
160167
});
161168

162-
it('provides correct suggestions at where the cursor is', () => {
169+
it('provides correct top level suggestions when a simple query is already present', () => {
163170
// Below should provide initial keywords
164-
expect(testSuggestions(' {}', new Position(0, 0))).toEqual([
171+
expect(testSuggestions(' { id }', new Position(0, 0))).toEqual([
165172
{ label: '{' },
166173
{ label: 'fragment' },
167174
{ label: 'mutation' },
@@ -501,7 +508,7 @@ describe('getAutocompleteSuggestions', () => {
501508
});
502509

503510
describe('with SDL types', () => {
504-
it('provides correct initial keywords', () => {
511+
it('provides correct initial keywords w/ graphqls', () => {
505512
expect(
506513
testSuggestions('', new Position(0, 0), [], { uri: 'schema.graphqls' }),
507514
).toEqual([
@@ -515,6 +522,25 @@ describe('getAutocompleteSuggestions', () => {
515522
]);
516523
});
517524

525+
it('provides correct initial keywords w/out graphqls', () => {
526+
expect(
527+
testSuggestions('', new Position(0, 0), [], { uri: 'schema.graphql' }),
528+
).toEqual([
529+
{ label: '{' },
530+
{ label: 'extend' },
531+
{ label: 'fragment' },
532+
{ label: 'input' },
533+
{ label: 'interface' },
534+
{ label: 'mutation' },
535+
{ label: 'query' },
536+
{ label: 'scalar' },
537+
{ label: 'schema' },
538+
{ label: 'subscription' },
539+
{ label: 'type' },
540+
{ label: 'union' },
541+
]);
542+
});
543+
518544
it('provides correct initial definition keywords', () => {
519545
expect(
520546
testSuggestions('type Type { field: String }\n\n', new Position(0, 31)),
@@ -595,7 +621,7 @@ describe('getAutocompleteSuggestions', () => {
595621
expect(testSuggestions('type Type @', new Position(0, 11))).toEqual([
596622
{ label: 'onAllDefs' },
597623
]));
598-
it('provides correct suggestions on object fields', () =>
624+
it('provides correct suggestions on object field w/ .graphqls', () =>
599625
expect(
600626
testSuggestions('type Type {\n aField: s', new Position(0, 23), [], {
601627
uri: 'schema.graphqls',
@@ -607,6 +633,18 @@ describe('getAutocompleteSuggestions', () => {
607633
{ label: 'TestType' },
608634
{ label: 'TestUnion' },
609635
]));
636+
it('provides correct suggestions on object fields', () =>
637+
expect(
638+
testSuggestions('type Type {\n aField: s', new Position(0, 23), [], {
639+
uri: 'schema.graphql',
640+
}),
641+
).toEqual([
642+
{ label: 'Episode' },
643+
{ label: 'String' },
644+
{ label: 'TestInterface' },
645+
{ label: 'TestType' },
646+
{ label: 'TestUnion' },
647+
]));
610648
// TODO: shouldn't TestType and TestUnion be available here?
611649
it('provides correct filtered suggestions on object fields in regular SDL files', () =>
612650
expect(

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

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -118,27 +118,28 @@ const typeSystemKinds: Kind[] = [
118118
Kind.INPUT_OBJECT_TYPE_EXTENSION,
119119
];
120120

121-
const hasTypeSystemDefinitions = (sdl: string | undefined) => {
122-
let hasTypeSystemDef = false;
121+
const getParsedMode = (sdl: string | undefined): GraphQLDocumentMode => {
122+
let mode = GraphQLDocumentMode.UNKNOWN;
123123
if (sdl) {
124124
try {
125125
visit(parse(sdl), {
126126
enter(node) {
127127
if (node.kind === 'Document') {
128+
mode = GraphQLDocumentMode.EXECUTABLE;
128129
return;
129130
}
130131
if (typeSystemKinds.includes(node.kind)) {
131-
hasTypeSystemDef = true;
132+
mode = GraphQLDocumentMode.TYPE_SYSTEM;
132133
return BREAK;
133134
}
134135
return false;
135136
},
136137
});
137138
} catch {
138-
return hasTypeSystemDef;
139+
return mode;
139140
}
140141
}
141-
return hasTypeSystemDef;
142+
return mode;
142143
};
143144

144145
export type AutocompleteSuggestionOptions = {
@@ -170,22 +171,24 @@ export function getAutocompleteSuggestions(
170171
const state =
171172
token.state.kind === 'Invalid' ? token.state.prevState : token.state;
172173

173-
const mode = options?.mode || getDocumentMode(queryText, options?.uri);
174-
175174
// relieve flow errors by checking if `state` exists
176175
if (!state) {
177176
return [];
178177
}
179178

180179
const { kind, step, prevState } = state;
181180
const typeInfo = getTypeInfo(schema, token.state);
181+
const mode = options?.mode || getDocumentMode(queryText, options?.uri);
182182

183183
// Definition kinds
184184
if (kind === RuleKinds.DOCUMENT) {
185185
if (mode === GraphQLDocumentMode.TYPE_SYSTEM) {
186186
return getSuggestionsForTypeSystemDefinitions(token);
187187
}
188-
return getSuggestionsForExecutableDefinitions(token);
188+
if (mode === GraphQLDocumentMode.EXECUTABLE) {
189+
return getSuggestionsForExecutableDefinitions(token);
190+
}
191+
return getSuggestionsForUnknownDocumentMode(token);
189192
}
190193

191194
if (kind === RuleKinds.EXTEND_DEF) {
@@ -469,38 +472,45 @@ const getInsertText = (field: GraphQLField<null, null>) => {
469472
return null;
470473
};
471474

475+
const typeSystemCompletionItems = [
476+
{ label: 'type', kind: CompletionItemKind.Function },
477+
{ label: 'interface', kind: CompletionItemKind.Function },
478+
{ label: 'union', kind: CompletionItemKind.Function },
479+
{ label: 'input', kind: CompletionItemKind.Function },
480+
{ label: 'scalar', kind: CompletionItemKind.Function },
481+
{ label: 'schema', kind: CompletionItemKind.Function },
482+
];
483+
484+
const executableCompletionItems = [
485+
{ label: 'query', kind: CompletionItemKind.Function },
486+
{ label: 'mutation', kind: CompletionItemKind.Function },
487+
{ label: 'subscription', kind: CompletionItemKind.Function },
488+
{ label: 'fragment', kind: CompletionItemKind.Function },
489+
{ label: '{', kind: CompletionItemKind.Constructor },
490+
];
491+
472492
// Helper functions to get suggestions for each kinds
473493
function getSuggestionsForTypeSystemDefinitions(token: ContextToken) {
474494
return hintList(token, [
475495
{ label: 'extend', kind: CompletionItemKind.Function },
476-
{ label: 'type', kind: CompletionItemKind.Function },
477-
{ label: 'interface', kind: CompletionItemKind.Function },
478-
{ label: 'union', kind: CompletionItemKind.Function },
479-
{ label: 'input', kind: CompletionItemKind.Function },
480-
{ label: 'scalar', kind: CompletionItemKind.Function },
481-
{ label: 'schema', kind: CompletionItemKind.Function },
496+
...typeSystemCompletionItems,
482497
]);
483498
}
484499

485500
function getSuggestionsForExecutableDefinitions(token: ContextToken) {
501+
return hintList(token, executableCompletionItems);
502+
}
503+
504+
function getSuggestionsForUnknownDocumentMode(token: ContextToken) {
486505
return hintList(token, [
487-
{ label: 'query', kind: CompletionItemKind.Function },
488-
{ label: 'mutation', kind: CompletionItemKind.Function },
489-
{ label: 'subscription', kind: CompletionItemKind.Function },
490-
{ label: 'fragment', kind: CompletionItemKind.Function },
491-
{ label: '{', kind: CompletionItemKind.Constructor },
506+
{ label: 'extend', kind: CompletionItemKind.Function },
507+
...executableCompletionItems,
508+
...typeSystemCompletionItems,
492509
]);
493510
}
494511

495512
function getSuggestionsForExtensionDefinitions(token: ContextToken) {
496-
return hintList(token, [
497-
{ label: 'type', kind: CompletionItemKind.Function },
498-
{ label: 'interface', kind: CompletionItemKind.Function },
499-
{ label: 'union', kind: CompletionItemKind.Function },
500-
{ label: 'input', kind: CompletionItemKind.Function },
501-
{ label: 'scalar', kind: CompletionItemKind.Function },
502-
{ label: 'schema', kind: CompletionItemKind.Function },
503-
]);
513+
return hintList(token, typeSystemCompletionItems);
504514
}
505515

506516
function getSuggestionsForFieldNames(
@@ -1280,6 +1290,7 @@ export function getTypeInfo(
12801290
export enum GraphQLDocumentMode {
12811291
TYPE_SYSTEM = 'TYPE_SYSTEM',
12821292
EXECUTABLE = 'EXECUTABLE',
1293+
UNKNOWN = 'UNKNOWN',
12831294
}
12841295

12851296
function getDocumentMode(
@@ -1289,9 +1300,7 @@ function getDocumentMode(
12891300
if (uri?.endsWith('.graphqls')) {
12901301
return GraphQLDocumentMode.TYPE_SYSTEM;
12911302
}
1292-
return hasTypeSystemDefinitions(documentText)
1293-
? GraphQLDocumentMode.TYPE_SYSTEM
1294-
: GraphQLDocumentMode.EXECUTABLE;
1303+
return getParsedMode(documentText);
12951304
}
12961305

12971306
function unwrapType(state: State): State {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import type {
3838
GraphQLProjectConfig,
3939
GraphQLExtensionDeclaration,
4040
} from 'graphql-config';
41+
import { GraphQLDocumentMode } from './interface';
4142

4243
export type {
4344
GraphQLConfig,

packages/vscode-graphql/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@
111111
"type": [
112112
"number"
113113
],
114-
"description": "Schema cache ttl in milliseconds before requesting a fresh schema when caching the local schema file is enabled. Defaults to 30000 (30 seconds).",
114+
"description": "Schema cache ttl in milliseconds - the interval before requesting a fresh schema when caching the local schema file is enabled. Defaults to 30000 (30 seconds).",
115115
"default": 30000
116116
},
117117
"graphql-config.load.rootDir": {

0 commit comments

Comments
 (0)