vscode-apollo 2.0.1 → 2.2.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 (42) hide show
  1. package/.circleci/config.yml +1 -1
  2. package/.vscode/launch.json +5 -1
  3. package/CHANGELOG.md +41 -0
  4. package/package.json +9 -4
  5. package/renovate.json +2 -1
  6. package/sampleWorkspace/localSchema/src/test.js +3 -0
  7. package/sampleWorkspace/rover/apollo.config.js +3 -0
  8. package/sampleWorkspace/rover/src/test.graphql +14 -0
  9. package/sampleWorkspace/rover/src/test.js +30 -0
  10. package/sampleWorkspace/sampleWorkspace.code-workspace +25 -19
  11. package/src/language-server/__tests__/document.test.ts +161 -3
  12. package/src/language-server/__tests__/fixtures/TypeScript.tmLanguage.json +5749 -0
  13. package/src/language-server/__tests__/fixtures/documents/commentWithTemplate.ts +41 -0
  14. package/src/language-server/__tests__/fixtures/documents/commentWithTemplate.ts.snap +185 -0
  15. package/src/language-server/__tests__/fixtures/documents/functionCall.ts +93 -0
  16. package/src/language-server/__tests__/fixtures/documents/functionCall.ts.snap +431 -0
  17. package/src/language-server/__tests__/fixtures/documents/taggedTemplate.ts +80 -0
  18. package/src/language-server/__tests__/fixtures/documents/taggedTemplate.ts.snap +353 -0
  19. package/src/language-server/__tests__/fixtures/documents/templateWithComment.ts +38 -0
  20. package/src/language-server/__tests__/fixtures/documents/templateWithComment.ts.snap +123 -0
  21. package/src/language-server/config/__tests__/loadConfig.ts +28 -16
  22. package/src/language-server/config/config.ts +50 -12
  23. package/src/language-server/config/loadConfig.ts +2 -1
  24. package/src/language-server/config/which.d.ts +19 -0
  25. package/src/language-server/document.ts +86 -53
  26. package/src/language-server/fileSet.ts +8 -6
  27. package/src/language-server/project/base.ts +64 -315
  28. package/src/language-server/project/client.ts +731 -21
  29. package/src/language-server/project/internal.ts +354 -0
  30. package/src/language-server/project/rover/DocumentSynchronization.ts +385 -0
  31. package/src/language-server/project/rover/__tests__/DocumentSynchronization.test.ts +302 -0
  32. package/src/language-server/project/rover/project.ts +341 -0
  33. package/src/language-server/server.ts +187 -98
  34. package/src/language-server/utilities/__tests__/source.test.ts +162 -0
  35. package/src/language-server/utilities/languageIdForExtension.ts +39 -0
  36. package/src/language-server/utilities/source.ts +38 -3
  37. package/src/language-server/workspace.ts +61 -12
  38. package/src/languageServerClient.ts +13 -15
  39. package/src/tools/utilities/getLanguageInformation.ts +41 -0
  40. package/src/tools/utilities/languageInformation.ts +41 -0
  41. package/syntaxes/graphql.js.json +18 -21
  42. package/src/language-server/languageProvider.ts +0 -795
@@ -2,6 +2,7 @@ import {
2
2
  WorkspaceFolder,
3
3
  NotificationHandler,
4
4
  PublishDiagnosticsParams,
5
+ ClientCapabilities,
5
6
  } from "vscode-languageserver/node";
6
7
  import { QuickPickItem } from "vscode";
7
8
  import { GraphQLProject, DocumentUri } from "./project/base";
@@ -15,6 +16,8 @@ import { URI } from "vscode-uri";
15
16
  import { Debug } from "./utilities";
16
17
  import type { EngineDecoration } from "../messages";
17
18
  import { equal } from "@wry/equality";
19
+ import { isRoverConfig, RoverProject } from "./project/rover/project";
20
+ import { VSCodeConnection } from "./server";
18
21
 
19
22
  export interface WorkspaceConfig {
20
23
  clientIdentity: ClientIdentity;
@@ -26,12 +29,14 @@ export class GraphQLWorkspace {
26
29
  private _onSchemaTags?: NotificationHandler<[ServiceID, SchemaTag[]]>;
27
30
  private _onConfigFilesFound?: NotificationHandler<(ApolloConfig | Error)[]>;
28
31
  private _projectForFileCache: Map<string, GraphQLProject> = new Map();
32
+ public capabilities?: ClientCapabilities;
29
33
 
30
34
  private projectsByFolderUri: Map<string, GraphQLProject[]> = new Map();
31
35
 
32
36
  constructor(
33
37
  private LanguageServerLoadingHandler: LanguageServerLoadingHandler,
34
38
  private config: WorkspaceConfig,
39
+ private whenConnectionInitialized: Promise<VSCodeConnection>,
35
40
  ) {}
36
41
 
37
42
  onDiagnostics(handler: NotificationHandler<PublishDiagnosticsParams>) {
@@ -65,8 +70,15 @@ export class GraphQLWorkspace {
65
70
  configFolderURI: URI.parse(folder.uri),
66
71
  clientIdentity,
67
72
  })
73
+ : isRoverConfig(config)
74
+ ? new RoverProject({
75
+ config,
76
+ loadingHandler: this.LanguageServerLoadingHandler,
77
+ configFolderURI: URI.parse(folder.uri),
78
+ capabilities: this.capabilities!, // TODO?
79
+ })
68
80
  : (() => {
69
- throw new Error("TODO rover project");
81
+ throw new Error("Impossible config!");
70
82
  })();
71
83
 
72
84
  project.onDiagnostics((params) => {
@@ -86,8 +98,13 @@ export class GraphQLWorkspace {
86
98
  // after a project has loaded, we do an initial validation to surface errors
87
99
  // on the start of the language server. Instead of doing this in the
88
100
  // base class which is used by codegen and other tools
89
- project.whenReady.then(() => project.validate());
101
+ project.whenReady.then(() => project.validate?.());
90
102
 
103
+ if (project.onVSCodeConnectionInitialized) {
104
+ this.whenConnectionInitialized.then(
105
+ project.onVSCodeConnectionInitialized.bind(project),
106
+ );
107
+ }
91
108
  return project;
92
109
  }
93
110
 
@@ -157,12 +174,24 @@ export class GraphQLWorkspace {
157
174
  this.projectsByFolderUri.forEach((projects, uri) => {
158
175
  this.projectsByFolderUri.set(
159
176
  uri,
160
- projects.map((project) => {
161
- project.clearAllDiagnostics();
162
- return this.createProject({
163
- config: project.config,
164
- folder: { uri } as WorkspaceFolder,
165
- });
177
+ projects.map((oldProject) => {
178
+ oldProject.clearAllDiagnostics();
179
+
180
+ try {
181
+ const newProject = this.createProject({
182
+ config: oldProject.config,
183
+ folder: { uri } as WorkspaceFolder,
184
+ });
185
+ if (
186
+ oldProject instanceof RoverProject &&
187
+ newProject instanceof RoverProject
188
+ ) {
189
+ newProject.restoreFromPreviousProject(oldProject);
190
+ }
191
+ return newProject;
192
+ } finally {
193
+ oldProject.dispose?.();
194
+ }
166
195
  }),
167
196
  );
168
197
  });
@@ -179,7 +208,7 @@ export class GraphQLWorkspace {
179
208
  error = e;
180
209
  }
181
210
 
182
- const project = this.projectForFile(configUri);
211
+ const project = this.projectForConfigFile(configUri);
183
212
 
184
213
  if (this._onConfigFilesFound) {
185
214
  this._onConfigFilesFound([config || error]);
@@ -227,7 +256,10 @@ export class GraphQLWorkspace {
227
256
  removeProjectsInFolder(folder: WorkspaceFolder) {
228
257
  const projects = this.projectsByFolderUri.get(folder.uri);
229
258
  if (projects) {
230
- projects.forEach((project) => project.clearAllDiagnostics());
259
+ projects.forEach((project) => {
260
+ project.clearAllDiagnostics();
261
+ project.dispose?.();
262
+ });
231
263
  this.projectsByFolderUri.delete(folder.uri);
232
264
  }
233
265
  }
@@ -236,14 +268,31 @@ export class GraphQLWorkspace {
236
268
  return Array.from(this.projectsByFolderUri.values()).flat();
237
269
  }
238
270
 
239
- projectForFile(uri: DocumentUri): GraphQLProject | undefined {
271
+ projectForConfigFile(configUri: DocumentUri): GraphQLProject | undefined {
272
+ for (const projects of this.projectsByFolderUri.values()) {
273
+ const project = projects.find((project) =>
274
+ project.isConfiguredBy(configUri),
275
+ );
276
+ if (project) {
277
+ return project;
278
+ }
279
+ }
280
+ return undefined;
281
+ }
282
+
283
+ projectForFile(
284
+ uri: DocumentUri,
285
+ languageId?: string,
286
+ ): GraphQLProject | undefined {
240
287
  const cachedResult = this._projectForFileCache.get(uri);
241
288
  if (cachedResult) {
242
289
  return cachedResult;
243
290
  }
244
291
 
245
292
  for (const projects of this.projectsByFolderUri.values()) {
246
- const project = projects.find((project) => project.includesFile(uri));
293
+ const project = projects.find((project) =>
294
+ project.includesFile(uri, languageId),
295
+ );
247
296
  if (project) {
248
297
  this._projectForFileCache.set(uri, project);
249
298
  return project;
@@ -6,9 +6,17 @@ import {
6
6
  RevealOutputChannelOn,
7
7
  } from "vscode-languageclient/node";
8
8
  import { workspace, OutputChannel } from "vscode";
9
+ import { supportedLanguageIds } from "./tools/utilities/languageInformation";
10
+ import type { InitializationOptions } from "./language-server/server";
11
+ import { getLangugageInformation } from "./tools/utilities/getLanguageInformation";
9
12
 
10
13
  const { version, referenceID } = require("../package.json");
11
14
 
15
+ const languageIdExtensionMap = getLangugageInformation();
16
+ const supportedExtensions = supportedLanguageIds.flatMap(
17
+ (id) => languageIdExtensionMap[id],
18
+ );
19
+
12
20
  export function getLanguageServerClient(
13
21
  serverModule: string,
14
22
  outputChannel: OutputChannel,
@@ -41,25 +49,12 @@ export function getLanguageServerClient(
41
49
  };
42
50
 
43
51
  const clientOptions: LanguageClientOptions = {
44
- documentSelector: [
45
- "graphql",
46
- "javascript",
47
- "typescript",
48
- "javascriptreact",
49
- "typescriptreact",
50
- "vue",
51
- "svelte",
52
- "python",
53
- "ruby",
54
- "dart",
55
- "reason",
56
- "elixir",
57
- ],
52
+ documentSelector: supportedLanguageIds,
58
53
  synchronize: {
59
54
  fileEvents: [
60
55
  workspace.createFileSystemWatcher("**/.env?(.local)"),
61
56
  workspace.createFileSystemWatcher(
62
- "**/*.{graphql,js,ts,cjs,mjs,jsx,tsx,vue,svelte,py,rb,dart,re,ex,exs}",
57
+ "**/*{" + supportedExtensions.join(",") + "}",
63
58
  ),
64
59
  ],
65
60
  },
@@ -69,6 +64,9 @@ export function getLanguageServerClient(
69
64
  .get("debug.revealOutputOnLanguageServerError")
70
65
  ? RevealOutputChannelOn.Error
71
66
  : RevealOutputChannelOn.Never,
67
+ initializationOptions: {
68
+ languageIdExtensionMap,
69
+ } satisfies InitializationOptions,
72
70
  };
73
71
 
74
72
  return new LanguageClient(
@@ -0,0 +1,41 @@
1
+ import * as vscode from "vscode";
2
+ import {
3
+ LanguageIdExtensionMap,
4
+ minimumKnownExtensions,
5
+ } from "./languageInformation";
6
+
7
+ /**
8
+ * @returns An object with language identifiers as keys and file extensions as values.
9
+ * see https://github.com/microsoft/vscode/issues/109919
10
+ */
11
+ export function getLangugageInformation(): LanguageIdExtensionMap {
12
+ const allKnownExtensions = vscode.extensions.all
13
+ .map(
14
+ (i) =>
15
+ i.packageJSON?.contributes?.languages as (
16
+ | undefined
17
+ | {
18
+ id?: string;
19
+ extensions?: string[];
20
+ }
21
+ )[],
22
+ )
23
+ .flat()
24
+ .filter(
25
+ (i): i is { id: string; extensions: `.${string}`[] } =>
26
+ !!(i && i.id && i.extensions?.length),
27
+ )
28
+ .reduce<Record<string, Set<`.${string}`>>>(
29
+ (acc, i) => {
30
+ if (!acc[i.id]) acc[i.id] = new Set();
31
+ for (const ext of i.extensions) acc[i.id].add(ext);
32
+ return acc;
33
+ },
34
+ Object.fromEntries(
35
+ Object.entries(minimumKnownExtensions).map(([k, v]) => [k, new Set(v)]),
36
+ ),
37
+ );
38
+ return Object.fromEntries(
39
+ Object.entries(allKnownExtensions).map(([k, v]) => [k, [...v]] as const),
40
+ ) as LanguageIdExtensionMap;
41
+ }
@@ -0,0 +1,41 @@
1
+ const _supportedDocumentTypes = [
2
+ "graphql",
3
+ "javascript",
4
+ "typescript",
5
+ "javascriptreact",
6
+ "typescriptreact",
7
+ "vue",
8
+ "svelte",
9
+ "python",
10
+ "ruby",
11
+ "dart",
12
+ "reason",
13
+ "elixir",
14
+ ] as const;
15
+ export type SupportedLanguageIds = (typeof _supportedDocumentTypes)[number];
16
+ export const supportedLanguageIds =
17
+ // remove the `readonly` we get from using `as const`
18
+ _supportedDocumentTypes as any as SupportedLanguageIds[];
19
+
20
+ export type FileExtension = `.${string}`;
21
+
22
+ export const minimumKnownExtensions: Record<
23
+ SupportedLanguageIds,
24
+ FileExtension[]
25
+ > = {
26
+ graphql: [".gql", ".graphql", ".graphqls"],
27
+ javascript: [".js", ".mjs", ".cjs"],
28
+ typescript: [".ts", ".mts", ".cts"],
29
+ javascriptreact: [".jsx"],
30
+ typescriptreact: [".tsx"],
31
+ vue: [".vue"],
32
+ svelte: [".svelte"],
33
+ python: [".py"],
34
+ ruby: [".rb"],
35
+ dart: [".dart"],
36
+ reason: [".re"],
37
+ elixir: [".ex", ".exs"],
38
+ };
39
+
40
+ export type LanguageIdExtensionMap = Record<string, `.${string}`[]> &
41
+ typeof minimumKnownExtensions;
@@ -3,43 +3,40 @@
3
3
  "injectionSelector": "L:source -string -comment",
4
4
  "patterns": [
5
5
  {
6
- "name": "taggedTemplates",
7
6
  "contentName": "meta.embedded.block.graphql",
8
- "begin": "(Relay\\.QL|gql|graphql(\\.experimental)?)\\(?(`)",
7
+ "begin": "(?:Relay\\.QL|gql|graphql(\\.experimental)?)\\s*(?:<.*?>\\s*)?`",
8
+ "end": "`",
9
+ "patterns": [{ "include": "source.graphql" }]
10
+ },
11
+ {
12
+ "contentName": "meta.embedded.block.graphql",
13
+ "begin": "`(\\s*#[ ]*(gql|graphql|GraphQL))",
9
14
  "beginCaptures": {
10
15
  "1": {
11
- "name": "entity.name.function.tagged-template.js"
16
+ "name": "meta.embedded.block.graphql comment.line.graphql.js"
12
17
  },
13
18
  "2": {
14
- "name": "punctuation.definition.string.template.begin.js"
15
- }
16
- },
17
- "end": "`\\)?",
18
- "endCaptures": {
19
- "0": {
20
- "name": "punctuation.definition.string.template.end.js"
19
+ "name": "markup.italic"
21
20
  }
22
21
  },
22
+ "end": "`",
23
23
  "patterns": [{ "include": "source.graphql" }]
24
24
  },
25
25
  {
26
- "name": "taggedTemplates",
27
26
  "contentName": "meta.embedded.block.graphql",
28
- "begin": "(`)(#graphql)",
27
+ "begin": "(?:gql|graphql)\\s*(?:<.*?>\\s*)?\\(\\s*`",
28
+ "end": "`\\s*\\)",
29
+ "patterns": [{ "include": "source.graphql" }]
30
+ },
31
+ {
32
+ "contentName": "meta.embedded.block.graphql",
33
+ "begin": "(?:\\/\\*[\\s\\*]*(gql|graphql|GraphQL)\\s*\\*\\/)\\s*(`)",
29
34
  "beginCaptures": {
30
35
  "1": {
31
- "name": "punctuation.definition.string.template.begin.js"
32
- },
33
- "2": {
34
- "name": "comment.line.graphql.js"
36
+ "name": "markup.italic"
35
37
  }
36
38
  },
37
39
  "end": "`",
38
- "endCaptures": {
39
- "0": {
40
- "name": "punctuation.definition.string.template.end.js"
41
- }
42
- },
43
40
  "patterns": [{ "include": "source.graphql" }]
44
41
  }
45
42
  ],