Back to list
narehart

custom-eslint-rules

by narehart

0🍴 0📅 Jan 8, 2026

SKILL.md


name: custom-eslint-rules description: Create custom ESLint rules to enforce architectural constraints

Custom ESLint Rules

Write custom ESLint rules in eslint-rules/ that enforce project-specific constraints.

Requirements

  • File naming: kebab-case .ts files (e.g., no-functions-in-constants.ts)
  • Default export: Rule.RuleModule object
  • Registration: Import and add to localPlugin in eslint.config.ts

Pattern

/**
 * ESLint rule: rule-name
 *
 * Brief description of what this rule enforces.
 */

import path from 'node:path';
import type { Rule } from 'eslint';

const rule: Rule.RuleModule = {
  meta: {
    type: 'problem', // or 'suggestion', 'layout'
    docs: {
      description: 'Human-readable description of the rule',
    },
    messages: {
      messageId: 'Error message with {{placeholder}} support.',
    },
    schema: [], // JSON Schema for rule options
  },
  create(context): Rule.RuleListener {
    const filename = context.filename;

    // Optional: Only check specific directories
    if (!filename.includes(path.join('src', 'target-dir'))) {
      return {};
    }

    return {
      // AST node visitors
      FunctionDeclaration(node): void {
        context.report({
          node,
          messageId: 'messageId',
          data: { placeholder: node.id?.name ?? 'unknown' },
        });
      },
    };
  },
};

export default rule;

Registration

  1. Import the rule in eslint.config.ts:
import myNewRule from './eslint-rules/my-new-rule.ts';
  1. Add to localPlugin.rules:
const localPlugin = {
  rules: {
    // ... existing rules
    'my-new-rule': myNewRule,
  },
};
  1. Enable in sharedRules or specific file config:
const sharedRules: Linter.RulesRecord = {
  // ... existing rules
  'local/my-new-rule': 'error',
};

Common AST Node Types

Node TypeWhen to Use
FunctionDeclarationNamed function declarations
ArrowFunctionExpressionArrow functions
FunctionExpressionAnonymous function expressions
ImportDeclarationImport statements
ExportNamedDeclarationNamed exports
VariableDeclarationconst/let/var declarations
TSInterfaceDeclarationTypeScript interfaces
TSTypeAliasDeclarationTypeScript type aliases
CallExpressionFunction calls
MemberExpressionProperty access (obj.prop)
IdentifierVariable/function names

Type-Safe AST Access

ESLint nodes are loosely typed. Use helper functions for safe access:

function isNonNullObject(value: unknown): value is Record<string, unknown> {
  return value !== null && value !== undefined && typeof value === 'object';
}

function getString(obj: Record<string, unknown>, key: string): string | null {
  if (!(key in obj)) return null;
  const value = obj[key];
  return typeof value === 'string' ? value : null;
}

function toRecord(value: unknown): Record<string, unknown> | null {
  return isNonNullObject(value) ? value : null;
}

Directory-Scoped Rules

Most project rules target specific directories:

// Only check src/constants/
if (!filename.includes(path.join('src', 'constants'))) {
  return {};
}

// Only check src/ecs/systems/
if (!filename.includes(path.join('src', 'ecs', 'systems'))) {
  return {};
}

Tracking Imports

To validate that files import from specific sources:

create(context): Rule.RuleListener {
  const importedNames = new Set<string>();

  return {
    ImportDeclaration(node): void {
      const source = node.source.value;
      if (typeof source === 'string' && source.includes('/queries/')) {
        for (const spec of node.specifiers) {
          if (spec.type === 'ImportSpecifier' && spec.local.name) {
            importedNames.add(spec.local.name);
          }
        }
      }
    },

    FunctionDeclaration(node): void {
      // Check if function uses any imported names
    },
  };
}

Testing Rules

Create a test file in the actual project directory (not /tmp - ESLint ignores /tmp):

# Create test file
echo "test content" > src/test-rule-file.ts

# Run ESLint on it
npx eslint src/test-rule-file.ts

# Clean up
rm src/test-rule-file.ts

Examples

Existing rules to reference:

  • no-functions-in-constants - Simple directory restriction
  • ecs-systems-use-world-or-queries - Complex import tracking + AST traversal
  • one-function-per-utils-file - Counting exports
  • function-interface-naming - TypeScript node inspection

Score

Total Score

50/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon