Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 45 additions & 16 deletions packages/eslint-config-graphql/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# @alma-oss/eslint-config-graphql

> Alma’s ESLint config for projects using GraphQL.

Validates GraphQL schema definitions and embedded GraphQL in JavaScript/TypeScript files.

## Install

```bash
Expand All @@ -8,34 +12,59 @@ npm install @alma-oss/eslint-config-graphql -D

## Usage

Create a _.eslintrc.js_ file with the following contents:
Create a _eslint.config.js_ file with the following contents:

```js
module.exports = {
extends: [
// ... (base eslint config)
'@alma-oss/eslint-config-graphql',
],
};
// eslint.config.js

import graphqlConfig from '@alma-oss/eslint-config-graphql';

export default [
// ... your other configs
...graphqlConfig,
];
Comment thread
literat marked this conversation as resolved.
```

The shareable config can be customized in your [**eslint** configuration file](https://eslint.org/docs/user-guide/configuring).
## Processing

### Embedded GraphQL

Additionally don’t forget to have `.graphqlconfig` file:
The config includes a processor that validates GraphQL code embedded in JavaScript/TypeScript template literals. Tag your GraphQL with `gql` or `graphql` to enable processing:

```json
{
// ...
"schemaPath": "schema.json"
// ...
}
```js
// ✅ Valid - will be linted
const query = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
}
}
`;

// ✅ Valid - graphql tag also works
const mutation = graphql`
mutation CreateUser($name: String!) {
createUser(name: $name) {
id
}
}
`;
```

### `.graphql` Files

The config also validates standalone `.graphql` schema and query files.

## Plugins

This configuration uses the following plugins:

- [`@graphql-eslint/eslint-plugin`](https://the-guild.dev/graphql/eslint/docs/getting-started)
- [`@graphql-eslint/eslint-plugin`](https://the-guild.dev/graphql/eslint/docs/getting-started) — GraphQL validation and code style rules

## Rules

For available rules see [ESLint plugin GraphQL](https://the-guild.dev/graphql/eslint/rules).

## 📝 License

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable no-unused-vars, no-undef */

const schema = gql`
type Query {
user: UnknownUser
}
`;

const fragment = graphql`
fragment AuthorInfo on NonExistentAuthor {
id
name
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* eslint-disable no-unused-vars, no-undef */

const query = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
}
}
`;

const mutation = graphql`
mutation CreateUser($name: String!) {
createUser(name: $name) {
id
}
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable no-unused-vars */

import React from 'react';
// eslint-disable-next-line import/no-unresolved
import { gql } from 'apollo-client';

const UserQuery = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;

export function UserProfile({ userId }: { userId: string }) {
return <div>User: {userId}</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type Query {
user: UnknownType
}

type User {
id: ID
name: String
profile: NonExistentProfile
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type Query {
user: User
}

type User {
id: ID
name: String
email: String
}
118 changes: 118 additions & 0 deletions packages/eslint-config-graphql/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import { beforeEach, describe, it } from 'node:test';

import { ESLint } from 'eslint';

import config from '../index.js';

describe('@alma-oss/eslint-config-graphql', () => {
describe('config structure', () => {
it('exports a flat config array', () => {
assert.ok(Array.isArray(config));
});

it('exports exactly 2 config entries', () => {
assert.equal(config.length, 2);
});

it('first entry has correct name', () => {
assert.equal(config[0].name, '@alma-oss/eslint-config-graphql');
});

it('first entry registers the graphql-eslint processor', () => {
assert.ok(config[0].processor != null);
});

it('second entry targets *.graphql files', () => {
assert.deepEqual(config[1].files, ['*.graphql']);
});

it('second entry registers the @graphql-eslint plugin', () => {
assert.ok(config[1].plugins['@graphql-eslint'] != null);
});

it('second entry enables known-type-names as error', () => {
assert.equal(config[1].rules['@graphql-eslint/known-type-names'], 'error');
});
});
Comment thread
literat marked this conversation as resolved.

describe('valid GraphQL schema', () => {
const code = fs.readFileSync('./__tests__/__fixtures__/schema-valid.graphql', 'utf-8');
let result;

beforeEach(async () => {
const overrideConfig = [
config[0],
{
...config[1],
languageOptions: {
...config[1].languageOptions,
parserOptions: {
schemaSdl: code,
},
},
},
];

const eslint = new ESLint({
overrideConfigFile: true,
overrideConfig,
});
const [lintResult] = await eslint.lintText(code, { filePath: 'schema-valid.graphql' });
result = lintResult;
});

it('flags no warnings', () => {
assert.equal(result.messages.length, 0);
});
});

describe('invalid GraphQL schema (unknown type names)', () => {
const validCode = fs.readFileSync('./__tests__/__fixtures__/schema-valid.graphql', 'utf-8');
const invalidCode = fs.readFileSync('./__tests__/__fixtures__/schema-invalid.graphql', 'utf-8');
let result;

beforeEach(async () => {
const overrideConfig = [
config[0],
{
...config[1],
languageOptions: {
...config[1].languageOptions,
parserOptions: {
// Use valid schema as the universe of known types
schemaSdl: validCode,
},
},
},
];

const eslint = new ESLint({
overrideConfigFile: true,
overrideConfig,
});
// Lint the invalid schema against the valid schema's type universe
const [lintResult] = await eslint.lintText(invalidCode, { filePath: 'schema-invalid.graphql' });
result = lintResult;
});

it('flags warnings', () => {
assert.ok(result.messages.length > 0);
});

it('flags the known-type-names rule', () => {
assert.ok(result.messages.some((m) => m.ruleId === '@graphql-eslint/known-type-names'));
});

it('reports error severity', () => {
const errors = result.messages.filter((m) => m.ruleId === '@graphql-eslint/known-type-names');
assert.ok(errors.every((m) => m.severity === 2));
});

it('flags exactly 2 unknown type references', () => {
const errors = result.messages.filter((m) => m.ruleId === '@graphql-eslint/known-type-names');
assert.equal(errors.length, 2);
});
});
});
43 changes: 25 additions & 18 deletions packages/eslint-config-graphql/index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
const globs = require('@lmc-eu/eslint-config-base/globs');
import globs from '@alma-oss/eslint-config-base/globs';
// eslint-disable-next-line import/no-unresolved
import graphqlPlugin from '@graphql-eslint/eslint-plugin';

module.exports = {
overrides: [
{
files: [...globs.configs, ...globs, globs.typescripts],
processor: '@graphql-eslint/graphql',
export default [
{
name: '@alma-oss/eslint-config-graphql',
files: [...globs.configs, ...globs.javascripts, ...globs.typescripts],
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The processor is only applied to globs.javascripts and globs.typescripts, but globs.javascripts does not include *.jsx. This means embedded GraphQL in JSX files (common with React) won’t be processed/linted. Consider expanding the files patterns here to include *.jsx (and any other JS variants you support) so the README claim about embedded GraphQL applies to React code too.

Suggested change
files: [...globs.configs, ...globs.javascripts, ...globs.typescripts],
files: [...globs.configs, ...globs.javascripts, ...globs.typescripts, '*.jsx'],

Copilot uses AI. Check for mistakes.
processor: graphqlPlugin.processor,
},
{
files: ['*.graphql'],
languageOptions: {
parser: graphqlPlugin.parser,
},
{
files: ['*.graphql'],

parser: '@graphql-eslint/eslint-plugin',

plugins: ['@graphql-eslint'],

rules: {
'@graphql-eslint/known-type-names': 'error',
},
plugins: {
'@graphql-eslint': graphqlPlugin,
},
rules: {
/**
* Enforce that all GraphQL types referenced in your schema or queries are actually defined.
*
* @see {@link https://the-guild.dev/graphql/eslint/rules/known-type-names}
*/
'@graphql-eslint/known-type-names': 'error',
},
],
};
},
];
17 changes: 13 additions & 4 deletions packages/eslint-config-graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"description": "Alma's ESLint config for javascript applications that use GraphQL and Apollo client.",
"version": "3.0.6",
"author": "Tomas Litera <tomas.litera@almacareer.com>",
"type": "module",
"keywords": [
"config",
"eslint",
Expand All @@ -25,13 +26,21 @@
"publishConfig": {
"access": "public"
},
"main": "index.js",
"scripts": {
"test:unit": "node --test"
},
"exports": {
".": "./index.js"
},
"dependencies": {
"@graphql-eslint/eslint-plugin": "^3.19.1",
"@lmc-eu/eslint-config-base": "^3.1.3",
"@alma-oss/eslint-config-base": "^4.0.0-alpha.2",
"@graphql-eslint/eslint-plugin": "^4.0.0",
"graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
},
"peerDependencies": {
"eslint": "^8"
"eslint": "^9"
},
"devDependencies": {
"eslint": "9.39.4"
Comment thread
literat marked this conversation as resolved.
}
}
Loading