Back to Blog
ESLintTypeScriptLintingCode Quality

ESLint and TypeScript Configuration

Configure ESLint for TypeScript projects. From basic setup to custom rules to IDE integration.

B
Bootspring Team
Engineering
May 12, 2021
6 min read

ESLint catches bugs and enforces consistency in TypeScript projects. Here's how to set it up.

Basic Setup#

# Install dependencies npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin # For flat config (ESLint 9+) npm install -D eslint @eslint/js typescript-eslint

Flat Config (ESLint 9+)#

1// eslint.config.js 2import eslint from '@eslint/js'; 3import tseslint from 'typescript-eslint'; 4 5export default tseslint.config( 6 eslint.configs.recommended, 7 ...tseslint.configs.recommended, 8 { 9 languageOptions: { 10 parserOptions: { 11 project: true, 12 tsconfigRootDir: import.meta.dirname, 13 }, 14 }, 15 }, 16 { 17 ignores: ['dist/', 'node_modules/', '*.config.js'], 18 } 19); 20 21// With strict type checking 22export default tseslint.config( 23 eslint.configs.recommended, 24 ...tseslint.configs.strictTypeChecked, 25 ...tseslint.configs.stylisticTypeChecked, 26 { 27 languageOptions: { 28 parserOptions: { 29 project: true, 30 tsconfigRootDir: import.meta.dirname, 31 }, 32 }, 33 } 34);

Legacy Config (.eslintrc)#

1{ 2 "root": true, 3 "parser": "@typescript-eslint/parser", 4 "parserOptions": { 5 "ecmaVersion": "latest", 6 "sourceType": "module", 7 "project": "./tsconfig.json" 8 }, 9 "plugins": ["@typescript-eslint"], 10 "extends": [ 11 "eslint:recommended", 12 "plugin:@typescript-eslint/recommended", 13 "plugin:@typescript-eslint/recommended-type-checked" 14 ], 15 "ignorePatterns": ["dist", "node_modules", "*.config.js"], 16 "rules": { 17 "@typescript-eslint/no-unused-vars": "error", 18 "@typescript-eslint/no-explicit-any": "warn" 19 } 20}

React Projects#

1// eslint.config.js 2import eslint from '@eslint/js'; 3import tseslint from 'typescript-eslint'; 4import react from 'eslint-plugin-react'; 5import reactHooks from 'eslint-plugin-react-hooks'; 6 7export default tseslint.config( 8 eslint.configs.recommended, 9 ...tseslint.configs.recommended, 10 { 11 files: ['**/*.{ts,tsx}'], 12 plugins: { 13 react, 14 'react-hooks': reactHooks, 15 }, 16 languageOptions: { 17 parserOptions: { 18 ecmaFeatures: { jsx: true }, 19 project: true, 20 }, 21 }, 22 settings: { 23 react: { version: 'detect' }, 24 }, 25 rules: { 26 ...react.configs.recommended.rules, 27 ...reactHooks.configs.recommended.rules, 28 'react/react-in-jsx-scope': 'off', 29 'react/prop-types': 'off', 30 }, 31 } 32);

Essential Rules#

1// eslint.config.js 2export default [ 3 // ... base config 4 { 5 rules: { 6 // TypeScript 7 '@typescript-eslint/no-unused-vars': ['error', { 8 argsIgnorePattern: '^_', 9 varsIgnorePattern: '^_', 10 }], 11 '@typescript-eslint/no-explicit-any': 'warn', 12 '@typescript-eslint/explicit-function-return-type': 'off', 13 '@typescript-eslint/explicit-module-boundary-types': 'off', 14 '@typescript-eslint/no-non-null-assertion': 'warn', 15 16 // Strict type-checked rules 17 '@typescript-eslint/no-floating-promises': 'error', 18 '@typescript-eslint/no-misused-promises': 'error', 19 '@typescript-eslint/await-thenable': 'error', 20 '@typescript-eslint/no-unnecessary-type-assertion': 'error', 21 '@typescript-eslint/prefer-nullish-coalescing': 'error', 22 '@typescript-eslint/prefer-optional-chain': 'error', 23 24 // Best practices 25 '@typescript-eslint/consistent-type-imports': ['error', { 26 prefer: 'type-imports', 27 }], 28 '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], 29 '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], 30 }, 31 }, 32];

Custom Rule Configuration#

1// eslint.config.js 2export default [ 3 { 4 rules: { 5 // Naming conventions 6 '@typescript-eslint/naming-convention': [ 7 'error', 8 // Variables and functions: camelCase 9 { 10 selector: 'variable', 11 format: ['camelCase', 'UPPER_CASE', 'PascalCase'], 12 }, 13 { 14 selector: 'function', 15 format: ['camelCase', 'PascalCase'], 16 }, 17 // Types and interfaces: PascalCase 18 { 19 selector: 'typeLike', 20 format: ['PascalCase'], 21 }, 22 // Private members: leading underscore 23 { 24 selector: 'memberLike', 25 modifiers: ['private'], 26 format: ['camelCase'], 27 leadingUnderscore: 'require', 28 }, 29 // Boolean variables: prefixed 30 { 31 selector: 'variable', 32 types: ['boolean'], 33 format: ['PascalCase'], 34 prefix: ['is', 'has', 'should', 'can', 'will'], 35 }, 36 ], 37 38 // Method signature style 39 '@typescript-eslint/method-signature-style': ['error', 'property'], 40 41 // No magic numbers 42 '@typescript-eslint/no-magic-numbers': ['warn', { 43 ignore: [0, 1, -1], 44 ignoreEnums: true, 45 ignoreReadonlyClassProperties: true, 46 }], 47 }, 48 }, 49];

Disabling Rules#

1// Disable for entire file 2/* eslint-disable @typescript-eslint/no-explicit-any */ 3 4// Disable for next line 5// eslint-disable-next-line @typescript-eslint/no-unused-vars 6const unusedVar = 'example'; 7 8// Disable for specific line 9const result = data as any; // eslint-disable-line @typescript-eslint/no-explicit-any 10 11// Disable with reason 12// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Validated above 13const value = obj!.property; 14 15// Re-enable rule 16/* eslint-enable @typescript-eslint/no-explicit-any */

Override Patterns#

1// eslint.config.js 2export default [ 3 // Base config for all files 4 { 5 rules: { 6 '@typescript-eslint/no-explicit-any': 'error', 7 }, 8 }, 9 10 // Override for test files 11 { 12 files: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**'], 13 rules: { 14 '@typescript-eslint/no-explicit-any': 'off', 15 '@typescript-eslint/no-non-null-assertion': 'off', 16 }, 17 }, 18 19 // Override for config files 20 { 21 files: ['*.config.ts', '*.config.js'], 22 rules: { 23 '@typescript-eslint/no-require-imports': 'off', 24 }, 25 }, 26 27 // Override for scripts 28 { 29 files: ['scripts/**/*.ts'], 30 rules: { 31 'no-console': 'off', 32 }, 33 }, 34];

Prettier Integration#

# Install prettier and eslint config npm install -D prettier eslint-config-prettier
1// eslint.config.js 2import eslint from '@eslint/js'; 3import tseslint from 'typescript-eslint'; 4import prettier from 'eslint-config-prettier'; 5 6export default tseslint.config( 7 eslint.configs.recommended, 8 ...tseslint.configs.recommended, 9 prettier, // Must be last to disable conflicting rules 10);
1// .prettierrc 2{ 3 "semi": true, 4 "singleQuote": true, 5 "tabWidth": 2, 6 "trailingComma": "es5", 7 "printWidth": 100 8}

Import Sorting#

npm install -D eslint-plugin-import @typescript-eslint/parser
1// eslint.config.js 2import importPlugin from 'eslint-plugin-import'; 3 4export default [ 5 { 6 plugins: { 7 import: importPlugin, 8 }, 9 rules: { 10 'import/order': ['error', { 11 groups: [ 12 'builtin', 13 'external', 14 'internal', 15 'parent', 16 'sibling', 17 'index', 18 'type', 19 ], 20 'newlines-between': 'always', 21 alphabetize: { 22 order: 'asc', 23 caseInsensitive: true, 24 }, 25 }], 26 'import/no-duplicates': 'error', 27 'import/no-unresolved': 'off', // TypeScript handles this 28 }, 29 settings: { 30 'import/resolver': { 31 typescript: true, 32 node: true, 33 }, 34 }, 35 }, 36];

IDE Integration#

1// .vscode/settings.json 2{ 3 "editor.codeActionsOnSave": { 4 "source.fixAll.eslint": "explicit" 5 }, 6 "eslint.validate": [ 7 "javascript", 8 "javascriptreact", 9 "typescript", 10 "typescriptreact" 11 ], 12 "eslint.useFlatConfig": true 13}
1// .vscode/extensions.json 2{ 3 "recommendations": [ 4 "dbaeumer.vscode-eslint", 5 "esbenp.prettier-vscode" 6 ] 7}

Package.json Scripts#

1{ 2 "scripts": { 3 "lint": "eslint .", 4 "lint:fix": "eslint . --fix", 5 "lint:check": "eslint . --max-warnings 0", 6 "format": "prettier --write .", 7 "format:check": "prettier --check ." 8 }, 9 "lint-staged": { 10 "*.{ts,tsx}": ["eslint --fix", "prettier --write"], 11 "*.{json,md}": "prettier --write" 12 } 13}

Pre-commit Hooks#

npm install -D husky lint-staged npx husky init
# .husky/pre-commit npx lint-staged
1// package.json 2{ 3 "lint-staged": { 4 "*.{ts,tsx}": [ 5 "eslint --fix --max-warnings 0", 6 "prettier --write" 7 ] 8 } 9}

CI Configuration#

1# .github/workflows/lint.yml 2name: Lint 3 4on: [push, pull_request] 5 6jobs: 7 lint: 8 runs-on: ubuntu-latest 9 steps: 10 - uses: actions/checkout@v4 11 - uses: actions/setup-node@v4 12 with: 13 node-version: '20' 14 cache: 'npm' 15 16 - run: npm ci 17 - run: npm run lint:check 18 - run: npm run format:check

Monorepo Setup#

1// eslint.config.js (root) 2import eslint from '@eslint/js'; 3import tseslint from 'typescript-eslint'; 4 5export default tseslint.config( 6 eslint.configs.recommended, 7 ...tseslint.configs.recommended, 8 { 9 languageOptions: { 10 parserOptions: { 11 project: ['./tsconfig.json', './packages/*/tsconfig.json'], 12 tsconfigRootDir: import.meta.dirname, 13 }, 14 }, 15 }, 16 { 17 ignores: ['**/dist/', '**/node_modules/', '**/coverage/'], 18 } 19);

Debugging ESLint#

1# Check config for specific file 2npx eslint --print-config src/index.ts 3 4# Debug why rule is failing 5npx eslint src/index.ts --debug 6 7# Check which files are being linted 8npx eslint . --debug 2>&1 | grep "Processing" 9 10# Show timing information 11TIMING=1 npx eslint .

Best Practices#

Configuration: ✓ Use flat config for new projects ✓ Enable type-checked rules ✓ Integrate with Prettier ✓ Set up pre-commit hooks Rules: ✓ Start with recommended, customize as needed ✓ Use consistent-type-imports ✓ Enable no-floating-promises ✓ Allow underscore for unused vars Performance: ✓ Cache results in CI ✓ Use project references for monorepos ✓ Ignore generated files ✓ Run in parallel when possible

Conclusion#

ESLint with TypeScript catches type-related bugs and enforces consistency. Use flat config for new projects, enable strict type checking, and integrate with Prettier for formatting. Pre-commit hooks ensure code quality before commits reach the repository.

Share this article

Help spread the word about Bootspring