# ESLint Custom Rules: Enforce Code Standards

# 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:

```javascript
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:

```javascript
// 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:

```javascript
// 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:

```javascript
// 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`:

```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:

```javascript
// 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.
