vscode-apollo 1.19.3 → 2.0.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 (179) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +14 -0
  3. package/.circleci/config.yml +91 -0
  4. package/.eslintrc.js +10 -0
  5. package/.git-blame-ignore-revs +2 -0
  6. package/.gitattributes +1 -0
  7. package/.github/workflows/release.yml +95 -0
  8. package/.gitleaks.toml +26 -0
  9. package/.nvmrc +1 -0
  10. package/.prettierrc +5 -0
  11. package/.vscode/launch.json +66 -0
  12. package/.vscode/settings.json +16 -0
  13. package/.vscode/tasks.json +60 -0
  14. package/.vscodeignore +28 -1
  15. package/CHANGELOG.md +250 -1
  16. package/CODEOWNERS +4 -0
  17. package/LICENSE +2 -2
  18. package/README.md +104 -55
  19. package/codegen.yml +12 -0
  20. package/graphql.configuration.json +5 -1
  21. package/images/IconRun.svg +8 -0
  22. package/images/marketplace/apollo-wordmark.png +0 -0
  23. package/jest.config.ts +21 -0
  24. package/jest.e2e.config.js +17 -0
  25. package/package.json +102 -23
  26. package/renovate.json +30 -0
  27. package/sampleWorkspace/clientSchema/apollo.config.cjs +10 -0
  28. package/sampleWorkspace/clientSchema/src/clientSchema.js +16 -0
  29. package/sampleWorkspace/clientSchema/src/test.js +18 -0
  30. package/sampleWorkspace/fixtures/starwarsSchema.graphql +299 -0
  31. package/sampleWorkspace/httpSchema/apollo.config.ts +8 -0
  32. package/sampleWorkspace/httpSchema/src/test.js +9 -0
  33. package/sampleWorkspace/localSchema/apollo.config.js +8 -0
  34. package/sampleWorkspace/localSchema/src/test.js +8 -0
  35. package/sampleWorkspace/localSchemaArray/apollo.config.js +12 -0
  36. package/sampleWorkspace/localSchemaArray/planets.graphql +20 -0
  37. package/sampleWorkspace/localSchemaArray/src/test.js +12 -0
  38. package/sampleWorkspace/sampleWorkspace.code-workspace +20 -0
  39. package/sampleWorkspace/spotifyGraph/apollo.config.mjs +5 -0
  40. package/sampleWorkspace/spotifyGraph/src/test.js +11 -0
  41. package/src/__e2e__/mockServer.js +117 -0
  42. package/src/__e2e__/mocks.js +13094 -0
  43. package/src/__e2e__/run.js +23 -0
  44. package/src/__e2e__/runTests.js +44 -0
  45. package/src/__e2e__/setup.js +1 -0
  46. package/src/__e2e__/vscode-environment.js +16 -0
  47. package/src/__e2e__/vscode.js +1 -0
  48. package/src/__mocks__/fs.js +3 -0
  49. package/src/__tests__/statusBar.test.ts +8 -7
  50. package/src/build.js +57 -0
  51. package/src/debug.ts +2 -5
  52. package/src/env/index.ts +1 -0
  53. package/src/env/typescript-utility-types.ts +2 -0
  54. package/src/extension.ts +265 -170
  55. package/src/language-server/__e2e__/clientSchema.e2e.ts +147 -0
  56. package/src/language-server/__e2e__/httpSchema.e2e.ts +21 -0
  57. package/src/language-server/__e2e__/localSchema.e2e.ts +25 -0
  58. package/src/language-server/__e2e__/localSchemaArray.e2e.ts +31 -0
  59. package/src/language-server/__e2e__/studioGraph.e2e.ts +65 -0
  60. package/src/language-server/__e2e__/utils.ts +151 -0
  61. package/src/language-server/__tests__/diagnostics.test.ts +86 -0
  62. package/src/language-server/__tests__/document.test.ts +187 -0
  63. package/src/language-server/__tests__/fileSet.test.ts +46 -0
  64. package/src/language-server/__tests__/fixtures/starwarsSchema.ts +1917 -0
  65. package/src/language-server/config/__tests__/config.ts +54 -0
  66. package/src/language-server/config/__tests__/loadConfig.ts +384 -0
  67. package/src/language-server/config/__tests__/utils.ts +99 -0
  68. package/src/language-server/config/config.ts +284 -0
  69. package/src/language-server/config/index.ts +3 -0
  70. package/src/language-server/config/loadConfig.ts +101 -0
  71. package/src/language-server/config/utils.ts +45 -0
  72. package/src/language-server/diagnostics.ts +118 -0
  73. package/src/language-server/document.ts +277 -0
  74. package/src/language-server/engine/index.ts +123 -0
  75. package/src/language-server/engine/operations/frontendUrlRoot.ts +15 -0
  76. package/src/language-server/engine/operations/schemaTagsAndFieldStats.ts +32 -0
  77. package/src/language-server/errors/__tests__/NoMissingClientDirectives.test.ts +225 -0
  78. package/src/language-server/errors/logger.ts +58 -0
  79. package/src/language-server/errors/validation.ts +274 -0
  80. package/src/language-server/fileSet.ts +63 -0
  81. package/src/language-server/format.ts +48 -0
  82. package/src/language-server/graphqlTypes.ts +16741 -0
  83. package/src/language-server/index.ts +28 -0
  84. package/src/language-server/languageProvider.ts +795 -0
  85. package/src/language-server/loadingHandler.ts +47 -0
  86. package/src/language-server/project/base.ts +406 -0
  87. package/src/language-server/project/client.ts +568 -0
  88. package/src/language-server/project/defaultClientSchema.ts +70 -0
  89. package/src/language-server/providers/schema/__tests__/file.ts +191 -0
  90. package/src/language-server/providers/schema/base.ts +15 -0
  91. package/src/language-server/providers/schema/endpoint.ts +138 -0
  92. package/src/language-server/providers/schema/engine.ts +204 -0
  93. package/src/language-server/providers/schema/file.ts +176 -0
  94. package/src/language-server/providers/schema/index.ts +59 -0
  95. package/src/language-server/server.ts +274 -0
  96. package/src/language-server/typings/graphql.d.ts +27 -0
  97. package/src/language-server/utilities/__tests__/graphql.test.ts +399 -0
  98. package/src/language-server/utilities/__tests__/uri.ts +55 -0
  99. package/src/language-server/utilities/debouncer.ts +8 -0
  100. package/src/language-server/utilities/debug.ts +90 -0
  101. package/src/language-server/utilities/graphql.ts +433 -0
  102. package/src/language-server/utilities/index.ts +3 -0
  103. package/src/language-server/utilities/source.ts +182 -0
  104. package/src/language-server/utilities/uri.ts +19 -0
  105. package/src/language-server/workspace.ts +254 -0
  106. package/src/languageServerClient.ts +22 -15
  107. package/src/messages.ts +75 -0
  108. package/src/statusBar.ts +5 -5
  109. package/src/tools/__tests__/buildServiceDefinition.test.ts +491 -0
  110. package/src/tools/__tests__/snapshotSerializers/astSerializer.ts +19 -0
  111. package/src/tools/__tests__/snapshotSerializers/graphQLTypeSerializer.ts +14 -0
  112. package/src/tools/buildServiceDefinition.ts +241 -0
  113. package/src/tools/index.ts +6 -0
  114. package/src/tools/schema/index.ts +2 -0
  115. package/src/tools/schema/resolveObject.ts +18 -0
  116. package/src/tools/schema/resolverMap.ts +23 -0
  117. package/src/tools/utilities/graphql.ts +22 -0
  118. package/src/tools/utilities/index.ts +3 -0
  119. package/src/tools/utilities/invariant.ts +5 -0
  120. package/src/tools/utilities/predicates.ts +5 -0
  121. package/src/utils.ts +7 -21
  122. package/syntaxes/graphql.dart.json +2 -4
  123. package/syntaxes/graphql.ex.json +1 -4
  124. package/syntaxes/graphql.js.json +3 -3
  125. package/syntaxes/graphql.json +13 -9
  126. package/syntaxes/graphql.lua.json +51 -0
  127. package/syntaxes/graphql.rb.json +1 -1
  128. package/tsconfig.build.json +11 -0
  129. package/tsconfig.json +22 -7
  130. package/create-server-symlink.js +0 -8
  131. package/lib/debug.d.ts +0 -11
  132. package/lib/debug.d.ts.map +0 -1
  133. package/lib/debug.js +0 -48
  134. package/lib/debug.js.map +0 -1
  135. package/lib/extension.d.ts +0 -4
  136. package/lib/extension.d.ts.map +0 -1
  137. package/lib/extension.js +0 -187
  138. package/lib/extension.js.map +0 -1
  139. package/lib/languageServerClient.d.ts +0 -4
  140. package/lib/languageServerClient.d.ts.map +0 -1
  141. package/lib/languageServerClient.js +0 -57
  142. package/lib/languageServerClient.js.map +0 -1
  143. package/lib/statusBar.d.ts +0 -24
  144. package/lib/statusBar.d.ts.map +0 -1
  145. package/lib/statusBar.js +0 -46
  146. package/lib/statusBar.js.map +0 -1
  147. package/lib/testRunner/index.d.ts +0 -3
  148. package/lib/testRunner/index.d.ts.map +0 -1
  149. package/lib/testRunner/index.js +0 -49
  150. package/lib/testRunner/index.js.map +0 -1
  151. package/lib/testRunner/jest-config.d.ts +0 -14
  152. package/lib/testRunner/jest-config.d.ts.map +0 -1
  153. package/lib/testRunner/jest-config.js +0 -18
  154. package/lib/testRunner/jest-config.js.map +0 -1
  155. package/lib/testRunner/jest-vscode-environment.d.ts +0 -2
  156. package/lib/testRunner/jest-vscode-environment.d.ts.map +0 -1
  157. package/lib/testRunner/jest-vscode-environment.js +0 -19
  158. package/lib/testRunner/jest-vscode-environment.js.map +0 -1
  159. package/lib/testRunner/jest-vscode-framework-setup.d.ts +0 -1
  160. package/lib/testRunner/jest-vscode-framework-setup.d.ts.map +0 -1
  161. package/lib/testRunner/jest-vscode-framework-setup.js +0 -3
  162. package/lib/testRunner/jest-vscode-framework-setup.js.map +0 -1
  163. package/lib/testRunner/vscode-test-script.d.ts +0 -2
  164. package/lib/testRunner/vscode-test-script.d.ts.map +0 -1
  165. package/lib/testRunner/vscode-test-script.js +0 -23
  166. package/lib/testRunner/vscode-test-script.js.map +0 -1
  167. package/lib/utils.d.ts +0 -18
  168. package/lib/utils.d.ts.map +0 -1
  169. package/lib/utils.js +0 -52
  170. package/lib/utils.js.map +0 -1
  171. package/src/testRunner/README.md +0 -23
  172. package/src/testRunner/index.ts +0 -72
  173. package/src/testRunner/jest-config.ts +0 -17
  174. package/src/testRunner/jest-vscode-environment.ts +0 -25
  175. package/src/testRunner/jest-vscode-framework-setup.ts +0 -10
  176. package/src/testRunner/jest.d.ts +0 -37
  177. package/src/testRunner/vscode-test-script.ts +0 -38
  178. package/tsconfig.test.json +0 -4
  179. package/tsconfig.tsbuildinfo +0 -2486
@@ -0,0 +1,25 @@
1
+ import { TextEditor } from "vscode";
2
+ import { closeAllEditors, openEditor, testCompletion, getHover } from "./utils";
3
+
4
+ let editor: TextEditor;
5
+ beforeAll(async () => {
6
+ closeAllEditors();
7
+ editor = await openEditor("localSchema/src/test.js");
8
+ });
9
+
10
+ test("completion", async () => {
11
+ await testCompletion(editor, [3, 7], [["droid", "Droid"]]);
12
+ await testCompletion(editor, [4, 8], [["name", "String!"]]);
13
+ });
14
+
15
+ test("hover", async () => {
16
+ expect(await getHover(editor, [4, 8])).toMatchInlineSnapshot(`
17
+ "\`\`\`graphql
18
+ Droid.name: String!
19
+ \`\`\`
20
+
21
+ ---
22
+
23
+ What others call this droid"
24
+ `);
25
+ });
@@ -0,0 +1,31 @@
1
+ import { TextEditor } from "vscode";
2
+ import { closeAllEditors, openEditor, testCompletion, getHover } from "./utils";
3
+
4
+ let editor: TextEditor;
5
+ beforeAll(async () => {
6
+ closeAllEditors();
7
+ editor = await openEditor("localSchemaArray/src/test.js");
8
+ });
9
+
10
+ test("completion", async () => {
11
+ await testCompletion(editor, [3, 7], [["droid", "Droid"]]);
12
+ await testCompletion(editor, [4, 8], [["name", "String!"]]);
13
+ await testCompletion(editor, [6, 7], [["planets", "[Planet]"]]);
14
+ });
15
+
16
+ test("hover", async () => {
17
+ expect(await getHover(editor, [4, 8])).toMatchInlineSnapshot(`
18
+ "\`\`\`graphql
19
+ Droid.name: String!
20
+ \`\`\`
21
+
22
+ ---
23
+
24
+ What others call this droid"
25
+ `);
26
+ expect(await getHover(editor, [6, 7])).toMatchInlineSnapshot(`
27
+ "\`\`\`graphql
28
+ Query.planets: [Planet]
29
+ \`\`\`"
30
+ `);
31
+ });
@@ -0,0 +1,65 @@
1
+ import { TextEditor } from "vscode";
2
+ import {
3
+ closeAllEditors,
4
+ openEditor,
5
+ testCompletion,
6
+ getHover,
7
+ getExtension,
8
+ getOutputChannelDocument,
9
+ reloadService,
10
+ } from "./utils";
11
+ import mocks from "../../__e2e__/mocks.js";
12
+ import vscode from "vscode";
13
+ import { scheduler } from "node:timers/promises";
14
+
15
+ const mockPort = Number(process.env.MOCK_SERVER_PORT);
16
+ beforeAll(async () => {
17
+ closeAllEditors();
18
+ });
19
+
20
+ test("completion", async () => {
21
+ const editor = await openEditor("spotifyGraph/src/test.js");
22
+ await testCompletion(editor, [4, 9], [["profile", "CurrentUserProfile!"]]);
23
+ await testCompletion(editor, [6, 15], [["displayName", "String"]]);
24
+ });
25
+
26
+ test("hover", async () => {
27
+ const editor = await openEditor("spotifyGraph/src/test.js");
28
+ expect(await getHover(editor, [4, 9])).toMatchInlineSnapshot(`
29
+ "\`\`\`graphql
30
+ CurrentUser.profile: CurrentUserProfile!
31
+ \`\`\`
32
+
33
+ ---
34
+
35
+ Get detailed profile information about the current user (including the current user's username)."
36
+ `);
37
+ });
38
+
39
+ test("wrong token", async () => {
40
+ try {
41
+ await mocks.sendMock(mockPort, mocks.GetSchemaByTag_WRONG_TOKEN);
42
+ await mocks.sendMock(mockPort, mocks.SchemaTagsAndFieldStats_WRONG_TOKEN);
43
+
44
+ const ext = getExtension();
45
+ ext.outputChannel.clear();
46
+ const outputDoc = await getOutputChannelDocument();
47
+
48
+ await reloadService();
49
+ const output = outputDoc.getText();
50
+
51
+ // currently, this logs twice, along with a full stracktrace, but no indication of where it came from
52
+ // this should be improved on.
53
+ expect(output).toContain(
54
+ `
55
+ [GraphQL error]: HTTP fetch failed from 'kotlin': 406: Not Acceptable
56
+ [GraphQL error]: Invalid credentials provided
57
+ ApolloError: HTTP fetch failed from 'kotlin': 406: Not Acceptable
58
+ Invalid credentials provided
59
+ at new ApolloError`.trim(),
60
+ );
61
+ } finally {
62
+ await mocks.loadDefaultMocks(mockPort);
63
+ await reloadService();
64
+ }
65
+ });
@@ -0,0 +1,151 @@
1
+ import * as vscode from "vscode";
2
+ import { join } from "node:path";
3
+ import { scheduler } from "node:timers/promises";
4
+ import { ProjectStats } from "src/messages";
5
+ import { VSCodeGraphQLExtension } from "src/extension";
6
+
7
+ function resolve(file: string) {
8
+ return join(__dirname, "..", "..", "..", "sampleWorkspace", file);
9
+ }
10
+
11
+ export async function closeAllEditors() {
12
+ while (vscode.window.visibleTextEditors.length > 0) {
13
+ await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
14
+ }
15
+ }
16
+
17
+ async function waitFor<T>(
18
+ fn: () => Promise<T>,
19
+ { retries = 15, delay = 200 } = {},
20
+ ) {
21
+ for (let i = 0; i < retries; i++) {
22
+ try {
23
+ return await fn();
24
+ } catch (error) {
25
+ if (i === retries - 1) {
26
+ throw error;
27
+ }
28
+ await scheduler.wait(delay);
29
+ }
30
+ }
31
+ throw "unreachable";
32
+ }
33
+
34
+ export async function openEditor(file: string) {
35
+ const textDocument = await vscode.workspace.openTextDocument(resolve(file));
36
+ const editor = await vscode.window.showTextDocument(textDocument);
37
+ await waitForLSP(file);
38
+ return editor;
39
+ }
40
+
41
+ export function waitForLSP(file: string) {
42
+ return waitFor(async () => {
43
+ const uri = vscode.Uri.file(resolve(file));
44
+ const stats = await vscode.commands.executeCommand<ProjectStats>(
45
+ "apollographql/fileStats",
46
+ uri.toString(),
47
+ );
48
+ expect(stats.loaded).toBe(true);
49
+ return stats;
50
+ });
51
+ }
52
+
53
+ export async function testCompletion(
54
+ editor: vscode.TextEditor,
55
+ [line, character]: [number, number],
56
+ expected: Array<[label: string, detail: string]>,
57
+ ) {
58
+ await waitFor(async () => {
59
+ editor.selection = new vscode.Selection(line, character, line, character);
60
+ // without this, the completion list is not updated
61
+ await scheduler.wait(300);
62
+ const completions =
63
+ await vscode.commands.executeCommand<vscode.CompletionList>(
64
+ "vscode.executeCompletionItemProvider",
65
+ editor.document.uri,
66
+ new vscode.Position(line, character),
67
+ );
68
+
69
+ const labels = completions.items.slice(0, expected.length).map((item) =>
70
+ typeof item.label === "string"
71
+ ? { label: item.label, detail: "" }
72
+ : {
73
+ label: item.label.label,
74
+ detail: item.detail,
75
+ },
76
+ );
77
+ expect(labels).toStrictEqual(
78
+ expected.map(([label, detail]) => ({ label, detail })),
79
+ );
80
+ });
81
+ }
82
+
83
+ export async function getHover(
84
+ editor: vscode.TextEditor,
85
+ [line, character]: [number, number],
86
+ ) {
87
+ editor.selection = new vscode.Selection(line, character, line, character);
88
+ // without this, the completion list is not updated
89
+ await scheduler.wait(300);
90
+ const hovers = await vscode.commands.executeCommand<vscode.Hover[]>(
91
+ "vscode.executeHoverProvider",
92
+ editor.document.uri,
93
+ new vscode.Position(line, character),
94
+ );
95
+
96
+ const item = hovers[0];
97
+ const content = item.contents[0];
98
+ const label = typeof content === "string" ? content : content.value;
99
+ return label;
100
+ }
101
+
102
+ export function getExtension(): VSCodeGraphQLExtension {
103
+ return vscode.extensions.getExtension("apollographql.vscode-apollo")!.exports;
104
+ }
105
+
106
+ export async function getOutputChannelDocument() {
107
+ const ext = getExtension();
108
+ ext.outputChannel.show();
109
+ await scheduler.wait(300);
110
+ const doc = vscode.workspace.textDocuments.find((d) =>
111
+ d.uri.path.startsWith("extension-output-apollographql.vscode-apollo"),
112
+ );
113
+ if (!doc) {
114
+ throw new Error("Output channel document not found");
115
+ }
116
+ return doc;
117
+ }
118
+
119
+ export function getReloadPromise() {
120
+ const disposables: vscode.Disposable[] = [];
121
+ const ext = getExtension();
122
+ const waitingTokens = new Set<number>();
123
+ disposables.push(
124
+ ext.client.onNotification(
125
+ ext.LanguageServerNotifications.Loading,
126
+ ({ token }) => {
127
+ waitingTokens.add(token);
128
+ },
129
+ ),
130
+ );
131
+ return new Promise<void>((resolve) => {
132
+ disposables.push(
133
+ ext.client.onNotification(
134
+ ext.LanguageServerNotifications.LoadingComplete,
135
+ (token) => {
136
+ waitingTokens.delete(token);
137
+ if (waitingTokens.size === 0) resolve();
138
+ },
139
+ ),
140
+ );
141
+ }).finally(() => disposables.forEach((d) => d.dispose()));
142
+ }
143
+
144
+ export async function reloadService() {
145
+ const reloaded = getReloadPromise();
146
+
147
+ vscode.commands.executeCommand("apollographql/reloadService");
148
+
149
+ await reloaded;
150
+ await scheduler.wait(100);
151
+ }
@@ -0,0 +1,86 @@
1
+ import { Source, buildClientSchema } from "graphql";
2
+ import { GraphQLDocument } from "../document";
3
+ import { collectExecutableDefinitionDiagnositics } from "../diagnostics";
4
+ import { starwarsSchema } from "./fixtures/starwarsSchema";
5
+
6
+ const schema = buildClientSchema(starwarsSchema);
7
+
8
+ const validDocument = new GraphQLDocument(
9
+ new Source(`
10
+ query HeroAndFriendsNames {
11
+ hero {
12
+ name
13
+ friends {
14
+ name
15
+ }
16
+ }
17
+ }`),
18
+ );
19
+ const invalidDocument = new GraphQLDocument(
20
+ new Source(`
21
+ query HeroAndFriendsNames {
22
+ hero {
23
+ nam # Missing letter 'e'
24
+ friend { # Missing letter 's'
25
+ name
26
+ }
27
+ }
28
+ }`),
29
+ );
30
+ const documentWithTypes = new GraphQLDocument(
31
+ new Source(`
32
+ type SomeType {
33
+ thing: String
34
+ }
35
+ enum SomeEnum {
36
+ THING_ONE
37
+ THING_TWO
38
+ }
39
+ query HeroAndFriendsNames {
40
+ hero {
41
+ name
42
+ friends {
43
+ name
44
+ }
45
+ }
46
+ }`),
47
+ );
48
+ const documentWithOffset = new GraphQLDocument(
49
+ new Source(`query QueryWithOffset { hero { nam } }`, "testDocument", {
50
+ line: 5,
51
+ column: 10,
52
+ }),
53
+ );
54
+ describe("Language server diagnostics", () => {
55
+ describe("#collectExecutableDefinitionDiagnositics", () => {
56
+ it("returns no diagnostics for a correct document", () => {
57
+ const diagnostics = collectExecutableDefinitionDiagnositics(
58
+ schema,
59
+ validDocument,
60
+ );
61
+ expect(diagnostics.length).toEqual(0);
62
+ });
63
+ it("returns two diagnostics for a document with two errors", () => {
64
+ const diagnostics = collectExecutableDefinitionDiagnositics(
65
+ schema,
66
+ invalidDocument,
67
+ );
68
+ expect(diagnostics.length).toEqual(2);
69
+ });
70
+ it("returns no diagnostics for a document that includes type definitions", () => {
71
+ const diagnostics = collectExecutableDefinitionDiagnositics(
72
+ schema,
73
+ documentWithTypes,
74
+ );
75
+ expect(diagnostics.length).toEqual(0);
76
+ });
77
+ it("correctly offsets locations", () => {
78
+ const diagnostics = collectExecutableDefinitionDiagnositics(
79
+ schema,
80
+ documentWithOffset,
81
+ );
82
+ expect(diagnostics.length).toEqual(1);
83
+ expect(diagnostics[0].range.start.character).toEqual(40);
84
+ });
85
+ });
86
+ });
@@ -0,0 +1,187 @@
1
+ import { extractGraphQLDocuments } from "../document";
2
+ import { TextDocument, Position } from "vscode-languageserver";
3
+
4
+ describe("extractGraphQLDocuments", () => {
5
+ describe("extracting documents from JavaScript template literals", () => {
6
+ const mockTextDocument = (text: string): TextDocument => ({
7
+ getText: jest.fn().mockReturnValue(text),
8
+ offsetAt(): number {
9
+ return 0;
10
+ },
11
+ positionAt(): Position {
12
+ return {
13
+ character: 0,
14
+ line: 0,
15
+ };
16
+ },
17
+ languageId: "javascript",
18
+ lineCount: 0,
19
+ uri: "",
20
+ version: 1,
21
+ });
22
+
23
+ it("works with placeholders that span multiple rows", () => {
24
+ const textDocument = mockTextDocument(`
25
+ gql\`
26
+ {
27
+ hero {
28
+ ...Hero_character
29
+ }
30
+ }
31
+
32
+ \${Hero.fragments
33
+ .character}
34
+ \`
35
+ `);
36
+ const documents = extractGraphQLDocuments(textDocument);
37
+
38
+ expect(documents?.length).toEqual(1);
39
+ expect(documents?.[0].syntaxErrors.length).toBe(0);
40
+ expect(documents?.[0].ast?.definitions.length).toBe(1);
41
+ });
42
+
43
+ it("works with multiple placeholders in a document", () => {
44
+ const textDocument = mockTextDocument(`
45
+ gql\`
46
+ {
47
+ hero {
48
+ ...Hero_character
49
+ }
50
+ }
51
+
52
+ \${Hero.fragments.character}
53
+
54
+ {
55
+ reviews(episode: NEWHOPE) {
56
+ ...ReviewList_reviews
57
+ }
58
+ }
59
+
60
+ \${ReviewList.fragments.reviews}
61
+ \`
62
+ `);
63
+ const documents = extractGraphQLDocuments(textDocument);
64
+
65
+ expect(documents?.length).toEqual(1);
66
+ expect(documents?.[0].syntaxErrors.length).toBe(0);
67
+ expect(documents?.[0].ast?.definitions.length).toBe(2);
68
+ });
69
+
70
+ it("works with a custom tagname", () => {
71
+ const textDocument = mockTextDocument(`
72
+ gqltag\`
73
+ {
74
+ hero {
75
+ ...Hero_character
76
+ }
77
+ }
78
+
79
+ \${Hero.fragments.character}
80
+
81
+ {
82
+ reviews(episode: NEWHOPE) {
83
+ ...ReviewList_reviews
84
+ }
85
+ }
86
+
87
+ \${ReviewList.fragments.reviews}
88
+ \`
89
+ `);
90
+ const documents = extractGraphQLDocuments(textDocument, "gqltag");
91
+
92
+ expect(documents?.length).toEqual(1);
93
+ expect(documents?.[0].syntaxErrors.length).toBe(0);
94
+ expect(documents?.[0].ast?.definitions.length).toBe(2);
95
+ });
96
+
97
+ it("works with parens", () => {
98
+ const textDocument = mockTextDocument(`
99
+ gql(\`
100
+ {
101
+ hero {
102
+ ...Hero_character
103
+ }
104
+ }
105
+
106
+ \${Hero.fragments.character}
107
+
108
+ {
109
+ reviews(episode: NEWHOPE) {
110
+ ...ReviewList_reviews
111
+ }
112
+ }
113
+
114
+ \${ReviewList.fragments.reviews}
115
+ \`)
116
+ `);
117
+ const documents = extractGraphQLDocuments(textDocument);
118
+
119
+ expect(documents?.length).toEqual(1);
120
+ expect(documents?.[0].syntaxErrors.length).toBe(0);
121
+ expect(documents?.[0].ast?.definitions.length).toBe(2);
122
+ });
123
+ });
124
+
125
+ describe("extracting documents from ReasonML extension nodes", () => {
126
+ const mockTextDocument = (text: string): TextDocument => ({
127
+ getText: jest.fn().mockReturnValue(text),
128
+ offsetAt(): number {
129
+ return 0;
130
+ },
131
+ positionAt(): Position {
132
+ return {
133
+ character: 0,
134
+ line: 0,
135
+ };
136
+ },
137
+ languageId: "reason",
138
+ lineCount: 0,
139
+ uri: "",
140
+ version: 1,
141
+ });
142
+
143
+ it("works with ReasonRelay nodes", () => {
144
+ const textDocument = mockTextDocument(`
145
+ module Query = [%relay.query
146
+ {|
147
+ query SomeQuery {
148
+ id
149
+ }
150
+ |}
151
+ ];
152
+
153
+ module Fragment = [%relay.fragment
154
+ {|
155
+ fragment X on Hero {
156
+ id
157
+ }
158
+ |}
159
+ ];
160
+ `);
161
+ const documents = extractGraphQLDocuments(textDocument);
162
+
163
+ expect(documents?.length).toEqual(2);
164
+ expect(documents?.[0].syntaxErrors.length).toBe(0);
165
+ expect(documents?.[1].syntaxErrors.length).toBe(0);
166
+ expect(documents?.[0].ast?.definitions.length).toBe(1);
167
+ expect(documents?.[1].ast?.definitions.length).toBe(1);
168
+ });
169
+
170
+ it("works with graphql_ppx style node", () => {
171
+ const textDocument = mockTextDocument(`
172
+ module Query = [%graphql
173
+ {|
174
+ query SomeQuery {
175
+ id
176
+ }
177
+ |}
178
+ ];
179
+ `);
180
+ const documents = extractGraphQLDocuments(textDocument);
181
+
182
+ expect(documents?.length).toEqual(1);
183
+ expect(documents?.[0].syntaxErrors.length).toBe(0);
184
+ expect(documents?.[0].ast?.definitions.length).toBe(1);
185
+ });
186
+ });
187
+ });
@@ -0,0 +1,46 @@
1
+ import { FileSet } from "../fileSet";
2
+ import { URI } from "vscode-uri";
3
+
4
+ describe("fileSet", () => {
5
+ describe("includesFile", () => {
6
+ it("matches includes starting with ./", () => {
7
+ const fileSet = new FileSet({
8
+ excludes: [],
9
+ includes: ["./src/**/*.tsx"],
10
+ rootURI: URI.parse("/project"),
11
+ });
12
+ const file = "file:///project/src/Component.tsx";
13
+ expect(fileSet.includesFile(file)).toBe(true);
14
+ });
15
+
16
+ it("matches includes not starting with ./", () => {
17
+ const fileSet = new FileSet({
18
+ excludes: [],
19
+ includes: ["src/**/*.tsx"],
20
+ rootURI: URI.parse("/project"),
21
+ });
22
+ const file = "file:///project/src/Component.tsx";
23
+ expect(fileSet.includesFile(file)).toBe(true);
24
+ });
25
+
26
+ it("does not match excludes starting with ./", () => {
27
+ const fileSet = new FileSet({
28
+ excludes: ["./src/Component.tsx"],
29
+ includes: ["./src/**/*.tsx"],
30
+ rootURI: URI.parse("/project"),
31
+ });
32
+ const file = "file:///project/src/Component.tsx";
33
+ expect(fileSet.includesFile(file)).toBe(false);
34
+ });
35
+
36
+ it("does not match excludes not starting with ./", () => {
37
+ const fileSet = new FileSet({
38
+ excludes: ["src/Component.tsx"],
39
+ includes: ["src/**/*.tsx"],
40
+ rootURI: URI.parse("/project"),
41
+ });
42
+ const file = "file:///project/src/Component.tsx";
43
+ expect(fileSet.includesFile(file)).toBe(false);
44
+ });
45
+ });
46
+ });