Skip to content

base

npm version License: MIT Node.js ESLint TypeScript

> Strict ESLint configuration for TypeScript projects. Maximum type safety, best practices, and consistent code quality. Extends @jmlweb/eslint-config-base-js with strict type checking.

  • 🔒 Strict Type Checking: Enables strictTypeChecked and stylisticTypeChecked configs
  • 🛡️ Type Safety: Enforces explicit return types and prevents any usage
  • 📦 Import Management: Enforces type-only imports with inline style + automatic sorting
  • 🎯 Best Practices: Prevents enum usage, encourages immutability, enforces naming conventions
  • 🚫 No Default Exports: Enforces named exports for better tree-shaking and clearer imports
  • 🎨 Prettier Integration: Disables all ESLint rules that conflict with Prettier
  • 🚀 Flat Config: Uses ESLint 9+ flat config format (latest stable)
  • 🔧 Extensible: Built on @jmlweb/eslint-config-base-js foundation
Terminal window
pnpm add -D @jmlweb/eslint-config-base eslint @eslint/js typescript-eslint eslint-config-prettier eslint-plugin-simple-import-sort @jmlweb/eslint-config-base-js

> 💡 Upgrading from a previous version? See the Migration Guide for breaking changes and upgrade instructions.

Create an eslint.config.js file in your project root:

import baseConfig from '@jmlweb/eslint-config-base';
export default [
...baseConfig,
// Add your project-specific overrides here
];
eslint.config.js
import baseConfig from '@jmlweb/eslint-config-base';
export default [...baseConfig];
eslint.config.js
import baseConfig from '@jmlweb/eslint-config-base';
export default [
...baseConfig,
{
files: ['**/*.test.ts', '**/*.test.tsx'],
rules: {
// Allow any in tests
'@typescript-eslint/no-explicit-any': 'off',
// Allow console in tests
'no-console': 'off',
},
},
{
ignores: ['dist/', 'build/', 'node_modules/', '*.config.ts'],
},
];
eslint.config.js
import baseConfig from '@jmlweb/eslint-config-base';
export default [
...baseConfig,
{
files: ['**/*.tsx'],
rules: {
// React-specific overrides if needed
},
},
];

This config forbids default exports by default for better tree-shaking and clearer imports. If you need default exports (e.g., for config files or specific libraries):

eslint.config.js
import baseConfig from '@jmlweb/eslint-config-base';
export default [
...baseConfig,
{
files: ['*.config.ts', '*.config.js'],
rules: {
'no-restricted-exports': 'off',
},
},
];

This config uses strict type checking by default. If you need non-strict rules, you have two options:

Option 1: Use base-js config and add only recommended TypeScript rules

import baseJsConfig from '@jmlweb/eslint-config-base-js';
import tseslint from 'typescript-eslint';
import prettierConfig from 'eslint-config-prettier';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
export default [
...baseJsConfig,
// Use only recommended TypeScript rules (non-strict)
...tseslint.configs.recommended.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
plugins: {
...config.plugins,
'simple-import-sort': simpleImportSort,
},
rules: {
...config.rules,
...prettierConfig.rules,
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
},
})),
];

Option 2: Override specific strict rules

import baseConfig from '@jmlweb/eslint-config-base';
export default [
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
// Override strict rules to be less strict
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/consistent-type-imports': 'off',
},
},
];

This configuration applies strict TypeScript rules to:

  • **/*.ts - TypeScript files
  • **/*.tsx - TypeScript React files
RuleLevelDescription
@typescript-eslint/no-explicit-anyerrorPrevents any type usage
@typescript-eslint/explicit-function-return-typeerrorRequires explicit return types
@typescript-eslint/consistent-type-importserrorEnforces import type for type-only imports
@typescript-eslint/consistent-type-definitionserrorPrefers type over interface
no-restricted-syntax (TSEnumDeclaration)errorPrevents enum usage (prefer const maps)
no-restricted-exportserrorPrevents default exports (named exports only)
@typescript-eslint/naming-conventionerrorEnforces naming conventions
  • ✅ TypeScript ESLint recommended rules
  • ✅ Strict type checking (strictTypeChecked)
  • ✅ Stylistic type checking (stylisticTypeChecked)
  • ✅ TypeScript parser configuration with project service
  • ✅ Automatic import/export sorting
  • ✅ Prettier conflict resolution
  • ✅ All JavaScript rules from @jmlweb/eslint-config-base-js

The configuration automatically sorts imports and enforces type-only imports:

Before:

import { Component } from './component';
import React, { useState } from 'react';
import { User } from './types';
import fs from 'fs';

After auto-fix:

import fs from 'fs';
import React, { useState } from 'react';
import type { User } from './types';
import { Component } from './component';

Fix import order automatically:

Terminal window
pnpm exec eslint --fix .

> Philosophy: Maximize type safety. Catch bugs at compile time, not runtime. Make invalid states unrepresentable.

This package enforces strict TypeScript practices inspired by the principle that “if it compiles, it works.” The configuration choices reflect a philosophy of using TypeScript’s type system to its fullest potential to prevent bugs before they happen.

Strict Type Checking (strictTypeChecked): Enables all strict type-aware rules

  • Why: TypeScript’s power comes from its type system. Strict checking catches errors like unreachable code, unnecessary conditions, and type mismatches that would otherwise cause runtime bugs
  • Trade-off: More initial errors when adopting this config, and requires explicit typing. However, the bugs caught far outweigh the extra effort
  • When to override: If migrating a large JavaScript codebase and need a gradual transition (consider using @jmlweb/eslint-config-base-js with recommended TypeScript rules instead)

Explicit Return Types (@typescript-eslint/explicit-function-return-type): Requires explicit return types on functions

  • Why: Explicit return types prevent accidental type changes and make code easier to reason about. They serve as inline documentation and catch errors where functions return unexpected types
  • Trade-off: More verbose code, but improved clarity and safety. Inference can hide bugs
  • When to override: For simple, obvious functions where the return type is trivial (but be conservative - explicit is usually better)

No any Type (@typescript-eslint/no-explicit-any): Prohibits using any

  • Why: any defeats the purpose of TypeScript by disabling type checking. It’s a common escape hatch that creates type holes and runtime bugs
  • Trade-off: Forces you to properly type external libraries or complex types. Requires more upfront work but prevents bugs
  • When to override: Only when dealing with truly dynamic data that can’t be typed (rare). Consider unknown first

Type-Only Imports (@typescript-eslint/consistent-type-imports): Enforces import type for type-only imports

  • Why: Separates runtime imports from type imports, improving tree-shaking and build performance. Makes it clear what’s used at runtime vs. compile time
  • Trade-off: More explicit imports, but clearer code and better build optimization
  • When to override: Never - this is a best practice with no real downsides

No Enums (no-restricted-syntax): Prevents TypeScript enum usage

  • Why: TypeScript enums have surprising runtime behavior, bundling issues, and const vs. regular enum confusion. Const maps with as const provide the same benefits without the pitfalls
  • Trade-off: Slightly more verbose syntax (const Status = { ... } as const), but more predictable and type-safe
  • When to override: If you’re comfortable with enum limitations or maintaining legacy code

No Default Exports (no-restricted-exports): Enforces named exports only

  • Why: Named exports enable better tree-shaking, clearer imports, easier refactoring, and better IDE autocomplete. Default exports hide what’s being imported
  • Trade-off: Can’t use import Foo from './foo' syntax. Requires import { Foo } from './foo'
  • When to override: For compatibility with libraries that require default exports (Next.js pages, etc.) - override per-file

Naming Conventions (@typescript-eslint/naming-convention): Enforces consistent naming patterns

  • Why: Consistent naming improves readability and prevents bugs. For example, booleans starting with is/has/can are self-documenting
  • Trade-off: May conflict with legacy code or third-party APIs
  • When to override: When integrating with external APIs that use different conventions

Use this configuration when you want:

  • ✅ Maximum type safety with strict TypeScript rules
  • ✅ Strict code quality standards
  • ✅ Consistent code style across the team
  • ✅ Prevention of common TypeScript pitfalls
  • ✅ Best practices enforcement

For JavaScript-only projects, use @jmlweb/eslint-config-base-js instead.

For React projects, use @jmlweb/eslint-config-react instead.

For less strict projects, you can override the strict rules as shown in the examples above.

You can extend or override the configuration for your specific needs:

import baseConfig from '@jmlweb/eslint-config-base';
export default [
...baseConfig,
{
files: ['**/*.test.ts', '**/*.test.tsx'],
rules: {
// Test-specific rules
'@typescript-eslint/no-explicit-any': 'off',
},
},
{
ignores: ['dist/', 'build/', 'node_modules/'],
},
];

Add linting scripts to your package.json:

{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}

Then run:

Terminal window
pnpm lint # Lint all files
pnpm lint:fix # Fix auto-fixable issues
  • Node.js >= 20.11.0 (required for import.meta.dirname in config files)
  • ESLint >= 9.0.0 (flat config format)
  • TypeScript project with tsconfig.json
  • TypeScript project service enabled (automatic with this config)

This package requires the following peer dependencies:

  • eslint (^9.0.0)
  • @eslint/js (^9.0.0)
  • typescript-eslint (^8.0.0)
  • eslint-config-prettier (^9.1.0)
  • eslint-plugin-simple-import-sort (^12.0.0)
  • @jmlweb/eslint-config-base-js (workspace or published version)

See real-world usage examples:

> Note: This section documents known issues and their solutions. If you encounter a problem not listed here, please open an issue.

Symptoms:

  • npm WARN messages about unmet peer dependencies during installation
  • Messages like “requires a peer of eslint@^8.0.0 but none is installed”

Cause:

  • Some ESLint plugins haven’t updated their peer dependencies to support ESLint 9.x
  • This is a transitional issue as the ecosystem adapts to ESLint’s flat config

Solution:

Terminal window
# pnpm automatically handles peer dependencies
pnpm install

The warnings are usually safe to ignore if your linting works correctly. The plugins typically work fine with ESLint 9.x despite the warnings.

Symptoms:

  • ESLint rules not being applied
  • IDE showing no linting errors
  • Files outside src/ directory not being linted

Cause:

  • Missing TypeScript project service configuration
  • tsconfig.json not in the correct location
  • IDE ESLint extension not configured for flat config

Solution:

  1. Ensure your tsconfig.json is in your project root
  2. Verify your eslint.config.js is using flat config format
  3. For VS Code, update .vscode/settings.json:
{
"eslint.experimental.useFlatConfig": true
}
  1. Restart your IDE/ESLint server

Symptoms:

  • Type-aware rules like @typescript-eslint/no-unnecessary-condition not triggering
  • “Parsing error: Cannot read file” messages

Cause:

  • TypeScript project service not finding your tsconfig.json
  • Multiple tsconfig.json files causing confusion

Solution:

Check your project structure:

Terminal window
# Your tsconfig.json should be in the root
project/
├── tsconfig.json
├── eslint.config.js
└── src/

If you have multiple tsconfig files, this config uses TypeScript project service which automatically detects them.

Symptoms:

  • ESLint auto-fix and Prettier format fighting each other
  • Code gets reformatted back and forth

Cause:

  • ESLint formatting rules conflicting with Prettier
  • Running both tools without proper integration

Solution:

This config already includes eslint-config-prettier to disable conflicting rules. Ensure you:

  1. Run Prettier before ESLint:
{
"scripts": {
"format": "prettier --write .",
"lint": "eslint .",
"check": "prettier --check . && eslint ."
}
}
  1. Configure your IDE to format with Prettier first, then lint with ESLint

> Note: If no breaking changes were introduced in a version, it’s safe to upgrade without additional steps.

No breaking changes have been introduced yet. This package follows semantic versioning. When breaking changes are introduced, detailed migration instructions will be provided here.

For version history, see the Changelog.

Need Help? If you encounter issues during migration, please open an issue.

See CHANGELOG.md for version history and release notes.

MIT