Skip to content

Commit c5325dd

Browse files
motemenacao
andauthored
fix "Unknown fragment" validation error (#3861)
* fix unknown fragment error * use MessageProcessor._parser * add changeset * fix: fs import for mock-fs `mock-fs` uses `process.binding('fs')`, so it cannot support mocking the `node:fs` import alias it seems. this should fix test failures in CI (for node 22 at least) --------- Co-authored-by: Rikki Schulte <[email protected]>
1 parent 136510b commit c5325dd

File tree

3 files changed

+85
-12
lines changed

3 files changed

+85
-12
lines changed

.changeset/warm-sheep-brake.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'graphql-language-service-server': patch
3+
---
4+
5+
fix parsing non-graphql documents

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

+14-10
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
* LICENSE file in the root directory of this source tree.
77
*
88
*/
9-
10-
import { existsSync, mkdirSync } from 'node:fs';
9+
// do not change to node:fs import, or it will break mock-fs
10+
import { existsSync, mkdirSync } from 'fs';
1111
import { mkdir, readFile, writeFile } from 'node:fs/promises';
1212
import * as path from 'node:path';
1313
import { URI } from 'vscode-uri';
@@ -1223,25 +1223,29 @@ export class MessageProcessor {
12231223
private async _cacheDocumentFilesforProject(project: GraphQLProjectConfig) {
12241224
try {
12251225
const documents = await project.getDocuments();
1226-
return Promise.all(
1227-
documents.map(async document => {
1228-
if (!document.location || !document.rawSDL) {
1229-
return;
1230-
}
1226+
const documentLocations = new Set(
1227+
documents
1228+
.filter(doc => doc.location && doc.rawSDL)
1229+
.map(doc => doc.location!),
1230+
);
12311231

1232-
let filePath = document.location;
1232+
return Promise.all(
1233+
Array.from(documentLocations).map(async loc => {
1234+
let filePath = loc;
12331235
if (!path.isAbsolute(filePath)) {
1234-
filePath = path.join(project.dirpath, document.location);
1236+
filePath = path.join(project.dirpath, loc);
12351237
}
12361238

12371239
// build full system URI path with protocol
12381240
const uri = URI.file(filePath).toString();
12391241

1242+
const fileContent = await readFile(filePath, 'utf-8');
12401243
// I would use the already existing graphql-config AST, but there are a few reasons we can't yet
1241-
const contents = await this._parser(document.rawSDL, uri);
1244+
const contents = await this._parser(fileContent, uri);
12421245
if (!contents[0]?.query) {
12431246
return;
12441247
}
1248+
12451249
await this._updateObjectTypeDefinition(uri, contents);
12461250
await this._updateFragmentDefinition(uri, contents);
12471251
await this._invalidateCache({ version: 1, uri }, uri, contents);

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

+66-2
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ describe('MessageProcessor with config', () => {
218218
character: 0,
219219
},
220220
end: {
221-
line: 2,
222-
character: 1,
221+
line: 0,
222+
character: 25,
223223
},
224224
});
225225

@@ -542,6 +542,9 @@ describe('MessageProcessor with config', () => {
542542

543543
expect(project.lsp._logger.error).not.toHaveBeenCalled();
544544
expect(await project.lsp._graphQLCache.getSchema('a')).toBeDefined();
545+
expect(project.lsp._logger.info).not.toHaveBeenCalledWith(
546+
expect.stringMatching(/SyntaxError: Unexpected token/),
547+
);
545548

546549
fetchMock.restore();
547550
mockSchema(
@@ -636,4 +639,65 @@ describe('MessageProcessor with config', () => {
636639
expect(project.lsp._logger.error).not.toHaveBeenCalled();
637640
project.lsp.handleShutdownRequest();
638641
});
642+
643+
it('correctly handles a fragment inside a TypeScript file', async () => {
644+
const project = new MockProject({
645+
files: [
646+
[
647+
'schema.graphql',
648+
`
649+
type Item {
650+
foo: String
651+
bar: Int
652+
}
653+
654+
type Query {
655+
items: [Item]
656+
}
657+
`,
658+
],
659+
[
660+
'query.ts',
661+
`
662+
import gql from 'graphql-tag'
663+
664+
const query = gql\`
665+
query {
666+
items {
667+
...ItemFragment
668+
}
669+
}
670+
\`
671+
`,
672+
],
673+
[
674+
'fragments.ts',
675+
`
676+
import gql from 'graphql-tag'
677+
678+
export const ItemFragment = gql\`
679+
fragment ItemFragment on Item {
680+
foo
681+
bar
682+
}
683+
\`
684+
`,
685+
],
686+
[
687+
'graphql.config.json',
688+
'{ "schema": "./schema.graphql", "documents": "./**.{graphql,ts}" }',
689+
],
690+
],
691+
});
692+
693+
const initParams = await project.init('query.ts');
694+
expect(initParams.diagnostics).toEqual([]);
695+
696+
const fragmentDefinition = await project.lsp.handleDefinitionRequest({
697+
textDocument: { uri: project.uri('query.ts') },
698+
position: { character: 10, line: 6 },
699+
});
700+
701+
expect(fragmentDefinition[0]?.uri).toEqual(project.uri('fragments.ts'));
702+
});
639703
});

0 commit comments

Comments
 (0)