@mherod/eslint-plugin-custom
v1.4.0
Published
Modular ESLint plugin with specialized rules for TypeScript, React/Next.js, Vue.js, security, and general code quality
Maintainers
Readme
@mherod/eslint-plugin-custom
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.
Installation
npm install --save-dev @mherod/eslint-plugin-custom
# or
yarn add -D @mherod/eslint-plugin-custom
# or
pnpm add -D @mherod/eslint-plugin-customRequirements
- 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)
Available Plugins
This package provides five specialised plugins with 76 rules total:
@mherod/typescript(5 rules) - TypeScript-specific rules for better type safety and code patterns@mherod/react(44 rules) - React and Next.js rules for component patterns and SSR/CSR separation@mherod/vue(1 rule) - Vue.js rules for composition API best practices and reactivity patterns@mherod/general(14 rules) - General code organisation rules (imports, naming, etc.)@mherod/security(12 rules) - Security-focused rules to prevent common vulnerabilities
Quick Start
Modern Flat Config (ESLint 9+)
// 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,
},
},
];✅ ESLint 9 Compatibility
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.
Plugin Configuration Guides
TypeScript Plugin
Focuses on TypeScript best practices, API patterns, and code documentation.
Flat Config (ESLint 9+)
// 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,
},
},
];Legacy Config (.eslintrc)
{
"plugins": ["@mherod/typescript"],
"rules": {
"@mherod/typescript/enforce-typescript-patterns": "warn",
"@mherod/typescript/enforce-zod-schema-naming": "warn",
"@mherod/typescript/no-empty-function-implementations": "warn"
}
}Available Rules
enforce-api-patterns- Enforces consistent API endpoint patternsenforce-documentation- Requires JSDoc comments for public APIsenforce-typescript-patterns- General TypeScript best practicesenforce-zod-schema-naming- Consistent naming for Zod schemasno-empty-function-implementations- Prevents empty function bodies
React/Next.js Plugin
Specialised rules for React components and Next.js applications, including server/client component separation.
Flat Config (ESLint 9+)
// 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,
},
},
];Legacy Config (.eslintrc)
{
"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"
}
}Available Rules (44 rules)
Server/Client Separation:
enforce-admin-separation- Isolates admin-only functionalityenforce-server-client-separation- Prevents server code in client componentsenforce-use-server-vs-server-only- Enforces correct use of "use server" vs server-only importsno-async-server-component-in-client- Prevents async server components imported in client filesno-conflicting-directives- Prevents conflicting "use client" and "use server" directivesno-context-provider-in-server-component- Prevents Context providers in server componentsno-event-handlers-to-client-props- Prevents passing event handlers to client componentsno-internal-fetch-in-server-component- Warns against internal fetch calls in server componentsno-react-hooks-in-server-component- Prevents React hooks in server componentsno-use-client-in-layout- Prevents "use client" directive in layout filesno-use-client-in-page- Prevents "use client" directive in page filesno-use-params-in-client-component- Prevents use of params in client componentsno-use-state-in-async-component- Prevents useState in server componentsprevent-environment-poisoning- Enforces proper server-only/client-only importsrequire-use-client-for-client-named-files- Requires "use client" in files with client naming conventionsrequire-use-client-for-react-hooks- Requires "use client" in files using client-only React hooks
Directives and Caching:
no-parenthesized-use-cache- Prevents parenthesized "use cache" expressionsno-reexports-in-use-server- Prevents re-exports in "use server" filesno-request-access-in-use-cache- Prevents request-time data access in "use cache" functionsrequire-directive-first- Requires directives to be the first statement in a file
React Patterns:
enforce-component-patterns- Enforces consistent component patternsno-jsx-logical-and- Prevents&&in JSX (use ternary instead)prefer-async-page-component- Prefers async page componentsprefer-await-params-in-page- Requires await for params in page componentsprefer-react-destructured-imports- Use destructured React importssuggest-server-component-pages- Suggests server components for pages
Next.js Navigation:
prefer-link-over-router-push- Use Link component over router.pushprefer-next-navigation- Use Next.js navigation over window.locationprefer-search-params-over-state- Use URL search params over component state for shareable UI state
Data Fetching:
no-sequential-data-fetching- Warns against sequential data fetchingno-waterfall-chains- Prevents waterfall request chainsprefer-cache-api- Use Next.js Cache APIprefer-reusable-swr-hooks- Create reusable SWR hooksprefer-ui-promise-handling- Handle promises properly in UIprefer-use-hook-for-promise-props- Use hooks for promise propsprefer-use-swr-over-fetch- Use SWR for data fetchinguse-after-for-non-blocking- Useafter()for non-blocking operations
Re-render and Bundle Optimization:
no-lazy-state-init- FlagsuseState(fn())— should beuseState(() => fn())(auto-fixable)no-usememo-for-primitives- FlagsuseMemoreturning a provably-primitive valueprefer-dynamic-import-for-heavy-libs- Suggestsnext/dynamicfor heavy packagesprefer-start-transition-for-server-actions- UsestartTransitionfor server action calls
Serialization and Props:
no-non-serializable-props- Prevents non-serializable props across the server/client boundary
Code Quality:
no-dynamic-tailwind-classes- Prevents dynamic Tailwind class generation (auto-fixable)no-unstable-math-random- Prevents Math.random() in React components
General Plugin
Code organisation and quality rules applicable to any JavaScript/TypeScript project.
Flat Config (ESLint 9+)
// 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,
},
},
];Legacy Config (.eslintrc)
{
"plugins": ["@mherod/general"],
"rules": {
"@mherod/general/enforce-import-order": "warn",
"@mherod/general/enforce-file-naming": "warn"
}
}Available Rules (14 rules)
enforce-file-naming- Enforces consistent file naming conventionsenforce-import-order- Enforces consistent import ordering (external → internal → relative)no-debug-comments- Flags TODO, FIXME, HACK, and other debug commentsno-deprecated-declarations- Warns on usage of deprecated APIs and patternsno-import-type-queries- Preventsimport typequeries (strict only)no-long-relative-imports- Warns against deeply nested relative importsprefer-date-fns- Prefer date-fns library over native Date methodsprefer-date-fns-over-date-operations- Use date-fns for date arithmetic and formattingprefer-direct-imports- Prefer direct module imports over barrel file re-exportsprefer-lodash-es-imports- Prefer lodash-es imports over lodash for tree-shakingprefer-lodash-uniq-over-set- Use lodash uniq over Set for array deduplicationprefer-ufo-with-query- Use ufo library'swithQueryfor URL query manipulationprefer-zod-default-with-catch- Use.default().catch()pattern for Zod defaultsprefer-zod-url- Usez.string().url()for URL validation
Security Plugin
Security-focused rules to prevent common vulnerabilities and enforce secure coding patterns.
Flat Config (ESLint 9+)
// 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,
},
},
];Legacy Config (.eslintrc)
{
"plugins": ["@mherod/security"],
"rules": {
"@mherod/security/enforce-security-patterns": "error",
"@mherod/security/no-hardcoded-secrets": "error",
"@mherod/security/no-sql-injection": "error",
"@mherod/security/no-unsafe-eval": "error"
}
}Available Rules (12 rules)
enforce-security-patterns- Comprehensive security pattern enforcementno-hardcoded-secrets- Prevents hardcoded API keys, tokens, and passwordsno-log-secrets- Prevents logging sensitive datano-sql-injection- Detects SQL injection vulnerabilitiesno-unsafe-eval- Prevents use ofeval()andFunction()constructorno-unsafe-innerHTML- Prevents use ofdangerouslySetInnerHTMLandinnerHTMLno-unsafe-redirect- Prevents open redirect vulnerabilitiesno-unsafe-template-literals- Warns against unsafe template literal interpolationno-unstable-math-random- Prevents Math.random() for security-sensitive operationsno-weak-crypto- Prevents use of weak cryptographic algorithmsrequire-auth-validation- Requires authentication validation in protected routesrequire-rate-limiting- Requires rate limiting on API endpoints
Vue.js Plugin
Modern Vue 3 composition API best practices and reactivity pattern enforcement.
Flat Config (ESLint 9+)
// 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,
},
},
];Legacy Config (.eslintrc)
{
"plugins": ["@mherod/vue"],
"rules": {
"@mherod/vue/prefer-to-value": "warn"
}
}Available Rules
prefer-to-value- PrefertoValue()over.valueorunref()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
Complete Setup Examples
Next.js App Router Project
// 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,
},
},
];TypeScript Node.js Project
// 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,
},
},
];Vue 3 Application
// 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,
},
},
];Using All Rules (Backward Compatibility)
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',
},
},
];Configuration Presets
Each plugin provides two configuration presets:
recommended- Balanced set of rules for most projectsstrict- Stricter rules for maximum code quality
// Use recommended preset
...reactPlugin.configs.recommended.rules
// Use strict preset
...reactPlugin.configs.strict.rulesMigration Guide
From Legacy to Flat Config
Before (.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',
},
},
];Using Category-Specific Plugins
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';TypeScript Support
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
Available Scripts
# 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 publishingPackage Exports
The 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';Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Commit Message Format
This project uses Conventional Commits:
feat:New featuresfix:Bug fixesdocs:Documentation changestest:Test changesrefactor:Code refactoringchore:Maintenance tasks
License
MIT
Author
Matthew Herod
