A modular ESLint plugin providing specialized rule sets for TypeScript, React/Next.js, and general code quality. Each category can be used independently or combined based on your project needs.
npm install --save-dev @mherod/eslint-plugin-custom
# or
yarn add -D @mherod/eslint-plugin-custom
# or
pnpm add -D @mherod/eslint-plugin-custom- Node.js >= 18.0.0
- ESLint ^8.0.0 or ^9.0.0
- TypeScript ^5.0.0 (for TypeScript projects)
- @typescript-eslint/parser ^8.0.0 (for TypeScript projects)
This package provides five specialised plugins:
- @mherod/typescript- TypeScript-specific rules for better type safety and code patterns
- @mherod/react- React and Next.js rules for component patterns and SSR/CSR separation
- @mherod/vue- Vue.js rules for composition API best practices and reactivity patterns
- @mherod/general- General code organisation rules (imports, naming, etc.)
- @mherod/security- Security-focused rules to prevent common vulnerabilities
// eslint.config.js
import typescript from '@mherod/eslint-plugin-custom/typescript';
import react from '@mherod/eslint-plugin-custom/react';
import vue from '@mherod/eslint-plugin-custom/vue';
import general from '@mherod/eslint-plugin-custom/general';
import security from '@mherod/eslint-plugin-custom/security';
export default [
  // Use all plugins with recommended settings
  {
    files: ['**/*.ts', '**/*.tsx', '**/*.vue'],
    plugins: {
      '@mherod/typescript': typescript,
      '@mherod/react': react,
      '@mherod/vue': vue,
      '@mherod/general': general,
      '@mherod/security': security,
    },
    rules: {
      ...typescript.configs.recommended.rules,
      ...react.configs.recommended.rules,
      ...vue.configs.recommended.rules,
      ...general.configs.recommended.rules,
      ...security.configs.recommended.rules,
    },
  },
];This plugin is fully compatible with ESLint 9's flat config format. No workarounds or special configuration required!
The plugin exports the correct structure for both ESM and CommonJS environments:
// β
 ESM imports work correctly
import reactPlugin from '@mherod/eslint-plugin-custom/react';
export default [
  {
    plugins: {
      '@mherod/react': reactPlugin, // Direct usage - no .default needed
    },
    rules: {
      '@mherod/react/no-dynamic-tailwind-classes': 'warn',
    },
  },
];Fixed Issues (v1.2.0+):
- β Dynamic require of "eslint/use-at-your-own-risk" is not supported
- β Could not find "rule-name" in plugin "@mherod/react"
- β Requiring .defaultaccess to plugin object
All rules are now properly exported and accessible without workarounds.
Focuses on TypeScript best practices, API patterns, and code documentation.
// eslint.config.js
import typescriptPlugin from '@mherod/eslint-plugin-custom/typescript';
export default [
  {
    files: ['**/*.ts', '**/*.tsx'],
    plugins: {
      '@mherod/typescript': typescriptPlugin,
    },
    rules: {
      // Recommended rules
      '@mherod/typescript/enforce-typescript-patterns': 'warn',
      '@mherod/typescript/enforce-zod-schema-naming': 'warn',
      '@mherod/typescript/no-empty-function-implementations': 'warn',
      // Or use preset
      ...typescriptPlugin.configs.recommended.rules,
      // Or strict mode
      ...typescriptPlugin.configs.strict.rules,
    },
  },
];{
  "plugins": ["@mherod/typescript"],
  "rules": {
    "@mherod/typescript/enforce-typescript-patterns": "warn",
    "@mherod/typescript/enforce-zod-schema-naming": "warn",
    "@mherod/typescript/no-empty-function-implementations": "warn"
  }
}- enforce-api-patterns- Enforces consistent API endpoint patterns
- enforce-documentation- Requires JSDoc comments for public APIs
- enforce-typescript-patterns- General TypeScript best practices
- enforce-zod-schema-naming- Consistent naming for Zod schemas
- no-empty-function-implementations- Prevents empty function bodies
- prefer-lodash-uniq-over-set- Use lodash for array deduplication
Specialised rules for React components and Next.js applications, including server/client component separation.
// eslint.config.js
import reactPlugin from '@mherod/eslint-plugin-custom/react';
export default [
  {
    files: ['**/*.tsx', '**/*.jsx'],
    plugins: {
      '@mherod/react': reactPlugin,
    },
    rules: {
      // Critical Next.js App Router rules
      '@mherod/react/no-use-state-in-async-component': 'error',
      '@mherod/react/no-event-handlers-to-client-props': 'error',
      '@mherod/react/prevent-environment-poisoning': 'error',
      '@mherod/react/enforce-server-client-separation': 'error',
      // Or use preset
      ...reactPlugin.configs.recommended.rules,
    },
  },
];{
  "plugins": ["@mherod/react"],
  "rules": {
    "@mherod/react/no-use-state-in-async-component": "error",
    "@mherod/react/no-event-handlers-to-client-props": "error",
    "@mherod/react/prevent-environment-poisoning": "error"
  }
}Server/Client Separation:
- no-use-state-in-async-component- Prevents useState in server components
- no-event-handlers-to-client-props- Prevents passing event handlers to client components
- prevent-environment-poisoning- Enforces proper server-only/client-only imports
- enforce-server-client-separation- Prevents server code in client components
- enforce-admin-separation- Isolates admin-only functionality
React Patterns:
- enforce-component-patterns- Enforces consistent component patterns
- prefer-react-destructured-imports- Use destructured React imports
- suggest-server-component-pages- Suggests server components for pages
Next.js Navigation:
- prefer-next-navigation- Use Next.js navigation over window.location
- prefer-link-over-router-push- Use Link component over router.push
Data Fetching:
- prefer-use-swr-over-fetch- Use SWR for data fetching
- prefer-reusable-swr-hooks- Create reusable SWR hooks
- prefer-ui-promise-handling- Handle promises properly in UI
Code Quality:
- no-unstable-math-random- Prevents Math.random() in React components
- no-dynamic-tailwind-classes- Prevents dynamic Tailwind class generation (auto-fixable)- Detects template literals, concatenation, ternary operators in className
- Configuration: allowedDynamicClasses: string[],allowConditionalClasses: boolean
- Auto-fixes to use clsx/cn utilities when possible
 
Code organisation and quality rules applicable to any JavaScript/TypeScript project.
// eslint.config.js
import generalPlugin from '@mherod/eslint-plugin-custom/general';
export default [
  {
    files: ['**/*.{js,jsx,ts,tsx}'],
    plugins: {
      '@mherod/general': generalPlugin,
    },
    rules: {
      '@mherod/general/enforce-import-order': 'warn',
      '@mherod/general/enforce-file-naming': 'warn',
      '@mherod/general/prefer-date-fns-over-date-operations': 'warn',
      // Or use preset
      ...generalPlugin.configs.recommended.rules,
    },
  },
];{
  "plugins": ["@mherod/general"],
  "rules": {
    "@mherod/general/enforce-import-order": "warn",
    "@mherod/general/enforce-file-naming": "warn"
  }
}- enforce-import-order- Enforces consistent import ordering (external β internal β relative)
- enforce-file-naming- Enforces consistent file naming conventions
- prefer-date-fns-over-date-operations- Use date-fns for date operations
Security-focused rules to prevent common vulnerabilities and enforce secure coding patterns.
// eslint.config.js
import securityPlugin from '@mherod/eslint-plugin-custom/security';
export default [
  {
    files: ['**/*.{js,jsx,ts,tsx}'],
    plugins: {
      '@mherod/security': securityPlugin,
    },
    rules: {
      '@mherod/security/enforce-security-patterns': 'error',
      // Or use preset
      ...securityPlugin.configs.recommended.rules,
    },
  },
];{
  "plugins": ["@mherod/security"],
  "rules": {
    "@mherod/security/enforce-security-patterns": "error"
  }
}- enforce-security-patterns- Comprehensive security pattern enforcement (checks for eval, innerHTML, SQL injection patterns, etc.)
Modern Vue 3 composition API best practices and reactivity pattern enforcement.
// eslint.config.js
import vuePlugin from '@mherod/eslint-plugin-custom/vue';
export default [
  {
    files: ['**/*.vue', '**/*.ts', '**/*.js'],
    plugins: {
      '@mherod/vue': vuePlugin,
    },
    rules: {
      // Reactivity best practices
      '@mherod/vue/prefer-to-value': 'warn',
      // Or use preset
      ...vuePlugin.configs.recommended.rules,
    },
  },
];{
  "plugins": ["@mherod/vue"],
  "rules": {
    "@mherod/vue/prefer-to-value": "warn"
  }
}- prefer-to-value- Prefer- toValue()over- .valueor- unref()for unwrapping refs- Detects ref.valueaccess patterns
- Detects unref(ref)calls
- Detects isRef(x) ? x.value : xpatterns
- Auto-fixes to use toValue()from Vue 3.3+
- Optional auto-import capability
 
- Detects 
// eslint.config.js
import js from '@eslint/js';
import typescript from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import reactPlugin from '@mherod/eslint-plugin-custom/react';
import generalPlugin from '@mherod/eslint-plugin-custom/general';
import securityPlugin from '@mherod/eslint-plugin-custom/security';
export default [
  js.configs.recommended,
  {
    files: ['**/*.{ts,tsx}'],
    languageOptions: {
      parser: tsParser,
      parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module',
        ecmaFeatures: {
          jsx: true,
        },
      },
    },
    plugins: {
      '@typescript-eslint': typescript,
      '@mherod/react': reactPlugin,
      '@mherod/general': generalPlugin,
      '@mherod/security': securityPlugin,
    },
    rules: {
      // TypeScript ESLint rules
      '@typescript-eslint/no-unused-vars': 'error',
      // React/Next.js rules
      ...reactPlugin.configs.strict.rules,
      // General rules
      ...generalPlugin.configs.recommended.rules,
      // Security rules
      ...securityPlugin.configs.strict.rules,
    },
  },
];// eslint.config.js
import js from '@eslint/js';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import typescriptPlugin from '@mherod/eslint-plugin-custom/typescript';
import generalPlugin from '@mherod/eslint-plugin-custom/general';
import securityPlugin from '@mherod/eslint-plugin-custom/security';
export default [
  js.configs.recommended,
  {
    files: ['**/*.ts'],
    languageOptions: {
      parser: tsParser,
      parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module',
      },
    },
    plugins: {
      '@typescript-eslint': tsPlugin,
      '@mherod/typescript': typescriptPlugin,
      '@mherod/general': generalPlugin,
      '@mherod/security': securityPlugin,
    },
    rules: {
      ...typescriptPlugin.configs.strict.rules,
      ...generalPlugin.configs.strict.rules,
      ...securityPlugin.configs.strict.rules,
    },
  },
];// eslint.config.js
import js from '@eslint/js';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import vueParser from 'vue-eslint-parser';
import vuePlugin from '@mherod/eslint-plugin-custom/vue';
import typescriptPlugin from '@mherod/eslint-plugin-custom/typescript';
import generalPlugin from '@mherod/eslint-plugin-custom/general';
export default [
  js.configs.recommended,
  {
    files: ['**/*.vue'],
    languageOptions: {
      parser: vueParser,
      parserOptions: {
        parser: tsParser,
        ecmaVersion: 'latest',
        sourceType: 'module',
      },
    },
    plugins: {
      '@mherod/vue': vuePlugin,
      '@mherod/typescript': typescriptPlugin,
      '@mherod/general': generalPlugin,
    },
    rules: {
      ...vuePlugin.configs.recommended.rules,
      ...typescriptPlugin.configs.recommended.rules,
      ...generalPlugin.configs.recommended.rules,
    },
  },
  {
    files: ['**/*.ts', '**/*.js'],
    languageOptions: {
      parser: tsParser,
      parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module',
      },
    },
    plugins: {
      '@typescript-eslint': tsPlugin,
      '@mherod/vue': vuePlugin,
      '@mherod/typescript': typescriptPlugin,
      '@mherod/general': generalPlugin,
    },
    rules: {
      ...vuePlugin.configs.recommended.rules,
      ...typescriptPlugin.configs.recommended.rules,
      ...generalPlugin.configs.recommended.rules,
    },
  },
];If you want to use all rules with the original namespace:
// eslint.config.js
import customPlugin from '@mherod/eslint-plugin-custom';
export default [
  {
    files: ['**/*.{ts,tsx}'],
    plugins: {
      '@mherod/custom': customPlugin,
    },
    rules: {
      ...customPlugin.configs.recommended.rules,
      // Or individual rules
      '@mherod/custom/no-use-state-in-async-component': 'error',
    },
  },
];Each plugin provides two configuration presets:
- recommended- Balanced set of rules for most projects
- strict- Stricter rules for maximum code quality
// Use recommended preset
...reactPlugin.configs.recommended.rules
// Use strict preset
...reactPlugin.configs.strict.rulesBefore (.eslintrc.json):
{
  "plugins": ["@mherod/custom"],
  "rules": {
    "@mherod/custom/no-use-state-in-async-component": "error"
  }
}After (eslint.config.js):
import customPlugin from '@mherod/eslint-plugin-custom';
export default [
  {
    plugins: {
      '@mherod/custom': customPlugin,
    },
    rules: {
      '@mherod/custom/no-use-state-in-async-component': 'error',
    },
  },
];Instead of using all rules, import only what you need:
// Before - all rules
import customPlugin from '@mherod/eslint-plugin-custom';
// After - specific categories
import reactPlugin from '@mherod/eslint-plugin-custom/react';
import vuePlugin from '@mherod/eslint-plugin-custom/vue';
import typescriptPlugin from '@mherod/eslint-plugin-custom/typescript';This plugin is written in TypeScript and provides full type definitions. When using TypeScript, you'll get autocomplete for rule names and configurations.
import type { Linter } from 'eslint';
import reactPlugin from '@mherod/eslint-plugin-custom/react';
const config: Linter.FlatConfig[] = [
  {
    plugins: {
      '@mherod/react': reactPlugin,
    },
    rules: {
      // Full TypeScript support with autocomplete
      '@mherod/react/no-use-state-in-async-component': 'error',
    },
  },
];
export default config;# Development
npm run dev          # Watch mode for development
npm run build        # Build all plugins
npm run clean        # Clean build output
# Testing
npm test             # Run all tests
npm run test:watch   # Run tests in watch mode
# Code Quality
npm run lint         # Check for linting issues
npm run lint:fix     # Auto-fix linting issues
npm run typecheck    # TypeScript type checking
# Git Hooks
npm run commitlint   # Check commit messages (Conventional Commits)
npm run prepublishOnly  # Full build and test before publishingThe package provides multiple entry points:
// Main plugin (all rules)
import customPlugin from '@mherod/eslint-plugin-custom';
// Category-specific plugins
import typescriptPlugin from '@mherod/eslint-plugin-custom/typescript';
import reactPlugin from '@mherod/eslint-plugin-custom/react';
import vuePlugin from '@mherod/eslint-plugin-custom/vue';
import generalPlugin from '@mherod/eslint-plugin-custom/general';
import securityPlugin from '@mherod/eslint-plugin-custom/security';Contributions are welcome! Please feel free to submit a Pull Request.
This project uses Conventional Commits:
- feat:New features
- fix:Bug fixes
- docs:Documentation changes
- test:Test changes
- refactor:Code refactoring
- chore:Maintenance tasks
MIT
Matthew Herod