master 64c524cd6607 cached
46 files
105.5 KB
28.1k tokens
17 symbols
1 requests
Download .txt
Repository: apollostack/eslint-plugin-graphql
Branch: master
Commit: 64c524cd6607
Files: 46
Total size: 105.5 KB

Directory structure:
gitextract_uah1ygf4/

├── .babelrc.js
├── .eslintrc.js
├── .github/
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       └── nodejs.yml
├── .gitignore
├── .npmignore
├── .tav.yml
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── README.md
├── package.json
├── renovate.json
├── src/
│   ├── constants.js
│   ├── createRule.js
│   ├── customGraphQLValidationRules.js
│   └── index.js
└── test/
    ├── __fixtures__/
    │   ├── required-fields-invalid-array.graphql
    │   ├── required-fields-invalid-no-id.graphql
    │   ├── required-fields-valid-array.graphql
    │   ├── required-fields-valid-id.graphql
    │   └── required-fields-valid-no-id.graphql
    ├── customTagName.js
    ├── default.js
    ├── env/
    │   ├── apollo.js
    │   ├── fraql.js
    │   ├── index.js
    │   ├── lokka.js
    │   └── relay.js
    ├── graphqlconfig/
    │   ├── index.js
    │   ├── multiproject/
    │   │   └── .graphqlconfig
    │   ├── multiproject-literal/
    │   │   └── graphql.config.json
    │   └── simple/
    │       └── graphql.config.json
    ├── helpers.js
    ├── index.js
    ├── makeProcessors.js
    ├── schema.graphql
    ├── schemaTests/
    │   └── index.js
    ├── second-schema.graphql
    ├── updateSchemaJson.js
    └── validationRules/
        ├── built-in.js
        ├── capitalized-type-name.js
        ├── index.js
        ├── named-operations.js
        ├── no-deprecated-fields.js
        └── required-fields.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc.js
================================================
module.exports = {
  presets: ['@babel/preset-env'],
  plugins: ['@babel/plugin-transform-runtime'],
};


================================================
FILE: .eslintrc.js
================================================
module.exports = {
  extends: "eslint:recommended",
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: "module"
  },
  env: {
    mocha: true,
    node: true,
    es6: true,
  }
};


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
  Thanks for filing a pull request on eslint-plugin-graphql!

  Please look at the following checklist to ensure that your PR
  can be accepted quickly:
-->

TODO:

- [ ] Make sure all of the significant new logic is covered by tests
- [ ] Rebase your changes on master so that they can be merged easily
- [ ] Make sure all tests pass
- [ ] Update CHANGELOG.md with your change
- [ ] If this was a change that affects the external API, update the README



================================================
FILE: .github/workflows/nodejs.yml
================================================
name: Node CI

on: 
  push:
    branches:
      - !master

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [8.x, 10.x, 12.x]

    steps:
    - uses: actions/checkout@v1
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - name: npm install, build, and test
      run: |
        npm install
        npm run build --if-present
        npm test


================================================
FILE: .gitignore
================================================
node_modules
lib
test/schema.json
test/second-schema.json
.vscode/


================================================
FILE: .npmignore
================================================
src
docs
test


================================================
FILE: .tav.yml
================================================

graphql:
  versions: ^0.12.0  || ^0.13.0 || ^14.0.0 || ^15.0.0
  commands: mocha test/index.js


================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
  - "10"
  - "12"
install:
  - npm install

script:
  - npm run lint && npm test

# Allow Travis tests to run in containers.
sudo: false


================================================
FILE: CHANGELOG.md
================================================
# Change log

### vNEXT

- _Nothing yet!_

### v4.0.0

- Improve identity template literal tag docs. [PR #254](https://github.com/apollographql/eslint-plugin-graphql/pull/254) by [Jayden Seric](https://github.com/jaydenseric).
- Add support for GraphQL 15. [PR #271](https://github.com/apollographql/eslint-plugin-graphql/pull/271) by [Scott Taylor](https://github.com/staylor).
- Update all `devDependencies` - upgrades the project to use Babel 7 and ESLint 6. [PR #271](https://github.com/apollographql/eslint-plugin-graphql/pull/271) by [Scott Taylor](https://github.com/staylor).
- **BREAKING**: Minimum supported Node.js version is now Node.js 10; Dropped support for Node.js 8. [PR #271](https://github.com/apollographql/eslint-plugin-graphql/pull/271) by [Scott Taylor](https://github.com/staylor).

### v3.1.1

- Update the `required-fields` rule to handle inline fragments without field ancestors. [PR #240](https://github.com/apollographql/eslint-plugin-graphql/pull/240) by [Henry Q. Dineen](https://github.com/henryqdineen)

### v3.1.0

- Fix an issue that caused `graphql/required-fields` to throw on non-existent field references. [PR #231](https://github.com/apollographql/eslint-plugin-graphql/pull/231) by [Vitor Balocco](https://github.com/vitorbal)
- chore: Update dependency `graphql-tools` to `v4.0.5`. [PR #239](https://github.com/apollographql/eslint-plugin-graphql/pull/239)
- chore: Update dependency `eslint` to `v5.16.0`. [PR #218](https://github.com/apollographql/eslint-plugin-graphql/pull/218)
- chore: Update dependency `graphql` to `v14.4.2`. [PR #220](https://github.com/apollographql/eslint-plugin-graphql/pull/220)
- chore: Update dependency `test-all-versions` to `v4.1.1`. [PR #230](https://github.com/apollographql/eslint-plugin-graphql/pull/230)
- chore: Update dependency `lodash` to `v4.17.13`. [PR #234](https://github.com/apollographql/eslint-plugin-graphql/pull/234)
- chore: Update dependency `mocha` to `v6`. [PR #213](https://github.com/apollographql/eslint-plugin-graphql/pull/213)
- chore: Running `prettier` on all files [PR #237](https://github.com/apollographql/eslint-plugin-graphql/pull/237)

### v3.0.3

- chore: Update dependency `graphql-tools` to `v4.0.4`. [PR #210](https://github.com/apollographql/eslint-plugin-graphql/pull/210)
- chore: Update dependency `eslint` to `v5.12.1`. [PR #206](https://github.com/apollographql/eslint-plugin-graphql/pull/206)
- chore: Update dependency `graphql` to `v14.1.1`. [PR #208](https://github.com/apollographql/eslint-plugin-graphql/pull/208)

### v3.0.2

- Fix regression which caused `graphql/required-fields` to throw on non-existent field references. [PR #203](https://github.com/apollographql/eslint-plugin-graphql/pull/203) by [Matt Bretl](https://github.com/mattbretl)

### v3.0.1

- Fix support for multi-schema workspaces [PR #179](https://github.com/apollographql/eslint-plugin-graphql/pull/179) by [Pat Sissons](https://github.com/patsissons)

### v3.0.0

- BREAKING: The `required-fields` rule has been significantly changed to make it a completely reliable method of ensuring an `id` field (or any other field name) is always requested when available. [PR #199](https://github.com/apollographql/eslint-plugin-graphql/pull/199) Here is the behavior, let's say we are requiring field `id`:
  - On any field whose return type defines a field called `id`, the selection set must directly contain `id`.
  - In any named fragment declaration whose type defines a field called `id`, the selection set must directly contain `id`.
  - An inline fragment whose type defines a field called `id` must contain `id` in its selection set unless its parent is also an inline fragment that contains the field `id`.
  - Here's a specific case which is _no longer valid_:
    - `query { greetings { hello ... on Greetings { id } } }`
    - This must now be written as `query { greetings { id hello ... on Greetings { id } } }`
  - This is a more conservative approach than before, driven by the fact that it's quite hard to ensure that a combination of inline fragments actually covers all of the possible types of a selection set.
- Fix breaking change in `graphql@^14.0.0` that renamed `ProvidedNonNullArguments` to `ProvidedRequiredArguments` [#192](https://github.com/apollographql/eslint-plugin-graphql/pull/192)
- Update dependencies to graphql-tools 4 and eslint 5.9 [#193](https://github.com/apollographql/eslint-plugin-graphql/pull/193)

### v2.1.1

- Fix support for InlineFragments with the `required-fields` rule in [#140](https://github.com/apollographql/eslint-plugin-graphql/pull/140/files) by [Steve Hollaar](https://github.com/stevehollaar)
- Fix error location information for literal .graphql files and strings with leading newlines in [#122](https://github.com/apollographql/eslint-plugin-graphql/pull/122) by [Dan Freeman](https://github.com/dfreeman)
- Add [`fraql`](https://github.com/smooth-code/fraql) environment

### v2.1.0

- Retrieves `.graphqlconfig` relative to the file being linted, which re-enables support for `vscode-eslint` using `.graphqlconfig` in [#108](https://github.com/apollographql/eslint-plugin-graphql/pull/108) by [Jon Wong][https://github.com/jnwng/]
- Cache schema reading/parsing results each time a rule is created in [#137](https://github.com/apollographql/eslint-plugin-graphql/pull/137) by [Kristján Oddsson](https://github.com/koddsson)

### v2.0.0

- Add support for `graphql-js@^0.12.0` and `graphql-js@^0.13.0` in [Jon Wong](https://github.com/jnwng/)[#119](https://github.com/apollographql/eslint-plugin-graphql/pull/93)
- Update list of available validators in [#121]((https://github.com/apollographql/eslint-plugin-graphql/pull/121) by [Pleun Vanderbauwhede](https://github.com/pleunv)
- Update supported Node `engines` to >= 6.0 in [#133](https://github.com/apollographql/eslint-plugin-graphql/pull/133) by [Jon Wong](https://github.com/jnwng/)

### v1.5.0

- Add new rule `no-deprecated-fields` in [Kristján Oddsson](https://github.com/koddsson/)[#92](https://github.com/apollographql/eslint-plugin-graphql/pull/93)
- Add support for deprecated fields on enums in [#100](https://github.com/apollographql/eslint-plugin-graphql/pull/100) by [Daniel Rinehart](https://github.com/NeoPhi)
- Bumped `babel-eslint` and pinned `graphql-config` in [#101](https://github.com/apollographql/eslint-plugin-graphql/pull/101) by [Jon Wong](https://github.com/jnwng)

### v1.4.1

Skipped v1.4.0 because of incorrect version tag in `package.json`

- Move `graphql-js` to `peerDependencies`, support `graphql@^0.11.0` in [Jon Wong](https://github.com/jnwng/)[#91](https://github.com/apollographql/eslint-plugin-graphql/pull/91)
- Fix escaping of literal .graphql files [Simon Lydell](https://github.com/lydell/) in [#88](https://github.com/apollographql/eslint-plugin-graphql/pull/88)
- Fix ESLint config validation [Simon Lydell](https://github.com/lydell/) in [#86](https://github.com/apollographql/eslint-plugin-graphql/pull/86)

### v1.3.0

- add support for [.graphqlconfig](https://github.com/graphcool/graphql-config) [Roman Hotsiy](https://github.com/RomanGotsiy) in [#80](https://github.com/apollographql/eslint-plugin-graphql/pull/80)
- add `graphql/capitalized-type-name` rule to warn on uncapitalized type names [DianaSuvorova](https://github.com/DianaSuvorova) in [#81](https://github.com/apollographql/eslint-plugin-graphql/pull/81)

### v1.2.0

- Add env config option for required-fields rule [Justin Schulz](https://github.com/PepperTeasdale) in [#75](https://github.com/apollographql/eslint-plugin-graphql/pull/75)

### v1.1.0

- Add option to pass schema as string [Christopher Cliff](https://github.com/christophercliff) in [#78](https://github.com/apollographql/eslint-plugin-graphql/pull/78)

### v1.0.0

- Fix template env for older runtimes (eg. node 5 and earlier) [Justin Schulz](https://github.com/PepperTeasdale) in [#73](https://github.com/apollographql/eslint-plugin-graphql/pull/73)
- Updated `graphql-js` to `v0.10.1` in [#67](https://github.com/apollographql/eslint-plugin-graphql/pull/67) [Sashko Stubailo](https://github.com/stubailo)

### v0.8.2

- Remove `KnownFragmentNames` and `UnusedFragment` from default rules in `literal` env. [Justin Schulz](https://github.com/PepperTeasdale) in [#70](https://github.com/apollographql/eslint-plugin-graphql/pull/70)

### v0.8.1

- Fix `graphql/required-fields` throwing on non-existent field reference [Benjie](https://github.com/benjie) in [#65](https://github.com/apollographql/eslint-plugin-graphql/pull/65)

### v0.8.0

- Updated `graphql` dependency to resolve test failures ([wording change](https://github.com/graphql/graphql-js/commit/ba401e154461bca5323ca9121c6dacaee10ebe88), no API change) [jnwng](https://github.com/jnwng)
- Add lint rule to enforce that required fields are specified. [rgoldfinger](https://github.com/rgoldfinger) in [#47](https://github.com/apollographql/eslint-plugin-graphql/pull/50)

### v0.7.0

- Add lint rule to enforce that operations have names [gauravmk](https://github.com/gauravmk) in [#47](https://github.com/apollographql/eslint-plugin-graphql/pull/47)

### v0.6.1

- Remove `babel-polyfill` from runtime dependencies, since it was only being used in test code. [joelgriffith](https://github.com/joelgriffith) in [#44](https://github.com/apollographql/eslint-plugin-graphql/pull/44)

### v0.6.0

- Update graphql-js dependency to 0.9.0 [jonbretman](https://github.com/jonbretman) in [#41](https://github.com/apollostack/eslint-plugin-graphql/pull/41)

### v0.5.0

- Take into account Apollo fragment interpolation rules [jnwng](https://github.com/jnwng) in [#33](https://github.com/apollostack/eslint-plugin-graphql/pull/33)
- Update graphql-js dependency to 0.8.2 [jonbretman](https://github.com/jonbretman) in [#40](https://github.com/apollostack/eslint-plugin-graphql/pull/40)

### v0.4.3

- Add `'literal'` option to options schema, so that it can actually be used. [stefanorg](https://github.com/stefanorg) in [#39](https://github.com/apollostack/eslint-plugin-graphql/pull/39)

### v0.4.2

- Added `'literal'` option to `env` for when working with `.graphql` and `.gql` files, by [jtmthf in #36](https://github.com/apollostack/eslint-plugin-graphql/pull/36)

### v0.4.1

- Support for selecting validation rules one by one, by [erydo in
  #34](https://github.com/apollostack/eslint-plugin-graphql/pull/34)

### v0.4.0

- Support for multiple schemas, by [erydo in
  #31](https://github.com/apollostack/eslint-plugin-graphql/pull/31)

### v0.3.1

- We didn't keep track of changes before this version. Consult the commit log.


================================================
FILE: CONTRIBUTING.md
================================================
# Apollo Contributor Guide

Excited about Apollo and want to make it better? We’re excited too!

Apollo is a community of developers just like you, striving to create the best tools and libraries around GraphQL. We welcome anyone who wants to contribute or provide constructive feedback, no matter the age or level of experience. If you want to help but don't know where to start, let us know, and we'll find something for you.

Oh, and if you haven't already, sign up for the [Apollo Slack](http://www.apollodata.com/#slack).

Here are some ways to contribute to the project, from easiest to most difficult:

* [Reporting bugs](#reporting-bugs)
* [Improving the documentation](#improving-the-documentation)
* [Responding to issues](#responding-to-issues)
* [Small bug fixes](#small-bug-fixes)
* [Suggesting features](#suggesting-features)
* [Big pull requests](#big-prs)

## Issues

### Reporting bugs

If you encounter a bug, please file an issue on GitHub via the repository of the sub-project you think contains the bug. If an issue you have is already reported, please add additional information or add a 👍 reaction to indicate your agreement.

While we will try to be as helpful as we can on any issue reported, please include the following to maximize the chances of a quick fix:

1. **Intended outcome:** What you were trying to accomplish when the bug occurred, and as much code as possible related to the source of the problem.
2. **Actual outcome:** A description of what actually happened, including a screenshot or copy-paste of any related error messages, logs, or other output that might be related. Places to look for information include your browser console, server console, and network logs. Please avoid non-specific phrases like “didn’t work” or “broke”.
3. **How to reproduce the issue:** Instructions for how the issue can be reproduced by a maintainer or contributor. Be as specific as possible, and only mention what is necessary to reproduce the bug. If possible, try to isolate the exact circumstances in which the bug occurs and avoid speculation over what the cause might be.

Creating a good reproduction really helps contributors investigate and resolve your issue quickly. In many cases, the act of creating a minimal reproduction illuminates that the source of the bug was somewhere outside the library in question, saving time and effort for everyone.

### Improving the documentation

Improving the documentation, examples, and other open source content can be the easiest way to contribute to the library. If you see a piece of content that can be better, open a PR with an improvement, no matter how small! If you would like to suggest a big change or major rewrite, we’d love to hear your ideas but please open an issue for discussion before writing the PR.

### Responding to issues

In addition to reporting issues, a great way to contribute to Apollo is to respond to other peoples' issues and try to identify the problem or help them work around it. If you’re interested in taking a more active role in this process, please go ahead and respond to issues. And don't forget to say "Hi" on Apollo Slack!

### Small bug fixes

For a small bug fix change (less than 20 lines of code changed), feel free to open a pull request. We’ll try to merge it as fast as possible and ideally publish a new release on the same day. The only requirement is, make sure you also add a test that verifies the bug you are trying to fix.

### Suggesting features

Most of the features in Apollo came from suggestions by you, the community! We welcome any ideas about how to make Apollo  better for your use case. Unless there is overwhelming demand for a feature, it might not get implemented immediately, but please include as much information as possible that will help people have a discussion about your proposal:

1. **Use case:** What are you trying to accomplish, in specific terms? Often, there might already be a good way to do what you need and a new feature is unnecessary, but it’s hard to know without information about the specific use case.
2. **Could this be a plugin?** In many cases, a feature might be too niche to be included in the core of a library, and is better implemented as a companion package. If there isn’t a way to extend the library to do what you want, could we add additional plugin APIs? It’s important to make the case for why a feature should be part of the core functionality of the library.
3. **Is there a workaround?** Is this a more convenient way to do something that is already possible, or is there some blocker that makes a workaround unfeasible?

Feature requests will be labeled as such, and we encourage using GitHub issues as a place to discuss new features and possible implementation designs. Please refrain from submitting a pull request to implement a proposed feature until there is consensus that it should be included. This way, you can avoid putting in work that can’t be merged in.

Once there is a consensus on the need for a new feature, proceed as listed below under “Big PRs”.

## Big PRs

This includes:

- Big bug fixes
- New features

For significant changes to a repository, it’s important to settle on a design before starting on the implementation. This way, we can make sure that major improvements get the care and attention they deserve. Since big changes can be risky and might not always get merged, it’s good to reduce the amount of possible wasted effort by agreeing on an implementation design/plan first.

1. **Open an issue.** Open an issue about your bug or feature, as described above.
2. **Reach consensus.** Some contributors and community members should reach an agreement that this feature or bug is important, and that someone should work on implementing or fixing it.
3. **Agree on intended behavior.** On the issue, reach an agreement about the desired behavior. In the case of a bug fix, it should be clear what it means for the bug to be fixed, and in the case of a feature, it should be clear what it will be like for developers to use the new feature.
4. **Agree on implementation plan.** Write a plan for how this feature or bug fix should be implemented. What modules need to be added or rewritten? Should this be one pull request or multiple incremental improvements? Who is going to do each part?
5. **Submit PR.** In the case where multiple dependent patches need to be made to implement the change, only submit one at a time. Otherwise, the others might get stale while the first is reviewed and merged. Make sure to avoid “while we’re here” type changes - if something isn’t relevant to the improvement at hand, it should be in a separate PR; this especially includes code style changes of unrelated code.
6. **Review.** At least one core contributor should sign off on the change before it’s merged. Look at the “code review” section below to learn about factors are important in the code review. If you want to expedite the code being merged, try to review your own code first!
7. **Merge and release!**

### Code review guidelines

It’s important that every piece of code in Apollo packages is reviewed by at least one core contributor familiar with that codebase. Here are some things we look for:

1. **Required CI checks pass.** This is a prerequisite for the review, and it is the PR author's responsibility. As long as the tests don’t pass, the PR won't get reviewed.
2. **Simplicity.** Is this the simplest way to achieve the intended goal? If there are too many files, redundant functions, or complex lines of code, suggest a simpler way to do the same thing. In particular, avoid implementing an overly general solution when a simple, small, and pragmatic fix will do.
3. **Testing.** Do the tests ensure this code won’t break when other stuff changes around it? When it does break, will the tests added help us identify which part of the library has the problem? Did we cover an appropriate set of edge cases? Look at the test coverage report if there is one. Are all significant code paths in the new code exercised at least once?
4. **No unnecessary or unrelated changes.** PRs shouldn’t come with random formatting changes, especially in unrelated parts of the code. If there is some refactoring that needs to be done, it should be in a separate PR from a bug fix or feature, if possible.
5. **Code has appropriate comments.** Code should be commented, or written in a clear “self-documenting” way.
6. **Idiomatic use of the language.** In TypeScript, make sure the typings are specific and correct. In ES2015, make sure to use imports rather than require and const instead of var, etc. Ideally a linter enforces a lot of this, but use your common sense and follow the style of the surrounding code.


================================================
FILE: README.md
================================================
# eslint-plugin-graphql
[![npm version](https://badge.fury.io/js/eslint-plugin-graphql.svg)](https://badge.fury.io/js/eslint-plugin-graphql)
[![Build Status](https://travis-ci.org/apollographql/eslint-plugin-graphql.svg?branch=master)](https://travis-ci.org/apollographql/eslint-plugin-graphql)
[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](http://www.apollostack.com/#slack)

---

> **[2022-01-25] Note - Upcoming Deprecation Plans:** We (Apollo) are working towards fully deprecating this repository. We suggest migrating to the community-supported [graphql-eslint](https://github.com/dotansimha/graphql-eslint). We'll share detailed migration documentation when everything here is ready to be officially deprecated, but just a heads up in case you're planning on adopting anything here for a new project (which you still can of course if this project works for you - support for this project will be minimal however).

---

An ESLint plugin that checks tagged query strings inside JavaScript, or queries inside `.graphql` files, against a GraphQL schema.

```
npm install eslint-plugin-graphql
```

![Screenshot from Atom](https://github.com/apollostack/eslint-plugin-graphql/raw/master/screenshot.png)

`eslint-plugin-graphql` has built-in settings for four GraphQL clients out of the box:

1. [Apollo client](http://docs.apollostack.com/apollo-client/index.html)
2. [Relay](https://facebook.github.io/relay/)
3. [Lokka](https://github.com/kadirahq/lokka)
4. [FraQL](https://github.com/smooth-code/fraql)

If you want to lint your GraphQL schema, rather than queries, check out [cjoudrey/graphql-schema-linter](https://github.com/cjoudrey/graphql-schema-linter).

### Importing schema JSON

You'll need to import your [introspection query result](https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js) or the schema as a string in the Schema Language format. This can be done if you define your ESLint config in a JS file.

### Retrieving a remote GraphQL schema

[graphql-cli](https://github.com/graphcool/graphql-cli) provides a `get-schema` command (in conjunction with a `.graphqlconfig` file) that makes retrieving remote schemas very simple.

[apollo-codegen](https://github.com/apollographql/apollo-codegen) also provides an [introspect-schema](https://github.com/apollographql/apollo-codegen#introspect-schema) command that can get your remote schemas as well

### Common options

All of the rules provided by this plugin have a few options in common. There are examples of how to use these with Apollo, Relay, Lokka, FraQL and literal files further down.

- `env`: Import default settings for your GraphQL client. Supported values: `'apollo'`, `'relay'`, `'lokka'`, `'fraql'` `'literal'`. Defaults to `'apollo'`. This is used for the slight parsing differences in the GraphQL syntax between Apollo, Relay, Lokka and FraQL as well as giving nice defaults to some other options.

- `tagName`: The name of the template literal tag that this plugin should look for when searching for GraphQL queries. It has different defaults depending on the `env` option:

  - `'relay'`: `'Relay.QL'`
  - `'internal'`: Special automatic value
  - others: `'gql'`, `'graphql'`

You also have to specify a schema. You can either do it using _one_ of these options:

- `schemaJson`: Your schema as JSON.
- `schemaJsonFilepath`: The absolute path to your schema as a .json file. (Warning: this variant is incompatible with `eslint --cache`.)
- `schemaString`: Your schema in the Schema Language format as a string.

Alternatively, you can use a [.graphqlconfig](https://github.com/graphcool/graphql-config) file instead of the above three options. If you do there's one more option to know about:

- `projectName`: In case you specify multiple schemas in your `.graphqlconfig` file, choose which one to use by providing the project name here as a string.

There's an example on how to use a `.graphqlconfig` file further down.

### Identity template literal tag

This plugin relies on GraphQL queries being prefixed with a special tag. In Relay and Apollo this is done often, but some other clients take query strings without a tag. In this case, [`fake-tag`](https://npm.im/fake-tag) can be used to define an identity tag that doesn't do anything except for tell the linter this is a GraphQL query:

```js
import gql from "fake-tag";

const QUERY_VIEWER_NAME = gql`
  query ViewerName {
    viewer {
      name
    }
  }
`;
```

Fake tags won’t be necessary [once `/* GraphQL */` comment tags are supported](https://github.com/apollographql/eslint-plugin-graphql/issues/224).

### GraphQL literal files

This plugin also lints GraphQL literal files ending on `.gql` or `.graphql`.
In order to do so set `env` to `'literal'` in your `.eslintrc.js` and tell eslint to check these files as well.

```bash
eslint . --ext .js --ext .gql --ext .graphql
```

### Example config for Apollo

```js
// In a file called .eslintrc.js
module.exports = {
  parser: "babel-eslint",
  rules: {
    "graphql/template-strings": ['error', {
      // Import default settings for your GraphQL client. Supported values:
      // 'apollo', 'relay', 'lokka', 'fraql', 'literal'
      env: 'apollo',

      // Import your schema JSON here
      schemaJson: require('./schema.json'),

      // OR provide absolute path to your schema JSON (but not if using `eslint --cache`!)
      // schemaJsonFilepath: path.resolve(__dirname, './schema.json'),

      // OR provide the schema in the Schema Language format
      // schemaString: printSchema(schema),

      // tagName is gql by default
    }]
  },
  plugins: [
    'graphql'
  ]
}
```

### Example config for Relay

```js
// In a file called .eslintrc.js
module.exports = {
  parser: "babel-eslint",
  rules: {
    "graphql/template-strings": ['error', {
      // Import default settings for your GraphQL client. Supported values:
      // 'apollo', 'relay', 'lokka', 'fraql', 'literal'
      env: 'relay',

      // Import your schema JSON here
      schemaJson: require('./schema.json'),

      // OR provide absolute path to your schema JSON (but not if using `eslint --cache`!)
      // schemaJsonFilepath: path.resolve(__dirname, './schema.json'),

      // OR provide the schema in the Schema Language format
      // schemaString: printSchema(schema),

      // tagName is set for you to Relay.QL
    }]
  },
  plugins: [
    'graphql'
  ]
}
```

### Example config for Lokka

```js
// In a file called .eslintrc.js
module.exports = {
  parser: "babel-eslint",
  rules: {
    "graphql/template-strings": ['error', {
      // Import default settings for your GraphQL client. Supported values:
      // 'apollo', 'relay', 'lokka', 'fraql', 'literal'
      env: 'lokka',

      // Import your schema JSON here
      schemaJson: require('./schema.json'),

      // OR provide absolute path to your schema JSON (but not if using `eslint --cache`!)
      // schemaJsonFilepath: path.resolve(__dirname, './schema.json'),

      // OR provide the schema in the Schema Language format
      // schemaString: printSchema(schema),

      // Optional, the name of the template tag, defaults to 'gql'
      tagName: 'gql'
    }]
  },
  plugins: [
    'graphql'
  ]
}
```

### Example config for FraQL

```js
// In a file called .eslintrc.js
module.exports = {
  parser: "babel-eslint",
  rules: {
    "graphql/template-strings": ['error', {
      // Import default settings for your GraphQL client. Supported values:
      // 'apollo', 'relay', 'lokka', 'fraql', 'literal'
      env: 'fraql',

      // Import your schema JSON here
      schemaJson: require('./schema.json'),

      // OR provide absolute path to your schema JSON
      // schemaJsonFilepath: path.resolve(__dirname, './schema.json'),

      // OR provide the schema in the Schema Language format
      // schemaString: printSchema(schema),

      // Optional, the name of the template tag, defaults to 'gql'
      tagName: 'gql'
    }]
  },
  plugins: [
    'graphql'
  ]
}
```

### Example config for literal graphql files

```js
// In a file called .eslintrc.js
module.exports = {
  parser: "babel-eslint",
  rules: {
    "graphql/template-strings": ['error', {
      // Import default settings for your GraphQL client. Supported values:
      // 'apollo', 'relay', 'lokka', 'fraql', 'literal'
      env: 'literal',

      // Import your schema JSON here
      schemaJson: require('./schema.json'),

      // OR provide absolute path to your schema JSON (but not if using `eslint --cache`!)
      // schemaJsonFilepath: path.resolve(__dirname, './schema.json'),

      // OR provide the schema in the Schema Language format
      // schemaString: printSchema(schema),

      // tagName is set automatically
    }]
  },
  plugins: [
    'graphql'
  ]
}
```

### Additional Schemas or Tags

This plugin can be used to validate against multiple schemas by identifying them with different tags. This is useful for applications interacting with multiple GraphQL systems. Additional schemas can simply be appended to the options list:

```js
module.exports = {
  parser: "babel-eslint",
  rules: {
    "graphql/template-strings": ['error', {
      env: 'apollo',
      tagName: 'FirstGQL',
      schemaJson: require('./schema-first.json')
    }, {
      env: 'relay',
      tagName: 'SecondGQL',
      schemaJson: require('./schema-second.json')
    }]
  },
  plugins: [
    'graphql'
  ]
}
```

### Example config when using [.graphqlconfig](https://github.com/graphcool/graphql-config)

If you have `.graphqlconfig` file in the root of your repo you can omit schema-related
properties (`schemaJson`, `schemaJsonFilepath` and `schemaString`) from rule config.

```js
// In a file called .eslintrc.js
module.exports = {
  parser: "babel-eslint",
  rules: {
    "graphql/template-strings": ['error', {
      // Import default settings for your GraphQL client. Supported values:
      // 'apollo', 'relay', 'lokka', 'fraql', 'literal'
      env: 'literal'
      // no need to specify schema here, it will be automatically determined using .graphqlconfig
    }]
  },
  plugins: [
    'graphql'
  ]
}
```

In case you use additional schemas, specify `projectName` from `.graphqlconfig` for each `tagName`:
```js
module.exports = {
  parser: "babel-eslint",
  rules: {
    "graphql/template-strings": ['error', {
      env: 'apollo',
      tagName: 'FirstGQL',
      projectName: 'FirstGQLProject'
    }, {
      env: 'relay',
      tagName: 'SecondGQL',
      projectName: 'SecondGQLProject'
    }]
  },
  plugins: [
    'graphql'
  ]
}
```

### Selecting Validation Rules

GraphQL validation rules can be configured in the eslint rule configuration using the `validators` option. The default selection depends on the `env` setting. If no `env` is specified, all rules are enabled by default.

The `validators` setting can be set either to a list of specific validator names or to the special value `"all"`.

```js
module.exports = {
  parser: "babel-eslint",
  rules: {
    "graphql/template-strings": ['error', {
      env: 'apollo',
      validators: 'all',
      tagName: 'FirstGQL',
      schemaJson: require('./schema-first.json')
    }, {
      validators: ['FieldsOnCorrectType'],
      tagName: 'SecondGQL',
      schemaJson: require('./schema-second.json')
    }]
  },
  plugins: [
    'graphql'
  ]
}
```

The full list of available validators is:
  - `ExecutableDefinitions`
  - `FieldsOnCorrectType`
  - `FragmentsOnCompositeTypes`
  - `KnownArgumentNames`
  - `KnownDirectives` (*disabled by default in `relay`*)
  - `KnownFragmentNames` (*disabled by default in all envs*)
  - `KnownTypeNames`
  - `LoneAnonymousOperation`
  - `NoFragmentCycles`
  - `NoUndefinedVariables` (*disabled by default in `relay`*)
  - `NoUnusedFragments` (*disabled by default in all envs*)
  - `NoUnusedVariables`
  - `OverlappingFieldsCanBeMerged`
  - `PossibleFragmentSpreads`
  - `ProvidedRequiredArguments` (*disabled by default in `relay`*)
  - `ScalarLeafs` (*disabled by default in `relay`*)
  - `SingleFieldSubscriptions`
  - `UniqueArgumentNames`
  - `UniqueDirectivesPerLocation`
  - `UniqueFragmentNames`
  - `UniqueInputFieldNames`
  - `UniqueOperationNames`
  - `UniqueVariableNames`
  - `ValuesOfCorrectType`
  - `VariablesAreInputTypes`
  - `VariablesDefaultValueAllowed`
  - `VariablesInAllowedPosition`

### Named Operations Validation Rule

The Named Operation rule validates that all operations are named. Naming operations is valuable for including in server-side logs and debugging.

**Pass**
```
query FetchUsername {
  viewer {
    name
  }
}
```

**Fail**
```
query {
  viewer {
    name
  }
}
```

The rule is defined as `graphql/named-operations`.

```js
// In a file called .eslintrc.js
module.exports = {
  parser: "babel-eslint",
  rules: {
    "graphql/template-strings": ['error', {
      env: 'apollo',
      schemaJson: require('./schema.json'),
    }],
    "graphql/named-operations": ['warn', {
      schemaJson: require('./schema.json'),
    }],
  },
  plugins: [
    'graphql'
  ]
}
```
### Required Fields Validation Rule

The Required Fields rule validates that any specified required field is part of the query, but only if that field is available in schema. This is useful to ensure that query results are cached properly in the client.

**Pass**
```
// 'uuid' required and present in the schema

schema {
  query {
    viewer {
      name
      uuid
    }
  }
}

query ViewerName {
  viewer {
    name
    uuid
  }
}
```

**Pass**
```
// 'uuid' usually required but not present in the schema here

schema {
  query {
    viewer {
      name
    }
  }
}

query ViewerName {
  viewer {
    name
  }
}
```

**Fail**
```
// 'uuid' required and present in the schema

schema {
  query {
    viewer {
      uuid
      name
    }
  }
}

query ViewerName {
  viewer {
    name
  }
}
```

The rule is defined as `graphql/required-fields` and requires the `requiredFields` option.

```js
// In a file called .eslintrc.js
module.exports = {
  rules: {
    'graphql/required-fields': [
      'error',
      {
        env: 'apollo',
        schemaJsonFilepath: path.resolve(__dirname, './schema.json'),
        requiredFields: ['uuid'],
      },
    ],
  },
  plugins: [
    'graphql'
  ]
}
```

### Capitalization of a first letter of a Type name

This rule enforces that first letter of types is capitalized

**Pass**
```
query {
  someUnion {
    ... on SomeType {
      someField
    }
  }
}
```

**Fail**
```
query {
  someUnion {
    ... on someType {
      someField
    }
  }
}
```

The rule is defined as `graphql/capitalized-type-name`.

```js
// In a file called .eslintrc.js
module.exports = {
  parser: "babel-eslint",
  rules: {
    "graphql/template-strings": ['error', {
      env: 'apollo',
      schemaJson: require('./schema.json'),
    }],
    "graphql/capitalized-type-name": ['warn', {
      schemaJson: require('./schema.json'),
    }],
  },
  plugins: [
    'graphql'
  ]
}
```

### No Deprecated Fields Validation Rule

The No Deprecated Fields rule validates that no deprecated fields are part of the query. This is useful to discover fields that have been marked as deprecated and shouldn't be used.

**Fail**
```
// 'id' requested and marked as deprecated in the schema

schema {
  query {
    viewer {
      id: Int @deprecated(reason: "Use the 'uuid' field instead")
      uuid: String
    }
  }
}

query ViewerName {
  viewer {
    id
  }
}
```

The rule is defined as `graphql/no-deprecated-fields`.

```js
// In a file called .eslintrc.js
module.exports = {
  rules: {
    'graphql/no-deprecated-fields': [
      'error',
      {
        env: 'relay',
        schemaJson: require('./schema.json')
      },
    ],
  },
  plugins: [
    'graphql'
  ]
}
```


================================================
FILE: package.json
================================================
{
  "name": "eslint-plugin-graphql",
  "version": "4.0.0",
  "description": "GraphQL ESLint plugin.",
  "author": "Sashko Stubailo",
  "main": "lib/index.js",
  "scripts": {
    "test": "tav --ci && mocha test/index.js",
    "prepare": "babel ./src --ignore test --out-dir ./lib",
    "pretest": "babel-node test/updateSchemaJson.js",
    "tav": "tav",
    "lint": "eslint 'src/**/*.js' 'test/**/*.js'"
  },
  "homepage": "https://github.com/apollostack/eslint-plugin-graphql",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/apollostack/eslint-plugin-graphql.git"
  },
  "devDependencies": {
    "@babel/cli": "7.10.1",
    "@babel/core": "7.10.1",
    "@babel/node": "7.10.1",
    "@babel/plugin-transform-runtime": "7.10.1",
    "@babel/preset-env": "7.10.1",
    "@babel/register": "7.10.1",
    "babel-eslint": "10.1.0",
    "eslint": "6.8.0",
    "graphql": "15.0.0",
    "mocha": "7.2.0",
    "pretty-quick": "2.0.1",
    "test-all-versions": "4.1.1"
  },
  "husky": {
    "hooks": {
      "pre-commit": "pretty-quick --staged"
    }
  },
  "engines": {
    "node": ">=10.0"
  },
  "browserslist": "node 10",
  "license": "MIT",
  "dependencies": {
    "@babel/runtime": "^7.10.0",
    "graphql-config": "^3.0.2",
    "lodash.flatten": "^4.4.0",
    "lodash.without": "^4.4.0"
  },
  "peerDependencies": {
    "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
  }
}


================================================
FILE: renovate.json
================================================
{
  "extends": [
    "config:base"
  ]
}


================================================
FILE: src/constants.js
================================================
export const internalTag = "ESLintPluginGraphQLFile";


================================================
FILE: src/createRule.js
================================================
import { parse, validate } from "graphql";

import { internalTag } from "./constants";

function strWithLen(len) {
  // from http://stackoverflow.com/questions/14343844/create-a-string-of-variable-length-filled-with-a-repeated-character
  return new Array(len + 1).join("x");
}

function replaceExpressions(node, context, env) {
  const chunks = [];

  node.quasis.forEach((element, i) => {
    const chunk = element.value.cooked;
    const value = node.expressions[i];

    chunks.push(chunk);

    if (!env || env === "apollo") {
      // In Apollo, interpolation is only valid outside top-level structures like `query` or `mutation`.
      // We'll check to make sure there's an equivalent set of opening and closing brackets, otherwise
      // we're attempting to do an invalid interpolation.
      if (chunk.split("{").length - 1 !== chunk.split("}").length - 1) {
        context.report({
          node: value,
          message:
            "Invalid interpolation - fragment interpolation must occur outside of the brackets."
        });
        throw new Error("Invalid interpolation");
      }
    }

    if (!element.tail) {
      // Preserve location of errors by replacing with exactly the same length
      const nameLength = value.end - value.start;

      if (env === "relay" && /:\s*$/.test(chunk)) {
        // The chunk before this one had a colon at the end, so this
        // is a variable

        // Add 2 for brackets in the interpolation
        const placeholder = strWithLen(nameLength + 2);
        chunks.push("$" + placeholder);
      } else if (env === "lokka" && /\.\.\.\s*$/.test(chunk)) {
        // This is Lokka-style fragment interpolation where you actually type the '...' yourself
        const placeholder = strWithLen(nameLength + 3);
        chunks.push(placeholder);
      } else if (env === "relay") {
        // This is Relay-style fragment interpolation where you don't type '...'
        // Ellipsis cancels out extra characters
        const placeholder = strWithLen(nameLength);
        chunks.push("..." + placeholder);
      } else if (!env || env === "apollo") {
        // In Apollo, fragment interpolation is only valid outside of brackets
        // Since we don't know what we'd interpolate here (that occurs at runtime),
        // we're not going to do anything with this interpolation.
      } else if (env === "fraql") {
        if (chunk.lastIndexOf("{") > chunk.lastIndexOf("}")) {
          chunks.push("__typename");
        }
      } else {
        // Invalid interpolation
        context.report({
          node: value,
          message: "Invalid interpolation - not a valid fragment or variable."
        });
        throw new Error("Invalid interpolation");
      }
    }
  });

  return chunks.join("");
}

function locFrom(node, error) {
  if (!error.locations || !error.locations.length) {
    return;
  }
  const location = error.locations[0];

  let line;
  let column;
  if (location.line === 1 && node.tag.name !== internalTag) {
    line = node.loc.start.line;
    column = node.tag.loc.end.column + location.column;
  } else {
    line = node.loc.start.line + location.line - 1;
    column = location.column - 1;
  }

  return {
    line,
    column
  };
}

function handleTemplateTag(node, context, schema, env, validators) {
  let text;
  try {
    text = replaceExpressions(node.quasi, context, env);
  } catch (e) {
    if (e.message !== "Invalid interpolation") {
      console.log(e); // eslint-disable-line no-console
    }
    return;
  }

  // Re-implement syntax sugar for fragment names, which is technically not valid
  // graphql
  if (
    (env === "lokka" || env === "relay" || env === "fraql") &&
    /fragment\s+on/.test(text)
  ) {
    text = text.replace("fragment", `fragment _`);
  }

  let ast;

  try {
    ast = parse(text);
  } catch (error) {
    context.report({
      node,
      message: error.message.split("\n")[0],
      loc: locFrom(node, error)
    });
    return;
  }

  const validationErrors = schema ? validate(schema, ast, validators) : [];
  if (validationErrors && validationErrors.length > 0) {
    context.report({
      node,
      message: validationErrors[0].message,
      loc: locFrom(node, validationErrors[0])
    });
    return;
  }
}

function templateExpressionMatchesTag(tagName, node) {
  const tagNameSegments = tagName.split(".").length;
  if (tagNameSegments === 1) {
    // Check for single identifier, like 'gql'
    if (node.tag.type !== "Identifier" || node.tag.name !== tagName) {
      return false;
    }
  } else if (tagNameSegments === 2) {
    // Check for dotted identifier, like 'Relay.QL'
    if (
      node.tag.type !== "MemberExpression" ||
      node.tag.object.name + "." + node.tag.property.name !== tagName
    ) {
      return false;
    }
  } else {
    // We don't currently support 3 segments so ignore
    return false;
  }
  return true;
}

export function createRule(context, optionParser) {
  const tagNames = new Set();
  const tagRules = [];
  const options = context.options.length === 0 ? [{}] : context.options;
  for (const optionGroup of options) {
    const { schema, env, tagName, validators } = optionParser(optionGroup);
    const boundValidators = validators.map(v => ctx => v(ctx, optionGroup));
    if (tagNames.has(tagName)) {
      throw new Error("Multiple options for GraphQL tag " + tagName);
    }
    tagNames.add(tagName);
    tagRules.push({ schema, env, tagName, validators: boundValidators });
  }

  return {
    TaggedTemplateExpression(node) {
      for (const { schema, env, tagName, validators } of tagRules) {
        if (templateExpressionMatchesTag(tagName, node)) {
          return handleTemplateTag(node, context, schema, env, validators);
        }
      }
    }
  };
}


================================================
FILE: src/customGraphQLValidationRules.js
================================================
import { GraphQLError, getNamedType } from "graphql";

export function OperationsMustHaveNames(context) {
  return {
    OperationDefinition(node) {
      if (!node.name) {
        context.reportError(
          new GraphQLError("All operations must be named", [node])
        );
      }
    }
  };
}

function getFieldWasRequestedOnNode(node, field) {
  return node.selectionSet.selections.some(n => {
    return n.kind === "Field" && n.name.value === field;
  });
}

function fieldAvailableOnType(type, field) {
  if (!type) {
    return false;
  }

  return (
    (type._fields && type._fields[field]) ||
    (type.ofType && fieldAvailableOnType(type.ofType, field))
  );
}

export function RequiredFields(context, options) {
  const { requiredFields } = options;

  return {
    FragmentDefinition(node) {
      requiredFields.forEach(field => {
        const type = context.getType();

        if (fieldAvailableOnType(type, field)) {
          const fieldWasRequested = getFieldWasRequestedOnNode(node, field);
          if (!fieldWasRequested) {
            context.reportError(
              new GraphQLError(
                `'${field}' field required on 'fragment ${node.name.value} on ${node.typeCondition.name.value}'`,
                [node]
              )
            );
          }
        }
      });
    },

    // Every inline fragment must have the required field specified inside
    // itself or in some parent selection set.
    InlineFragment(node, key, parent, path, ancestors) {
      requiredFields.forEach(field => {
        const type = context.getType();

        if (fieldAvailableOnType(type, field)) {
          // First, check the selection set on this inline fragment
          if (node.selectionSet && getFieldWasRequestedOnNode(node, field)) {
            return true;
          }

          const ancestorClone = [...ancestors];

          let nearestFieldOrExecutableDefinition;
          let nextAncestor;

          // Now, walk up the ancestors, until you see a field or executable definition.
          while (!nearestFieldOrExecutableDefinition) {
            nextAncestor = ancestorClone.pop();

            if (
              nextAncestor.selectionSet &&
              getFieldWasRequestedOnNode(nextAncestor, field)
            ) {
              return true;
            }

            if (
              nextAncestor.kind === "Field" ||
              nextAncestor.kind === "FragmentDefinition" ||
              nextAncestor.kind === "OperationDefiniton"
            ) {
              nearestFieldOrExecutableDefinition = nextAncestor;
            }
          }

          // If we never found a field or executable definition, the query is malformed
          if (!nearestFieldOrExecutableDefinition) {
            throw new Error(
              "Inline fragment found inside document without a parent field, fragment definition, or operation definition."
            );
          }

          // We found a field or executable definition, but we never saw the field we were looking for in
          // the intermediate selection sets.
          context.reportError(
            new GraphQLError(
              `'${field}' field required on '... on ${node.typeCondition.name.value}'`,
              [node]
            )
          );
        }
      });
    },

    // Every field that can have the field directly on it, should. It's not
    // enough to have some child fragment to include the field, since we don't
    // know if that fragment covers all of the possible type options.
    Field(node) {
      const def = context.getFieldDef();
      if (!def) {
        return;
      }

      requiredFields.forEach(field => {
        if (fieldAvailableOnType(def.type, field)) {
          const fieldWasRequested = getFieldWasRequestedOnNode(node, field);
          if (!fieldWasRequested) {
            context.reportError(
              new GraphQLError(
                `'${field}' field required on '${node.name.value}'`,
                [node]
              )
            );
          }
        }
      });
    }
  };
}

export function typeNamesShouldBeCapitalized(context) {
  return {
    NamedType(node) {
      const typeName = node.name.value;
      if (typeName[0] == typeName[0].toLowerCase()) {
        context.reportError(
          new GraphQLError(
            "All type names should start with a capital letter",
            [node]
          )
        );
      }
    }
  };
}

// Mostly taken from https://github.com/graphql/graphql-js/blob/063148de039b02670a760b8d3dfaf2a04a467169/src/utilities/findDeprecatedUsages.js
// See explanation in [#93](https://github.com/apollographql/eslint-plugin-graphql/pull/93)
export function noDeprecatedFields(context) {
  return {
    Field(node) {
      const fieldDef = context.getFieldDef();
      if (fieldDef && fieldDef.isDeprecated) {
        const parentType = context.getParentType();
        if (parentType) {
          const reason = fieldDef.deprecationReason;
          context.reportError(
            new GraphQLError(
              `The field ${parentType.name}.${fieldDef.name} is deprecated.` +
                (reason ? " " + reason : ""),
              [node]
            )
          );
        }
      }
    },
    EnumValue(node) {
      // context is of type ValidationContext which doesn't export getEnumValue.
      // Bypass the public API to grab that information directly from _typeInfo.
      const enumVal = context._typeInfo.getEnumValue();
      if (enumVal && enumVal.isDeprecated) {
        const type = getNamedType(context.getInputType());
        if (!type) {
          return;
        }

        const reason = enumVal.deprecationReason;
        context.reportError(
          new GraphQLError(
            `The enum value ${type.name}.${enumVal.name} is deprecated.` +
              (reason ? " " + reason : ""),
            [node]
          )
        );
      }
    }
  };
}


================================================
FILE: src/index.js
================================================
import fs from "fs";
import path from "path";
import {
  buildClientSchema,
  buildSchema,
  specifiedRules as allGraphQLValidators
} from "graphql";

import flatten from "lodash.flatten";
import without from "lodash.without";

import { loadConfigSync, ConfigNotFoundError, ProjectNotFoundError } from "graphql-config";

import * as customRules from "./customGraphQLValidationRules";
import { internalTag } from "./constants";
import { createRule } from "./createRule";

const allGraphQLValidatorNames = allGraphQLValidators.map(rule => rule.name);

// Map of env name to list of rule names.
const envGraphQLValidatorNames = {
  apollo: without(
    allGraphQLValidatorNames,
    "KnownFragmentNames",
    "NoUnusedFragments",
    // `graphql`@15
    "KnownFragmentNamesRule",
    "NoUnusedFragmentsRule"
  ),
  lokka: without(
    allGraphQLValidatorNames,
    "KnownFragmentNames",
    "NoUnusedFragments",
    // `graphql`@15
    "KnownFragmentNamesRule",
    "NoUnusedFragmentsRule"
  ),
  fraql: without(
    allGraphQLValidatorNames,
    "KnownFragmentNames",
    "NoUnusedFragments",
    // `graphql`@15
    "KnownFragmentNamesRule",
    "NoUnusedFragmentsRule"
  ),
  relay: without(
    allGraphQLValidatorNames,
    "KnownDirectives",
    "KnownFragmentNames",
    "NoUndefinedVariables",
    "NoUnusedFragments",
    // `graphql`@15
    "KnownDirectivesRule",
    "KnownFragmentNamesRule",
    "NoUndefinedVariablesRule",
    "NoUnusedFragmentsRule",
    // `graphql` < 14
    "ProvidedNonNullArguments",
    // `graphql`@14
    "ProvidedRequiredArguments",
    "ScalarLeafs",
    // `graphql`@15
    "ProvidedRequiredArgumentsRule",
    "ScalarLeafsRule"
  ),
  literal: without(
    allGraphQLValidatorNames,
    "KnownFragmentNames",
    "NoUnusedFragments",
    // `graphql`@15
    "KnownFragmentNamesRule",
    "NoUnusedFragmentsRule"
  )
};

const gqlFiles = ["gql", "graphql"];

const defaultRuleProperties = {
  env: {
    enum: ["lokka", "fraql", "relay", "apollo", "literal"]
  },
  schemaJson: {
    type: "object"
  },
  schemaJsonFilepath: {
    type: "string"
  },
  schemaString: {
    type: "string"
  },
  tagName: {
    type: "string",
    pattern: "^[$_a-zA-Z$_][a-zA-Z0-9$_]+(\\.[a-zA-Z0-9$_]+)?$"
  },
  projectName: {
    type: "string"
  }
};

// schemaJson, schemaJsonFilepath, schemaString and projectName are mutually exclusive:
const schemaPropsExclusiveness = {
  oneOf: [
    {
      required: ["schemaJson"],
      not: { required: ["schemaString", "schemaJsonFilepath", "projectName"] }
    },
    {
      required: ["schemaJsonFilepath"],
      not: { required: ["schemaJson", "schemaString", "projectName"] }
    },
    {
      required: ["schemaString"],
      not: { required: ["schemaJson", "schemaJsonFilepath", "projectName"] }
    },
    {
      not: {
        anyOf: [
          { required: ["schemaString"] },
          { required: ["schemaJson"] },
          { required: ["schemaJsonFilepath"] }
        ]
      }
    }
  ]
};

export const rules = {
  "template-strings": {
    meta: {
      schema: {
        type: "array",
        items: {
          additionalProperties: false,
          properties: {
            ...defaultRuleProperties,
            validators: {
              oneOf: [
                {
                  type: "array",
                  uniqueItems: true,
                  items: {
                    enum: allGraphQLValidatorNames
                  }
                },
                {
                  enum: ["all"]
                }
              ]
            }
          },
          ...schemaPropsExclusiveness
        }
      }
    },
    create: context =>
      createRule(context, optionGroup => parseOptions(optionGroup, context))
  },
  "named-operations": {
    meta: {
      schema: {
        type: "array",
        items: {
          additionalProperties: false,
          properties: { ...defaultRuleProperties },
          ...schemaPropsExclusiveness
        }
      }
    },
    create: context => {
      return createRule(context, optionGroup =>
        parseOptions(
          {
            validators: ["OperationsMustHaveNames"],
            ...optionGroup
          },
          context
        )
      );
    }
  },
  "required-fields": {
    meta: {
      schema: {
        type: "array",
        minItems: 1,
        items: {
          additionalProperties: false,
          properties: {
            ...defaultRuleProperties,
            requiredFields: {
              type: "array",
              items: {
                type: "string"
              }
            }
          },
          required: ["requiredFields"],
          ...schemaPropsExclusiveness
        }
      }
    },
    create: context => {
      return createRule(context, optionGroup =>
        parseOptions(
          {
            validators: ["RequiredFields"],
            options: { requiredFields: optionGroup.requiredFields },
            ...optionGroup
          },
          context
        )
      );
    }
  },
  "capitalized-type-name": {
    meta: {
      schema: {
        type: "array",
        items: {
          additionalProperties: false,
          properties: { ...defaultRuleProperties },
          ...schemaPropsExclusiveness
        }
      }
    },
    create: context => {
      return createRule(context, optionGroup =>
        parseOptions(
          {
            validators: ["typeNamesShouldBeCapitalized"],
            ...optionGroup
          },
          context
        )
      );
    }
  },
  "no-deprecated-fields": {
    meta: {
      schema: {
        type: "array",
        items: {
          additionalProperties: false,
          properties: { ...defaultRuleProperties },
          ...schemaPropsExclusiveness
        }
      }
    },
    create: context => {
      return createRule(context, optionGroup =>
        parseOptions(
          {
            validators: ["noDeprecatedFields"],
            ...optionGroup
          },
          context
        )
      );
    }
  }
};

const schemaCache = {};
const projectCache = {};

function parseOptions(optionGroup, context) {
  const {
    schemaJson, // Schema via JSON object
    schemaJsonFilepath, // Or Schema via absolute filepath
    schemaString, // Or Schema as string,
    env,
    projectName,
    tagName: tagNameOption,
    validators: validatorNamesOption
  } = optionGroup;

  const cacheHit = schemaCache[JSON.stringify(optionGroup)];
  if (cacheHit && env !== "literal") {
    return cacheHit;
  }

  // Validate and unpack schema
  let schema;
  if (schemaJson) {
    schema = initSchema(schemaJson);
  } else if (schemaJsonFilepath) {
    schema = initSchemaFromFile(schemaJsonFilepath);
  } else if (schemaString) {
    schema = initSchemaFromString(schemaString);
  } else {
    try {
      const config = loadConfigSync({
        rootDir: path.resolve(
          process.cwd(),
          path.dirname(context.getFilename())
        )
      });
      let projectConfig;
      if (projectName) {
        projectConfig = config.getProject(projectName);
        if (!projectConfig) {
          throw new Error(
            `Project with name "${projectName}" not found in ${config.filepath}.`
          );
        }
      } else {
        try {
          projectConfig = config.getProjectForFile(context.getFilename());
        } catch (e) {
          if (!(e instanceof ProjectNotFoundError)) {
            throw e;
          }
        }
      }
      if (projectConfig) {
        const key = `${config.filepath}[${projectConfig.name}]`;
        schema = projectCache[key];
        if (!schema) {
          schema = projectConfig.getSchemaSync();
          projectCache[key] = schema;
        }
      }
      if (cacheHit) {
        return { ...cacheHit, schema };
      }
    } catch (e) {
      if (e instanceof ConfigNotFoundError) {
        throw new Error(
          "Must provide GraphQL Config file or pass in `schemaJson` option " +
            "with schema object or `schemaJsonFilepath` with absolute path to the json file."
        );
      }
      throw e;
    }
  }

  // Validate env
  if (
    env &&
    env !== "lokka" &&
    env !== "fraql" &&
    env !== "relay" &&
    env !== "apollo" &&
    env !== "literal"
  ) {
    throw new Error(
      "Invalid option for env, only `apollo`, `lokka`, `fraql`, `relay`, and `literal` supported."
    );
  }

  // Validate tagName and set default
  let tagName;
  if (tagNameOption) {
    tagName = tagNameOption;
  } else if (env === "relay") {
    tagName = "Relay.QL";
  } else if (env === "literal") {
    tagName = internalTag;
  } else {
    tagName = "gql";
  }

  // The validator list may be:
  //    The string 'all' to use all rules.
  //    An array of rule names.
  //    null/undefined to use the default rule set of the environment, or all rules.
  let validatorNames;
  if (validatorNamesOption === "all") {
    validatorNames = allGraphQLValidatorNames;
  } else if (validatorNamesOption) {
    validatorNames = validatorNamesOption;
  } else {
    validatorNames = envGraphQLValidatorNames[env] || allGraphQLValidatorNames;
  }

  const validators = validatorNames.map(name => {
    if (name in customRules) {
      return customRules[name];
    } else {
      return require(`graphql/validation/rules/${name}`)[name];
    }
  });
  const results = { schema, env, tagName, validators };
  schemaCache[JSON.stringify(optionGroup)] = results;
  return results;
}

function initSchema(json) {
  const unpackedSchemaJson = json.data ? json.data : json;
  if (!unpackedSchemaJson.__schema) {
    throw new Error("Please pass a valid GraphQL introspection query result.");
  }
  return buildClientSchema(unpackedSchemaJson);
}

function initSchemaFromFile(jsonFile) {
  return initSchema(JSON.parse(fs.readFileSync(jsonFile, "utf8")));
}

function initSchemaFromString(source) {
  return buildSchema(source);
}

const gqlProcessor = {
  preprocess: function(text) {
    // Wrap the text in backticks and prepend the internal tag. First the text
    // must be escaped, because of the three sequences that have special
    // meaning in JavaScript template literals, and could change the meaning of
    // the text or cause syntax errors.
    // https://tc39.github.io/ecma262/#prod-TemplateCharacter
    //
    // - "`" would end the template literal.
    // - "\" would start an escape sequence.
    // - "${" would start an interpolation.
    const escaped = text.replace(/[`\\]|\$\{/g, "\\$&");
    return [`${internalTag}\`${escaped}\``];
  },
  postprocess: function(messages) {
    // only report graphql-errors
    return flatten(messages).filter(message =>
      Object.keys(rules).map(key => `graphql/${key}`).includes(message.ruleId)
    );
  }
};

export const processors = gqlFiles.reduce(
  (result, value) => {
    return { ...result, [`.${value}`]: gqlProcessor };
  },
  {}
);

export default {
  rules,
  processors
};


================================================
FILE: test/__fixtures__/required-fields-invalid-array.graphql
================================================
query { stories { comments { text } } }


================================================
FILE: test/__fixtures__/required-fields-invalid-no-id.graphql
================================================
query {
  greetings {
    hello
  }
}


================================================
FILE: test/__fixtures__/required-fields-valid-array.graphql
================================================
query { stories { id comments { text } } }


================================================
FILE: test/__fixtures__/required-fields-valid-id.graphql
================================================
query { greetings { id, hello, hi } }


================================================
FILE: test/__fixtures__/required-fields-valid-no-id.graphql
================================================
query { allFilms { films { title } } }


================================================
FILE: test/customTagName.js
================================================
import schemaJson from './schema.json';
import { isAtLeastGraphQL15 } from './helpers';

import {
  rule,
  ruleTester,
  parserOptions
} from './helpers';

const options = [
  { schemaJson, tagName: 'myGraphQLTag' },
];

ruleTester.run('custom tag name', rule, {
  valid: [
    {
      options,
      parserOptions,
      code: 'const x = myGraphQLTag`{ number }`',
    },
  ],

  invalid: [
    {
      options,
      parserOptions,
      code: 'const x = myGraphQLTag``',
      errors: [{
        message: isAtLeastGraphQL15 ? 'Syntax Error: Unexpected <EOF>.' : 'Syntax Error: Unexpected <EOF>',
        type: 'TaggedTemplateExpression'
      }]
    },
    {
      options,
      parserOptions,
      code: 'const x = myGraphQLTag`{ nonExistentQuery }`',
      errors: [{
        message: 'Cannot query field "nonExistentQuery" on type "Query".',
        type: 'TaggedTemplateExpression'
      }]
    },
    {
      options,
      parserOptions,
      code: 'const x = myGraphQLTag`{ ${x} }`',
      errors: [{
        type: 'Identifier',
        message: 'Invalid interpolation - fragment interpolation must occur outside of the brackets.'
      }]
    },
  ]
});


================================================
FILE: test/default.js
================================================
import { isAtLeastGraphQL15 } from './helpers';
import schemaJson from './schema.json';

import {
  rule,
  ruleTester,
  parserOptions
} from './helpers';

const options = [
  { schemaJson },
];

ruleTester.run('default options', rule, {
  valid: [
    {
      options,
      parserOptions,
      code: 'const x = gql`{ number }`',
    },
    {
      options,
      parserOptions,
      code: 'const x = segmented.TagName`height: 12px;`'
    },
    {
      options,
      parserOptions,
      code: 'const x = segmented.gql`height: 12px;`'
    },
    {
      options,
      parserOptions,
      code: 'const x = gql.segmented`height: 12px;`'
    },
    {
      options,
      parserOptions,
      code: 'const x = gql`{ number } ${x}`',
    },
  ],

  invalid: [
    {
      options,
      parserOptions,
      code: 'const x = gql``',
      errors: [{
        message: isAtLeastGraphQL15 ? 'Syntax Error: Unexpected <EOF>.' : 'Syntax Error: Unexpected <EOF>',
        type: 'TaggedTemplateExpression'
      }]
    },
    {
      options,
      parserOptions,
      code: 'const x = gql`\n  query {\n    nonExistentQuery\n  }\n`',
      errors: [{
        message: 'Cannot query field "nonExistentQuery" on type "Query".',
        type: 'TaggedTemplateExpression',
        line: 3,
        column: 5
      }]
    },
    {
      options,
      parserOptions,
      code: 'const x = gql`{ ${x} }`',
      errors: [{
        message: 'Invalid interpolation - fragment interpolation must occur outside of the brackets.',
        type: 'Identifier',
        line: 1,
        column: 19
      }]
    },
  ]
});


================================================
FILE: test/env/apollo.js
================================================
import { isAtLeastGraphQL15 } from '../helpers';
import schemaJson from '../schema.json';

import {
  rule,
  ruleTester,
  parserOptions
} from '../helpers';

const options = [
  { schemaJson, env: 'apollo' },
];

ruleTester.run('apollo', rule, {
  valid: [
    {
      options,
      parserOptions,
      code: 'const x = gql`{ number } ${x}`',
    },
  ],

  invalid: [
    {
      options,
      parserOptions,
      code: 'const x = gql`query { ${x} }`',
      errors: [{
        message: 'Invalid interpolation - fragment interpolation must occur outside of the brackets.',
        type: 'Identifier'
      }]
    },
    {
      options,
      parserOptions,
      code: 'const x = gql`query }{ ${x}`',
      errors: [{
        message: isAtLeastGraphQL15 ? 'Syntax Error: Expected "{", found "}".' : 'Syntax Error: Expected {, found }',
        type: 'TaggedTemplateExpression'
      }]
    }
  ],
})


================================================
FILE: test/env/fraql.js
================================================
import schemaJson from '../schema.json';

import {
  rule,
  ruleTester,
  parserOptions
} from '../helpers';

const options = [
  { schemaJson, env: 'fraql' },
];

ruleTester.run('fraql', rule, {
  valid: [
    {
      options,
      parserOptions,
      code: 'const x = gql`{ number } ${x}`',
    },
    {
      options,
      parserOptions,
      code: 'const x = gql`query { ${x} }`',
    },
    {
      options,
      parserOptions,
      code: `const x = gql\`
        fragment _ on Film {
          title
          director
          releaseDate
          ... on Film {
            title
            \${xxx}
          }
          ... on Film {
            \${xxx}
          }
        }
      \`
      `,
    },
  ],

  invalid: [],
})


================================================
FILE: test/env/index.js
================================================
import './apollo';
import './lokka';
import './fraql';
import './relay';


================================================
FILE: test/env/lokka.js
================================================
import { isAtLeastGraphQL15 } from '../helpers';
import schemaJson from '../schema.json';

import {
  rule,
  ruleTester,
  parserOptions
} from '../helpers';

const options = [
  { schemaJson, env: 'lokka' },
];

ruleTester.run('lokka', rule, {
  valid: [
    `
      client.query(gql\`
          {
            allFilms {
              films {
                title
              }
            }
          }
      \`).then(result => {
          console.log(result.allFilms);
      });
    `,
    `
      const filmInfo = client.createFragment(gql\`
        fragment on Film {
          title,
          director,
          releaseDate
        }
      \`);
    `,
    `
      client.query(gql\`
        {
          allFilms {
            films {
              ...\${filmInfo}
            }
          }
        }
      \`).then(result => {
        console.log(result.allFilms.films);
      });
    `,
    // Not possible to validate lokka mutations because you can't put the 'mutation' keyword in
    // there
    // `
    //   client.mutate(gql\`{
    //       newFilm: createMovie(
    //           title: "Star Wars: The Force Awakens",
    //           director: "J.J. Abrams",
    //           producers: [
    //               "J.J. Abrams", "Bryan Burk", "Kathleen Kennedy"
    //           ],
    //           releaseDate: "December 14, 2015"
    //       ) {
    //           ...\${filmInfo}
    //       }
    //   }\`).then(response => {
    //       console.log(response.newFilm);
    //   });
    // `,
    `
      const query = gql\`
        query sumNow($a: Int!, $b: Int!) {
          sum(a: $a, b: $b)
        }
      \`;
    `,
  ].map((code) => ({ options, parserOptions, code })),

  invalid: [
    {
      options,
      parserOptions,
      code: `
        client.query(gql\`
            {
              allFilmsx {
                films {
                  title
                }
              }
            }
        \`).then(result => {
            console.log(result.allFilms);
        });
      `,
      errors: [{
        message: 'Cannot query field "allFilmsx" on type "Query". Did you mean "allFilms"?',
        type: 'TaggedTemplateExpression',
        line: 4,
        column: 15
      }]
    },
    {
      options,
      parserOptions,
      code: `
        const filmInfo = client.createFragment(gql\`
          fragment on Film {
            title,
            director(wrongArg: 7),
            releaseDate
          }
        \`);
      `,
      errors: [{
        message: isAtLeastGraphQL15 ?
          'Unknown argument "wrongArg" on field "Film.director".' :
          'Unknown argument "wrongArg" on field "director" of type "Film".',
        type: 'TaggedTemplateExpression',
        line: 5,
        column: 22
      }]
    },
    {
      options,
      parserOptions,
      code: `
        client.query(gql\`
          {
            allFilms {
              films {
                ...\${filmInfo}
                unknownField
              }
            }
          }
        \`).then(result => {
          console.log(result.allFilms.films);
        });
      `,
      errors: [{
        message: 'Cannot query field "unknownField" on type "Film".',
        type: 'TaggedTemplateExpression',
        line: 7,
        column: 17
      }]
    },
    {
      options,
      parserOptions,
      code: `
        client.query(gql\`
          {
            allFilms {
              films {
                \${filmInfo}
              }
            }
          }
        \`).then(result => {
          console.log(result.allFilms.films);
        });
      `,
      errors: [{
        message: 'Invalid interpolation - not a valid fragment or variable.',
        type: 'Identifier',
        line: 6,
        column: 19
      }]
    },
  ]
});


================================================
FILE: test/env/relay.js
================================================
import schemaJson from '../schema.json';

import {
  rule,
  ruleTester,
} from '../helpers';

const options = [
  {
    schemaJson,
    env: 'relay',
  },
];

// Need this to support statics
const parser = require.resolve('babel-eslint');

ruleTester.run('relay', rule, {
  valid: [
    `
      @relay({
        fragments: {
          greetings: () => Relay.QL\`
            fragment on Greetings {
              hello,
              hi,
            }
          \`,
        }
      })
      class HelloApp extends React.Component {}
    `,
    `
      const StyledComponent = css\`height: 12px;\`
    `,
    `
      HelloApp = Relay.createContainer(HelloApp, {
        fragments: {
          greetings: () => Relay.QL\`
            fragment on Greetings {
              hello,
            }
          \`,
        }
      });
    `,
    `
      class HelloRoute extends Relay.Route {
        static routeName = 'Hello';  // A unique name
        static queries = {
          greetings: (Component) => Relay.QL\`
            query GreetingsQuery {
              greetings {
                \${Component.getFragment('greetings')},
              },
            }
          \`,
        };
      }
    `,
    `
      class CreateCommentMutation extends Relay.Mutation {
        static fragments = {
          story: () => Relay.QL\`
            fragment on Story { id }
          \`,
        };
        getMutation() {
          return Relay.QL\`
            mutation { createComment }
          \`;
        }
        getFatQuery() {
          return Relay.QL\`
            fragment on CreateCommentPayload {
              story { comments },
            }
          \`;
        }
        getConfigs() {
          return [{
            type: 'FIELDS_CHANGE',
            fieldIDs: { story: this.props.story.id },
          }];
        }
        getVariables() {
          return { text: this.props.text };
        }
      }
    `,
    `
      Relay.QL\`fragment on CreateEventPayload @relay(pattern: true) {
        viewer {
          events
        }
        user {
          events
        }
      }\`
    `
  ].map((code) => ({ options, parser, code })),

  invalid: [
    {
      options,
      parser,
      code: `
        @relay({
          fragments: {
            greetings: () => Relay.QL\`
              fragment on Greetings {
                hellox,
              }
            \`,
          }
        })
        class HelloApp extends React.Component {}
      `,
      errors: [{
        message: 'Cannot query field "hellox" on type "Greetings". Did you mean "hello"?',
        type: 'TaggedTemplateExpression',
        line: 6,
        column: 17
      }]
    },
    // Example from issue report:
    // https://github.com/apollostack/eslint-plugin-graphql/issues/12#issuecomment-215445880
    {
      options,
      parser,
      code: `
        import React, { Component, View } from 'react-native';
        import Relay from 'react-relay';

        @relay({
          fragments: {
            user: () => Relay.QL\`
              fragment on PublicUser {
                fullName
                nonExistentField
              }
            \`
          }
        })
        class Example extends Component {
          render() {
            return <View/>;
          }
        }

        class AnotherExample extends Component {
          render() {
            return <View/>;
          }
        }

        Relay.createContainer(
          AnotherExample,
          {
            fragments: {
              user: () => Relay.QL\`
                fragment on PublicUser {
                  fullName
                  nonExistentField
                }
              \`
            }
          }
        );
      `,
      errors: [
        {
          message: 'Cannot query field "nonExistentField" on type "PublicUser".',
          line: 10,
          column: 17,
        },
        {
          message: 'Cannot query field "nonExistentField" on type "PublicUser".',
          line: 34,
          column: 19,
        },
      ],
    },
  ],
});


================================================
FILE: test/graphqlconfig/index.js
================================================
import { rules } from '../../src';
import { RuleTester } from 'eslint';

const rule = rules['template-strings'];

const parserOptions = {
  'ecmaVersion': 6,
  'sourceType': 'module',
};

const ruleTester = new RuleTester({parserOptions});

describe('graphqlconfig support', () => {
  describe('simple', () => {
    const options = [
      { tagName: 'gql' }
    ];

    beforeEach(() => {
      process.chdir('test/graphqlconfig/simple');
    })

    afterEach(() => {
      process.chdir('../../..');
    })

    ruleTester.run('validation works using schema from .graphqlconfig', rule, {
      valid: [
        {
          options,
          code: 'const x = gql`{ number, sum(a: 1, b: 1) }`;',
        },
      ],
      invalid: [
        {
          options,
          code: 'const y = gql`{ hero(episode: NEWHOPE) { id, name } }`;',
          errors: [{
            message: 'Cannot query field "hero" on type "Query".',
            type: 'TaggedTemplateExpression',
          }],
        },
      ],
    });
  });

  describe('multiproject', () => {
    const options = [
      { projectName: 'gql', tagName: 'gql' },
      { projectName: 'swapi', tagName: 'swapi' },
    ];

    beforeEach(() => {
      process.chdir('test/graphqlconfig/multiproject');
    })

    afterEach(() => {
      process.chdir('../../..');
    })

    ruleTester.run('validation works using multiple schema from .graphqlconfig', rule, {
      valid: [
        {
          options,
          code: [
            'const x = gql`{ number, sum(a: 1, b: 1) }`;',
            'const y = swapi`{ hero(episode: NEWHOPE) { id, name } }`;',
          ].join('\n'),
        },
      ],

      invalid: [
        {
          options,
          code: [
            'const x = swapi`{ number, sum(a: 1, b: 1) }`;',
            'const y = gql`{ hero(episode: NEWHOPE) { id, name } }`;',
          ].join('\n'),
          errors: [{
            message: 'Cannot query field "number" on type "Query".',
            type: 'TaggedTemplateExpression',
          }, {
            message: 'Cannot query field "hero" on type "Query".',
            type: 'TaggedTemplateExpression',
          }],
        },
      ],
    });
  });

  describe('multiproject literal', () => {
    beforeEach(() => {
      process.chdir('test/graphqlconfig/multiproject-literal');
    })

    afterEach(() => {
      process.chdir('../../..');
    })

    ruleTester.run('validation works using multiple schemas from .graphqlconfig', rule, {
      valid: [
        {
          options: [{ env: 'literal' }],
          code: 'const x = ESLintPluginGraphQLFile`{ number, sum(a: 1, b: 1) }`;',
          filename: 'first.graphql',
        },
        {
          options: [{ env: 'literal' }],
          code: 'const x = ESLintPluginGraphQLFile`{ hero(episode: NEWHOPE) { id, name } }`;',
          filename: 'second.graphql',
        },
        {
          options: [{ env: 'literal' }],
          code: 'const x = ESLintPluginGraphQLFile`{ number, sum(a: 1, b: 1) }`;',
          filename: 'excluded1.graphql',
        },
        {
          options: [{ env: 'literal' }],
          code: 'const x = ESLintPluginGraphQLFile`{ hero(episode: NEWHOPE) { id, name } }`;',
          filename: 'excluded2.graphql',
        },
      ],

      invalid: [
        {
          options: [{ env: 'literal' }],
          code: 'const x = ESLintPluginGraphQLFile`{ number, sum(a: 1, b: 1) }`;',
          filename: 'second.graphql',
          errors: [{
            message: /cannot query field .number./i,
          }],
        },
        {
          options: [{ env: 'literal' }],
          code: 'const x = ESLintPluginGraphQLFile`{ hero(episode: NEWHOPE) { id, name } }`;',
          filename: 'first.graphql',
          errors: [{
            message: /cannot query field .hero./i,
          }],
        },
      ],
    });
  });
});


================================================
FILE: test/graphqlconfig/multiproject/.graphqlconfig
================================================
{
  "projects": {
    "gql": {
      "schema": "../../schema.graphql"
    },
    "swapi": {
      "schema": "../../second-schema.graphql"
    }
  }
}


================================================
FILE: test/graphqlconfig/multiproject-literal/graphql.config.json
================================================
{
  "projects": {
    "gql": {
      "schema": "../../schema.graphql",
      "include": ["first.graphql"]
    },
    "swapi": {
      "schema": "../../second-schema.graphql",
      "include": ["second.graphql"]
    }
  }
}


================================================
FILE: test/graphqlconfig/simple/graphql.config.json
================================================
{
  "schema": "../../schema.graphql"
}


================================================
FILE: test/helpers.js
================================================
import { rules } from '../src';
import { RuleTester } from 'eslint';
import schemaJson from './schema.json';
import path from 'path';
import * as graphql from 'graphql';

const { printSchema, buildClientSchema, specifiedRules: allGraphQLValidators } = graphql;

export const isAtLeastGraphQL15 = graphql.versionInfo && graphql.versionInfo.major >= 15;

export const schemaJsonFilepath = path.resolve(__dirname, './schema.json');
export const secondSchemaJsonFilepath = path.resolve(__dirname, './second-schema.json');
export const schemaString = printSchema(buildClientSchema(schemaJson.data))

const allGraphQLValidatorNames = allGraphQLValidators.map(rule => rule.name);

let requiredArgumentRule = 'ProvidedNonNullArguments';
if (allGraphQLValidatorNames.includes('ProvidedRequiredArgumentsRule')) {
  requiredArgumentRule = 'ProvidedRequiredArgumentsRule';
} else if (allGraphQLValidatorNames.includes('ProvidedRequiredArguments')) {
  requiredArgumentRule = 'ProvidedRequiredArguments';
}

export const requiredArgumentRuleName = requiredArgumentRule;

// Init rule

export const rule = rules['template-strings'];

// Set up tests

export const ruleTester = new RuleTester();

export const parserOptions = {
  'ecmaVersion': 6,
  'sourceType': 'module',
};


================================================
FILE: test/index.js
================================================
require('@babel/register');

require('./makeProcessors');
require('./graphqlconfig/');
require('./env');
require('./customTagName');
require('./default');
require('./schemaTests');
require('./validationRules');


================================================
FILE: test/makeProcessors.js
================================================
import assert from 'assert';
import { CLIEngine } from 'eslint';
import path from 'path';

import schemaJson from './schema.json';
import plugin, { processors } from '../src';

function execute(file) {
  const cli = new CLIEngine({
    extensions: ['.gql', '.graphql'],
    baseConfig: {
      plugins: ['graphql'],
      rules: {
        'graphql/required-fields': [
          'error',
          {
            schemaJson,
            env: 'literal',
            requiredFields: ['id']
          }
        ]
      }
    },
    ignore: false,
    useEslintrc: false,
    parserOptions: {
      ecmaVersion: 6,
      sourceType: 'module'
    }
  });
  cli.addPlugin('eslint-plugin-graphql', plugin);
  return cli.executeOnFiles([
    path.join(__dirname, '__fixtures__', `${file}.graphql`)
  ]);
}

describe('processors', () => {
  it('should define processors', () => {
    const extensions = Object.keys(processors);

    assert(extensions.includes('.gql'));
    assert(extensions.includes('.graphql'));
  });

  it('should wrap with backticks, escape properly and prepend internalTag', () => {
    const query = 'query { search(q: "` \\n ${}") { title } }';
    const expected = 'ESLintPluginGraphQLFile`query { search(q: "\\` \\\\n \\${}") { title } }`';
    const preprocess = processors['.gql'].preprocess;
    const result = preprocess(query);

    assert.equal(result, expected);
  });

  it('should filter only graphql/* rules ', () => {
    const messages = [
      { ruleId: 'no-undef' },
      { ruleId: 'semi' },
      { ruleId: 'graphql/randomString' },
      { ruleId: 'graphql/template-strings' }
    ];
    const expected = { ruleId: 'graphql/template-strings' };
    const postprocess = processors['.gql'].postprocess;
    const result = postprocess(messages);

    assert.equal(result.length, 1);
    assert.equal(result[0].ruleId, expected.ruleId);
  });

  describe('graphql/required-fields', () => {
    describe('valid', () => {
      [
        'required-fields-valid-no-id',
        'required-fields-valid-id',
        'required-fields-valid-array'
      ].forEach(filename => {
        it(`does not warn on file ${filename}`, () => {
          const results = execute(filename);
          assert.equal(results.errorCount, 0);
        });
      });
    });

    describe('invalid', () => {
      [
        'required-fields-invalid-no-id',
        'required-fields-invalid-array'
      ].forEach(filename => {
        it(`warns on file ${filename}`, () => {
          const results = execute(filename);
          assert.equal(results.errorCount, 1);
          const message = results.results[0].messages[0].message;
          assert.ok(new RegExp("'id' field required").test(message));
        });
      });
    });

    describe('error line/column locations', () => {
      it('populates correctly for a single-line document', () => {
        const results = execute('required-fields-invalid-array');
        assert.equal(results.errorCount, 1);
        assert.deepEqual(results.results[0].messages[0], {
          column: 9,
          line: 1,
          message: "'id' field required on 'stories'",
          nodeType: 'TaggedTemplateExpression',
          ruleId: 'graphql/required-fields',
          severity: 2,
        });
      });

      it('populates correctly for a multi-line document', () => {
        const results = execute('required-fields-invalid-no-id');
        assert.equal(results.errorCount, 1);
        assert.deepEqual(results.results[0].messages[0], {
          column: 3,
          line: 2,
          message: "'id' field required on 'greetings'",
          nodeType: 'TaggedTemplateExpression',
          ruleId: 'graphql/required-fields',
          severity: 2,
        });
      });
    });
  });
});


================================================
FILE: test/schema.graphql
================================================
schema {
  query: Query
  mutation: Mutation
}

type Mutation {
  createComment(input: CreateCommentInput): CreateCommentPayload
}

input CreateCommentInput {
  stuff: String
}

type CreateCommentPayload {
  story: Story
}

type CreateEventPayload {
  viewer: User
  user: User
}

type User {
  events: [String]
}

type Query {
  number: Int
  allFilms: AllFilmsObj
  sum(a: Int!, b: Int!): Int!
  greetings: Greetings
  stories: [Story!]!
  greetingOrStory: GreetingOrStory
  nodes: [Node]
}

type AllFilmsObj {
  films: [Film]
}

type Film {
  title: String
  director: String
  releaseDate: String
}

type Greetings {
  id: ID
  hello: String
  hi: String @deprecated(reason: "Please use the more formal greeting 'hello'")
  image(size: ImageSize!): Image
}

type Image {
  size: ImageSize!
}

enum ImageSize {
  SMALL @deprecated(reason: "Use 'LARGE' instead")
  LARGE
}

type Story {
  id: ID
  comments: [Comment]
}

type Comment {
  text: String
}

type PublicUser {
  fullName: String
}

union GreetingOrStory = Greetings | Story

interface Node {
  id: ID!
}

type NodeA implements Node {
  id: ID!
  fieldA: String
}

type NodeB implements Node{
  id: ID!
  fieldB: String
}


================================================
FILE: test/schemaTests/index.js
================================================
import {
  isAtLeastGraphQL15,
  schemaJsonFilepath,
  secondSchemaJsonFilepath,
  schemaString,
  rule,
  ruleTester,
  parserOptions
} from '../helpers';

{
  const options = [
    { schemaString },
  ];

  ruleTester.run('schemaString', rule, {
    valid: [
      {
        options,
        parserOptions,
        code: 'const x = gql`{ number }`',
      },
      {
        options,
        parserOptions,
        code: 'const x = segmented.TagName`height: 12px;`'
      },
      {
        options,
        parserOptions,
        code: 'const x = segmented.gql`height: 12px;`'
      },
      {
        options,
        parserOptions,
        code: 'const x = gql.segmented`height: 12px;`'
      },
      {
        options,
        parserOptions,
        code: 'const x = gql`{ number } ${x}`',
      },
    ],

    invalid: [
      {
        options,
        parserOptions,
        code: 'const x = gql``',
        errors: [{
          message: isAtLeastGraphQL15 ? 'Syntax Error: Unexpected <EOF>.' :  'Syntax Error: Unexpected <EOF>',
          type: 'TaggedTemplateExpression'
        }]
      },
      {
        options,
        parserOptions,
        code: 'const x = gql`{ nonExistentQuery }`',
        errors: [{
          message: 'Cannot query field "nonExistentQuery" on type "Query".',
          type: 'TaggedTemplateExpression'
        }]
      },
      {
        options,
        parserOptions,
        code: 'const x = gql`{ ${x} }`',
        errors: [{
          message: 'Invalid interpolation - fragment interpolation must occur outside of the brackets.',
          type: 'Identifier',
          line: 1,
          column: 19
        }]
      },
    ]
  });
}

{
  const options = [
    { schemaJsonFilepath, tagName: 'absolute' },
  ];

  ruleTester.run('schema by absolute path', rule, {
    valid: [
      {
        options,
        parserOptions,
        code: 'const x = absolute`{ number, sum(a: 1, b: 1) }`',
      },
    ],

    invalid: [
      {
        options,
        parserOptions,
        code: 'const x = absolute`{ nonExistentQuery }`',
        errors: [{
          message: 'Cannot query field "nonExistentQuery" on type "Query".',
          type: 'TaggedTemplateExpression'
        }]
      }
    ]
  });
}

{
  const options = [
    { schemaJsonFilepath, tagName: 'gql' },
    { schemaJsonFilepath: secondSchemaJsonFilepath, tagName: 'swapi' },
  ];

  ruleTester.run('validates multiple schemas correctly', rule, {
    valid: [
      {
        options,
        parserOptions,
        code: [
          'const x = gql`{ number, sum(a: 1, b: 1) }`;',
          'const y = swapi`{ hero(episode: NEWHOPE) { id, name } }`;',
        ].join('\n'),
      },
    ],

    invalid: [
      {
        options,
        parserOptions,
        code: [
          'const x = swapi`{ number, sum(a: 1, b: 1) }`;',
          'const y = gql`{ hero(episode: NEWHOPE) { id, name } }`;',
        ].join('\n'),
        errors: [{
          message: 'Cannot query field "number" on type "Query".',
          type: 'TaggedTemplateExpression',
        }, {
          message: 'Cannot query field "hero" on type "Query".',
          type: 'TaggedTemplateExpression',
        }],
      },
    ],
  });
}


================================================
FILE: test/second-schema.graphql
================================================
schema {
  query: Query
}

enum Episode { NEWHOPE, EMPIRE, JEDI }

interface Character {
  id: String!
  name: String
  friends: [Character]
  appearsIn: [Episode]
}

type Human implements Character {
  id: String!
  name: String
  friends: [Character]
  appearsIn: [Episode]
  homePlanet: String
}

type Droid implements Character {
  id: String!
  name: String
  friends: [Character]
  appearsIn: [Episode]
  primaryFunction: String
}

type Query {
  hero(episode: Episode): Character
  human(id: String!): Human
  droid(id: String!): Droid
}


================================================
FILE: test/updateSchemaJson.js
================================================
const fs = require('fs');
const path = require('path');
const graphql = require('graphql');
const process = require('process');

const isAtLeastGraphQL15 = graphql.versionInfo && graphql.versionInfo.major >= 15;

Promise.all(['schema', 'second-schema'].map(schemaName => {
  const typeDefinition = fs.readFileSync(path.join(__dirname, schemaName + '.graphql'), 'utf8');
  const schema = graphql.buildASTSchema(graphql.parse(typeDefinition));
  const outputPath = path.join(__dirname, schemaName + '.json');
  const introspectionQuery = isAtLeastGraphQL15 ? graphql.getIntrospectionQuery() : graphql.introspectionQuery;

  return graphql.graphql(schema, introspectionQuery)
    .then(result => fs.writeFileSync(outputPath, JSON.stringify(result, null, 2)));
}))
.then(() => process.exit(0))
.catch(e => {
  console.error(e); // eslint-disable-line no-console
  process.exit(127);
});


================================================
FILE: test/validationRules/built-in.js
================================================
import schemaJson from "../schema.json";

import {
  isAtLeastGraphQL15,
  requiredArgumentRuleName,
  rule,
  ruleTester,
  parserOptions
} from "../helpers";

const setRuleName = name => {
  if (name === requiredArgumentRuleName) {
    return name;
  }
  return isAtLeastGraphQL15 ? `${name}Rule` : name;
};

const validatorCases = {
  FieldsOnCorrectType: {
    pass: "const x = gql`{ allFilms { films { title } } }`",
    fail: "const x = gql`{ allFilms { films { greetings } } }`",
    errors: [
      {
        message: 'Cannot query field "greetings" on type "Film".',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  FragmentsOnCompositeTypes: {
    pass: "const x = gql`{ allFilms { films { ...on Film { title } } } }`",
    fail: "const x = gql`{ allFilms { films { ...on String { foo } } } }`",
    alsoBreaks: ["PossibleFragmentSpreads"],
    errors: [
      {
        message: 'Fragment cannot condition on non composite type "String".',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  KnownArgumentNames: {
    pass: "const x = gql`{ sum(a: 1, b: 2) }`",
    fail: "const x = gql`{ sum(c: 1, d: 2) }`",
    alsoBreaks: [requiredArgumentRuleName],
    errors: [
      {
        message: isAtLeastGraphQL15 ?
          'Unknown argument "c" on field "Query.sum". Did you mean "a" or "b"?' :
          'Unknown argument "c" on field "sum" of type "Query". Did you mean "a" or "b"?',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  KnownDirectives: {
    pass:
      "const x = gql`{ number, allFilms @include(if: false) { films { title } } }`",
    fail:
      "const x = gql`{ number, allFilms @goofy(if: false) { films { title } } }`",
    errors: [
      {
        message: isAtLeastGraphQL15 ? 'Unknown directive "@goofy".' : 'Unknown directive "goofy".',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  KnownFragmentNames: {
    pass:
      "const x = gql`fragment FilmFragment on Film { title } { allFilms { films { ...FilmFragment } } }`",
    fail: "const x = gql`{ allFilms { films { ...FilmFragment } } }`",
    errors: [
      {
        message: 'Unknown fragment "FilmFragment".',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  KnownTypeNames: {
    pass:
      "const x = gql`fragment FilmFragment on Film { title } { allFilms { films { ...FilmFragment } } }`",
    fail:
      "const x = gql`fragment FilmFragment on Floof { title } { allFilms { films { ...FilmFragment } } }`",
    errors: [
      {
        message: new RegExp('Unknown type "Floof".'),
        type: "TaggedTemplateExpression"
      }
    ]
  },
  LoneAnonymousOperation: {
    pass: "const x = gql`{ number }`",
    fail: "const x = gql`{ number } { number }`",
    errors: [
      {
        message: "This anonymous operation must be the only defined operation.",
        type: "TaggedTemplateExpression"
      }
    ]
  },

  // This causes a `RangeError: Maximum call stack size exceeded` exception in graphql 0.8.x
  // 'NoFragmentCycles': {
  //   pass: 'const x = gql`fragment FilmFragment on Film { title } { allFilms { films { ...FilmFragment } } }`',
  //   fail: 'const x = gql`fragment FilmFragment on Film { title, ...FilmFragment } { allFilms { films { ...FilmFragment } } }`',
  //   errors: [{
  //     message: 'Cannot spread fragment "FilmFragment" within itself.',
  //     type: 'TaggedTemplateExpression',
  //   }],
  // },

  NoUndefinedVariables: {
    pass: "const x = gql`query($a: Int!) { sum(a: $a, b: 1) }`",
    fail: "const x = gql`query($a: Int!) { sum(a: $a, b: $b) }`",
    errors: [
      {
        message: 'Variable "$b" is not defined.',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  NoUnusedFragments: {
    pass:
      "const x = gql`fragment FilmFragment on Film { title } { allFilms { films { ...FilmFragment } } }`",
    fail:
      "const x = gql`fragment FilmFragment on Film { title } { allFilms { films { title } } }`",
    errors: [
      {
        message: 'Fragment "FilmFragment" is never used.',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  NoUnusedVariables: {
    pass: "const x = gql`query($a: Int!) { sum(a: $a, b: 1) }`",
    fail: "const x = gql`query($a: Int!) { sum(a: 1, b: 1) }`",
    errors: [
      {
        message: 'Variable "$a" is never used.',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  OverlappingFieldsCanBeMerged: {
    pass:
      "const x = gql`fragment Sum on Query { sum(a: 1, b: 2) } { ...Sum, sum(a: 1, b: 2) }`",
    fail:
      "const x = gql`fragment Sum on Query { sum(a: 1, b: 2) } { ...Sum, sum(a: 2, b: 3) }`",
    errors: [
      {
        message:
          'Fields "sum" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  PossibleFragmentSpreads: {
    pass:
      "const x = gql`fragment FilmFragment on Film { title } { allFilms { films { ...FilmFragment } } }`",
    fail:
      "const x = gql`fragment FilmFragment on Film { title } { greetings { ...FilmFragment } }`",
    errors: [
      {
        message:
          'Fragment "FilmFragment" cannot be spread here as objects of type "Greetings" can never be of type "Film".',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  [requiredArgumentRuleName]: {
    pass: "const x = gql`{ sum(a: 1, b: 2) }`",
    fail: "const x = gql`{ sum(a: 1) }`",
    errors: [
      {
        message: new RegExp(
          'Field "sum" argument "b" of type "Int!" is required'
        ),
        type: "TaggedTemplateExpression"
      }
    ]
  },
  ScalarLeafs: {
    pass: "const x = gql`{ number }`",
    fail: "const x = gql`{ allFilms }`",
    errors: [
      {
        message:
          'Field "allFilms" of type "AllFilmsObj" must have a selection of subfields. Did you mean "allFilms { ... }"?',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  UniqueArgumentNames: {
    pass: "const x = gql`{ sum(a: 1, b: 2) }`",
    fail: "const x = gql`{ sum(a: 1, a: 2) }`",
    alsoBreaks: [requiredArgumentRuleName],
    errors: [
      {
        message: 'There can be only one argument named "a".',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  UniqueFragmentNames: {
    pass:
      "const x = gql`fragment FF1 on Film { title } fragment FF2 on Film { director } { allFilms { films { ...FF1, ...FF2 } } }`",
    fail:
      "const x = gql`fragment FF on Film { title } fragment FF on Film { director } { allFilms { films { ...FF } } }`",
    errors: [
      {
        message: 'There can be only one fragment named "FF".',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  UniqueInputFieldNames: {
    pass:
      'const x = gql`mutation { createComment(input: { stuff: "Yay" }) { story { id } } }`',
    fail:
      'const x = gql`mutation { createComment(input: { stuff: "Yay", stuff: "No" }) { story { id } } }`',
    errors: [
      {
        message: 'There can be only one input field named "stuff".',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  UniqueOperationNames: {
    pass:
      "const x = gql`query Q1 { sum(a: 1, b: 2) } query Q2 { sum(a: 2, b: 3) }`",
    fail:
      "const x = gql`query Q { sum(a: 1, b: 2) } query Q { sum(a: 2, b: 3) }`",
    errors: [
      {
        message: 'There can be only one operation named "Q".',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  UniqueVariableNames: {
    pass: "const x = gql`query($a: Int!, $b: Int!) { sum(a: $a, b: $b) }`",
    fail: "const x = gql`query($a: Int!, $a: Int!) { sum(a: $a, b: $a) }`",
    errors: [
      {
        message: isAtLeastGraphQL15 ?
          'There can be only one variable named "$a".' :
          'There can be only one variable named "a".',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  VariablesAreInputTypes: {
    pass: "const x = gql`query($a: Int!, $b: Int!) { sum(a: $a, b: $b) }`",
    fail: "const x = gql`query($a: Film!) { sum(a: 1, b: 1) }`",
    alsoBreaks: [isAtLeastGraphQL15 ? "NoUnusedVariablesRule" : "NoUnusedVariables"],
    errors: [
      {
        message: 'Variable "$a" cannot be non-input type "Film!".',
        type: "TaggedTemplateExpression"
      }
    ]
  },
  VariablesInAllowedPosition: {
    pass: "const x = gql`query($a: Int!) { sum(a: $a, b: 1) }`",
    fail: "const x = gql`query($a: String!) { sum(a: $a, b: 1) }`",
    errors: [
      {
        message:
          'Variable "$a" of type "String!" used in position expecting type "Int!".',
        type: "TaggedTemplateExpression"
      }
    ]
  }
};

{
  let options = [
    {
      schemaJson,
      tagName: "gql",
      validators: "all"
    }
  ];
  ruleTester.run("enabled all validators", rule, {
    valid: Object.values(validatorCases).map(({ pass: code }) => ({
      options,
      parserOptions,
      code
    })),
    invalid: Object.values(validatorCases).map(({ fail: code, errors }) => ({
      options,
      parserOptions,
      code,
      errors
    }))
  });

  options = [
    {
      schemaJson,
      tagName: "gql",
      validators: []
    }
  ];
  ruleTester.run("disabled all validators", rule, {
    valid: []
      .concat(
        Object.values(validatorCases).map(({ pass: code }) => code),
        Object.values(validatorCases).map(({ fail: code }) => code)
      )
      .map(code => ({ options, parserOptions, code })),
    invalid: []
  });

  // Check that when only a given validation is enabled, it's the only thing
  // that can fail. (Excluding test cases that include this validation rule as
  // 'alsoBreaks'…sometimes it's hard to make a test that fails exactly one
  // validator).
  for (const [graphqlValidatorName, { fail, errors }] of Object.entries(validatorCases)) {
    const validatorName = setRuleName(graphqlValidatorName);
    options = [
      {
        schemaJson,
        tagName: "gql",
        validators: [validatorName]
      }
    ];
    const otherValidators = Object.entries(validatorCases)
      .filter(
        ([otherGraphQLValidatorName, { alsoBreaks }]) => {
          const otherValidatorName = setRuleName(otherGraphQLValidatorName);
          return otherValidatorName !== validatorName &&
            !(alsoBreaks || []).includes(validatorName)
        }
      )
      .map(kvPair => kvPair[1]);
    ruleTester.run(`enabled only ${validatorName} validator`, rule, {
      valid: []
        .concat(
          Object.values(validatorCases).map(({ pass: code }) => code),
          otherValidators.map(({ fail: code }) => code)
        )
        .map(code => ({ options, parserOptions, code })),
      invalid: [{ options, parserOptions, errors, code: fail }]
    });
  }
}


================================================
FILE: test/validationRules/capitalized-type-name.js
================================================
import { rules } from "../../src";
import schemaJson from "../schema.json";

import { ruleTester, parserOptions } from "../helpers";

const typeNameCapValidatorCases = {
  pass: [
    "const x = gql`fragment FilmFragment on Film { title } { allFilms { films { ...FilmFragment } } }`",
    "const x = gql`query { someUnion {... on SomeUnionMember { someField }}}`"
  ],
  fail: [
    {
      code:
        "const x = gql`fragment FilmFragment on film { title } { allFilms { films { ...FilmFragment } } }`",
      errors: [
        {
          message: "All type names should start with a capital letter",
          type: "TaggedTemplateExpression"
        }
      ]
    },
    {
      code:
        "const x = gql`query { someUnion {... on someUnionMember { someField }}}`",
      errors: [
        {
          message: "All type names should start with a capital letter",
          type: "TaggedTemplateExpression"
        }
      ]
    }
  ]
};

const options = [
  {
    schemaJson,
    tagName: "gql"
  }
];
ruleTester.run(
  "testing capitalized-type-name rule",
  rules["capitalized-type-name"],
  {
    valid: typeNameCapValidatorCases.pass.map(code => ({
      options,
      parserOptions,
      code
    })),
    invalid: typeNameCapValidatorCases.fail.map(({ code, errors }) => ({
      options,
      parserOptions,
      code,
      errors
    }))
  }
);


================================================
FILE: test/validationRules/index.js
================================================
import './named-operations';
import './required-fields';
import './no-deprecated-fields';
import './capitalized-type-name';
import './built-in';


================================================
FILE: test/validationRules/named-operations.js
================================================
import { rules } from "../../src";
import schemaJson from "../schema.json";

import { ruleTester, parserOptions } from "../helpers";

const namedOperationsValidatorCases = {
  OperationsMustHaveNames: {
    pass: "const x = gql`query Test { sum(a: 1, b: 2) }`",
    fail: "const x = gql`query { sum(a: 1, b: 2) }`",
    errors: [
      {
        message: "All operations must be named",
        type: "TaggedTemplateExpression"
      }
    ]
  }
};

// Validate the named-operations rule
const options = [
  {
    schemaJson,
    tagName: "gql"
  }
];
ruleTester.run("testing named-operations rule", rules["named-operations"], {
  valid: Object.values(namedOperationsValidatorCases).map(({ pass: code }) => ({
    options,
    parserOptions,
    code
  })),
  invalid: Object.values(namedOperationsValidatorCases).map(
    ({ fail: code, errors }) => ({ options, parserOptions, code, errors })
  )
});


================================================
FILE: test/validationRules/no-deprecated-fields.js
================================================
import { rules } from "../../src";
import schemaJson from "../schema.json";

import { ruleTester } from "../helpers";

const parser = require.resolve('babel-eslint');

const options = [
  {
    schemaJson,
    env: "relay"
  }
];

const noDeprecatedFieldsCases = {
  pass: [
    `
      @relay({
        fragments: {
          greetings: () => Relay.QL\`
            fragment on Greetings {
              hello,
            }
          \`,
        }
      })
      class HelloApp extends React.Component {}
    `,
    `
      @relay({
        fragments: {
          greetings: () => Relay.QL\`
            fragment on Greetings {
              image(size: LARGE) { size }
            }
          \`,
        }
      })
      class HelloApp extends React.Component {}
    `
  ],
  fail: [
    {
      options,
      parser,
      code: `
        @relay({
          fragments: {
            greetings: () => Relay.QL\`
              fragment on Greetings {
                hi,
              }
            \`,
          }
        })
        class HelloApp extends React.Component {}
      `,
      errors: [
        {
          message:
            "The field Greetings.hi is deprecated. Please use the more formal greeting 'hello'",
          type: "TaggedTemplateExpression",
          line: 6,
          column: 17
        }
      ]
    },
    {
      options,
      parser,
      code: `
        @relay({
          fragments: {
            greetings: () => Relay.QL\`
              fragment on Greetings {
                image(size: SMALL) { size }
              }
            \`,
          }
        })
        class HelloApp extends React.Component {}
      `,
      errors: [
        {
          message:
            "The enum value ImageSize.SMALL is deprecated. Use 'LARGE' instead",
          type: "TaggedTemplateExpression",
          line: 6,
          column: 29
        }
      ]
    }
  ]
};

ruleTester.run(
  "testing no-deprecated-fields rule",
  rules["no-deprecated-fields"],
  {
    valid: noDeprecatedFieldsCases.pass.map(code => ({
      options,
      parser,
      code
    })),
    invalid: noDeprecatedFieldsCases.fail.map(({ code, errors }) => ({
      options,
      parser,
      code,
      errors
    }))
  }
);


================================================
FILE: test/validationRules/required-fields.js
================================================
import { rules } from "../../src";
import schemaJson from "../schema.json";

import { ruleTester, parserOptions } from "../helpers";

const requiredFieldsTestCases = {
  pass: [
    "const x = gql`query { allFilms { films { title } } }`",
    "const x = gql`query { stories { id comments { text } } }`",
    "const x = gql`query { greetings { id, hello, hi } }`",
    "const x = gql`query { greetings { id, hello, foo } }`",
    "const x = gql`query { greetings { id ... on Greetings { hello } } }`",
    "const x = gql`query { greetingOrStory { ... on Greetings { id hello } } }`",
    "const x = gql`query { greetingOrStory { id ... on Greetings { hello } } }`",
    "const x = gql`fragment Name on GreetingOrStory { ... on Greetings { id hello } }`",
    "const x = gql`fragment Name on GreetingOrStory { id ... on Greetings { hello } }`",
    "const x = gql`fragment Name on Greetings { id hello }`",
    "const x = gql`fragment Foo on FooBar { id, hello, foo }`",
    "const x = gql`fragment Id on Node { id ... on NodeA { fieldA } }`",
    "const x = gql`query { nodes { id ... on NodeA { fieldA } } }`",
  ],
  fail: [
    {
      code: "const x = gql`query { stories { comments { text } } }`",
      errors: [
        {
          message: `'id' field required on 'stories'`,
          type: "TaggedTemplateExpression"
        }
      ]
    },
    {
      code: "const x = gql`query { greetings { hello } }`",
      errors: [
        {
          message: `'id' field required on 'greetings'`,
          type: "TaggedTemplateExpression"
        }
      ]
    },
    {
      code:
        "const x = gql`query { greetings { hello ... on Greetings { hello } } }`",
      errors: [
        {
          message: `'id' field required on 'greetings'`,
          type: "TaggedTemplateExpression"
        }
      ]
    },
    {
      code: 'const x = gql`query { greetings { hello ...GreetingsFragment} }`',
      errors: [
        {
          message: `'id' field required on 'greetings'`,
          type: 'TaggedTemplateExpression',
        },
      ],
    },
    {
      code: 'const x = gql`fragment Name on Greetings { hello }`',
      errors: [
        {
          message: `'id' field required on 'fragment Name on Greetings'`,
          type: 'TaggedTemplateExpression',
        },
      ],
    },
    {
      code:
        "const x = gql`query { greetingOrStory { ... on Greetings { id } ... on Story { comments { text } } } }`",
      errors: [
        {
          message: `'id' field required on '... on Story'`,
          type: "TaggedTemplateExpression"
        }
      ]
    },
    {
      code:
        "const x = gql`query { nodes { ... on NodeA { id fieldA } ... on NodeB { id fieldB }}}`",
      errors: [
        {
          message: `'id' field required on 'nodes'`,
          type: "TaggedTemplateExpression"
        }
      ]
    },
    {
      code:
        "const x = gql`fragment Name on GreetingOrStory { ... on Greetings { hello } }`",
      errors: [
        {
          message: `'id' field required on '... on Greetings'`,
          type: "TaggedTemplateExpression"
        }
      ]
    },
  ]
};

// Validate the required-fields rule with env specified
let options = [
  {
    schemaJson,
    env: "apollo",
    tagName: "gql",
    requiredFields: ["id"]
  }
];

ruleTester.run("testing required-fields rule with env", rules["required-fields"], {
  valid: requiredFieldsTestCases.pass.map(code => ({
    options,
    parserOptions,
    code
  })),
  invalid: requiredFieldsTestCases.fail.map(({ code, errors }) => ({
    options,
    parserOptions,
    code,
    errors
  }))
});

// Validate required-fields without optional env argument
options = [
  {
    schemaJson,
    tagName: "gql",
    requiredFields: ["id"]
  }
];
ruleTester.run("testing required-fields rule without env", rules["required-fields"], {
  valid: requiredFieldsTestCases.pass.map(code => ({
    options,
    parserOptions,
    code
  })),
  invalid: requiredFieldsTestCases.fail.map(({ code, errors }) => ({
    options,
    parserOptions,
    code,
    errors
  }))
});
Download .txt
gitextract_uah1ygf4/

├── .babelrc.js
├── .eslintrc.js
├── .github/
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       └── nodejs.yml
├── .gitignore
├── .npmignore
├── .tav.yml
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── README.md
├── package.json
├── renovate.json
├── src/
│   ├── constants.js
│   ├── createRule.js
│   ├── customGraphQLValidationRules.js
│   └── index.js
└── test/
    ├── __fixtures__/
    │   ├── required-fields-invalid-array.graphql
    │   ├── required-fields-invalid-no-id.graphql
    │   ├── required-fields-valid-array.graphql
    │   ├── required-fields-valid-id.graphql
    │   └── required-fields-valid-no-id.graphql
    ├── customTagName.js
    ├── default.js
    ├── env/
    │   ├── apollo.js
    │   ├── fraql.js
    │   ├── index.js
    │   ├── lokka.js
    │   └── relay.js
    ├── graphqlconfig/
    │   ├── index.js
    │   ├── multiproject/
    │   │   └── .graphqlconfig
    │   ├── multiproject-literal/
    │   │   └── graphql.config.json
    │   └── simple/
    │       └── graphql.config.json
    ├── helpers.js
    ├── index.js
    ├── makeProcessors.js
    ├── schema.graphql
    ├── schemaTests/
    │   └── index.js
    ├── second-schema.graphql
    ├── updateSchemaJson.js
    └── validationRules/
        ├── built-in.js
        ├── capitalized-type-name.js
        ├── index.js
        ├── named-operations.js
        ├── no-deprecated-fields.js
        └── required-fields.js
Download .txt
SYMBOL INDEX (17 symbols across 4 files)

FILE: src/createRule.js
  function strWithLen (line 5) | function strWithLen(len) {
  function replaceExpressions (line 10) | function replaceExpressions(node, context, env) {
  function locFrom (line 75) | function locFrom(node, error) {
  function handleTemplateTag (line 97) | function handleTemplateTag(node, context, schema, env, validators) {
  function templateExpressionMatchesTag (line 141) | function templateExpressionMatchesTag(tagName, node) {
  function createRule (line 163) | function createRule(context, optionParser) {

FILE: src/customGraphQLValidationRules.js
  function OperationsMustHaveNames (line 3) | function OperationsMustHaveNames(context) {
  function getFieldWasRequestedOnNode (line 15) | function getFieldWasRequestedOnNode(node, field) {
  function fieldAvailableOnType (line 21) | function fieldAvailableOnType(type, field) {
  function RequiredFields (line 32) | function RequiredFields(context, options) {
  function typeNamesShouldBeCapitalized (line 136) | function typeNamesShouldBeCapitalized(context) {
  function noDeprecatedFields (line 154) | function noDeprecatedFields(context) {

FILE: src/index.js
  function parseOptions (line 266) | function parseOptions(optionGroup, context) {
  function initSchema (line 388) | function initSchema(json) {
  function initSchemaFromFile (line 396) | function initSchemaFromFile(jsonFile) {
  function initSchemaFromString (line 400) | function initSchemaFromString(source) {

FILE: test/makeProcessors.js
  function execute (line 8) | function execute(file) {
Condensed preview — 46 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (115K chars).
[
  {
    "path": ".babelrc.js",
    "chars": 104,
    "preview": "module.exports = {\n  presets: ['@babel/preset-env'],\n  plugins: ['@babel/plugin-transform-runtime'],\n};\n"
  },
  {
    "path": ".eslintrc.js",
    "chars": 188,
    "preview": "module.exports = {\n  extends: \"eslint:recommended\",\n  parserOptions: {\n    ecmaVersion: 2018,\n    sourceType: \"module\"\n "
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 460,
    "preview": "<!--\n  Thanks for filing a pull request on eslint-plugin-graphql!\n\n  Please look at the following checklist to ensure th"
  },
  {
    "path": ".github/workflows/nodejs.yml",
    "chars": 487,
    "preview": "name: Node CI\n\non: \n  push:\n    branches:\n      - !master\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    strategy:\n   "
  },
  {
    "path": ".gitignore",
    "chars": 67,
    "preview": "node_modules\nlib\ntest/schema.json\ntest/second-schema.json\n.vscode/\n"
  },
  {
    "path": ".npmignore",
    "chars": 14,
    "preview": "src\ndocs\ntest\n"
  },
  {
    "path": ".tav.yml",
    "chars": 96,
    "preview": "\ngraphql:\n  versions: ^0.12.0  || ^0.13.0 || ^14.0.0 || ^15.0.0\n  commands: mocha test/index.js\n"
  },
  {
    "path": ".travis.yml",
    "chars": 164,
    "preview": "language: node_js\nnode_js:\n  - \"10\"\n  - \"12\"\ninstall:\n  - npm install\n\nscript:\n  - npm run lint && npm test\n\n# Allow Tra"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 10592,
    "preview": "# Change log\n\n### vNEXT\n\n- _Nothing yet!_\n\n### v4.0.0\n\n- Improve identity template literal tag docs. [PR #254](https://g"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 8733,
    "preview": "# Apollo Contributor Guide\n\nExcited about Apollo and want to make it better? We’re excited too!\n\nApollo is a community o"
  },
  {
    "path": "README.md",
    "chars": 15739,
    "preview": "# eslint-plugin-graphql\n[![npm version](https://badge.fury.io/js/eslint-plugin-graphql.svg)](https://badge.fury.io/js/es"
  },
  {
    "path": "package.json",
    "chars": 1401,
    "preview": "{\n  \"name\": \"eslint-plugin-graphql\",\n  \"version\": \"4.0.0\",\n  \"description\": \"GraphQL ESLint plugin.\",\n  \"author\": \"Sashk"
  },
  {
    "path": "renovate.json",
    "chars": 41,
    "preview": "{\n  \"extends\": [\n    \"config:base\"\n  ]\n}\n"
  },
  {
    "path": "src/constants.js",
    "chars": 54,
    "preview": "export const internalTag = \"ESLintPluginGraphQLFile\";\n"
  },
  {
    "path": "src/createRule.js",
    "chars": 5774,
    "preview": "import { parse, validate } from \"graphql\";\n\nimport { internalTag } from \"./constants\";\n\nfunction strWithLen(len) {\n  // "
  },
  {
    "path": "src/customGraphQLValidationRules.js",
    "chars": 5921,
    "preview": "import { GraphQLError, getNamedType } from \"graphql\";\n\nexport function OperationsMustHaveNames(context) {\n  return {\n   "
  },
  {
    "path": "src/index.js",
    "chars": 10901,
    "preview": "import fs from \"fs\";\nimport path from \"path\";\nimport {\n  buildClientSchema,\n  buildSchema,\n  specifiedRules as allGraphQ"
  },
  {
    "path": "test/__fixtures__/required-fields-invalid-array.graphql",
    "chars": 40,
    "preview": "query { stories { comments { text } } }\n"
  },
  {
    "path": "test/__fixtures__/required-fields-invalid-no-id.graphql",
    "chars": 38,
    "preview": "query {\n  greetings {\n    hello\n  }\n}\n"
  },
  {
    "path": "test/__fixtures__/required-fields-valid-array.graphql",
    "chars": 43,
    "preview": "query { stories { id comments { text } } }\n"
  },
  {
    "path": "test/__fixtures__/required-fields-valid-id.graphql",
    "chars": 38,
    "preview": "query { greetings { id, hello, hi } }\n"
  },
  {
    "path": "test/__fixtures__/required-fields-valid-no-id.graphql",
    "chars": 39,
    "preview": "query { allFilms { films { title } } }\n"
  },
  {
    "path": "test/customTagName.js",
    "chars": 1169,
    "preview": "import schemaJson from './schema.json';\nimport { isAtLeastGraphQL15 } from './helpers';\n\nimport {\n  rule,\n  ruleTester,\n"
  },
  {
    "path": "test/default.js",
    "chars": 1606,
    "preview": "import { isAtLeastGraphQL15 } from './helpers';\nimport schemaJson from './schema.json';\n\nimport {\n  rule,\n  ruleTester,\n"
  },
  {
    "path": "test/env/apollo.js",
    "chars": 908,
    "preview": "import { isAtLeastGraphQL15 } from '../helpers';\nimport schemaJson from '../schema.json';\n\nimport {\n  rule,\n  ruleTester"
  },
  {
    "path": "test/env/fraql.js",
    "chars": 743,
    "preview": "import schemaJson from '../schema.json';\n\nimport {\n  rule,\n  ruleTester,\n  parserOptions\n} from '../helpers';\n\nconst opt"
  },
  {
    "path": "test/env/index.js",
    "chars": 73,
    "preview": "import './apollo';\nimport './lokka';\nimport './fraql';\nimport './relay';\n"
  },
  {
    "path": "test/env/lokka.js",
    "chars": 3783,
    "preview": "import { isAtLeastGraphQL15 } from '../helpers';\nimport schemaJson from '../schema.json';\n\nimport {\n  rule,\n  ruleTester"
  },
  {
    "path": "test/env/relay.js",
    "chars": 4062,
    "preview": "import schemaJson from '../schema.json';\n\nimport {\n  rule,\n  ruleTester,\n} from '../helpers';\n\nconst options = [\n  {\n   "
  },
  {
    "path": "test/graphqlconfig/index.js",
    "chars": 3866,
    "preview": "import { rules } from '../../src';\nimport { RuleTester } from 'eslint';\n\nconst rule = rules['template-strings'];\n\nconst "
  },
  {
    "path": "test/graphqlconfig/multiproject/.graphqlconfig",
    "chars": 150,
    "preview": "{\n  \"projects\": {\n    \"gql\": {\n      \"schema\": \"../../schema.graphql\"\n    },\n    \"swapi\": {\n      \"schema\": \"../../secon"
  },
  {
    "path": "test/graphqlconfig/multiproject-literal/graphql.config.json",
    "chars": 223,
    "preview": "{\n  \"projects\": {\n    \"gql\": {\n      \"schema\": \"../../schema.graphql\",\n      \"include\": [\"first.graphql\"]\n    },\n    \"sw"
  },
  {
    "path": "test/graphqlconfig/simple/graphql.config.json",
    "chars": 39,
    "preview": "{\n  \"schema\": \"../../schema.graphql\"\n}\n"
  },
  {
    "path": "test/helpers.js",
    "chars": 1262,
    "preview": "import { rules } from '../src';\nimport { RuleTester } from 'eslint';\nimport schemaJson from './schema.json';\nimport path"
  },
  {
    "path": "test/index.js",
    "chars": 211,
    "preview": "require('@babel/register');\n\nrequire('./makeProcessors');\nrequire('./graphqlconfig/');\nrequire('./env');\nrequire('./cust"
  },
  {
    "path": "test/makeProcessors.js",
    "chars": 3750,
    "preview": "import assert from 'assert';\nimport { CLIEngine } from 'eslint';\nimport path from 'path';\n\nimport schemaJson from './sch"
  },
  {
    "path": "test/schema.graphql",
    "chars": 1185,
    "preview": "schema {\n  query: Query\n  mutation: Mutation\n}\n\ntype Mutation {\n  createComment(input: CreateCommentInput): CreateCommen"
  },
  {
    "path": "test/schemaTests/index.js",
    "chars": 3217,
    "preview": "import {\n  isAtLeastGraphQL15,\n  schemaJsonFilepath,\n  secondSchemaJsonFilepath,\n  schemaString,\n  rule,\n  ruleTester,\n "
  },
  {
    "path": "test/second-schema.graphql",
    "chars": 545,
    "preview": "schema {\n  query: Query\n}\n\nenum Episode { NEWHOPE, EMPIRE, JEDI }\n\ninterface Character {\n  id: String!\n  name: String\n  "
  },
  {
    "path": "test/updateSchemaJson.js",
    "chars": 883,
    "preview": "const fs = require('fs');\nconst path = require('path');\nconst graphql = require('graphql');\nconst process = require('pro"
  },
  {
    "path": "test/validationRules/built-in.js",
    "chars": 10721,
    "preview": "import schemaJson from \"../schema.json\";\n\nimport {\n  isAtLeastGraphQL15,\n  requiredArgumentRuleName,\n  rule,\n  ruleTeste"
  },
  {
    "path": "test/validationRules/capitalized-type-name.js",
    "chars": 1367,
    "preview": "import { rules } from \"../../src\";\nimport schemaJson from \"../schema.json\";\n\nimport { ruleTester, parserOptions } from \""
  },
  {
    "path": "test/validationRules/index.js",
    "chars": 145,
    "preview": "import './named-operations';\nimport './required-fields';\nimport './no-deprecated-fields';\nimport './capitalized-type-nam"
  },
  {
    "path": "test/validationRules/named-operations.js",
    "chars": 902,
    "preview": "import { rules } from \"../../src\";\nimport schemaJson from \"../schema.json\";\n\nimport { ruleTester, parserOptions } from \""
  },
  {
    "path": "test/validationRules/no-deprecated-fields.js",
    "chars": 2243,
    "preview": "import { rules } from \"../../src\";\nimport schemaJson from \"../schema.json\";\n\nimport { ruleTester } from \"../helpers\";\n\nc"
  },
  {
    "path": "test/validationRules/required-fields.js",
    "chars": 4076,
    "preview": "import { rules } from \"../../src\";\nimport schemaJson from \"../schema.json\";\n\nimport { ruleTester, parserOptions } from \""
  }
]

About this extraction

This page contains the full source code of the apollostack/eslint-plugin-graphql GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 46 files (105.5 KB), approximately 28.1k tokens, and a symbol index with 17 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!