vscode-apollo 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.circleci/config.yml +1 -1
  2. package/.github/workflows/build-prs.yml +55 -0
  3. package/.github/workflows/release.yml +1 -1
  4. package/.gitleaks.toml +10 -3
  5. package/.vscodeignore +0 -1
  6. package/CHANGELOG.md +26 -0
  7. package/README.md +68 -52
  8. package/package.json +31 -3
  9. package/renovate.json +5 -5
  10. package/sampleWorkspace/httpSchema/apollo.config.ts +2 -0
  11. package/sampleWorkspace/httpSchema/self-signed.crt +22 -0
  12. package/sampleWorkspace/httpSchema/self-signed.key +28 -0
  13. package/sampleWorkspace/localSchemaArray/apollo.config.json +9 -0
  14. package/sampleWorkspace/rover/apollo.config.yaml +2 -0
  15. package/sampleWorkspace/rover/supergraph.yaml +0 -0
  16. package/schemas/apollo.config.schema.json +184 -0
  17. package/src/__e2e__/mockServer.js +37 -11
  18. package/src/__e2e__/mocks.js +11 -7
  19. package/src/__e2e__/runTests.js +8 -6
  20. package/src/build.js +53 -1
  21. package/src/language-server/__e2e__/studioGraph.e2e.ts +4 -3
  22. package/src/language-server/config/__tests__/loadConfig.ts +35 -2
  23. package/src/language-server/config/cache-busting-resolver.js +65 -0
  24. package/src/language-server/config/cache-busting-resolver.types.ts +45 -0
  25. package/src/language-server/config/config.ts +136 -60
  26. package/src/language-server/config/loadConfig.ts +27 -6
  27. package/src/language-server/config/loadTsConfig.ts +74 -38
  28. package/src/language-server/project/base.ts +8 -8
  29. package/src/language-server/project/rover/DocumentSynchronization.ts +44 -22
  30. package/src/language-server/project/rover/project.ts +6 -0
  31. package/src/language-server/providers/schema/endpoint.ts +15 -8
  32. package/src/language-server/server.ts +8 -7
  33. package/src/language-server/workspace.ts +2 -5
  34. package/src/languageServerClient.ts +3 -1
  35. package/syntaxes/graphql.json +2 -2
  36. package/sampleWorkspace/localSchemaArray/apollo.config.js +0 -12
  37. package/sampleWorkspace/rover/apollo.config.js +0 -3
  38. /package/sampleWorkspace/localSchema/{apollo.config.js → apollo.config.ts} +0 -0
@@ -25,7 +25,10 @@ function ignoredFieldWarning(
25
25
  Debug.warning(getMessage(ctx.path.join(".")));
26
26
  }
27
27
  })
28
- .optional();
28
+ .optional()
29
+ .describe(
30
+ `This option is no longer supported, please remove it from your configuration file.`,
31
+ );
29
32
  }
30
33
  export interface Context {
31
34
  apiKey?: string;
@@ -34,53 +37,113 @@ export interface Context {
34
37
  }
35
38
  const contextStore = new AsyncLocalStorage<Context>();
36
39
 
37
- const studioServiceConfig = z.string();
40
+ const NAME_DESCRIPTION =
41
+ "The name your project will be referred to by the Apollo GraphQL extension.";
38
42
 
39
- const remoteServiceConfig = z.object({
40
- name: z.string().optional(),
41
- url: z.string(),
42
- headers: z.record(z.string()).default({}),
43
- skipSSLValidation: z.boolean().default(false),
44
- });
43
+ const studioServiceConfig = z
44
+ .string()
45
+ .describe(
46
+ "The name of the Apollo Studio graph to use. Alternatively pass in an object to configure a local schema.",
47
+ );
48
+
49
+ const remoteServiceConfig = z
50
+ .object({
51
+ name: z.string().optional().describe(NAME_DESCRIPTION),
52
+ url: z
53
+ .string()
54
+ .describe(
55
+ "URL of a GraphQL to use for the GraphQL Schema for this project. Needs introspection enabled.",
56
+ ),
57
+ headers: z
58
+ .record(z.string())
59
+ .default({})
60
+ .describe("Additional headers to send to the server."),
61
+ skipSSLValidation: z
62
+ .boolean()
63
+ .default(false)
64
+ .describe(
65
+ "Skip SSL validation. May be required for self-signed certificates.",
66
+ ),
67
+ })
68
+ .describe("Configuration for using a local schema from a URL.");
45
69
  export type RemoteServiceConfig = z.infer<typeof remoteServiceConfig>;
46
70
 
47
- const localServiceConfig = z.object({
48
- name: z.string().optional(),
49
- localSchemaFile: z.union([z.string(), z.array(z.string())]),
50
- });
71
+ const LOCAL_SCHEMA_FILE_DESCRIPTION =
72
+ "Path to a local schema file to use as GraphQL Schema for this project. Can be a string or an array of strings to merge multiple partial schemas into one.";
73
+ const localServiceConfig = z
74
+ .object({
75
+ name: z.string().optional().describe(NAME_DESCRIPTION),
76
+ localSchemaFile: z
77
+ .union([
78
+ z.string().describe(LOCAL_SCHEMA_FILE_DESCRIPTION),
79
+ z.array(z.string()).describe(LOCAL_SCHEMA_FILE_DESCRIPTION),
80
+ ])
81
+ .describe(LOCAL_SCHEMA_FILE_DESCRIPTION),
82
+ })
83
+ .describe("Configuration for using a local schema from a file.");
51
84
  export type LocalServiceConfig = z.infer<typeof localServiceConfig>;
52
85
 
53
- const clientServiceConfig = z.preprocess(
54
- (value) => value || contextStore.getStore()?.serviceName,
55
- z.union([studioServiceConfig, remoteServiceConfig, localServiceConfig]),
56
- );
86
+ const clientServiceConfig = z
87
+ .preprocess(
88
+ (value) => value || contextStore.getStore()?.serviceName,
89
+ z.union([studioServiceConfig, remoteServiceConfig, localServiceConfig]),
90
+ )
91
+ .describe(
92
+ "A string to refer to a graph in Apollo Studio, or an object for a local schema.",
93
+ );
57
94
  export type ClientServiceConfig = z.infer<typeof clientServiceConfig>;
58
95
 
59
- const clientConfig = z.object({
60
- service: clientServiceConfig,
61
- validationRules: z
62
- .union([
63
- z.array(z.custom<ValidationRule>()),
64
- z.function().args(z.custom<ValidationRule>()).returns(z.boolean()),
65
- ])
66
- .optional(),
67
- // maybe shared with rover?
68
- includes: z.array(z.string()).optional(),
69
- // maybe shared with rover?
70
- excludes: z.array(z.string()).default(["**/node_modules", "**/__tests__"]),
71
- // maybe shared with rover?
72
- tagName: z.string().default("gql"),
73
- // removed:
74
- clientOnlyDirectives: ignoredFieldWarning(),
75
- clientSchemaDirectives: ignoredFieldWarning(),
76
- statsWindow: ignoredFieldWarning(),
77
- name: ignoredFieldWarning(),
78
- referenceId: ignoredFieldWarning(),
79
- version: ignoredFieldWarning(),
80
- });
96
+ const VALIDATION_RULES_DESCRIPTION =
97
+ "Additional validation rules to check for. To use this feature, please use a configuration file format that allows passing JavaScript objects.";
98
+ export const clientConfig = z
99
+ .object({
100
+ service: clientServiceConfig,
101
+ validationRules: z
102
+ .union([
103
+ z
104
+ .array(z.custom<ValidationRule>())
105
+ .describe(VALIDATION_RULES_DESCRIPTION),
106
+ z
107
+ .function()
108
+ .args(z.custom<ValidationRule>())
109
+ .returns(z.boolean())
110
+ .describe(VALIDATION_RULES_DESCRIPTION),
111
+ ])
112
+ .optional()
113
+ .describe(VALIDATION_RULES_DESCRIPTION),
114
+ // maybe shared with rover?
115
+ includes: z
116
+ .array(z.string())
117
+ .optional()
118
+ .describe(
119
+ "An array of glob patterns this project should be active on. The Apollo GraphQL extension will only support IntelliSense-like features in files listed here.",
120
+ ),
121
+ // maybe shared with rover?
122
+ excludes: z
123
+ .array(z.string())
124
+ .default(["**/node_modules", "**/__tests__"])
125
+ .describe(
126
+ "Files to exclude from this project. The Apollo GraphQL extension will not provide IntelliSense-like features in these files.",
127
+ ),
128
+ // maybe shared with rover?
129
+ tagName: z
130
+ .string()
131
+ .default("gql")
132
+ .describe(
133
+ "The name of the template literal tag or function used in JavaScript files to declare GraphQL Documents.",
134
+ ),
135
+ // removed:
136
+ clientOnlyDirectives: ignoredFieldWarning(),
137
+ clientSchemaDirectives: ignoredFieldWarning(),
138
+ statsWindow: ignoredFieldWarning(),
139
+ name: ignoredFieldWarning(),
140
+ referenceId: ignoredFieldWarning(),
141
+ version: ignoredFieldWarning(),
142
+ })
143
+ .describe("Configuration for a Client project.");
81
144
  export type ClientConfigFormat = z.infer<typeof clientConfig>;
82
145
 
83
- const roverConfig = z.object({
146
+ export const roverConfig = z.object({
84
147
  bin: z
85
148
  .preprocess(
86
149
  (val) => val || which.sync("rover", { nothrow: true }) || undefined,
@@ -104,18 +167,19 @@ const roverConfig = z.object({
104
167
  message:
105
168
  "Rover binary is not marked as an executable. If you are using OS X or Linux, ensure to set the executable bit.",
106
169
  },
107
- ),
108
- profile: z.string().optional(),
170
+ )
171
+ .describe("The path to your Rover binary. If omitted, will look in PATH."),
172
+ profile: z.string().optional().describe("The name of the profile to use."),
109
173
  supergraphConfig: z
110
174
  .preprocess((value) => {
111
175
  if (value !== undefined) return value;
112
176
  const configPath = contextStore.getStore()?.configPath!;
113
- const supergraphConfig = join(configPath, "supergraph.yml");
177
+ const supergraphConfig = join(configPath, "supergraph.yaml");
114
178
  return existsSync(supergraphConfig) ? supergraphConfig : undefined;
115
179
  }, z.string().nullable().optional())
116
180
  .describe(
117
- "The path to your `supergraph.yml` file. \n" +
118
- "Defaults to a `supergraph.yml` in the folder of your `apollo.config.js`, if there is one.",
181
+ "The path to your `supergraph.yaml` file. \n" +
182
+ "Defaults to a `supergraph.yaml` in the folder of your `apollo.config.js`, if there is one.",
119
183
  ),
120
184
  extraArgs: z
121
185
  .array(z.string())
@@ -124,24 +188,36 @@ const roverConfig = z.object({
124
188
  });
125
189
  type RoverConfigFormat = z.infer<typeof roverConfig>;
126
190
 
127
- const engineConfig = z.object({
128
- endpoint: z
129
- .string()
130
- .default(
131
- process.env.APOLLO_ENGINE_ENDPOINT ||
132
- "https://graphql.api.apollographql.com/api/graphql",
133
- ),
134
- apiKey: z.preprocess(
135
- (val) => val || contextStore.getStore()?.apiKey,
136
- z.string().optional(),
137
- ),
138
- });
191
+ export const engineConfig = z
192
+ .object({
193
+ endpoint: z
194
+ .string()
195
+ .default(
196
+ process.env.APOLLO_ENGINE_ENDPOINT ||
197
+ "https://graphql.api.apollographql.com/api/graphql",
198
+ )
199
+ .describe("The URL of the Apollo Studio API."),
200
+ apiKey: z
201
+ .preprocess(
202
+ (val) => val || contextStore.getStore()?.apiKey,
203
+ z.string().optional(),
204
+ )
205
+ .describe(
206
+ "The API key to use for Apollo Studio. If possible, use a `.env` file or `.env.local` file instead to store secrets like this.",
207
+ ),
208
+ })
209
+ .describe("Network configuration for Apollo Studio API.");
139
210
  export type EngineConfig = z.infer<typeof engineConfig>;
140
211
 
141
- const baseConfig = z.object({
212
+ export const baseConfig = z.object({
142
213
  engine: engineConfig.default({}),
143
- client: z.unknown().optional(),
144
- rover: z.unknown().optional(),
214
+ client: z.unknown().optional().describe(clientConfig.description!),
215
+ ...ifRoverAvailable(
216
+ {
217
+ rover: z.unknown().optional(),
218
+ },
219
+ {},
220
+ ),
145
221
  service: ignoredFieldWarning(
146
222
  (path) =>
147
223
  `Service-type projects are no longer supported. Please remove the "${path}" field from your configuration file.`,
@@ -268,7 +344,7 @@ export abstract class ApolloConfig {
268
344
  get configDirURI() {
269
345
  // if the filepath has a _file_ in it, then we get its dir
270
346
  return this.configURI &&
271
- this.configURI.fsPath.match(/\.(ts|js|cjs|mjs|json)$/i)
347
+ this.configURI.fsPath.match(/\.(ts|js|cjs|mjs|yaml|yml|json)$/i)
272
348
  ? URI.parse(dirname(this.configURI.fsPath))
273
349
  : this.configURI;
274
350
  }
@@ -1,4 +1,4 @@
1
- import { cosmiconfig, defaultLoaders } from "cosmiconfig";
1
+ import { cosmiconfig, defaultLoaders, Loader } from "cosmiconfig";
2
2
  import { dirname, resolve } from "path";
3
3
  import { readFileSync, existsSync, lstatSync } from "fs";
4
4
  import {
@@ -9,18 +9,22 @@ import {
9
9
  import { getServiceFromKey } from "./utils";
10
10
  import { URI } from "vscode-uri";
11
11
  import { Debug } from "../utilities";
12
- import { loadTs } from "./loadTsConfig";
12
+ import { ParseError, parse as parseJsonC } from "jsonc-parser";
13
+ import { loadJs, loadTs } from "./loadTsConfig";
13
14
 
14
15
  // config settings
15
16
  const MODULE_NAME = "apollo";
16
- const defaultFileNames = [
17
+ export const supportedConfigFileNames = [
17
18
  "package.json",
18
19
  `${MODULE_NAME}.config.js`,
19
20
  `${MODULE_NAME}.config.ts`,
20
21
  `${MODULE_NAME}.config.mjs`,
21
22
  `${MODULE_NAME}.config.cjs`,
23
+ `${MODULE_NAME}.config.yaml`,
24
+ `${MODULE_NAME}.config.yml`,
25
+ `${MODULE_NAME}.config.json`,
22
26
  ];
23
- const envFileNames = [".env", ".env.local"];
27
+ export const envFileNames = [".env", ".env.local"];
24
28
 
25
29
  export const keyEnvVar = "APOLLO_KEY";
26
30
 
@@ -39,14 +43,31 @@ export type ConfigResult<T> = {
39
43
 
40
44
  // XXX load .env files automatically
41
45
 
46
+ const loadJsonc: Loader = (filename, contents) => {
47
+ const errors: ParseError[] = [];
48
+ try {
49
+ return parseJsonC(contents, errors);
50
+ } finally {
51
+ if (errors.length) {
52
+ Debug.error(
53
+ `Error parsing JSONC file ${filename}, file might not be valid JSONC`,
54
+ );
55
+ }
56
+ }
57
+ };
58
+
42
59
  export async function loadConfig({
43
60
  configPath,
44
61
  }: LoadConfigSettings): Promise<ApolloConfig | null> {
45
62
  const explorer = cosmiconfig(MODULE_NAME, {
46
- searchPlaces: defaultFileNames,
63
+ searchPlaces: supportedConfigFileNames,
47
64
  loaders: {
48
65
  ...defaultLoaders,
49
- [".ts"]: loadTs,
66
+ ".ts": loadTs,
67
+ ".mjs": loadJs,
68
+ ".cjs": loadJs,
69
+ ".js": loadJs,
70
+ ".json": loadJsonc,
50
71
  },
51
72
  });
52
73
 
@@ -1,56 +1,78 @@
1
- import { Loader, defaultLoaders } from "cosmiconfig";
1
+ import { Loader } from "cosmiconfig";
2
2
  import { dirname } from "node:path";
3
- import { rm, writeFile } from "node:fs/promises";
4
- import { existsSync } from "node:fs";
5
-
3
+ import typescript from "typescript";
4
+ import { pathToFileURL } from "node:url";
5
+ import { register } from "node:module";
6
+ import { ImportAttributes } from "./cache-busting-resolver.types";
6
7
  // implementation based on https://github.com/cosmiconfig/cosmiconfig/blob/a5a842547c13392ebb89a485b9e56d9f37e3cbd3/src/loaders.ts
7
8
  // Copyright (c) 2015 David Clark licensed MIT. Full license can be found here:
8
9
  // https://github.com/cosmiconfig/cosmiconfig/blob/a5a842547c13392ebb89a485b9e56d9f37e3cbd3/LICENSE
9
10
 
10
- let typescript: typeof import("typescript");
11
+ if (process.env.JEST_WORKER_ID === undefined) {
12
+ register(
13
+ pathToFileURL(require.resolve("./config/cache-busting-resolver.js")),
14
+ );
15
+ } else {
16
+ register(pathToFileURL(require.resolve("./cache-busting-resolver.js")));
17
+ }
18
+
11
19
  export const loadTs: Loader = async function loadTs(filepath, content) {
12
20
  try {
13
- return await defaultLoaders[".ts"](filepath, content);
21
+ return await load(filepath, content, "module", {
22
+ module: typescript.ModuleKind.ES2022,
23
+ });
14
24
  } catch (error) {
15
25
  if (
16
- !(error instanceof Error) ||
17
- !error.message.includes("module is not defined")
18
- )
26
+ error instanceof Error &&
27
+ // [ERROR] ReferenceError: module is not defined in ES module scope
28
+ error.message.includes("module is not defined")
29
+ ) {
30
+ return await load(filepath, content, "commonjs", {
31
+ module: typescript.ModuleKind.CommonJS,
32
+ });
33
+ } else {
19
34
  throw error;
35
+ }
20
36
  }
37
+ };
21
38
 
22
- if (typescript === undefined) {
23
- typescript = await import("typescript");
24
- }
25
- const compiledFilepath = `${filepath.slice(0, -2)}cjs`;
39
+ async function load(
40
+ filepath: string,
41
+ content: string,
42
+ type: "module" | "commonjs",
43
+ compilerOptions: Partial<import("typescript").CompilerOptions>,
44
+ ) {
26
45
  let transpiledContent;
46
+
27
47
  try {
28
- try {
29
- const config = resolveTsConfig(dirname(filepath)) ?? {};
30
- config.compilerOptions = {
31
- ...config.compilerOptions,
32
- module: typescript.ModuleKind.CommonJS,
33
- moduleResolution: typescript.ModuleResolutionKind.Bundler,
34
- target: typescript.ScriptTarget.ES2022,
35
- noEmit: false,
36
- };
37
- transpiledContent = typescript.transpileModule(
38
- content,
39
- config,
40
- ).outputText;
41
- await writeFile(compiledFilepath, transpiledContent);
42
- } catch (error: any) {
43
- error.message = `TypeScript Error in ${filepath}:\n${error.message}`;
44
- throw error;
45
- }
46
- // eslint-disable-next-line @typescript-eslint/return-await
47
- return await defaultLoaders[".js"](compiledFilepath, transpiledContent);
48
- } finally {
49
- if (existsSync(compiledFilepath)) {
50
- await rm(compiledFilepath);
51
- }
48
+ const config = resolveTsConfig(dirname(filepath)) ?? {};
49
+ config.compilerOptions = {
50
+ ...config.compilerOptions,
51
+
52
+ moduleResolution: typescript.ModuleResolutionKind.Bundler,
53
+ target: typescript.ScriptTarget.ES2022,
54
+ noEmit: false,
55
+ ...compilerOptions,
56
+ };
57
+ transpiledContent = typescript.transpileModule(content, config).outputText;
58
+ } catch (error: any) {
59
+ error.message = `TypeScript Error in ${filepath}:\n${error.message}`;
60
+ throw error;
52
61
  }
53
- };
62
+ // eslint-disable-next-line @typescript-eslint/return-await
63
+ const imported = await import(
64
+ filepath,
65
+ //@ts-ignore
66
+ {
67
+ with: {
68
+ as: "cachebust",
69
+ contents: transpiledContent,
70
+ format: type,
71
+ } satisfies ImportAttributes,
72
+ }
73
+ );
74
+ return imported.default;
75
+ }
54
76
 
55
77
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
78
  function resolveTsConfig(directory: string): any {
@@ -68,3 +90,17 @@ function resolveTsConfig(directory: string): any {
68
90
  }
69
91
  return;
70
92
  }
93
+
94
+ export const loadJs: Loader = async function loadJs(filepath, contents) {
95
+ return (
96
+ await import(
97
+ filepath, // @ts-ignore
98
+ {
99
+ with: {
100
+ as: "cachebust",
101
+ contents,
102
+ } satisfies ImportAttributes,
103
+ }
104
+ )
105
+ ).default;
106
+ };
@@ -18,7 +18,13 @@ import { TextDocument } from "vscode-languageserver-textdocument";
18
18
 
19
19
  import type { LoadingHandler } from "../loadingHandler";
20
20
  import { FileSet } from "../fileSet";
21
- import { ApolloConfig, ClientConfig, RoverConfig } from "../config";
21
+ import {
22
+ ApolloConfig,
23
+ ClientConfig,
24
+ envFileNames,
25
+ RoverConfig,
26
+ supportedConfigFileNames,
27
+ } from "../config";
22
28
  import type { ProjectStats } from "../../messages";
23
29
 
24
30
  export type DocumentUri = string;
@@ -67,13 +73,7 @@ export abstract class GraphQLProject {
67
73
 
68
74
  this.configFileSet = new FileSet({
69
75
  rootURI: this.rootURI,
70
- includes: [
71
- ".env",
72
- "apollo.config.js",
73
- "apollo.config.cjs",
74
- "apollo.config.mjs",
75
- "apollo.config.ts",
76
- ],
76
+ includes: supportedConfigFileNames.concat(envFileNames),
77
77
  excludes: [],
78
78
  });
79
79
 
@@ -99,10 +99,17 @@ export class DocumentSynchronization {
99
99
  private pendingDocumentChanges = new Map<DocumentUri, TextDocument>();
100
100
  private knownFiles = new Map<
101
101
  DocumentUri,
102
- {
103
- full: TextDocument;
104
- parts: ReadonlyArray<FilePart>;
105
- }
102
+ | {
103
+ source: "editor";
104
+ full: TextDocument;
105
+ parts: ReadonlyArray<FilePart>;
106
+ }
107
+ | {
108
+ source: "lsp";
109
+ full: Pick<TextDocument, "uri">;
110
+ parts?: undefined;
111
+ diagnostics?: Diagnostic[];
112
+ }
106
113
  >();
107
114
 
108
115
  constructor(
@@ -158,7 +165,11 @@ export class DocumentSynchronization {
158
165
  const newObj = Object.fromEntries(
159
166
  newParts.map((p) => [p.fractionalIndex, p]),
160
167
  );
161
- this.knownFiles.set(document.uri, { full: document, parts: newParts });
168
+ this.knownFiles.set(document.uri, {
169
+ source: "editor",
170
+ full: document,
171
+ parts: newParts,
172
+ });
162
173
 
163
174
  for (const newPart of newParts) {
164
175
  const previousPart = previousObj[newPart.fractionalIndex];
@@ -198,7 +209,9 @@ export class DocumentSynchronization {
198
209
 
199
210
  async resendAllDocuments() {
200
211
  for (const file of this.knownFiles.values()) {
201
- await this.sendDocumentChanges(file.full, []);
212
+ if (file.source === "editor") {
213
+ await this.sendDocumentChanges(file.full, []);
214
+ }
202
215
  }
203
216
  }
204
217
 
@@ -208,7 +221,7 @@ export class DocumentSynchronization {
208
221
  this.documentDidChange(params.document);
209
222
  };
210
223
 
211
- onDidCloseTextDocument: NonNullable<GraphQLProject["onDidClose"]> = (
224
+ onDidCloseTextDocument: NonNullable<GraphQLProject["onDidClose"]> = async (
212
225
  params,
213
226
  ) => {
214
227
  const known = this.knownFiles.get(params.document.uri);
@@ -216,15 +229,15 @@ export class DocumentSynchronization {
216
229
  return;
217
230
  }
218
231
  this.knownFiles.delete(params.document.uri);
219
- return Promise.all(
220
- known.parts.map((part) =>
221
- this.sendNotification(DidCloseTextDocumentNotification.type, {
232
+ if (known.source === "editor") {
233
+ for (const part of known.parts) {
234
+ await this.sendNotification(DidCloseTextDocumentNotification.type, {
222
235
  textDocument: {
223
236
  uri: getUri(known.full, part),
224
237
  },
225
- }),
226
- ),
227
- );
238
+ });
239
+ }
240
+ }
228
241
  };
229
242
 
230
243
  async documentDidChange(document: TextDocument) {
@@ -254,7 +267,7 @@ export class DocumentSynchronization {
254
267
  ): Promise<T | undefined> {
255
268
  await this.synchronizedWithDocument(positionParams.textDocument.uri);
256
269
  const found = this.knownFiles.get(positionParams.textDocument.uri);
257
- if (!found) {
270
+ if (!found || found.source !== "editor") {
258
271
  return;
259
272
  }
260
273
  const match = findContainedSourceAndPosition(
@@ -274,11 +287,14 @@ export class DocumentSynchronization {
274
287
  handlePartDiagnostics(params: PublishDiagnosticsParams) {
275
288
  DEBUG && console.log("Received diagnostics", params);
276
289
  const uriDetails = splitUri(params.uri);
277
- if (!uriDetails) {
278
- return;
279
- }
280
290
  const found = this.knownFiles.get(uriDetails.uri);
281
- if (!found) {
291
+ if (!found || found.source === "lsp") {
292
+ this.knownFiles.set(uriDetails.uri, {
293
+ source: "lsp",
294
+ full: { uri: uriDetails.uri },
295
+ diagnostics: params.diagnostics,
296
+ });
297
+ this.sendDiagnostics(params);
282
298
  return;
283
299
  }
284
300
  const part = found.parts.find(
@@ -304,13 +320,19 @@ export class DocumentSynchronization {
304
320
  }
305
321
 
306
322
  get openDocuments() {
307
- return [...this.knownFiles.values()].map((f) => f.full);
323
+ return [...this.knownFiles.values()]
324
+ .filter((f) => f.source === "editor")
325
+ .map((f) => f.full);
308
326
  }
309
327
 
310
328
  clearAllDiagnostics() {
311
329
  for (const file of this.knownFiles.values()) {
312
- for (const part of file.parts) {
313
- part.diagnostics = [];
330
+ if (file.source === "editor") {
331
+ for (const part of file.parts) {
332
+ part.diagnostics = [];
333
+ }
334
+ } else {
335
+ file.diagnostics = [];
314
336
  }
315
337
  this.sendDiagnostics({ uri: file.full.uri, diagnostics: [] });
316
338
  }
@@ -332,7 +354,7 @@ export class DocumentSynchronization {
332
354
  ): Promise<SemanticTokens | null> {
333
355
  await this.synchronizedWithDocument(params.textDocument.uri);
334
356
  const found = this.knownFiles.get(params.textDocument.uri);
335
- if (!found) {
357
+ if (!found || found.source !== "editor") {
336
358
  return null;
337
359
  }
338
360
  const allParts = await Promise.all(
@@ -24,6 +24,7 @@ import {
24
24
  SemanticTokensRegistrationType,
25
25
  SemanticTokensOptions,
26
26
  SemanticTokensRegistrationOptions,
27
+ DefinitionRequest,
27
28
  } from "vscode-languageserver/node";
28
29
  import cp from "node:child_process";
29
30
  import { GraphQLProjectConfig } from "../base";
@@ -281,6 +282,11 @@ export class RoverProject extends GraphQLProject {
281
282
  this.sendRequest(HoverRequest.type, virtualParams, token),
282
283
  );
283
284
 
285
+ onDefinition: GraphQLProject["onDefinition"] = async (params, token) =>
286
+ this.documents.insideVirtualDocument(params, (virtualParams) =>
287
+ this.sendRequest(DefinitionRequest.type, virtualParams, token),
288
+ );
289
+
284
290
  onUnhandledRequest: GraphQLProject["onUnhandledRequest"] = async (
285
291
  type,
286
292
  params,