├── examples ├── prettier │ ├── valid.graphql │ ├── prettier.config.js │ ├── valid.js │ ├── invalid.graphql │ ├── invalid.js │ ├── package.json │ ├── .eslintrc.cjs │ └── eslint.config.js ├── monorepo │ ├── server │ │ └── types │ │ │ ├── root.gql │ │ │ ├── scalar.gql │ │ │ ├── user.gql │ │ │ └── post.gql │ ├── graphql.config.js │ ├── client │ │ ├── graphql │ │ │ └── query.users.gql │ │ └── pages │ │ │ └── index.tsx │ ├── package.json │ └── eslint.config.js ├── custom-rules │ ├── test.graphql │ ├── my-rule.js │ ├── eslint.config.js │ └── package.json ├── programmatic │ ├── fragment.graphql │ ├── fragment2.graphql │ ├── schema.graphql │ ├── query.graphql │ ├── package.json │ ├── eslint.config.js │ └── .eslintrc.cjs ├── code-file │ ├── not-query.js │ ├── graphql.config.js │ ├── schema.graphql │ ├── query.js │ ├── package.json │ ├── eslint.config.js │ └── .eslintrc.cjs ├── graphql-config │ ├── operations │ │ ├── query.graphql │ │ └── user.fragment.graphql │ ├── graphql.config.js │ ├── schema.graphql │ ├── package.json │ ├── eslint.config.js │ └── .eslintrc.cjs ├── multiple-projects-graphql-config │ ├── schema.first-project.graphql │ ├── schema.second-project.graphql │ ├── query.first-project.js │ ├── query.second-project.js │ ├── package.json │ ├── eslint.config.js │ ├── graphql.config.ts │ └── .eslintrc.cjs ├── svelte-code-file │ ├── test.svelte │ ├── package.json │ ├── eslint.config.js │ └── .eslintrc.cjs └── vue-code-file │ ├── test.vue │ ├── package.json │ └── .eslintrc.cjs ├── .npmignore ├── packages ├── plugin │ ├── src │ │ ├── meta.ts │ │ ├── estree-converter │ │ │ └── index.ts │ │ ├── rules │ │ │ ├── unique-enum-value-names │ │ │ │ ├── index.test.ts │ │ │ │ └── snapshot.md │ │ │ ├── no-anonymous-operations │ │ │ │ ├── index.test.ts │ │ │ │ └── snapshot.md │ │ │ ├── no-one-place-fragments │ │ │ │ ├── snapshot.md │ │ │ │ └── index.test.ts │ │ │ ├── unique-operation-name │ │ │ │ ├── snapshot.md │ │ │ │ └── index.test.ts │ │ │ ├── unique-fragment-name │ │ │ │ └── snapshot.md │ │ │ ├── require-nullable-fields-with-oneof │ │ │ │ ├── index.test.ts │ │ │ │ └── snapshot.md │ │ │ ├── require-type-pattern-with-oneof │ │ │ │ ├── snapshot.md │ │ │ │ └── index.test.ts │ │ │ ├── description-style │ │ │ │ └── index.test.ts │ │ │ ├── require-nullable-result-in-root │ │ │ │ └── index.test.ts │ │ │ ├── no-duplicate-fields │ │ │ │ └── index.test.ts │ │ │ ├── no-unused-fields │ │ │ │ └── snapshot.md │ │ │ ├── no-typename-prefix │ │ │ │ └── index.test.ts │ │ │ ├── require-deprecation-date │ │ │ │ └── index.test.ts │ │ │ ├── no-root-type │ │ │ │ └── index.test.ts │ │ │ └── require-deprecation-reason │ │ │ │ └── index.test.ts │ │ ├── configs │ │ │ ├── schema-relay.ts │ │ │ ├── operations-all.ts │ │ │ ├── schema-all.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── cache.ts │ ├── __tests__ │ │ ├── mocks │ │ │ ├── using-config │ │ │ │ ├── nested │ │ │ │ │ └── test.graphql │ │ │ │ ├── .graphqlrc │ │ │ │ └── schema-in-config.graphql │ │ │ ├── post.graphql │ │ │ ├── user.graphql │ │ │ ├── known-fragment-names │ │ │ │ ├── user.gql │ │ │ │ ├── operation-with-undefined-fragment.gql │ │ │ │ └── user-fields.gql │ │ │ ├── user-fields.graphql │ │ │ ├── import-fragments │ │ │ │ ├── fragments │ │ │ │ │ ├── bar-fragment.gql │ │ │ │ │ └── foo-fragment.gql │ │ │ │ ├── missing-import.gql │ │ │ │ ├── invalid-query-default.gql │ │ │ │ ├── same-file.gql │ │ │ │ ├── invalid-query.gql │ │ │ │ ├── valid-query-default.gql │ │ │ │ └── valid-query.gql │ │ │ ├── no-undefined-variables.gql │ │ │ ├── post-fields.graphql │ │ │ ├── possible-type-extension │ │ │ │ ├── separate-graphql-files │ │ │ │ │ ├── extend-type-user.gql │ │ │ │ │ └── type-user.gql │ │ │ │ ├── separate-code-files │ │ │ │ │ ├── extend-type-user.ts │ │ │ │ │ └── type-user.ts │ │ │ │ └── one-graphql-file │ │ │ │ │ └── type-user.gql │ │ │ ├── no-unused-variables.gql │ │ │ ├── test-directives-with-import.graphql │ │ │ ├── user-fields-with-variables.gql │ │ │ ├── no-unused-variables-imported.gql │ │ │ ├── no-one-place-fragments.graphql │ │ │ ├── unique-fragment.js │ │ │ ├── known-fragment-names.ts │ │ │ ├── two-fragments-in-code-file.js │ │ │ ├── user-schema.graphql │ │ │ ├── user-schema.ts │ │ │ └── user-fields-with-nested-fragment.gql │ │ ├── __snapshots__ │ │ │ ├── executable-definitions.spec.md │ │ │ ├── known-directives.spec.md │ │ │ ├── known-fragment-names.spec.md │ │ │ ├── possible-type-extension.spec.md │ │ │ ├── unique-type-names.spec.md │ │ │ ├── no-undefined-variables.spec.md │ │ │ ├── lone-schema-definition.spec.md │ │ │ ├── eslint-directives.spec.md │ │ │ └── fields-on-correct-type.spec.md │ │ ├── no-unused-variables.spec.ts │ │ ├── test-utils.ts │ │ ├── federation.spec.ts │ │ ├── lone-schema-definition.spec.ts │ │ ├── no-undefined-variables.spec.ts │ │ ├── no-unused-fragments.spec.ts │ │ ├── executable-definitions.spec.ts │ │ ├── unique-type-names.spec.ts │ │ ├── fields-on-correct-type.spec.ts │ │ ├── processor-without-graphql-config.spec.ts │ │ ├── processor-with-graphql-config.spec.ts │ │ └── eslint-directives.spec.ts │ ├── serializer.ts │ ├── tsconfig.json │ └── vite.config.ts └── rule-tester │ ├── tsup.config.ts │ ├── tsconfig.json │ └── package.json ├── pnpm-workspace.yaml ├── .npmrc ├── website ├── postcss.config.js ├── app │ ├── icon.png │ ├── icons │ │ ├── vue.svg │ │ ├── half.svg │ │ ├── javascript.svg │ │ ├── graphql.svg │ │ ├── stack.svg │ │ ├── astro.svg │ │ ├── prettier.svg │ │ └── svelte.svg │ ├── graphql-config-info.mdx │ ├── [[...mdxPath]] │ │ └── page.tsx │ └── play │ │ └── button.tsx ├── public │ └── demo.mp4 ├── content │ ├── docs │ │ ├── usage │ │ │ ├── vue.mdx │ │ │ ├── programmatic.mdx │ │ │ ├── svelte.mdx │ │ │ ├── prettier.mdx │ │ │ ├── schema-and-operations.mdx │ │ │ ├── graphql.mdx │ │ │ ├── multiple-projects.mdx │ │ │ ├── custom-rules.mdx │ │ │ ├── astro.mdx │ │ │ └── js.mdx │ │ ├── vscode.mdx │ │ ├── disabling-rules.mdx │ │ └── index.mdx │ └── rules │ │ ├── prettier.md │ │ ├── unique-type-names.mdx │ │ ├── unique-operation-types.mdx │ │ ├── variables-in-allowed-position.mdx │ │ ├── lone-schema-definition.mdx │ │ ├── no-fragment-cycles.mdx │ │ ├── unique-variable-names.mdx │ │ ├── unique-directive-names.mdx │ │ ├── one-field-subscriptions.mdx │ │ ├── deprecated-rules.md │ │ ├── unique-field-definition-names.mdx │ │ ├── unique-argument-names.mdx │ │ ├── unique-input-field-names.mdx │ │ ├── executable-definitions.mdx │ │ ├── no-unused-variables.mdx │ │ ├── variables-are-input-types.mdx │ │ ├── possible-type-extension.mdx │ │ ├── no-undefined-variables.mdx │ │ ├── no-unused-fragments.mdx │ │ ├── lone-anonymous-operation.mdx │ │ ├── scalar-leafs.mdx │ │ ├── provided-required-arguments.mdx │ │ ├── unique-directive-names-per-location.mdx │ │ ├── known-argument-names.mdx │ │ ├── value-literals-of-correct-type.mdx │ │ ├── overlapping-fields-can-be-merged.mdx │ │ ├── require-nullable-fields-with-oneof.mdx │ │ ├── require-type-pattern-with-oneof.mdx │ │ ├── fields-on-correct-type.mdx │ │ ├── fragments-on-composite-type.mdx │ │ ├── possible-fragment-spread.mdx │ │ ├── no-one-place-fragments.mdx │ │ ├── require-nullable-result-in-root.mdx │ │ ├── known-type-names.mdx │ │ ├── relay-page-info.mdx │ │ ├── no-scalar-result-type-on-mutation.mdx │ │ ├── no-typename-prefix.mdx │ │ ├── no-anonymous-operations.mdx │ │ ├── unique-operation-name.mdx │ │ ├── no-unreachable-types.mdx │ │ ├── unique-fragment-name.mdx │ │ ├── require-deprecation-reason.mdx │ │ ├── unique-enum-value-names.mdx │ │ ├── lone-executable-definition.mdx │ │ ├── no-root-type.mdx │ │ ├── known-directives.mdx │ │ ├── description-style.mdx │ │ ├── relay-connection-types.mdx │ │ ├── require-field-of-type-query-in-mutation-result.mdx │ │ ├── relay-arguments.mdx │ │ ├── require-deprecation-date.mdx │ │ └── require-import-fragment.mdx ├── next-sitemap.config.js ├── next-env.d.ts ├── tailwind.config.ts ├── tsconfig.json └── package.json ├── prettier.config.js ├── .prettierignore ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.md │ └── bug_report.md ├── workflows │ ├── release.yml │ ├── pr.yml │ └── website.yml ├── renovate.json └── labels.yml ├── .whitesource ├── .gitignore ├── .changeset ├── README.md └── config.json ├── .vscode ├── launch.json └── settings.json ├── tsconfig.json ├── turbo.json ├── README.md └── LICENSE /examples/prettier/valid.graphql: -------------------------------------------------------------------------------- 1 | scalar Test 2 | -------------------------------------------------------------------------------- /examples/monorepo/server/types/root.gql: -------------------------------------------------------------------------------- 1 | type Query 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tests 3 | node_modules 4 | .bob 5 | temp 6 | -------------------------------------------------------------------------------- /examples/custom-rules/test.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | foo 3 | } 4 | -------------------------------------------------------------------------------- /examples/monorepo/server/types/scalar.gql: -------------------------------------------------------------------------------- 1 | scalar DateTime 2 | -------------------------------------------------------------------------------- /packages/plugin/src/meta.ts: -------------------------------------------------------------------------------- 1 | export const version = process.env.VERSION; 2 | -------------------------------------------------------------------------------- /examples/programmatic/fragment.graphql: -------------------------------------------------------------------------------- 1 | fragment Test on User { 2 | id 3 | } 4 | -------------------------------------------------------------------------------- /examples/code-file/not-query.js: -------------------------------------------------------------------------------- 1 | console.log('should report `no-console` error'); 2 | -------------------------------------------------------------------------------- /examples/programmatic/fragment2.graphql: -------------------------------------------------------------------------------- 1 | fragment Test on User { 2 | name 3 | } 4 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/using-config/nested/test.graphql: -------------------------------------------------------------------------------- 1 | { 2 | hello 3 | } 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - examples/* 4 | - website 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | strict-peer-dependencies=false 2 | shell-emulator=true 3 | enable-pre-post-scripts=true 4 | -------------------------------------------------------------------------------- /examples/code-file/graphql.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | schema: 'schema.graphql', 3 | }; 4 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/using-config/.graphqlrc: -------------------------------------------------------------------------------- 1 | schema: ./schema-in-config.graphql 2 | -------------------------------------------------------------------------------- /website/postcss.config.js: -------------------------------------------------------------------------------- 1 | export { default } from '@theguild/tailwind-config/postcss.config'; 2 | -------------------------------------------------------------------------------- /website/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-eslint/HEAD/website/app/icon.png -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/post.graphql: -------------------------------------------------------------------------------- 1 | query Post { 2 | post { 3 | ...PostFields 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/user.graphql: -------------------------------------------------------------------------------- 1 | query User { 2 | user { 3 | ...UserFields 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/using-config/schema-in-config.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | hello: String 3 | } 4 | -------------------------------------------------------------------------------- /website/public/demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-eslint/HEAD/website/public/demo.mp4 -------------------------------------------------------------------------------- /examples/prettier/prettier.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | endOfLine: 'auto', 3 | singleQuote: true, 4 | }; 5 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/known-fragment-names/user.gql: -------------------------------------------------------------------------------- 1 | { 2 | user { 3 | ...UserFields 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/user-fields.graphql: -------------------------------------------------------------------------------- 1 | fragment UserFields on User { 2 | id 3 | firstName 4 | } 5 | -------------------------------------------------------------------------------- /examples/graphql-config/operations/query.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | user { 3 | name 4 | ...UserFields 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/import-fragments/fragments/bar-fragment.gql: -------------------------------------------------------------------------------- 1 | fragment BarFields on Bar { 2 | id 3 | } 4 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/import-fragments/fragments/foo-fragment.gql: -------------------------------------------------------------------------------- 1 | fragment FooFields on Foo { 2 | id 3 | } 4 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/import-fragments/missing-import.gql: -------------------------------------------------------------------------------- 1 | { 2 | foo { 3 | ...FooFields 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/graphql-config/operations/user.fragment.graphql: -------------------------------------------------------------------------------- 1 | fragment UserFields on User { 2 | id 3 | name 4 | name 5 | } 6 | -------------------------------------------------------------------------------- /examples/prettier/valid.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | const TEST = /* GraphQL */ ` 3 | scalar Test 4 | `; 5 | -------------------------------------------------------------------------------- /examples/code-file/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | user: User! 3 | } 4 | 5 | type User { 6 | id: ID! 7 | name: String! 8 | } 9 | -------------------------------------------------------------------------------- /examples/monorepo/graphql.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | schema: 'server/**/*.gql', 3 | documents: 'client/**/*.{tsx,gql}', 4 | }; 5 | -------------------------------------------------------------------------------- /examples/programmatic/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | user: User! 3 | } 4 | 5 | type User { 6 | id: ID! 7 | name: String! 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/no-undefined-variables.gql: -------------------------------------------------------------------------------- 1 | query User { 2 | user { 3 | id 4 | ...UserFields 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/post-fields.graphql: -------------------------------------------------------------------------------- 1 | fragment PostFields on Post { 2 | user { 3 | ...UserFields 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/graphql-config/graphql.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | schema: 'schema.graphql', 3 | documents: 'operations/*.graphql', 4 | }; 5 | -------------------------------------------------------------------------------- /examples/graphql-config/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | user: User! 3 | } 4 | 5 | type User { 6 | id: ID! 7 | name: String! 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/known-fragment-names/operation-with-undefined-fragment.gql: -------------------------------------------------------------------------------- 1 | { 2 | user { 3 | ...DoesNotExist 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin/src/estree-converter/index.ts: -------------------------------------------------------------------------------- 1 | export * from './converter.js'; 2 | export * from './types.js'; 3 | export * from './utils.js'; 4 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/possible-type-extension/separate-graphql-files/extend-type-user.gql: -------------------------------------------------------------------------------- 1 | extend type User { 2 | firstName: String 3 | } 4 | -------------------------------------------------------------------------------- /examples/programmatic/query.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | user { 3 | id 4 | name 5 | ... on User { 6 | name 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/no-unused-variables.gql: -------------------------------------------------------------------------------- 1 | query ($limit: Int!, $offset: Int!) { 2 | user { 3 | id 4 | ...UserFields 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/monorepo/client/graphql/query.users.gql: -------------------------------------------------------------------------------- 1 | query getUsers { 2 | users { 3 | id 4 | firstName 5 | lastName 6 | createdAt 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/test-directives-with-import.graphql: -------------------------------------------------------------------------------- 1 | #import './user-fields.graphql' 2 | 3 | # eslint-disable-next-line 4 | query { 5 | a 6 | } 7 | -------------------------------------------------------------------------------- /packages/plugin/serializer.ts: -------------------------------------------------------------------------------- 1 | import rawSnapshotSerializer from 'jest-snapshot-serializer-raw/always'; 2 | 3 | expect.addSnapshotSerializer(rawSnapshotSerializer); 4 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/possible-type-extension/separate-graphql-files/type-user.gql: -------------------------------------------------------------------------------- 1 | type User { 2 | id: ID! 3 | } 4 | 5 | type Query { 6 | user: User 7 | } 8 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/import-fragments/invalid-query-default.gql: -------------------------------------------------------------------------------- 1 | #import './fragments/bar-fragment.gql' 2 | query { 3 | foo { 4 | ...FooFields 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/import-fragments/same-file.gql: -------------------------------------------------------------------------------- 1 | { 2 | foo { 3 | ...FooFields 4 | } 5 | } 6 | 7 | fragment FooFields on Foo { 8 | id 9 | } 10 | -------------------------------------------------------------------------------- /website/content/docs/usage/vue.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Vue 3 | icon: VueIcon 4 | --- 5 | 6 | # Usage with `.vue` files 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/import-fragments/invalid-query.gql: -------------------------------------------------------------------------------- 1 | #import FooFields from "./fragments/bar-fragment.gql" 2 | query { 3 | foo { 4 | ...FooFields 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/user-fields-with-variables.gql: -------------------------------------------------------------------------------- 1 | fragment UserFields on User { 2 | firstName 3 | posts(limit: $limit, offset: $offset) { 4 | id 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/multiple-projects-graphql-config/schema.first-project.graphql: -------------------------------------------------------------------------------- 1 | type User { 2 | firstname: String 3 | lastname: String 4 | } 5 | 6 | type Query { 7 | user: User 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/no-unused-variables-imported.gql: -------------------------------------------------------------------------------- 1 | fragment UserFields on User { 2 | firstName 3 | posts(limit: $limit, offset: $offset) { 4 | id 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /website/content/docs/usage/programmatic.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Programmatic 3 | icon: GearIcon 4 | --- 5 | 6 | # Programmatic usage 7 | 8 | 9 | -------------------------------------------------------------------------------- /website/content/docs/usage/svelte.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Svelte 3 | icon: SvelteIcon 4 | --- 5 | 6 | # Usage with `.svelte` files 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/multiple-projects-graphql-config/schema.second-project.graphql: -------------------------------------------------------------------------------- 1 | type AnotherUser { 2 | firstName: String 3 | lastName: String 4 | } 5 | 6 | type Query { 7 | users: [AnotherUser] 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/possible-type-extension/separate-code-files/extend-type-user.ts: -------------------------------------------------------------------------------- 1 | const EXTEND_USER = /* GraphQL */ ` 2 | extend type User { 3 | firstName: String 4 | } 5 | `; 6 | -------------------------------------------------------------------------------- /website/content/docs/usage/prettier.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Prettier 3 | icon: PrettierIcon 4 | --- 5 | 6 | # Usage with `eslint-plugin-prettier` 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/no-one-place-fragments.graphql: -------------------------------------------------------------------------------- 1 | fragment UserFields on User { 2 | id 3 | } 4 | 5 | { 6 | user { 7 | ...UserFields 8 | friends { 9 | ...UserFields 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/possible-type-extension/one-graphql-file/type-user.gql: -------------------------------------------------------------------------------- 1 | type User { 2 | id: ID! 3 | } 4 | 5 | extend type User { 6 | firstName: String 7 | } 8 | 9 | type Query { 10 | user: User 11 | } 12 | -------------------------------------------------------------------------------- /website/next-sitemap.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("next-sitemap").IConfig} */ 2 | export default { 3 | siteUrl: process.env.SITE_URL || 'https://the-guild.dev/graphql/eslint', 4 | generateIndexSitemap: false, 5 | output: 'export', 6 | }; 7 | -------------------------------------------------------------------------------- /packages/rule-tester/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | name: 'eslint-rule-tester', 5 | entry: ['src/*.ts'], 6 | clean: true, 7 | format: 'esm', 8 | dts: true, 9 | }); 10 | -------------------------------------------------------------------------------- /website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/import-fragments/valid-query-default.gql: -------------------------------------------------------------------------------- 1 | # Imports could have extra whitespace and double/single quotes 2 | 3 | # import './fragments/foo-fragment.gql' 4 | 5 | query { 6 | foo { 7 | ...FooFields 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | import prettierConfig from '@theguild/prettier-config'; 2 | 3 | export default { 4 | ...prettierConfig, 5 | plugins: [...prettierConfig.plugins, 'prettier-plugin-tailwindcss'], 6 | tailwindConfig: './website/tailwind.config.ts', 7 | }; 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | packages/plugin/__tests__/__snapshots__/ 2 | packages/plugin/src/rules/*/snapshot.md 3 | examples/prettier/invalid.graphql 4 | examples/prettier/invalid.js 5 | pnpm-lock.yaml 6 | 7 | # prettier ignore doesn't work 8 | website/src/pages/docs/configs.mdx 9 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/import-fragments/valid-query.gql: -------------------------------------------------------------------------------- 1 | # Imports could have extra whitespace and double/single quotes 2 | 3 | # import FooFields from "./fragments/foo-fragment.gql" 4 | 5 | query { 6 | foo { 7 | ...FooFields 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/monorepo/server/types/user.gql: -------------------------------------------------------------------------------- 1 | type User { 2 | id: ID! 3 | firstName: String! 4 | lastName: String! 5 | createdAt: DateTime! 6 | updatedAt: DateTime! 7 | } 8 | 9 | extend type Query { 10 | user(id: ID!): User! 11 | users: [User!]! 12 | } 13 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/possible-type-extension/separate-code-files/type-user.ts: -------------------------------------------------------------------------------- 1 | const USER = /* GraphQL */ ` 2 | type User { 3 | id: ID! 4 | } 5 | `; 6 | 7 | const QUERY = /* GraphQL */ ` 8 | type Query { 9 | user: User 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /website/content/docs/usage/schema-and-operations.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Schema and Operations 3 | icon: HalfIcon 4 | --- 5 | 6 | # Usage to lint both schema/operations 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/monorepo/server/types/post.gql: -------------------------------------------------------------------------------- 1 | type Post { 2 | id: ID! 3 | title: String! 4 | content: String! 5 | author: User! 6 | createdAt: DateTime! 7 | updatedAt: DateTime! 8 | } 9 | 10 | extend type Query { 11 | post(id: ID!): Post! 12 | posts: [Post!]! 13 | } 14 | -------------------------------------------------------------------------------- /website/content/docs/usage/graphql.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: GraphQL Files 3 | icon: GraphQLIcon 4 | --- 5 | 6 | # Usage with `.graphql` files 7 | 8 | 12 | -------------------------------------------------------------------------------- /website/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import tailwindRadix from 'tailwindcss-radix'; 2 | import tailwindConfig from '@theguild/tailwind-config'; 3 | 4 | export default { 5 | ...tailwindConfig, 6 | // @ts-expect-error -- fixme 7 | plugins: [...tailwindConfig.plugins, tailwindRadix()], 8 | }; 9 | -------------------------------------------------------------------------------- /website/app/icons/vue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/unique-fragment.js: -------------------------------------------------------------------------------- 1 | const USER_FIELDS = /* GraphQL */ ` 2 | fragment UserFields on User { 3 | id 4 | } 5 | `; 6 | 7 | const GET_USER = /* GraphQL */ ` 8 | query User { 9 | user { 10 | ...UserFields 11 | } 12 | } 13 | ${USER_FIELDS} 14 | `; 15 | -------------------------------------------------------------------------------- /examples/multiple-projects-graphql-config/query.first-project.js: -------------------------------------------------------------------------------- 1 | import { gql } from 'graphql-tag'; 2 | 3 | /* GraphQL */ ` 4 | fragment UserFields on User { 5 | firstname 6 | lastname 7 | } 8 | `; 9 | 10 | gql` 11 | { 12 | user { 13 | ...UserFields 14 | } 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/known-fragment-names.ts: -------------------------------------------------------------------------------- 1 | const USER_FIELDS = /* GraphQL */ ` 2 | fragment UserFields on User { 3 | id 4 | } 5 | `; 6 | 7 | const GET_USER = /* GraphQL */ ` 8 | query User { 9 | user { 10 | ...UserFields 11 | } 12 | } 13 | ${USER_FIELDS} 14 | `; 15 | -------------------------------------------------------------------------------- /website/content/docs/usage/multiple-projects.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Multiple Projects 3 | icon: StackIcon 4 | --- 5 | 6 | # Usage to lint different schemas 7 | 8 | 12 | -------------------------------------------------------------------------------- /examples/prettier/invalid.graphql: -------------------------------------------------------------------------------- 1 | query User($userId: ID!) { 2 | user(id: $userId) { 3 | id, 4 | name, 5 | isViewerFriend, 6 | profilePicture(size: 50) { 7 | ...PictureFields 8 | } 9 | } 10 | } 11 | 12 | fragment PictureFields on Picture { 13 | uri, 14 | width, 15 | height 16 | } 17 | -------------------------------------------------------------------------------- /examples/code-file/query.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | const GET_USER = /* GraphQL */ ` 4 | query { 5 | user { 6 | name 7 | } 8 | } 9 | `; 10 | 11 | const GET_ANOTHER_USER = /* GraphQL */ ` 12 | query UserQuery { 13 | user { 14 | id 15 | name 16 | } 17 | } 18 | `; 19 | -------------------------------------------------------------------------------- /examples/multiple-projects-graphql-config/query.second-project.js: -------------------------------------------------------------------------------- 1 | import { custom } from 'custom-graphql-tag'; 2 | 3 | /* MyGraphQL */ ` 4 | fragment UserFields on AnotherUser { 5 | firstName 6 | lastName 7 | } 8 | `; 9 | 10 | custom` 11 | { 12 | users { 13 | ...UserFields 14 | } 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/two-fragments-in-code-file.js: -------------------------------------------------------------------------------- 1 | const USER_FIELDS = /* GraphQL */ ` 2 | fragment UserFields on User { 3 | id 4 | firstName 5 | } 6 | `; 7 | 8 | const ALL_USER_FIELDS = /* GraphQL */ ` 9 | fragment UserFields on User { 10 | id 11 | firstName 12 | lastName 13 | } 14 | `; 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Have a question? 4 | url: https://github.com/dimaMachina/graphql-eslint/discussions/new 5 | about: 6 | Not sure about something? need help from the community? have a question to our team? please 7 | ask and answer questions here. 8 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff", 8 | "useMendCheckNames": true 9 | }, 10 | "issueSettings": { 11 | "minSeverityLevel": "LOW", 12 | "issueType": "DEPENDENCY" 13 | } 14 | } -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/user-schema.graphql: -------------------------------------------------------------------------------- 1 | type User { 2 | id: ID! 3 | firstName: String! 4 | posts(limit: Int = 25, offset: Int = 0): [Post!]! 5 | } 6 | 7 | type Post { 8 | id: ID! 9 | title: String! 10 | content: String! 11 | user: User! 12 | } 13 | 14 | type Query { 15 | user: User 16 | post: Post 17 | } 18 | -------------------------------------------------------------------------------- /examples/custom-rules/my-rule.js: -------------------------------------------------------------------------------- 1 | export const rule = { 2 | create(context) { 3 | return { 4 | OperationDefinition(node) { 5 | if (!node.name?.value) { 6 | context.report({ 7 | node, 8 | message: 'Oops, name is required!', 9 | }); 10 | } 11 | }, 12 | }; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /examples/svelte-code-file/test.svelte: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/known-fragment-names/user-fields.gql: -------------------------------------------------------------------------------- 1 | fragment UserFields on User { 2 | id 3 | ... on User { 4 | ...AnotherUserFields 5 | } 6 | posts { 7 | ... on Post { 8 | ...PostFields 9 | } 10 | } 11 | } 12 | 13 | fragment AnotherUserFields on User { 14 | id 15 | } 16 | 17 | fragment PostFields on Post { 18 | id 19 | } 20 | -------------------------------------------------------------------------------- /website/content/docs/usage/custom-rules.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Custom GraphQL Rules 3 | --- 4 | 5 | # Usage with custom GraphQL rules 6 | 7 | 8 | 9 | > [!TIP] 10 | > 11 | > Check out the [custom rules](/docs/custom-rules) guide to learn how to create your own rules. 12 | -------------------------------------------------------------------------------- /website/content/docs/usage/astro.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Astro 3 | icon: AstroIcon 4 | --- 5 | 6 | # Usage with `.astro` files 7 | 8 | > [!NOTE] 9 | > 10 | > GraphQL-ESLint should work with Astro files as well. Feel free to submit a PR with a 11 | > [new example](https://github.com/dimaMachina/graphql-eslint/tree/master/examples) in the 12 | > GraphQL-ESLint repository. 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | 3 | # Dependency directories 4 | node_modules/ 5 | 6 | # TypeScript cache 7 | *.tsbuildinfo 8 | 9 | # Optional eslint cache 10 | .eslintcache 11 | 12 | # dotenv environment variables file 13 | .env 14 | .next/ 15 | dist/ 16 | .bob/ 17 | .idea/ 18 | out/ 19 | website/public/_redirects 20 | website/public/sitemap.xml 21 | website/public/robots.txt 22 | .turbo/ 23 | -------------------------------------------------------------------------------- /examples/monorepo/client/pages/index.tsx: -------------------------------------------------------------------------------- 1 | const GET_POSTS = /* GraphQL */ ` 2 | query Posts { 3 | posts { 4 | id 5 | title 6 | content 7 | author { 8 | id 9 | firstname 10 | } 11 | } 12 | } 13 | `; 14 | 15 | export default function IndexPage() { 16 | return ( 17 |
18 |

Hello world

19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/__snapshots__/executable-definitions.spec.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`executable-definitions > invalid > Invalid #1 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | type Query { t: String } 7 | 8 | #### ❌ Error 9 | 10 | > 1 | type Query { t: String } 11 | | ^^^^ The "Query" definition is not executable. 12 | `; 13 | -------------------------------------------------------------------------------- /examples/prettier/invalid.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | const GET_USER = /* GraphQL */ `query User($userId: ID!) { 3 | user(id: $userId) { 4 | id, 5 | name, 6 | isViewerFriend, 7 | profilePicture(size: 50) { 8 | ...PictureFields 9 | } 10 | } 11 | } 12 | 13 | fragment PictureFields on Picture { 14 | uri, 15 | width, 16 | height 17 | } 18 | ` 19 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/user-schema.ts: -------------------------------------------------------------------------------- 1 | const SCHEMA = /* GraphQL */ ` 2 | type User { 3 | id: ID! 4 | firstName: String! 5 | posts(limit: Int = 25, offset: Int = 0): [Post!]! 6 | } 7 | 8 | type Post { 9 | id: ID! 10 | title: String! 11 | content: String! 12 | user: User! 13 | } 14 | 15 | type Query { 16 | user: User 17 | post: Post 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: [master] 5 | 6 | jobs: 7 | stable: 8 | uses: the-guild-org/shared-config/.github/workflows/release-stable.yml@main 9 | with: 10 | releaseScript: release 11 | nodeVersion: 22 12 | packageManager: pnpm 13 | secrets: 14 | githubToken: ${{ secrets.GITHUB_TOKEN }} 15 | npmToken: ${{ secrets.NPM_TOKEN }} 16 | -------------------------------------------------------------------------------- /packages/rule-tester/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "node16", 6 | "declaration": false, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "strict": true, 10 | "lib": ["ESNext"], 11 | "types": ["vitest/globals"], 12 | "resolveJsonModule": true, 13 | "skipLibCheck": true 14 | }, 15 | "exclude": ["dist"] 16 | } 17 | -------------------------------------------------------------------------------- /website/content/docs/vscode.mdx: -------------------------------------------------------------------------------- 1 | # VSCode Integration 2 | 3 | Use 4 | [ESLint VSCode extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 5 | to integrate ESLint into VSCode. 6 | 7 | For syntax highlighting you need a GraphQL extension (which may potentially have its own linting), 8 | for example 9 | [GraphQL (by GraphQL Foundation)](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql). 10 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/unique-enum-value-names/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ruleTester } from '../../../__tests__/test-utils.js'; 2 | import { rule } from './index.js'; 3 | 4 | ruleTester.run('unique-enum-value-names', rule, { 5 | valid: [], 6 | invalid: [ 7 | { 8 | code: 'enum A { TEST TesT }', 9 | errors: 1, 10 | }, 11 | { 12 | code: 'extend enum A { TEST TesT }', 13 | errors: 1, 14 | }, 15 | ], 16 | }); 17 | -------------------------------------------------------------------------------- /packages/plugin/src/configs/schema-relay.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from 'eslint'; 2 | 3 | export default { 4 | parser: '@graphql-eslint/eslint-plugin', 5 | plugins: ['@graphql-eslint'], 6 | rules: { 7 | '@graphql-eslint/relay-arguments': 'error', 8 | '@graphql-eslint/relay-connection-types': 'error', 9 | '@graphql-eslint/relay-edge-types': 'error', 10 | '@graphql-eslint/relay-page-info': 'error', 11 | }, 12 | } satisfies Linter.LegacyConfig; 13 | -------------------------------------------------------------------------------- /examples/vue-code-file/test.vue: -------------------------------------------------------------------------------- 1 | 6 | 23 | -------------------------------------------------------------------------------- /examples/monorepo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-eslint/example-monorepo", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "author": "Dimitri POSTOLOV", 6 | "private": true, 7 | "scripts": { 8 | "lint": "eslint --cache ." 9 | }, 10 | "dependencies": { 11 | "graphql": "16.10.0" 12 | }, 13 | "devDependencies": { 14 | "@eslint/js": "9.22.0", 15 | "@graphql-eslint/eslint-plugin": "workspace:*", 16 | "eslint": "9.22.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/multiple-projects-graphql-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-eslint/example-multiple-projects-graphql-config", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "author": "Dimitri POSTOLOV", 6 | "private": true, 7 | "scripts": { 8 | "lint": "eslint --cache ." 9 | }, 10 | "dependencies": { 11 | "graphql": "16.10.0" 12 | }, 13 | "devDependencies": { 14 | "@graphql-eslint/eslint-plugin": "workspace:*", 15 | "eslint": "9.22.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/mocks/user-fields-with-nested-fragment.gql: -------------------------------------------------------------------------------- 1 | fragment UserFields on User { 2 | ...AnotherUserFields 3 | posts { 4 | ...PostFields 5 | } 6 | } 7 | 8 | fragment AnotherUserFields on User { 9 | firstName 10 | } 11 | 12 | fragment PostFields on Post { 13 | id 14 | ...AnotherPostFields 15 | } 16 | 17 | fragment AnotherPostFields on Post { 18 | title 19 | ...YetAnotherPostFields 20 | } 21 | 22 | fragment YetAnotherPostFields on Post { 23 | content 24 | } 25 | -------------------------------------------------------------------------------- /website/app/icons/half.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /examples/custom-rules/eslint.config.js: -------------------------------------------------------------------------------- 1 | import graphqlPlugin from '@graphql-eslint/eslint-plugin'; 2 | import { rule } from './my-rule.js'; 3 | 4 | export default [ 5 | { 6 | files: ['**/*.graphql'], 7 | languageOptions: { 8 | parser: graphqlPlugin.parser, 9 | }, 10 | plugins: { 11 | '@internal': { 12 | rules: { 13 | 'my-rule': rule, 14 | }, 15 | }, 16 | }, 17 | rules: { 18 | '@internal/my-rule': 'error', 19 | }, 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/__snapshots__/known-directives.spec.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`known-directives > invalid > should work only with Kind.FIELD 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | scalar Foo @bad 7 | 8 | #### ⚙️ Options 9 | 10 | { 11 | "ignoreClientDirectives": [ 12 | "bad" 13 | ] 14 | } 15 | 16 | #### ❌ Error 17 | 18 | > 1 | scalar Foo @bad 19 | | ^^^ Unknown directive "@bad". 20 | `; 21 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/no-anonymous-operations/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ruleTester } from '../../../__tests__/test-utils.js'; 2 | import { rule } from './index.js'; 3 | 4 | ruleTester.run('no-anonymous-operations', rule, { 5 | valid: ['query myQuery { a }', 'mutation doSomething { a }', 'subscription myData { a }'], 6 | invalid: [ 7 | { code: 'query { a }', errors: 1 }, 8 | { code: 'mutation { renamed: a }', errors: 1 }, 9 | { code: 'subscription { ...someFragmentSpread }', errors: 1 }, 10 | ], 11 | }); 12 | -------------------------------------------------------------------------------- /examples/custom-rules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-eslint/example-custom-rules", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "repository": "https://github.com/dimaMachina/graphql-eslint", 6 | "author": "Dimitri POSTOLOV ", 7 | "private": true, 8 | "scripts": { 9 | "lint": "eslint --cache ." 10 | }, 11 | "dependencies": { 12 | "graphql": "16.10.0" 13 | }, 14 | "devDependencies": { 15 | "@graphql-eslint/eslint-plugin": "workspace:*", 16 | "eslint": "9.22.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/__snapshots__/known-fragment-names.spec.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`known-fragment-names > invalid > should not throw an error on undefined fragment 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | { 7 | 2 | user { 8 | 3 | ...DoesNotExist 9 | 4 | } 10 | 5 | } 11 | 12 | #### ❌ Error 13 | 14 | 2 | user { 15 | > 3 | ...DoesNotExist 16 | | ^^^^^^^^^^^^ Unknown fragment "DoesNotExist". 17 | 4 | } 18 | `; 19 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool 4 | that works with multi-package repos, or single-package repos to help you version and publish your 5 | code. You can find the full documentation for it 6 | [in our repository](https://github.com/changesets/changesets) 7 | 8 | We have a quick list of common questions to get you started engaging with this project in 9 | [our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md) 10 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/__snapshots__/possible-type-extension.spec.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`possible-type-extension > invalid > Invalid #1 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | extend type OtherUser { 7 | 2 | name: String 8 | 3 | } 9 | 10 | #### ❌ Error 11 | 12 | > 1 | extend type OtherUser { 13 | | ^^^^^^^^^ Cannot extend type "OtherUser" because it is not defined. 14 | 2 | name: String 15 | `; 16 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/no-one-place-fragments/snapshot.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`no-one-place-fragments > invalid > should error fragment used in one place 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | fragment UserFields on User { 7 | 2 | id 8 | 3 | firstName 9 | 4 | } 10 | 11 | #### ❌ Error 12 | 13 | > 1 | fragment UserFields on User { 14 | | ^^^^^^^^^^ Fragment \`UserFields\` used only once. Inline him in "146179389.graphql". 15 | 2 | id 16 | `; 17 | -------------------------------------------------------------------------------- /examples/svelte-code-file/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-eslint/example-svelte-code-file", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "author": "Dimitri POSTOLOV", 6 | "private": true, 7 | "scripts": { 8 | "lint": "eslint --cache ." 9 | }, 10 | "dependencies": { 11 | "graphql": "16.10.0" 12 | }, 13 | "devDependencies": { 14 | "@graphql-eslint/eslint-plugin": "workspace:*", 15 | "eslint": "9.22.0", 16 | "svelte": "5.23.2", 17 | "svelte-eslint-parser": "1.3.0", 18 | "svelte2tsx": "0.7.35" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/code-file/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-eslint/example-code-file", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "repository": "https://github.com/dimaMachina/graphql-eslint", 6 | "author": "Dotan Simha ", 7 | "private": true, 8 | "scripts": { 9 | "lint": "eslint --cache ." 10 | }, 11 | "dependencies": { 12 | "graphql": "16.10.0" 13 | }, 14 | "devDependencies": { 15 | "@eslint/js": "9.22.0", 16 | "@graphql-eslint/eslint-plugin": "workspace:*", 17 | "eslint": "9.22.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/graphql-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-eslint/example-graphql-config", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "repository": "https://github.com/dimaMachina/graphql-eslint", 6 | "author": "Dotan Simha ", 7 | "private": true, 8 | "scripts": { 9 | "lint": "eslint --cache ." 10 | }, 11 | "dependencies": { 12 | "graphql": "16.10.0" 13 | }, 14 | "devDependencies": { 15 | "@eslint/js": "9.22.0", 16 | "@graphql-eslint/eslint-plugin": "4.4.0", 17 | "eslint": "9.22.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/programmatic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-eslint/example-programmatic", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "repository": "https://github.com/dimaMachina/graphql-eslint", 6 | "author": "Dotan Simha ", 7 | "private": true, 8 | "scripts": { 9 | "lint": "eslint --cache ." 10 | }, 11 | "dependencies": { 12 | "graphql": "16.10.0" 13 | }, 14 | "devDependencies": { 15 | "@eslint/js": "9.22.0", 16 | "@graphql-eslint/eslint-plugin": "workspace:*", 17 | "eslint": "9.22.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.1.0/schema.json", 3 | "commit": false, 4 | "linked": [], 5 | "access": "restricted", 6 | "baseBranch": "master", 7 | "updateInternalDependencies": "patch", 8 | "ignore": ["@graphql-eslint/example-*", "website"], 9 | "changelog": [ 10 | "@changesets/changelog-github", 11 | { 12 | "repo": "dimaMachina/graphql-eslint" 13 | } 14 | ], 15 | "snapshot": { 16 | "useCalculatedVersion": true, 17 | "prereleaseTemplate": "{tag}-{datetime}-{commit}" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/vue-code-file/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-eslint/example-vue-code-file", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "author": "Dimitri POSTOLOV", 6 | "private": true, 7 | "scripts": { 8 | "lint": "eslint --cache ." 9 | }, 10 | "dependencies": { 11 | "graphql": "16.10.0" 12 | }, 13 | "devDependencies": { 14 | "@graphql-eslint/eslint-plugin": "workspace:*", 15 | "eslint": "9.22.0", 16 | "eslint-merge-processors": "^2.0.0", 17 | "eslint-plugin-vue": "^10.0.0", 18 | "eslint-processor-vue-blocks": "^2.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "nodenext", 5 | "moduleResolution": "node16", 6 | "declaration": false, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "strict": true, 10 | "lib": ["ESNext", "dom"], 11 | "types": ["vitest/globals"], 12 | "strictNullChecks": true, 13 | "resolveJsonModule": true, 14 | "skipLibCheck": true, 15 | "paths": { 16 | "@/*": ["./src/*"], 17 | "@graphql-eslint/eslint-plugin": ["./src/index.ts"] 18 | } 19 | }, 20 | "exclude": ["dist"] 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Debug Tests", 11 | "skipFiles": ["/**"], 12 | "program": "${workspaceFolder}/node_modules/.bin/vitest", 13 | "preLaunchTask": "tsc: build - tsconfig.json", 14 | "outFiles": [] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/__snapshots__/unique-type-names.spec.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`unique-type-names > invalid > Invalid #1 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | type Query { 7 | 2 | foo: String 8 | 3 | } 9 | 4 | 10 | 5 | type Query { 11 | 6 | bar: Boolean 12 | 7 | } 13 | 14 | #### ❌ Error 15 | 16 | > 1 | type Query { 17 | | ^^^^^ There can be only one type named "Query". 18 | 2 | foo: String 19 | `; 20 | -------------------------------------------------------------------------------- /examples/graphql-config/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import graphqlPlugin from '@graphql-eslint/eslint-plugin'; 3 | 4 | export default [ 5 | { 6 | files: ['**/*.js'], 7 | rules: js.configs.recommended.rules, 8 | }, 9 | { 10 | files: ['**/*.graphql'], 11 | languageOptions: { 12 | parser: graphqlPlugin.parser, 13 | }, 14 | plugins: { 15 | '@graphql-eslint': graphqlPlugin, 16 | }, 17 | rules: { 18 | '@graphql-eslint/no-anonymous-operations': 'error', 19 | '@graphql-eslint/no-duplicate-fields': 'error', 20 | }, 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | on: 3 | pull_request: 4 | branches: [master] 5 | 6 | jobs: 7 | # dependencies: 8 | # uses: the-guild-org/shared-config/.github/workflows/changesets-dependencies.yaml@main 9 | # secrets: 10 | # githubToken: ${{ secrets.GUILD_BOT_TOKEN }} 11 | 12 | release: 13 | uses: the-guild-org/shared-config/.github/workflows/release-snapshot.yml@main 14 | with: 15 | npmTag: alpha 16 | buildScript: prerelease 17 | nodeVersion: 22 18 | packageManager: pnpm 19 | secrets: 20 | githubToken: ${{ secrets.GITHUB_TOKEN }} 21 | npmToken: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /examples/prettier/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-eslint/example-prettier", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "repository": "https://github.com/dimaMachina/graphql-eslint", 6 | "author": "JounQin ", 7 | "private": true, 8 | "scripts": { 9 | "lint": "eslint --cache ." 10 | }, 11 | "dependencies": { 12 | "graphql": "16.10.0" 13 | }, 14 | "devDependencies": { 15 | "@eslint/js": "9.22.0", 16 | "@graphql-eslint/eslint-plugin": "workspace:*", 17 | "eslint": "9.22.0", 18 | "eslint-config-prettier": "10.1.1", 19 | "eslint-plugin-prettier": "5.2.3", 20 | "prettier": "3.5.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.DS_Store": true, 6 | "**/node_modules": true, 7 | "test-lib": true, 8 | "lib": true, 9 | "coverage": true, 10 | "npm": true, 11 | "**/dist": true 12 | }, 13 | "typescript.tsdk": "node_modules/typescript/lib", 14 | "eslint.workingDirectories": [ 15 | { 16 | "mode": "auto", 17 | "changeProcessCWD": true 18 | } 19 | ], 20 | "eslint.validate": [ 21 | "javascript", 22 | "javascriptreact", 23 | "typescript", 24 | "typescriptreact", 25 | "graphql", 26 | "vue", 27 | "svelte" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for the core of this project 4 | --- 5 | 6 | **Is your feature request related to a problem? Please describe.** 7 | 8 | 9 | 10 | **Describe the solution you'd like** 11 | 12 | 13 | 14 | **Describe alternatives you've considered** 15 | 16 | 17 | 18 | **Additional context** 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/unique-operation-name/snapshot.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`unique-operation-name > invalid > Invalid #1 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | query test { bar } 7 | 8 | #### ❌ Error 9 | 10 | > 1 | query test { bar } 11 | | ^^^^ Operation named "test" already defined in: 12 | 6844040.graphql 13 | `; 14 | 15 | exports[`unique-operation-name > invalid > Invalid #2 1`] = ` 16 | #### ⌨️ Code 17 | 18 | 1 | query test { bar } 19 | 20 | #### ❌ Error 21 | 22 | > 1 | query test { bar } 23 | | ^^^^ Operation named "test" already defined in: 24 | 6844040.graphql 25 | 84823255.graphql 26 | `; 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "baseUrl": ".", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "importHelpers": true, 9 | "experimentalDecorators": true, 10 | "module": "Node16", 11 | "target": "es2019", 12 | "lib": ["ESNext"], 13 | "moduleResolution": "node", 14 | "emitDecoratorMetadata": true, 15 | "sourceMap": true, 16 | "declaration": true, 17 | "noImplicitThis": true, 18 | "alwaysStrict": true, 19 | "noImplicitReturns": true, 20 | "noUnusedLocals": true, 21 | "resolveJsonModule": true, 22 | "skipLibCheck": true 23 | }, 24 | "include": ["scripts"] 25 | } 26 | -------------------------------------------------------------------------------- /website/app/icons/javascript.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>the-guild-org/shared-config:renovate"], 4 | "automerge": true, 5 | "major": { 6 | "automerge": false 7 | }, 8 | "lockFileMaintenance": { 9 | "enabled": true, 10 | "automerge": true 11 | }, 12 | "packageRules": [ 13 | { 14 | "matchUpdateTypes": ["minor", "patch"], 15 | "groupName": "all non-major dependencies", 16 | "groupSlug": "all-minor-patch", 17 | "matchPackageNames": [ 18 | "!/@changesets/*/", 19 | "!/typescript/", 20 | "!/^@theguild//", 21 | "!/next/", 22 | "!/husky/", 23 | "*" 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/__snapshots__/no-undefined-variables.spec.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`no-undefined-variables > invalid > Invalid #1 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | query User { 7 | 2 | user { 8 | 3 | id 9 | 4 | ...UserFields 10 | 5 | } 11 | 6 | } 12 | 13 | #### ❌ Error 1/2 14 | 15 | 2 | user { 16 | > 3 | id 17 | | ^ Variable "$limit" is not defined by operation "User". 18 | 4 | ...UserFields 19 | 20 | #### ❌ Error 2/2 21 | 22 | 2 | user { 23 | > 3 | id 24 | | ^ Variable "$offset" is not defined by operation "User". 25 | 4 | ...UserFields 26 | `; 27 | -------------------------------------------------------------------------------- /examples/prettier/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Legacy config example, should be run with `ESLINT_USE_FLAT_CONFIG=false` environment variable in ESLint 9 3 | */ 4 | 5 | module.exports = { 6 | root: true, 7 | overrides: [ 8 | { 9 | files: ['*.js'], 10 | processor: '@graphql-eslint/graphql', 11 | extends: ['eslint:recommended', 'plugin:prettier/recommended'], 12 | env: { 13 | es2022: true, 14 | }, 15 | parserOptions: { 16 | sourceType: 'module', 17 | }, 18 | }, 19 | { 20 | files: ['*.graphql'], 21 | parser: '@graphql-eslint/eslint-plugin', 22 | plugins: ['@graphql-eslint'], 23 | rules: { 24 | 'prettier/prettier': 'error', 25 | }, 26 | }, 27 | ], 28 | }; 29 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/no-unused-variables.spec.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path'; 2 | import { GRAPHQL_JS_VALIDATIONS } from '../src/rules/graphql-js-validation.js'; 3 | import { ruleTester } from './test-utils.js'; 4 | 5 | ruleTester.run('no-unused-variables', GRAPHQL_JS_VALIDATIONS['no-unused-variables'], { 6 | valid: [ 7 | { 8 | filename: join(__dirname, 'mocks/no-unused-variables.gql'), 9 | code: ruleTester.fromMockFile('no-unused-variables.gql'), 10 | parserOptions: { 11 | graphQLConfig: { 12 | schema: join(__dirname, 'mocks/user-schema.graphql'), 13 | documents: join(__dirname, 'mocks/user-fields-with-variables.gql'), 14 | }, 15 | }, 16 | }, 17 | ], 18 | invalid: [], 19 | }); 20 | -------------------------------------------------------------------------------- /website/app/icons/graphql.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /website/content/docs/disabling-rules.mdx: -------------------------------------------------------------------------------- 1 | # Disabling Rules 2 | 3 | The `graphql-eslint` parser looks for GraphQL comments syntax (marked with `#`) and will send it to 4 | ESLint as directives. That means, you can use ESLint directives syntax to hint ESLint, just like in 5 | any other type of files. 6 | 7 | To disable ESLint for a specific line, you can do: 8 | 9 | ```graphql 10 | # eslint-disable-next-line 11 | type Query { 12 | foo: String! 13 | } 14 | ``` 15 | 16 | You can also specify specific rules to disable, apply it over the entire file, 17 | `eslint-disable-next-line` or current `eslint-disable-line`. 18 | 19 | You can find a list of 20 | [ESLint directives here](https://eslint.org/docs/latest/user-guide/configuring/rules#using-configuration-comments-1). 21 | -------------------------------------------------------------------------------- /packages/rule-tester/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@theguild/eslint-rule-tester", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "engines": { 7 | "node": ">=18" 8 | }, 9 | "exports": { 10 | "./package.json": "./package.json", 11 | ".": { 12 | "import": "./dist/index.js", 13 | "types": "./dist/index.d.ts" 14 | } 15 | }, 16 | "scripts": { 17 | "build": "tsup", 18 | "dev": "tsup --watch", 19 | "typecheck": "tsc --noEmit" 20 | }, 21 | "peerDependencies": { 22 | "eslint": "9.22.0" 23 | }, 24 | "dependencies": { 25 | "@babel/code-frame": "^7.18.6" 26 | }, 27 | "devDependencies": { 28 | "@types/babel__code-frame": "7.0.6", 29 | "@types/node": "22.13.10", 30 | "eslint": "9.22.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /website/content/rules/prettier.md: -------------------------------------------------------------------------------- 1 | # `prettier` Rule 2 | 3 | `eslint-plugin-prettier` supports `.graphql` files, and `v4.1.0` supports `graphql` blocks even 4 | better. You need to do the following: 5 | 6 | ```json filename=".eslintrc.json" 7 | { 8 | "overrides": [ 9 | { 10 | "files": ["*.js"], 11 | "processor": "@graphql-eslint/graphql", 12 | "extends": ["plugin:prettier/recommended"] 13 | }, 14 | { 15 | "files": ["*.graphql"], 16 | "parser": "@graphql-eslint/eslint-plugin", 17 | "plugins": ["@graphql-eslint"], 18 | "rules": { 19 | "prettier/prettier": "error" 20 | } 21 | } 22 | ] 23 | } 24 | ``` 25 | 26 | You can take 27 | [`this repository`](https://github.com/dimaMachina/graphql-eslint/tree/master/examples/prettier) as 28 | example. 29 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "module": "esnext", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "lib": ["dom", "dom.iterable", "esnext"], 10 | "allowJs": true, 11 | "noEmit": true, 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@icons/*": ["app/icons/*"] 20 | }, 21 | "plugins": [ 22 | { 23 | "name": "next" 24 | } 25 | ] 26 | }, 27 | "include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/unique-fragment-name/snapshot.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`unique-fragment-name > invalid > Invalid #1 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | fragment HasIdFields on U { a b c } 7 | 8 | #### ❌ Error 9 | 10 | > 1 | fragment HasIdFields on U { a b c } 11 | | ^^^^^^^^^^^ Fragment named "HasIdFields" already defined in: 12 | -1866344359.graphql 13 | `; 14 | 15 | exports[`unique-fragment-name > invalid > Invalid #2 1`] = ` 16 | #### ⌨️ Code 17 | 18 | 1 | fragment HasIdFields on U { a b c } 19 | 20 | #### ❌ Error 21 | 22 | > 1 | fragment HasIdFields on U { a b c } 23 | | ^^^^^^^^^^^ Fragment named "HasIdFields" already defined in: 24 | -1559743294.graphql 25 | -1866344359.graphql 26 | `; 27 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "build": { 4 | "outputs": ["dist/**"], 5 | "dependsOn": [ 6 | // Run `build` in workspaces I depend on first 7 | "^build" 8 | ] 9 | }, 10 | "dev": { 11 | "dependsOn": [ 12 | // Run `dev` in workspaces I depend on first 13 | "^dev" 14 | ], 15 | // Never cache anything (including logs) emitted by a `dev` task 16 | "cache": false, 17 | "persistent": true 18 | }, 19 | "typecheck": { 20 | "dependsOn": [ 21 | // Run `build` in workspaces I depend on first 22 | "^build" 23 | ] 24 | }, 25 | "test": { 26 | "dependsOn": [ 27 | // A workspace's `test` command depends on its own `build` commands first being completed 28 | "build" 29 | ] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /website/app/graphql-config-info.mdx: -------------------------------------------------------------------------------- 1 | import { compileMdx, MDXRemote } from '@theguild/components/server' 2 | 3 | export async function GraphqlConfigInfo({ name, configKey = name }) { 4 | const rawMdx = `> [!NOTE] 5 | > 6 | > If you have a 7 | > [GraphQL Config configuration file](https://the-guild.dev/graphql/config/docs/user/usage) in your 8 | > project, the GraphQL-ESLint parser will automatically use it to load your GraphQL ${name}. 9 | > 10 | > You should specify a \`${configKey}\` key in the GraphQL Config configuration file to load your 11 | > GraphQL ${name}. 12 | > 13 | > Alternatively, you can pass GraphQL Config options programmatically via 14 | > \`languageOptions.parserOptions.graphQLConfig\` in your \`eslint.config.js\` file.` 15 | 16 | const rawJs = await compileMdx(rawMdx) 17 | 18 | return } 19 | -------------------------------------------------------------------------------- /packages/plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | import { configs } from './configs/index.js'; 2 | import { parseForESLint, parser } from './parser.js'; 3 | import { processor } from './processor.js'; 4 | import { rules } from './rules/index.js'; 5 | 6 | export * from './types.js'; 7 | export type { IGraphQLConfig } from 'graphql-config'; 8 | export type { GraphQLTagPluckOptions } from '@graphql-tools/graphql-tag-pluck'; 9 | 10 | export { requireGraphQLSchema, requireGraphQLOperations } from './utils.js'; 11 | 12 | export const processors = { graphql: processor }; 13 | 14 | export { rules, configs, parser, parseForESLint }; 15 | 16 | // eslint-disable-next-line import/no-default-export -- It's common practice for ESLint plugins that supports Flat config to use the default export 17 | export default { 18 | parser, 19 | processor, 20 | rules, 21 | configs, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/__snapshots__/lone-schema-definition.spec.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`lone-schema-definition > invalid > Invalid #1 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | type Query { 7 | 2 | foo: String 8 | 3 | } 9 | 4 | 10 | 5 | schema { 11 | 6 | query: Query 12 | 7 | } 13 | 8 | 14 | 9 | type RootQuery { 15 | 10 | foo: String 16 | 11 | } 17 | 12 | 18 | 13 | schema { 19 | 14 | query: RootQuery 20 | 15 | } 21 | 22 | #### ❌ Error 23 | 24 | 12 | 25 | > 13 | schema { 26 | | ^^^^^^ Must provide only one schema definition. 27 | 14 | query: RootQuery 28 | `; 29 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | - color: 0366d6 2 | description: Pull requests that update a dependency file 3 | name: dependencies 4 | - color: 0075ca 5 | description: Improvements or additions to documentation 6 | name: documentation 7 | - color: cfd3d7 8 | description: This issue or pull request already exists 9 | name: duplicate 10 | - color: 7057ff 11 | description: Good for newcomers 12 | name: good first issue 13 | - color: 008672 14 | description: Extra attention is needed 15 | name: help wanted 16 | - color: e4e669 17 | description: This doesn't seem right 18 | name: invalid 19 | - color: 50e087 20 | name: new rule 21 | - color: d876e3 22 | description: Further information is requested 23 | name: question 24 | - color: f78ff0 25 | name: waiting for release 26 | - color: ffffff 27 | description: This will not be worked on 28 | name: wontfix 29 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/test-utils.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from 'eslint'; 2 | import graphqlPlugin, { ParserConfigGraphQLConfig } from '@graphql-eslint/eslint-plugin'; 3 | import { RuleTester } from '@theguild/eslint-rule-tester'; 4 | 5 | export const DEFAULT_CONFIG: Linter.Config = { 6 | languageOptions: { 7 | parser: graphqlPlugin.parser, 8 | }, 9 | }; 10 | 11 | export type ParserOptionsForTests = { 12 | graphQLConfig: Partial; 13 | }; 14 | 15 | export const ruleTester = new RuleTester(DEFAULT_CONFIG); 16 | 17 | export function withSchema({ code, ...rest }: T) { 18 | return { 19 | code, 20 | parserOptions: { 21 | graphQLConfig: { 22 | schema: code, 23 | }, 24 | } satisfies ParserOptionsForTests, 25 | ...rest, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /website/app/icons/stack.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/federation.spec.ts: -------------------------------------------------------------------------------- 1 | import { GRAPHQL_JS_VALIDATIONS } from '../src/rules/graphql-js-validation.js'; 2 | import { ruleTester, withSchema } from './test-utils.js'; 3 | 4 | ruleTester.run('federation', GRAPHQL_JS_VALIDATIONS['known-directives'], { 5 | valid: [ 6 | withSchema({ 7 | name: 'should parse federation directive without errors', 8 | code: /* GraphQL */ ` 9 | scalar DateTime 10 | 11 | type Post @key(fields: "id") { 12 | id: ID! 13 | title: String 14 | createdAt: DateTime 15 | modifiedAt: DateTime 16 | } 17 | 18 | type Query { 19 | post: Post! 20 | posts: [Post!] 21 | } 22 | 23 | extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 24 | `, 25 | }), 26 | ], 27 | invalid: [], 28 | }); 29 | -------------------------------------------------------------------------------- /examples/multiple-projects-graphql-config/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import graphqlPlugin from '@graphql-eslint/eslint-plugin'; 3 | 4 | export default [ 5 | { 6 | files: ['**/*.js'], 7 | processor: graphqlPlugin.processor, 8 | rules: js.configs.recommended.rules, 9 | }, 10 | { 11 | // Setup GraphQL Parser 12 | files: ['**/*.graphql'], 13 | languageOptions: { 14 | parser: graphqlPlugin.parser, 15 | }, 16 | plugins: { 17 | '@graphql-eslint': graphqlPlugin, 18 | }, 19 | }, 20 | { 21 | files: ['schema.*.graphql'], 22 | rules: { 23 | ...graphqlPlugin.configs['flat/schema-recommended'].rules, 24 | '@graphql-eslint/require-description': 'off', 25 | }, 26 | }, 27 | { 28 | files: ['**/*.js/*.graphql'], 29 | rules: graphqlPlugin.configs['flat/operations-recommended'].rules, 30 | }, 31 | ]; 32 | -------------------------------------------------------------------------------- /examples/prettier/eslint.config.js: -------------------------------------------------------------------------------- 1 | import prettierConfig from 'eslint-config-prettier'; 2 | import prettierPlugin from 'eslint-plugin-prettier'; 3 | import js from '@eslint/js'; 4 | import graphqlPlugin from '@graphql-eslint/eslint-plugin'; 5 | 6 | export default [ 7 | { 8 | plugins: { 9 | prettier: { rules: prettierPlugin.rules }, 10 | }, 11 | }, 12 | { 13 | files: ['**/*.js'], 14 | processor: graphqlPlugin.processor, 15 | rules: { 16 | ...js.configs.recommended.rules, 17 | ...prettierConfig.rules, 18 | ...prettierPlugin.configs.recommended.rules, 19 | }, 20 | }, 21 | { 22 | files: ['**/*.graphql'], 23 | languageOptions: { 24 | parser: graphqlPlugin.parser, 25 | }, 26 | plugins: { 27 | '@graphql-eslint': graphqlPlugin, 28 | }, 29 | rules: { 30 | 'prettier/prettier': 'error', 31 | }, 32 | }, 33 | ]; 34 | -------------------------------------------------------------------------------- /examples/multiple-projects-graphql-config/graphql.config.ts: -------------------------------------------------------------------------------- 1 | import type { GraphQLTagPluckOptions, IGraphQLConfig } from '@graphql-eslint/eslint-plugin'; 2 | 3 | const config: IGraphQLConfig = { 4 | projects: { 5 | firstProject: { 6 | schema: 'schema.first-project.graphql', 7 | documents: 'query.first-project.js', 8 | }, 9 | secondProject: { 10 | schema: 'schema.second-project.graphql', 11 | documents: 'query.second-project.js', 12 | extensions: { 13 | // in case you want to use different names for magic comment and module identifier 14 | pluckConfig: { 15 | modules: [{ name: 'custom-graphql-tag', identifier: 'custom' }], 16 | globalGqlIdentifierName: 'custom', 17 | gqlMagicComment: 'MyGraphQL', 18 | } satisfies GraphQLTagPluckOptions, 19 | }, 20 | }, 21 | }, 22 | }; 23 | 24 | export default config; 25 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/lone-schema-definition.spec.ts: -------------------------------------------------------------------------------- 1 | import { GRAPHQL_JS_VALIDATIONS } from '../src/rules/graphql-js-validation.js'; 2 | import { ruleTester } from './test-utils.js'; 3 | 4 | ruleTester.run('lone-schema-definition', GRAPHQL_JS_VALIDATIONS['lone-schema-definition'], { 5 | valid: [ 6 | /* GraphQL */ ` 7 | type Query { 8 | foo: String 9 | } 10 | 11 | schema { 12 | query: Query 13 | } 14 | `, 15 | ], 16 | invalid: [ 17 | { 18 | code: /* GraphQL */ ` 19 | type Query { 20 | foo: String 21 | } 22 | 23 | schema { 24 | query: Query 25 | } 26 | 27 | type RootQuery { 28 | foo: String 29 | } 30 | 31 | schema { 32 | query: RootQuery 33 | } 34 | `, 35 | errors: [{ message: 'Must provide only one schema definition.' }], 36 | }, 37 | ], 38 | }); 39 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/no-undefined-variables.spec.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path'; 2 | import { GRAPHQL_JS_VALIDATIONS } from '../src/rules/graphql-js-validation.js'; 3 | import { ruleTester } from './test-utils.js'; 4 | 5 | ruleTester.run('no-undefined-variables', GRAPHQL_JS_VALIDATIONS['no-undefined-variables'], { 6 | valid: [], 7 | invalid: [ 8 | { 9 | filename: join(__dirname, 'mocks/no-undefined-variables.gql'), 10 | code: ruleTester.fromMockFile('no-undefined-variables.gql'), 11 | parserOptions: { 12 | graphQLConfig: { 13 | schema: join(__dirname, 'mocks/user-schema.graphql'), 14 | documents: join(__dirname, 'mocks/user-fields-with-variables.gql'), 15 | }, 16 | }, 17 | errors: [ 18 | { message: 'Variable "$limit" is not defined by operation "User".' }, 19 | { message: 'Variable "$offset" is not defined by operation "User".' }, 20 | ], 21 | }, 22 | ], 23 | }); 24 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/unique-enum-value-names/snapshot.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`unique-enum-value-names > invalid > Invalid #1 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | enum A { TEST TesT } 7 | 8 | #### ❌ Error 9 | 10 | > 1 | enum A { TEST TesT } 11 | | ^^^^ Unexpected case-insensitive enum values duplicates for enum value "TesT" in enum "A" 12 | 13 | #### 💡 Suggestion: Remove \`TesT\` enum value 14 | 15 | 1 | enum A { TEST } 16 | `; 17 | 18 | exports[`unique-enum-value-names > invalid > Invalid #2 1`] = ` 19 | #### ⌨️ Code 20 | 21 | 1 | extend enum A { TEST TesT } 22 | 23 | #### ❌ Error 24 | 25 | > 1 | extend enum A { TEST TesT } 26 | | ^^^^ Unexpected case-insensitive enum values duplicates for enum value "TesT" in enum "A" 27 | 28 | #### 💡 Suggestion: Remove \`TesT\` enum value 29 | 30 | 1 | extend enum A { TEST } 31 | `; 32 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/no-unused-fragments.spec.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path'; 2 | import { GRAPHQL_JS_VALIDATIONS } from '../src/rules/graphql-js-validation.js'; 3 | import { ruleTester } from './test-utils.js'; 4 | 5 | ruleTester.run('no-unused-fragments', GRAPHQL_JS_VALIDATIONS['no-unused-fragments'], { 6 | valid: [ 7 | { 8 | name: 'should find file with operation definition that import current fragment', 9 | filename: join(__dirname, 'mocks/user-fields.graphql'), 10 | code: ruleTester.fromMockFile('user-fields.graphql'), 11 | parserOptions: { 12 | graphQLConfig: { 13 | schema: join(__dirname, 'mocks/user-schema.graphql'), 14 | documents: [ 15 | join(__dirname, 'mocks/user-fields.graphql'), 16 | join(__dirname, 'mocks/post-fields.graphql'), 17 | join(__dirname, 'mocks/post.graphql'), 18 | ], 19 | }, 20 | }, 21 | }, 22 | ], 23 | invalid: [], 24 | }); 25 | -------------------------------------------------------------------------------- /examples/code-file/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import graphqlPlugin from '@graphql-eslint/eslint-plugin'; 3 | 4 | export default [ 5 | { 6 | files: ['**/*.js'], 7 | processor: graphqlPlugin.processor, 8 | rules: { 9 | ...js.configs.recommended.rules, 10 | 'no-console': 'error', 11 | }, 12 | }, 13 | { 14 | files: ['**/*.graphql'], 15 | languageOptions: { 16 | parser: graphqlPlugin.parser, 17 | }, 18 | plugins: { 19 | '@graphql-eslint': graphqlPlugin, 20 | }, 21 | rules: { 22 | '@graphql-eslint/no-anonymous-operations': 'error', 23 | '@graphql-eslint/naming-convention': [ 24 | 'error', 25 | { 26 | OperationDefinition: { 27 | style: 'PascalCase', 28 | forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'], 29 | forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'], 30 | }, 31 | }, 32 | ], 33 | }, 34 | }, 35 | ]; 36 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/require-nullable-fields-with-oneof/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ruleTester } from '../../../__tests__/test-utils.js'; 2 | import { rule } from './index.js'; 3 | 4 | ruleTester.run('require-nullable-fields-with-oneof', rule, { 5 | valid: [ 6 | /* GraphQL */ ` 7 | input Input @oneOf { 8 | foo: [String] 9 | bar: Int 10 | } 11 | `, 12 | /* GraphQL */ ` 13 | type User @oneOf { 14 | foo: String 15 | bar: [Int!] 16 | } 17 | `, 18 | ], 19 | invalid: [ 20 | { 21 | name: 'should validate `input`', 22 | code: /* GraphQL */ ` 23 | input Input @oneOf { 24 | foo: String! 25 | bar: [Int]! 26 | } 27 | `, 28 | errors: 2, 29 | }, 30 | { 31 | name: 'should validate `type`', 32 | code: /* GraphQL */ ` 33 | type Type @oneOf { 34 | foo: String! 35 | bar: Int 36 | } 37 | `, 38 | errors: 1, 39 | }, 40 | ], 41 | }); 42 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/__snapshots__/eslint-directives.spec.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`test-directives > invalid > Invalid #1 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | # eslint-disable-next-line non-existing-rule 7 | 2 | { 8 | 3 | a 9 | 4 | } 10 | 11 | #### ❌ Error 1/2 12 | 13 | > 1 | # eslint-disable-next-line non-existing-rule 14 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition for rule 'non-existing-rule' was not found. 15 | 2 | { 16 | 17 | #### ❌ Error 2/2 18 | 19 | 1 | # eslint-disable-next-line non-existing-rule 20 | > 2 | { 21 | | ^^^^^ Anonymous GraphQL operations are forbidden. Make sure to name your query! 22 | 3 | a 23 | 24 | #### 💡 Suggestion: Rename to \`a\` 25 | 26 | 1 | # eslint-disable-next-line non-existing-rule 27 | 2 | query a { 28 | 3 | a 29 | 4 | } 30 | `; 31 | -------------------------------------------------------------------------------- /website/content/rules/unique-type-names.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'A GraphQL document is only valid if all defined types have unique names.' 3 | --- 4 | 5 | # `unique-type-names` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Schema` 11 | - Rule name: `@graphql-eslint/unique-type-names` 12 | - Requires GraphQL Schema: `false` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | A GraphQL document is only valid if all defined types have unique names. 18 | 19 | > This rule is a wrapper around a `graphql-js` validation function. 20 | 21 | ## Resources 22 | 23 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/UniqueTypeNamesRule.ts) 24 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/UniqueTypeNamesRule-test.ts) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # GraphQL-ESLint 4 | 5 | [![npm version](https://badge.fury.io/js/%40graphql-eslint%2Feslint-plugin.svg)](https://badge.fury.io/js/%40graphql-eslint%2Feslint-plugin) 6 | 7 | ## Documentation 8 | 9 | [https://the-guild.dev/graphql/eslint](https://the-guild.dev/graphql/eslint) 10 | 11 | ## Contributions 12 | 13 | Contributions, issues and feature requests are very welcome. If you are using this package and fixed 14 | a bug for yourself, please consider submitting a PR! 15 | 16 | And if this is your first time contributing to this project, please do read our 17 | [Contributor Workflow Guide](https://github.com/the-guild-org/Stack/blob/master/CONTRIBUTING.md) 18 | before you get started off. 19 | 20 | ## Code of Conduct 21 | 22 | Help us keep GraphQL ESLint open and inclusive. Please read and follow our 23 | [Code of Conduct](https://github.com/the-guild-org/Stack/blob/master/CODE_OF_CONDUCT.md) as adopted 24 | from [Contributor Covenant](https://contributor-covenant.org). 25 | 26 | ## License 27 | 28 | Released under the [MIT license](./LICENSE). 29 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/require-type-pattern-with-oneof/snapshot.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`require-type-pattern-with-oneof > invalid > should validate \`error\` field 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | type T @oneOf { 7 | 2 | ok: Ok 8 | 3 | err: Error 9 | 4 | } 10 | 11 | #### ❌ Error 12 | 13 | > 1 | type T @oneOf { 14 | | ^ type "T" is defined as output with "@oneOf" and must be defined with "error" field 15 | 2 | ok: Ok 16 | `; 17 | 18 | exports[`require-type-pattern-with-oneof > invalid > should validate \`ok\` field 1`] = ` 19 | #### ⌨️ Code 20 | 21 | 1 | type T @oneOf { 22 | 2 | notok: Ok 23 | 3 | error: Error 24 | 4 | } 25 | 26 | #### ❌ Error 27 | 28 | > 1 | type T @oneOf { 29 | | ^ type "T" is defined as output with "@oneOf" and must be defined with "ok" field 30 | 2 | notok: Ok 31 | `; 32 | -------------------------------------------------------------------------------- /website/content/rules/unique-operation-types.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'A GraphQL document is only valid if it has only one type per operation.' 3 | --- 4 | 5 | # `unique-operation-types` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Schema` 11 | - Rule name: `@graphql-eslint/unique-operation-types` 12 | - Requires GraphQL Schema: `false` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | A GraphQL document is only valid if it has only one type per operation. 18 | 19 | > This rule is a wrapper around a `graphql-js` validation function. 20 | 21 | ## Resources 22 | 23 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/UniqueOperationTypesRule.ts) 24 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/UniqueOperationTypesRule-test.ts) 25 | -------------------------------------------------------------------------------- /website/content/rules/variables-in-allowed-position.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Variables passed to field arguments conform to type.' 3 | --- 4 | 5 | # `variables-in-allowed-position` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Operations` 11 | - Rule name: `@graphql-eslint/variables-in-allowed-position` 12 | - Requires GraphQL Schema: `true` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | Variables passed to field arguments conform to type. 18 | 19 | > This rule is a wrapper around a `graphql-js` validation function. 20 | 21 | ## Resources 22 | 23 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/VariablesInAllowedPositionRule.ts) 24 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/VariablesInAllowedPositionRule-test.ts) 25 | -------------------------------------------------------------------------------- /packages/plugin/src/configs/operations-all.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * 🚨 IMPORTANT! Do not manually modify this file. Run: `pnpm generate:configs` 3 | */ 4 | 5 | import { Linter } from 'eslint'; 6 | 7 | export default { 8 | extends: './configs/operations-recommended', 9 | rules: { 10 | '@graphql-eslint/alphabetize': [ 11 | 'error', 12 | { 13 | definitions: true, 14 | selections: ['OperationDefinition', 'FragmentDefinition'], 15 | variables: true, 16 | arguments: ['Field', 'Directive'], 17 | groups: ['...', 'id', '*', '{'], 18 | }, 19 | ], 20 | '@graphql-eslint/lone-executable-definition': 'error', 21 | '@graphql-eslint/match-document-filename': [ 22 | 'error', 23 | { 24 | query: 'kebab-case', 25 | mutation: 'kebab-case', 26 | subscription: 'kebab-case', 27 | fragment: 'kebab-case', 28 | }, 29 | ], 30 | '@graphql-eslint/no-one-place-fragments': 'error', 31 | '@graphql-eslint/require-import-fragment': 'error', 32 | }, 33 | } satisfies Linter.LegacyConfig; 34 | -------------------------------------------------------------------------------- /website/content/rules/lone-schema-definition.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'A GraphQL document is only valid if it contains only one schema definition.' 3 | --- 4 | 5 | # `lone-schema-definition` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Schema` 11 | - Rule name: `@graphql-eslint/lone-schema-definition` 12 | - Requires GraphQL Schema: `false` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | A GraphQL document is only valid if it contains only one schema definition. 18 | 19 | > This rule is a wrapper around a `graphql-js` validation function. 20 | 21 | ## Resources 22 | 23 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/LoneSchemaDefinitionRule.ts) 24 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/LoneSchemaDefinitionRule-test.ts) 25 | -------------------------------------------------------------------------------- /website/content/rules/no-fragment-cycles.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'A GraphQL fragment is only valid when it does not have cycles in fragments usage.' 3 | --- 4 | 5 | # `no-fragment-cycles` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Operations` 11 | - Rule name: `@graphql-eslint/no-fragment-cycles` 12 | - Requires GraphQL Schema: `true` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | A GraphQL fragment is only valid when it does not have cycles in fragments usage. 18 | 19 | > This rule is a wrapper around a `graphql-js` validation function. 20 | 21 | ## Resources 22 | 23 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/NoFragmentCyclesRule.ts) 24 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/NoFragmentCyclesRule-test.ts) 25 | -------------------------------------------------------------------------------- /website/content/rules/unique-variable-names.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'A GraphQL operation is only valid if all its variables are uniquely named.' 3 | --- 4 | 5 | # `unique-variable-names` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Operations` 11 | - Rule name: `@graphql-eslint/unique-variable-names` 12 | - Requires GraphQL Schema: `true` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | A GraphQL operation is only valid if all its variables are uniquely named. 18 | 19 | > This rule is a wrapper around a `graphql-js` validation function. 20 | 21 | ## Resources 22 | 23 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/UniqueVariableNamesRule.ts) 24 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/UniqueVariableNamesRule-test.ts) 25 | -------------------------------------------------------------------------------- /website/app/[[...mdxPath]]/page.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/rules-of-hooks -- false positive, useMDXComponents are not react hooks */ 2 | 3 | import { generateStaticParamsFor, importPage } from '@theguild/components/pages'; 4 | import { useMDXComponents } from '../../mdx-components'; 5 | 6 | export const generateStaticParams = generateStaticParamsFor('mdxPath'); 7 | 8 | export async function generateMetadata(props: Props) { 9 | const params = await props.params; 10 | const { metadata } = await importPage(params.mdxPath); 11 | return metadata; 12 | } 13 | 14 | const Wrapper = useMDXComponents().wrapper; 15 | 16 | type Props = { 17 | params: Promise<{ 18 | mdxPath: string[]; 19 | }>; 20 | }; 21 | 22 | export default async function Page(props: Props) { 23 | const params = await props.params; 24 | const result = await importPage(params.mdxPath); 25 | const { default: MDXContent, toc, metadata } = result; 26 | return ( 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /website/content/rules/unique-directive-names.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'A GraphQL document is only valid if all defined directives have unique names.' 3 | --- 4 | 5 | # `unique-directive-names` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Schema` 11 | - Rule name: `@graphql-eslint/unique-directive-names` 12 | - Requires GraphQL Schema: `false` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | A GraphQL document is only valid if all defined directives have unique names. 18 | 19 | > This rule is a wrapper around a `graphql-js` validation function. 20 | 21 | ## Resources 22 | 23 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/UniqueDirectiveNamesRule.ts) 24 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/UniqueDirectiveNamesRule-test.ts) 25 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/description-style/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ruleTester } from '../../../__tests__/test-utils.js'; 2 | import { rule, RuleOptions } from './index.js'; 3 | 4 | const INLINE_SDL = /* GraphQL */ ` 5 | " Test " 6 | type CreateOneUserPayload { 7 | "Created document ID" 8 | recordId: MongoID 9 | 10 | "Created document" 11 | record: User 12 | } 13 | `; 14 | 15 | export const BLOCK_SDL = /* GraphQL */ ` 16 | enum EnumUserLanguagesSkill { 17 | """ 18 | basic 19 | """ 20 | basic 21 | """ 22 | fluent 23 | """ 24 | fluent 25 | """ 26 | native 27 | """ 28 | native 29 | } 30 | `; 31 | 32 | ruleTester.run('description-style', rule, { 33 | valid: [ 34 | BLOCK_SDL, 35 | { 36 | code: INLINE_SDL, 37 | options: [{ style: 'inline' }], 38 | }, 39 | ], 40 | invalid: [ 41 | { 42 | code: BLOCK_SDL, 43 | options: [{ style: 'inline' }], 44 | errors: 3, 45 | }, 46 | { 47 | code: INLINE_SDL, 48 | errors: 3, 49 | }, 50 | ], 51 | }); 52 | -------------------------------------------------------------------------------- /website/app/icons/astro.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/app/play/button.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps, forwardRef } from 'react'; 2 | import { clsx } from 'clsx'; 3 | 4 | export const Button = forwardRef>( 5 | ({ children, className, ...props }, ref) => ( 6 | 23 | ), 24 | ); 25 | 26 | Button.displayName = 'Button'; 27 | -------------------------------------------------------------------------------- /website/content/rules/one-field-subscriptions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'A GraphQL subscription is valid only if it contains a single root field.' 3 | --- 4 | 5 | # `one-field-subscriptions` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Operations` 11 | - Rule name: `@graphql-eslint/one-field-subscriptions` 12 | - Requires GraphQL Schema: `true` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | A GraphQL subscription is valid only if it contains a single root field. 18 | 19 | > This rule is a wrapper around a `graphql-js` validation function. 20 | 21 | ## Resources 22 | 23 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/SingleFieldSubscriptionsRule.ts) 24 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/SingleFieldSubscriptionsRule-test.ts) 25 | -------------------------------------------------------------------------------- /website/content/rules/deprecated-rules.md: -------------------------------------------------------------------------------- 1 | # Deprecated Rules 2 | 3 | ## `avoid-duplicate-fields` 4 | 5 | This rule was renamed to [`no-duplicate-fields`](/rules/no-duplicate-fields). 6 | 7 | ## `avoid-scalar-result-type-on-mutation` 8 | 9 | This rule was renamed to 10 | [`no-scalar-result-type-on-mutation`](/rules/no-scalar-result-type-on-mutation). 11 | 12 | ## `avoid-typename-prefix` 13 | 14 | This rule was renamed to [`no-typename-prefix`](/rules/no-typename-prefix). 15 | 16 | ## `avoid-operation-name-prefix` 17 | 18 | This rule was removed because the same things can be validated using 19 | [`naming-convention`](/rules/naming-convention). 20 | 21 | ## `no-operation-name-suffix` 22 | 23 | This rule was removed because the same things can be validated using 24 | [`naming-convention`](/rules/naming-convention). 25 | 26 | ## `require-id-when-available` 27 | 28 | This rule was renamed to [`require-selections`](/rules/require-selections). 29 | 30 | ## `no-case-insensitive-enum-values-duplicates` 31 | 32 | This rule was renamed to [`unique-enum-value-names`](/rules/unique-enum-value-names). 33 | -------------------------------------------------------------------------------- /website/content/rules/unique-field-definition-names.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'A GraphQL complex type is only valid if all its fields are uniquely named.' 3 | --- 4 | 5 | # `unique-field-definition-names` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Schema` 11 | - Rule name: `@graphql-eslint/unique-field-definition-names` 12 | - Requires GraphQL Schema: `false` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | A GraphQL complex type is only valid if all its fields are uniquely named. 18 | 19 | > This rule is a wrapper around a `graphql-js` validation function. 20 | 21 | ## Resources 22 | 23 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/UniqueFieldDefinitionNamesRule.ts) 24 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/UniqueFieldDefinitionNamesRule-test.ts) 25 | -------------------------------------------------------------------------------- /website/content/rules/unique-argument-names.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL field or directive is only valid if all supplied arguments are uniquely named.' 4 | --- 5 | 6 | # `unique-argument-names` 7 | 8 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 9 | enables this rule. 10 | 11 | - Category: `Operations` 12 | - Rule name: `@graphql-eslint/unique-argument-names` 13 | - Requires GraphQL Schema: `true` 14 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 15 | - Requires GraphQL Operations: `false` 16 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 17 | 18 | A GraphQL field or directive is only valid if all supplied arguments are uniquely named. 19 | 20 | > This rule is a wrapper around a `graphql-js` validation function. 21 | 22 | ## Resources 23 | 24 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/UniqueArgumentNamesRule.ts) 25 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/UniqueArgumentNamesRule-test.ts) 26 | -------------------------------------------------------------------------------- /website/content/rules/unique-input-field-names.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'A GraphQL input object value is only valid if all supplied fields are uniquely named.' 3 | --- 4 | 5 | # `unique-input-field-names` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Operations` 11 | - Rule name: `@graphql-eslint/unique-input-field-names` 12 | - Requires GraphQL Schema: `false` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | A GraphQL input object value is only valid if all supplied fields are uniquely named. 18 | 19 | > This rule is a wrapper around a `graphql-js` validation function. 20 | 21 | ## Resources 22 | 23 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/UniqueInputFieldNamesRule.ts) 24 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts) 25 | -------------------------------------------------------------------------------- /examples/graphql-config/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Legacy config example, should be run with `ESLINT_USE_FLAT_CONFIG=false` environment variable in ESLint 9 3 | */ 4 | 5 | module.exports = { 6 | root: true, 7 | // ❗️ It's very important that you don't have any rules configured at the top-level config, 8 | // and to move all configurations into the overrides section. Since JavaScript rules 9 | // can't run on GraphQL files and vice versa, if you have rules configured at the top level, 10 | // they will try to also execute for all overrides, as ESLint's configs cascade 11 | overrides: [ 12 | { 13 | files: ['*.js'], 14 | extends: ['eslint:recommended'], 15 | env: { 16 | es2022: true, 17 | }, 18 | parserOptions: { 19 | sourceType: 'module', 20 | }, 21 | }, 22 | { 23 | files: ['*.graphql'], 24 | parser: '@graphql-eslint/eslint-plugin', 25 | plugins: ['@graphql-eslint'], 26 | rules: { 27 | '@graphql-eslint/no-anonymous-operations': 'error', 28 | '@graphql-eslint/no-duplicate-fields': 'error', 29 | }, 30 | }, 31 | ], 32 | }; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dimitri POSTOLOV 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/__snapshots__/fields-on-correct-type.spec.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`fields-on-correct-type > invalid > should highlight selection on multi line 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | { 7 | 2 | user { 8 | 3 | id 9 | 4 | veryBad 10 | 5 | age 11 | 6 | } 12 | 7 | } 13 | 14 | #### ❌ Error 15 | 16 | 3 | id 17 | > 4 | veryBad 18 | | ^^^^^^^ Cannot query field "veryBad" on type "User". 19 | 5 | age 20 | `; 21 | 22 | exports[`fields-on-correct-type > invalid > should highlight selection on single line 1`] = ` 23 | #### ⌨️ Code 24 | 25 | 1 | fragment UserFields on User { id bad age } 26 | 27 | #### ❌ Error 28 | 29 | > 1 | fragment UserFields on User { id bad age } 30 | | ^^^ Cannot query field "bad" on type "User". Did you mean "id"? 31 | 32 | #### 💡 Suggestion: Rename to \`id\` 33 | 34 | 1 | fragment UserFields on User { id id age } 35 | `; 36 | -------------------------------------------------------------------------------- /website/content/rules/executable-definitions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL document is only valid for execution if all definitions are either operation or 4 | fragment definitions.' 5 | --- 6 | 7 | # `executable-definitions` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 10 | enables this rule. 11 | 12 | - Category: `Operations` 13 | - Rule name: `@graphql-eslint/executable-definitions` 14 | - Requires GraphQL Schema: `true` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 16 | - Requires GraphQL Operations: `false` 17 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 18 | 19 | A GraphQL document is only valid for execution if all definitions are either operation or fragment 20 | definitions. 21 | 22 | > This rule is a wrapper around a `graphql-js` validation function. 23 | 24 | ## Resources 25 | 26 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/ExecutableDefinitionsRule.ts) 27 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/ExecutableDefinitionsRule-test.ts) 28 | -------------------------------------------------------------------------------- /website/content/rules/no-unused-variables.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL operation is only valid if all variables defined by an operation are used, either 4 | directly or within a spread fragment.' 5 | --- 6 | 7 | # `no-unused-variables` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 10 | enables this rule. 11 | 12 | - Category: `Operations` 13 | - Rule name: `@graphql-eslint/no-unused-variables` 14 | - Requires GraphQL Schema: `true` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 16 | - Requires GraphQL Operations: `true` 17 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 18 | 19 | A GraphQL operation is only valid if all variables defined by an operation are used, either directly 20 | or within a spread fragment. 21 | 22 | > This rule is a wrapper around a `graphql-js` validation function. 23 | 24 | ## Resources 25 | 26 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/NoUnusedVariablesRule.ts) 27 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/NoUnusedVariablesRule-test.ts) 28 | -------------------------------------------------------------------------------- /examples/monorepo/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import graphqlPlugin from '@graphql-eslint/eslint-plugin'; 3 | 4 | export default [ 5 | { 6 | files: ['**/*.{js,tsx}'], 7 | rules: js.configs.recommended.rules, 8 | }, 9 | { 10 | files: ['client/**/*.tsx'], 11 | // Setup processor for operations/fragments definitions on code-files 12 | processor: graphqlPlugin.processor, 13 | languageOptions: { 14 | parserOptions: { 15 | ecmaFeatures: { 16 | jsx: true, 17 | }, 18 | }, 19 | }, 20 | }, 21 | { 22 | // Setup GraphQL Parser 23 | files: ['**/*.{graphql,gql}'], 24 | languageOptions: { 25 | parser: graphqlPlugin.parser, 26 | }, 27 | plugins: { 28 | '@graphql-eslint': graphqlPlugin, 29 | }, 30 | }, 31 | { 32 | // Setup recommended config for schema files 33 | files: ['server/**/*.gql'], 34 | rules: graphqlPlugin.configs['flat/schema-recommended'].rules, 35 | }, 36 | { 37 | // Setup recommended config for operations files 38 | files: ['client/**/*.{graphql,gql}'], 39 | rules: graphqlPlugin.configs['flat/operations-recommended'].rules, 40 | }, 41 | ]; 42 | -------------------------------------------------------------------------------- /website/content/rules/variables-are-input-types.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL operation is only valid if all the variables it defines are of input types (scalar, 4 | enum, or input object).' 5 | --- 6 | 7 | # `variables-are-input-types` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 10 | enables this rule. 11 | 12 | - Category: `Operations` 13 | - Rule name: `@graphql-eslint/variables-are-input-types` 14 | - Requires GraphQL Schema: `true` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 16 | - Requires GraphQL Operations: `false` 17 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 18 | 19 | A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, 20 | or input object). 21 | 22 | > This rule is a wrapper around a `graphql-js` validation function. 23 | 24 | ## Resources 25 | 26 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/VariablesAreInputTypesRule.ts) 27 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/VariablesAreInputTypesRule-test.ts) 28 | -------------------------------------------------------------------------------- /examples/svelte-code-file/eslint.config.js: -------------------------------------------------------------------------------- 1 | import svelteParser from 'svelte-eslint-parser'; 2 | import js from '@eslint/js'; 3 | import graphqlPlugin from '@graphql-eslint/eslint-plugin'; 4 | 5 | export default [ 6 | { 7 | files: ['**/*.js', '**/*.svelte'], 8 | processor: graphqlPlugin.processor, 9 | rules: js.configs.recommended.rules, 10 | }, 11 | { 12 | files: ['**/*.svelte'], 13 | languageOptions: { 14 | parser: svelteParser, 15 | }, 16 | }, 17 | { 18 | files: ['**/*.graphql'], 19 | languageOptions: { 20 | parser: graphqlPlugin.parser, 21 | }, 22 | plugins: { 23 | '@graphql-eslint': graphqlPlugin, 24 | }, 25 | rules: { 26 | '@graphql-eslint/no-anonymous-operations': 'error', 27 | '@graphql-eslint/no-duplicate-fields': 'error', 28 | '@graphql-eslint/naming-convention': [ 29 | 'error', 30 | { 31 | OperationDefinition: { 32 | style: 'PascalCase', 33 | forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'], 34 | forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'], 35 | }, 36 | }, 37 | ], 38 | }, 39 | }, 40 | ]; 41 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/no-one-place-fragments/index.test.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path'; 2 | import { CWD } from '@/utils.js'; 3 | import { ruleTester } from '../../../__tests__/test-utils.js'; 4 | import { rule } from './index.js'; 5 | 6 | ruleTester.run('no-one-place-fragments', rule, { 7 | valid: [ 8 | { 9 | name: 'ok when spread 2 times', 10 | code: ruleTester.fromMockFile('no-one-place-fragments.graphql'), 11 | parserOptions: { 12 | graphQLConfig: { 13 | documents: join(CWD, '__tests__/mocks/no-one-place-fragments.graphql'), 14 | }, 15 | }, 16 | }, 17 | ], 18 | invalid: [ 19 | { 20 | name: 'should error fragment used in one place', 21 | code: ruleTester.fromMockFile('user-fields.graphql'), 22 | errors: [ 23 | { message: 'Fragment `UserFields` used only once. Inline him in "146179389.graphql".' }, 24 | ], 25 | parserOptions: { 26 | graphQLConfig: { 27 | documents: /* GraphQL */ ` 28 | { 29 | user { 30 | ...UserFields 31 | } 32 | } 33 | `, 34 | }, 35 | }, 36 | }, 37 | ], 38 | }); 39 | -------------------------------------------------------------------------------- /website/content/rules/possible-type-extension.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'A type extension is only valid if the type is defined and has the same kind.' 3 | --- 4 | 5 | # `possible-type-extension` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | 💡 This rule provides 11 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 12 | 13 | - Category: `Schema` 14 | - Rule name: `@graphql-eslint/possible-type-extension` 15 | - Requires GraphQL Schema: `true` 16 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 17 | - Requires GraphQL Operations: `false` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 19 | 20 | A type extension is only valid if the type is defined and has the same kind. 21 | 22 | > This rule is a wrapper around a `graphql-js` validation function. 23 | 24 | ## Resources 25 | 26 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/PossibleTypeExtensionsRule.ts) 27 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/PossibleTypeExtensionsRule-test.ts) 28 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/executable-definitions.spec.ts: -------------------------------------------------------------------------------- 1 | import { GRAPHQL_JS_VALIDATIONS } from '../src/rules/graphql-js-validation.js'; 2 | import { ParserOptionsForTests, ruleTester } from './test-utils.js'; 3 | 4 | const TEST_SCHEMA = /* GraphQL */ ` 5 | type Query { 6 | foo: String! 7 | bar: String! 8 | } 9 | 10 | type Mutation { 11 | foo: String! 12 | } 13 | 14 | type T { 15 | foo: String! 16 | } 17 | `; 18 | 19 | const WITH_SCHEMA = { 20 | parserOptions: { 21 | graphQLConfig: { 22 | schema: TEST_SCHEMA, 23 | }, 24 | } satisfies ParserOptionsForTests, 25 | }; 26 | 27 | ruleTester.run('executable-definitions', GRAPHQL_JS_VALIDATIONS['executable-definitions'], { 28 | valid: [ 29 | { 30 | ...WITH_SCHEMA, 31 | code: 'query test2 { foo }', 32 | }, 33 | { 34 | ...WITH_SCHEMA, 35 | code: 'mutation test { foo }', 36 | }, 37 | { 38 | ...WITH_SCHEMA, 39 | code: 'fragment Test on T { foo }', 40 | }, 41 | ], 42 | invalid: [ 43 | { 44 | ...WITH_SCHEMA, 45 | code: 'type Query { t: String }', 46 | errors: [{ message: 'The "Query" definition is not executable.' }], 47 | }, 48 | ], 49 | }); 50 | -------------------------------------------------------------------------------- /website/content/rules/no-undefined-variables.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL operation is only valid if all variables encountered, both directly and via fragment 4 | spreads, are defined by that operation.' 5 | --- 6 | 7 | # `no-undefined-variables` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 10 | enables this rule. 11 | 12 | - Category: `Operations` 13 | - Rule name: `@graphql-eslint/no-undefined-variables` 14 | - Requires GraphQL Schema: `true` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 16 | - Requires GraphQL Operations: `true` 17 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 18 | 19 | A GraphQL operation is only valid if all variables encountered, both directly and via fragment 20 | spreads, are defined by that operation. 21 | 22 | > This rule is a wrapper around a `graphql-js` validation function. 23 | 24 | ## Resources 25 | 26 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/NoUndefinedVariablesRule.ts) 27 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/NoUndefinedVariablesRule-test.ts) 28 | -------------------------------------------------------------------------------- /website/content/rules/no-unused-fragments.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL document is only valid if all fragment definitions are spread within operations, or 4 | spread within other fragments spread within operations.' 5 | --- 6 | 7 | # `no-unused-fragments` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 10 | enables this rule. 11 | 12 | - Category: `Operations` 13 | - Rule name: `@graphql-eslint/no-unused-fragments` 14 | - Requires GraphQL Schema: `true` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 16 | - Requires GraphQL Operations: `true` 17 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 18 | 19 | A GraphQL document is only valid if all fragment definitions are spread within operations, or spread 20 | within other fragments spread within operations. 21 | 22 | > This rule is a wrapper around a `graphql-js` validation function. 23 | 24 | ## Resources 25 | 26 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/NoUnusedFragmentsRule.ts) 27 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/NoUnusedFragmentsRule-test.ts) 28 | -------------------------------------------------------------------------------- /packages/plugin/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import tsconfigPaths from 'vite-tsconfig-paths'; 3 | import { defineConfig } from 'vitest/config'; 4 | 5 | const GRAPHQL_PATH = path.join(__dirname, 'node_modules', 'graphql'); 6 | 7 | export default defineConfig({ 8 | test: { 9 | globals: true, 10 | resolveSnapshotPath(testPath) { 11 | if (testPath.endsWith('/index.test.ts')) { 12 | return testPath.replace('/index.test.ts', '/snapshot.md'); 13 | } 14 | 15 | return testPath.replace('__tests__/', '__tests__/__snapshots__/').replace(/\.ts$/, '.md'); 16 | }, 17 | setupFiles: ['./serializer.ts'], 18 | alias: { 19 | // fixes Duplicate "graphql" modules cannot be used at the same time since different 20 | 'graphql/validation/index.js': path.join(GRAPHQL_PATH, 'validation', 'index.js'), 21 | 'graphql/validation/validate.js': path.join(GRAPHQL_PATH, 'validation', 'validate.js'), 22 | 'graphql/utilities/valueFromASTUntyped.js': path.join( 23 | GRAPHQL_PATH, 24 | 'utilities', 25 | 'valueFromASTUntyped.js', 26 | ), 27 | graphql: path.join(GRAPHQL_PATH, 'index.js'), 28 | }, 29 | }, 30 | plugins: [tsconfigPaths()], 31 | }); 32 | -------------------------------------------------------------------------------- /examples/multiple-projects-graphql-config/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Legacy config example, should be run with `ESLINT_USE_FLAT_CONFIG=false` environment variable in ESLint 9 3 | */ 4 | 5 | module.exports = { 6 | root: true, 7 | // ❗️ It's very important that you don't have any rules configured at the top-level config, 8 | // and to move all configurations into the overrides section. Since JavaScript rules 9 | // can't run on GraphQL files and vice versa, if you have rules configured at the top level, 10 | // they will try to also execute for all overrides, as ESLint's configs cascade 11 | overrides: [ 12 | { 13 | files: ['*.js'], 14 | processor: '@graphql-eslint/graphql', 15 | extends: ['eslint:recommended'], 16 | env: { 17 | es2022: true, 18 | }, 19 | parserOptions: { 20 | sourceType: 'module', 21 | }, 22 | }, 23 | { 24 | files: ['schema.*.graphql'], 25 | extends: ['plugin:@graphql-eslint/schema-recommended'], 26 | rules: { 27 | '@graphql-eslint/require-description': 'off', 28 | }, 29 | }, 30 | { 31 | files: ['*.js/*.graphql'], 32 | extends: ['plugin:@graphql-eslint/operations-recommended'], 33 | }, 34 | ], 35 | }; 36 | -------------------------------------------------------------------------------- /website/content/rules/lone-anonymous-operation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL document that contains an anonymous operation (the `query` short-hand) is only valid if 4 | it contains only that one operation definition.' 5 | --- 6 | 7 | # `lone-anonymous-operation` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 10 | enables this rule. 11 | 12 | - Category: `Operations` 13 | - Rule name: `@graphql-eslint/lone-anonymous-operation` 14 | - Requires GraphQL Schema: `true` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 16 | - Requires GraphQL Operations: `false` 17 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 18 | 19 | A GraphQL document that contains an anonymous operation (the `query` short-hand) is only valid if it 20 | contains only that one operation definition. 21 | 22 | > This rule is a wrapper around a `graphql-js` validation function. 23 | 24 | ## Resources 25 | 26 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/LoneAnonymousOperationRule.ts) 27 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/LoneAnonymousOperationRule-test.ts) 28 | -------------------------------------------------------------------------------- /packages/plugin/src/cache.ts: -------------------------------------------------------------------------------- 1 | // Based on the `eslint-plugin-import`'s cache 2 | // https://github.com/import-js/eslint-plugin-import/blob/main/utils/ModuleCache.js 3 | import debugFactory from 'debug'; 4 | 5 | const log = debugFactory('graphql-eslint:ModuleCache'); 6 | 7 | export class ModuleCache { 8 | map = new Map(); 9 | 10 | set(cacheKey: K, result: T): void { 11 | // Remove server-side cache code in browser 12 | if (typeof window !== 'undefined') return; 13 | 14 | this.map.set(cacheKey, { lastSeen: process.hrtime(), result }); 15 | log('setting entry for', cacheKey); 16 | } 17 | 18 | get(cacheKey: K, settings = { lifetime: 10 /* seconds */ }): T | void { 19 | // Remove server-side cache code in browser 20 | if (typeof window !== 'undefined') return; 21 | 22 | const value = this.map.get(cacheKey); 23 | if (!value) { 24 | log('cache miss for', cacheKey); 25 | return; 26 | } 27 | const { lastSeen, result } = value; 28 | // check freshness 29 | if ( 30 | process.env.NODE /* don't check for ESLint CLI */ || 31 | process.hrtime(lastSeen)[0] < settings.lifetime 32 | ) { 33 | return result; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /website/content/rules/scalar-leafs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar 4 | or enum types.' 5 | --- 6 | 7 | # `scalar-leafs` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 10 | enables this rule. 11 | 12 | 💡 This rule provides 13 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 14 | 15 | - Category: `Operations` 16 | - Rule name: `@graphql-eslint/scalar-leafs` 17 | - Requires GraphQL Schema: `true` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 19 | - Requires GraphQL Operations: `false` 20 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 21 | 22 | A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or 23 | enum types. 24 | 25 | > This rule is a wrapper around a `graphql-js` validation function. 26 | 27 | ## Resources 28 | 29 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/ScalarLeafsRule.ts) 30 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/ScalarLeafsRule-test.ts) 31 | -------------------------------------------------------------------------------- /packages/plugin/src/configs/schema-all.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * 🚨 IMPORTANT! Do not manually modify this file. Run: `pnpm generate:configs` 3 | */ 4 | 5 | import { Linter } from 'eslint'; 6 | 7 | export default { 8 | extends: './configs/schema-recommended', 9 | rules: { 10 | '@graphql-eslint/alphabetize': [ 11 | 'error', 12 | { 13 | definitions: true, 14 | fields: ['ObjectTypeDefinition', 'InterfaceTypeDefinition', 'InputObjectTypeDefinition'], 15 | values: true, 16 | arguments: ['FieldDefinition', 'Field', 'DirectiveDefinition', 'Directive'], 17 | groups: ['id', '*', 'createdAt', 'updatedAt'], 18 | }, 19 | ], 20 | '@graphql-eslint/input-name': 'error', 21 | '@graphql-eslint/no-root-type': ['error', { disallow: ['mutation', 'subscription'] }], 22 | '@graphql-eslint/no-scalar-result-type-on-mutation': 'error', 23 | '@graphql-eslint/require-deprecation-date': 'error', 24 | '@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error', 25 | '@graphql-eslint/require-nullable-fields-with-oneof': 'error', 26 | '@graphql-eslint/require-nullable-result-in-root': 'error', 27 | '@graphql-eslint/require-type-pattern-with-oneof': 'error', 28 | }, 29 | } satisfies Linter.LegacyConfig; 30 | -------------------------------------------------------------------------------- /website/content/rules/provided-required-arguments.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A field or directive is only valid if all required (non-null without a default value) field 4 | arguments have been provided.' 5 | --- 6 | 7 | # `provided-required-arguments` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` and 10 | `"plugin:@graphql-eslint/operations-recommended"` property in a configuration file enables this 11 | rule. 12 | 13 | - Category: `Schema & Operations` 14 | - Rule name: `@graphql-eslint/provided-required-arguments` 15 | - Requires GraphQL Schema: `true` 16 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 17 | - Requires GraphQL Operations: `false` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 19 | 20 | A field or directive is only valid if all required (non-null without a default value) field 21 | arguments have been provided. 22 | 23 | > This rule is a wrapper around a `graphql-js` validation function. 24 | 25 | ## Resources 26 | 27 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/ProvidedRequiredArgumentsRule.ts) 28 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/ProvidedRequiredArgumentsRule-test.ts) 29 | -------------------------------------------------------------------------------- /website/content/rules/unique-directive-names-per-location.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL document is only valid if all non-repeatable directives at a given location are 4 | uniquely named.' 5 | --- 6 | 7 | # `unique-directive-names-per-location` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` and 10 | `"plugin:@graphql-eslint/operations-recommended"` property in a configuration file enables this 11 | rule. 12 | 13 | - Category: `Schema & Operations` 14 | - Rule name: `@graphql-eslint/unique-directive-names-per-location` 15 | - Requires GraphQL Schema: `true` 16 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 17 | - Requires GraphQL Operations: `false` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 19 | 20 | A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely 21 | named. 22 | 23 | > This rule is a wrapper around a `graphql-js` validation function. 24 | 25 | ## Resources 26 | 27 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/UniqueDirectivesPerLocationRule.ts) 28 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/UniqueDirectivesPerLocationRule-test.ts) 29 | -------------------------------------------------------------------------------- /website/content/rules/known-argument-names.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'A GraphQL field is only valid if all supplied arguments are defined by that field.' 3 | --- 4 | 5 | # `known-argument-names` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` and 8 | `"plugin:@graphql-eslint/operations-recommended"` property in a configuration file enables this 9 | rule. 10 | 11 | 💡 This rule provides 12 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 13 | 14 | - Category: `Schema & Operations` 15 | - Rule name: `@graphql-eslint/known-argument-names` 16 | - Requires GraphQL Schema: `true` 17 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 18 | - Requires GraphQL Operations: `false` 19 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 20 | 21 | A GraphQL field is only valid if all supplied arguments are defined by that field. 22 | 23 | > This rule is a wrapper around a `graphql-js` validation function. 24 | 25 | ## Resources 26 | 27 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/KnownArgumentNamesRule.ts) 28 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/KnownArgumentNamesRule-test.ts) 29 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/unique-type-names.spec.ts: -------------------------------------------------------------------------------- 1 | import { GRAPHQL_JS_VALIDATIONS } from '../src/rules/graphql-js-validation.js'; 2 | import { ParserOptionsForTests, ruleTester } from './test-utils.js'; 3 | 4 | const TEST_SCHEMA = /* GraphQL */ ` 5 | type Query { 6 | foo: String 7 | bar: Boolean 8 | } 9 | `; 10 | 11 | const WITH_SCHEMA = { 12 | languageOptions: { 13 | parserOptions: { 14 | graphQLConfig: { 15 | schema: TEST_SCHEMA, 16 | }, 17 | } satisfies ParserOptionsForTests, 18 | }, 19 | }; 20 | 21 | ruleTester.run('unique-type-names', GRAPHQL_JS_VALIDATIONS['unique-type-names'], { 22 | valid: [ 23 | { ...WITH_SCHEMA, code: TEST_SCHEMA }, 24 | { 25 | ...WITH_SCHEMA, 26 | code: /* GraphQL */ ` 27 | type Query { 28 | foo: String 29 | } 30 | 31 | extend type Query { 32 | bar: Boolean 33 | } 34 | `, 35 | }, 36 | ], 37 | invalid: [ 38 | { 39 | ...WITH_SCHEMA, 40 | code: /* GraphQL */ ` 41 | type Query { 42 | foo: String 43 | } 44 | 45 | type Query { 46 | bar: Boolean 47 | } 48 | `, 49 | errors: [{ message: 'There can be only one type named "Query".' }], 50 | }, 51 | ], 52 | }); 53 | -------------------------------------------------------------------------------- /website/content/rules/value-literals-of-correct-type.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL document is only valid if all value literals are of the type expected at their 4 | position.' 5 | --- 6 | 7 | # `value-literals-of-correct-type` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 10 | enables this rule. 11 | 12 | 💡 This rule provides 13 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 14 | 15 | - Category: `Operations` 16 | - Rule name: `@graphql-eslint/value-literals-of-correct-type` 17 | - Requires GraphQL Schema: `true` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 19 | - Requires GraphQL Operations: `false` 20 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 21 | 22 | A GraphQL document is only valid if all value literals are of the type expected at their position. 23 | 24 | > This rule is a wrapper around a `graphql-js` validation function. 25 | 26 | ## Resources 27 | 28 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/ValuesOfCorrectTypeRule.ts) 29 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts) 30 | -------------------------------------------------------------------------------- /website/content/rules/overlapping-fields-can-be-merged.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A selection set is only valid if all fields (including spreading any fragments) either correspond 4 | to distinct response names or can be merged without ambiguity.' 5 | --- 6 | 7 | # `overlapping-fields-can-be-merged` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 10 | enables this rule. 11 | 12 | - Category: `Operations` 13 | - Rule name: `@graphql-eslint/overlapping-fields-can-be-merged` 14 | - Requires GraphQL Schema: `true` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 16 | - Requires GraphQL Operations: `false` 17 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 18 | 19 | A selection set is only valid if all fields (including spreading any fragments) either correspond to 20 | distinct response names or can be merged without ambiguity. 21 | 22 | > This rule is a wrapper around a `graphql-js` validation function. 23 | 24 | ## Resources 25 | 26 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/OverlappingFieldsCanBeMergedRule.ts) 27 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/OverlappingFieldsCanBeMergedRule-test.ts) 28 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/require-type-pattern-with-oneof/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ruleTester } from '../../../__tests__/test-utils.js'; 2 | import { rule } from './index.js'; 3 | 4 | ruleTester.run('require-type-pattern-with-oneof', rule, { 5 | valid: [ 6 | /* GraphQL */ ` 7 | type T @oneOf { 8 | ok: Ok 9 | error: Error 10 | } 11 | `, 12 | { 13 | name: 'should ignore types without `@oneOf` directive', 14 | code: /* GraphQL */ ` 15 | type T { 16 | notok: Ok 17 | err: Error 18 | } 19 | `, 20 | }, 21 | { 22 | name: 'should validate only `type` with `@oneOf` directive', 23 | code: /* GraphQL */ ` 24 | input I { 25 | notok: Ok 26 | err: Error 27 | } 28 | `, 29 | }, 30 | ], 31 | invalid: [ 32 | { 33 | name: 'should validate `ok` field', 34 | code: /* GraphQL */ ` 35 | type T @oneOf { 36 | notok: Ok 37 | error: Error 38 | } 39 | `, 40 | errors: 1, 41 | }, 42 | { 43 | name: 'should validate `error` field', 44 | code: /* GraphQL */ ` 45 | type T @oneOf { 46 | ok: Ok 47 | err: Error 48 | } 49 | `, 50 | errors: 1, 51 | }, 52 | ], 53 | }); 54 | -------------------------------------------------------------------------------- /website/content/rules/require-nullable-fields-with-oneof.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Require `input` or `type` fields to be non-nullable with `@oneOf` directive.' 3 | --- 4 | 5 | # `require-nullable-fields-with-oneof` 6 | 7 | - Category: `Schema` 8 | - Rule name: `@graphql-eslint/require-nullable-fields-with-oneof` 9 | - Requires GraphQL Schema: `false` 10 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 11 | - Requires GraphQL Operations: `false` 12 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 13 | 14 | {metadata.description} 15 | 16 | ## Usage Examples 17 | 18 | ### Incorrect 19 | 20 | ```graphql 21 | # eslint @graphql-eslint/require-nullable-fields-with-oneof: 'error' 22 | 23 | input Input @oneOf { 24 | foo: String! 25 | b: Int 26 | } 27 | ``` 28 | 29 | ### Correct 30 | 31 | ```graphql 32 | # eslint @graphql-eslint/require-nullable-fields-with-oneof: 'error' 33 | 34 | input Input @oneOf { 35 | foo: String 36 | bar: Int 37 | } 38 | ``` 39 | 40 | ## Resources 41 | 42 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/require-nullable-fields-with-oneof.ts) 43 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/require-nullable-fields-with-oneof.spec.ts) 44 | -------------------------------------------------------------------------------- /website/content/rules/require-type-pattern-with-oneof.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Enforce types with `@oneOf` directive have `error` and `ok` fields.' 3 | --- 4 | 5 | # `require-type-pattern-with-oneof` 6 | 7 | - Category: `Schema` 8 | - Rule name: `@graphql-eslint/require-type-pattern-with-oneof` 9 | - Requires GraphQL Schema: `false` 10 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 11 | - Requires GraphQL Operations: `false` 12 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 13 | 14 | {metadata.description} 15 | 16 | ## Usage Examples 17 | 18 | ### Correct 19 | 20 | ```graphql 21 | # eslint @graphql-eslint/require-type-pattern-with-oneof: 'error' 22 | 23 | type Mutation { 24 | doSomething: DoSomethingMutationResult! 25 | } 26 | 27 | interface Error { 28 | message: String! 29 | } 30 | 31 | type DoSomethingMutationResult @oneOf { 32 | ok: DoSomethingSuccess 33 | error: Error 34 | } 35 | 36 | type DoSomethingSuccess { 37 | # ... 38 | } 39 | ``` 40 | 41 | ## Resources 42 | 43 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/require-type-pattern-with-oneof.ts) 44 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/require-type-pattern-with-oneof.spec.ts) 45 | -------------------------------------------------------------------------------- /packages/plugin/src/configs/index.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from 'eslint'; 2 | import { ConfigName } from '../types.js'; 3 | import operationsAllConfig from './operations-all.js'; 4 | import operationsRecommendedConfig from './operations-recommended.js'; 5 | import schemaAllConfig from './schema-all.js'; 6 | import schemaRecommendedConfig from './schema-recommended.js'; 7 | import relayConfig from './schema-relay.js'; 8 | 9 | export const configs = { 10 | 'schema-recommended': schemaRecommendedConfig, 11 | 'schema-all': schemaAllConfig, 12 | 'schema-relay': relayConfig, 13 | 'operations-recommended': operationsRecommendedConfig, 14 | 'operations-all': operationsAllConfig, 15 | 'flat/schema-recommended': { 16 | rules: schemaRecommendedConfig.rules, 17 | }, 18 | 'flat/schema-all': { 19 | rules: { 20 | ...schemaRecommendedConfig.rules, 21 | ...schemaAllConfig.rules, 22 | }, 23 | }, 24 | 'flat/schema-relay': { 25 | rules: relayConfig.rules, 26 | }, 27 | 'flat/operations-recommended': { 28 | rules: operationsRecommendedConfig.rules, 29 | }, 30 | 'flat/operations-all': { 31 | rules: { 32 | ...operationsRecommendedConfig.rules, 33 | ...operationsAllConfig.rules, 34 | }, 35 | }, 36 | } satisfies Record & Record<`flat/${ConfigName}`, Linter.Config>; 37 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/require-nullable-result-in-root/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ruleTester, withSchema } from '../../../__tests__/test-utils.js'; 2 | import { rule } from './index.js'; 3 | 4 | ruleTester.run('require-nullable-result-in-root', rule, { 5 | valid: [ 6 | withSchema({ 7 | code: /* GraphQL */ ` 8 | type Query { 9 | foo: User 10 | baz: [User]! 11 | bar: [User!]! 12 | } 13 | type User { 14 | id: ID! 15 | } 16 | `, 17 | }), 18 | ], 19 | invalid: [ 20 | withSchema({ 21 | code: /* GraphQL */ ` 22 | type Query { 23 | user: User! 24 | } 25 | type User { 26 | id: ID! 27 | } 28 | `, 29 | errors: 1, 30 | }), 31 | withSchema({ 32 | name: 'should work with extend query', 33 | code: /* GraphQL */ ` 34 | type MyMutation 35 | extend type MyMutation { 36 | user: User! 37 | } 38 | interface User { 39 | id: ID! 40 | } 41 | schema { 42 | mutation: MyMutation 43 | } 44 | `, 45 | errors: 1, 46 | }), 47 | withSchema({ 48 | name: 'should work with default scalars', 49 | code: 'type Mutation { foo: Boolean! }', 50 | errors: 1, 51 | }), 52 | ], 53 | }); 54 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/no-anonymous-operations/snapshot.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`no-anonymous-operations > invalid > Invalid #1 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | query { a } 7 | 8 | #### ❌ Error 9 | 10 | > 1 | query { a } 11 | | ^^^^^ Anonymous GraphQL operations are forbidden. Make sure to name your query! 12 | 13 | #### 💡 Suggestion: Rename to \`a\` 14 | 15 | 1 | query a { a } 16 | `; 17 | 18 | exports[`no-anonymous-operations > invalid > Invalid #2 1`] = ` 19 | #### ⌨️ Code 20 | 21 | 1 | mutation { renamed: a } 22 | 23 | #### ❌ Error 24 | 25 | > 1 | mutation { renamed: a } 26 | | ^^^^^^^^ Anonymous GraphQL operations are forbidden. Make sure to name your mutation! 27 | 28 | #### 💡 Suggestion: Rename to \`renamed\` 29 | 30 | 1 | mutation renamed { renamed: a } 31 | `; 32 | 33 | exports[`no-anonymous-operations > invalid > Invalid #3 1`] = ` 34 | #### ⌨️ Code 35 | 36 | 1 | subscription { ...someFragmentSpread } 37 | 38 | #### ❌ Error 39 | 40 | > 1 | subscription { ...someFragmentSpread } 41 | | ^^^^^^^^^^^^ Anonymous GraphQL operations are forbidden. Make sure to name your subscription! 42 | 43 | #### 💡 Suggestion: Rename to \`subscription\` 44 | 45 | 1 | subscription subscription { ...someFragmentSpread } 46 | `; 47 | -------------------------------------------------------------------------------- /website/content/rules/fields-on-correct-type.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL document is only valid if all fields selected are defined by the parent type, or are an 4 | allowed meta field such as `__typename`.' 5 | --- 6 | 7 | # `fields-on-correct-type` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 10 | enables this rule. 11 | 12 | 💡 This rule provides 13 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 14 | 15 | - Category: `Operations` 16 | - Rule name: `@graphql-eslint/fields-on-correct-type` 17 | - Requires GraphQL Schema: `true` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 19 | - Requires GraphQL Operations: `false` 20 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 21 | 22 | A GraphQL document is only valid if all fields selected are defined by the parent type, or are an 23 | allowed meta field such as `__typename`. 24 | 25 | > This rule is a wrapper around a `graphql-js` validation function. 26 | 27 | ## Resources 28 | 29 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/FieldsOnCorrectTypeRule.ts) 30 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts) 31 | -------------------------------------------------------------------------------- /website/content/rules/fragments-on-composite-type.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'Fragments use a type condition to determine if they apply, since fragments can only be spread 4 | into a composite type (object, interface, or union), the type condition must also be a composite 5 | type.' 6 | --- 7 | 8 | # `fragments-on-composite-type` 9 | 10 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 11 | enables this rule. 12 | 13 | - Category: `Operations` 14 | - Rule name: `@graphql-eslint/fragments-on-composite-type` 15 | - Requires GraphQL Schema: `true` 16 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 17 | - Requires GraphQL Operations: `false` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 19 | 20 | Fragments use a type condition to determine if they apply, since fragments can only be spread into a 21 | composite type (object, interface, or union), the type condition must also be a composite type. 22 | 23 | > This rule is a wrapper around a `graphql-js` validation function. 24 | 25 | ## Resources 26 | 27 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/FragmentsOnCompositeTypesRule.ts) 28 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/FragmentsOnCompositeTypesRule-test.ts) 29 | -------------------------------------------------------------------------------- /website/content/rules/possible-fragment-spread.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A fragment spread is only valid if the type condition could ever possibly be true: if there is a 4 | non-empty intersection of the possible parent types, and possible types which pass the type 5 | condition.' 6 | --- 7 | 8 | # `possible-fragment-spread` 9 | 10 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 11 | enables this rule. 12 | 13 | - Category: `Operations` 14 | - Rule name: `@graphql-eslint/possible-fragment-spread` 15 | - Requires GraphQL Schema: `true` 16 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 17 | - Requires GraphQL Operations: `false` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 19 | 20 | A fragment spread is only valid if the type condition could ever possibly be true: if there is a 21 | non-empty intersection of the possible parent types, and possible types which pass the type 22 | condition. 23 | 24 | > This rule is a wrapper around a `graphql-js` validation function. 25 | 26 | ## Resources 27 | 28 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/PossibleFragmentSpreadsRule.ts) 29 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/PossibleFragmentSpreadsRule-test.ts) 30 | -------------------------------------------------------------------------------- /website/content/docs/usage/js.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Code Files 3 | icon: JSIcon 4 | --- 5 | 6 | # Usage with code files `.js`/`.jsx` 7 | 8 | You need to add a new 9 | [configuration object](https://eslint.org/docs/latest/use/configure/configuration-files#configuration-objects) 10 | in your `eslint.config.js` to setup GraphQL-ESLint processor for `.js` files. 11 | 12 | ```diff filename="eslint.config.js" 13 | import graphqlPlugin from '@graphql-eslint/eslint-plugin'; 14 | 15 | export default [ 16 | + { 17 | + files: ['**/*.js'], 18 | + processor: graphqlPlugin.processor 19 | + }, 20 | { 21 | files: ['**/*.graphql'], 22 | languageOptions: { 23 | parser: graphqlPlugin.parser 24 | }, 25 | plugins: { 26 | '@graphql-eslint': graphqlPlugin 27 | }, 28 | rules: { 29 | '@graphql-eslint/known-type-names': 'error' 30 | } 31 | } 32 | ] 33 | ``` 34 | 35 | > [!TIP] 36 | > 37 | > Under the hood, the processor extracts schema and operation files from `files: ['**/*.js']` and 38 | > treats them as virtual GraphQL documents with `.graphql` extensions. This enables the overrides 39 | > you define for `.graphql` files, under `files: ['**/*.graphql']`, to be applied to the definitions 40 | > within your code files. 41 | 42 | --- 43 | 44 | 45 | -------------------------------------------------------------------------------- /website/content/rules/no-one-place-fragments.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Disallow fragments that are used only in one place.' 3 | --- 4 | 5 | # `no-one-place-fragments` 6 | 7 | - Category: `Operations` 8 | - Rule name: `@graphql-eslint/no-one-place-fragments` 9 | - Requires GraphQL Schema: `false` 10 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 11 | - Requires GraphQL Operations: `true` 12 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 13 | 14 | {metadata.description} 15 | 16 | ## Usage Examples 17 | 18 | ### Incorrect 19 | 20 | ```graphql 21 | # eslint @graphql-eslint/no-one-place-fragments: 'error' 22 | 23 | fragment UserFields on User { 24 | id 25 | } 26 | 27 | { 28 | user { 29 | ...UserFields 30 | } 31 | } 32 | ``` 33 | 34 | ### Correct 35 | 36 | ```graphql 37 | # eslint @graphql-eslint/no-one-place-fragments: 'error' 38 | 39 | fragment UserFields on User { 40 | id 41 | } 42 | 43 | { 44 | user { 45 | ...UserFields 46 | friends { 47 | ...UserFields 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | ## Resources 54 | 55 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/no-one-place-fragments.ts) 56 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/no-one-place-fragments.spec.ts) 57 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/require-nullable-fields-with-oneof/snapshot.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`require-nullable-fields-with-oneof > invalid > should validate \`input\` 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | input Input @oneOf { 7 | 2 | foo: String! 8 | 3 | bar: [Int]! 9 | 4 | } 10 | 11 | #### ❌ Error 1/2 12 | 13 | 1 | input Input @oneOf { 14 | > 2 | foo: String! 15 | | ^^^ input value "foo" in input "Input" must be nullable when "@oneOf" is in use 16 | 3 | bar: [Int]! 17 | 18 | #### ❌ Error 2/2 19 | 20 | 2 | foo: String! 21 | > 3 | bar: [Int]! 22 | | ^^^ input value "bar" in input "Input" must be nullable when "@oneOf" is in use 23 | 4 | } 24 | `; 25 | 26 | exports[`require-nullable-fields-with-oneof > invalid > should validate \`type\` 1`] = ` 27 | #### ⌨️ Code 28 | 29 | 1 | type Type @oneOf { 30 | 2 | foo: String! 31 | 3 | bar: Int 32 | 4 | } 33 | 34 | #### ❌ Error 35 | 36 | 1 | type Type @oneOf { 37 | > 2 | foo: String! 38 | | ^^^ field "foo" in type "Type" must be nullable when "@oneOf" is in use 39 | 3 | bar: Int 40 | `; 41 | -------------------------------------------------------------------------------- /website/content/rules/require-nullable-result-in-root.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Require nullable fields in root types.' 3 | --- 4 | 5 | # `require-nullable-result-in-root` 6 | 7 | 💡 This rule provides 8 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 9 | 10 | - Category: `Schema` 11 | - Rule name: `@graphql-eslint/require-nullable-result-in-root` 12 | - Requires GraphQL Schema: `true` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | {metadata.description} 18 | 19 | ## Usage Examples 20 | 21 | ### Incorrect 22 | 23 | ```graphql 24 | # eslint @graphql-eslint/require-nullable-result-in-root: 'error' 25 | 26 | type Query { 27 | user: User! 28 | } 29 | ``` 30 | 31 | ### Correct 32 | 33 | ```graphql 34 | # eslint @graphql-eslint/require-nullable-result-in-root: 'error' 35 | 36 | type Query { 37 | foo: User 38 | baz: [User]! 39 | bar: [User!]! 40 | } 41 | ``` 42 | 43 | ## Resources 44 | 45 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/require-nullable-result-in-root.ts) 46 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/require-nullable-result-in-root.spec.ts) 47 | -------------------------------------------------------------------------------- /website/content/rules/known-type-names.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL document is only valid if referenced types (specifically variable definitions and 4 | fragment conditions) are defined by the type schema.' 5 | --- 6 | 7 | # `known-type-names` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` and 10 | `"plugin:@graphql-eslint/operations-recommended"` property in a configuration file enables this 11 | rule. 12 | 13 | 💡 This rule provides 14 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 15 | 16 | - Category: `Schema & Operations` 17 | - Rule name: `@graphql-eslint/known-type-names` 18 | - Requires GraphQL Schema: `true` 19 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 20 | - Requires GraphQL Operations: `false` 21 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 22 | 23 | A GraphQL document is only valid if referenced types (specifically variable definitions and fragment 24 | conditions) are defined by the type schema. 25 | 26 | > This rule is a wrapper around a `graphql-js` validation function. 27 | 28 | ## Resources 29 | 30 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/KnownTypeNamesRule.ts) 31 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/KnownTypeNamesRule-test.ts) 32 | -------------------------------------------------------------------------------- /examples/programmatic/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import graphqlPlugin from '@graphql-eslint/eslint-plugin'; 3 | 4 | export default [ 5 | { 6 | files: ['**/*.js'], 7 | rules: js.configs.recommended.rules, 8 | }, 9 | { 10 | files: ['**/*.graphql'], 11 | languageOptions: { 12 | parser: graphqlPlugin.parser, 13 | parserOptions: { 14 | graphQLConfig: { 15 | schema: 'schema.graphql', 16 | documents: ['query.graphql', 'fragment.graphql', 'fragment2.graphql'], 17 | }, 18 | }, 19 | }, 20 | plugins: { 21 | '@graphql-eslint': graphqlPlugin, 22 | }, 23 | rules: { 24 | '@graphql-eslint/require-selections': ['error', { fieldName: '_id' }], 25 | '@graphql-eslint/unique-fragment-name': 'error', 26 | '@graphql-eslint/no-anonymous-operations': 'error', 27 | '@graphql-eslint/naming-convention': [ 28 | 'error', 29 | { 30 | OperationDefinition: { 31 | style: 'PascalCase', 32 | forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'], 33 | forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'], 34 | }, 35 | }, 36 | ], 37 | '@graphql-eslint/unique-enum-value-names': 'error', 38 | '@graphql-eslint/require-description': ['error', { FieldDefinition: true }], 39 | }, 40 | }, 41 | ]; 42 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/no-duplicate-fields/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ruleTester } from '../../../__tests__/test-utils.js'; 2 | import { rule } from './index.js'; 3 | 4 | ruleTester.run('no-duplicate-fields', rule, { 5 | valid: [], 6 | invalid: [ 7 | { 8 | code: /* GraphQL */ ` 9 | query test($v: String, $t: String, $v: String) { 10 | id 11 | } 12 | `, 13 | errors: [{ message: 'Variable `v` defined multiple times.' }], 14 | }, 15 | { 16 | code: /* GraphQL */ ` 17 | query test { 18 | users(first: 100, after: 10, filter: "test", first: 50) { 19 | id 20 | } 21 | } 22 | `, 23 | errors: [{ message: 'Argument `first` defined multiple times.' }], 24 | }, 25 | { 26 | code: /* GraphQL */ ` 27 | query test { 28 | users { 29 | id 30 | name 31 | email 32 | name 33 | } 34 | } 35 | `, 36 | errors: [{ message: 'Field `name` defined multiple times.' }], 37 | }, 38 | { 39 | code: /* GraphQL */ ` 40 | query test { 41 | users { 42 | id 43 | name 44 | email 45 | email: somethingElse 46 | } 47 | } 48 | `, 49 | errors: [{ message: 'Field `email` defined multiple times.' }], 50 | }, 51 | ], 52 | }); 53 | -------------------------------------------------------------------------------- /website/content/rules/relay-page-info.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Set of rules to follow Relay specification for `PageInfo` object.' 3 | --- 4 | 5 | # `relay-page-info` 6 | 7 | - Category: `Schema` 8 | - Rule name: `@graphql-eslint/relay-page-info` 9 | - Requires GraphQL Schema: `true` 10 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 11 | - Requires GraphQL Operations: `false` 12 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 13 | 14 | Set of rules to follow Relay specification for `PageInfo` object. 15 | 16 | - `PageInfo` must be an Object type 17 | - `PageInfo` must contain fields `hasPreviousPage` and `hasNextPage`, that return non-null Boolean 18 | - `PageInfo` must contain fields `startCursor` and `endCursor`, that return either String or Scalar, 19 | which can be null if there are no results 20 | 21 | ## Usage Examples 22 | 23 | ### Correct 24 | 25 | ```graphql 26 | # eslint @graphql-eslint/relay-page-info: 'error' 27 | 28 | type PageInfo { 29 | hasPreviousPage: Boolean! 30 | hasNextPage: Boolean! 31 | startCursor: String 32 | endCursor: String 33 | } 34 | ``` 35 | 36 | ## Resources 37 | 38 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/relay-page-info.ts) 39 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/relay-page-info.spec.ts) 40 | -------------------------------------------------------------------------------- /website/content/rules/no-scalar-result-type-on-mutation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Avoid scalar result type on mutation type to make sure to return a valid state.' 3 | --- 4 | 5 | # `no-scalar-result-type-on-mutation` 6 | 7 | 💡 This rule provides 8 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 9 | 10 | - Category: `Schema` 11 | - Rule name: `@graphql-eslint/no-scalar-result-type-on-mutation` 12 | - Requires GraphQL Schema: `true` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | {metadata.description} 18 | 19 | ## Usage Examples 20 | 21 | ### Incorrect 22 | 23 | ```graphql 24 | # eslint @graphql-eslint/no-scalar-result-type-on-mutation: 'error' 25 | 26 | type Mutation { 27 | createUser: Boolean 28 | } 29 | ``` 30 | 31 | ### Correct 32 | 33 | ```graphql 34 | # eslint @graphql-eslint/no-scalar-result-type-on-mutation: 'error' 35 | 36 | type Mutation { 37 | createUser: User! 38 | } 39 | ``` 40 | 41 | ## Resources 42 | 43 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/no-scalar-result-type-on-mutation.ts) 44 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/no-scalar-result-type-on-mutation.spec.ts) 45 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/no-unused-fields/snapshot.md: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`no-unused-fields > invalid > Invalid #1 1`] = ` 4 | #### ⌨️ Code 5 | 6 | 1 | type User { 7 | 2 | id: ID! 8 | 3 | firstName: String 9 | 4 | } 10 | 11 | #### ❌ Error 12 | 13 | 2 | id: ID! 14 | > 3 | firstName: String 15 | | ^^^^^^^^^ Field "firstName" is unused 16 | 4 | } 17 | 18 | #### 💡 Suggestion: Remove \`firstName\` field 19 | 20 | 1 | type User { 21 | 2 | id: ID! 22 | 3 | 23 | 4 | } 24 | `; 25 | 26 | exports[`no-unused-fields > invalid > Invalid #2 1`] = ` 27 | #### ⌨️ Code 28 | 29 | 1 | type Query { 30 | 2 | user(id: ID!): User 31 | 3 | } 32 | 4 | 33 | 5 | type Mutation { 34 | 6 | deleteUser(id: ID!): User 35 | 7 | } 36 | 37 | #### ❌ Error 38 | 39 | 5 | type Mutation { 40 | > 6 | deleteUser(id: ID!): User 41 | | ^^^^^^^^^^ Field "deleteUser" is unused 42 | 7 | } 43 | 44 | #### 💡 Suggestion: Remove \`deleteUser\` field 45 | 46 | 1 | type Query { 47 | 2 | user(id: ID!): User 48 | 3 | } 49 | 4 | 50 | 5 | 51 | `; 52 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/no-typename-prefix/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ruleTester } from '../../../__tests__/test-utils.js'; 2 | import { rule } from './index.js'; 3 | 4 | ruleTester.run('no-typename-prefix', rule, { 5 | valid: [ 6 | /* GraphQL */ ` 7 | type User { 8 | id: ID! 9 | } 10 | `, 11 | /* GraphQL */ ` 12 | interface Node { 13 | id: ID! 14 | } 15 | `, 16 | /* GraphQL */ ` 17 | type User { 18 | # eslint-disable-next-line 19 | userId: ID! 20 | } 21 | `, 22 | ], 23 | invalid: [ 24 | { 25 | code: /* GraphQL */ ` 26 | type User { 27 | userId: ID! 28 | } 29 | `, 30 | errors: [{ message: 'Field "userId" starts with the name of the parent type "User"' }], 31 | }, 32 | { 33 | code: /* GraphQL */ ` 34 | type User { 35 | userId: ID! 36 | userName: String! 37 | } 38 | `, 39 | errors: [ 40 | { message: 'Field "userId" starts with the name of the parent type "User"' }, 41 | { message: 'Field "userName" starts with the name of the parent type "User"' }, 42 | ], 43 | }, 44 | { 45 | code: /* GraphQL */ ` 46 | interface Node { 47 | nodeId: ID! 48 | } 49 | `, 50 | errors: [{ message: 'Field "nodeId" starts with the name of the parent type "Node"' }], 51 | }, 52 | ], 53 | }); 54 | -------------------------------------------------------------------------------- /website/content/rules/no-typename-prefix.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'Enforces users to avoid using the type name in a field name while defining your schema.' 4 | --- 5 | 6 | # `no-typename-prefix` 7 | 8 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file 9 | enables this rule. 10 | 11 | 💡 This rule provides 12 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 13 | 14 | - Category: `Schema` 15 | - Rule name: `@graphql-eslint/no-typename-prefix` 16 | - Requires GraphQL Schema: `false` 17 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 18 | - Requires GraphQL Operations: `false` 19 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 20 | 21 | {metadata.description} 22 | 23 | ## Usage Examples 24 | 25 | ### Incorrect 26 | 27 | ```graphql 28 | # eslint @graphql-eslint/no-typename-prefix: 'error' 29 | 30 | type User { 31 | userId: ID! 32 | } 33 | ``` 34 | 35 | ### Correct 36 | 37 | ```graphql 38 | # eslint @graphql-eslint/no-typename-prefix: 'error' 39 | 40 | type User { 41 | id: ID! 42 | } 43 | ``` 44 | 45 | ## Resources 46 | 47 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/no-typename-prefix.ts) 48 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/no-typename-prefix.spec.ts) 49 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/fields-on-correct-type.spec.ts: -------------------------------------------------------------------------------- 1 | import { RuleTester } from '@theguild/eslint-rule-tester'; 2 | import { GRAPHQL_JS_VALIDATIONS } from '../src/rules/graphql-js-validation.js'; 3 | import { DEFAULT_CONFIG, ParserOptionsForTests } from './test-utils.js'; 4 | 5 | const ruleTester = new RuleTester({ 6 | languageOptions: { 7 | ...DEFAULT_CONFIG.languageOptions, 8 | parserOptions: { 9 | graphQLConfig: { 10 | schema: /* GraphQL */ ` 11 | type User { 12 | id: ID 13 | age: Int 14 | } 15 | 16 | type Query { 17 | user: User 18 | } 19 | `, 20 | }, 21 | }, 22 | }, 23 | }); 24 | 25 | ruleTester.run('fields-on-correct-type', GRAPHQL_JS_VALIDATIONS['fields-on-correct-type'], { 26 | valid: [], 27 | invalid: [ 28 | { 29 | name: 'should highlight selection on single line', 30 | code: 'fragment UserFields on User { id bad age }', 31 | errors: [{ message: 'Cannot query field "bad" on type "User". Did you mean "id"?' }], 32 | }, 33 | { 34 | name: 'should highlight selection on multi line', 35 | code: /* GraphQL */ ` 36 | { 37 | user { 38 | id 39 | veryBad 40 | age 41 | } 42 | } 43 | `, 44 | errors: [{ message: 'Cannot query field "veryBad" on type "User".' }], 45 | }, 46 | ], 47 | }); 48 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/processor-without-graphql-config.spec.ts: -------------------------------------------------------------------------------- 1 | import { Block, processor } from '../src/processor.js'; 2 | 3 | describe('processor.preprocess() without graphql-config', () => { 4 | const QUERY = 'query users { id }'; 5 | it('should find "gql" tag', () => { 6 | const code = ` 7 | import { gql } from 'graphql' 8 | const fooQuery = gql\`${QUERY}\` 9 | `; 10 | const blocks = processor.preprocess(code, 'test.js') as Block[]; 11 | 12 | expect(blocks[0].text).toBe(QUERY); 13 | expect(blocks).toMatchInlineSnapshot(` 14 | [ 15 | { 16 | filename: document.graphql, 17 | lineOffset: 2, 18 | offset: 64, 19 | text: query users { id }, 20 | }, 21 | 22 | import { gql } from 'graphql' 23 | const fooQuery = gql\`query users { id }\` 24 | , 25 | ] 26 | `); 27 | }); 28 | 29 | it('should find /* GraphQL */ magic comment', () => { 30 | const code = `/* GraphQL */ \`${QUERY}\``; 31 | const blocks = processor.preprocess(code, 'test.js') as Block[]; 32 | 33 | expect(blocks[0].text).toBe(QUERY); 34 | expect(blocks).toMatchInlineSnapshot(` 35 | [ 36 | { 37 | filename: document.graphql, 38 | lineOffset: 0, 39 | offset: 17, 40 | text: query users { id }, 41 | }, 42 | /* GraphQL */ \`query users { id }\`, 43 | ] 44 | `); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us improve 4 | --- 5 | 6 | ### Issue workflow progress 7 | 8 | 9 | 10 | _Progress of the issue based on the 11 | [Contributor Workflow](https://github.com/the-guild-org/Stack/blob/master/CONTRIBUTING.md#a-typical-contributor-workflow)_ 12 | 13 | - [ ] 1. The issue provides a reproduction available on GitHub, Stackblitz or CodeSandbox 14 | 15 | > For example, you can start off by editing the 16 | > ['basic' example on Stackblitz](https://stackblitz.com/github/dimaMachina/graphql-eslint/tree/master/examples/graphql-config). 17 | 18 | > Please make sure the graphql-eslint version under `package.json` matches yours. 19 | 20 | - [ ] 2. A failing test has been provided 21 | - [ ] 3. A local solution has been provided 22 | - [ ] 4. A pull request is pending review 23 | 24 | --- 25 | 26 | **Describe the bug** 27 | 28 | 29 | 30 | **To Reproduce** Steps to reproduce the behavior: 31 | 32 | 33 | 34 | **Expected behavior** 35 | 36 | 37 | 38 | **Environment:** 39 | 40 | - OS: 41 | - `@graphql-eslint/eslint-plugin`: 42 | - Node.js: 43 | 44 | **Additional context** 45 | 46 | 47 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Website 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | deployment: 11 | runs-on: ubuntu-latest 12 | if: 13 | github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 14 | 'push' 15 | steps: 16 | - name: checkout 17 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - uses: the-guild-org/shared-config/setup@main 22 | name: setup env 23 | with: 24 | nodeVersion: 22 25 | packageManager: pnpm 26 | 27 | - uses: the-guild-org/shared-config/website-cf@main 28 | name: build and deploy website 29 | env: 30 | NEXT_BASE_PATH: ${{ github.ref == 'refs/heads/master' && '/graphql/eslint' || '' }} 31 | SITE_URL: 32 | ${{ github.ref == 'refs/heads/master' && 'https://the-guild.dev/graphql/eslint' || '' }} 33 | with: 34 | cloudflareApiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} 35 | cloudflareAccountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} 36 | githubToken: ${{ secrets.GITHUB_TOKEN }} 37 | projectName: graphql-eslint 38 | prId: ${{ github.event.pull_request.number }} 39 | websiteDirectory: ./ 40 | buildScript: pnpm run build && pnpm --filter website run build 41 | artifactDir: website/out 42 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/unique-operation-name/index.test.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path'; 2 | import { CWD } from '@/utils.js'; 3 | import { ParserOptionsForTests, ruleTester } from '../../../__tests__/test-utils.js'; 4 | import { rule } from './index.js'; 5 | 6 | const TEST_OPERATION = 'query test { foo }'; 7 | 8 | const SIBLING_OPERATIONS = (...documents: string[]) => ({ 9 | parserOptions: { 10 | graphQLConfig: { 11 | documents, 12 | }, 13 | } satisfies ParserOptionsForTests, 14 | }); 15 | 16 | ruleTester.run('unique-operation-name', rule, { 17 | valid: [ 18 | { 19 | ...SIBLING_OPERATIONS(TEST_OPERATION), 20 | code: 'query test2 { foo }', 21 | }, 22 | { 23 | // Compare filepath of code as real instead of virtual with siblings 24 | ...SIBLING_OPERATIONS(join(CWD, '__tests__/mocks/unique-fragment.js')), 25 | filename: join(CWD, '__tests__/mocks/unique-fragment.js/1_document.graphql'), 26 | code: /* GraphQL */ ` 27 | query User { 28 | user { 29 | ...UserFields 30 | } 31 | } 32 | `, 33 | }, 34 | ], 35 | invalid: [ 36 | { 37 | ...SIBLING_OPERATIONS(TEST_OPERATION), 38 | code: 'query test { bar }', 39 | errors: [{ messageId: 'unique-operation-name' }], 40 | }, 41 | { 42 | ...SIBLING_OPERATIONS(TEST_OPERATION, 'query test { bar2 }'), 43 | code: 'query test { bar }', 44 | errors: [{ messageId: 'unique-operation-name' }], 45 | }, 46 | ], 47 | }); 48 | -------------------------------------------------------------------------------- /website/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: What's GraphQL-ESLint, key features and helpful resources. 3 | --- 4 | 5 | # Introduction 6 | 7 | This project integrates GraphQL and ESLint, for a better developer experience. 8 | 9 |
10 | 13 |
Demo GraphQL-ESLint in VSCode
14 |
15 | 16 | ## Features 17 | 18 | - Integrates with ESLint core (as a ESTree parser) 19 | - Works on `.graphql` files, `gql` usages and `/* GraphQL */` magic comments 20 | - Lints both GraphQL schema and GraphQL operations 21 | - Extended type info for more advanced usages 22 | - Supports ESLint directives (for example: `eslint-disable-next-line`) 23 | - Easily extendable - supports custom rules based on GraphQL's AST and ESLint API 24 | - Validates, lints, prettifies and checks for best practices across GraphQL schema and GraphQL 25 | operations 26 | - Integrates with [GraphQL Config](https://the-guild.dev/graphql/config) 27 | - Integrates and visualizes lint issues in popular IDEs (VSCode / WebStorm) 28 | 29 | ## Resources 30 | 31 | - [Shared Schema Policies and Automatic Standards Across Your Company’s Teams](https://youtube.com/watch?v=tjuVrOhdyGY) 32 | 33 | - [Introducing GraphQL-ESLint!](https://the-guild.dev/blog/introducing-graphql-eslint) 34 | 35 | - [GraphQL-ESLint v3.14 - What's New?](https://the-guild.dev/blog/graphql-eslint-3.14) 36 | -------------------------------------------------------------------------------- /website/content/rules/no-anonymous-operations.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'Require name for your GraphQL operations. This is useful since most GraphQL client libraries are 4 | using the operation name for caching purposes.' 5 | --- 6 | 7 | # `no-anonymous-operations` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 10 | enables this rule. 11 | 12 | 💡 This rule provides 13 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 14 | 15 | - Category: `Operations` 16 | - Rule name: `@graphql-eslint/no-anonymous-operations` 17 | - Requires GraphQL Schema: `false` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 19 | - Requires GraphQL Operations: `false` 20 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 21 | 22 | {metadata.description} 23 | 24 | ## Usage Examples 25 | 26 | ### Incorrect 27 | 28 | ```graphql 29 | # eslint @graphql-eslint/no-anonymous-operations: 'error' 30 | 31 | query { 32 | # ... 33 | } 34 | ``` 35 | 36 | ### Correct 37 | 38 | ```graphql 39 | # eslint @graphql-eslint/no-anonymous-operations: 'error' 40 | 41 | query user { 42 | # ... 43 | } 44 | ``` 45 | 46 | ## Resources 47 | 48 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/no-anonymous-operations.ts) 49 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/no-anonymous-operations.spec.ts) 50 | -------------------------------------------------------------------------------- /examples/vue-code-file/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Legacy config example, should be run with `ESLINT_USE_FLAT_CONFIG=false` environment variable in ESLint 9 3 | */ 4 | 5 | module.exports = { 6 | root: true, 7 | // ❗️ It's very important that you don't have any rules configured at the top-level config, 8 | // and to move all configurations into the overrides section. Since JavaScript rules 9 | // can't run on GraphQL files and vice versa, if you have rules configured at the top level, 10 | // they will try to also execute for all overrides, as ESLint's configs cascade 11 | overrides: [ 12 | { 13 | files: ['*.js', '*.vue'], 14 | parser: 'vue-eslint-parser', 15 | processor: '@graphql-eslint/graphql', 16 | extends: ['eslint:recommended'], 17 | env: { 18 | es2022: true, 19 | }, 20 | }, 21 | { 22 | files: ['*.graphql'], 23 | parser: '@graphql-eslint/eslint-plugin', 24 | plugins: ['@graphql-eslint'], 25 | rules: { 26 | '@graphql-eslint/no-anonymous-operations': 'error', 27 | '@graphql-eslint/no-duplicate-fields': 'error', 28 | '@graphql-eslint/naming-convention': [ 29 | 'error', 30 | { 31 | OperationDefinition: { 32 | style: 'PascalCase', 33 | forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'], 34 | forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'], 35 | }, 36 | }, 37 | ], 38 | }, 39 | }, 40 | ], 41 | }; 42 | -------------------------------------------------------------------------------- /examples/svelte-code-file/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Legacy config example, should be run with `ESLINT_USE_FLAT_CONFIG=false` environment variable in ESLint 9 3 | */ 4 | 5 | module.exports = { 6 | root: true, 7 | // ❗️ It's very important that you don't have any rules configured at the top-level config, 8 | // and to move all configurations into the overrides section. Since JavaScript rules 9 | // can't run on GraphQL files and vice versa, if you have rules configured at the top level, 10 | // they will try to also execute for all overrides, as ESLint's configs cascade 11 | overrides: [ 12 | { 13 | files: ['*.js', '*.svelte'], 14 | parser: 'svelte-eslint-parser', 15 | processor: '@graphql-eslint/graphql', 16 | extends: ['eslint:recommended'], 17 | env: { 18 | es2022: true, 19 | }, 20 | }, 21 | { 22 | files: ['*.graphql'], 23 | parser: '@graphql-eslint/eslint-plugin', 24 | plugins: ['@graphql-eslint'], 25 | rules: { 26 | '@graphql-eslint/no-anonymous-operations': 'error', 27 | '@graphql-eslint/no-duplicate-fields': 'error', 28 | '@graphql-eslint/naming-convention': [ 29 | 'error', 30 | { 31 | OperationDefinition: { 32 | style: 'PascalCase', 33 | forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'], 34 | forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'], 35 | }, 36 | }, 37 | ], 38 | }, 39 | }, 40 | ], 41 | }; 42 | -------------------------------------------------------------------------------- /website/content/rules/unique-operation-name.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Enforce unique operation names across your project.' 3 | --- 4 | 5 | # `unique-operation-name` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Operations` 11 | - Rule name: `@graphql-eslint/unique-operation-name` 12 | - Requires GraphQL Schema: `false` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `true` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | {metadata.description} 18 | 19 | ## Usage Examples 20 | 21 | ### Incorrect 22 | 23 | ```graphql 24 | # eslint @graphql-eslint/unique-operation-name: 'error' 25 | 26 | # foo.query.graphql 27 | query user { 28 | user { 29 | id 30 | } 31 | } 32 | 33 | # bar.query.graphql 34 | query user { 35 | me { 36 | id 37 | } 38 | } 39 | ``` 40 | 41 | ### Correct 42 | 43 | ```graphql 44 | # eslint @graphql-eslint/unique-operation-name: 'error' 45 | 46 | # foo.query.graphql 47 | query user { 48 | user { 49 | id 50 | } 51 | } 52 | 53 | # bar.query.graphql 54 | query me { 55 | me { 56 | id 57 | } 58 | } 59 | ``` 60 | 61 | ## Resources 62 | 63 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/unique-operation-name.ts) 64 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/unique-operation-name.spec.ts) 65 | -------------------------------------------------------------------------------- /website/content/rules/no-unreachable-types.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Requires all types to be reachable at some level by root level fields.' 3 | --- 4 | 5 | # `no-unreachable-types` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | 💡 This rule provides 11 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 12 | 13 | - Category: `Schema` 14 | - Rule name: `@graphql-eslint/no-unreachable-types` 15 | - Requires GraphQL Schema: `true` 16 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 17 | - Requires GraphQL Operations: `false` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 19 | 20 | {metadata.description} 21 | 22 | ## Usage Examples 23 | 24 | ### Incorrect 25 | 26 | ```graphql 27 | # eslint @graphql-eslint/no-unreachable-types: 'error' 28 | 29 | type User { 30 | id: ID! 31 | name: String 32 | } 33 | 34 | type Query { 35 | me: String 36 | } 37 | ``` 38 | 39 | ### Correct 40 | 41 | ```graphql 42 | # eslint @graphql-eslint/no-unreachable-types: 'error' 43 | 44 | type User { 45 | id: ID! 46 | name: String 47 | } 48 | 49 | type Query { 50 | me: User 51 | } 52 | ``` 53 | 54 | ## Resources 55 | 56 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/no-unreachable-types.ts) 57 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/no-unreachable-types.spec.ts) 58 | -------------------------------------------------------------------------------- /examples/code-file/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Legacy config example, should be run with `ESLINT_USE_FLAT_CONFIG=false` environment variable in ESLint 9 3 | */ 4 | 5 | module.exports = { 6 | root: true, 7 | // ❗️ It's very important that you don't have any rules configured at the top-level config, 8 | // and to move all configurations into the overrides section. Since JavaScript rules 9 | // can't run on GraphQL files and vice versa, if you have rules configured at the top level, 10 | // they will try to also execute for all overrides, as ESLint's configs cascade 11 | overrides: [ 12 | { 13 | files: ['*.js'], 14 | processor: '@graphql-eslint/graphql', 15 | extends: ['eslint:recommended'], 16 | env: { 17 | es2022: true, 18 | }, 19 | parserOptions: { 20 | sourceType: 'module', 21 | }, 22 | rules: { 23 | 'no-console': 'error', 24 | }, 25 | }, 26 | { 27 | files: ['*.graphql'], 28 | parser: '@graphql-eslint/eslint-plugin', 29 | plugins: ['@graphql-eslint'], 30 | rules: { 31 | '@graphql-eslint/no-anonymous-operations': 'error', 32 | '@graphql-eslint/naming-convention': [ 33 | 'error', 34 | { 35 | OperationDefinition: { 36 | style: 'PascalCase', 37 | forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'], 38 | forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'], 39 | }, 40 | }, 41 | ], 42 | }, 43 | }, 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "analyze": "ANALYZE=true yarn build", 8 | "build": "next build && next-sitemap", 9 | "dev": "next", 10 | "postbuild": "pagefind --site .next/server/app --output-path out/_pagefind", 11 | "prebuild": "tsx ../scripts/generate-docs.ts", 12 | "start": "next start" 13 | }, 14 | "dependencies": { 15 | "@graphql-eslint/eslint-plugin": "workspace:*", 16 | "@monaco-editor/react": "^4.6.0", 17 | "@radix-ui/react-icons": "^1.3.0", 18 | "@radix-ui/react-select": "^2.0.0", 19 | "@theguild/components": "8.0.0-alpha-20241206200036-57d75dbef3b4deb9b1f5dc238935dedaa0922382", 20 | "clsx": "^2.0.0", 21 | "graphql": "^16.9.0", 22 | "lodash.uniqwith": "^4.5.0", 23 | "next": "15.4.7", 24 | "next-sitemap": "4.2.3", 25 | "react": "^18.3.1", 26 | "react-dom": "^18.3.1" 27 | }, 28 | "devDependencies": { 29 | "@svgr/webpack": "^8.1.0", 30 | "@theguild/tailwind-config": "0.6.1", 31 | "@types/lodash.uniqwith": "4.5.9", 32 | "@types/node": "22.13.10", 33 | "@types/react": "18.3.19", 34 | "pagefind": "1.3.0", 35 | "tailwindcss-radix": "4.0.2", 36 | "webpack": "^5.88.2" 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /website/content/rules/unique-fragment-name.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Enforce unique fragment names across your project.' 3 | --- 4 | 5 | # `unique-fragment-name` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Operations` 11 | - Rule name: `@graphql-eslint/unique-fragment-name` 12 | - Requires GraphQL Schema: `false` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `true` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | {metadata.description} 18 | 19 | ## Usage Examples 20 | 21 | ### Incorrect 22 | 23 | ```graphql 24 | # eslint @graphql-eslint/unique-fragment-name: 'error' 25 | 26 | # user.fragment.graphql 27 | fragment UserFields on User { 28 | id 29 | name 30 | fullName 31 | } 32 | 33 | # user-fields.graphql 34 | fragment UserFields on User { 35 | id 36 | } 37 | ``` 38 | 39 | ### Correct 40 | 41 | ```graphql 42 | # eslint @graphql-eslint/unique-fragment-name: 'error' 43 | 44 | # user.fragment.graphql 45 | fragment AllUserFields on User { 46 | id 47 | name 48 | fullName 49 | } 50 | 51 | # user-fields.graphql 52 | fragment UserFields on User { 53 | id 54 | } 55 | ``` 56 | 57 | ## Resources 58 | 59 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/unique-fragment-name.ts) 60 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/unique-fragment-name.spec.ts) 61 | -------------------------------------------------------------------------------- /website/content/rules/require-deprecation-reason.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Require all deprecation directives to specify a reason.' 3 | --- 4 | 5 | # `require-deprecation-reason` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | - Category: `Schema` 11 | - Rule name: `@graphql-eslint/require-deprecation-reason` 12 | - Requires GraphQL Schema: `false` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | {metadata.description} 18 | 19 | ## Usage Examples 20 | 21 | ### Incorrect 22 | 23 | ```graphql 24 | # eslint @graphql-eslint/require-deprecation-reason: 'error' 25 | 26 | type MyType { 27 | name: String @deprecated 28 | } 29 | ``` 30 | 31 | ### Incorrect 32 | 33 | ```graphql 34 | # eslint @graphql-eslint/require-deprecation-reason: 'error' 35 | 36 | type MyType { 37 | name: String @deprecated(reason: "") 38 | } 39 | ``` 40 | 41 | ### Correct 42 | 43 | ```graphql 44 | # eslint @graphql-eslint/require-deprecation-reason: 'error' 45 | 46 | type MyType { 47 | name: String @deprecated(reason: "no longer relevant, please use fullName field") 48 | } 49 | ``` 50 | 51 | ## Resources 52 | 53 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/require-deprecation-reason.ts) 54 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/require-deprecation-reason.spec.ts) 55 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/require-deprecation-date/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ruleTester } from '../../../__tests__/test-utils.js'; 2 | import { rule, RuleOptions } from './index.js'; 3 | 4 | const now = new Date(); 5 | now.setDate(now.getDate() + 1); 6 | 7 | const day = now.getDate().toString().padStart(2, '0'); 8 | const month = (now.getMonth() + 1).toString().padStart(2, '0'); 9 | const year = now.getFullYear(); 10 | 11 | const tomorrow = `${day}/${month}/${year}`; 12 | 13 | ruleTester.run('require-deprecation-date', rule, { 14 | valid: [ 15 | 'type User { firstName: String }', 16 | `scalar Old @deprecated(deletionDate: "${tomorrow}")`, 17 | { 18 | code: `scalar Old @deprecated(untilDate: "${tomorrow}")`, 19 | options: [{ argumentName: 'untilDate' }], 20 | }, 21 | /* GraphQL */ ` 22 | type User { 23 | firstname: String @deprecated(deletionDate: "22/08/2031") 24 | firstName: String 25 | } 26 | `, 27 | ], 28 | invalid: [ 29 | { 30 | code: 'scalar Old @deprecated(deletionDate: "22/08/2021")', 31 | errors: 1, 32 | }, 33 | { 34 | code: 'scalar Old @deprecated(untilDate: "22/08/2021")', 35 | options: [{ argumentName: 'untilDate' }], 36 | errors: 1, 37 | }, 38 | { 39 | code: 'scalar Old @deprecated(deletionDate: "bad")', 40 | errors: 1, 41 | }, 42 | { 43 | code: 'scalar Old @deprecated(deletionDate: "32/08/2021")', 44 | errors: 1, 45 | }, 46 | { 47 | code: 'type Old { oldField: ID @deprecated }', 48 | errors: 1, 49 | }, 50 | ], 51 | }); 52 | -------------------------------------------------------------------------------- /website/content/rules/unique-enum-value-names.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'A GraphQL enum type is only valid if all its values are uniquely named.' 3 | --- 4 | 5 | # `unique-enum-value-names` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | 💡 This rule provides 11 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 12 | 13 | - Category: `Schema` 14 | - Rule name: `@graphql-eslint/unique-enum-value-names` 15 | - Requires GraphQL Schema: `false` 16 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 17 | - Requires GraphQL Operations: `false` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 19 | 20 | A GraphQL enum type is only valid if all its values are uniquely named. 21 | 22 | > This rule disallows case-insensitive enum values duplicates too. 23 | 24 | ## Usage Examples 25 | 26 | ### Incorrect 27 | 28 | ```graphql 29 | # eslint @graphql-eslint/unique-enum-value-names: 'error' 30 | 31 | enum MyEnum { 32 | Value 33 | VALUE 34 | ValuE 35 | } 36 | ``` 37 | 38 | ### Correct 39 | 40 | ```graphql 41 | # eslint @graphql-eslint/unique-enum-value-names: 'error' 42 | 43 | enum MyEnum { 44 | Value1 45 | Value2 46 | Value3 47 | } 48 | ``` 49 | 50 | ## Resources 51 | 52 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/unique-enum-value-names.ts) 53 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/unique-enum-value-names.spec.ts) 54 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/no-root-type/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ParserOptionsForTests, ruleTester } from '../../../__tests__/test-utils.js'; 2 | import { rule, RuleOptions } from './index.js'; 3 | 4 | const useSchema = (code: string, schema = '') => ({ 5 | code, 6 | parserOptions: { 7 | graphQLConfig: { 8 | schema: schema + code, 9 | }, 10 | } satisfies ParserOptionsForTests, 11 | }); 12 | 13 | ruleTester.run('no-root-type', rule, { 14 | valid: [ 15 | { 16 | ...useSchema('type Query'), 17 | options: [{ disallow: ['mutation', 'subscription'] }], 18 | }, 19 | ], 20 | invalid: [ 21 | { 22 | ...useSchema('type Mutation'), 23 | name: 'disallow mutation', 24 | options: [{ disallow: ['mutation'] }], 25 | errors: [{ message: 'Root type `Mutation` is forbidden.' }], 26 | }, 27 | { 28 | ...useSchema('type Subscription'), 29 | name: 'disallow subscription', 30 | options: [{ disallow: ['subscription'] }], 31 | errors: [{ message: 'Root type `Subscription` is forbidden.' }], 32 | }, 33 | { 34 | ...useSchema('extend type Mutation { foo: ID }', 'type Mutation'), 35 | name: 'disallow with extend', 36 | options: [{ disallow: ['mutation'] }], 37 | errors: [{ message: 'Root type `Mutation` is forbidden.' }], 38 | }, 39 | { 40 | ...useSchema('type MyMutation', 'schema { mutation: MyMutation }'), 41 | name: 'disallow when root type name is renamed', 42 | options: [{ disallow: ['mutation'] }], 43 | errors: [{ message: 'Root type `MyMutation` is forbidden.' }], 44 | }, 45 | ], 46 | }); 47 | -------------------------------------------------------------------------------- /website/content/rules/lone-executable-definition.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'Require queries, mutations, subscriptions or fragments to be located in separate files.' 4 | --- 5 | 6 | # `lone-executable-definition` 7 | 8 | - Category: `Operations` 9 | - Rule name: `@graphql-eslint/lone-executable-definition` 10 | - Requires GraphQL Schema: `false` 11 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 12 | - Requires GraphQL Operations: `false` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 14 | 15 | {metadata.description} 16 | 17 | ## Usage Examples 18 | 19 | ### Incorrect 20 | 21 | ```graphql 22 | # eslint @graphql-eslint/lone-executable-definition: 'error' 23 | 24 | query Foo { 25 | id 26 | } 27 | fragment Bar on Baz { 28 | id 29 | } 30 | ``` 31 | 32 | ### Correct 33 | 34 | ```graphql 35 | # eslint @graphql-eslint/lone-executable-definition: 'error' 36 | 37 | query Foo { 38 | id 39 | } 40 | ``` 41 | 42 | ## Config Schema 43 | 44 | The schema defines the following properties: 45 | 46 | ### `ignore` (array) 47 | 48 | Allow certain definitions to be placed alongside others. 49 | 50 | The elements of the array can contain the following enum values: 51 | 52 | - `fragment` 53 | - `query` 54 | - `mutation` 55 | - `subscription` 56 | 57 | Additional restrictions: 58 | 59 | - Minimum items: `1` 60 | - Unique items: `true` 61 | 62 | ## Resources 63 | 64 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/lone-executable-definition.ts) 65 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/lone-executable-definition.spec.ts) 66 | -------------------------------------------------------------------------------- /website/content/rules/no-root-type.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Disallow using root types `mutation` and/or `subscription`.' 3 | --- 4 | 5 | # `no-root-type` 6 | 7 | 💡 This rule provides 8 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 9 | 10 | - Category: `Schema` 11 | - Rule name: `@graphql-eslint/no-root-type` 12 | - Requires GraphQL Schema: `true` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | {metadata.description} 18 | 19 | ## Usage Examples 20 | 21 | ### Incorrect 22 | 23 | ```graphql 24 | # eslint @graphql-eslint/no-root-type: ['error', { disallow: ['mutation', 'subscription'] }] 25 | 26 | type Mutation { 27 | createUser(input: CreateUserInput!): User! 28 | } 29 | ``` 30 | 31 | ### Correct 32 | 33 | ```graphql 34 | # eslint @graphql-eslint/no-root-type: ['error', { disallow: ['mutation', 'subscription'] }] 35 | 36 | type Query { 37 | users: [User!]! 38 | } 39 | ``` 40 | 41 | ## Config Schema 42 | 43 | The schema defines the following properties: 44 | 45 | ### `disallow` (array, required) 46 | 47 | The elements of the array can contain the following enum values: 48 | 49 | - `mutation` 50 | - `subscription` 51 | 52 | Additional restrictions: 53 | 54 | - Minimum items: `1` 55 | - Unique items: `true` 56 | 57 | ## Resources 58 | 59 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/no-root-type.ts) 60 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/no-root-type.spec.ts) 61 | -------------------------------------------------------------------------------- /website/content/rules/known-directives.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'A GraphQL document is only valid if all `@directive`s are known by the schema and legally 4 | positioned.' 5 | --- 6 | 7 | # `known-directives` 8 | 9 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` and 10 | `"plugin:@graphql-eslint/operations-recommended"` property in a configuration file enables this 11 | rule. 12 | 13 | - Category: `Schema & Operations` 14 | - Rule name: `@graphql-eslint/known-directives` 15 | - Requires GraphQL Schema: `true` 16 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 17 | - Requires GraphQL Operations: `false` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 19 | 20 | A GraphQL document is only valid if all `@directive`s are known by the schema and legally 21 | positioned. 22 | 23 | > This rule is a wrapper around a `graphql-js` validation function. 24 | 25 | ## Usage Examples 26 | 27 | ### Valid 28 | 29 | ```graphql 30 | # eslint @graphql-eslint/known-directives: ['error', { ignoreClientDirectives: ['client'] }] 31 | 32 | { 33 | product { 34 | someClientField @client 35 | } 36 | } 37 | ``` 38 | 39 | ## Config Schema 40 | 41 | The schema defines the following properties: 42 | 43 | ### `ignoreClientDirectives` (array, required) 44 | 45 | The object is an array with all elements of the type `string`. 46 | 47 | Additional restrictions: 48 | 49 | - Minimum items: `1` 50 | - Unique items: `true` 51 | 52 | ## Resources 53 | 54 | - [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/KnownDirectivesRule.ts) 55 | - [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/KnownDirectivesRule-test.ts) 56 | -------------------------------------------------------------------------------- /packages/plugin/src/rules/require-deprecation-reason/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ruleTester } from '../../../__tests__/test-utils.js'; 2 | import { rule } from './index.js'; 3 | 4 | ruleTester.run('require-deprecation-reason', rule, { 5 | valid: [ 6 | /* GraphQL */ ` 7 | query getUser { 8 | f 9 | a 10 | b 11 | } 12 | `, 13 | /* GraphQL */ ` 14 | type test { 15 | field1: String @authorized 16 | field2: Number 17 | field4: String @deprecated(reason: "Reason") 18 | } 19 | 20 | enum testEnum { 21 | item1 @authorized 22 | item2 @deprecated(reason: 0) 23 | item3 24 | } 25 | 26 | interface testInterface { 27 | field1: String @authorized 28 | field2: Number 29 | field3: String @deprecated(reason: 1.5) 30 | } 31 | `, 32 | ], 33 | invalid: [ 34 | { 35 | code: /* GraphQL */ ` 36 | type A { 37 | deprecatedWithoutReason: String @deprecated 38 | deprecatedWithReason: String @deprecated(reason: "Reason") 39 | notDeprecated: String 40 | } 41 | 42 | enum TestEnum { 43 | item1 @deprecated 44 | item2 @deprecated(reason: "Reason") 45 | } 46 | 47 | interface TestInterface { 48 | item1: String @deprecated 49 | item2: Number @deprecated(reason: "Reason") 50 | item3: String 51 | item4: String @deprecated(reason: "") 52 | item5: String @deprecated(reason: " ") 53 | } 54 | 55 | type MyQuery @deprecated 56 | 57 | input MyInput { 58 | foo: String! @deprecated 59 | } 60 | `, 61 | errors: 7, 62 | }, 63 | ], 64 | }); 65 | -------------------------------------------------------------------------------- /website/content/rules/description-style.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Require all comments to follow the same style (either block or inline).' 3 | --- 4 | 5 | # `description-style` 6 | 7 | ✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file 8 | enables this rule. 9 | 10 | 💡 This rule provides 11 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 12 | 13 | - Category: `Schema` 14 | - Rule name: `@graphql-eslint/description-style` 15 | - Requires GraphQL Schema: `false` 16 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 17 | - Requires GraphQL Operations: `false` 18 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 19 | 20 | {metadata.description} 21 | 22 | ## Usage Examples 23 | 24 | ### Incorrect 25 | 26 | ```graphql 27 | # eslint @graphql-eslint/description-style: ['error', { style: 'inline' }] 28 | 29 | """ Description """ 30 | type someTypeName { 31 | # ... 32 | } 33 | ``` 34 | 35 | ### Correct 36 | 37 | ```graphql 38 | # eslint @graphql-eslint/description-style: ['error', { style: 'inline' }] 39 | 40 | " Description " 41 | type someTypeName { 42 | # ... 43 | } 44 | ``` 45 | 46 | ## Config Schema 47 | 48 | The schema defines the following properties: 49 | 50 | ### `style` (enum) 51 | 52 | This element must be one of the following enum values: 53 | 54 | - `block` 55 | - `inline` 56 | 57 | Default: `"block"` 58 | 59 | ## Resources 60 | 61 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/description-style.ts) 62 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/description-style.spec.ts) 63 | -------------------------------------------------------------------------------- /website/content/rules/relay-connection-types.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Set of rules to follow Relay specification for Connection types.' 3 | --- 4 | 5 | # `relay-connection-types` 6 | 7 | - Category: `Schema` 8 | - Rule name: `@graphql-eslint/relay-connection-types` 9 | - Requires GraphQL Schema: `false` 10 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 11 | - Requires GraphQL Operations: `false` 12 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 13 | 14 | Set of rules to follow Relay specification for Connection types. 15 | 16 | - Any type whose name ends in "Connection" is considered by spec to be a `Connection type` 17 | - Connection type must be an Object type 18 | - Connection type must contain a field `edges` that return a list type that wraps an edge type 19 | - Connection type must contain a field `pageInfo` that return a non-null `PageInfo` Object type 20 | 21 | ## Usage Examples 22 | 23 | ### Incorrect 24 | 25 | ```graphql 26 | # eslint @graphql-eslint/relay-connection-types: 'error' 27 | 28 | type UserPayload { # should be an Object type with `Connection` suffix 29 | edges: UserEdge! # should return a list type 30 | pageInfo: PageInfo # should return a non-null `PageInfo` Object type 31 | } 32 | ``` 33 | 34 | ### Correct 35 | 36 | ```graphql 37 | # eslint @graphql-eslint/relay-connection-types: 'error' 38 | 39 | type UserConnection { 40 | edges: [UserEdge] 41 | pageInfo: PageInfo! 42 | } 43 | ``` 44 | 45 | ## Resources 46 | 47 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/relay-connection-types.ts) 48 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/relay-connection-types.spec.ts) 49 | -------------------------------------------------------------------------------- /website/app/icons/prettier.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /website/content/rules/require-field-of-type-query-in-mutation-result.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'Allow the client in one round-trip not only to call mutation but also to get a wagon of data to 4 | update their application.' 5 | --- 6 | 7 | # `require-field-of-type-query-in-mutation-result` 8 | 9 | - Category: `Schema` 10 | - Rule name: `@graphql-eslint/require-field-of-type-query-in-mutation-result` 11 | - Requires GraphQL Schema: `true` 12 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 13 | - Requires GraphQL Operations: `false` 14 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 15 | 16 | Allow the client in one round-trip not only to call mutation but also to get a wagon of data to 17 | update their application. 18 | 19 | > Currently, no errors are reported for result type `union`, `interface` and `scalar`. 20 | 21 | ## Usage Examples 22 | 23 | ### Incorrect 24 | 25 | ```graphql 26 | # eslint @graphql-eslint/require-field-of-type-query-in-mutation-result: 'error' 27 | 28 | type User { ... } 29 | 30 | type Mutation { 31 | createUser: User! 32 | } 33 | ``` 34 | 35 | ### Correct 36 | 37 | ```graphql 38 | # eslint @graphql-eslint/require-field-of-type-query-in-mutation-result: 'error' 39 | 40 | type User { ... } 41 | 42 | type Query { ... } 43 | 44 | type CreateUserPayload { 45 | user: User! 46 | query: Query! 47 | } 48 | 49 | type Mutation { 50 | createUser: CreateUserPayload! 51 | } 52 | ``` 53 | 54 | ## Resources 55 | 56 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/require-field-of-type-query-in-mutation-result.ts) 57 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/require-field-of-type-query-in-mutation-result.spec.ts) 58 | -------------------------------------------------------------------------------- /examples/programmatic/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Legacy config example, should be run with `ESLINT_USE_FLAT_CONFIG=false` environment variable in ESLint 9 3 | */ 4 | 5 | module.exports = { 6 | root: true, 7 | // ❗️ It's very important that you don't have any rules configured at the top-level config, 8 | // and to move all configurations into the overrides section. Since JavaScript rules 9 | // can't run on GraphQL files and vice versa, if you have rules configured at the top level, 10 | // they will try to also execute for all overrides, as ESLint's configs cascade 11 | overrides: [ 12 | { 13 | files: ['*.js'], 14 | extends: ['eslint:recommended'], 15 | }, 16 | { 17 | files: ['*.graphql'], 18 | parser: '@graphql-eslint/eslint-plugin', 19 | parserOptions: { 20 | graphQLConfig: { 21 | schema: 'schema.graphql', 22 | documents: ['query.graphql', 'fragment.graphql', 'fragment2.graphql'], 23 | }, 24 | }, 25 | plugins: ['@graphql-eslint'], 26 | rules: { 27 | '@graphql-eslint/require-selections': ['error', { fieldName: '_id' }], 28 | '@graphql-eslint/unique-fragment-name': 'error', 29 | '@graphql-eslint/no-anonymous-operations': 'error', 30 | '@graphql-eslint/naming-convention': [ 31 | 'error', 32 | { 33 | OperationDefinition: { 34 | style: 'PascalCase', 35 | forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'], 36 | forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'], 37 | }, 38 | }, 39 | ], 40 | '@graphql-eslint/unique-enum-value-names': 'error', 41 | '@graphql-eslint/require-description': ['error', { FieldDefinition: true }], 42 | }, 43 | }, 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/processor-with-graphql-config.spec.ts: -------------------------------------------------------------------------------- 1 | import { Block, processor } from '../src/processor.js'; 2 | 3 | vi.mock('../src/graphql-config', () => ({ 4 | loadOnDiskGraphQLConfig: vi.fn(() => ({ 5 | getProjectForFile: () => ({ 6 | extensions: { 7 | pluckConfig: { 8 | modules: [{ name: 'custom-gql-tag', identifier: 'custom' }], 9 | gqlMagicComment: 'CustoM', 10 | }, 11 | }, 12 | }), 13 | })), 14 | })); 15 | 16 | describe('processor.preprocess() with graphql-config', () => { 17 | const QUERY = 'query users { id }'; 18 | it('should find "custom" tag', () => { 19 | const code = ` 20 | import { custom } from 'custom-gql-tag' 21 | const fooQuery = custom\`${QUERY}\` 22 | `; 23 | const blocks = processor.preprocess(code, 'test.js') as Block[]; 24 | 25 | expect(blocks[0].text).toBe(QUERY); 26 | expect(blocks).toMatchInlineSnapshot(` 27 | [ 28 | { 29 | filename: document.graphql, 30 | lineOffset: 2, 31 | offset: 77, 32 | text: query users { id }, 33 | }, 34 | 35 | import { custom } from 'custom-gql-tag' 36 | const fooQuery = custom\`query users { id }\` 37 | , 38 | ] 39 | `); 40 | }); 41 | 42 | it('should find /* CustoM */ magic comment', () => { 43 | const code = `/* CustoM */ \`${QUERY}\``; 44 | const blocks = processor.preprocess(code, 'test.js') as Block[]; 45 | 46 | expect(blocks[0].text).toBe(QUERY); 47 | expect(blocks).toMatchInlineSnapshot(` 48 | [ 49 | { 50 | filename: document.graphql, 51 | lineOffset: 0, 52 | offset: 16, 53 | text: query users { id }, 54 | }, 55 | /* CustoM */ \`query users { id }\`, 56 | ] 57 | `); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /packages/plugin/__tests__/eslint-directives.spec.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path'; 2 | import { rule as noAnonymousOperations } from '@/rules/no-anonymous-operations/index.js'; 3 | import { rule as noTypenamePrefix } from '@/rules/no-typename-prefix/index.js'; 4 | import { ruleTester } from './test-utils.js'; 5 | 6 | ruleTester.run('no-typename-prefix', noTypenamePrefix, { 7 | valid: [ 8 | { 9 | name: 'should work with descriptions #942', 10 | code: /* GraphQL */ ` 11 | type Type { 12 | "Some description" 13 | typeName: String! # eslint-disable-line rule-to-test/no-typename-prefix 14 | } 15 | `, 16 | }, 17 | ], 18 | invalid: [], 19 | }); 20 | 21 | ruleTester.run('test-directives', noAnonymousOperations, { 22 | valid: [ 23 | /* GraphQL */ ` 24 | # eslint-disable-next-line 25 | { 26 | a 27 | } 28 | `, 29 | /* GraphQL */ ` 30 | # eslint-disable-next-line rule-to-test/test-directives 31 | { 32 | a 33 | } 34 | `, 35 | '{ a } # eslint-disable-line rule-to-test/test-directives', 36 | '{ a } # eslint-disable-line', 37 | /* GraphQL */ ` 38 | # eslint-disable 39 | { 40 | a 41 | } 42 | `, 43 | { 44 | filename: join(__dirname, 'mocks/test-directives-with-import.graphql'), 45 | code: ruleTester.fromMockFile('test-directives-with-import.graphql'), 46 | }, 47 | ], 48 | invalid: [ 49 | { 50 | code: /* GraphQL */ ` 51 | # eslint-disable-next-line non-existing-rule 52 | { 53 | a 54 | } 55 | `, 56 | errors: [ 57 | { message: "Definition for rule 'non-existing-rule' was not found." }, 58 | { message: 'Anonymous GraphQL operations are forbidden. Make sure to name your query!' }, 59 | ], 60 | }, 61 | ], 62 | }); 63 | -------------------------------------------------------------------------------- /website/app/icons/svelte.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /website/content/rules/relay-arguments.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Set of rules to follow Relay specification for Arguments.' 3 | --- 4 | 5 | # `relay-arguments` 6 | 7 | - Category: `Schema` 8 | - Rule name: `@graphql-eslint/relay-arguments` 9 | - Requires GraphQL Schema: `false` 10 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 11 | - Requires GraphQL Operations: `false` 12 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 13 | 14 | Set of rules to follow Relay specification for Arguments. 15 | 16 | - A field that returns a Connection type must include forward pagination arguments (`first` and 17 | `after`), backward pagination arguments (`last` and `before`), or both 18 | 19 | Forward pagination arguments 20 | 21 | - `first` takes a non-negative integer 22 | - `after` takes the Cursor type 23 | 24 | Backward pagination arguments 25 | 26 | - `last` takes a non-negative integer 27 | - `before` takes the Cursor type 28 | 29 | ## Usage Examples 30 | 31 | ### Incorrect 32 | 33 | ```graphql 34 | # eslint @graphql-eslint/relay-arguments: 'error' 35 | 36 | type User { 37 | posts: PostConnection 38 | } 39 | ``` 40 | 41 | ### Correct 42 | 43 | ```graphql 44 | # eslint @graphql-eslint/relay-arguments: 'error' 45 | 46 | type User { 47 | posts(after: String, first: Int, before: String, last: Int): PostConnection 48 | } 49 | ``` 50 | 51 | ## Config Schema 52 | 53 | The schema defines the following properties: 54 | 55 | ### `includeBoth` (boolean) 56 | 57 | Enforce including both forward and backward pagination arguments 58 | 59 | Default: `true` 60 | 61 | ## Resources 62 | 63 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/relay-arguments.ts) 64 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/relay-arguments.spec.ts) 65 | -------------------------------------------------------------------------------- /website/content/rules/require-deprecation-date.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | 'Require deletion date on `@deprecated` directive. Suggest removing deprecated things after 4 | deprecated date.' 5 | --- 6 | 7 | # `require-deprecation-date` 8 | 9 | 💡 This rule provides 10 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 11 | 12 | - Category: `Schema` 13 | - Rule name: `@graphql-eslint/require-deprecation-date` 14 | - Requires GraphQL Schema: `false` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 16 | - Requires GraphQL Operations: `false` 17 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 18 | 19 | {metadata.description} 20 | 21 | ## Usage Examples 22 | 23 | ### Incorrect 24 | 25 | ```graphql 26 | # eslint @graphql-eslint/require-deprecation-date: 'error' 27 | 28 | type User { 29 | firstname: String @deprecated 30 | firstName: String 31 | } 32 | ``` 33 | 34 | ### Incorrect 35 | 36 | ```graphql 37 | # eslint @graphql-eslint/require-deprecation-date: 'error' 38 | 39 | type User { 40 | firstname: String @deprecated(reason: "Use 'firstName' instead") 41 | firstName: String 42 | } 43 | ``` 44 | 45 | ### Correct 46 | 47 | ```graphql 48 | # eslint @graphql-eslint/require-deprecation-date: 'error' 49 | 50 | type User { 51 | firstname: String @deprecated(reason: "Use 'firstName' instead", deletionDate: "25/12/2022") 52 | firstName: String 53 | } 54 | ``` 55 | 56 | ## Config Schema 57 | 58 | The schema defines the following properties: 59 | 60 | ### `argumentName` (string) 61 | 62 | ## Resources 63 | 64 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/require-deprecation-date.ts) 65 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/require-deprecation-date.spec.ts) 66 | -------------------------------------------------------------------------------- /website/content/rules/require-import-fragment.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Require fragments to be imported via an import expression.' 3 | --- 4 | 5 | # `require-import-fragment` 6 | 7 | 💡 This rule provides 8 | [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) 9 | 10 | - Category: `Operations` 11 | - Rule name: `@graphql-eslint/require-import-fragment` 12 | - Requires GraphQL Schema: `false` 13 | [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) 14 | - Requires GraphQL Operations: `true` 15 | [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) 16 | 17 | {metadata.description} 18 | 19 | ## Usage Examples 20 | 21 | ### Incorrect 22 | 23 | ```graphql 24 | # eslint @graphql-eslint/require-import-fragment: 'error' 25 | 26 | query { 27 | user { 28 | ...UserFields 29 | } 30 | } 31 | ``` 32 | 33 | ### Incorrect 34 | 35 | ```graphql 36 | # eslint @graphql-eslint/require-import-fragment: 'error' 37 | 38 | # import 'post-fields.fragment.graphql' 39 | query { 40 | user { 41 | ...UserFields 42 | } 43 | } 44 | ``` 45 | 46 | ### Incorrect 47 | 48 | ```graphql 49 | # eslint @graphql-eslint/require-import-fragment: 'error' 50 | 51 | # import UserFields from 'post-fields.fragment.graphql' 52 | query { 53 | user { 54 | ...UserFields 55 | } 56 | } 57 | ``` 58 | 59 | ### Correct 60 | 61 | ```graphql 62 | # eslint @graphql-eslint/require-import-fragment: 'error' 63 | 64 | # import UserFields from 'user-fields.fragment.graphql' 65 | query { 66 | user { 67 | ...UserFields 68 | } 69 | } 70 | ``` 71 | 72 | ## Resources 73 | 74 | - [Rule source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/src/rules/require-import-fragment.ts) 75 | - [Test source](https://github.com/dimaMachina/graphql-eslint/tree/master/packages/plugin/__tests__/require-import-fragment.spec.ts) 76 | --------------------------------------------------------------------------------