CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-eslint-plugin-relay

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

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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.

docs

index.md

tile.json