or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-eslint-plugin-relay

ESLint plugin providing linting rules for Relay GraphQL applications with syntax validation, naming conventions, and best practices enforcement.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/eslint-plugin-relay@2.0.x

To install, run

npx @tessl/cli install tessl/npm-eslint-plugin-relay@2.0.0

index.mddocs/

ESLint Plugin Relay

ESLint Plugin Relay is a comprehensive ESLint plugin designed specifically for Relay GraphQL applications. It provides 8 specialized linting rules that catch common problems in Relay code early in development, validating GraphQL syntax, enforcing naming conventions, detecting unused fields, and ensuring proper fragment colocation patterns.

Package Information

  • Package Name: eslint-plugin-relay
  • Package Type: npm
  • Language: JavaScript
  • Installation: npm install --save-dev eslint-plugin-relay

Core Imports

The plugin is imported and configured through ESLint configuration files:

// .eslintrc.js
module.exports = {
  plugins: ['relay'],
  rules: {
    'relay/graphql-syntax': 'error',
    'relay/graphql-naming': 'error',
    // ... other rules
  }
};

For configuration presets:

// .eslintrc.js
module.exports = {
  extends: ['plugin:relay/recommended']
};

Basic Usage

Individual Rule Configuration

// .eslintrc.js
module.exports = {
  plugins: ['relay'],
  rules: {
    'relay/graphql-syntax': 'error',
    'relay/graphql-naming': 'error',
    'relay/must-colocate-fragment-spreads': 'warn',
    'relay/no-future-added-value': 'warn',
    'relay/unused-fields': 'warn',
    'relay/function-required-argument': 'warn',
    'relay/hook-required-argument': 'warn'
  }
};

Configuration Presets

// Basic recommended rules
{
  "extends": ["plugin:relay/recommended"]
}

// TypeScript recommended rules
{
  "extends": ["plugin:relay/ts-recommended"]
}

// Strict configuration (all errors)  
{
  "extends": ["plugin:relay/strict"]
}

// TypeScript strict configuration
{
  "extends": ["plugin:relay/ts-strict"]
}

Architecture

ESLint Plugin Relay is structured around:

  • Rules System: 8 individual ESLint rules, each targeting specific Relay patterns
  • Configuration Presets: 4 pre-configured rule sets for different project needs
  • GraphQL AST Analysis: Utilities for parsing and analyzing GraphQL tagged template literals
  • Module Name Resolution: Relay-compatible module naming for fragment validation
  • Suppression Support: Inline suppression for specific rules within GraphQL tagged templates

Capabilities

GraphQL Syntax Validation

Validates syntax of graphql tagged template literals to catch parsing errors early.

// Rule: 'relay/graphql-syntax'
// Validates that graphql`` tagged templates contain valid GraphQL syntax
// Reports errors for:
// - Invalid GraphQL syntax
// - Template substitutions (${...} not allowed)
// - Multiple definitions in single template
// - Operations without names

Usage Example:

// Valid
const query = graphql`
  query MyComponentQuery {
    user(id: "123") {
      name
    }
  }
`;

// Invalid - syntax error will be caught
const badQuery = graphql`
  query {
    user(id: "123" {  // Missing closing parenthesis
      name
    }
  }
`;

GraphQL Naming Conventions

Enforces Relay's naming conventions for GraphQL fragments and queries.

// Rule: 'relay/graphql-naming'
// Validates naming patterns:
// - Queries must start with component name
// - Fragment names must follow <ComponentName>_<propName> pattern
// Provides auto-fixing capabilities

Usage Example:

// Valid naming
const MyComponentQuery = graphql`
  query MyComponentQuery {
    viewer { name }
  }
`;

const MyComponentFragment = graphql`
  fragment MyComponent_user on User {
    name
    email
  }
`;

// Invalid - will be flagged
const BadQuery = graphql`
  query SomeOtherName {  // Should start with 'MyComponent'
    viewer { name }
  }
`;

Generated TypeScript Types

Validates and manages TypeScript type imports for generated Relay types.

// Rule: 'relay/generated-typescript-types'
// Schema: {
//   type: 'object',
//   properties: {
//     fix: { type: 'boolean' },      // Auto-fix missing type imports
//     haste: { type: 'boolean' }     // Use Haste module resolution
//   },
//   additionalProperties: false
// }
// Manages TypeScript imports for Relay-generated types
// Can auto-fix missing type imports when fix: true
// Supports Haste module resolution when haste: true

Configuration Examples:

// Basic usage (no auto-fixing)
{
  "rules": {
    "relay/generated-typescript-types": "warn"
  }
}

// With auto-fixing enabled
{
  "rules": {
    "relay/generated-typescript-types": ["warn", { "fix": true }]
  }
}

// With Haste module resolution
{
  "rules": {
    "relay/generated-typescript-types": ["warn", { "haste": true, "fix": true }]
  }
}

Future Added Value Prevention

Prevents explicit handling of Relay's "%future added value" enum placeholder.

// Rule: 'relay/no-future-added-value'
// Prevents using '%future added value' literal
// Catches attempts to explicitly handle future enum variants

Usage Example:

// Invalid - will be flagged
if (status === '%future added value') {
  // This should not be explicitly handled
}

// Valid - let default case handle future values
switch (status) {
  case 'ACTIVE':
    return 'User is active';
  case 'INACTIVE': 
    return 'User is inactive';
  default:
    return 'Unknown status';
}

Unused Fields Detection

Ensures all GraphQL fields queried are actually used in the component.

// Rule: 'relay/unused-fields'
// Detects GraphQL fields that are queried but not used
// Supports suppression: # eslint-disable-next-line relay/unused-fields
// Ignores pageInfo fields and __typename by default

Usage Example:

// Invalid - 'email' field is queried but not used
const fragment = graphql`
  fragment MyComponent_user on User {
    name
    email  // This will be flagged as unused
  }
`;

function MyComponent({ user }) {
  return <div>{user.name}</div>;  // Only 'name' is used
}

Fragment Spread Colocation

Ensures fragment spreads are colocated with components that use them.

// Rule: 'relay/must-colocate-fragment-spreads'  
// Validates that fragment spreads are imported from defining modules
// Prevents anti-pattern of fetching data for child components
// Supports suppression: # eslint-disable-next-line relay/must-colocate-fragment-spreads

Usage Example:

// Valid - fragment spread colocated with import
import ChildComponent from './ChildComponent';

const fragment = graphql`
  fragment ParentComponent_data on Data {
    ...ChildComponent_data  // Valid if ChildComponent exports this fragment
  }
`;

// Invalid - using fragment without importing defining module
const badFragment = graphql`
  fragment ParentComponent_data on Data {
    ...SomeOtherComponent_data  // No import for SomeOtherComponent
  }
`;

Function Required Arguments

Ensures readInlineData function always receives explicit arguments.

// Rule: 'relay/function-required-argument'
// Validates that readInlineData() always has 2 arguments
// Prevents runtime errors from missing fragment references

Usage Example:

// Valid
const data = readInlineData(fragment, props.data);

// Invalid - missing second argument
const data = readInlineData(fragment);  // Will be flagged

Hook Required Arguments

Ensures Relay hooks always receive explicit arguments.

// Rule: 'relay/hook-required-argument'
// Validates that Relay hooks always have required arguments:
// - useFragment(fragment, fragmentRef)
// - usePaginationFragment(fragment, fragmentRef) 
// - useBlockingPaginationFragment(fragment, fragmentRef)
// - useLegacyPaginationFragment(fragment, fragmentRef)
// - useRefetchableFragment(fragment, fragmentRef)

Usage Example:

// Valid
const data = useFragment(fragment, props.user);
const { data, loadNext } = usePaginationFragment(fragment, props.connection);

// Invalid - missing fragment reference arguments
const data = useFragment(fragment);  // Will be flagged
const { data } = usePaginationFragment(fragment);  // Will be flagged

Configuration Presets

Recommended Configuration

Standard rule set for general Relay development.

const recommended = {
  rules: {
    'relay/graphql-syntax': 'error',
    'relay/graphql-naming': 'error', 
    'relay/no-future-added-value': 'warn',
    'relay/unused-fields': 'warn',
    'relay/must-colocate-fragment-spreads': 'warn',
    'relay/function-required-argument': 'warn',
    'relay/hook-required-argument': 'warn'
  }
};

TypeScript Recommended Configuration

Includes TypeScript-specific rules for Relay projects.

const tsRecommended = {
  rules: {
    'relay/graphql-syntax': 'error',
    'relay/graphql-naming': 'error',
    'relay/generated-typescript-types': 'warn',
    'relay/no-future-added-value': 'warn', 
    'relay/unused-fields': 'warn',
    'relay/must-colocate-fragment-spreads': 'warn',
    'relay/function-required-argument': 'warn',
    'relay/hook-required-argument': 'warn'
  }
};

Strict Configuration

All rules set to error level for maximum enforcement.

const strict = {
  rules: {
    'relay/graphql-syntax': 'error',
    'relay/graphql-naming': 'error',
    'relay/no-future-added-value': 'error',
    'relay/unused-fields': 'error', 
    'relay/must-colocate-fragment-spreads': 'error',
    'relay/function-required-argument': 'error',
    'relay/hook-required-argument': 'error'
  }
};

TypeScript Strict Configuration

TypeScript strict rules with all rules as errors.

const tsStrict = {
  rules: {
    'relay/graphql-syntax': 'error',
    'relay/graphql-naming': 'error',
    'relay/generated-typescript-types': 'error',
    'relay/no-future-added-value': 'error',
    'relay/unused-fields': 'error',
    'relay/must-colocate-fragment-spreads': 'error', 
    'relay/function-required-argument': 'error',
    'relay/hook-required-argument': 'error'
  }
};

Plugin Structure

// Main plugin export structure
interface ESLintPluginRelay {
  rules: {
    'graphql-syntax': ESLintRule;
    'graphql-naming': ESLintRule;
    'generated-typescript-types': ESLintRule;
    'no-future-added-value': ESLintRule;
    'unused-fields': ESLintRule;
    'must-colocate-fragment-spreads': ESLintRule;
    'function-required-argument': ESLintRule;
    'hook-required-argument': ESLintRule;
  };
  configs: {
    recommended: ESLintConfig;
    'ts-recommended': ESLintConfig;
    strict: ESLintConfig;
    'ts-strict': ESLintConfig;
  };
}

interface ESLintRule {
  meta: {
    docs?: { description: string };
    fixable?: 'code';
    schema?: any[];
  };
  create: (context: ESLintContext) => ESLintVisitor;
}

interface ESLintConfig {
  rules: Record<string, 'error' | 'warn' | 'off'>;
}

Rule Suppression

Some rules support inline suppression within GraphQL tagged templates:

// Supported for unused-fields and must-colocate-fragment-spreads
const fragment = graphql`
  fragment MyComponent_data on Data {
    # eslint-disable-next-line relay/unused-fields
    unusedField
    # eslint-disable-next-line relay/must-colocate-fragment-spreads  
    ...NonColocatedFragment_data
  }
`;

Note: Only eslint-disable-next-line format works within GraphQL templates. eslint-disable-line is not supported due to GraphQL AST limitations.