@7ka/eslint-config
v0.1.5
Published
Strict ESLint flat config for TypeScript + React + FSD projects
Maintainers
Readme
@7ka/eslint-config
Shared ESLint flat config for 7ka collective projects. Built for strict TypeScript + React + FSD codebases.
Install
npm install -D @7ka/eslint-config \
eslint \
typescript-eslint \
eslint-plugin-react-hooks \
eslint-plugin-react-refresh \
eslint-plugin-import \
eslint-plugin-unicorn \
@feature-sliced/eslint-configUsage
Base (no FSD)
// eslint.config.ts
import config from '@7ka/eslint-config'
export default configStrict FSD
import config from '@7ka/eslint-config/fsd-strict'
export default configLight FSD
import config from '@7ka/eslint-config/fsd-light'
export default configRules
TypeScript
consistent-type-imports
Forces import type when importing only a type. Type-only imports are stripped at compile time — faster builds, cleaner output, signals to readers that the import disappears at runtime.
// bad
import { User } from './types'
// good
import type { User } from './types'no-explicit-any
Bans any as a type. Forces you to properly type values or use unknown when the shape is genuinely unknown.
// bad
function parse(data: any): any {
return data.value
}
// good
function parse(data: unknown): string {
if (typeof data === 'object' && data !== null && 'value' in data) {
return String(data.value)
}
throw new Error('Invalid data shape')
}no-unused-vars
Errors on variables, imports, or arguments that are declared but never used. Prefix with _ to explicitly mark something as intentionally unused.
// bad
import { useEffect, useState } from 'react'
// useState imported but never used
// good — remove unused import
import { useEffect } from 'react'
// good — underscore prefix for intentionally unused args
const handler = (_event: MouseEvent, value: string): void => {
console.warn(value)
}no-floating-promises
Errors when a Promise is not awaited or handled with .catch(). Unhandled promises fail silently.
// bad
function loadData(): void {
fetchUser() // promise result ignored, errors swallowed
}
// good
async function loadData(): Promise<void> {
await fetchUser()
}
// also good
void fetchUser().catch(console.error)no-unsafe-assignment
Errors when a value typed as any is assigned to a variable. Prevents any from spreading through the codebase from third-party boundaries.
// bad
const data = JSON.parse(response) // JSON.parse returns any
const name = data.name // name is now silently any
// good
const raw: unknown = JSON.parse(response)
// TypeScript now forces you to validate before usingno-misused-promises
Errors when a Promise is used where a non-Promise is expected — most commonly async callbacks passed to event handlers that don't await them.
// bad
<button onClick={async () => {
await saveData() // onClick doesn't await — errors are swallowed
}} />
// good
const handleClick = (): void => {
void saveData().catch(console.error)
}
<button onClick={handleClick} />explicit-function-return-type
Forces explicit return type annotations on all functions. No accidental any returns, and readers know immediately what a function produces.
// bad
function getUser(id: string) {
return users.find(u => u.id === id)
}
// good
function getUser(id: string): User | undefined {
return users.find(u => u.id === id)
}naming-convention
Enforces consistent naming patterns across the codebase.
| Selector | Format | Example |
|---|---|---|
| Types / Interfaces | PascalCase | UserProfile, ApiResponse |
| Variables | camelCase or UPPER_CASE | userName, MAX_RETRIES |
| Functions | camelCase or PascalCase | getUser, UserCard |
| React components | PascalCase | ProfilePage, AuthButton |
// bad
interface user_profile { name: string }
const Max_Retries = 3
function get_user(): User { ... }
// good
interface UserProfile { name: string }
const MAX_RETRIES = 3
function getUser(): User { ... }General JavaScript
no-console
Warns on console.log. Allows console.warn and console.error since those are intentional. Prevents debug leftovers reaching production.
// warned
console.log('user loaded', user)
// allowed
console.warn('Deprecated method called')
console.error('Failed to fetch user', error)no-magic-numbers
Bans raw numbers in logic. Forces named constants so intent is clear. 0, 1, and -1 are allowed as common neutral values.
// bad
if (response.status === 403) { ... }
setTimeout(sync, 86400000)
// good
const FORBIDDEN = 403
const ONE_DAY_MS = 86_400_000
if (response.status === FORBIDDEN) { ... }
setTimeout(sync, ONE_DAY_MS)eqeqeq
Forces === instead of ==. Prevents silent type coercion bugs.
// bad — all of these are true with ==
0 == false
'' == false
null == undefined
'1' == 1
// good
value === null
count === 0prefer-const
Forces const when a variable is never reassigned. Signals intent — let implies mutation is coming.
// bad
let userId = getUserId() // never reassigned below
// good
const userId = getUserId()no-var
Bans var entirely. var is function-scoped and hoisted — a source of subtle bugs. let and const are block-scoped and predictable.
// bad
var count = 0
// good
let count = 0
const MAX = 10React Hooks
react-hooks/rules-of-hooks
Enforces the Rules of Hooks. Hooks must be called at the top level — never inside conditions, loops, or nested functions. React relies on hook call order being stable between renders.
// bad
function Component({ isAdmin }: Props): JSX.Element {
if (isAdmin) {
const [data, setData] = useState(null) // conditional hook
}
}
// good
function Component({ isAdmin }: Props): JSX.Element {
const [data, setData] = useState(null)
if (!isAdmin) return <AccessDenied />
...
}react-hooks/exhaustive-deps
Warns when useEffect, useCallback, or useMemo has missing dependencies. Missing deps cause stale closures — the effect runs but sees old values.
// bad — userId missing from deps, effect uses stale value
useEffect(() => {
fetchUser(userId)
}, [])
// good
useEffect(() => {
void fetchUser(userId)
}, [userId])Imports
import/order
Enforces consistent import ordering. Groups are separated by a blank line.
Order:
- Node built-ins (
path,fs) - External packages (
react,effector) - Internal / FSD layers (
@/shared,@/entities) - Parent imports (
../foo) - Sibling imports (
./bar) - Index imports (
./)
// bad
import { useState } from 'react'
import path from 'path'
import { UserCard } from './UserCard'
import type { User } from '@/entities/user'
// good
import path from 'path'
import { useState } from 'react'
import type { User } from '@/entities/user'
import { UserCard } from './UserCard'Unicorn
unicorn/prefer-early-return
Forces early returns instead of deeply nested if blocks. Flatter code is easier to read and reason about.
// bad
function processUser(user: User | undefined): string {
if (user) {
if (user.isActive) {
return user.name
}
}
return 'unknown'
}
// good
function processUser(user: User | undefined): string {
if (!user) return 'unknown'
if (!user.isActive) return 'unknown'
return user.name
}unicorn/no-array-for-each
Forces for...of instead of .forEach(). for...of supports break, continue, and await — .forEach does not.
// bad
users.forEach(user => {
processUser(user)
})
// good
for (const user of users) {
processUser(user)
}unicorn/prefer-query-selector
Forces querySelector / querySelectorAll over older DOM methods. Consistent, composable, works with any CSS selector.
// bad
document.getElementById('app')
document.getElementsByClassName('card')
// good
document.querySelector('#app')
document.querySelectorAll('.card')FSD Layer Boundaries
Enforced in fsd-strict and fsd-light variants only.
Layers can only import from layers below them. Cross-layer upward imports are banned.
app → pages → widgets → features → entities → shared// bad — features importing from pages (upward)
// src/features/auth/model.ts
import { HomePage } from '@/pages/home'
// bad — entities importing from features (upward)
// src/entities/user/model.ts
import { loginFeature } from '@/features/auth'
// good — features importing from entities (downward)
// src/features/auth/model.ts
import type { User } from '@/entities/user'Light FSD omits widgets and entities — suitable for smaller projects:
app → pages → features → shared