Skip to content

Commit ba7fd81

Browse files
committed
fix: cleanup, potentially fix project name cache key bug?
1 parent 0395e64 commit ba7fd81

File tree

6 files changed

+170
-31
lines changed

6 files changed

+170
-31
lines changed

packages/graphql-language-service-server/src/GraphQLCache.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -601,10 +601,10 @@ export class GraphQLCache implements GraphQLCacheInterface {
601601
}
602602

603603
getSchema = async (
604-
appName?: string,
604+
projectName: string,
605605
queryHasExtensions?: boolean | null,
606606
): Promise<GraphQLSchema | null> => {
607-
const projectConfig = this._graphQLConfig.getProject(appName);
607+
const projectConfig = this._graphQLConfig.getProject(projectName);
608608

609609
if (!projectConfig) {
610610
return null;
@@ -668,7 +668,7 @@ export class GraphQLCache implements GraphQLCacheInterface {
668668
}
669669

670670
_getProjectName(projectConfig: GraphQLProjectConfig) {
671-
return projectConfig || 'default';
671+
return projectConfig?.name || 'default';
672672
}
673673

674674
/**

packages/graphql-language-service-server/src/MessageProcessor.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import type {
3232
DidOpenTextDocumentParams,
3333
DidChangeConfigurationParams,
3434
Diagnostic,
35-
CompletionItem,
3635
CompletionList,
3736
CancellationToken,
3837
Hover,
@@ -544,9 +543,9 @@ export class MessageProcessor {
544543

545544
async handleCompletionRequest(
546545
params: CompletionParams,
547-
): Promise<CompletionList | Array<CompletionItem>> {
546+
): Promise<CompletionList> {
548547
if (!this._isInitialized) {
549-
return [];
548+
return { items: [], isIncomplete: false };
550549
}
551550

552551
this.validateDocumentAndPosition(params);
@@ -560,7 +559,7 @@ export class MessageProcessor {
560559

561560
const cachedDocument = this._getCachedDocument(textDocument.uri);
562561
if (!cachedDocument) {
563-
return [];
562+
return { items: [], isIncomplete: false };
564563
}
565564

566565
const found = cachedDocument.contents.find(content => {
@@ -572,7 +571,7 @@ export class MessageProcessor {
572571

573572
// If there is no GraphQL query in this file, return an empty result.
574573
if (!found) {
575-
return [];
574+
return { items: [], isIncomplete: false };
576575
}
577576

578577
const { query, range } = found;

packages/graphql-language-service-server/src/__tests__/MessageProcessor.spec.ts

Lines changed: 135 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const defaultFiles = [
1414
] as MockFile[];
1515
const schemaFile: MockFile = [
1616
'schema.graphql',
17-
'type Query { foo: Foo }\n\ntype Foo { bar: String }',
17+
'type Query { foo: Foo }\n\ntype Foo { bar: String }',
1818
];
1919

2020
const genSchemaPath =
@@ -102,6 +102,22 @@ describe('project with simple config and graphql files', () => {
102102
],
103103
});
104104
await project.init('query.graphql');
105+
const initSchemaDefRequest = await project.lsp.handleDefinitionRequest({
106+
textDocument: { uri: project.uri('schema.graphql') },
107+
position: { character: 19, line: 0 },
108+
});
109+
expect(initSchemaDefRequest.length).toEqual(1);
110+
expect(initSchemaDefRequest[0].uri).toEqual(project.uri('schema.graphql'));
111+
expect(serializeRange(initSchemaDefRequest[0].range)).toEqual({
112+
start: {
113+
line: 2,
114+
character: 0,
115+
},
116+
end: {
117+
character: 24,
118+
line: 2,
119+
},
120+
});
105121
expect(project.lsp._logger.error).not.toHaveBeenCalled();
106122
expect(await project.lsp._graphQLCache.getSchema()).toBeDefined();
107123
// TODO: for some reason the cache result formats the graphql query??
@@ -151,8 +167,26 @@ describe('project with simple config and graphql files', () => {
151167
});
152168
const typeCache =
153169
project.lsp._graphQLCache._typeDefinitionsCache.get('/tmp/test-default');
154-
155170
expect(typeCache?.get('Test')?.definition.name.value).toEqual('Test');
171+
172+
// test in-file schema defs! important!
173+
const schemaDefRequest = await project.lsp.handleDefinitionRequest({
174+
textDocument: { uri: project.uri('schema.graphql') },
175+
position: { character: 19, line: 0 },
176+
});
177+
expect(schemaDefRequest.length).toEqual(1);
178+
expect(schemaDefRequest[0].uri).toEqual(project.uri('schema.graphql'));
179+
expect(serializeRange(schemaDefRequest[0].range)).toEqual({
180+
start: {
181+
line: 7,
182+
character: 0,
183+
},
184+
end: {
185+
character: 21,
186+
line: 7,
187+
},
188+
});
189+
156190
// TODO: this fragment should now be invalid
157191
const result = await project.lsp.handleDidOpenOrSaveNotification({
158192
textDocument: { uri: project.uri('fragments.graphql') },
@@ -237,7 +271,7 @@ describe('project with simple config and graphql files', () => {
237271
expect(changeParams?.diagnostics[0].message).toEqual(
238272
'Cannot query field "or" on type "Test".',
239273
);
240-
expect(await project.lsp._graphQLCache.getSchema()).toBeDefined();
274+
expect(await project.lsp._graphQLCache.getSchema('default')).toBeDefined();
241275

242276
// schema file is present and contains schema
243277
const file = await readFile(join(genSchemaPath), { encoding: 'utf-8' });
@@ -305,10 +339,11 @@ describe('project with simple config and graphql files', () => {
305339
});
306340
// lets remove the fragments file
307341
await project.deleteFile('fragments.graphql');
308-
// and add a fragments.ts file
342+
// and add a fragments.ts file, watched
309343
await project.addFile(
310344
'fragments.ts',
311345
'\n\n\nexport const fragment = gql`\n\n fragment T on Test { isTest }\n`',
346+
true,
312347
);
313348

314349
await project.lsp.handleWatchedFilesChangedNotification({
@@ -333,4 +368,100 @@ describe('project with simple config and graphql files', () => {
333368
},
334369
});
335370
});
371+
it('caches multiple projects with files and schema with a URL config and a local schema', async () => {
372+
const project = new MockProject({
373+
files: [
374+
[
375+
'a/fragments.ts',
376+
'\n\n\nexport const fragment = gql`\n\n fragment TestFragment on Test { isTest }\n`',
377+
],
378+
[
379+
'a/query.ts',
380+
'\n\n\nexport const query = gql`query { test() { isTest, ...T } }`',
381+
],
382+
383+
[
384+
'b/query.ts',
385+
'import graphql from "graphql"\n\n\nconst a = graphql` query example { test() { isTest ...T } }`',
386+
],
387+
[
388+
'b/fragments.ts',
389+
'\n\n\nexport const fragment = gql`\n\n fragment T on Test { isTest }\n`',
390+
],
391+
['b/schema.graphql', schemaFile[1]],
392+
[
393+
'package.json',
394+
`{ "graphql": { "projects": {
395+
"a": { "schema": "http://localhost:3100/graphql", "documents": "./a/**" },
396+
"b": { "schema": "./b/schema.graphql", "documents": "./b/**" } }
397+
}
398+
}`,
399+
],
400+
schemaFile,
401+
],
402+
});
403+
404+
const initParams = await project.init('a/query.graphql');
405+
expect(initParams.diagnostics).toEqual([]);
406+
407+
expect(project.lsp._logger.error).not.toHaveBeenCalled();
408+
expect(await project.lsp._graphQLCache.getSchema('a')).toBeDefined();
409+
const file = await readFile(join(genSchemaPath.replace('default', 'a')), {
410+
encoding: 'utf-8',
411+
});
412+
expect(file.split('\n').length).toBeGreaterThan(10);
413+
// add a new typescript file with empty query to the b project
414+
// and expect autocomplete to only show options for project b
415+
await project.addFile(
416+
'b/empty.ts',
417+
'import gql from "graphql-tag"\ngql`query a { }`',
418+
);
419+
const completion = await project.lsp.handleCompletionRequest({
420+
textDocument: { uri: project.uri('b/empty.ts') },
421+
position: { character: 13, line: 1 },
422+
});
423+
424+
expect(completion.items?.length).toEqual(4);
425+
expect(completion.items.map(i => i.label)).toEqual([
426+
'foo',
427+
'__typename',
428+
'__schema',
429+
'__type',
430+
]);
431+
432+
// TODO this didn't work at all, how to register incomplete changes to model autocomplete, etc?
433+
// project.changeFile(
434+
// 'b/schema.graphql',
435+
// schemaFile[1] + '\ntype Example1 { field: }',
436+
// );
437+
// await project.lsp.handleWatchedFilesChangedNotification({
438+
// changes: [
439+
// { uri: project.uri('b/schema.graphql'), type: FileChangeType.Changed },
440+
// ],
441+
// });
442+
// better - fails on a graphql parsing error! annoying
443+
// await project.lsp.handleDidChangeNotification({
444+
// textDocument: { uri: project.uri('b/schema.graphql'), version: 1 },
445+
// contentChanges: [
446+
// { text: schemaFile[1] + '\ntype Example1 { field: }' },
447+
// ],
448+
// });
449+
450+
// const schemaCompletion = await project.lsp.handleCompletionRequest({
451+
// textDocument: { uri: project.uri('b/schema.graphql') },
452+
// position: { character: 23, line: 3 },
453+
// });
454+
// expect(schemaCompletion.items.map(i => i.label)).toEqual([
455+
// 'foo',
456+
// '__typename',
457+
// '__schema',
458+
// '__type',
459+
// ]);
460+
// this confirms that autocomplete respects cross-project boundaries for types
461+
const schemaCompletion = await project.lsp.handleCompletionRequest({
462+
textDocument: { uri: project.uri('b/schema.graphql') },
463+
position: { character: 21, line: 0 },
464+
});
465+
expect(schemaCompletion.items.map(i => i.label)).toEqual(['Foo']);
466+
});
336467
});

packages/graphql-language-service-server/src/__tests__/MessageProcessor.test.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,10 @@ describe('MessageProcessor', () => {
219219
textDocument: { uri: `${queryPathUri}/test13.graphql` },
220220
};
221221
const result = await messageProcessor.handleCompletionRequest(test);
222-
expect(result).toEqual([]);
222+
expect(result).toEqual({
223+
items: [],
224+
isIncomplete: false,
225+
});
223226
});
224227
it('runs completion requests properly when not initialized', async () => {
225228
const test = {
@@ -228,7 +231,10 @@ describe('MessageProcessor', () => {
228231
};
229232
messageProcessor._isInitialized = false;
230233
const result = await messageProcessor.handleCompletionRequest(test);
231-
expect(result).toEqual([]);
234+
expect(result).toEqual({
235+
items: [],
236+
isIncomplete: false,
237+
});
232238
});
233239

234240
it('runs document symbol requests', async () => {
@@ -344,7 +350,7 @@ describe('MessageProcessor', () => {
344350
jest.setTimeout(10000);
345351
const previousConfigurationValue = getConfigurationReturnValue;
346352
getConfigurationReturnValue = null;
347-
const result = await messageProcessor.handleDidChangeConfiguration();
353+
const result = await messageProcessor.handleDidChangeConfiguration({});
348354
expect(result).toEqual({});
349355
getConfigurationReturnValue = previousConfigurationValue;
350356
});

packages/graphql-language-service-server/src/__tests__/__utils__/MockProject.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Logger as VSCodeLogger } from 'vscode-jsonrpc';
44
import { URI } from 'vscode-uri';
55
import { FileChangeType } from 'vscode-languageserver';
66
import { FileChangeTypeKind } from 'graphql-language-service';
7+
import { mock } from 'fetch-mock';
78

89
export type MockFile = [filename: string, text: string];
910

@@ -83,7 +84,7 @@ export class MockProject {
8384
});
8485
return this.lsp.handleDidOpenOrSaveNotification({
8586
textDocument: {
86-
uri: this.uri(filename || this.uri('query.graphql')),
87+
uri: this.uri(filename || 'query.graphql'),
8788
version: 1,
8889
text: this.fileCache.get('query.graphql') || fileText,
8990
},
@@ -106,9 +107,19 @@ export class MockProject {
106107
this.fileCache.set(filename, text);
107108
this.mockFiles();
108109
}
109-
async addFile(filename: string, text: string) {
110+
async addFile(filename: string, text: string, watched = false) {
110111
this.fileCache.set(filename, text);
111112
this.mockFiles();
113+
if (watched) {
114+
await this.lsp.handleWatchedFilesChangedNotification({
115+
changes: [
116+
{
117+
uri: this.uri(filename),
118+
type: FileChangeTypeKind.Created,
119+
},
120+
],
121+
});
122+
}
112123
await this.lsp.handleDidChangeNotification({
113124
contentChanges: [
114125
{
@@ -159,19 +170,16 @@ export class MockProject {
159170
});
160171
}
161172
async deleteFile(filename: string) {
173+
mockfs.restore();
162174
this.fileCache.delete(filename);
163175
this.mockFiles();
164-
await this.lsp.handleDidChangeNotification({
165-
contentChanges: [
176+
await this.lsp.handleWatchedFilesChangedNotification({
177+
changes: [
166178
{
167-
type: FileChangeTypeKind.Deleted,
168-
text: '',
179+
type: FileChangeType.Deleted,
180+
uri: this.uri(filename),
169181
},
170182
],
171-
textDocument: {
172-
uri: this.uri(filename),
173-
version: 2,
174-
},
175183
});
176184
}
177185
get lsp() {

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,6 @@ export interface GraphQLCache {
5050

5151
getProjectForFile: (uri: string) => GraphQLProjectConfig | void;
5252

53-
getObjectTypeDependencies: (
54-
query: string,
55-
fragmentDefinitions: Map<string, ObjectTypeInfo>,
56-
) => Promise<ObjectTypeInfo[]>;
57-
5853
getObjectTypeDependenciesForAST: (
5954
parsedQuery: ASTNode,
6055
fragmentDefinitions: Map<string, ObjectTypeInfo>,
@@ -90,7 +85,7 @@ export interface GraphQLCache {
9085
contents: CachedContent[],
9186
) => Promise<void>;
9287
getSchema: (
93-
appName?: string,
88+
appName: string,
9489
queryHasExtensions?: boolean,
9590
) => Promise<GraphQLSchema | null>;
9691
}

0 commit comments

Comments
 (0)