Skip to content

Commit e8dc958

Browse files
authored
feat: custom config baseDir, embedded fragment def offsets (#1651)
- requires using createConnection() strategy - extension settings on server side for the first time - allow graphql-config loadConnfig() baseDir to be configured - properly calculate offsets for inline operation fragment defs
1 parent f779aa3 commit e8dc958

File tree

5 files changed

+146
-43
lines changed

5 files changed

+146
-43
lines changed

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,8 @@ export async function getGraphQLCache({
6262
loadConfigOptions: LoadConfigOptions;
6363
config?: GraphQLConfig;
6464
}): Promise<GraphQLCacheInterface> {
65-
let graphQLConfig;
66-
if (config) {
67-
graphQLConfig = config;
68-
} else {
65+
let graphQLConfig = config;
66+
if (!graphQLConfig) {
6967
graphQLConfig = await loadConfig(loadConfigOptions);
7068
}
7169
return new GraphQLCache({

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,10 @@ export class Logger implements VSCodeLogger {
6767
const logMessage = `${timestamp} [${severity}] (pid: ${pid}) graphql-language-service-usage-logs: ${message}\n\n`;
6868
// write to the file in tmpdir
6969
fs.appendFile(this._logFilePath, logMessage, _error => {});
70-
// const processSt = (severity === DIAGNOSTIC_SEVERITY.Error) ? process.stderr : process.stdout
71-
process.stderr.write(logMessage, _err => {
72-
// console.error(err);
70+
const processSt =
71+
severity === DIAGNOSTIC_SEVERITY.Error ? process.stderr : process.stdout;
72+
processSt.write(logMessage, err => {
73+
console.error(err);
7374
});
7475
}
7576
}

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

Lines changed: 94 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,16 @@ import type {
5252
DocumentSymbolParams,
5353
SymbolInformation,
5454
WorkspaceSymbolParams,
55+
IConnection,
5556
} from 'vscode-languageserver';
5657

5758
import type { UnnormalizedTypeDefPointer } from '@graphql-tools/load';
5859

5960
import { getGraphQLCache } from './GraphQLCache';
60-
import { parseDocument } from './parseDocument';
61+
import { parseDocument, DEFAULT_SUPPORTED_EXTENSIONS } from './parseDocument';
6162

6263
import { Logger } from './Logger';
63-
import { printSchema } from 'graphql';
64+
import { printSchema, visit, parse, FragmentDefinitionNode } from 'graphql';
6465
import { tmpdir } from 'os';
6566
import { GraphQLExtensionDeclaration } from 'graphql-config';
6667
import type { LoadConfigOptions } from './types';
@@ -74,6 +75,7 @@ type CachedDocumentType = {
7475
};
7576

7677
export class MessageProcessor {
78+
_connection: IConnection;
7779
_graphQLCache!: GraphQLCache;
7880
_graphQLConfig: GraphQLConfig | undefined;
7981
_languageService!: GraphQLLanguageService;
@@ -88,6 +90,7 @@ export class MessageProcessor {
8890
_tmpDirBase: string;
8991
_loadConfigOptions: LoadConfigOptions;
9092
_schemaCacheInit: boolean = false;
93+
_rootPath: string = process.cwd();
9194

9295
constructor({
9396
logger,
@@ -97,6 +100,7 @@ export class MessageProcessor {
97100
config,
98101
parser,
99102
tmpDir,
103+
connection,
100104
}: {
101105
logger: Logger;
102106
fileExtensions: string[];
@@ -105,7 +109,9 @@ export class MessageProcessor {
105109
config?: GraphQLConfig;
106110
parser?: typeof parseDocument;
107111
tmpDir?: string;
112+
connection: IConnection;
108113
}) {
114+
this._connection = connection;
109115
this._textDocumentCache = new Map();
110116
this._isInitialized = false;
111117
this._willShutdown = false;
@@ -130,6 +136,12 @@ export class MessageProcessor {
130136
mkdirp(this._tmpDirBase);
131137
}
132138
}
139+
get connection(): IConnection {
140+
return this._connection;
141+
}
142+
set connection(connection: IConnection) {
143+
this._connection = connection;
144+
}
133145

134146
async handleInitializeRequest(
135147
params: InitializeParams,
@@ -151,15 +163,25 @@ export class MessageProcessor {
151163
definitionProvider: true,
152164
textDocumentSync: 1,
153165
hoverProvider: true,
166+
workspace: {
167+
workspaceFolders: {
168+
supported: true,
169+
changeNotifications: true,
170+
},
171+
},
154172
},
155173
};
156174

157-
const rootPath = configDir ? configDir.trim() : params.rootPath;
158-
if (!rootPath) {
159-
throw new Error(
160-
'`--configDir` option or `rootPath` argument is required.',
175+
this._rootPath = configDir
176+
? configDir.trim()
177+
: params.rootPath || this._rootPath;
178+
if (!this._rootPath) {
179+
this._logger.warn(
180+
'no rootPath configured in extension or server, defaulting to cwd',
161181
);
162182
}
183+
this._loadConfigOptions.rootDir = this._rootPath;
184+
163185
this._graphQLCache = await getGraphQLCache({
164186
parser: this._parser,
165187
loadConfigOptions: this._loadConfigOptions,
@@ -192,6 +214,21 @@ export class MessageProcessor {
192214
return null;
193215
}
194216

217+
const settings = await this._connection.workspace.getConfiguration({
218+
section: 'graphql-config',
219+
});
220+
if (settings.rootDir && settings.rootDir !== this._rootPath) {
221+
this._rootPath = settings.rootDir;
222+
this._loadConfigOptions.rootDir = settings.rootDir;
223+
224+
this._graphQLCache = await getGraphQLCache({
225+
parser: this._parser,
226+
loadConfigOptions: this._loadConfigOptions,
227+
config: this._graphQLConfig,
228+
});
229+
this._languageService = new GraphQLLanguageService(this._graphQLCache);
230+
}
231+
195232
if (!params || !params.textDocument) {
196233
throw new Error('`textDocument` argument is required.');
197234
}
@@ -579,9 +616,9 @@ export class MessageProcessor {
579616
return [];
580617
}
581618

582-
const { query, range } = found;
583-
if (range) {
584-
position.line -= range.start.line;
619+
const { query, range: parentRange } = found;
620+
if (parentRange) {
621+
position.line -= parentRange.start.line;
585622
}
586623

587624
const result = await this._languageService.getDefinition(
@@ -590,9 +627,42 @@ export class MessageProcessor {
590627
textDocument.uri,
591628
);
592629

630+
const inlineFragments: string[] = [];
631+
632+
visit(
633+
parse(query, {
634+
allowLegacySDLEmptyFields: true,
635+
allowLegacySDLImplementsInterfaces: true,
636+
}),
637+
{
638+
FragmentDefinition: (node: FragmentDefinitionNode) => {
639+
inlineFragments.push(node.name.value);
640+
},
641+
},
642+
);
643+
593644
const formatted = result
594645
? result.definitions.map(res => {
595646
const defRange = res.range as Range;
647+
648+
if (parentRange && res.name) {
649+
const isInline = inlineFragments.includes(res.name);
650+
const isEmbedded = DEFAULT_SUPPORTED_EXTENSIONS.includes(
651+
path.extname(textDocument.uri),
652+
);
653+
if (isInline && isEmbedded) {
654+
const vOffset = parentRange.start.line;
655+
defRange.setStart(
656+
(defRange.start.line += vOffset),
657+
defRange.start.character,
658+
);
659+
defRange.setEnd(
660+
(defRange.end.line += vOffset),
661+
defRange.end.character,
662+
);
663+
}
664+
}
665+
596666
return {
597667
// TODO: fix this hack!
598668
// URI is being misused all over this library - there's a link that
@@ -605,7 +675,7 @@ export class MessageProcessor {
605675
? res.path
606676
: `file://${path.resolve(res.path)}`,
607677
range: defRange,
608-
};
678+
} as Location;
609679
})
610680
: [];
611681

@@ -693,6 +763,7 @@ export class MessageProcessor {
693763

694764
return [];
695765
}
766+
696767
_getTextDocuments() {
697768
return Array.from(this._textDocumentCache);
698769
}
@@ -734,8 +805,8 @@ export class MessageProcessor {
734805
appendPath?: string,
735806
) {
736807
const baseDir = this._graphQLCache.getGraphQLConfig().dirpath;
737-
const baseName = path.basename(baseDir);
738-
const basePath = path.join(this._tmpDirBase, baseName);
808+
const workspaceName = path.basename(baseDir);
809+
const basePath = path.join(this._tmpDirBase, workspaceName);
739810
let projectTmpPath = path.join(basePath, 'projects', project.name);
740811
if (!existsSync(projectTmpPath)) {
741812
mkdirp(projectTmpPath);
@@ -744,9 +815,10 @@ export class MessageProcessor {
744815
projectTmpPath = path.join(projectTmpPath, appendPath);
745816
}
746817
if (prependWithProtocol) {
747-
projectTmpPath = `file://${projectTmpPath}`;
818+
return `file://${path.resolve(projectTmpPath)}`;
819+
} else {
820+
return path.resolve(projectTmpPath);
748821
}
749-
return projectTmpPath;
750822
}
751823
async _cacheSchemaFilesForProject(project: GraphQLProjectConfig) {
752824
const schema = project?.schema;
@@ -789,23 +861,27 @@ export class MessageProcessor {
789861
try {
790862
const schema = await this._graphQLCache.getSchema(project.name);
791863
if (schema) {
792-
const schemaText = printSchema(schema, {
864+
let schemaText = printSchema(schema, {
793865
commentDescriptions: true,
794866
});
795867
// file:// protocol path
796-
const uri = this._getTmpProjectPath(project, true, 'schema.graphql');
868+
const uri = this._getTmpProjectPath(
869+
project,
870+
true,
871+
'generated-schema.graphql',
872+
);
797873

798874
// no file:// protocol for fs.writeFileSync()
799875
const fsPath = this._getTmpProjectPath(
800876
project,
801877
false,
802-
'schema.graphql',
878+
'generated-schema.graphql',
803879
);
880+
schemaText = `# This is an automatically generated representation of your schema.\n# Any changes to this file will be overwritten and will not be\n# reflected in the resulting GraphQL schema\n\n${schemaText}`;
804881

805882
const cachedSchemaDoc = this._getCachedDocument(uri);
806883

807884
if (!cachedSchemaDoc) {
808-
console.log({ fsPath });
809885
await writeFileAsync(fsPath, schemaText, {
810886
encoding: 'utf-8',
811887
});

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ import type { DefinitionQueryResult, Outline } from 'graphql-language-service';
2424
import { Logger } from '../Logger';
2525

2626
const baseConfig = { dirpath: __dirname };
27+
2728
describe('MessageProcessor', () => {
2829
const logger = new Logger(tmpdir());
2930
const messageProcessor = new MessageProcessor({
31+
// @ts-ignore
32+
connection: {},
3033
logger,
3134
fileExtensions: ['js'],
3235
graphqlFileExtensions: ['graphql'],
@@ -103,6 +106,17 @@ describe('MessageProcessor', () => {
103106
},
104107
};
105108
});
109+
// @ts-ignore
110+
messageProcessor._connection = {
111+
// @ts-ignore
112+
get workspace() {
113+
return {
114+
getConfiguration: async () => {
115+
return [{}];
116+
},
117+
};
118+
},
119+
};
106120

107121
const initialDocument = {
108122
textDocument: {
@@ -247,6 +261,7 @@ describe('MessageProcessor', () => {
247261

248262
// modified to work with jest.mock() of WatchmanClient
249263
it('runs definition requests', async () => {
264+
jest.setTimeout(10000);
250265
const validQuery = `
251266
{
252267
hero(episode: EMPIRE){

0 commit comments

Comments
 (0)