A custom ESLint plugin for enforcing consistent code formatting and style rules in React/JSX projects
npm install eslint-plugin-code-style









A powerful ESLint plugin for enforcing consistent code formatting and style rules in React/JSX projects.
79 rules (70 auto-fixable, 19 configurable) to keep your codebase clean and consistent
This plugin provides 79 custom rules (70 auto-fixable, 19 configurable) for code formatting. Built for ESLint v9 flat configs.
> Note: ESLint deprecated 79 formatting rules in v8.53.0. Our recommended configs use @stylistic/eslint-plugin as the replacement for these deprecated rules.
Key Benefits:
- Fills the gaps β Provides formatting rules not available in other plugins
- Works alongside existing tools β Complements ESLint's built-in rules and packages like eslint-plugin-react, eslint-plugin-import, etc
- Self-sufficient rules β Each rule handles complete formatting independently
- Consistency at scale β Reduces code-style differences between team members by enforcing uniform formatting across your projects
- Highly automated β 70 of 79 rules support auto-fix with eslint --fix
When combined with ESLint's native rules and other popular plugins, this package helps create a complete code style solution that keeps your codebase clean and consistent.
Installation β’
Quick Start β’
Recommended Configs β’
Rules β’
Contributing
We provide ready-to-use ESLint flat configuration files that combine eslint-plugin-code-style with carefully selected third-party plugins and ESLint built-in rules. These configurations represent our battle-tested setup that reduces code-style differences by ~95%.
- Complete Coverage β Combines ESLint built-in rules, third-party plugins, and all 79 code-style rules
- Ready-to-Use β Copy the config file and start linting immediately
- Battle-Tested β These configurations have been refined through real-world usage
- Fully Documented β Each config includes detailed instructions and explanations
| Configuration | Description | Status |
|---------------|-------------|--------|
| React | React.js projects (JavaScript, JSX) | View Config |
| React + TS + Tailwind | React + TypeScript + Tailwind CSS | View Config |
| React + TypeScript | React + TypeScript projects | Coming Soon |
| React + Tailwind | React + Tailwind CSS projects | Coming Soon |
1. Navigate to the recommended-configs folder
2. Choose the configuration for your project type
3. Follow the installation instructions in the README
4. Copy the eslint.config.js to your project root
5. Run eslint src/ --fix
> Note: Each configuration includes a detailed README with installation commands, plugin explanations, and rule documentation.
---
$370 rules support automatic fixing with eslint --fix. 19 rules have configurable options. 9 rules are report-only (require manual changes). | $3Built specifically for React projects with comprehensive JSX formatting rules. |
$3Designed for ESLint's new flat config system. Modern and future-proof. | $3Lightweight plugin with no external dependencies. Fast and efficient. |
``bashnpm
npm install eslint-plugin-code-style --save-dev
$3
| Dependency | Version |
|------------|---------|
| ESLint |
>= 9.0.0 |
| Node.js | >= 20.0.0 |
π Quick Start
Create or update your
eslint.config.js:`javascript
import codeStyle from "eslint-plugin-code-style";export default [
{
plugins: {
"code-style": codeStyle,
},
rules: {
// Enable individual rules
"code-style/import-format": "error",
"code-style/jsx-children-on-new-line": "error",
// ... add more rules as needed
},
},
];
`Then run ESLint with auto-fix:
`bash
eslint src/ --fix
`
π Enable All Rules
`javascript
rules: {
"code-style/absolute-imports-only": "error",
"code-style/array-callback-destructure": "error",
"code-style/array-items-per-line": "error",
"code-style/array-objects-on-new-lines": "error",
"code-style/arrow-function-block-body": "error",
"code-style/arrow-function-simple-jsx": "error",
"code-style/arrow-function-simplify": "error",
"code-style/assignment-value-same-line": "error",
"code-style/block-statement-newlines": "error",
"code-style/class-method-definition-format": "error",
"code-style/class-naming-convention": "error",
"code-style/classname-dynamic-at-end": "error",
"code-style/classname-multiline": "error",
"code-style/classname-no-extra-spaces": "error",
"code-style/classname-order": "error",
"code-style/comment-format": "error",
"code-style/component-props-destructure": "error",
"code-style/component-props-inline-type": "error",
"code-style/svg-icon-naming-convention": "error",
"code-style/curried-arrow-same-line": "error",
"code-style/empty-line-after-block": "error",
"code-style/enum-format": "error",
"code-style/enum-type-enforcement": "error",
"code-style/export-format": "error",
"code-style/folder-based-naming-convention": "error",
"code-style/folder-structure-consistency": "error",
"code-style/no-redundant-folder-suffix": "error",
"code-style/function-arguments-format": "error",
"code-style/function-call-spacing": "error",
"code-style/function-declaration-style": "error",
"code-style/function-naming-convention": "error",
"code-style/function-object-destructure": "error",
"code-style/function-params-per-line": "error",
"code-style/hook-callback-format": "error",
"code-style/hook-deps-per-line": "error",
"code-style/use-state-naming-convention": "error",
"code-style/if-else-spacing": "error",
"code-style/if-statement-format": "error",
"code-style/import-format": "error",
"code-style/import-source-spacing": "error",
"code-style/index-export-style": "error",
"code-style/index-exports-only": "error",
"code-style/inline-export-declaration": "error",
"code-style/interface-format": "error",
"code-style/jsx-children-on-new-line": "error",
"code-style/jsx-closing-bracket-spacing": "error",
"code-style/jsx-element-child-new-line": "error",
"code-style/jsx-logical-expression-simplify": "error",
"code-style/jsx-parentheses-position": "error",
"code-style/jsx-prop-naming-convention": "error",
"code-style/jsx-simple-element-one-line": "error",
"code-style/jsx-string-value-trim": "error",
"code-style/jsx-ternary-format": "error",
"code-style/logical-expression-multiline": "error",
"code-style/member-expression-bracket-spacing": "error",
"code-style/module-index-exports": "error",
"code-style/multiline-if-conditions": "error",
"code-style/nested-call-closing-brackets": "error",
"code-style/no-empty-lines-in-function-calls": "error",
"code-style/no-empty-lines-in-function-params": "error",
"code-style/no-empty-lines-in-jsx": "error",
"code-style/no-empty-lines-in-objects": "error",
"code-style/no-empty-lines-in-switch-cases": "error",
"code-style/no-hardcoded-strings": "error",
"code-style/no-inline-type-definitions": "error",
"code-style/object-property-per-line": "error",
"code-style/object-property-value-brace": "error",
"code-style/object-property-value-format": "error",
"code-style/opening-brackets-same-line": "error",
"code-style/prop-naming-convention": "error",
"code-style/react-code-order": "error",
"code-style/simple-call-single-line": "error",
"code-style/single-argument-on-one-line": "error",
"code-style/string-property-spacing": "error",
"code-style/ternary-condition-multiline": "error",
"code-style/type-annotation-spacing": "error",
"code-style/type-format": "error",
"code-style/typescript-definition-location": "error",
"code-style/variable-naming-convention": "error",
}
`
---
π Rules Categories
> 79 rules total β 70 with auto-fix π§, 19 configurable βοΈ, 9 report-only. See detailed examples in Rules Reference below.
>
> Legend: π§ Auto-fixable with
eslint --fix β’ βοΈ Customizable options| Rule | Description |
|------|-------------|
| Array Rules | |
|
array-callback-destructure | Destructured params in array callbacks (map, filter, find) go multiline when β₯2 properties π§ |
| array-items-per-line | Collapse arrays β€ threshold to one line; expand larger arrays with each item on own line (default: β€3) π§ βοΈ |
| array-objects-on-new-lines | Each object in an array starts on its own line for better visual scanning π§ |
| Arrow Function Rules | |
| arrow-function-block-body | Wrap multiline arrow function expressions in parentheses for clear boundaries π§ |
| arrow-function-simple-jsx | Collapse arrow functions returning simple single-element JSX to one line, remove unnecessary parens π§ |
| arrow-function-simplify | Convert block body with single return to implicit return: () => { return x; } β () => x π§ |
| curried-arrow-same-line | Curried arrow functions start on same line as =>, not on new line π§ |
| Call Expression Rules | |
| function-arguments-format | Args β₯ threshold or multiline: first arg on new line, each on own line, closing ) on new line (default: β₯2) π§ βοΈ |
| nested-call-closing-brackets | Chain closing brackets on same line: })); not scattered across lines π§ |
| no-empty-lines-in-function-calls | No empty lines between arguments or after (/before ) π§ |
| opening-brackets-same-line | Opening {, [, or ( on same line as function call, not on new line π§ |
| simple-call-single-line | Collapse simple arrow function calls to single line (including callbacks with params and optional chaining) π§ |
| single-argument-on-one-line | Single simple argument stays on one line: fn(x) not expanded π§ |
| Comment Rules | |
| comment-format | Space after //, space inside / /, convert single-line blocks to //, no blank lines between file-top comments π§ |
| Component Rules | |
| component-props-destructure | Component props must be destructured ({ prop }) not received as (props) π§ |
| component-props-inline-type | Inline type annotation } : { with matching props, proper spacing, commas, no interface reference π§ |
| folder-based-naming-convention | Enforce naming based on folder: suffix for views/layouts/pages/providers/reducers/contexts/themes, camelCase suffix for data/constants/strings/services/reducers folders, chained folder names for nested files π§ |
| folder-structure-consistency | Enforce consistent folder structure (flat vs wrapped) in module folders (atoms, components, hooks, enums, views, etc.) βοΈ |
| no-redundant-folder-suffix | Disallow file and folder names that redundantly include the parent folder name as a suffix |
| svg-icon-naming-convention | SVG components must end with "Icon" suffix; "Icon" suffix components must return SVG |
| Class Rules | |
| class-method-definition-format | Consistent spacing in class/method definitions: space before {, no space before ( π§ |
| class-naming-convention | Class declarations must end with "Class" suffix (e.g., ApiServiceClass) π§ |
| Control Flow Rules | |
| block-statement-newlines | Newline after { and before } in if/for/while/function blocks π§ |
| empty-line-after-block | Empty line required between closing } of block and next statement π§ |
| if-else-spacing | Empty line between consecutive if blocks, no empty line between single-line if/else π§ |
| if-statement-format | { on same line as if/else if, else on same line as }, proper spacing π§ |
| logical-expression-multiline | Logical expressions (&&, \|\|) with >maxOperands get one operand per line (default: >3) π§ βοΈ |
| multiline-if-conditions | Conditions exceeding threshold get one operand per line with proper indentation (default: >3) π§ βοΈ |
| no-empty-lines-in-switch-cases | No empty line after case X: before code, no empty lines between cases π§ |
| ternary-condition-multiline | β€maxOperands always single line; >maxOperands multiline (based on operand count, not line length) π§ βοΈ |
| Function Rules | |
| function-call-spacing | No space between function name and (: fn() not fn () π§ |
| function-declaration-style | Auto-fix for func-style: converts function declarations to arrow expressions π§ |
| function-naming-convention | Functions use camelCase, start with verb, end with Handler suffix; handleXxx β xxxHandler π§ |
| function-object-destructure | Non-component functions: use typed params (not destructured), destructure in body; report dot notation access π§ |
| function-params-per-line | When multiline, each param on own line with consistent indentation π§ |
| no-empty-lines-in-function-params | No empty lines between parameters or after (/before ) π§ |
| Hook Rules | |
| hook-callback-format | React hooks: callback on new line, deps array on separate line, proper indentation π§ |
| hook-deps-per-line | Collapse deps β€ threshold to one line; expand larger arrays with each dep on own line (default: >2) π§ βοΈ |
| use-state-naming-convention | Boolean useState variables must start with is/has/with/without prefix π§ βοΈ |
| Import/Export Rules | |
| absolute-imports-only | Use alias imports from index files only (not deep paths), no relative imports; files within the same module folder must use relative imports β auto-fixes absolute imports to relative (default: @/) π§ βοΈ |
| export-format | export { on same line; collapse β€ threshold to one line; expand larger with each specifier on own line (default: β€3) π§ βοΈ |
| import-format | import { and } from on same line; collapse β€ threshold; expand larger with each specifier on own line (default: β€3) π§ βοΈ |
| import-source-spacing | No leading/trailing spaces inside import path quotes π§ |
| index-export-style | Index files: no blank lines, enforce shorthand or import-export style; Regular files: require blank lines between exports (default: shorthand) π§ βοΈ |
| index-exports-only | Index files should only contain imports and re-exports, not code definitions (types, functions, variables, classes) |
| inline-export-declaration | Enforce inline export declarations instead of grouped export statements in non-index files π§ βοΈ |
| module-index-exports | Index files must export all folder contents (files and subfolders) βοΈ |
| JSX Rules | |
| classname-dynamic-at-end | Dynamic expressions (${className}) must be at the end of class strings (JSX and variables) π§ |
| classname-multiline | Long className strings broken into multiple lines; smart detection for objects/returns with Tailwind values π§ βοΈ |
| classname-no-extra-spaces | No extra/leading/trailing spaces in class strings; smart detection for objects/returns with Tailwind values π§ |
| classname-order | Tailwind class ordering in variables/objects/returns; smart detection for Tailwind values π§ |
| jsx-children-on-new-line | Multiple JSX children: each on own line with proper indentation π§ |
| jsx-closing-bracket-spacing | No space before > or /> in JSX tags π§ |
| jsx-element-child-new-line | Nested JSX elements on new lines; text/expression children can stay inline π§ |
| jsx-logical-expression-simplify | Remove unnecessary parens around conditions and JSX in logical expressions π§ |
| jsx-parentheses-position | Opening ( for multiline JSX on same line as return/=>, not on new line π§ |
| jsx-prop-naming-convention | Props: camelCase for regular, kebab-case for data-/aria-, PascalCase for component refs |
| jsx-simple-element-one-line | Collapse simple JSX with single text/expression child to one line π§ |
| jsx-string-value-trim | No leading/trailing whitespace inside JSX string attribute values π§ |
| jsx-ternary-format | Simple ternaries on one line; complex branches get parens with proper indentation π§ |
| no-empty-lines-in-jsx | No empty lines between children or after opening/before closing tags π§ |
| Object Rules | |
| no-empty-lines-in-objects | No empty lines between properties or after {/before } π§ |
| object-property-per-line | Collapse β€ threshold to one line; expand larger with {/} on own lines and each property on own line (default: β₯2) π§ βοΈ |
| object-property-value-brace | Opening { of object value on same line as :, not on new line π§ |
| object-property-value-format | Simple property values on same line as :, not on new line π§ |
| string-property-spacing | No leading/trailing spaces inside string property keys π§ |
| Spacing Rules | |
| assignment-value-same-line | Assignment values start on same line as =, not on new line π§ |
| member-expression-bracket-spacing | No spaces inside brackets in computed member expressions: arr[0] not arr[ 0 ] π§ |
| TypeScript Rules | |
| enum-format | Enforce enum naming (PascalCase + Enum suffix), UPPER_CASE members, no empty lines, and trailing commas π§ |
| enum-type-enforcement | Enforce using enum values instead of string literals for variables typed with *Type (e.g., use ButtonVariantEnum.PRIMARY not "primary") π§ |
| interface-format | Enforce interface naming (PascalCase + Interface suffix), camelCase properties, no empty lines, and trailing commas π§ |
| no-inline-type-definitions | Inline union types in function params should be extracted to named types βοΈ |
| prop-naming-convention | Enforce boolean props start with is/has/with/without, callback props start with on π§ βοΈ |
| type-annotation-spacing | Enforce consistent spacing in type annotations: no space before colon/generic/array brackets, one space after colon π§ |
| type-format | Enforce type naming (PascalCase + Type suffix), camelCase properties, union type formatting, and trailing commas π§ βοΈ |
| typescript-definition-location | Enforce TypeScript definitions (interfaces, types, enums) to be in designated folders βοΈ |
| React Rules | |
| react-code-order | Enforce consistent ordering in components and hooks: props destructure β refs β state β redux β router β context β custom hooks β derived β memo β callback β handlers β effects β return π§ |
| String Rules | |
| no-hardcoded-strings | Enforce importing strings from constants/strings modules instead of hardcoding them βοΈ |
| Variable Rules | |
| variable-naming-convention | camelCase for all variables and constants, PascalCase for components, use prefix for hooks π§ |
---
π Rules Reference
> Rules marked with π§ are auto-fixable using
eslint --fix
π Array Rules
$3
What it does: When destructuring parameters in array method callbacks (map, filter, find, etc.), enforces each property on its own line when there are 2 or more properties.
Why use it: Improves readability of array transformations by making destructured properties easy to scan vertically.
`javascript
// β
Good β each destructured property on its own line
const result = items.map(({
name,
value,
}) => ${name}: ${value});const filtered = users.filter(({
age,
isActive,
}) => age > 18 && isActive);
// β
Good β single property stays inline
const names = items.map(({ name }) => name);
// β Bad β multiple properties on same line
const result = items.map(({ name, value, id }) =>
${name}: ${value});// β Bad β hard to scan properties
const data = records.filter(({ status, type, category }) => status === "active");
`---
$3
What it does: Controls array formatting based on the number of items. Short arrays stay on one line for compactness, while longer arrays get expanded with each item on its own line for better readability.
Why use it: Prevents overly long single-line arrays that are hard to scan, while avoiding unnecessary vertical expansion for simple arrays.
`javascript
// β
Good β 3 or fewer items stay compact
const colors = ["red", "green", "blue"];
const nums = [1, 2, 3];// β
Good β 4+ items expand for readability
const weekdays = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
];
// β Bad β too many items on one line
const weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];
// β Bad β inconsistent formatting
const items = [item1,
item2, item3,
item4];
`Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
maxItems | integer | 3 | Maximum items to keep on single line |`javascript
// Example: Allow up to 4 items on single line
"code-style/array-items-per-line": ["error", { maxItems: 4 }]
`---
$3
What it does: In arrays containing objects, ensures each object starts on its own line regardless of object size.
Why use it: Object literals in arrays are visually complex. Putting each on its own line makes it easier to scan, compare, and edit individual items.
`javascript
// β
Good β each object clearly separated
const users = [
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "user" },
{ id: 3, name: "Charlie", role: "user" },
];// β
Good β even short objects get their own line
const points = [
{ x: 0, y: 0 },
{ x: 10, y: 20 },
];
// β Bad β objects crammed together
const users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
// β Bad β inconsistent line breaks
const items = [{ id: 1 },
{ id: 2 }, { id: 3 }];
`
β‘οΈ Arrow Function Rules
$3
What it does: Ensures arrow functions with multiline expressions use block body with explicit return, wrapped in parentheses when needed.
Why use it: Multiline expressions without block body can be confusing. Clear boundaries with
{ and } make the function body obvious.`javascript
// β
Good β block body for complex logic
const handleSubmit = () => {
validateForm();
submitData();
return result;
};// β
Good β multiline JSX wrapped properly
const Button = () => (
);
// β Bad β comma operator is confusing
const handleSubmit = () => (validateForm(), submitData(), result);
// β Bad β multiline without clear boundaries
const Button = () => ;
`---
$3
What it does: Collapses arrow functions that return a single simple JSX element onto one line by removing unnecessary parentheses and line breaks.
Why use it: Simple component wrappers don't need multi-line formatting. Single-line is more scannable and reduces vertical space.
`javascript
// β
Good β simple JSX on one line
export const Layout = ({ children }) => {children} ;
export const Icon = () => ;
const Wrapper = (props) => ;// β Bad β unnecessary multi-line for simple JSX
export const Layout = ({ children }) => (
{children}
);
// β Bad β extra parentheses not needed
const Icon = () => (
);
`---
$3
What it does: Converts arrow functions with a single return statement to use implicit return, removing the block body and
return keyword.Why use it: Implicit returns are more concise and idiomatic JavaScript. They reduce noise and make the code easier to read.
`javascript
// β
Good β implicit return
const double = (x) => x * 2;
const getName = (user) => user.name;
const items = data.map((item) => item.value);
const isValid = (x) => x > 0 && x < 100;// β Bad β unnecessary block body and return
const double = (x) => { return x * 2; };
const getName = (user) => { return user.name; };
const items = data.map((item) => { return item.value; });
const isValid = (x) => { return x > 0 && x < 100; };
`---
$3
What it does: Ensures that when an arrow function returns another function, the returned function starts on the same line as
=>.Why use it: Curried functions are easier to read when the chain is visible. Breaking after
=> obscures the function structure.`javascript
// β
Good β curried function visible on same line
const createAction = (type) => (payload) => ({ type, payload });const withLogger = (fn) => (...args) => {
console.log("Called with:", args);
return fn(...args);
};
const mapDispatch = () => async (dispatch) => {
await dispatch(fetchData());
};
// β Bad β chain broken across lines
const createAction = (type) =>
(payload) => ({ type, payload });
const mapDispatch = () =>
async (dispatch) => {
await dispatch(fetchData());
};
`
π Call Expression Rules
$3
What it does: Enforces consistent formatting for function call arguments:
- Single simple argument stays on one line
- 2+ arguments get one per line
- Multiline arguments trigger full expansion
- React hooks are skipped by default (they have their own rule)
Why use it: Consistent argument formatting makes function calls scannable and diffs clean when adding/removing arguments.
`javascript
// β
Good β single argument stays compact
fetchUser(userId);
console.log(message);
dispatch(action);// β
Good β 2+ arguments get one per line
setValue(
"email",
"user@example.com",
);
createUser(
name,
email,
password,
);
// β
Good β multiline argument triggers expansion
processData(
{
id: 1,
name: "test",
},
);
// β
Good β callback with body triggers expansion
items.forEach(
(item) => {
process(item);
save(item);
},
);
// β Bad β multiple arguments on same line
setValue("email", "user@example.com");
createUser(name, email, password);
// β Bad β inconsistent formatting
fn(arg1,
arg2, arg3);
`Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
minArgs | integer | 2 | Minimum arguments to enforce multiline |
| skipHooks | boolean | true | Skip React hooks (useEffect, etc.) |
| skipSingleArg | boolean | true | Skip calls with single complex argument |`javascript
// Example: Require multiline for 3+ arguments
"code-style/function-arguments-format": ["error", { minArgs: 3 }]// Example: Don't skip React hooks
"code-style/function-arguments-format": ["error", { skipHooks: false }]
`---
$3
What it does: Ensures nested function calls (common in styled-components, HOCs) have closing brackets on the same line:
}));Why use it: Scattered closing brackets (
}\n);\n ) waste vertical space and make it harder to see where expressions end.`javascript
// β
Good β closing brackets together
const StyledCard = styled(Card)(({ theme }) => ({
color: theme.palette.text.primary,
padding: theme.spacing(2),
}));const StyledButton = styled("button")(({ theme }) => ({
backgroundColor: theme.colors.primary,
}));
// β
Good β multiple levels
const Component = connect(
mapStateToProps,
mapDispatchToProps,
)(withRouter(MyComponent));
// β Bad β closing brackets scattered
const StyledCard = styled(Card)(({ theme }) => ({
color: theme.palette.text.primary,
})
);
// β Bad β each bracket on its own line
const StyledCard = styled(Card)(({ theme }) => ({
color: theme.colors.primary,
})
)
;
`---
$3
What it does: Removes empty lines within function call argument lists β between arguments and after opening/before closing parentheses.
Why use it: Empty lines between arguments break visual grouping. Arguments should flow as a cohesive list.
`javascript
// β
Good β no empty lines
createUser(
name,
email,
password,
role,
);fetchData(
url,
{
method: "POST",
body: data,
},
);
// β Bad β empty line between arguments
createUser(
name,
email,
password,
);
// β Bad β empty line after opening paren
fetchData(
url,
options,
);
// β Bad β empty line before closing paren
fetchData(
url,
options,
);
`---
$3
What it does: Ensures opening brackets (
{, [, () in function arguments stay on the same line as the function call.Why use it: Opening brackets on new lines create unnecessary indentation and vertical space.
`javascript
// β
Good β brackets on same line as call
fn({ key: value });
process([1, 2, 3]);
items.map(({ id }) => id);
configure({ debug: true });// β
Good β multiline content is fine
fn({
key: value,
other: data,
});
items.map(({ id, name }) => (
));
// β Bad β opening bracket on new line
fn(
{ key: value }
);
process(
[1, 2, 3]
);
items.map(
({ id }) => id
);
`---
$3
What it does: Collapses simple function calls with an arrow function onto one line when the result fits within 120 characters. Handles:
- Zero-param callbacks:
lazy(() => import("./Page"))
- Callbacks with params and simple expression bodies: .find((f) => f.code === x)
- Optional chaining: .find(...)?.symbolWhy use it: Common patterns like
lazy(() => import(...)) and .find((item) => item.id === id) don't need multiline formatting. Single line is cleaner.`javascript
// β
Good β simple patterns on one line
const Page = lazy(() => import("./Page"));
setTimeout(() => callback(), 100);
const symbol = items.find(({ code }) => code === currency)?.symbol;// β
Good β complex callbacks stay multiline
const Page = lazy(() => {
console.log("Loading page");
return import("./Page");
});
// β Bad β unnecessary multiline for simple pattern
const Page = lazy(
() => import("./Page"),
);
const symbol = items.find(({ code }) =>
code === currency)?.symbol;
const symbol = items.find(({ code }) => code === currency)?.
symbol;
`---
$3
What it does: Ensures function calls with a single simple argument (literal, identifier, member expression) stay on one line.
Why use it: Single-argument calls don't need multiline formatting. Expanding them wastes vertical space.
`javascript
// β
Good β single argument on one line
fetchUser(userId);
console.log(message);
process(data.items);
dispatch(action);
setValue("key");
getElement(document.body);// β
Good β complex single argument can be multiline
processConfig({
key: value,
other: data,
});
// β Bad β simple argument expanded unnecessarily
fetchUser(
userId,
);
console.log(
message,
);
dispatch(
action,
);
`
π¬ Comment Rules
$3
What it does: Enforces proper comment formatting:
- Space after
// in line comments
- Space after / and before / in block comments
- Single-line block comments converted to line comments
- No blank lines between consecutive comments at file topWhy use it: Consistent comment formatting improves readability and maintains a clean, professional codebase.
`javascript
// β
Good β proper spacing
// This is a comment
/ This is a block comment //*
* This is a multi-line
* block comment
*/
// β
Good β file-top comments without gaps
// File: utils.js
// Author: John Doe
// License: MIT
// β Bad β missing space after //
//This is a comment
// β Bad β no space in block comment
/No space/
// β Bad β single-line block should be line comment
/ This should use // syntax /
`
ποΈ Class Rules
$3
What it does: Enforces consistent spacing in class and method definitions:
- Space before opening brace
{ in class declarations
- No space between method name and opening parenthesis (
- Space before opening brace { in method definitions
- Opening brace must be on same line as class/method signatureWhy use it: Consistent formatting makes code more readable and prevents common spacing inconsistencies in class definitions.
`javascript
// β
Good β proper spacing in class and methods
class ApiServiceClass {
getDataHandler(): string {
return "data";
} async fetchUserHandler(id: string): Promise {
return await this.fetch(id);
}
}
// β Bad β missing space before { in class
class ApiServiceClass{
getDataHandler(): string {
return "data";
}
}
// β Bad β space between method name and (
class ApiServiceClass {
getDataHandler (): string {
return "data";
}
}
// β Bad β missing space before { in method
class ApiServiceClass {
getDataHandler(): string{
return "data";
}
}
// β Bad β opening brace on different line
class ApiServiceClass {
getDataHandler(): string
{
return "data";
}
}
`---
$3
What it does: Enforces that class declarations must end with "Class" suffix. This distinguishes class definitions from other PascalCase names like React components or type definitions.
Why use it: Clear naming conventions prevent confusion between classes, components, and types. The "Class" suffix immediately identifies the construct.
`javascript
// β
Good β class ends with "Class"
class ApiServiceClass {
constructor() {}
fetch() {}
}class UserRepositoryClass {
save(user) {}
}
// β Bad β missing "Class" suffix
class ApiService {
constructor() {}
}
class UserRepository {
save(user) {}
}
`
π Control Flow Rules
$3
What it does: Enforces newlines after the opening brace
{ and before the closing brace } in block statements (if, for, while, etc.).Why use it: Consistent block formatting improves readability. Single-line blocks are harder to scan and edit.
`javascript
// β
Good β proper block formatting
if (condition) {
doSomething();
}for (const item of items) {
process(item);
}
while (running) {
tick();
}
// β Bad β everything on one line
if (condition) { doSomething(); }
// β Bad β no space after brace
if (condition) {doSomething();}
// β Bad β inconsistent formatting
for (const item of items) { process(item);
}
`---
$3
What it does: Requires an empty line between a closing brace
} of a block statement (if, try, for, while, etc.) and the next statement, unless the next statement is part of the same construct (else, catch, finally).Why use it: Visual separation between logical blocks improves code readability and makes the structure clearer.
> Note: Consecutive if statements are handled by
if-else-spacing rule.`javascript
// β
Good β empty line after block
if (condition) {
doSomething();
}const x = 1;
// β
Good β else is part of same construct (no empty line needed)
if (condition) {
doSomething();
} else {
doOther();
}
// β Bad β no empty line after block
if (condition) {
doSomething();
}
const x = 1;
`---
$3
What it does: Enforces proper spacing between if statements and if-else chains:
- Consecutive if statements with block bodies must have an empty line between them
- Single-line if and else should NOT have empty lines between them
Why use it: Maintains visual separation between distinct conditional blocks while keeping related single-line if-else pairs compact.
`javascript
// β
Good β empty line between consecutive if blocks
if (!hasValidParams) return null;if (status === "loading") {
return ;
}
if (status === "error") {
return ;
}
// β
Good β no empty line between single-line if-else
if (error) prom.reject(error);
else prom.resolve(token);
// β Bad β no empty line between if blocks
if (!hasValidParams) return null;
if (status === "loading") {
return ;
}
if (status === "error") {
return ;
}
// β Bad β empty line between single-line if-else
if (error) prom.reject(error);
else prom.resolve(token);
`---
$3
What it does: Enforces consistent if/else formatting:
- Opening
{ on the same line as if/else if/else
- else on the same line as the closing }
- Proper spacing around keywordsWhy use it: Consistent brace placement reduces visual noise and follows the most common JavaScript style (K&R / "one true brace style").
`javascript
// β
Good β consistent formatting
if (condition) {
doSomething(); doMore();
}
if (condition) {
doSomething();
doMore();
} else {
doOther();
doAnother();
}
if (conditionA) {
handleA();
processA();
} else if (conditionB) {
handleB();
processB();
} else {
handleDefault();
processDefault();
}
// β Bad β brace on new line
if (condition)
{
doSomething();
doMore();
}
// β Bad β else on new line
if (condition) {
doSomething();
doMore();
}
else {
doOther();
doAnother();
}
// β Bad β inconsistent formatting
if (condition)
{
doSomething();
doMore();
}
else
{
doOther();
doAnother();
}
`---
$3
What it does: When a logical expression (
&&, ||) has more operands than the threshold (default: 3), each operand goes on its own line with the operator at the start.Why use it: Long logical expressions are hard to read on one line. One operand per line makes each part clear and easy to modify.
`javascript
// β
Good β 3 or fewer operands stay inline
const isValid = a && b && c;
const result = x || y;// β
Good β 4+ operands get one per line
const err = data.error
|| data.message
|| data.status
|| data.fallback;
const isComplete = hasName
&& hasEmail
&& hasPhone
&& hasAddress;
// β Bad β 4+ operands on single line
const err = data.error || data.message || data.status || data.fallback;
`Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
maxOperands | integer | 3 | Maximum operands allowed on a single line |`javascript
// Configuration example - allow up to 4 operands on single line
"code-style/logical-expression-multiline": ["error", { maxOperands: 4 }]
`---
$3
What it does: When an if statement has more conditions than the threshold (default: 3), each condition goes on its own line with proper indentation.
Why use it: Long conditions are hard to read on one line. One per line makes each condition clear and easy to modify.
`javascript
// β
Good β 3 or fewer conditions stay inline
if (isValid && isActive) {}
if (a && b && c) {}// β
Good β 4+ conditions get one per line
if (
isAuthenticated &&
hasPermission &&
!isExpired &&
isEnabled
) {
allowAccess();
}
if (
user.isAdmin ||
user.isModerator ||
user.hasSpecialAccess ||
isPublicResource
) {
showContent();
}
// β Bad β too many conditions on one line
if (isAuthenticated && hasPermission && !isExpired && isEnabled) {}
// β Bad β inconsistent formatting
if (isAuthenticated &&
hasPermission && !isExpired &&
isEnabled) {}
`Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
maxOperands | integer | 3 | Maximum operands to keep on single line. Also applies to nested groups |`javascript
// Example: Allow up to 4 operands on single line
"code-style/multiline-if-conditions": ["error", { maxOperands: 4 }]
`Auto-formatting: Nested groups with >maxOperands are formatted multiline inline:
`javascript
// β Before (nested group has 4 operands)
if ((a || b || c || d) && e) {}// β
After auto-fix β formats nested group multiline
if ((
a
|| b
|| c
|| d
) && e) {}
`Double nesting: Both levels expand when both exceed maxOperands:
`javascript
// β Before (both parent and nested have 4 operands)
if ((a || (c && d && a && b) || c || d) && e) {}// β
After auto-fix β both levels formatted multiline
if ((
a
|| (
c
&& d
&& a
&& b
)
|| c
|| d
) && e) {}
`Extraction: Groups exceeding nesting level 2 are extracted to variables:
`javascript
// β Before (level 3 nesting)
if ((a && (b || (c && d))) || e) {}// β
After auto-fix β extracts deepest nested group
const isCAndD = (c && d);
if ((a && (b || isCAndD)) || e) {}
`---
$3
What it does: Removes empty lines at the start of case blocks and between consecutive case statements.
Why use it: Empty lines inside switch cases create unnecessary gaps. Cases should flow together as a cohesive block.
`javascript
// β
Good β no empty lines
switch (status) {
case "pending":
return "Waiting...";
case "success":
return "Done!";
case "error":
return "Failed";
default:
return "Unknown";
}// β
Good β fall-through cases grouped
switch (day) {
case "Saturday":
case "Sunday":
return "Weekend";
default:
return "Weekday";
}
// β Bad β empty line after case label
switch (status) {
case "pending":
return "Waiting...";
case "success":
return "Done!";
}
// β Bad β empty lines between cases
switch (status) {
case "pending":
return "Waiting...";
case "success":
return "Done!";
default:
return "Unknown";
}
`---
$3
What it does: Formats ternary expressions based on condition operand count:
- β€maxOperands (default: 3): Always collapse to single line regardless of line length
- \>maxOperands: Expand to multiline with each operand on its own line
- Simple parenthesized nested ternaries (β€maxOperands) count as 1 operand and collapse
- Complex nested ternaries (>maxOperands in their condition) are skipped for manual formatting
- Nesting level is fixed at 2 to prevent overly complex conditions
Why use it: Consistent formatting based on complexity, not line length. Simple conditions stay readable on one line; complex conditions get proper multiline formatting.
Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
maxOperands | integer | 3 | Maximum condition operands to keep ternary on single line. Also applies to nested groups |`javascript
// β
Good β β€3 operands always on single line
const x = a && b && c ? "yes" : "no";
const url = lang === "ar" ? ${apiEndpoints.exam.status}/${jobId}?lang=ar : ${apiEndpoints.exam.status}/${jobId};// β
Good β parenthesized nested ternary counts as 1 operand
const inputType = showToggle ? (showPassword ? "text" : "password") : type;
// β
Good β >3 operands formatted multiline
const style = variant === "ghost"
|| variant === "ghost-danger"
|| variant === "muted"
|| variant === "primary"
? "transparent"
: "solid";
// β
Good β nested group with >3 operands formatted multiline inline
const result = (
a
|| (
c
&& d
&& a
&& b
)
|| c
|| d
) && e ? "yes" : "no";
// β Bad β β€3 operands split across lines
const x = a && b && c
? "yes"
: "no";
// β Bad β >3 operands crammed on one line
const style = variant === "ghost" || variant === "ghost-danger" || variant === "muted" || variant === "primary" ? "transparent" : "solid";
`Auto-extraction: Nested groups are auto-extracted to variables only when nesting depth exceeds 2 levels:
`javascript
// β Before (level 3 nesting exceeds limit)
const result = (a && (b || (c && d))) || e ? "yes" : "no";// β
After auto-fix β extracts deepest nested group
const isCAndD = (c && d);
const result = (a && (b || isCAndD)) || e ? "yes" : "no";
`Note: When nested groups exceed
maxOperands but stay within the 2-level nesting limit, they are formatted multiline inline (not extracted).
β‘ Function Rules
$3
What it does: Removes any space between a function name and its opening parenthesis.
Why use it: Standard JavaScript convention.
fn() is correct, fn () looks like a typo and can cause confusion.`javascript
// β
Good β no space before parenthesis
useDispatch();
myFunction(arg);
console.log("message");
array.map((x) => x * 2);
obj.method();// β Bad β space before parenthesis
useDispatch ();
myFunction (arg);
console.log ("message");
array.map ((x) => x * 2);
`---
$3
What it does: Converts function declarations to
const arrow function expressions. This is the auto-fixable companion to ESLint's built-in func-style rule.Why use it: The built-in
func-style: ["error", "expression"] rule reports function declarations but does not auto-fix them. This rule provides the auto-fix. Both rules should be used together for the best experience.> Important: This rule depends on
func-style: ["error", "expression"] being configured. If func-style is set to "declaration" or is disabled, do not enable this rule β it would conflict.`typescript
// β
Good β arrow function expression
export const getToken = (): string | null => getCookie(tokenKey);export const clearAuth = (): void => {
removeToken();
clearStorage();
};
const isAuthenticated = (): boolean => {
const token = getToken();
return !!token;
};
// β Bad β function declaration
export function getToken(): string | null {
return getCookie(tokenKey);
}
export function clearAuth(): void {
removeToken();
clearStorage();
}
function isAuthenticated(): boolean {
const token = getToken();
return !!token;
}
`---
$3
What it does: Enforces naming conventions for functions:
- camelCase required
- Verb prefix required (get, set, fetch, is, has, can, should, click, submit, etc.)
- Handler suffix required (all functions must end with
Handler)
- Auto-fixes handleXxx to xxxHandler (avoids redundant handleClickHandler)
- Auto-fixes PascalCase to camelCase for verb-prefixed functionsWhy use it: Function names should describe actions. Verb prefixes make the purpose immediately clear, and consistent Handler suffix makes event handlers easy to identify.
`javascript
// β
Good β verb prefix + Handler suffix
function getUserDataHandler() {}
function setUserNameHandler(name) {}
function clickHandler() {}
function submitHandler() {}
function isValidEmailHandler(email) {}
function hasPermissionHandler(user) {}
function canAccessHandler(resource) {}
const fetchUsersHandler = async () => {};// β Bad (auto-fixed) β handleXxx β xxxHandler
function handleClick() {} // β clickHandler
function handleSubmit() {} // β submitHandler
function handleChange() {} // β changeHandler
// β Bad (auto-fixed) β missing Handler suffix
function getUserData() {} // β getUserDataHandler
function setUserName() {} // β setUserNameHandler
function fetchUsers() {} // β fetchUsersHandler
// β Bad (auto-fixed) β PascalCase to camelCase
function GetUserData() {} // β getUserDataHandler
function FetchStatus() {} // β fetchStatusHandler
`---
$3
What it does: Enforces that non-component functions should not destructure parameters in the function signature. Instead, use a typed parameter and destructure at the top of the function body. Also reports when parameters are accessed via dot notation (suggesting destructuring).
Why use it: Keeping function signatures clean and short improves readability. Destructuring in the body makes it clear what properties are being used. For React components, this rule does NOT apply β components should destructure props in the signature.
`typescript
// β
Good β typed param with destructuring in body
const createUserHandler = async (data: CreateUserParamsInterface) => {
const { age, email, isActive, name } = data; // Use age, email, isActive, name...
};
const updateUserHandler = (params: UpdateParamsInterface) => {
const { id, updates } = params;
// Use id, updates...
};
// β
Good β React components CAN destructure in signature
const UserCard = ({
name,
email,
} : {
name: string,
email: string,
}) => (
{name} - {email}
);// β Bad β non-component function destructures in signature
const createUserHandler = async ({
age,
email,
isActive,
name,
}: CreateUserParamsInterface) => {
// ...
};
// β Bad β accessing param via dot notation (should destructure)
const processDataHandler = (data: DataInterface) => {
console.log(data.id); // Bad: use destructuring
console.log(data.name); // Bad: use destructuring
return data.value * 2; // Bad: use destructuring
};
`---
$3
What it does: When function parameters span multiple lines, ensures each parameter is on its own line with consistent indentation.
Why use it: Mixed formatting (some params on same line, some on different lines) is confusing. One per line is scannable and easy to edit.
`javascript
// β
Good β each param on own line
function createUser(
name,
email,
password,
role,
) {}const handler = (
event,
context,
callback,
) => {};
// β
Good β short params can stay on one line
function add(a, b) {}
// β Bad β mixed formatting
function createUser(name,
email, password,
role) {}
// β Bad β some on same line, some not
const handler = (event, context,
callback) => {};
`---
$3
What it does: Removes empty lines within function parameter lists β between parameters and after opening/before closing parentheses.
Why use it: Empty lines in parameter lists waste space and make parameters harder to scan as a group.
`javascript
// β
Good β no empty lines
function createUser(
name,
email,
role,
) {}const handler = (
event,
context,
) => {};
// β Bad β empty line between params
function createUser(
name,
email,
role,
) {}
// β Bad β empty line after opening paren
const handler = (
event,
context,
) => {};
`
πͺ Hook Rules
$3
What it does: Enforces consistent multi-line formatting for React hooks that take a callback and dependency array (useEffect, useCallback, useMemo, useLayoutEffect).
Why use it: Hooks with callbacks and dependencies are complex. Multi-line formatting makes the callback, return cleanup, and dependencies clearly visible.
`javascript
// β
Good β callback and deps clearly separated
useEffect(
() => {
fetchData();
},
[userId],
);useCallback(
() => {
handleSubmit(data);
},
[data, handleSubmit],
);
useMemo(
() => expensiveCalculation(items),
[items],
);
// β
Good β cleanup function visible
useEffect(
() => {
const subscription = subscribe();
return () => subscription.unsubscribe();
},
[subscribe],
);
// β Bad β everything crammed on one line
useEffect(() => { fetchData(); }, [userId]);
// β Bad β hard to see dependencies
useCallback(() => { handleSubmit(data); }, [data, handleSubmit]);
`---
$3
What it does: When a hook's dependency array exceeds the threshold (default: 2), each dependency goes on its own line.
Why use it: Long dependency arrays are hard to scan and diff. One per line makes it easy to see what changed and catch missing/extra dependencies.
`javascript
// β
Good β 2 or fewer deps stay inline
useEffect(() => {}, [userId]);
useEffect(() => {}, [userId, token]);// β
Good β 3+ deps get one per line
useEffect(
() => {},
[
userId,
token,
refreshToken,
],
);
useCallback(
() => handleSubmit(data),
[
data,
handleSubmit,
validateForm,
showError,
],
);
// β Bad β too many deps on one line
useEffect(() => {}, [userId, token, refreshToken, apiUrl]);
// β Bad β deps should be one per line when expanded
useEffect(() => {}, [
userId, token, refreshToken,
]);
`Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
maxDeps | integer | 2 | Maximum dependencies to keep on single line |`javascript
// Example: Allow up to 3 dependencies on single line
"code-style/hook-deps-per-line": ["error", { maxDeps: 3 }]
`
$3
What it does: Enforces boolean useState variables to start with valid prefixes (is, has, with, without).
Why use it: Consistent boolean state naming makes code more predictable and self-documenting. When you see
isLoading, you immediately know it's a boolean state.`typescript
// β
Good β boolean state with proper prefix
const [isLoading, setIsLoading] = useState(false);
const [hasError, setHasError] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState(true);
const [withBorder, setWithBorder] = useState(false);// β Bad β boolean state without prefix
const [loading, setLoading] = useState(false);
const [authenticated, setAuthenticated] = useState(true);
const [error, setError] = useState(false);
`Customization Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
booleanPrefixes | string[] | ["is", "has", "with", "without"] | Replace default prefixes entirely |
| extendBooleanPrefixes | string[] | [] | Add additional prefixes to defaults |
| allowPastVerbBoolean | boolean | false | Allow past verb names without prefix (disabled, selected) |
| allowContinuousVerbBoolean | boolean | false | Allow continuous verb names without prefix (loading, saving) |`javascript
// Example: Allow "loading" and "disabled" without prefix
"code-style/use-state-naming-convention": ["error", {
allowPastVerbBoolean: true,
allowContinuousVerbBoolean: true
}]// Example: Add "should" prefix
"code-style/use-state-naming-convention": ["error", {
extendBooleanPrefixes: ["should"]
}]
`
π₯ Import/Export Rules
$3
What it does: Enforces importing from folder index files using absolute paths (aliases like
@/) instead of relative paths or deep file imports. Files within the same module folder must use relative imports (./ or ../) instead of absolute paths to avoid circular dependencies through the index file. Auto-fixes absolute imports to own module folder into relative paths. π§Why use it:
- Absolute imports are cleaner than
../../../components
- Index imports create a public API for each folder
- Refactoring file locations doesn't break imports
- Encourages proper module organization
- Relative imports within the same module folder avoid circular dependencies`javascript
// β
Good β import from index files using alias
import { Button, Input } from "@/components";
import { useAuth, useUser } from "@/hooks";
import { fetchUsers } from "@/apis";
import { formatDate } from "@/utils";// β
Good β assets allow deep imports by default
import logo from "@/assets/images/logo.png";
// β
Good β relative import within the same module folder (siblings)
// File: utils/formatters.js
import { isNumber } from "./validators";
// β
Good β relative import within the same module folder (nested)
// File: data/auth/forget-password/index.ts
import { guestLoginData } from "../../login/guest";
// β Bad β absolute import to own module folder (should use relative)
// File: data/auth/forget-password/index.ts
import { guestLoginData } from "@/data";
// β use relative import instead: import { guestLoginData } from "../../login/guest";
// β Bad β relative imports across different folders
import { Button } from "../../components";
import { useAuth } from "../../../hooks";
// β Bad β deep imports into component internals
import { Button } from "@/components/buttons/primary-button";
import { useAuth } from "@/hooks/auth/useAuth";
import { fetchUsers } from "@/apis/users/fetchUsers";
`Default Allowed Folders:
actions, apis, assets, atoms, components, config, configs, constants, contexts, data, enums, helpers, hooks, interfaces, layouts, lib, middlewares, pages, providers, reducers, redux, requests, routes, schemas, services, store, styles, theme, thunks, types, ui, utils, utilities, viewsCustomization Options:
| Option | Type | Description |
|--------|------|-------------|
|
extraAllowedFolders | string[] | Add custom folders that can be imported with @/folder. Extends defaults without replacing them. Use when your project has folders like features/, modules/, etc. |
| extraReduxSubfolders | string[] | Add Redux-related subfolders that can be imported directly (@/selectors) or nested (@/redux/selectors). Default subfolders: actions, reducers, store, thunks, types |
| extraDeepImportFolders | string[] | Add folders where direct file imports are allowed (@/assets/images/logo.svg). Use for folders without index files like images, fonts, etc. Default: assets |
| aliasPrefix | string | Change the path alias prefix if your project uses something other than @/ (e.g., ~/, src/) |
| allowedFolders | string[] | Completely replace the default allowed folders list. Use only if you need full control over which folders are valid |
| reduxSubfolders | string[] | Completely replace the default Redux subfolders list |
| deepImportFolders | string[] | Completely replace the default deep import folders list |`javascript
// Example: Add custom folders to the defaults
"code-style/absolute-imports-only": ["error", {
extraAllowedFolders: ["features", "modules"],
extraDeepImportFolders: ["images", "fonts"]
}]
`---
$3
What it does: Formats export statements consistently:
-
export { always on the same line as export keyword
- β€3 specifiers stay on one line (collapsed)
- 4+ specifiers get one per line (expanded)
- Proper spacing and trailing commasWhy use it: Consistent export formatting improves readability. Short exports stay compact, long exports become scannable.
`javascript
// β
Good β 3 or fewer specifiers stay compact
export { Button };
export { Button, Input };
export { Button, Input, Select };// β
Good β 4+ specifiers expand with one per line
export {
Button,
Input,
Select,
Checkbox,
};
// β
Good β re-exports follow same rules
export { Button, Input, Select } from "./components";
export {
createUser,
updateUser,
deleteUser,
getUser,
} from "./api";
// β Bad β no spaces
export {Button,Input,Select};
// β Bad β keyword on different line
export
{ Button };
// β Bad β too many on one line
export { Button, Input, Select, Checkbox, Radio };
`Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
maxSpecifiers | integer | 3 | Maximum specifiers to keep on single line |`javascript
"code-style/export-format": ["error", { maxSpecifiers: 4 }]
`---
$3
What it does: Formats import statements consistently:
-
import { on the same line as import keyword
- } from on the same line as closing brace
- β€3 specifiers stay on one line (collapsed)
- 4+ specifiers get one per line (expanded)Why use it: Consistent import formatting improves readability and makes diffs cleaner when adding/removing imports.
`javascript
// β
Good β 3 or fewer specifiers stay compact
import { useState } from "react";
import { Button, Input } from "@/components";
import { get, post, put } from "@/api";// β
Good β 4+ specifiers expand with one per line
import {
useState,
useEffect,
useCallback,
useMemo,
} from "react";
import {
Button,
Input,
Select,
Checkbox,
} from "@/components";
// β Bad β no spaces
import {useState,useEffect} from "react";
// β Bad β keyword on different line
import
{ Button } from "@/components";
// β Bad β from on different line
import { Button }
from "@/components";
// β Bad β too many on one line
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
`Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
maxSpecifiers | integer | 3 | Maximum specifiers to keep on single line |`javascript
"code-style/import-format": ["error", { maxSpecifiers: 4 }]
`---
$3
What it does: Removes any leading or trailing whitespace inside import path strings.
Why use it: Spaces in module paths are almost always typos and can cause import resolution issues.
`javascript
// β
Good β no extra spaces
import { Button } from "@mui/material";
import React from "react";
import styles from "./styles.css";// β Bad β leading space
import { Button } from " @mui/material";
// β Bad β trailing space
import React from "react ";
// β Bad β both
import styles from " ./styles.css ";
`---
$3
What it does: Enforces different export formatting rules for index files vs regular files:
- Index files: No blank lines between exports, use shorthand or import-export style
- Regular files: Require blank lines between exports
Why use it: Index files are re-export aggregators and should be compact. Regular files benefit from spacing between exports for readability.
Regular files (non-index):
`javascript
// β
Good β blank lines between exports
export const API_URL = "/api";export const MAX_RETRIES = 3;
export const fetchData = async () => {};
// β Bad β no blank lines in regular file
export const API_URL = "/api";
export const MAX_RETRIES = 3;
export const fetchData = async () => {};
`Index files β Style: "shorthand" (default):
`javascript
// β
Good β shorthand re-exports, no blank lines
export { Button } from "./button";
export { Input, Select } from "./form";
export { Modal } from "./modal";
export { useAuth, useUser } from "./hooks";
`Index files β Style: "import-export":
`javascript
// β
Good β imports grouped, single export at bottom
import { Button } from "./button";
import { Input, Select } from "./form";
import { Modal } from "./modal";export {
Button,
Input,
Modal,
Select,
};
`Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
style | "shorthand" \| "import-export" | "shorthand" | Export style for index files |`javascript
"code-style/index-export-style": ["error", { style: "import-export" }]
`---
$3
What it does: Index files (
index.ts, index.tsx, index.js, index.jsx`) should only contain imports and re-exports, not any code definitions. All definitions (types, interfaces, functions, variables, classes) should be moved to