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.
@@ -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: ${{ secrets.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'
@@ -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.3",
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.9.0",
56
- "graphql-language-service": "5.2.2",
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
- query Test($defer: Boolean!) {
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
- "⚠️ It looks like there are 0 files associated with this Apollo Project. " +
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);
@@ -0,0 +1,4 @@
1
+ export const intlConjunction = new Intl.ListFormat("en", {
2
+ style: "long",
3
+ type: "conjunction",
4
+ });
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
  },