├── .changeset ├── README.md └── config.json ├── .envrc_sample ├── .github ├── disabled-workflows │ └── pre-release.yml └── workflows │ ├── release-or-pr.yml │ └── test.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── assets └── images │ └── logo.png ├── grammars ├── graphql.js.json ├── graphql.json ├── graphql.markdown.codeblock.json ├── graphql.re.json └── graphql.rescript.json ├── language └── language-configuration.json ├── package-lock.json ├── package.json ├── renovate.json ├── snippets └── graphql.json ├── src ├── CustomInitializationFailedHandler.ts ├── client │ ├── graphql-codelens-provider.ts │ ├── graphql-content-provider.ts │ ├── network-helper.ts │ └── source-helper.ts ├── extension.ts ├── server │ └── server.ts ├── status │ └── index.ts └── test │ ├── extension.test.ts │ └── index.ts ├── tsconfig.json └── tslint.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.5.0/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { "repo": "graphql/vscode-graphql" } 6 | ], 7 | "commit": false, 8 | "access": "public", 9 | "baseBranch": "master" 10 | } 11 | -------------------------------------------------------------------------------- /.envrc_sample: -------------------------------------------------------------------------------- 1 | PRODUCTION=<1> # Default to dry-run, to actually publish, use PRODUCTION=1 2 | PAT= # https://code.visualstudio.com/api/working-with-extensions/publishing-extension#get-a-personal-access-token -------------------------------------------------------------------------------- /.github/disabled-workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | name: automatic-release 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - master 7 | 8 | env: 9 | BOT_GH_TOKEN: ${{ secrets.BOT_GH_TOKEN }} 10 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 11 | 12 | jobs: 13 | prerelease: 14 | name: Create prerelease 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Install Dependencies 19 | run: npm ci 20 | 21 | - name: Setup GIT 22 | run: | 23 | git config --global user.email "95909575+graphql-bot@users.noreply.github.com" 24 | git config --global user.name "GraphQL Bot" 25 | git remote add github "https://$GITHUB_ACTOR:$BOT_GH_TOKEN@github.com/$GITHUB_REPOSITORY.git" || true 26 | 27 | - name: Bump version 28 | id: bump 29 | run: | 30 | npm version patch -m "%s (prerelease)" 31 | 32 | - name: VSCE Pre-Release Publish 33 | id: publish 34 | run: | 35 | ./node_modules/.bin/vsce publish --pre-release 36 | 37 | - name: Push to master 38 | run: | 39 | git push github HEAD:"${GITHUB_REF}" 40 | -------------------------------------------------------------------------------- /.github/workflows/release-or-pr.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Repo 14 | uses: actions/checkout@v2 15 | with: 16 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 17 | fetch-depth: 0 18 | 19 | - name: Setup Node.js 18.x 20 | uses: actions/setup-node@v2 21 | with: 22 | node-version: "14" 23 | 24 | - name: Install Dependencies 25 | run: npm ci 26 | 27 | - name: Create Release Pull Request or Publish to npm 28 | id: changesets 29 | uses: changesets/action@v1 30 | with: 31 | publish: npm run publish 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | OPEN_VSX_ACCESS_TOKEN: ${{ secrets.OPEN_VSX_ACCESS_TOKEN }} 35 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 36 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | compile: 7 | name: Compile and check peer dependencies 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Install Dependencies 12 | run: npm install 13 | - name: Compile 14 | run: npm run compile 15 | - name: Check peer dependencies 16 | run: npm list --production --parseable --depth=99999 --loglevel=error 17 | - name: Attempt vsce Package 18 | run: npm run vsce:package 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | .vscode/ 6 | 7 | .DS_Store 8 | .envrc -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Attach to Server", 10 | "type": "node", 11 | "request": "attach", 12 | "port": 6009, 13 | "sourceMaps": true, 14 | "outFiles": ["${workspaceRoot}/client/server/**/*.js"] 15 | }, 16 | { 17 | "name": "Extension", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 22 | "outFiles": ["${workspaceFolder}/out/extension.js"], 23 | "sourceMaps": true, 24 | "preLaunchTask": "npm: compile" 25 | }, 26 | { 27 | "name": "Extension Tests", 28 | "type": "extensionHost", 29 | "request": "launch", 30 | "runtimeExecutable": "${execPath}", 31 | "args": [ 32 | "--extensionDevelopmentPath=${workspaceFolder}", 33 | "--extensionTestsPath=${workspaceFolder}/out/test" 34 | ], 35 | "outFiles": ["${workspaceFolder}/out/test/**/*.js"], 36 | "preLaunchTask": "npm: watch" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.formatOnSave": true, 4 | "prettier.tabWidth": 2, 5 | "prettier.eslintIntegration": true, 6 | "prettier.singleQuote": false, 7 | "prettier.semi": false, 8 | "prettier.trailingComma": "all", 9 | "prettier.arrowParens": "avoid", 10 | "files.exclude": { 11 | "out": false // set this to true to hide the "out" folder with the compiled JS files 12 | }, 13 | "search.exclude": { 14 | "out": true // set this to false to include "out" folder in search results 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "always" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | out/**/*.map 5 | src/** 6 | .gitignore 7 | tsconfig.json 8 | vsc-extension-quickstart.md 9 | tslint.json 10 | node_modules 11 | .github 12 | renovate.json -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 0.3.52 4 | 5 | ### Patch Changes 6 | 7 | - [#452](https://github.com/graphql/vscode-graphql/pull/452) [`8878e42`](https://github.com/graphql/vscode-graphql/commit/8878e428c83eed4f53510f9071e9964f48b5d214) Thanks [@acao](https://github.com/acao)! - Limit activation events for package.json file provided `graphql-config` 8 | 9 | ## 0.3.50 10 | 11 | ### Patch Changes 12 | 13 | - [#448](https://github.com/graphql/vscode-graphql/pull/448) [`f894dad`](https://github.com/graphql/vscode-graphql/commit/f894daddfe7382f7eb8e9c921c54904255a3557c) Thanks [@acao](https://github.com/acao)! - ugprade graphql-language-service-server to the latest patch version for windows path fix 14 | 15 | * [#436](https://github.com/graphql/vscode-graphql/pull/436) [`2370607`](https://github.com/graphql/vscode-graphql/commit/23706071c6338c05e951783a3e7dfd5000da6d02) Thanks [@orta](https://github.com/orta)! - Adds support for making clicking on the graphql status item show the output channel 16 | 17 | - [#277](https://github.com/graphql/vscode-graphql/pull/277) [`6017872`](https://github.com/graphql/vscode-graphql/commit/6017872b7f19ef5c3fcad404fca9ffd5b8ba5d87) Thanks [@AumyF](https://github.com/AumyF)! - provide 'Execute Query' for `/* GraphQL */` templates 18 | 19 | * [#422](https://github.com/graphql/vscode-graphql/pull/422) [`0e2235d`](https://github.com/graphql/vscode-graphql/commit/0e2235d7fa229b78fb330c337d14fabf679884c2) Thanks [@orta](https://github.com/orta)! - Use the vscode theme API to set the right colours for the status bar item 20 | 21 | ## 0.3.48 22 | 23 | ### Patch Changes 24 | 25 | - [#402](https://github.com/graphql/vscode-graphql/pull/402) [`a97e5df`](https://github.com/graphql/vscode-graphql/commit/a97e5df9933dfc79b06e5202c84216cfe2d5f865) Thanks [@acao](https://github.com/acao)! - thanks @markusjwetzel! Add directive highlighting for type system directives. [https://github.com/graphql/vscode-graphql/pull/326](https://github.com/graphql/vscode-graphql/pull/326) 26 | 27 | ## 0.3.43 28 | 29 | ### Patch Changes 30 | 31 | - [#391](https://github.com/graphql/vscode-graphql/pull/391) [`6be5593`](https://github.com/graphql/vscode-graphql/commit/6be5593a45a4629f3438f59223ecb04949cb48d2) Thanks [@acao](https://github.com/acao)! - LSP upgrades: 32 | 33 | - bugfix for `insertText` & completion on invalid list types 34 | - add support for template strings and tags with replacement expressions, so strings like these should work now: 35 | 36 | ```ts 37 | const = /*GraphiQL*/ 38 | ` 39 | ${myFragments} 40 | query MyQuery { 41 | something 42 | ${anotherString} 43 | } 44 | 45 | ` 46 | ``` 47 | 48 | ```ts 49 | const = gql` 50 | ${myFragments} 51 | query MyQuery { 52 | something 53 | ${anotherString} 54 | } 55 | 56 | ` 57 | ``` 58 | 59 | All notable changes to the "vscode-graphql" extension will be manually documented in this file. 60 | 61 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 62 | 63 | The git log should show a fairly clean view of each of these new versions, and the issues/PRs associated. 64 | 65 | # 0.3.25 66 | 67 | Remove `node_modules` from bundle after adding `esbuild` to make the extension bundle smaller 68 | 69 | # 0.3.24 70 | 71 | Add highlighting and langauge support for `.mjs`, `.cjs`, `.es6`, `.esm` and other similar extensions 72 | 73 | # 0.3.23 74 | 75 | Major bugfixes for language features. Most bugs with language features not working should be resolved. 76 | 77 | The `useSchemaFileDefinition` setting was deprecated, and SDL-driven projects work by default. If you want to opt-into an experimental feature to cache graphql-config schema result for definitions (useful for remote schemas), consult the readme on how to configure `cacheSchemaFileForLookup` option in vscode settings, or graphql config (yes you can enable/disable it per-project!) 78 | 79 | Definition lookup works by default with SDL file schemas. `cacheSchemaFileForLookup` must be enabled if you have a remote schema want definition lookup for input types, etc in queries 80 | 81 | # 0.3.19 82 | 83 | - support `graphql-config` for `.ts` and `.toml` files by upgrading `graphql-config` & `graphql-language-service-server` 84 | - use `*` activation event, because `graphql-config` in `package.json` is impossible to detect otherwise using vscode `activationEvents` 85 | - support additional language features in `graphql-language-service-server` such as interface implements interfaces, etc 86 | - upgrade operation execution to use a new graphql client and support subscriptions 87 | - fix openvsx & vscode publish by re-creating PATs and signing new agreements 88 | 89 | Note: there are still some known bugs in the language server we will be fixing soon: 90 | 91 | - if you don't see editor output, please check your config 92 | - output channel may show errors even after your configuration works 93 | - there may be issues with schema file loading 94 | 95 | # 0.3.13 96 | 97 | LSP bugfixes: 98 | 99 | - [streaming interface bug](https://github.com/graphql/graphiql/blob/main/packages/graphql-language-service-server/CHANGELOG.md#256-2020-11-28) 100 | - bugfixes for windows filepaths in LSP server 101 | 102 | # 0.3.8 103 | 104 | - require `dotenv` in the server runtime (for loading graphql config values), and allow a `graphql-config.dotEnvPath` configuration to specify specific paths 105 | - reload server on workspace configuration changes 106 | - reload severside `graphql-config` and language service on config file changes. definitions cache/etc will be rebuilt 107 | - note: client not configured to reload on graphql config changes yet (i.e endpoints) 108 | - accept all `graphql-config.loadConfig()` options 109 | 110 | # 0.3.7 111 | 112 | - update underlying `graphql-language-service-server` to allow .gql, .graphqls extensions 113 | 114 | # 0.3.6 115 | 116 | - documentation fix 117 | 118 | # 0.3.5 119 | 120 | - readme documentation improvements, more examples, FAQ, known issues 121 | - bump `graphql-language-service-server` to allow implicit fragment completion (non-inline fragments). just include your fragments file or string in the graphql-config `documents` 122 | 123 | # 0.3.4 124 | 125 | - remove insiders announcement until tooling is properly in place, and insiders extension is up to date 126 | 127 | # 0.3.3 128 | 129 | - `useSchemaFileDefinition` setting 130 | 131 | # 0.3.2 132 | 133 | - #213: bugfix for input validation on operation exection 134 | 135 | # 0.3.1 🎉 136 | 137 | - upgrade to `graphql-config@3` 138 | - upgrade to latest major version of `graphql-language-service-server` 139 | - upgrades `graphql-config@3` 140 | - remove watchman dependency 🎉 141 | - introduce workspace symbol lookup, outline 142 | - validation and completion for input variables 143 | - generate a schema output by default, for code-first schemas. SDL first schemas have an override option now 144 | 145 | ## Historical 0.2.x versions 146 | 147 | [todo] 148 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Actual Behavior 2 | 3 | ## Expected Behavior 4 | 5 | ## Steps to Reproduce the Problem Or Description 6 | 7 | ## Specifications 8 | 9 | - GraphQL for VSCode Extension Version: 10 | - VSCode Version: 11 | - OS Name: 12 | - OS Version: 13 | - graphql config filename and format example: 14 | 15 | ## Logs Of TS Server || GraphQL Language Service 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Prisma Data Services 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VSCode GraphQL [ARCHIVED] 2 | 3 | > The extension has been moved to [`graphql/graphiql` monorepo](https://github.com/graphql/graphiql/tree/main/packages/vscode-graphql) - please open all new tickets and PRs there. Operation execution support was temporarily dropped and is planned to be replaced before 2023. 4 | Another useful operation exection extension that works inline is [vscode-graphiql-explorer](https://marketplace.visualstudio.com/items?itemName=GabrielNordeborn.vscode-graphiql-explorer). Another handy extension - [graphql notebooks](https://github.com/joyceerhl/vscode-github-graphql-notebooks/issues/7#issuecomment-1086962711) provides a UX where you can keep graphql "notebooks" using the vscode notebooks API. 5 | 6 | GraphQL extension for VSCode was built with the aim to tightly integrate the GraphQL Ecosystem with VSCode for an awesome developer experience. 7 | 8 | ![](https://camo.githubusercontent.com/97dc1080d5e6883c4eec3eaa6b7d0f29802e6b4b/687474703a2f2f672e7265636f726469742e636f2f497379504655484e5a342e676966) 9 | 10 | > 💡 **Note:** This extension no longer supports `.prisma` files. If you are using this extension with GraphQL 1, please rename your datamodel from `datamodel.prisma` to `datamodel.graphql` and this extension will pick that up. 11 | 12 | ## Features 13 | 14 | Lots of new improvements happening! We now have a [`CHANGELOG.md`](https://github.com/graphql/vscode-graphql/blob/master/CHANGELOG.md#change-log) 15 | 16 | ### General features 17 | 18 | - Load the extension on detecting `graphql-config file` at root level or in a parent level directory 19 | - Load the extension in `.graphql`, `.gql files` 20 | - Load the extension on detecting `gql` tag in js, ts, jsx, tsx, vue files 21 | - Load the extension inside `gql`/`graphql` fenced code blocks in markdown files 22 | - execute query/mutation/subscription operation, embedded or in graphql files 23 | - pre-load schema and document defintitions 24 | - Support [`graphql-config`](https://graphql-config.com/) files with one project and multiple projects 25 | - the language service re-starts on changes to vscode settings and/or graphql config! 26 | 27 | ### `.graphql`, `.gql` file extension support 28 | 29 | - syntax highlighting (type, query, mutation, interface, union, enum, scalar, fragments, directives) 30 | - autocomplete suggestions 31 | - validation against schema 32 | - snippets (interface, type, input, enum, union) 33 | - hover support 34 | - go to definition support (input, enum, type) 35 | - outline support 36 | 37 | ### `gql`/`graphql` tagged template literal support for tsx, jsx, ts, js 38 | 39 | - syntax highlighting (type, query, mutation, interface, union, enum, scalar, fragments, directives) 40 | - autocomplete suggestions 41 | - validation against schema 42 | - snippets 43 | - hover support 44 | - go to definition for fragments and input types 45 | - outline support 46 | 47 | ## Usage 48 | 49 | Install the [VSCode GraphQL Extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql). 50 | 51 | (Watchman is no longer required, you can uninstall it now) 52 | 53 | **This extension requires a graphql-config file**. 54 | 55 | As of `vscode-graphql@0.3.0` we support `graphql-config@3`. You can read more about that [here](https://www.graphql-config.com/docs/user/user-usage). Because it now uses `cosmicconfig` there are plenty of new options for loading config files: 56 | 57 | ``` 58 | graphql.config.json 59 | graphql.config.js 60 | graphql.config.yaml 61 | graphql.config.yml 62 | .graphqlrc (YAML or JSON) 63 | .graphqlrc.json 64 | .graphqlrc.yaml 65 | .graphqlrc.yml 66 | .graphqlrc.js 67 | graphql property in package.json 68 | ``` 69 | 70 | the file needs to be placed at the project root by default, but you can configure paths per project. see the FAQ below for details. 71 | 72 | Previous versions of this extension support `graphql-config@2` format, which follows [legacy configuration patterns](https://github.com/kamilkisiela/graphql-config/tree/legacy#usage) 73 | 74 | If you need legacy support for `.graphqlconfig` files or older graphql-config formats, see [this FAQ answer](#legacy). If you are missing legacy `graphql-config` features, please consult [the `graphql-config` repository](https://github.com/kamilkisiela/graphql-config). 75 | 76 | To support language features like "go-to definition" across multiple files, please include `documents` key in the `graphql-config` file default or per-project (this was `include` in 2.0). 77 | 78 | ## Configuration Examples 79 | 80 | ### Simple Example 81 | 82 | ```yaml 83 | # .graphqlrc.yml 84 | schema: "schema.graphql" 85 | documents: "src/**/*.{graphql,js,ts,jsx,tsx}" 86 | ``` 87 | 88 | ### Advanced Example 89 | 90 | ```js 91 | // graphql.config.js 92 | module.exports = { 93 | projects: { 94 | app: { 95 | schema: ["src/schema.graphql", "directives.graphql"], 96 | documents: ["**/*.{graphql,js,ts,jsx,tsx}", "my/fragments.graphql"], 97 | extensions: { 98 | endpoints: { 99 | default: { 100 | url: "http://localhost:8000", 101 | headers: { Authorization: `Bearer ${process.env.API_TOKEN}` }, 102 | }, 103 | }, 104 | }, 105 | }, 106 | db: { 107 | schema: "src/generated/db.graphql", 108 | documents: ["src/db/**/*.graphql", "my/fragments.graphql"], 109 | extensions: { 110 | codegen: [ 111 | { 112 | generator: "graphql-binding", 113 | language: "typescript", 114 | output: { 115 | binding: "src/generated/db.ts", 116 | }, 117 | }, 118 | ], 119 | endpoints: { 120 | default: { 121 | url: "http://localhost:8080", 122 | headers: { Authorization: `Bearer ${process.env.API_TOKEN}` }, 123 | }, 124 | }, 125 | }, 126 | }, 127 | }, 128 | } 129 | ``` 130 | 131 | Notice that `documents` key supports glob pattern and hence `["**/*.graphql"]` is also valid. 132 | 133 | ## Frequently Asked Questions 134 | 135 | 136 | 137 | ### I can't load `.graphqlconfig` files anymore 138 | 139 | If you need to use a legacy config file, then you just need to enable legacy mode for `graphql-config`: 140 | 141 | ```json 142 | "graphql-config.load.legacy": true 143 | ``` 144 | 145 | ### Go to definition is not working for my URL 146 | 147 | You can try the new experimental `cacheSchemaFileForLookup` option. NOTE: this will disable all definition lookup for local SDL graphql schema files, and _only_ perform lookup of the result an SDL result of `graphql-config` `getSchema()` 148 | 149 | To enable, add this to your settings: 150 | 151 | ```json 152 | "vscode-graphql.cacheSchemaFileForLookup": true, 153 | ``` 154 | 155 | you can also use graphql config if you need to mix and match these settings: 156 | 157 | ```yml 158 | schema: http://myschema.com/graphql 159 | extensions: 160 | languageService: 161 | cacheSchemaFileForLookup: true 162 | projects: 163 | project1: 164 | schema: project1/schema/schema.graphql 165 | documents: project1/queries/**/*.{graphql,tsx,jsx,ts,js} 166 | extensions: 167 | languageService: 168 | cacheSchemaFileForLookup: false 169 | 170 | project2: 171 | schema: https://api.spacex.land/graphql/ 172 | documents: project2/queries.graphql 173 | extensions: 174 | endpoints: 175 | default: 176 | url: https://api.spacex.land/graphql/ 177 | languageService: 178 | # Do project configs inherit parent config? 179 | cacheSchemaFileForLookup: true 180 | ``` 181 | 182 | ### The extension fails with errors about duplicate types 183 | 184 | Make sure that you aren't including schema files in the `documents` blob 185 | 186 | ### The extension fails with errors about missing scalars, directives, etc 187 | 188 | Make sure that your `schema` pointers refer to a complete schema! 189 | 190 | ### In JSX and TSX files I see completion items I don't need 191 | 192 | The way vscode lets you filter these out is [on the user end](https://github.com/microsoft/vscode/issues/45039) 193 | 194 | So you'll need to add something like this to your global vscode settings: 195 | 196 | ```json 197 | "[typescriptreact]": { 198 | "editor.suggest.filteredTypes": { 199 | "snippet": false 200 | } 201 | }, 202 | "[javascriptreact]": { 203 | "editor.suggest.filteredTypes": { 204 | "snippet": false 205 | } 206 | } 207 | ``` 208 | 209 | ### "Execute Query/Mutation/Subscription" always fails 210 | 211 | The best way to make "execute " codelens work is to add endpoints config to the global graphql config or the project config. 212 | 213 | This would look like: 214 | 215 | ```ts 216 | export default { 217 | schema: "mschema.graphql", 218 | extension: { 219 | endpoints: { 220 | default: "http://localhost:9000", 221 | }, 222 | }, 223 | } 224 | ``` 225 | 226 | (see above for per-project examples) 227 | 228 | If there is an issue with execution that has to do with your server, the error response should show now in the result panel. 229 | 230 | In case the request fails due to self signed certificate, you can bypass that check by adding this to your settings: 231 | 232 | ```json 233 | "vscode-graphql.rejectUnauthorized": false 234 | ``` 235 | 236 | ### My graphql config file is not at the root 237 | 238 | Good news, we have configs for this now! 239 | 240 | You can search a folder for any of the matching config file names listed above: 241 | 242 | ```json 243 | "graphql-config.load.rootDir": "./config" 244 | ``` 245 | 246 | Or a specific filepath: 247 | 248 | ```json 249 | "graphql-config.load.filePath": "./config/my-graphql-config.js" 250 | ``` 251 | 252 | Or a different `configName` that allows different formats: 253 | 254 | ```json 255 | "graphql-config.load.rootDir": "./config", 256 | "graphql-config.load.configName": "acme" 257 | ``` 258 | 259 | which would search for `./config/.acmerc`, `.config/.acmerc.js`, `.config/acme.config.json`, etc matching the config paths above 260 | 261 | If you have multiple projects, you need to define one top-level config that defines all project configs using `projects` 262 | 263 | ### How do I highlight an embedded graphql string? 264 | 265 | If you aren't using a template tag function such as `gql` or `graphql`, and just want to use a plain string, you can use an inline `#graphql` comment: 266 | 267 | ```ts 268 | const myQuery = `#graphql 269 | query { 270 | something 271 | } 272 | ` 273 | ``` 274 | 275 | or 276 | 277 | 278 | ```ts 279 | const myQuery = 280 | /* GraphiQL */ 281 | ` 282 | query { 283 | something 284 | } 285 | ` 286 | ``` 287 | 288 | ### Template literal expressions dont work with `Execute Query` 289 | 290 | Experimental support for template literal expressions ala `${}` has been added for language support, which just add an empty newline behind the scenes. It does not yet work for `Execute Query` codelans. 291 | 292 | 293 | 294 | 295 | ## Known Issues 296 | 297 | - the output channel occasionally shows "definition not found" when you first start the language service, but once the definition cache is built for each project, definition lookup will work. so if a "peek definition" fails when you first start the editor or when you first install the extension, just try the definition lookup again. 298 | 299 | ## Development 300 | 301 | This plugin uses the [GraphQL language server](https://github.com/graphql/graphql-language-service-server) 302 | 303 | 1. Clone the repository - https://github.com/graphql/vscode-graphql 304 | 1. `npm install` 305 | 1. Open it in VSCode 306 | 1. Go to the debugging section and run the launch program "Extension" 307 | 1. This will open another VSCode instance with extension enabled 308 | 1. Open a project with a graphql config file - ":electric_plug: graphql" in VSCode status bar indicates that the extension is in use 309 | 1. Logs for GraphQL language service will appear in output section under GraphQL Language Service 310 | ![GraphQL Language Service Logging](https://s3-ap-southeast-1.amazonaws.com/divyendusingh/vscode-graphql/Screen+Shot+2018-06-25+at+12.31.57+PM.png) 311 | 312 | ### Contributing back to this project 313 | 314 | This repository is managed by EasyCLA. Project participants must sign the free ([GraphQL Specification Membership agreement](https://preview-spec-membership.graphql.org) before making a contribution. You only need to do this one time, and it can be signed by [individual contributors](http://individual-spec-membership.graphql.org/) or their [employers](http://corporate-spec-membership.graphql.org/). 315 | 316 | To initiate the signature process please open a PR against this repo. The EasyCLA bot will block the merge if we still need a membership agreement from you. 317 | 318 | You can find [detailed information here](https://github.com/graphql/graphql-wg/tree/main/membership). If you have issues, please email [operations@graphql.org](mailto:operations@graphql.org). 319 | 320 | If your company benefits from GraphQL and you would like to provide essential financial support for the systems and people that power our community, please also consider membership in the [GraphQL Foundation](https://foundation.graphql.org/join). 321 | 322 | ## License 323 | 324 | MIT 325 | -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql/vscode-graphql/66e9d69b76c237714db0e606fa7ac32fc4fa3a94/assets/images/logo.png -------------------------------------------------------------------------------- /grammars/graphql.js.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [ 3 | "js", 4 | "jsx", 5 | "mjs", 6 | "cjs", 7 | "es6", 8 | "es", 9 | "esm", 10 | "ts", 11 | "tsx", 12 | "vue", 13 | "svelte" 14 | ], 15 | "injectionSelector": "L:source -string -comment", 16 | "patterns": [ 17 | { 18 | "contentName": "meta.embedded.block.graphql", 19 | "begin": "\\s*+(?:(?:(Relay)\\??\\.)(QL)|(gql|graphql|graphql\\.experimental)|(/\\* GraphQL \\*/))\\s*(`)", 20 | "beginCaptures": { 21 | "1": { 22 | "name": "variable.other.class.js" 23 | }, 24 | "2": { 25 | "name": "entity.name.function.tagged-template.js" 26 | }, 27 | "3": { 28 | "name": "entity.name.function.tagged-template.js" 29 | }, 30 | "4": { 31 | "name": "comment.graphql.js" 32 | }, 33 | "5": { 34 | "name": "punctuation.definition.string.template.begin.js" 35 | } 36 | }, 37 | "end": "`", 38 | "endCaptures": { 39 | "0": { 40 | "name": "punctuation.definition.string.template.end.js" 41 | } 42 | }, 43 | "patterns": [{ "include": "source.graphql" }] 44 | }, 45 | { 46 | "name": "taggedTemplates", 47 | "contentName": "meta.embedded.block.graphql", 48 | "begin": "(`)(#graphql)", 49 | "beginCaptures": { 50 | "1": { 51 | "name": "punctuation.definition.string.template.begin.js" 52 | }, 53 | "2": { 54 | "name": "comment.line.graphql.js" 55 | } 56 | }, 57 | "end": "`", 58 | "endCaptures": { 59 | "0": { 60 | "name": "punctuation.definition.string.template.end.js" 61 | } 62 | }, 63 | "patterns": [{ "include": "source.graphql" }] 64 | } 65 | ], 66 | "scopeName": "inline.graphql" 67 | } 68 | -------------------------------------------------------------------------------- /grammars/graphql.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GraphQL", 3 | "scopeName": "source.graphql", 4 | "fileTypes": ["graphql", "graphqls", "gql", "graphcool"], 5 | "patterns": [{ "include": "#graphql" }], 6 | "repository": { 7 | "graphql": { 8 | "patterns": [ 9 | { "include": "#graphql-comment" }, 10 | { "include": "#graphql-description-docstring" }, 11 | { "include": "#graphql-description-singleline" }, 12 | { "include": "#graphql-fragment-definition" }, 13 | { "include": "#graphql-directive-definition" }, 14 | { "include": "#graphql-type-interface" }, 15 | { "include": "#graphql-enum" }, 16 | { "include": "#graphql-scalar" }, 17 | { "include": "#graphql-union" }, 18 | { "include": "#graphql-schema" }, 19 | { "include": "#graphql-operation-def" }, 20 | { "include": "#literal-quasi-embedded" } 21 | ] 22 | }, 23 | "graphql-operation-def": { 24 | "patterns": [ 25 | { "include": "#graphql-query-mutation" }, 26 | { "include": "#graphql-name" }, 27 | { "include": "#graphql-variable-definitions" }, 28 | { "include": "#graphql-directive" }, 29 | { "include": "#graphql-selection-set" } 30 | ] 31 | }, 32 | "graphql-fragment-definition": { 33 | "name": "meta.fragment.graphql", 34 | "begin": "\\s*(?:(\\bfragment\\b)\\s*([_A-Za-z][_0-9A-Za-z]*)?\\s*(?:(\\bon\\b)\\s*([_A-Za-z][_0-9A-Za-z]*)))", 35 | "end": "(?<=})", 36 | "captures": { 37 | "1": { "name": "keyword.fragment.graphql" }, 38 | "2": { "name": "entity.name.fragment.graphql" }, 39 | "3": { "name": "keyword.on.graphql" }, 40 | "4": { "name": "support.type.graphql" } 41 | }, 42 | "patterns": [ 43 | { "include": "#graphql-comment" }, 44 | { "include": "#graphql-description-docstring" }, 45 | { "include": "#graphql-description-singleline" }, 46 | { "include": "#graphql-selection-set" }, 47 | { "include": "#graphql-directive" }, 48 | { "include": "#graphql-skip-newlines" }, 49 | { "include": "#literal-quasi-embedded" } 50 | ] 51 | }, 52 | "graphql-query-mutation": { 53 | "match": "\\s*\\b(query|mutation)\\b", 54 | "captures": { 55 | "1": { "name": "keyword.operation.graphql" } 56 | } 57 | }, 58 | "graphql-type-interface": { 59 | "name": "meta.type.interface.graphql", 60 | "begin": "\\s*\\b(?:(extends?)?\\b\\s*\\b(type)|(interface)|(input))\\b\\s*([_A-Za-z][_0-9A-Za-z]*)?", 61 | "end": "(?=.)", 62 | "applyEndPatternLast": 1, 63 | "captures": { 64 | "1": { "name": "keyword.type.graphql" }, 65 | "2": { "name": "keyword.type.graphql" }, 66 | "3": { "name": "keyword.interface.graphql" }, 67 | "4": { "name": "keyword.input.graphql" }, 68 | "5": { "name": "support.type.graphql" } 69 | }, 70 | "patterns": [ 71 | { 72 | "begin": "\\s*\\b(implements)\\b\\s*", 73 | "end": "\\s*(?={)", 74 | "beginCaptures": { 75 | "1": { "name": "keyword.implements.graphql" } 76 | }, 77 | "patterns": [ 78 | { 79 | "match": "\\s*([_A-Za-z][_0-9A-Za-z]*)", 80 | "captures": { 81 | "1": { "name": "support.type.graphql" } 82 | } 83 | }, 84 | { "include": "#graphql-comment" }, 85 | { "include": "#graphql-description-docstring" }, 86 | { "include": "#graphql-description-singleline" }, 87 | { "include": "#graphql-directive" }, 88 | { "include": "#graphql-ampersand" }, 89 | { "include": "#graphql-comma" } 90 | ] 91 | }, 92 | { "include": "#graphql-comment" }, 93 | { "include": "#graphql-description-docstring" }, 94 | { "include": "#graphql-description-singleline" }, 95 | { "include": "#graphql-directive" }, 96 | { "include": "#graphql-type-object" }, 97 | { "include": "#literal-quasi-embedded" }, 98 | { "include": "#graphql-ignore-spaces" } 99 | ] 100 | }, 101 | "graphql-ignore-spaces": { 102 | "match": "\\s*" 103 | }, 104 | "graphql-type-object": { 105 | "name": "meta.type.object.graphql", 106 | "begin": "\\s*({)", 107 | "end": "\\s*(})", 108 | "beginCaptures": { 109 | "1": { "name": "punctuation.operation.graphql" } 110 | }, 111 | "endCaptures": { 112 | "1": { "name": "punctuation.operation.graphql" } 113 | }, 114 | "patterns": [ 115 | { "include": "#graphql-comment" }, 116 | { "include": "#graphql-description-docstring" }, 117 | { "include": "#graphql-description-singleline" }, 118 | { "include": "#graphql-object-type" }, 119 | { "include": "#graphql-type-definition" }, 120 | { "include": "#literal-quasi-embedded" } 121 | ] 122 | }, 123 | "graphql-type-definition": { 124 | "comment": "key (optionalArgs): Type", 125 | "begin": "\\s*([_A-Za-z][_0-9A-Za-z]*)(?=\\s*\\(|:)", 126 | "end": "(?=\\s*(([_A-Za-z][_0-9A-Za-z]*)\\s*(\\(|:)|(})))|\\s*(,)", 127 | "beginCaptures": { 128 | "1": { "name": "variable.graphql" } 129 | }, 130 | "endCaptures": { 131 | "5": { "name": "punctuation.comma.graphql" } 132 | }, 133 | "patterns": [ 134 | { "include": "#graphql-comment" }, 135 | { "include": "#graphql-description-docstring" }, 136 | { "include": "#graphql-description-singleline" }, 137 | { "include": "#graphql-directive" }, 138 | { "include": "#graphql-variable-definitions" }, 139 | { "include": "#graphql-type-object" }, 140 | { "include": "#graphql-colon" }, 141 | { "include": "#graphql-input-types" }, 142 | { "include": "#literal-quasi-embedded" } 143 | ] 144 | }, 145 | "graphql-schema": { 146 | "begin": "\\s*\\b(schema)\\b", 147 | "end": "(?<=})", 148 | "beginCaptures": { 149 | "1": { "name": "keyword.schema.graphql" } 150 | }, 151 | "patterns": [ 152 | { 153 | "begin": "\\s*({)", 154 | "end": "\\s*(})", 155 | "beginCaptures": { 156 | "1": { "name": "punctuation.operation.graphql" } 157 | }, 158 | "endCaptures": { 159 | "1": { "name": "punctuation.operation.graphql" } 160 | }, 161 | "patterns": [ 162 | { 163 | "begin": "\\s*([_A-Za-z][_0-9A-Za-z]*)(?=\\s*\\(|:)", 164 | "end": "(?=\\s*(([_A-Za-z][_0-9A-Za-z]*)\\s*(\\(|:)|(})))|\\s*(,)", 165 | "beginCaptures": { 166 | "1": { "name": "variable.arguments.graphql" } 167 | }, 168 | "endCaptures": { 169 | "5": { "name": "punctuation.comma.graphql" } 170 | }, 171 | "patterns": [ 172 | { 173 | "match": "\\s*([_A-Za-z][_0-9A-Za-z]*)", 174 | "captures": { 175 | "1": { "name": "support.type.graphql" } 176 | } 177 | }, 178 | { "include": "#graphql-comment" }, 179 | { "include": "#graphql-description-docstring" }, 180 | { "include": "#graphql-description-singleline" }, 181 | { "include": "#graphql-colon" }, 182 | { "include": "#graphql-skip-newlines" } 183 | ] 184 | }, 185 | { "include": "#graphql-comment" }, 186 | { "include": "#graphql-description-docstring" }, 187 | { "include": "#graphql-description-singleline" }, 188 | { "include": "#graphql-skip-newlines" } 189 | ] 190 | }, 191 | { "include": "#graphql-comment" }, 192 | { "include": "#graphql-description-docstring" }, 193 | { "include": "#graphql-description-singleline" }, 194 | { "include": "#graphql-directive" }, 195 | { "include": "#graphql-skip-newlines" } 196 | ] 197 | }, 198 | "graphql-comment": { 199 | "patterns": [ 200 | { 201 | "comment": "need to prefix comment space with a scope else Atom's reflow cmd doesn't work", 202 | "name": "comment.line.graphql.js", 203 | "match": "(\\s*)(#).*", 204 | "captures": { 205 | "1": { 206 | "name": "punctuation.whitespace.comment.leading.graphql" 207 | } 208 | } 209 | }, 210 | { 211 | "name": "comment.line.graphql.js", 212 | "begin": "(\"\"\")", 213 | "end": "(\"\"\")", 214 | "beginCaptures": { 215 | "1": { 216 | "name": "punctuation.whitespace.comment.leading.graphql" 217 | } 218 | } 219 | }, 220 | { 221 | "name": "comment.line.graphql.js", 222 | "begin": "(\")", 223 | "end": "(\")", 224 | "beginCaptures": { 225 | "1": { 226 | "name": "punctuation.whitespace.comment.leading.graphql" 227 | } 228 | } 229 | } 230 | ] 231 | }, 232 | "graphql-description-singleline": { 233 | "name": "comment.line.number-sign.graphql", 234 | "match": "#(?=([^\"]*\"[^\"]*\")*[^\"]*$).*$" 235 | }, 236 | "graphql-description-docstring": { 237 | "name": "comment.block.graphql", 238 | "begin": "\"\"\"", 239 | "end": "\"\"\"" 240 | }, 241 | "graphql-variable-definitions": { 242 | "begin": "\\s*(\\()", 243 | "end": "\\s*(\\))", 244 | "captures": { 245 | "1": { "name": "meta.brace.round.graphql" } 246 | }, 247 | "patterns": [ 248 | { "include": "#graphql-comment" }, 249 | { "include": "#graphql-description-docstring" }, 250 | { "include": "#graphql-description-singleline" }, 251 | { "include": "#graphql-variable-definition" }, 252 | { "include": "#literal-quasi-embedded" } 253 | ] 254 | }, 255 | "graphql-variable-definition": { 256 | "comment": "variable: type = value,.... which may be a list", 257 | "name": "meta.variables.graphql", 258 | "begin": "\\s*(\\$?[_A-Za-z][_0-9A-Za-z]*)(?=\\s*\\(|:)", 259 | "end": "(?=\\s*((\\$?[_A-Za-z][_0-9A-Za-z]*)\\s*(\\(|:)|(}|\\))))|\\s*(,)", 260 | "beginCaptures": { 261 | "1": { "name": "variable.parameter.graphql" } 262 | }, 263 | "endCaptures": { 264 | "5": { "name": "punctuation.comma.graphql" } 265 | }, 266 | "patterns": [ 267 | { "include": "#graphql-comment" }, 268 | { "include": "#graphql-description-docstring" }, 269 | { "include": "#graphql-description-singleline" }, 270 | { "include": "#graphql-directive" }, 271 | { "include": "#graphql-colon" }, 272 | { "include": "#graphql-input-types" }, 273 | { "include": "#graphql-variable-assignment" }, 274 | { "include": "#literal-quasi-embedded" }, 275 | { "include": "#graphql-skip-newlines" } 276 | ] 277 | }, 278 | "graphql-input-types": { 279 | "patterns": [ 280 | { "include": "#graphql-scalar-type" }, 281 | { 282 | "match": "\\s*([_A-Za-z][_0-9A-Za-z]*)(?:\\s*(!))?", 283 | "captures": { 284 | "1": { "name": "support.type.graphql" }, 285 | "2": { "name": "keyword.operator.nulltype.graphql" } 286 | } 287 | }, 288 | { 289 | "name": "meta.type.list.graphql", 290 | "begin": "\\s*(\\[)", 291 | "end": "\\s*(\\])(?:\\s*(!))?", 292 | "captures": { 293 | "1": { "name": "meta.brace.square.graphql" }, 294 | "2": { "name": "keyword.operator.nulltype.graphql" } 295 | }, 296 | "patterns": [ 297 | { "include": "#graphql-comment" }, 298 | { "include": "#graphql-description-docstring" }, 299 | { "include": "#graphql-description-singleline" }, 300 | { "include": "#graphql-input-types" }, 301 | { "include": "#graphql-comma" }, 302 | { "include": "#literal-quasi-embedded" } 303 | ] 304 | } 305 | ] 306 | }, 307 | "graphql-scalar": { 308 | "match": "\\s*\\b(scalar)\\b\\s*([_A-Za-z][_0-9A-Za-z]*)", 309 | "captures": { 310 | "1": { "name": "keyword.scalar.graphql" }, 311 | "2": { "name": "entity.scalar.graphql" } 312 | } 313 | }, 314 | "graphql-scalar-type": { 315 | "match": "\\s*\\b(Int|Float|String|Boolean|ID)\\b(?:\\s*(!))?", 316 | "captures": { 317 | "1": { "name": "support.type.builtin.graphql" }, 318 | "2": { "name": "keyword.operator.nulltype.graphql" } 319 | } 320 | }, 321 | "graphql-variable-assignment": { 322 | "begin": "\\s(=)", 323 | "end": "(?=[\n,)])", 324 | "applyEndPatternLast": 1, 325 | "beginCaptures": { 326 | "1": { "name": "punctuation.assignment.graphql" } 327 | }, 328 | "patterns": [{ "include": "#graphql-value" }] 329 | }, 330 | "graphql-comma": { 331 | "match": "\\s*(,)", 332 | "captures": { 333 | "1": { "name": "punctuation.comma.graphql" } 334 | } 335 | }, 336 | "graphql-ampersand": { 337 | "match": "\\s*(&)", 338 | "captures": { 339 | "1": { "name": "keyword.operator.logical.graphql" } 340 | } 341 | }, 342 | "graphql-colon": { 343 | "match": "\\s*(:)", 344 | "captures": { 345 | "1": { "name": "punctuation.colon.graphql" } 346 | } 347 | }, 348 | "graphql-union-mark": { 349 | "match": "\\s*(\\|)", 350 | "captures": { 351 | "1": { "name": "punctuation.union.graphql" } 352 | } 353 | }, 354 | "graphql-name": { 355 | "match": "\\s*([_A-Za-z][_0-9A-Za-z]*)", 356 | "captures": { 357 | "1": { "name": "entity.name.function.graphql" } 358 | } 359 | }, 360 | "graphql-directive": { 361 | "begin": "\\s*((@)\\s*([_A-Za-z][_0-9A-Za-z]*))", 362 | "end": "(?=.)", 363 | "applyEndPatternLast": 1, 364 | "beginCaptures": { 365 | "1": { "name": "entity.name.function.directive.graphql" } 366 | }, 367 | "patterns": [ 368 | { "include": "#graphql-comment" }, 369 | { "include": "#graphql-description-docstring" }, 370 | { "include": "#graphql-description-singleline" }, 371 | { "include": "#graphql-arguments" }, 372 | { "include": "#literal-quasi-embedded" }, 373 | { "include": "#graphql-skip-newlines" } 374 | ] 375 | }, 376 | "graphql-directive-definition": { 377 | "begin": "\\s*(\\bdirective\\b)\\s*(@[_A-Za-z][_0-9A-Za-z]*)", 378 | "end": "(?=.)", 379 | "applyEndPatternLast": 1, 380 | "beginCaptures": { 381 | "1": { 382 | "name": "keyword.directive.graphql" 383 | }, 384 | "2": { 385 | "name": "entity.name.function.directive.graphql" 386 | }, 387 | "3": { "name": "keyword.on.graphql" }, 388 | "4": { "name": "support.type.graphql" } 389 | }, 390 | "patterns": [ 391 | { 392 | "include": "#graphql-variable-definitions" 393 | }, 394 | { 395 | "begin": "\\s*(\\bon\\b)\\s*([_A-Za-z]*)", 396 | "end": "(?=.)", 397 | "applyEndPatternLast": 1, 398 | "beginCaptures": { 399 | "1": { 400 | "name": "keyword.on.graphql" 401 | }, 402 | "2": { 403 | "name": "support.type.location.graphql" 404 | } 405 | }, 406 | "patterns": [ 407 | { 408 | "include": "#graphql-skip-newlines" 409 | }, 410 | { 411 | "include": "#graphql-comment" 412 | }, 413 | { 414 | "include": "#literal-quasi-embedded" 415 | }, 416 | { 417 | "match": "\\s*(\\|)\\s*([_A-Za-z]*)", 418 | "captures": { 419 | "2": { 420 | "name": "support.type.location.graphql" 421 | } 422 | } 423 | } 424 | ] 425 | }, 426 | { 427 | "include": "#graphql-skip-newlines" 428 | }, 429 | { 430 | "include": "#graphql-comment" 431 | }, 432 | { 433 | "include": "#literal-quasi-embedded" 434 | } 435 | ] 436 | }, 437 | "graphql-selection-set": { 438 | "name": "meta.selectionset.graphql", 439 | "begin": "\\s*({)", 440 | "end": "\\s*(})", 441 | "beginCaptures": { 442 | "1": { "name": "punctuation.operation.graphql" } 443 | }, 444 | "endCaptures": { 445 | "1": { "name": "punctuation.operation.graphql" } 446 | }, 447 | "patterns": [ 448 | { "include": "#graphql-comment" }, 449 | { "include": "#graphql-description-docstring" }, 450 | { "include": "#graphql-description-singleline" }, 451 | { "include": "#graphql-field" }, 452 | { "include": "#graphql-fragment-spread" }, 453 | { "include": "#graphql-inline-fragment" }, 454 | { "include": "#graphql-comma" }, 455 | { "include": "#native-interpolation" }, 456 | { "include": "#literal-quasi-embedded" } 457 | ] 458 | }, 459 | "graphql-field": { 460 | "patterns": [ 461 | { 462 | "match": "\\s*([_A-Za-z][_0-9A-Za-z]*)\\s*(:)", 463 | "captures": { 464 | "1": { "name": "string.unquoted.alias.graphql" }, 465 | "2": { "name": "punctuation.colon.graphql" } 466 | } 467 | }, 468 | { 469 | "match": "\\s*([_A-Za-z][_0-9A-Za-z]*)", 470 | "captures": { 471 | "1": { "name": "variable.graphql" } 472 | } 473 | }, 474 | { "include": "#graphql-arguments" }, 475 | { "include": "#graphql-directive" }, 476 | { "include": "#graphql-selection-set" }, 477 | { "include": "#literal-quasi-embedded" }, 478 | { "include": "#graphql-skip-newlines" } 479 | ] 480 | }, 481 | "graphql-fragment-spread": { 482 | "begin": "\\s*(\\.\\.\\.)\\s*(?!\\bon\\b)([_A-Za-z][_0-9A-Za-z]*)", 483 | "end": "(?=.)", 484 | "applyEndPatternLast": 1, 485 | "captures": { 486 | "1": { "name": "keyword.operator.spread.graphql" }, 487 | "2": { "name": "variable.fragment.graphql" } 488 | }, 489 | "patterns": [ 490 | { "include": "#graphql-comment" }, 491 | { "include": "#graphql-description-docstring" }, 492 | { "include": "#graphql-description-singleline" }, 493 | { "include": "#graphql-selection-set" }, 494 | { "include": "#graphql-directive" }, 495 | { "include": "#literal-quasi-embedded" }, 496 | { "include": "#graphql-skip-newlines" } 497 | ] 498 | }, 499 | "graphql-inline-fragment": { 500 | "begin": "\\s*(\\.\\.\\.)\\s*(?:(\\bon\\b)\\s*([_A-Za-z][_0-9A-Za-z]*))?", 501 | "end": "(?=.)", 502 | "applyEndPatternLast": 1, 503 | "captures": { 504 | "1": { "name": "keyword.operator.spread.graphql" }, 505 | "2": { "name": "keyword.on.graphql" }, 506 | "3": { "name": "support.type.graphql" } 507 | }, 508 | "patterns": [ 509 | { "include": "#graphql-comment" }, 510 | { "include": "#graphql-description-docstring" }, 511 | { "include": "#graphql-description-singleline" }, 512 | { "include": "#graphql-selection-set" }, 513 | { "include": "#graphql-directive" }, 514 | { "include": "#graphql-skip-newlines" }, 515 | { "include": "#literal-quasi-embedded" } 516 | ] 517 | }, 518 | "graphql-arguments": { 519 | "name": "meta.arguments.graphql", 520 | "begin": "\\s*(\\()", 521 | "end": "\\s*(\\))", 522 | "beginCaptures": { 523 | "1": { "name": "meta.brace.round.directive.graphql" } 524 | }, 525 | "endCaptures": { 526 | "1": { "name": "meta.brace.round.directive.graphql" } 527 | }, 528 | "patterns": [ 529 | { "include": "#graphql-comment" }, 530 | { "include": "#graphql-description-docstring" }, 531 | { "include": "#graphql-description-singleline" }, 532 | { 533 | "begin": "\\s*([_A-Za-z][_0-9A-Za-z]*)(?:\\s*(:))", 534 | "end": "(?=\\s*(?:(?:([_A-Za-z][_0-9A-Za-z]*)\\s*(:))|\\)))|\\s*(,)", 535 | "beginCaptures": { 536 | "1": { "name": "variable.parameter.graphql" }, 537 | "2": { "name": "punctuation.colon.graphql" } 538 | }, 539 | "endCaptures": { 540 | "3": { "name": "punctuation.comma.graphql" } 541 | }, 542 | "patterns": [ 543 | { "include": "#graphql-comment" }, 544 | { "include": "#graphql-description-docstring" }, 545 | { "include": "#graphql-description-singleline" }, 546 | { "include": "#graphql-directive" }, 547 | { "include": "#graphql-value" }, 548 | { "include": "#graphql-skip-newlines" } 549 | ] 550 | }, 551 | { "include": "#literal-quasi-embedded" } 552 | ] 553 | }, 554 | "graphql-variable-name": { 555 | "match": "\\s*(\\$[_A-Za-z][_0-9A-Za-z]*)", 556 | "captures": { 557 | "1": { "name": "variable.graphql" } 558 | } 559 | }, 560 | "graphql-float-value": { 561 | "match": "\\s*(-?(0|[1-9][0-9]*)(\\.[0-9]+)?((e|E)(\\+|-)?[0-9]+)?)", 562 | "captures": { 563 | "1": { "name": "constant.numeric.float.graphql" } 564 | } 565 | }, 566 | "graphql-boolean-value": { 567 | "match": "\\s*\\b(true|false)\\b", 568 | "captures": { 569 | "1": { "name": "constant.language.boolean.graphql" } 570 | } 571 | }, 572 | "graphql-null-value": { 573 | "match": "\\s*\\b(null)\\b", 574 | "captures": { 575 | "1": { "name": "constant.language.null.graphql" } 576 | } 577 | }, 578 | "graphql-string-value": { 579 | "contentName": "string.quoted.double.graphql", 580 | "begin": "\\s*+((\"))", 581 | "end": "\\s*+(?:((\"))|(\n))", 582 | "beginCaptures": { 583 | "1": { "name": "string.quoted.double.graphql" }, 584 | "2": { "name": "punctuation.definition.string.begin.graphql" } 585 | }, 586 | "endCaptures": { 587 | "1": { "name": "string.quoted.double.graphql" }, 588 | "2": { "name": "punctuation.definition.string.end.graphql" }, 589 | "3": { "name": "invalid.illegal.newline.graphql" } 590 | }, 591 | "patterns": [ 592 | { "include": "#graphql-string-content" }, 593 | { "include": "#literal-quasi-embedded" } 594 | ] 595 | }, 596 | "graphql-string-content": { 597 | "patterns": [ 598 | { 599 | "name": "constant.character.escape.graphql", 600 | "match": "\\\\[/'\"\\\\nrtbf]" 601 | }, 602 | { 603 | "name": "constant.character.escape.graphql", 604 | "match": "\\\\u([0-9a-fA-F]{4})" 605 | } 606 | ] 607 | }, 608 | "graphql-enum": { 609 | "name": "meta.enum.graphql", 610 | "begin": "\\s*+\\b(enum)\\b\\s*([_A-Za-z][_0-9A-Za-z]*)", 611 | "end": "(?<=})", 612 | "beginCaptures": { 613 | "1": { "name": "keyword.enum.graphql" }, 614 | "2": { "name": "support.type.enum.graphql" } 615 | }, 616 | "patterns": [ 617 | { 618 | "name": "meta.type.object.graphql", 619 | "begin": "\\s*({)", 620 | "end": "\\s*(})", 621 | "beginCaptures": { 622 | "1": { "name": "punctuation.operation.graphql" } 623 | }, 624 | "endCaptures": { 625 | "1": { "name": "punctuation.operation.graphql" } 626 | }, 627 | "patterns": [ 628 | { "include": "#graphql-object-type" }, 629 | 630 | { "include": "#graphql-comment" }, 631 | { "include": "#graphql-description-docstring" }, 632 | { "include": "#graphql-description-singleline" }, 633 | { "include": "#graphql-directive" }, 634 | { "include": "#graphql-enum-value" }, 635 | { "include": "#literal-quasi-embedded" } 636 | ] 637 | }, 638 | { "include": "#graphql-comment" }, 639 | { "include": "#graphql-description-docstring" }, 640 | { "include": "#graphql-description-singleline" }, 641 | { "include": "#graphql-directive" } 642 | ] 643 | }, 644 | "graphql-enum-value": { 645 | "name": "constant.character.enum.graphql", 646 | "match": "\\s*(?!=\\b(true|false|null)\\b)([_A-Za-z][_0-9A-Za-z]*)" 647 | }, 648 | "graphql-value": { 649 | "patterns": [ 650 | { "include": "#graphql-comment" }, 651 | { "include": "#graphql-description-docstring" }, 652 | { "include": "#graphql-variable-name" }, 653 | { "include": "#graphql-float-value" }, 654 | { "include": "#graphql-string-value" }, 655 | { "include": "#graphql-boolean-value" }, 656 | { "include": "#graphql-null-value" }, 657 | { "include": "#graphql-enum-value" }, 658 | { "include": "#graphql-list-value" }, 659 | { "include": "#graphql-object-value" }, 660 | { "include": "#literal-quasi-embedded" } 661 | ] 662 | }, 663 | "graphql-list-value": { 664 | "patterns": [ 665 | { 666 | "name": "meta.listvalues.graphql", 667 | "begin": "\\s*+(\\[)", 668 | "end": "\\s*(\\])", 669 | "endCaptures": { 670 | "1": { "name": "meta.brace.square.graphql" } 671 | }, 672 | "beginCaptures": { 673 | "1": { "name": "meta.brace.square.graphql" } 674 | }, 675 | "patterns": [{ "include": "#graphql-value" }] 676 | } 677 | ] 678 | }, 679 | "graphql-object-value": { 680 | "patterns": [ 681 | { 682 | "name": "meta.objectvalues.graphql", 683 | "begin": "\\s*+({)", 684 | "end": "\\s*(})", 685 | "beginCaptures": { 686 | "1": { "name": "meta.brace.curly.graphql" } 687 | }, 688 | "endCaptures": { 689 | "1": { "name": "meta.brace.curly.graphql" } 690 | }, 691 | "patterns": [ 692 | { "include": "#graphql-object-field" }, 693 | { "include": "#graphql-value" } 694 | ] 695 | } 696 | ] 697 | }, 698 | "graphql-object-field": { 699 | "match": "\\s*(([_A-Za-z][_0-9A-Za-z]*))\\s*(:)", 700 | "captures": { 701 | "1": { "name": "constant.object.key.graphql" }, 702 | "2": { "name": "string.unquoted.graphql" }, 703 | "3": { "name": "punctuation.graphql" } 704 | } 705 | }, 706 | "graphql-union": { 707 | "begin": "\\s*\\b(union)\\b\\s*([_A-Za-z][_0-9A-Za-z]*)", 708 | "end": "(?=.)", 709 | "applyEndPatternLast": 1, 710 | "captures": { 711 | "1": { "name": "keyword.union.graphql" }, 712 | "2": { "name": "support.type.graphql" } 713 | }, 714 | "patterns": [ 715 | { 716 | "begin": "\\s*(=)\\s*([_A-Za-z][_0-9A-Za-z]*)", 717 | "end": "(?=.)", 718 | "applyEndPatternLast": 1, 719 | "captures": { 720 | "1": { "name": "punctuation.assignment.graphql" }, 721 | "2": { "name": "support.type.graphql" } 722 | }, 723 | "patterns": [ 724 | { "include": "#graphql-comment" }, 725 | { "include": "#graphql-description-docstring" }, 726 | { "include": "#graphql-description-singleline" }, 727 | { "include": "#graphql-skip-newlines" }, 728 | { "include": "#literal-quasi-embedded" }, 729 | { 730 | "match": "\\s*(\\|)\\s*([_A-Za-z][_0-9A-Za-z]*)", 731 | "captures": { 732 | "1": { "name": "punctuation.or.graphql" }, 733 | "2": { "name": "support.type.graphql" } 734 | } 735 | } 736 | ] 737 | }, 738 | { "include": "#graphql-comment" }, 739 | { "include": "#graphql-description-docstring" }, 740 | { "include": "#graphql-description-singleline" }, 741 | { "include": "#graphql-skip-newlines" }, 742 | { "include": "#literal-quasi-embedded" } 743 | ] 744 | }, 745 | "native-interpolation": { 746 | "name": "native.interpolation", 747 | "begin": "\\s*(\\${)", 748 | "end": "(})", 749 | "beginCaptures": { 750 | "1": { "name": "keyword.other.substitution.begin" } 751 | }, 752 | "endCaptures": { 753 | "1": { "name": "keyword.other.substitution.end" } 754 | }, 755 | "patterns": [ 756 | { "include": "source.js" }, 757 | { "include": "source.ts" }, 758 | { "include": "source.js.jsx" }, 759 | { "include": "source.tsx" } 760 | ] 761 | }, 762 | "graphql-skip-newlines": { 763 | "match": "\\s*\n" 764 | } 765 | } 766 | } 767 | -------------------------------------------------------------------------------- /grammars/graphql.markdown.codeblock.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [], 3 | "scopeName": "markdown.graphql.codeblock", 4 | "injectionSelector": "L:markup.fenced_code.block.markdown", 5 | "patterns": [ 6 | { 7 | "contentName": "meta.embedded.block.graphql", 8 | "begin": "(gql|graphql|GraphQL)(\\s+[^`~]*)?$", 9 | "end": "(^|\\G)(?=\\s*[`~]{3,}\\s*$)", 10 | "patterns": [ 11 | { 12 | "begin": "(^|\\G)(\\s*)(.*)", 13 | "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", 14 | "patterns": [ 15 | { 16 | "include": "source.graphql" 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /grammars/graphql.re.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": ["re", "ml"], 3 | "injectionSelector": "L:source -string -comment", 4 | "patterns": [ 5 | { 6 | "contentName": "meta.embedded.block.graphql", 7 | "begin": "({)(gql)(\\|)", 8 | "end": "(\\|)(\\2)(})", 9 | "patterns": [ 10 | { 11 | "include": "source.graphql" 12 | } 13 | ] 14 | }, 15 | { 16 | "contentName": "meta.embedded.block.graphql", 17 | "begin": "(\\[%graphql)s*$", 18 | "end": "(?<=])", 19 | "patterns": [ 20 | { 21 | "begin": "^\\s*({\\|)$", 22 | "end": "^\\s*(\\|})", 23 | "patterns": [{ "include": "source.graphql" }] 24 | } 25 | ] 26 | }, 27 | { 28 | "contentName": "meta.embedded.block.graphql", 29 | "begin": "(\\[%graphql {\\|)", 30 | "end": "(\\|}( )?])", 31 | "patterns": [{ "include": "source.graphql" }] 32 | } 33 | ], 34 | "scopeName": "inline.graphql.re" 35 | } 36 | -------------------------------------------------------------------------------- /grammars/graphql.rescript.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": ["res", "resi"], 3 | "injectionSelector": "L:source -string -comment", 4 | "patterns": [ 5 | { 6 | "contentName": "meta.embedded.block.graphql", 7 | "begin": "(%graphql\\()\\s*$", 8 | "end": "(?<=\\))", 9 | "patterns": [ 10 | { 11 | "begin": "^\\s*(`)$", 12 | "end": "^\\s*(`)", 13 | "patterns": [{ "include": "source.graphql" }] 14 | } 15 | ] 16 | }, 17 | { 18 | "contentName": "meta.embedded.block.graphql", 19 | "begin": "(%graphql\\(`)", 20 | "end": "(\\`( )?\\))", 21 | "patterns": [{ "include": "source.graphql" }] 22 | } 23 | ], 24 | "scopeName": "inline.graphql.res" 25 | } 26 | -------------------------------------------------------------------------------- /language/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "#", 4 | "blockComment": ["\"\"\"", "\"\"\""] 5 | }, 6 | "brackets": [["{", "}"], ["[", "]"], ["(", ")"]], 7 | "autoClosingPairs": [ 8 | ["{", "}"], 9 | ["[", "]"], 10 | ["(", ")"], 11 | { "open": "\"", "close": "\"", "notIn": ["string", "comment"] }, 12 | ["'", "'"] 13 | ], 14 | "surroundingPairs": [ 15 | ["{", "}"], 16 | ["[", "]"], 17 | ["(", ")"], 18 | ["\"", "\""], 19 | ["'", "'"] 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-graphql", 3 | "version": "0.3.53", 4 | "preview": true, 5 | "private": true, 6 | "license": "MIT", 7 | "displayName": "GraphQL", 8 | "keywords": [ 9 | "multi-root ready", 10 | "graphql", 11 | "lsp", 12 | "graph" 13 | ], 14 | "description": "GraphQL extension for VSCode adds syntax highlighting, validation, and language features like go to definition, hover information and autocompletion for graphql projects. This extension also works with queries annotated with gql tags or comments.", 15 | "icon": "assets/images/logo.png", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/graphql/vscode-graphql" 19 | }, 20 | "homepage": "https://github.com/graphql/vscode-graphql/blob/master/README.md", 21 | "galleryBanner": { 22 | "color": "#032539", 23 | "theme": "dark" 24 | }, 25 | "publisher": "GraphQL", 26 | "engines": { 27 | "vscode": "^1.63.0" 28 | }, 29 | "categories": [ 30 | "Programming Languages", 31 | "Snippets", 32 | "Linters", 33 | "Other" 34 | ], 35 | "activationEvents": [ 36 | "onCommand:vscode-graphql.isDebugging", 37 | "onCommand:vscode-graphql.contentProvider", 38 | "onLanguage:graphql", 39 | "workspaceContains:**/.graphqlrc", 40 | "workspaceContains:**/.graphqlrc.{json,yaml,yml,js,ts,toml}", 41 | "workspaceContains:**/graphql.config.{json,yaml,yml,js,ts,toml}", 42 | "workspaceContains:**/package.json" 43 | ], 44 | "main": "./out/extension", 45 | "contributes": { 46 | "languages": [ 47 | { 48 | "id": "graphql", 49 | "extensions": [ 50 | ".gql", 51 | ".graphql", 52 | ".graphqls" 53 | ], 54 | "aliases": [ 55 | "GraphQL", 56 | "graphql" 57 | ], 58 | "configuration": "./language/language-configuration.json" 59 | } 60 | ], 61 | "grammars": [ 62 | { 63 | "language": "graphql", 64 | "path": "./grammars/graphql.json", 65 | "scopeName": "source.graphql" 66 | }, 67 | { 68 | "injectTo": [ 69 | "source.js", 70 | "source.ts", 71 | "source.js.jsx", 72 | "source.tsx", 73 | "source.vue", 74 | "source.svelte" 75 | ], 76 | "scopeName": "inline.graphql", 77 | "path": "./grammars/graphql.js.json", 78 | "embeddedLanguages": { 79 | "meta.embedded.block.graphql": "graphql" 80 | } 81 | }, 82 | { 83 | "injectTo": [ 84 | "source.reason", 85 | "source.ocaml" 86 | ], 87 | "scopeName": "inline.graphql.re", 88 | "path": "./grammars/graphql.re.json", 89 | "embeddedLanguages": { 90 | "meta.embedded.block.graphql": "graphql" 91 | } 92 | }, 93 | { 94 | "injectTo": [ 95 | "source.rescript" 96 | ], 97 | "scopeName": "inline.graphql.res", 98 | "path": "./grammars/graphql.rescript.json", 99 | "embeddedLanguages": { 100 | "meta.embedded.block.graphql": "graphql" 101 | } 102 | }, 103 | { 104 | "injectTo": [ 105 | "text.html.markdown" 106 | ], 107 | "scopeName": "markdown.graphql.codeblock", 108 | "path": "./grammars/graphql.markdown.codeblock.json", 109 | "embeddedLanguages": { 110 | "meta.embedded.block.graphql": "graphql" 111 | } 112 | } 113 | ], 114 | "snippets": [ 115 | { 116 | "language": "graphql", 117 | "path": "./snippets/graphql.json" 118 | } 119 | ], 120 | "configuration": { 121 | "title": "VSCode GraphQL", 122 | "properties": { 123 | "vscode-graphql.debug": { 124 | "type": [ 125 | "boolean", 126 | "null" 127 | ], 128 | "default": false, 129 | "description": "Enable debug logs" 130 | }, 131 | "vscode-graphql.trace.server": { 132 | "type": [ 133 | "string" 134 | ], 135 | "default": "off", 136 | "description": "Enable tracing for language server" 137 | }, 138 | "vscode-graphql.showExecCodelens": { 139 | "type": [ 140 | "boolean" 141 | ], 142 | "description": "Show codelens to execute operations inline", 143 | "default": true 144 | }, 145 | "vscode-graphql.cacheSchemaFileForLookup": { 146 | "type": [ 147 | "boolean" 148 | ], 149 | "description": "Use a cached file output of your graphql-config schema result for definition lookups, symbols, outline, etc. Disabled by default." 150 | }, 151 | "vscode-graphql.rejectUnauthorized": { 152 | "type": [ 153 | "boolean" 154 | ], 155 | "description": "Fail the request on invalid certificate", 156 | "default": true 157 | }, 158 | "graphql-config.load.rootDir": { 159 | "type": [ 160 | "string" 161 | ], 162 | "description": "Base dir for graphql config loadConfig()" 163 | }, 164 | "graphql-config.load.filePath": { 165 | "type": [ 166 | "string" 167 | ], 168 | "description": "filePath for graphql config loadConfig()", 169 | "default": null 170 | }, 171 | "graphql-config.load.legacy": { 172 | "type": [ 173 | "boolean" 174 | ], 175 | "description": "legacy mode for graphql config v2 config", 176 | "default": null 177 | }, 178 | "graphql-config.load.configName": { 179 | "type": [ 180 | "string" 181 | ], 182 | "description": "optional .config.js instead of default `graphql`", 183 | "default": null 184 | }, 185 | "graphql-config.dotEnvPath": { 186 | "type": [ 187 | "string" 188 | ], 189 | "description": "optional .env load path, if not the default", 190 | "default": null 191 | } 192 | } 193 | }, 194 | "commands": [ 195 | { 196 | "command": "vscode-graphql.isDebugging", 197 | "title": "VSCode GraphQL: Is Debugging?" 198 | }, 199 | { 200 | "command": "vscode-graphql.restart", 201 | "title": "VSCode GraphQL: Manual Restart" 202 | }, 203 | { 204 | "command": "vscode-graphql.showOutputChannel", 205 | "title": "VSCode GraphQL: Show output channel" 206 | }, 207 | { 208 | "command": "vscode-graphql.contentProvider", 209 | "title": "VSCode GraphQL: Execute GraphQL Operations" 210 | } 211 | ] 212 | }, 213 | "scripts": { 214 | "vscode:prepublish": "npm run compile -- --minify", 215 | "compile": "npm run compile:server && esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", 216 | "compile:server": "esbuild ./src/server/server.ts --bundle --outfile=out/server/server.js --external:vscode --format=cjs --platform=node", 217 | "build": "npm run compile -- --sourcemap", 218 | "watch": "npm run build --watch", 219 | "test": "npm run compile && node ./node_modules/vscode/bin/test", 220 | "vsce:package": "vsce package", 221 | "env:source": "export $(cat .envrc | xargs)", 222 | "vsce:publish": "vsce publish", 223 | "open-vsx:publish": "ovsx publish -p \"$OPEN_VSX_ACCESS_TOKEN\"", 224 | "publish": "npm run vsce:publish && npm run open-vsx:publish", 225 | "upgrade-interactive": "npx npm-check -u" 226 | }, 227 | "devDependencies": { 228 | "@types/capitalize": "2.0.0", 229 | "@types/dotenv": "8.2.0", 230 | "@types/mocha": "5.2.7", 231 | "@types/node": "16.11.26", 232 | "@types/node-fetch": "3.0.3", 233 | "@types/vscode": "1.62.0", 234 | "@types/ws": "8.2.2", 235 | "esbuild": "0.13.15", 236 | "ovsx": "0.3.0", 237 | "tslint": "5.20.1", 238 | "typescript": "4.4.4", 239 | "vsce": "2.6.7", 240 | "vscode": "1.1.37" 241 | }, 242 | "dependencies": { 243 | "@changesets/changelog-github": "0.4.3", 244 | "@changesets/cli": "2.21.1", 245 | "@graphql-tools/load": "7.5.2", 246 | "@graphql-tools/url-loader": "7.8.0", 247 | "@graphql-tools/wrap": "8.4.3", 248 | "@urql/core": "2.3.6", 249 | "babel-polyfill": "6.26.0", 250 | "capitalize": "2.0.4", 251 | "dotenv": "10.0.0", 252 | "escape-html": "1.0.3", 253 | "graphql": "15.8.0", 254 | "graphql-config": "4.1.0", 255 | "graphql-language-service-server": "2.7.15", 256 | "graphql-tag": "2.12.6", 257 | "graphql-ws": "5.5.5", 258 | "node-fetch": "2.6.7", 259 | "vscode-languageclient": "5.2.1", 260 | "ws": "8.2.3" 261 | }, 262 | "resolutions": { 263 | "graphql-config": "4.1.0" 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "docker": { 4 | "enabled": false 5 | }, 6 | "automerge": true, 7 | "major": { 8 | "automerge": false 9 | }, 10 | "minor": { 11 | "automerge": false 12 | }, 13 | "semanticCommits": true, 14 | "packageRules": [ 15 | { 16 | "matchPackageNames": [ 17 | "graphql-language-service-server", 18 | "graphql", 19 | "graphql-config", 20 | "graphql-ws", 21 | "@urql/core" 22 | ], 23 | "extends": ["schedule:daily"] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /snippets/graphql.json: -------------------------------------------------------------------------------- 1 | { 2 | ".source.graphql": { 3 | "Interface definition": { 4 | "prefix": "int", 5 | "body": ["interface ${1:IName} {", "\t$2", "}"] 6 | }, 7 | "Type definition": { 8 | "prefix": "typ", 9 | "body": ["type ${1:TypeName} ${2:implements ${3:IName} }{", "\t$4", "}"] 10 | }, 11 | "Input Field definition": { 12 | "prefix": "inp", 13 | "body": ["input ${1:TypeInput} {", "\t$2", "}"] 14 | }, 15 | "Enum definition": { 16 | "prefix": "enu", 17 | "body": ["enum ${1:EnumName} {", "\t$2", "}"] 18 | }, 19 | "Union definition": { 20 | "prefix": "uni", 21 | "body": ["union ${1:UnionName} = $2 | $3"] 22 | }, 23 | "Fragment definition": { 24 | "prefix": "fra", 25 | "body": ["fragment ${1:FragmentName} on ${2:TypeName} {", "\t$3", "}"] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/CustomInitializationFailedHandler.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InitializationFailedHandler, 3 | ResponseError, 4 | InitializeError, 5 | } from "vscode-languageclient" 6 | import { OutputChannel } from "vscode" 7 | 8 | export function CustomInitializationFailedHandler( 9 | outputChannel: OutputChannel, 10 | ): InitializationFailedHandler { 11 | return (error: ResponseError | Error | any) => { 12 | outputChannel.appendLine(`Caught the error ${error}`) 13 | error.stack && outputChannel.appendLine(error.stack) 14 | return false 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/client/graphql-codelens-provider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OutputChannel, 3 | CodeLensProvider, 4 | TextDocument, 5 | CancellationToken, 6 | CodeLens, 7 | Range, 8 | Position, 9 | ProviderResult, 10 | } from "vscode" 11 | 12 | import { SourceHelper, ExtractedTemplateLiteral } from "./source-helper" 13 | import capitalize from "capitalize" 14 | 15 | export class GraphQLCodeLensProvider implements CodeLensProvider { 16 | outputChannel: OutputChannel 17 | sourceHelper: SourceHelper 18 | 19 | constructor(outputChannel: OutputChannel) { 20 | this.outputChannel = outputChannel 21 | this.sourceHelper = new SourceHelper(this.outputChannel) 22 | } 23 | 24 | public provideCodeLenses( 25 | document: TextDocument, 26 | _token: CancellationToken, 27 | // for some reason, ProviderResult doesn't work here 28 | // anymore after upgrading types 29 | ): ProviderResult<[]> { 30 | const literals: ExtractedTemplateLiteral[] = this.sourceHelper.extractAllTemplateLiterals( 31 | document, 32 | ["gql", "graphql", "/\\* GraphQL \\*/"], 33 | ) 34 | const results = literals.map(literal => { 35 | return new CodeLens( 36 | new Range( 37 | new Position(literal.position.line, 0), 38 | new Position(literal.position.line, 0), 39 | ), 40 | { 41 | title: `Execute ${capitalize(literal.definition.operation)}`, 42 | command: "vscode-graphql.contentProvider", 43 | arguments: [literal], 44 | }, 45 | ) 46 | }) 47 | 48 | return results as ProviderResult<[]> 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/client/graphql-content-provider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | workspace, 3 | OutputChannel, 4 | TextDocumentContentProvider, 5 | EventEmitter, 6 | Uri, 7 | Event, 8 | ProviderResult, 9 | window, 10 | WebviewPanel, 11 | WorkspaceFolder, 12 | } from "vscode" 13 | import escapeHtml from "escape-html" 14 | import type { ExtractedTemplateLiteral } from "./source-helper" 15 | import { loadConfig, GraphQLProjectConfig } from "graphql-config" 16 | import { visit, VariableDefinitionNode } from "graphql" 17 | import { NetworkHelper } from "./network-helper" 18 | import { SourceHelper, GraphQLScalarTSType } from "./source-helper" 19 | import type { Endpoints, Endpoint } from "graphql-config/extensions/endpoints" 20 | 21 | export type UserVariables = { [key: string]: GraphQLScalarTSType } 22 | 23 | // TODO: remove residue of previewHtml API https://github.com/microsoft/vscode/issues/62630 24 | // We update the panel directly now in place of a event based update API (we might make a custom event updater and remove panel dep though) 25 | export class GraphQLContentProvider implements TextDocumentContentProvider { 26 | private uri: Uri 27 | private outputChannel: OutputChannel 28 | private networkHelper: NetworkHelper 29 | private sourceHelper: SourceHelper 30 | private panel: WebviewPanel 31 | private rootDir: WorkspaceFolder | undefined 32 | private literal: ExtractedTemplateLiteral 33 | 34 | // Event emitter which invokes document updates 35 | private _onDidChange = new EventEmitter() 36 | 37 | private html: string = "" // HTML document buffer 38 | 39 | timeout = ms => new Promise(res => setTimeout(res, ms)) 40 | 41 | getCurrentHtml(): Promise { 42 | return new Promise(resolve => { 43 | resolve(this.html) 44 | }) 45 | } 46 | 47 | updatePanel() { 48 | this.panel.webview.html = this.html 49 | } 50 | 51 | async getVariablesFromUser( 52 | variableDefinitionNodes: VariableDefinitionNode[], 53 | ): Promise { 54 | await this.timeout(500) 55 | let variables = {} 56 | for (let node of variableDefinitionNodes) { 57 | const variableType = 58 | this.sourceHelper.getTypeForVariableDefinitionNode(node) 59 | variables = { 60 | ...variables, 61 | [`${node.variable.name.value}`]: this.sourceHelper.typeCast( 62 | (await window.showInputBox({ 63 | ignoreFocusOut: true, 64 | placeHolder: `Please enter the value for ${node.variable.name.value}`, 65 | validateInput: async (value: string) => 66 | this.sourceHelper.validate(value, variableType), 67 | })) as string, 68 | variableType, 69 | ), 70 | } 71 | } 72 | return variables 73 | } 74 | 75 | async getEndpointName(endpointNames: string[]) { 76 | // Endpoints extensions docs say that at least "default" will be there 77 | let endpointName = endpointNames[0] 78 | if (endpointNames.length > 1) { 79 | const pickedValue = await window.showQuickPick(endpointNames, { 80 | canPickMany: false, 81 | ignoreFocusOut: true, 82 | placeHolder: "Select an endpoint", 83 | }) 84 | 85 | if (pickedValue) { 86 | endpointName = pickedValue 87 | } 88 | } 89 | return endpointName 90 | } 91 | 92 | constructor( 93 | uri: Uri, 94 | outputChannel: OutputChannel, 95 | literal: ExtractedTemplateLiteral, 96 | panel: WebviewPanel, 97 | ) { 98 | this.uri = uri 99 | this.outputChannel = outputChannel 100 | this.sourceHelper = new SourceHelper(this.outputChannel) 101 | this.networkHelper = new NetworkHelper( 102 | this.outputChannel, 103 | this.sourceHelper, 104 | ) 105 | this.panel = panel 106 | this.rootDir = workspace.getWorkspaceFolder(Uri.file(literal.uri)) 107 | this.literal = literal 108 | this.panel.webview.options = { 109 | enableScripts: true, 110 | } 111 | 112 | this.loadProvider() 113 | .then() 114 | .catch(err => { 115 | this.html = err.toString() 116 | }) 117 | } 118 | validUrlFromSchema(pathOrUrl: string) { 119 | return Boolean(pathOrUrl.match(/^https?:\/\//g)) 120 | } 121 | reportError(message: string) { 122 | this.outputChannel.appendLine(message) 123 | this.setContentAndUpdate(message) 124 | } 125 | setContentAndUpdate(html: string) { 126 | this.html = html 127 | this.update(this.uri) 128 | this.updatePanel() 129 | } 130 | async loadEndpoint( 131 | projectConfig?: GraphQLProjectConfig, 132 | ): Promise { 133 | let endpoints: Endpoints = projectConfig?.extensions?.endpoints 134 | 135 | if (!endpoints) { 136 | endpoints = { 137 | default: { url: "" }, 138 | } as Endpoints 139 | 140 | this.update(this.uri) 141 | this.updatePanel() 142 | if (projectConfig?.schema) { 143 | this.outputChannel.appendLine( 144 | `Warning: endpoints missing from graphql config. will try 'schema' value(s) instead`, 145 | ) 146 | const schema = projectConfig.schema 147 | if (schema && Array.isArray(schema)) { 148 | schema.map(s => { 149 | if (this.validUrlFromSchema(s as string)) { 150 | endpoints!.default.url = s.toString() 151 | } 152 | }) 153 | } else if (schema && this.validUrlFromSchema(schema as string)) { 154 | endpoints.default.url = schema.toString() 155 | } 156 | } else if (!endpoints?.default?.url) { 157 | this.reportError( 158 | "Warning: No Endpoints configured. Config schema contains no URLs", 159 | ) 160 | return null 161 | } else { 162 | this.outputChannel.appendLine( 163 | `Warning: No Endpoints configured. Attempting to execute operation with 'config.schema' value '${endpoints.default.url}'`, 164 | ) 165 | } 166 | } 167 | const endpointNames = Object.keys(endpoints) 168 | 169 | if (endpointNames.length === 0) { 170 | this.reportError( 171 | `Error: endpoint data missing from graphql config endpoints extension`, 172 | ) 173 | return null 174 | } 175 | const endpointName = await this.getEndpointName(endpointNames) 176 | return endpoints[endpointName] || endpoints.default 177 | } 178 | async loadProvider() { 179 | try { 180 | const rootDir = workspace.getWorkspaceFolder(Uri.file(this.literal.uri)) 181 | if (!rootDir) { 182 | this.reportError("Error: this file is outside the workspace.") 183 | return 184 | } else { 185 | const config = await loadConfig({ rootDir: rootDir!.uri.fsPath }) 186 | let projectConfig = config?.getProjectForFile(this.literal.uri) 187 | if (!projectConfig) { 188 | return 189 | } 190 | 191 | const endpoint = await this.loadEndpoint(projectConfig) 192 | if (endpoint) { 193 | let variableDefinitionNodes: VariableDefinitionNode[] = [] 194 | visit(this.literal.ast, { 195 | VariableDefinition(node: VariableDefinitionNode) { 196 | variableDefinitionNodes.push(node) 197 | }, 198 | }) 199 | 200 | const updateCallback = (data: string, operation: string) => { 201 | if (operation === "subscription") { 202 | this.html = `
${escapeHtml(data)}
` + this.html 203 | } else { 204 | this.html += `
${escapeHtml(data)}
` 205 | } 206 | this.update(this.uri) 207 | this.updatePanel() 208 | } 209 | 210 | if (variableDefinitionNodes.length > 0) { 211 | const variables = await this.getVariablesFromUser( 212 | variableDefinitionNodes, 213 | ) 214 | 215 | await this.networkHelper.executeOperation({ 216 | endpoint, 217 | literal: this.literal, 218 | variables, 219 | updateCallback, 220 | projectConfig, 221 | }) 222 | } else { 223 | await this.networkHelper.executeOperation({ 224 | endpoint, 225 | literal: this.literal, 226 | variables: {}, 227 | updateCallback, 228 | projectConfig, 229 | }) 230 | } 231 | } 232 | } 233 | } catch (err: unknown) { 234 | // @ts-expect-error 235 | this.reportError(`Error: graphql operation failed\n ${err.toString()}`) 236 | } 237 | } 238 | async loadConfig() { 239 | const rootDir = this.rootDir 240 | if (!rootDir) { 241 | this.reportError(`Error: this file is outside the workspace.`) 242 | return 243 | } else { 244 | const config = await loadConfig({ rootDir: rootDir!.uri.fsPath }) 245 | let projectConfig = config?.getProjectForFile(this.literal.uri) 246 | 247 | if (!projectConfig!.schema) { 248 | this.reportError(`Error: schema from graphql config`) 249 | return 250 | } 251 | return projectConfig 252 | } 253 | } 254 | 255 | get onDidChange(): Event { 256 | return this._onDidChange.event 257 | } 258 | 259 | public update(uri: Uri) { 260 | this._onDidChange.fire(uri) 261 | } 262 | 263 | provideTextDocumentContent(_: Uri): ProviderResult { 264 | return this.html 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/client/network-helper.ts: -------------------------------------------------------------------------------- 1 | import { visit, OperationTypeNode, GraphQLError } from "graphql" 2 | import gql from "graphql-tag" 3 | import fetch from "node-fetch" 4 | import { Agent } from "https" 5 | import * as ws from "ws" 6 | import { pipe, subscribe } from "wonka" 7 | import { Endpoint } from "graphql-config/extensions/endpoints" 8 | import { OutputChannel, workspace } from "vscode" 9 | import { GraphQLProjectConfig } from "graphql-config" 10 | import { createClient as createWSClient, OperationResult } from "graphql-ws" 11 | import { 12 | CombinedError, 13 | createClient, 14 | defaultExchanges, 15 | subscriptionExchange, 16 | } from "@urql/core" 17 | 18 | import { 19 | ExtractedTemplateLiteral, 20 | SourceHelper, 21 | getFragmentDependenciesForAST, 22 | } from "./source-helper" 23 | 24 | import { UserVariables } from "./graphql-content-provider" 25 | export class NetworkHelper { 26 | private outputChannel: OutputChannel 27 | private sourceHelper: SourceHelper 28 | 29 | constructor(outputChannel: OutputChannel, sourceHelper: SourceHelper) { 30 | this.outputChannel = outputChannel 31 | this.sourceHelper = sourceHelper 32 | } 33 | private buildClient({ 34 | operation, 35 | endpoint, 36 | updateCallback, 37 | }: { 38 | operation: string 39 | endpoint: Endpoint 40 | updateCallback: (data: string, operation: string) => void 41 | }) { 42 | const { rejectUnauthorized } = workspace.getConfiguration("vscode-graphql") 43 | // this is a node specific setting that can allow requests against servers using self-signed certificates 44 | // it is similar to passing the nodejs env variable flag, except configured on a per-request basis here 45 | const agent = new Agent({ rejectUnauthorized }) 46 | 47 | const exchanges = [...defaultExchanges]; 48 | if (operation === "subscription") { 49 | const wsEndpointURL = endpoint.url.replace(/^http/, "ws") 50 | const wsClient = createWSClient({ 51 | url: wsEndpointURL, 52 | connectionAckWaitTimeout: 3000, 53 | webSocketImpl: ws, 54 | }) 55 | exchanges.push( 56 | subscriptionExchange({ 57 | forwardSubscription: operation => ({ 58 | subscribe: sink => ({ 59 | unsubscribe: wsClient.subscribe(operation, sink), 60 | }), 61 | }), 62 | }), 63 | ) 64 | } 65 | 66 | return createClient({ 67 | url: endpoint.url, 68 | fetch, 69 | fetchOptions: { 70 | headers: endpoint.headers as HeadersInit, 71 | // this is an option that's only available in `node-fetch`, not in the standard fetch API 72 | // @ts-expect-error 73 | agent: new URL(endpoint.url).protocol === 'https:' ? agent : undefined, 74 | }, 75 | exchanges, 76 | }) 77 | } 78 | 79 | buildSubscribeConsumer = 80 | (cb: ExecuteOperationOptions["updateCallback"], operation: string) => 81 | (result: OperationResult) => { 82 | const { errors, data, error } = result as { 83 | error?: CombinedError 84 | errors?: GraphQLError[] 85 | data?: unknown 86 | } 87 | if (errors || data) { 88 | cb(formatData(result), operation) 89 | } 90 | if (error) { 91 | if (error.graphQLErrors && error.graphQLErrors.length > 0) { 92 | cb( 93 | JSON.stringify({ errors: error.graphQLErrors }, null, 2), 94 | operation, 95 | ) 96 | } 97 | if (error.networkError) { 98 | cb(error.networkError.toString(), operation) 99 | } 100 | } 101 | } 102 | 103 | async executeOperation({ 104 | endpoint, 105 | literal, 106 | variables, 107 | updateCallback, 108 | projectConfig, 109 | }: ExecuteOperationOptions) { 110 | const operationTypes: OperationTypeNode[] = [] 111 | const operationNames: string[] = [] 112 | 113 | visit(literal.ast, { 114 | OperationDefinition(node) { 115 | operationTypes.push(node.operation) 116 | operationNames.push(node.name?.value || "") 117 | }, 118 | }) 119 | const fragmentDefinitions = await this.sourceHelper.getFragmentDefinitions( 120 | projectConfig, 121 | ) 122 | 123 | const fragmentInfos = await getFragmentDependenciesForAST( 124 | literal.ast, 125 | fragmentDefinitions, 126 | ) 127 | 128 | fragmentInfos.forEach(fragmentInfo => { 129 | literal.content = fragmentInfo.content + "\n" + literal.content 130 | }) 131 | 132 | const parsedOperation = gql` 133 | ${literal.content} 134 | ` 135 | return Promise.all( 136 | operationTypes.map(async operation => { 137 | const subscriber = this.buildSubscribeConsumer( 138 | updateCallback, 139 | operation, 140 | ) 141 | this.outputChannel.appendLine(`NetworkHelper: operation: ${operation}`) 142 | this.outputChannel.appendLine( 143 | `NetworkHelper: endpoint: ${endpoint.url}`, 144 | ) 145 | try { 146 | const urqlClient = this.buildClient({ 147 | operation, 148 | endpoint, 149 | updateCallback, 150 | }) 151 | if (operation === "subscription") { 152 | pipe( 153 | urqlClient.subscription(parsedOperation, variables), 154 | subscribe(subscriber), 155 | ) 156 | } else { 157 | if (operation === "query") { 158 | pipe( 159 | urqlClient.query(parsedOperation, variables), 160 | subscribe(subscriber), 161 | ) 162 | } else { 163 | pipe( 164 | urqlClient.mutation(parsedOperation, variables), 165 | subscribe(subscriber), 166 | ) 167 | } 168 | } 169 | } catch { 170 | console.error("error executing operation") 171 | } 172 | }), 173 | ) 174 | } 175 | } 176 | 177 | export interface ExecuteOperationOptions { 178 | endpoint: Endpoint 179 | literal: ExtractedTemplateLiteral 180 | variables: UserVariables 181 | updateCallback: (data: string, operation: string) => void 182 | projectConfig: GraphQLProjectConfig 183 | } 184 | 185 | function formatData({ data, errors }: any) { 186 | return JSON.stringify({ data, errors }, null, 2) 187 | } 188 | -------------------------------------------------------------------------------- /src/client/source-helper.ts: -------------------------------------------------------------------------------- 1 | import { Position, OutputChannel, TextDocument } from "vscode" 2 | import { 3 | visit, 4 | parse, 5 | VariableDefinitionNode, 6 | FragmentDefinitionNode, 7 | NamedTypeNode, 8 | ListTypeNode, 9 | OperationDefinitionNode, 10 | print, 11 | } from "graphql" 12 | import { GraphQLProjectConfig } from "graphql-config" 13 | import { ASTNode, DocumentNode } from "graphql/language" 14 | 15 | export type FragmentInfo = { 16 | filePath?: string 17 | content: string 18 | definition: FragmentDefinitionNode 19 | } 20 | 21 | import nullthrows from "nullthrows" 22 | 23 | export class SourceHelper { 24 | private outputChannel: OutputChannel 25 | private fragmentDefinitions: Map 26 | constructor(outputChannel: OutputChannel) { 27 | this.outputChannel = outputChannel 28 | this.fragmentDefinitions = new Map() 29 | } 30 | 31 | getTypeForVariableDefinitionNode( 32 | node: VariableDefinitionNode, 33 | ): GraphQLScalarType { 34 | let namedTypeNode: NamedTypeNode | null = null 35 | let isList = false 36 | visit(node, { 37 | ListType(_listNode: ListTypeNode) { 38 | isList = true 39 | }, 40 | NamedType(namedNode: NamedTypeNode) { 41 | namedTypeNode = namedNode 42 | }, 43 | }) 44 | if (isList) { 45 | // TODO: This is not a name.value but a custom type that might confuse future programmers 46 | return "ListNode" 47 | } 48 | if (namedTypeNode) { 49 | // TODO: Handle this for object types/ enums/ custom scalars 50 | return (namedTypeNode as NamedTypeNode).name.value as GraphQLScalarType 51 | } else { 52 | // TODO: Is handling all via string a correct fallback? 53 | return "String" 54 | } 55 | } 56 | validate(value: string, type: GraphQLScalarType) { 57 | try { 58 | switch (type) { 59 | case "Int": 60 | if (parseInt(value)) { 61 | return null 62 | } 63 | break 64 | case "Float": 65 | if (parseFloat(value)) { 66 | return null 67 | } 68 | break 69 | case "Boolean": 70 | if (value === "true" || value === "false") { 71 | return null 72 | } 73 | break 74 | case "String": 75 | if (value.length && !Array.isArray(value)) { 76 | return null 77 | } 78 | break 79 | default: 80 | // For scalar types, it is impossible to know what data type they 81 | // should be. Therefore we don't do any validation. 82 | return null 83 | } 84 | } catch { 85 | return `${value} is not a valid ${type}` 86 | } 87 | return `${value} is not a valid ${type}` 88 | } 89 | 90 | typeCast(value: string, type: GraphQLScalarType) { 91 | if (type === "Int") { 92 | return parseInt(value) 93 | } 94 | if (type === "Float") { 95 | return parseFloat(value) 96 | } 97 | if (type === "Boolean") { 98 | return Boolean(value) 99 | } 100 | if (type === "String") { 101 | return value 102 | } 103 | 104 | // TODO: Does this note need to have an impact? 105 | // NOTE: 106 | // -- We don't do anything for non-nulls - the backend will throw a meaninful error 107 | // -- We treat custom types and lists similarly - as JSON - tedious for user to provide JSON but it works 108 | // -- We treat enums as string and that fits 109 | 110 | // Object type 111 | try { 112 | return JSON.parse(value) 113 | } catch (e) { 114 | this.outputChannel.appendLine( 115 | `Failed to parse user input as JSON, please use double quotes.`, 116 | ) 117 | return value 118 | } 119 | } 120 | async getFragmentDefinitions( 121 | projectConfig: GraphQLProjectConfig, 122 | ): Promise> { 123 | const sources = await projectConfig.getDocuments() 124 | const fragmentDefinitions = this.fragmentDefinitions 125 | 126 | sources.forEach(source => { 127 | visit(source.document as DocumentNode, { 128 | FragmentDefinition(node) { 129 | const existingDef = fragmentDefinitions.get(node.name.value) 130 | const newVal = print(node) 131 | if (existingDef && existingDef.content !== newVal) { 132 | fragmentDefinitions.set(node.name.value, { 133 | definition: node, 134 | content: newVal, 135 | filePath: source.location, 136 | }) 137 | } else if (!existingDef) { 138 | fragmentDefinitions.set(node.name.value, { 139 | definition: node, 140 | content: newVal, 141 | filePath: source.location, 142 | }) 143 | } 144 | }, 145 | }) 146 | }) 147 | return fragmentDefinitions 148 | } 149 | 150 | extractAllTemplateLiterals( 151 | document: TextDocument, 152 | tags: string[] = ["gql"], 153 | ): ExtractedTemplateLiteral[] { 154 | const text = document.getText() 155 | const documents: ExtractedTemplateLiteral[] = [] 156 | 157 | if (document.languageId === "graphql") { 158 | const text = document.getText() 159 | processGraphQLString(text, 0) 160 | return documents 161 | } 162 | 163 | tags.forEach(tag => { 164 | // https://regex101.com/r/Pd5PaU/2 165 | const regExpGQL = new RegExp(tag + "\\s*`([\\s\\S]+?)`", "mg") 166 | 167 | let result: RegExpExecArray | null 168 | while ((result = regExpGQL.exec(text)) !== null) { 169 | const contents = result[1] 170 | 171 | // https://regex101.com/r/KFMXFg/2 172 | if (Boolean(contents.match("/${(.+)?}/g"))) { 173 | // We are ignoring operations with template variables for now 174 | continue 175 | } 176 | try { 177 | processGraphQLString(contents, result.index + tag.length + 1) 178 | } catch (e) {} 179 | } 180 | }) 181 | return documents 182 | 183 | function processGraphQLString(text: string, offset: number) { 184 | try { 185 | const ast = parse(text) 186 | const operations = ast.definitions.filter( 187 | def => def.kind === "OperationDefinition", 188 | ) 189 | operations.forEach((op: any) => { 190 | const filteredAst = { 191 | ...ast, 192 | definitions: ast.definitions.filter(def => { 193 | if (def.kind === "OperationDefinition" && def !== op) { 194 | return false 195 | } 196 | return true 197 | }), 198 | } 199 | const content = print(filteredAst) 200 | documents.push({ 201 | content: content, 202 | uri: document.uri.path, 203 | position: document.positionAt(op.loc.start + offset), 204 | definition: op, 205 | ast: filteredAst, 206 | }) 207 | }) 208 | } catch (e) {} 209 | } 210 | } 211 | } 212 | 213 | export type GraphQLScalarType = "String" | "Float" | "Int" | "Boolean" | string 214 | export type GraphQLScalarTSType = string | number | boolean 215 | 216 | export interface ExtractedTemplateLiteral { 217 | content: string 218 | uri: string 219 | position: Position 220 | ast: DocumentNode 221 | definition: OperationDefinitionNode 222 | } 223 | 224 | export const getFragmentDependencies = async ( 225 | query: string, 226 | fragmentDefinitions?: Map | null, 227 | ): Promise => { 228 | // If there isn't context for fragment references, 229 | // return an empty array. 230 | if (!fragmentDefinitions) { 231 | return [] 232 | } 233 | // If the query cannot be parsed, validations cannot happen yet. 234 | // Return an empty array. 235 | let parsedQuery 236 | try { 237 | parsedQuery = parse(query) 238 | } catch (error) { 239 | return [] 240 | } 241 | return getFragmentDependenciesForAST(parsedQuery, fragmentDefinitions) 242 | } 243 | 244 | export const getFragmentDependenciesForAST = async ( 245 | parsedQuery: ASTNode, 246 | fragmentDefinitions: Map, 247 | ): Promise => { 248 | if (!fragmentDefinitions) { 249 | return [] 250 | } 251 | 252 | const existingFrags = new Map() 253 | const referencedFragNames = new Set() 254 | 255 | visit(parsedQuery, { 256 | FragmentDefinition(node) { 257 | existingFrags.set(node.name.value, true) 258 | }, 259 | FragmentSpread(node) { 260 | if (!referencedFragNames.has(node.name.value)) { 261 | referencedFragNames.add(node.name.value) 262 | } 263 | }, 264 | }) 265 | 266 | const asts = new Set() 267 | referencedFragNames.forEach(name => { 268 | if (!existingFrags.has(name) && fragmentDefinitions.has(name)) { 269 | asts.add(nullthrows(fragmentDefinitions.get(name))) 270 | } 271 | }) 272 | 273 | const referencedFragments: FragmentInfo[] = [] 274 | 275 | asts.forEach(ast => { 276 | visit(ast.definition, { 277 | FragmentSpread(node) { 278 | if ( 279 | !referencedFragNames.has(node.name.value) && 280 | fragmentDefinitions.get(node.name.value) 281 | ) { 282 | asts.add(nullthrows(fragmentDefinitions.get(node.name.value))) 283 | referencedFragNames.add(node.name.value) 284 | } 285 | }, 286 | }) 287 | if (!existingFrags.has(ast.definition.name.value)) { 288 | referencedFragments.push(ast) 289 | } 290 | }) 291 | 292 | return referencedFragments 293 | } 294 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | "use strict" 2 | import * as path from "path" 3 | import { 4 | workspace, 5 | ExtensionContext, 6 | window, 7 | commands, 8 | OutputChannel, 9 | languages, 10 | Uri, 11 | ViewColumn, 12 | } from "vscode" 13 | import { 14 | LanguageClient, 15 | LanguageClientOptions, 16 | ServerOptions, 17 | TransportKind, 18 | RevealOutputChannelOn, 19 | } from "vscode-languageclient" 20 | 21 | import statusBarItem, { initStatusBar } from "./status" 22 | 23 | import { GraphQLContentProvider } from "./client/graphql-content-provider" 24 | import { GraphQLCodeLensProvider } from "./client/graphql-codelens-provider" 25 | import { ExtractedTemplateLiteral } from "./client/source-helper" 26 | import { CustomInitializationFailedHandler } from "./CustomInitializationFailedHandler" 27 | 28 | function getConfig() { 29 | return workspace.getConfiguration( 30 | "vscode-graphql", 31 | window.activeTextEditor ? window.activeTextEditor.document.uri : null, 32 | ) 33 | } 34 | 35 | export function activate(context: ExtensionContext) { 36 | let outputChannel: OutputChannel = window.createOutputChannel( 37 | "GraphQL Language Server", 38 | ) 39 | const config = getConfig() 40 | const { debug } = config 41 | 42 | if (debug) { 43 | console.log('Extension "vscode-graphql" is now active!') 44 | } 45 | 46 | const serverModule = context.asAbsolutePath( 47 | path.join("out/server", "server.js"), 48 | ) 49 | 50 | const debugOptions = { 51 | execArgv: ["--nolazy", "--inspect=localhost:6009"], 52 | } 53 | 54 | let serverOptions: ServerOptions = { 55 | run: { 56 | module: serverModule, 57 | transport: TransportKind.ipc, 58 | }, 59 | debug: { 60 | module: serverModule, 61 | transport: TransportKind.ipc, 62 | options: { ...(debug ? debugOptions : {}) }, 63 | }, 64 | } 65 | 66 | let clientOptions: LanguageClientOptions = { 67 | documentSelector: [ 68 | { scheme: "file", language: "graphql" }, 69 | { scheme: "file", language: "javascript" }, 70 | { scheme: "file", language: "javascriptreact" }, 71 | { scheme: "file", language: "typescript" }, 72 | { scheme: "file", language: "typescriptreact" }, 73 | ], 74 | synchronize: { 75 | // TODO: should this focus on `graphql-config` documents, schema and/or includes? 76 | fileEvents: [ 77 | workspace.createFileSystemWatcher( 78 | "/{graphql.config.*,.graphqlrc,.graphqlrc.*,package.json}", 79 | false, 80 | // ignore change events for graphql config, we only care about create, delete and save events 81 | true, 82 | ), 83 | // these ignore node_modules and .git by default 84 | workspace.createFileSystemWatcher( 85 | "**/{*.graphql,*.graphqls,*.gql,*.js,*.mjs,*.cjs,*.esm,*.es,*.es6,*.jsx,*.ts,*.tsx}", 86 | ), 87 | ], 88 | }, 89 | outputChannel: outputChannel, 90 | outputChannelName: "GraphQL Language Server", 91 | revealOutputChannelOn: RevealOutputChannelOn.Never, 92 | initializationFailedHandler: CustomInitializationFailedHandler( 93 | outputChannel, 94 | ), 95 | } 96 | 97 | const client = new LanguageClient( 98 | "vscode-graphql", 99 | "GraphQL Language Server", 100 | serverOptions, 101 | clientOptions, 102 | debug, 103 | ) 104 | 105 | let clientLSPDisposable = client.start() 106 | context.subscriptions.push(clientLSPDisposable) 107 | 108 | const commandIsDebugging = commands.registerCommand( 109 | "vscode-graphql.isDebugging", 110 | () => { 111 | outputChannel.appendLine(`is in debug mode: ${!!debug}`) 112 | }, 113 | ) 114 | context.subscriptions.push(commandIsDebugging) 115 | 116 | const commandShowOutputChannel = commands.registerCommand( 117 | "vscode-graphql.showOutputChannel", 118 | () => { 119 | outputChannel.show() 120 | }, 121 | ) 122 | context.subscriptions.push(commandShowOutputChannel) 123 | 124 | // Manage Status Bar 125 | context.subscriptions.push(statusBarItem) 126 | client.onReady().then(() => { 127 | initStatusBar(statusBarItem, client, window.activeTextEditor) 128 | }) 129 | 130 | const settings = workspace.getConfiguration("vscode-graphql") 131 | 132 | const registerCodeLens = () => { 133 | context.subscriptions.push( 134 | languages.registerCodeLensProvider( 135 | [ 136 | "javascript", 137 | "typescript", 138 | "javascriptreact", 139 | "typescriptreact", 140 | "graphql", 141 | ], 142 | new GraphQLCodeLensProvider(outputChannel), 143 | ), 144 | ) 145 | } 146 | 147 | if (settings.showExecCodelens) { 148 | registerCodeLens() 149 | } 150 | 151 | workspace.onDidChangeConfiguration(() => { 152 | const newSettings = workspace.getConfiguration("vscode-graphql") 153 | if (newSettings.showExecCodeLens) { 154 | registerCodeLens() 155 | } 156 | }) 157 | 158 | const commandContentProvider = commands.registerCommand( 159 | "vscode-graphql.contentProvider", 160 | async (literal: ExtractedTemplateLiteral) => { 161 | const uri = Uri.parse("graphql://authority/graphql") 162 | 163 | const panel = window.createWebviewPanel( 164 | "vscode-graphql.results-preview", 165 | "GraphQL Execution Result", 166 | ViewColumn.Two, 167 | {}, 168 | ) 169 | 170 | const contentProvider = new GraphQLContentProvider( 171 | uri, 172 | outputChannel, 173 | literal, 174 | panel, 175 | ) 176 | const registration = workspace.registerTextDocumentContentProvider( 177 | "graphql", 178 | contentProvider, 179 | ) 180 | context.subscriptions.push(registration) 181 | 182 | const html = await contentProvider.getCurrentHtml() 183 | panel.webview.html = html 184 | }, 185 | ) 186 | context.subscriptions.push(commandContentProvider) 187 | 188 | commands.registerCommand("vscode-graphql.restart", async () => { 189 | outputChannel.appendLine(`Stopping GraphQL LSP`) 190 | await client.stop() 191 | clientLSPDisposable.dispose() 192 | 193 | outputChannel.appendLine(`Restarting GraphQL LSP`) 194 | clientLSPDisposable = await client.start() 195 | context.subscriptions.push(clientLSPDisposable) 196 | 197 | outputChannel.appendLine(`GraphQL LSP restarted`) 198 | }) 199 | } 200 | 201 | export function deactivate() { 202 | console.log('Extension "vscode-graphql" is now de-active!') 203 | } 204 | -------------------------------------------------------------------------------- /src/server/server.ts: -------------------------------------------------------------------------------- 1 | import "babel-polyfill" 2 | import { startServer } from "graphql-language-service-server" 3 | // import { patchConfig } from "graphql-config-extension-prisma" 4 | 5 | // the npm scripts are configured to only build this once before watching the extension, 6 | // so please restart the extension debugger for changes! 7 | const start = () => { 8 | startServer({ 9 | method: "node", 10 | }) 11 | .then(() => {}) 12 | .catch(err => { 13 | console.error(err) 14 | }) 15 | } 16 | 17 | start() 18 | -------------------------------------------------------------------------------- /src/status/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | StatusBarAlignment, 3 | StatusBarItem, 4 | TextEditor, 5 | window, 6 | ThemeColor, 7 | } from "vscode" 8 | import { LanguageClient, State } from "vscode-languageclient" 9 | 10 | enum Status { 11 | INIT = 1, 12 | RUNNING = 2, 13 | ERROR = 3, 14 | } 15 | 16 | const statusBarText = "GraphQL" 17 | const statusBarUIElements = { 18 | [Status.INIT]: { 19 | icon: "sync", 20 | tooltip: "GraphQL language server is initializing", 21 | }, 22 | [Status.RUNNING]: { 23 | icon: "plug", 24 | tooltip: "GraphQL language server is running", 25 | }, 26 | [Status.ERROR]: { 27 | icon: "stop", 28 | color: new ThemeColor("list.warningForeground"), 29 | tooltip: "GraphQL language server has stopped", 30 | }, 31 | } 32 | const statusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, 0) 33 | let extensionStatus: Status = Status.RUNNING 34 | let serverRunning: boolean = true // TODO: See comment with client.onNotification("init"..... 35 | const statusBarActivationLanguageIds = [ 36 | "graphql", 37 | "javascript", 38 | "javascriptreact", 39 | "typescript", 40 | "typescriptreact", 41 | ] 42 | 43 | function initStatusBar( 44 | statusBarItem: StatusBarItem, 45 | client: LanguageClient, 46 | editor: TextEditor | undefined, 47 | ) { 48 | extensionStatus = Status.INIT 49 | // TODO: Make graphql-language-service-server throw relevant 50 | // notifications. Currently, it does not throw "init" or "exit" 51 | // and status bar is hard coded to all greens. 52 | client.onNotification("init", params => { 53 | extensionStatus = Status.RUNNING 54 | serverRunning = true 55 | updateStatusBar(statusBarItem, editor) 56 | }) 57 | client.onNotification("exit", params => { 58 | extensionStatus = Status.ERROR 59 | serverRunning = false 60 | updateStatusBar(statusBarItem, editor) 61 | }) 62 | 63 | client.onDidChangeState(event => { 64 | if (event.newState === State.Running) { 65 | extensionStatus = Status.RUNNING 66 | serverRunning = true 67 | } else { 68 | extensionStatus = Status.ERROR 69 | client.info("The graphql server has stopped running") 70 | serverRunning = false 71 | } 72 | updateStatusBar(statusBarItem, editor) 73 | }) 74 | updateStatusBar(statusBarItem, editor) 75 | 76 | window.onDidChangeActiveTextEditor((editor: TextEditor | undefined) => { 77 | // update the status if the server is running 78 | updateStatusBar(statusBarItem, editor) 79 | }) 80 | } 81 | 82 | function updateStatusBar( 83 | statusBarItem: StatusBarItem, 84 | editor: TextEditor | undefined, 85 | ) { 86 | extensionStatus = serverRunning ? Status.RUNNING : Status.ERROR; 87 | 88 | const statusUI = statusBarUIElements[extensionStatus]; 89 | statusBarItem.text = `$(${statusUI.icon}) ${statusBarText}`; 90 | statusBarItem.tooltip = statusUI.tooltip; 91 | statusBarItem.command = "vscode-graphql.showOutputChannel"; 92 | if ("color" in statusUI) statusBarItem.color = statusUI.color 93 | 94 | if ( 95 | editor && 96 | statusBarActivationLanguageIds.indexOf(editor.document.languageId) > -1 97 | ) { 98 | statusBarItem.show() 99 | } else { 100 | statusBarItem.hide() 101 | } 102 | } 103 | 104 | export default statusBarItem 105 | export { statusBarItem, initStatusBar, updateStatusBar } 106 | -------------------------------------------------------------------------------- /src/test/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | 3 | suite("Extension Tests", function() { 4 | // Defines a Mocha unit test 5 | test("Something 1", function() { 6 | assert.equal(-1, [1, 2, 3].indexOf(5)); 7 | assert.equal(-1, [1, 2, 3].indexOf(0)); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/test/index.ts: -------------------------------------------------------------------------------- 1 | import * as testRunner from "vscode/lib/testrunner"; 2 | 3 | testRunner.configure({ 4 | ui: "tdd", 5 | useColors: true 6 | }); 7 | 8 | module.exports = testRunner; 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": ["es6", "esnext", "dom"], 7 | "sourceMap": true, 8 | "rootDir": "src", 9 | /* Strict Type-Checking Option */ 10 | "strict": true /* enable all strict type-checking options */, 11 | /* Additional Checks */ 12 | "noUnusedLocals": true /* Report errors on unused locals. */, 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | "noImplicitAny": false, 17 | "skipLibCheck": true, 18 | "esModuleInterop": true 19 | }, 20 | "exclude": ["node_modules", ".vscode-test", "ts-graphql-plugin"] 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": true, 7 | "class-name": true, 8 | "semicolon": [true, "always"], 9 | "triple-equals": true 10 | }, 11 | "defaultSeverity": "warning" 12 | } 13 | --------------------------------------------------------------------------------