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.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +14 -0
- package/.circleci/config.yml +91 -0
- package/.eslintrc.js +10 -0
- package/.git-blame-ignore-revs +2 -0
- package/.gitattributes +1 -0
- package/.github/workflows/release.yml +95 -0
- package/.gitleaks.toml +26 -0
- package/.nvmrc +1 -0
- package/.prettierrc +5 -0
- package/.vscode/launch.json +66 -0
- package/.vscode/settings.json +16 -0
- package/.vscode/tasks.json +60 -0
- package/.vscodeignore +28 -1
- package/CHANGELOG.md +250 -1
- package/CODEOWNERS +4 -0
- package/LICENSE +2 -2
- package/README.md +104 -55
- package/codegen.yml +12 -0
- package/graphql.configuration.json +5 -1
- package/images/IconRun.svg +8 -0
- package/images/marketplace/apollo-wordmark.png +0 -0
- package/jest.config.ts +21 -0
- package/jest.e2e.config.js +17 -0
- package/package.json +102 -23
- package/renovate.json +30 -0
- package/sampleWorkspace/clientSchema/apollo.config.cjs +10 -0
- package/sampleWorkspace/clientSchema/src/clientSchema.js +16 -0
- package/sampleWorkspace/clientSchema/src/test.js +18 -0
- package/sampleWorkspace/fixtures/starwarsSchema.graphql +299 -0
- package/sampleWorkspace/httpSchema/apollo.config.ts +8 -0
- package/sampleWorkspace/httpSchema/src/test.js +9 -0
- package/sampleWorkspace/localSchema/apollo.config.js +8 -0
- package/sampleWorkspace/localSchema/src/test.js +8 -0
- package/sampleWorkspace/localSchemaArray/apollo.config.js +12 -0
- package/sampleWorkspace/localSchemaArray/planets.graphql +20 -0
- package/sampleWorkspace/localSchemaArray/src/test.js +12 -0
- package/sampleWorkspace/sampleWorkspace.code-workspace +20 -0
- package/sampleWorkspace/spotifyGraph/apollo.config.mjs +5 -0
- package/sampleWorkspace/spotifyGraph/src/test.js +11 -0
- package/src/__e2e__/mockServer.js +117 -0
- package/src/__e2e__/mocks.js +13094 -0
- package/src/__e2e__/run.js +23 -0
- package/src/__e2e__/runTests.js +44 -0
- package/src/__e2e__/setup.js +1 -0
- package/src/__e2e__/vscode-environment.js +16 -0
- package/src/__e2e__/vscode.js +1 -0
- package/src/__mocks__/fs.js +3 -0
- package/src/__tests__/statusBar.test.ts +8 -7
- package/src/build.js +57 -0
- package/src/debug.ts +2 -5
- package/src/env/index.ts +1 -0
- package/src/env/typescript-utility-types.ts +2 -0
- package/src/extension.ts +265 -170
- package/src/language-server/__e2e__/clientSchema.e2e.ts +147 -0
- package/src/language-server/__e2e__/httpSchema.e2e.ts +21 -0
- package/src/language-server/__e2e__/localSchema.e2e.ts +25 -0
- package/src/language-server/__e2e__/localSchemaArray.e2e.ts +31 -0
- package/src/language-server/__e2e__/studioGraph.e2e.ts +65 -0
- package/src/language-server/__e2e__/utils.ts +151 -0
- package/src/language-server/__tests__/diagnostics.test.ts +86 -0
- package/src/language-server/__tests__/document.test.ts +187 -0
- package/src/language-server/__tests__/fileSet.test.ts +46 -0
- package/src/language-server/__tests__/fixtures/starwarsSchema.ts +1917 -0
- package/src/language-server/config/__tests__/config.ts +54 -0
- package/src/language-server/config/__tests__/loadConfig.ts +384 -0
- package/src/language-server/config/__tests__/utils.ts +99 -0
- package/src/language-server/config/config.ts +284 -0
- package/src/language-server/config/index.ts +3 -0
- package/src/language-server/config/loadConfig.ts +101 -0
- package/src/language-server/config/utils.ts +45 -0
- package/src/language-server/diagnostics.ts +118 -0
- package/src/language-server/document.ts +277 -0
- package/src/language-server/engine/index.ts +123 -0
- package/src/language-server/engine/operations/frontendUrlRoot.ts +15 -0
- package/src/language-server/engine/operations/schemaTagsAndFieldStats.ts +32 -0
- package/src/language-server/errors/__tests__/NoMissingClientDirectives.test.ts +225 -0
- package/src/language-server/errors/logger.ts +58 -0
- package/src/language-server/errors/validation.ts +274 -0
- package/src/language-server/fileSet.ts +63 -0
- package/src/language-server/format.ts +48 -0
- package/src/language-server/graphqlTypes.ts +16741 -0
- package/src/language-server/index.ts +28 -0
- package/src/language-server/languageProvider.ts +795 -0
- package/src/language-server/loadingHandler.ts +47 -0
- package/src/language-server/project/base.ts +406 -0
- package/src/language-server/project/client.ts +568 -0
- package/src/language-server/project/defaultClientSchema.ts +70 -0
- package/src/language-server/providers/schema/__tests__/file.ts +191 -0
- package/src/language-server/providers/schema/base.ts +15 -0
- package/src/language-server/providers/schema/endpoint.ts +138 -0
- package/src/language-server/providers/schema/engine.ts +204 -0
- package/src/language-server/providers/schema/file.ts +176 -0
- package/src/language-server/providers/schema/index.ts +59 -0
- package/src/language-server/server.ts +274 -0
- package/src/language-server/typings/graphql.d.ts +27 -0
- package/src/language-server/utilities/__tests__/graphql.test.ts +399 -0
- package/src/language-server/utilities/__tests__/uri.ts +55 -0
- package/src/language-server/utilities/debouncer.ts +8 -0
- package/src/language-server/utilities/debug.ts +90 -0
- package/src/language-server/utilities/graphql.ts +433 -0
- package/src/language-server/utilities/index.ts +3 -0
- package/src/language-server/utilities/source.ts +182 -0
- package/src/language-server/utilities/uri.ts +19 -0
- package/src/language-server/workspace.ts +254 -0
- package/src/languageServerClient.ts +22 -15
- package/src/messages.ts +75 -0
- package/src/statusBar.ts +5 -5
- package/src/tools/__tests__/buildServiceDefinition.test.ts +491 -0
- package/src/tools/__tests__/snapshotSerializers/astSerializer.ts +19 -0
- package/src/tools/__tests__/snapshotSerializers/graphQLTypeSerializer.ts +14 -0
- package/src/tools/buildServiceDefinition.ts +241 -0
- package/src/tools/index.ts +6 -0
- package/src/tools/schema/index.ts +2 -0
- package/src/tools/schema/resolveObject.ts +18 -0
- package/src/tools/schema/resolverMap.ts +23 -0
- package/src/tools/utilities/graphql.ts +22 -0
- package/src/tools/utilities/index.ts +3 -0
- package/src/tools/utilities/invariant.ts +5 -0
- package/src/tools/utilities/predicates.ts +5 -0
- package/src/utils.ts +7 -21
- package/syntaxes/graphql.dart.json +2 -4
- package/syntaxes/graphql.ex.json +1 -4
- package/syntaxes/graphql.js.json +3 -3
- package/syntaxes/graphql.json +13 -9
- package/syntaxes/graphql.lua.json +51 -0
- package/syntaxes/graphql.rb.json +1 -1
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +22 -7
- package/create-server-symlink.js +0 -8
- package/lib/debug.d.ts +0 -11
- package/lib/debug.d.ts.map +0 -1
- package/lib/debug.js +0 -48
- package/lib/debug.js.map +0 -1
- package/lib/extension.d.ts +0 -4
- package/lib/extension.d.ts.map +0 -1
- package/lib/extension.js +0 -187
- package/lib/extension.js.map +0 -1
- package/lib/languageServerClient.d.ts +0 -4
- package/lib/languageServerClient.d.ts.map +0 -1
- package/lib/languageServerClient.js +0 -57
- package/lib/languageServerClient.js.map +0 -1
- package/lib/statusBar.d.ts +0 -24
- package/lib/statusBar.d.ts.map +0 -1
- package/lib/statusBar.js +0 -46
- package/lib/statusBar.js.map +0 -1
- package/lib/testRunner/index.d.ts +0 -3
- package/lib/testRunner/index.d.ts.map +0 -1
- package/lib/testRunner/index.js +0 -49
- package/lib/testRunner/index.js.map +0 -1
- package/lib/testRunner/jest-config.d.ts +0 -14
- package/lib/testRunner/jest-config.d.ts.map +0 -1
- package/lib/testRunner/jest-config.js +0 -18
- package/lib/testRunner/jest-config.js.map +0 -1
- package/lib/testRunner/jest-vscode-environment.d.ts +0 -2
- package/lib/testRunner/jest-vscode-environment.d.ts.map +0 -1
- package/lib/testRunner/jest-vscode-environment.js +0 -19
- package/lib/testRunner/jest-vscode-environment.js.map +0 -1
- package/lib/testRunner/jest-vscode-framework-setup.d.ts +0 -1
- package/lib/testRunner/jest-vscode-framework-setup.d.ts.map +0 -1
- package/lib/testRunner/jest-vscode-framework-setup.js +0 -3
- package/lib/testRunner/jest-vscode-framework-setup.js.map +0 -1
- package/lib/testRunner/vscode-test-script.d.ts +0 -2
- package/lib/testRunner/vscode-test-script.d.ts.map +0 -1
- package/lib/testRunner/vscode-test-script.js +0 -23
- package/lib/testRunner/vscode-test-script.js.map +0 -1
- package/lib/utils.d.ts +0 -18
- package/lib/utils.d.ts.map +0 -1
- package/lib/utils.js +0 -52
- package/lib/utils.js.map +0 -1
- package/src/testRunner/README.md +0 -23
- package/src/testRunner/index.ts +0 -72
- package/src/testRunner/jest-config.ts +0 -17
- package/src/testRunner/jest-vscode-environment.ts +0 -25
- package/src/testRunner/jest-vscode-framework-setup.ts +0 -10
- package/src/testRunner/jest.d.ts +0 -37
- package/src/testRunner/vscode-test-script.ts +0 -38
- package/tsconfig.test.json +0 -4
- 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
|
+
});
|