vscode-apollo 2.6.3 → 2.6.5
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/.github/workflows/release.yml +1 -9
- package/.vscode/settings.json +5 -0
- package/CHANGELOG.md +16 -0
- package/package.json +6 -6
- package/patches/graphql+16.12.0.patch +26 -0
- package/sampleWorkspace/clientSchema/src/test.js +10 -1
- package/src/language-server/project/__tests__/duplicateOperations.test.ts +224 -0
- package/src/language-server/project/client.ts +50 -1
- package/src/language-server/project/internal.ts +0 -37
- package/src/language-server/utilities/intlConjunction.ts +4 -0
- package/tsconfig.json +1 -1
|
@@ -34,14 +34,6 @@ jobs:
|
|
|
34
34
|
with:
|
|
35
35
|
files: ".changeset/pre.json"
|
|
36
36
|
|
|
37
|
-
- name: Append NPM token to .npmrc
|
|
38
|
-
run: |
|
|
39
|
-
cat << EOF > "$HOME/.npmrc"
|
|
40
|
-
//registry.npmjs.org/:_authToken=$NPM_TOKEN
|
|
41
|
-
EOF
|
|
42
|
-
env:
|
|
43
|
-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
44
|
-
|
|
45
37
|
- name: Setup Node.js 24.x
|
|
46
38
|
uses: actions/setup-node@v3
|
|
47
39
|
with:
|
|
@@ -59,7 +51,7 @@ jobs:
|
|
|
59
51
|
publish: npm run changeset-publish
|
|
60
52
|
env:
|
|
61
53
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
62
|
-
NPM_TOKEN:
|
|
54
|
+
NPM_TOKEN: ""
|
|
63
55
|
|
|
64
56
|
- name: Attach VSX to GitHub release
|
|
65
57
|
if: steps.changesets.outcome == 'success' && steps.changesets.outputs.published == 'true'
|
package/.vscode/settings.json
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
"editor.wordWrapColumn": 110,
|
|
9
9
|
"files.trimTrailingWhitespace": true,
|
|
10
10
|
"files.insertFinalNewline": true,
|
|
11
|
+
"[typescript]": {
|
|
12
|
+
"editor.codeActionsOnSave": {
|
|
13
|
+
"source.organizeImports": "never"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
11
16
|
"files.exclude": {
|
|
12
17
|
"**/.git": true,
|
|
13
18
|
"**/.DS_Store": true,
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 2.6.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#259](https://github.com/apollographql/vscode-graphql/pull/259) [`b55410d`](https://github.com/apollographql/vscode-graphql/commit/b55410d78910e9f16cddff00fd6d9b7f7cc0af39) Thanks [@Cellule](https://github.com/Cellule)! - Report project name in warning when no files are found. Allows to better pinpoint offending project when working with multiple projects.
|
|
8
|
+
|
|
9
|
+
- [#306](https://github.com/apollographql/vscode-graphql/pull/306) [`9582690`](https://github.com/apollographql/vscode-graphql/commit/958269018a562f2eee880b68848407170a75af5f) Thanks [@phryneas](https://github.com/phryneas)! - report duplicate operations as diagnostic instead of throwing an error
|
|
10
|
+
|
|
11
|
+
## 2.6.4
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [#303](https://github.com/apollographql/vscode-graphql/pull/303) [`1be1252`](https://github.com/apollographql/vscode-graphql/commit/1be1252f9ce5135a557ce884ada3f96ca82e643c) Thanks [@phryneas](https://github.com/phryneas)! - Update `graphql` and `graphql-language-service` packages.
|
|
16
|
+
|
|
17
|
+
This allows descriptions in executable documents to be parsed correctly.
|
|
18
|
+
|
|
3
19
|
## 2.6.3
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "vscode-apollo",
|
|
3
3
|
"displayName": "Apollo GraphQL",
|
|
4
4
|
"description": "Rich editor support for GraphQL client and server development that seamlessly integrates with the Apollo platform",
|
|
5
|
-
"version": "2.6.
|
|
5
|
+
"version": "2.6.5",
|
|
6
6
|
"referenceID": "87197759-7617-40d0-b32e-46d378e907c7",
|
|
7
7
|
"author": "Apollo GraphQL <opensource@apollographql.com>",
|
|
8
8
|
"license": "MIT",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"test:textmate-graphql-connectors-mapping": "npm run build:textmate -- --snapshot connectors.mapping.yaml; npx vscode-tmgrammar-test src/__tests__/fixtures/textmate/*.graphql",
|
|
38
38
|
"update-connectors-community": "git subtree pull --prefix sampleWorkspace/connectors-community https://github.com/apollographql/connectors-community.git main --squash",
|
|
39
39
|
"codegen": "graphql-codegen",
|
|
40
|
-
"vscode:prepublish": "npm run build:production"
|
|
40
|
+
"vscode:prepublish": "npm run build:production",
|
|
41
|
+
"postinstall": "patch-package"
|
|
41
42
|
},
|
|
42
43
|
"engines": {
|
|
43
44
|
"vscode": "^1.90.0"
|
|
@@ -52,12 +53,11 @@
|
|
|
52
53
|
"dotenv": "16.4.7",
|
|
53
54
|
"fractional-indexing": "2.1.0",
|
|
54
55
|
"glob": "11.0.0",
|
|
55
|
-
"graphql": "16.
|
|
56
|
-
"graphql-language-service": "5.
|
|
56
|
+
"graphql": "16.12.0",
|
|
57
|
+
"graphql-language-service": "5.5.0",
|
|
57
58
|
"graphql-tag": "2.12.6",
|
|
58
59
|
"jsonc-parser": "^3.3.1",
|
|
59
60
|
"lodash.debounce": "4.0.8",
|
|
60
|
-
"lodash.throttle": "4.1.1",
|
|
61
61
|
"lz-string": "1.5.0",
|
|
62
62
|
"minimatch": "10.0.1",
|
|
63
63
|
"moment": "2.30.1",
|
|
@@ -81,7 +81,6 @@
|
|
|
81
81
|
"@types/jest": "29.5.14",
|
|
82
82
|
"@types/lodash.debounce": "4.0.9",
|
|
83
83
|
"@types/lodash.merge": "4.6.9",
|
|
84
|
-
"@types/lodash.throttle": "4.1.9",
|
|
85
84
|
"@types/node": "20.14.10",
|
|
86
85
|
"@types/vscode": "1.90.0",
|
|
87
86
|
"@typescript-eslint/eslint-plugin": "6.9.1",
|
|
@@ -100,6 +99,7 @@
|
|
|
100
99
|
"memfs": "4.15.0",
|
|
101
100
|
"npm-run-all": "4.1.5",
|
|
102
101
|
"oniguruma-parser": "^0.12.1",
|
|
102
|
+
"patch-package": "^8.0.1",
|
|
103
103
|
"prettier": "3.4.2",
|
|
104
104
|
"rimraf": "6.0.1",
|
|
105
105
|
"ts-jest": "29.2.5",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
diff --git a/node_modules/graphql/validation/rules/ValuesOfCorrectTypeRule.js b/node_modules/graphql/validation/rules/ValuesOfCorrectTypeRule.js
|
|
2
|
+
index d2247e0..4c61625 100644
|
|
3
|
+
--- a/node_modules/graphql/validation/rules/ValuesOfCorrectTypeRule.js
|
|
4
|
+
+++ b/node_modules/graphql/validation/rules/ValuesOfCorrectTypeRule.js
|
|
5
|
+
@@ -132,7 +132,7 @@ function ValuesOfCorrectTypeRule(context) {
|
|
6
|
+
EnumValue: (node) => isValidValueNode(context, node),
|
|
7
|
+
IntValue: (node) => isValidValueNode(context, node),
|
|
8
|
+
FloatValue: (node) => isValidValueNode(context, node),
|
|
9
|
+
- StringValue: (node) => isValidValueNode(context, node),
|
|
10
|
+
+ StringValue: (node, key) => key !== "description" && isValidValueNode(context, node),
|
|
11
|
+
BooleanValue: (node) => isValidValueNode(context, node),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
diff --git a/node_modules/graphql/validation/rules/ValuesOfCorrectTypeRule.mjs b/node_modules/graphql/validation/rules/ValuesOfCorrectTypeRule.mjs
|
|
15
|
+
index fec0a51..55f4d56 100644
|
|
16
|
+
--- a/node_modules/graphql/validation/rules/ValuesOfCorrectTypeRule.mjs
|
|
17
|
+
+++ b/node_modules/graphql/validation/rules/ValuesOfCorrectTypeRule.mjs
|
|
18
|
+
@@ -117,7 +117,7 @@ export function ValuesOfCorrectTypeRule(context) {
|
|
19
|
+
EnumValue: (node) => isValidValueNode(context, node),
|
|
20
|
+
IntValue: (node) => isValidValueNode(context, node),
|
|
21
|
+
FloatValue: (node) => isValidValueNode(context, node),
|
|
22
|
+
- StringValue: (node) => isValidValueNode(context, node),
|
|
23
|
+
+ StringValue: (node, key) => key !== "description" && isValidValueNode(context, node),
|
|
24
|
+
BooleanValue: (node) => isValidValueNode(context, node),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import gql from "graphql-tag";
|
|
2
2
|
gql`
|
|
3
|
-
|
|
3
|
+
"""
|
|
4
|
+
Query-Level Comment Test
|
|
5
|
+
"""
|
|
6
|
+
query Test(
|
|
7
|
+
"""
|
|
8
|
+
Argument-Level Comment Test
|
|
9
|
+
"""
|
|
10
|
+
$defer: Boolean!
|
|
11
|
+
) {
|
|
12
|
+
|
|
4
13
|
featureFlagDefer @client(always: false) @export(as: "defer")
|
|
5
14
|
droid(id: "2000") {
|
|
6
15
|
name
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { GraphQLClientProject } from "../client";
|
|
2
|
+
import { basename } from "path";
|
|
3
|
+
import { DiagnosticSeverity } from "vscode-languageserver/node";
|
|
4
|
+
|
|
5
|
+
import { vol } from "memfs";
|
|
6
|
+
import { ClientConfig, parseApolloConfig } from "../../config";
|
|
7
|
+
import { URI } from "vscode-uri";
|
|
8
|
+
|
|
9
|
+
const serviceSchema = /* GraphQL */ `
|
|
10
|
+
type Query {
|
|
11
|
+
me: User
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type User {
|
|
15
|
+
name: String
|
|
16
|
+
friends: [User]
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
// Operation with name "GetUser" in first file
|
|
21
|
+
const fileA = /* GraphQL */ `
|
|
22
|
+
query GetUser {
|
|
23
|
+
me {
|
|
24
|
+
name
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
// Operation with same name "GetUser" in second file
|
|
30
|
+
const fileB = /* GraphQL */ `
|
|
31
|
+
query GetUser {
|
|
32
|
+
me {
|
|
33
|
+
friends {
|
|
34
|
+
name
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
// File with two operations with duplicate names in the same file
|
|
41
|
+
const fileWithDuplicates = /* GraphQL */ `
|
|
42
|
+
query DuplicateOp {
|
|
43
|
+
me {
|
|
44
|
+
name
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
query DuplicateOp {
|
|
49
|
+
me {
|
|
50
|
+
friends {
|
|
51
|
+
name
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const rootURI = URI.file(process.cwd());
|
|
58
|
+
|
|
59
|
+
const config = parseApolloConfig({
|
|
60
|
+
client: {
|
|
61
|
+
service: {
|
|
62
|
+
name: "server",
|
|
63
|
+
localSchemaFile: "./schema.graphql",
|
|
64
|
+
},
|
|
65
|
+
includes: ["./src/**.graphql"],
|
|
66
|
+
excludes: ["./__tests__"],
|
|
67
|
+
},
|
|
68
|
+
engine: {},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
jest.mock("fs");
|
|
72
|
+
|
|
73
|
+
describe("Duplicate operation detection", () => {
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
vol.reset();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
afterEach(async () => {
|
|
79
|
+
jest.restoreAllMocks();
|
|
80
|
+
// Wait for potentially throttled calls to complete (as suggested in code review)
|
|
81
|
+
// The 300ms timeout accounts for the setTimeout(0) used in invalidate()
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should report error diagnostics for duplicate operations across multiple files", async () => {
|
|
86
|
+
vol.fromJSON({
|
|
87
|
+
"apollo.config.js": `module.exports = {
|
|
88
|
+
client: {
|
|
89
|
+
service: {
|
|
90
|
+
localSchemaFile: './schema.graphql'
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}`,
|
|
94
|
+
"schema.graphql": serviceSchema,
|
|
95
|
+
"src/a.graphql": fileA,
|
|
96
|
+
"src/b.graphql": fileB,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const project = new GraphQLClientProject({
|
|
100
|
+
config: config as ClientConfig,
|
|
101
|
+
loadingHandler: {
|
|
102
|
+
handle: async (msg, val) => val,
|
|
103
|
+
handleSync: (msg, val) => val(),
|
|
104
|
+
showError: (msg) => {
|
|
105
|
+
// fail the test if this is ever called
|
|
106
|
+
expect(msg).toBeUndefined();
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
configFolderURI: rootURI,
|
|
110
|
+
clientIdentity: {
|
|
111
|
+
name: "",
|
|
112
|
+
version: "",
|
|
113
|
+
referenceID: "",
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const diagnosticsByFile: Record<string, any[]> = Object.create(null);
|
|
118
|
+
project.onDiagnostics(({ diagnostics, uri }) => {
|
|
119
|
+
const path = basename(URI.parse(uri).path);
|
|
120
|
+
if (!diagnosticsByFile[path]) diagnosticsByFile[path] = [];
|
|
121
|
+
diagnosticsByFile[path].push(...diagnostics);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await project.whenReady;
|
|
125
|
+
await project.validate();
|
|
126
|
+
|
|
127
|
+
// Both files should have error diagnostics
|
|
128
|
+
expect(diagnosticsByFile["a.graphql"]).toBeDefined();
|
|
129
|
+
expect(diagnosticsByFile["b.graphql"]).toBeDefined();
|
|
130
|
+
|
|
131
|
+
// Check that both files have diagnostics with Error severity
|
|
132
|
+
const aGraphqlErrors = diagnosticsByFile["a.graphql"].filter(
|
|
133
|
+
(d) => d.severity === DiagnosticSeverity.Error,
|
|
134
|
+
);
|
|
135
|
+
const bGraphqlErrors = diagnosticsByFile["b.graphql"].filter(
|
|
136
|
+
(d) => d.severity === DiagnosticSeverity.Error,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
expect(aGraphqlErrors.length).toBeGreaterThan(0);
|
|
140
|
+
expect(bGraphqlErrors.length).toBeGreaterThan(0);
|
|
141
|
+
|
|
142
|
+
// Check that the diagnostic messages mention duplicate operations
|
|
143
|
+
expect(aGraphqlErrors[0].message).toMatch(/multiple definitions.*GetUser/s);
|
|
144
|
+
expect(bGraphqlErrors[0].message).toMatch(/multiple definitions.*GetUser/s);
|
|
145
|
+
|
|
146
|
+
// Check that the source is correct
|
|
147
|
+
expect(aGraphqlErrors[0].source).toBe("GraphQL: Validation");
|
|
148
|
+
expect(bGraphqlErrors[0].source).toBe("GraphQL: Validation");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should report error diagnostics for duplicate operations in the same file", async () => {
|
|
152
|
+
vol.fromJSON({
|
|
153
|
+
"apollo.config.js": `module.exports = {
|
|
154
|
+
client: {
|
|
155
|
+
service: {
|
|
156
|
+
localSchemaFile: './schema.graphql'
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}`,
|
|
160
|
+
"schema.graphql": serviceSchema,
|
|
161
|
+
"src/duplicates.graphql": fileWithDuplicates,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const project = new GraphQLClientProject({
|
|
165
|
+
config: config as ClientConfig,
|
|
166
|
+
loadingHandler: {
|
|
167
|
+
handle: async (msg, val) => val,
|
|
168
|
+
handleSync: (msg, val) => val(),
|
|
169
|
+
showError: (msg) => {
|
|
170
|
+
// fail the test if this is ever called
|
|
171
|
+
expect(msg).toBeUndefined();
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
configFolderURI: rootURI,
|
|
175
|
+
clientIdentity: {
|
|
176
|
+
name: "",
|
|
177
|
+
version: "",
|
|
178
|
+
referenceID: "",
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const diagnosticsByFile: Record<string, any[]> = Object.create(null);
|
|
183
|
+
project.onDiagnostics(({ diagnostics, uri }) => {
|
|
184
|
+
const path = basename(URI.parse(uri).path);
|
|
185
|
+
if (!diagnosticsByFile[path]) diagnosticsByFile[path] = [];
|
|
186
|
+
diagnosticsByFile[path].push(...diagnostics);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await project.whenReady;
|
|
190
|
+
await project.validate();
|
|
191
|
+
|
|
192
|
+
// File should have error diagnostics
|
|
193
|
+
expect(diagnosticsByFile["duplicates.graphql"]).toBeDefined();
|
|
194
|
+
|
|
195
|
+
// Check that the file has diagnostics with Error severity
|
|
196
|
+
const duplicatesErrors = diagnosticsByFile["duplicates.graphql"].filter(
|
|
197
|
+
(d) => d.severity === DiagnosticSeverity.Error,
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// Should have at least 2 error diagnostics (one for each occurrence of DuplicateOp)
|
|
201
|
+
// Note: GraphQL's native validation may also report these, so we might have more
|
|
202
|
+
expect(duplicatesErrors.length).toBeGreaterThanOrEqual(2);
|
|
203
|
+
|
|
204
|
+
// Find diagnostics specifically from our duplicate detection (not native GraphQL validation)
|
|
205
|
+
const ourDuplicateErrors = duplicatesErrors.filter((d) =>
|
|
206
|
+
d.message.includes("multiple definitions"),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// We should have exactly 2 diagnostics from our duplicate detection
|
|
210
|
+
expect(ourDuplicateErrors.length).toBe(2);
|
|
211
|
+
|
|
212
|
+
// Check that both diagnostic messages mention duplicate operations
|
|
213
|
+
expect(ourDuplicateErrors[0].message).toMatch(
|
|
214
|
+
/multiple definitions.*DuplicateOp/s,
|
|
215
|
+
);
|
|
216
|
+
expect(ourDuplicateErrors[1].message).toMatch(
|
|
217
|
+
/multiple definitions.*DuplicateOp/s,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Check that the source is correct
|
|
221
|
+
expect(ourDuplicateErrors[0].source).toBe("GraphQL: Validation");
|
|
222
|
+
expect(ourDuplicateErrors[1].source).toBe("GraphQL: Validation");
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
DiagnosticSeverity,
|
|
47
47
|
CancellationToken,
|
|
48
48
|
Position,
|
|
49
|
+
Range,
|
|
49
50
|
Location,
|
|
50
51
|
CodeLens,
|
|
51
52
|
InsertTextFormat,
|
|
@@ -104,6 +105,7 @@ import type { CodeActionInfo } from "../errors/validation";
|
|
|
104
105
|
import { GraphQLDiagnostic } from "../diagnostics";
|
|
105
106
|
import { isInterfaceType } from "graphql";
|
|
106
107
|
import { GraphQLInternalProject } from "./internal";
|
|
108
|
+
import { intlConjunction } from "../utilities/intlConjunction";
|
|
107
109
|
|
|
108
110
|
type Maybe<T> = null | undefined | T;
|
|
109
111
|
|
|
@@ -203,7 +205,7 @@ export class GraphQLClientProject extends GraphQLInternalProject {
|
|
|
203
205
|
|
|
204
206
|
if (this.allIncludedFiles().length === 0) {
|
|
205
207
|
console.warn(
|
|
206
|
-
|
|
208
|
+
`⚠️ It looks like there are 0 files associated with Apollo Project: "${this.displayName}". ` +
|
|
207
209
|
"This may be because you don't have any files yet, or your includes/excludes " +
|
|
208
210
|
"fields are configured incorrectly, and Apollo can't find your files. " +
|
|
209
211
|
"For help configuring Apollo projects, see this guide: https://go.apollo.dev/t/config",
|
|
@@ -381,6 +383,11 @@ export class GraphQLClientProject extends GraphQLInternalProject {
|
|
|
381
383
|
|
|
382
384
|
const fragments = this.fragments;
|
|
383
385
|
|
|
386
|
+
const operationNameToLocations: Map<
|
|
387
|
+
string,
|
|
388
|
+
Array<{ uri: DocumentUri; range: Range }>
|
|
389
|
+
> = new Map();
|
|
390
|
+
|
|
384
391
|
for (const [uri, documentsForFile] of this.documentsByFile) {
|
|
385
392
|
for (const document of documentsForFile) {
|
|
386
393
|
diagnosticSet.addDiagnostics(
|
|
@@ -392,6 +399,48 @@ export class GraphQLClientProject extends GraphQLInternalProject {
|
|
|
392
399
|
this._validationRules,
|
|
393
400
|
),
|
|
394
401
|
);
|
|
402
|
+
|
|
403
|
+
// Collect operation names for duplicate detection
|
|
404
|
+
for (const definition of document.ast?.definitions || []) {
|
|
405
|
+
if (
|
|
406
|
+
definition.kind === Kind.OPERATION_DEFINITION &&
|
|
407
|
+
definition.name
|
|
408
|
+
) {
|
|
409
|
+
const operationName = definition.name.value;
|
|
410
|
+
|
|
411
|
+
let locations = operationNameToLocations.get(operationName);
|
|
412
|
+
if (!locations) {
|
|
413
|
+
operationNameToLocations.set(operationName, (locations = []));
|
|
414
|
+
}
|
|
415
|
+
locations.push({ uri, range: rangeForASTNode(definition.name) });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Add diagnostics for duplicate operation names
|
|
422
|
+
for (const [operationName, locations] of operationNameToLocations) {
|
|
423
|
+
if (locations.length > 1) {
|
|
424
|
+
const textualLocations = locations.map(({ uri, range }) => {
|
|
425
|
+
return `${URI.parse(uri).fsPath}:${range.start.line + 1},${range.start.character + 1}`;
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const message = `
|
|
429
|
+
There are multiple definitions for the \`${operationName}\` operation.
|
|
430
|
+
Please fix all naming conflicts before continuing.
|
|
431
|
+
Conflicting definitions found at ${intlConjunction.format(textualLocations)}.
|
|
432
|
+
`.trim();
|
|
433
|
+
|
|
434
|
+
for (const { uri, range } of locations) {
|
|
435
|
+
diagnosticSet.addDiagnostics(uri, [
|
|
436
|
+
{
|
|
437
|
+
severity: DiagnosticSeverity.Error,
|
|
438
|
+
range,
|
|
439
|
+
message,
|
|
440
|
+
source: "GraphQL: Validation",
|
|
441
|
+
},
|
|
442
|
+
]);
|
|
443
|
+
}
|
|
395
444
|
}
|
|
396
445
|
}
|
|
397
446
|
for (const [uri, diagnostics] of diagnosticSet.entries()) {
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
isTypeSystemExtensionNode,
|
|
10
10
|
DefinitionNode,
|
|
11
11
|
GraphQLSchema,
|
|
12
|
-
Kind,
|
|
13
12
|
} from "graphql";
|
|
14
13
|
|
|
15
14
|
import {
|
|
@@ -29,7 +28,6 @@ import {
|
|
|
29
28
|
} from "../providers/schema";
|
|
30
29
|
import { ApolloEngineClient, ClientIdentity } from "../engine";
|
|
31
30
|
import { GraphQLProject, DocumentUri, GraphQLProjectConfig } from "./base";
|
|
32
|
-
import throttle from "lodash.throttle";
|
|
33
31
|
import { FileSet } from "../fileSet";
|
|
34
32
|
import { getSupportedExtensions } from "../utilities/languageIdForExtension";
|
|
35
33
|
|
|
@@ -161,7 +159,6 @@ export abstract class GraphQLInternalProject
|
|
|
161
159
|
|
|
162
160
|
fileWasDeleted(uri: DocumentUri) {
|
|
163
161
|
this.removeGraphQLDocumentsFor(uri);
|
|
164
|
-
this.checkForDuplicateOperations();
|
|
165
162
|
}
|
|
166
163
|
|
|
167
164
|
documentDidChange = (document: TextDocument) => {
|
|
@@ -175,42 +172,8 @@ export abstract class GraphQLInternalProject
|
|
|
175
172
|
} else {
|
|
176
173
|
this.removeGraphQLDocumentsFor(document.uri);
|
|
177
174
|
}
|
|
178
|
-
this.checkForDuplicateOperations();
|
|
179
175
|
};
|
|
180
176
|
|
|
181
|
-
checkForDuplicateOperations = throttle(
|
|
182
|
-
() => {
|
|
183
|
-
const filePathForOperationName: Record<string, string> = {};
|
|
184
|
-
for (const [
|
|
185
|
-
fileUri,
|
|
186
|
-
documentsForFile,
|
|
187
|
-
] of this.documentsByFile.entries()) {
|
|
188
|
-
const filePath = URI.parse(fileUri).fsPath;
|
|
189
|
-
for (const document of documentsForFile) {
|
|
190
|
-
if (!document.ast) continue;
|
|
191
|
-
for (const definition of document.ast.definitions) {
|
|
192
|
-
if (
|
|
193
|
-
definition.kind === Kind.OPERATION_DEFINITION &&
|
|
194
|
-
definition.name
|
|
195
|
-
) {
|
|
196
|
-
const operationName = definition.name.value;
|
|
197
|
-
if (operationName in filePathForOperationName) {
|
|
198
|
-
const conflictingFilePath =
|
|
199
|
-
filePathForOperationName[operationName];
|
|
200
|
-
throw new Error(
|
|
201
|
-
`️️There are multiple definitions for the \`${definition.name.value}\` operation. Please fix all naming conflicts before continuing.\nConflicting definitions found at ${filePath} and ${conflictingFilePath}.`,
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
filePathForOperationName[operationName] = filePath;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
250,
|
|
211
|
-
{ leading: true, trailing: true },
|
|
212
|
-
);
|
|
213
|
-
|
|
214
177
|
private removeGraphQLDocumentsFor(uri: DocumentUri) {
|
|
215
178
|
if (this.documentsByFile.has(uri)) {
|
|
216
179
|
this.documentsByFile.delete(uri);
|
package/tsconfig.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"noUnusedParameters": false,
|
|
19
19
|
"noUnusedLocals": false,
|
|
20
20
|
"forceConsistentCasingInFileNames": true,
|
|
21
|
-
"lib": ["es2020", "esnext.asynciterable"],
|
|
21
|
+
"lib": ["es2020", "esnext.asynciterable", "ES2021.Intl"],
|
|
22
22
|
"types": ["node", "jest"],
|
|
23
23
|
"baseUrl": "."
|
|
24
24
|
},
|