eslint-plugin-strict-eg-rulez
v2.1.3
Published
Custom ESLint rules for frontend
Downloads
789
Maintainers
Readme
eslint-plugin-strict-eg-rulez
A collection of custom ESLint rules for React + TypeScript projects. Designed to enforce consistency, readability, and maintainability across frontend codebases.
Table of Contents
Installation
npm install eslint-plugin-strict-eg-rulez --save-devPeer dependency: ESLint
>=8.0.0is required.
Usage
.eslintrc.cjs or .eslintrc.json (Legacy Config):
Using the recommended config:
{
"plugins": ["strict-eg-rulez"],
"extends": [
"plugin:strict-eg-rulez/recommended"
]
}Or configure rules individually:
{
"plugins": ["strict-eg-rulez"],
"rules": {
"strict-eg-rulez/api-type-suffix": "error",
"strict-eg-rulez/boolean-prop-naming": "warn"
}
}Rules
api-type-suffix
Enforces that TypeScript interface and type declarations inside src/apis/ end with an allowed suffix.
- Default suffixes:
Model,Response,Request - Consecutive suffixes are not allowed (e.g.
UserRequestModel❌)
// ✅ Valid
interface UserModel { ... }
type LoginResponse = { ... }
// ❌ Invalid
interface User { ... } // Missing suffix
interface UserRequestModel { ... } // Consecutive suffixesOptions:
["error", { "suffixes": ["Model", "Response", "Request"] }]| Option | Type | Default | Description |
|------------|------------|--------------------------------------|--------------------------|
| suffixes | string[] | ["Model", "Response", "Request"] | Allowed suffixes list |
boolean-prop-naming
Enforces that boolean props and parameters in components/, hooks/, and utils/ folders start with a boolean prefix.
- Default prefixes:
is,has,can,should,will,did,show,hide - Applies to TypeScript-typed booleans:
boolean,boolean | null,boolean | undefined,true | false
// ✅ Valid
interface ButtonProps {
isDisabled: boolean;
hasError?: boolean;
}
// ❌ Invalid
interface ButtonProps {
disabled: boolean; // Missing prefix
}Options:
["error", { "prefixes": ["is", "has", "can"] }]| Option | Type | Default | Description |
|------------|------------|---------------------------------------------------------------------|-----------------------|
| prefixes | string[] | ["is", "has", "can", "should", "will", "did", "show", "hide"] | Allowed prefixes list |
component-callback-naming
Enforces that callback function props defined in React component prop types start with on.
- Also checks for past-tense event names (e.g.
onClicked→ useonClick) whenallowPastTenseisfalse - Supports
blacklistandwhitelistfor fine-grained control
// ✅ Valid
interface CardProps {
onClick: () => void;
onSubmit: (data: FormData) => void;
}
// ❌ Invalid
interface CardProps {
click: () => void; // Missing 'on' prefix
onClicked: () => void; // Past tense (when allowPastTense: false)
}Options:
["error", {
"allowPastTense": false,
"blacklist": ["Clicked"],
"whitelist": ["onRefetch"]
}]| Option | Type | Default | Description |
|-----------------|------------|---------|----------------------------------------------------|
| allowPastTense| boolean | false | Allow past tense event suffixes (e.g. ed/d) |
| blacklist | string[] | [] | Disallowed suffixes (e.g. Clicked) |
| whitelist | string[] | [] | Names always considered valid (bypass all checks) |
functions-naming
Enforces that functions are named according to their return type. React components, hooks, and event handlers are ignored.
| Return Type | Required Prefix |
|-------------------------|-------------------------------------|
| JSX / ReactNode | render |
| boolean | is, has, will, can |
| string / number / object / array | get, calculate, determine |
// ✅ Valid
const renderUserCard = () => <Card />;
const isLoggedIn = (): boolean => true;
const getUserName = (): string => 'John';
// ❌ Invalid
const userCard = () => <Card />; // Missing 'render'
const loggedIn = (): boolean => true; // Missing boolean prefix
const userName = (): string => 'John'; // Missing value prefixThis rule accepts no configuration options.
jsx-event-handler-naming
Enforces that locally defined event handlers passed to JSX event props (e.g. onClick) start with handle. In strict mode, the handler name must also end with the event name.
// ✅ Valid
const handleClick = () => {};
<Button onClick={handleClick} />
// ✅ Valid (strict: true)
const handleChange = () => {};
<Input onChange={handleChange} />
// ❌ Invalid — missing 'handle' prefix
const clickAction = () => {};
<Button onClick={clickAction} />
// ❌ Invalid — strict mode: must end with 'Change'
const handleSubmit = () => {};
<Input onChange={handleSubmit} />Options:
["error", { "strict": true }]| Option | Type | Default | Description |
|----------|-----------|---------|----------------------------------------------------------|
| strict | boolean | true | Handler name must end with the corresponding event name |
no-test-attrs
Disallow test-only attributes (e.g. data-testid, data-cy) in non-test source files. These attributes should only appear in test files or test mocks.
- Default forbidden attributes:
data-testid,data-test,data-test-id,data-cy,data-e2e - Automatically ignores files ending with
.test.*,.spec.*, or inside__tests__/
// ✅ Valid (in any file)
<div className="card" id="main" />
// ✅ Valid (inside a test file like Button.test.tsx)
<button data-testid="submit-btn" type="submit" />
// ❌ Invalid (inside a normal component like Button.tsx)
<button data-testid="submit-btn" type="submit" />
<input data-cy="email-input" />Options:
["error", { "attrs": ["data-testid", "data-cy"] }]| Option | Type | Default | Description |
|---------|------------|---------------------------------------------------------------------|-----------------------------------------------------|
| attrs | string[] | ["data-testid", "data-test", "data-test-id", "data-cy", "data-e2e"] | List of JSX attribute names considered test-only |
react-component-layout
Enforces a specific declaration order inside React components. Based on Separation of Concerns (SoC) and MVVM principles. Supports auto-fix (--fix).
Required order:
| Group | Category | Examples |
|-------|----------------------|-------------------------------------------------|
| 0 | Props Destructuring | const { id, name } = props |
| 1 | Priority Hooks | useLocation, useNavigate, useTranslation |
| 2 | Context Hooks | useThemeContext, useAuthContext |
| 3 | State Hooks | useState, useReducer, watch |
| 4 | Query/Mutation Hooks | useQuery, useMutation |
| 5 | Custom Hooks | useForm, useDebounce |
| 6 | Effect Hooks | useEffect, useMemo, useCallback |
| 7 | Utility Functions | const getLabel = () => ... |
| 8 | Event Handlers | const handleClick = () => ... |
| 9 | View Values | const title = isLoading ? '...' : name |
| 10 | Early Returns | if (!data) return null |
| 11 | JSX Return | return <div>...</div> |
- Dependency values (group
-1) are transparent — they can appear anywhere without triggering order violations. - Groups 3 (State), 7 (Utility), and 8 (Handler) must be contiguous within themselves.
- Groups 9 (View Values) and 10 (Early Returns) may be freely swapped with each other.
This rule accepts no configuration options.
test-statement-match
Enforces naming conventions for it and test blocks in test files (.test.*, .spec.*, __tests__/).
it(...)descriptions must start with"should "test(...)descriptions must contain a conjunction (if,when,while, etc.)
// ✅ Valid
it('should render the button', () => { ... });
test('returns null when data is empty', () => { ... });
// ❌ Invalid
it('renders the button', () => { ... }); // Missing 'should'
test('returns null for empty data', () => { ... }); // No conjunctionOptions:
["error", {
"conjunctions": ["if", "when", "while", "after", "before"],
"ignoreTestPatterns": [".*\\.e2e\\.ts$"]
}]| Option | Type | Default | Description |
|----------------------|------------|------------------------------------------------------------|---------------------------------------------|
| conjunctions | string[] | ["if", "when", "while", "after", "before", "with", ...] | Valid conjunctions list |
| ignoreTestPatterns | string[] | [] | File patterns (regex) to exclude from check |
Development
Requirements
- Node.js 18+
- npm
Setup
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Watch mode
npm test -- --watchProject Structure
eslint-eg-rules/
├── src/
│ ├── index.ts # Plugin entry point; exports rules and configs
│ ├── rules/
│ │ ├── api-type-suffix/
│ │ │ ├── index.ts # Rule implementation
│ │ │ └── index.test.ts # Tests
│ │ ├── boolean-prop-naming/
│ │ ├── component-callback-naming/
│ │ ├── functions-naming/
│ │ ├── jsx-event-handler-naming/
│ │ ├── no-test-attrs/
│ │ ├── react-component-layout/
│ │ └── test-statement-match/
│ └── utils/
│ └── react-events.ts # Shared event map definitions
├── demo/ # Vite + React 18 + TypeScript demo project
├── dist/ # Build output (not committed)
├── package.json
└── tsconfig.jsonDemo Project
The demo/ directory is a Vite + React 18 + TypeScript project configured to use only this plugin.
# Build the plugin first (from root)
npm run build
# Install demo dependencies
cd demo && npm install
# Run demo dev server
npm run dev
# Run lint check in demo
npm run lintThe demo project's ESLint config is independent from the root project's config.
Adding a New Rule
Create a new folder under
src/rules/:src/rules/my-new-rule/ ├── index.ts # Rule implementation └── index.test.ts # TestsImplement the rule using
@typescript-eslint/utils:import { TSESLint } from '@typescript-eslint/utils'; const rule: TSESLint.RuleModule<'myMessage', []> = { meta: { type: 'suggestion', docs: { description: 'Rule description' }, messages: { myMessage: 'Error message' }, schema: [], }, defaultOptions: [], create(context) { return { Identifier(node) { // ... }, }; }, }; export default rule;Register it in
src/index.ts:import myNewRule from './rules/my-new-rule'; export const rules = { // ...existing rules 'my-new-rule': myNewRule, }; export const configs = { recommended: { rules: { // ... 'strict-eg-rulez/my-new-rule': 'error', }, }, };Build and test:
npm run build cd demo && npm run lint
