Skip to main content

Command Palette

Search for a command to run...

ESLint Custom Rules: Enforce Code Standards

Updated
4 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

1600w ESLint Custom Rules: Enforce Code Standards

Introduction

ESLint is a powerful static analysis tool for JavaScript that helps developers identify and fix problems in their code. While ESLint comes with a comprehensive set of built-in rules, custom rules allow teams to enforce organization-specific coding standards and best practices. This guide explores how to create, implement, and maintain ESLint custom rules effectively.

Why Custom ESLint Rules Matter

Standard ESLint rules cover general JavaScript best practices, but they don't address domain-specific requirements. Custom rules enable teams to:

  • Enforce architectural patterns specific to their projects
  • Prevent common mistakes in their codebase
  • Maintain consistency across large teams
  • Implement security policies tailored to their needs
  • Educate developers through automated feedback

Anatomy of an ESLint Rule

Every ESLint rule follows a consistent structure:

module.exports = {
  meta: {
    type: 'problem', // or 'suggestion' or 'layout'
    docs: {
      description: 'Rule description',
      category: 'Best Practices',
      recommended: true
    },
    fixable: 'code', // or 'whitespace' or null
    schema: [] // options schema
  },
  create(context) {
    return {
      // AST node selectors and handlers
    };
  }
};

The meta object provides metadata about the rule, while the create function returns an object with AST node handlers.

Creating Your First Custom Rule

Let's create a rule that prevents console statements in production code:

// rules/no-console-in-production.js
module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'Disallow console methods in production code',
      category: 'Best Practices',
      recommended: true
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          allowedMethods: {
            type: 'array',
            items: { type: 'string' }
          }
        }
      }
    ]
  },
  create(context) {
    const options = context.options[0] || {};
    const allowedMethods = options.allowedMethods || [];

    return {
      CallExpression(node) {
        if (
          node.callee.type === 'MemberExpression' &&
          node.callee.object.name === 'console' &&
          !allowedMethods.includes(node.callee.property.name)
        ) {
          context.report({
            node,
            message: `Unexpected console.${node.callee.property.name}() in production code`
          });
        }
      }
    };
  }
};

Advanced Rule: Enforcing Naming Conventions

Create a rule that enforces specific naming patterns for React components:

// rules/react-component-naming.js
module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'Enforce PascalCase naming for React components',
      category: 'Best Practices'
    },
    fixable: null
  },
  create(context) {
    return {
      FunctionDeclaration(node) {
        if (isReactComponent(node)) {
          if (!/^[A-Z]/.test(node.id.name)) {
            context.report({
              node,
              message: `React component '${node.id.name}' must start with uppercase letter`
            });
          }
        }
      },
      VariableDeclarator(node) {
        if (
          node.init &&
          isReactComponentExpression(node.init) &&
          !/^[A-Z]/.test(node.id.name)
        ) {
          context.report({
            node,
            message: `React component '${node.id.name}' must start with uppercase letter`
          });
        }
      }
    };
  }
};

function isReactComponent(node) {
  return node.params.length === 0 || 
         (node.params.length === 1 && node.params[0].name === 'props');
}

function isReactComponentExpression(node) {
  return node.type === 'ArrowFunctionExpression' || 
         node.type === 'FunctionExpression';
}

Implementing Custom Rules in Your Project

Create a plugin to bundle your custom rules:

// eslint-plugin-custom/index.js
module.exports = {
  rules: {
    'no-console-in-production': require('./rules/no-console-in-production'),
    'react-component-naming': require('./rules/react-component-naming'),
    'no-hardcoded-strings': require('./rules/no-hardcoded-strings')
  },
  configs: {
    recommended: {
      plugins: ['custom'],
      rules: {
        'custom/no-console-in-production': 'error',
        'custom/react-component-naming': 'warn',
        'custom/no-hardcoded-strings': 'off'
      }
    }
  }
};

Configure in .eslintrc.json:

{
  "plugins": ["custom"],
  "extends": ["plugin:custom/recommended"],
  "rules": {
    "custom/no-console-in-production": [
      "error",
      { "allowedMethods": ["error", "warn"] }
    ]
  }
}

Testing Custom Rules

Use ESLint's RuleTester for comprehensive testing:

// tests/no-console-in-production.test.js
const RuleTester = require('eslint').RuleTester;
const rule = require('../rules/no-console-in-production');

const ruleTester = new RuleTester();

ruleTester.run('no-console-in-production', rule, {
  valid: [
    'console.error("Error occurred")',
    'console.warn("Warning")',
    'logger.log("message")'
  ],
  invalid: [
    {
      code: 'console.log("debug")',
      errors: [{ message: 'Unexpected console.log() in production code' }]
    },
    {
      code: 'console.info("info")',
      errors: [{ message: 'Unexpected console.info() in production code' }]
    }
  ]
});

Best Practices for Custom Rules

1. Keep Rules Focused: Each rule should address a single concern. Avoid creating monolithic rules that handle multiple scenarios.

2. Provide Clear Messages: Error messages should be specific and actionable, helping developers understand what's wrong and how to fix it.

3. Support Configuration: Use the schema property to allow teams to customize rule behavior for different contexts.

4. Document Thoroughly: Include clear documentation explaining the rule's purpose, examples, and configuration options.

5. Optimize Performance: Use efficient AST selectors and avoid unnecessary traversals that could slow down linting.

6. Make Rules Fixable: When possible, implement automatic fixes using the fixer API to improve developer experience.

Common Pitfalls to Avoid

  • Over-engineering: Don't create rules for every possible code smell; focus on high-impact standards
  • Ignoring context: Consider different file types and environments when designing rules
  • Poor error messages: Vague messages frustrate developers; be specific and helpful
  • Lack of testing: Thoroughly test rules with both valid and invalid code samples
  • Breaking changes: When updating rules, consider backward compatibility

Conclusion

Custom ESLint rules are powerful tools for enforcing code standards and maintaining consistency across projects. By creating focused, well-documented rules tailored to your team's needs, you can automate code quality checks and reduce manual review overhead. Start with high-impact rules addressing your most common issues, then expand gradually as your linting infrastructure matures.