Published on

What is a Linter? Understanding ESLint and Why It Matters for Clean Code

Authors

    What is a Linter? Understanding ESLint and Why It Matters for Clean Code

    When multiple developers work on the same codebase, style inconsistencies and hidden errors can quickly pile up.
    That’s where linters come in, they automatically check your code for potential problems and enforce a consistent style across your project.

    In this post, we’ll explore what a linter is, why you should use one, and why ESLint has become the go-to tool in the JavaScript and TypeScript ecosystem.

    What Linters Detect

    A linter analyzes your code and detects various categories of issues before they cause bugs or inconsistencies.

    • Syntax errors → Problems that make code invalid or non-runnable. Examples:

      • Missing parentheses or brackets → if (user.isLoggedIn {
      • Unclosed strings → console.log("Hello)
      • Misplaced or missing return statements.
    • Code quality issues → Patterns that don’t break syntax but can lead to runtime errors or confusion. Examples:

      • Unused variables → Declaring const result = computeValue(); but never using result.
      • Unreachable code → Having statements after a return.
      • Shadowed variables → Reusing a variable name from an outer scope.
    • Style inconsistencies → Formatting and style differences that make code harder to read. Examples:

      • Missing semicolons or inconsistent indentation.
      • Using "double quotes" in one file and 'single quotes' in another.
      • Mixing tabs and spaces for indentation.
    • Best practice violations → Code that technically works but goes against recommended patterns. Examples:

      • Using deprecated APIs (e.g., ReactDOM.render() in React 18).
      • Using var instead of let or const.
      • Calling asynchronous functions without await.
      • Using == instead of ===, which can cause subtle type coercion bugs.

    Linters like ESLint go beyond catching mistakes, they enforce conventions and prevent bad habits from ever landing in your repository.

    Why Use a Linter?

    1. Catch Errors Early

    Linters detect issues before your app even runs.
    For example, ESLint can warn you about an undefined variable or a missing await in an async function, saving you debugging time later.

    2. Ensure Consistency

    A linter enforces a unified coding style across your team, avoiding unnecessary debates about tabs vs. spaces or where to put commas.

    3. Improve Code Quality

    Linters encourage cleaner, more maintainable code by warning against complex or risky patterns (like deeply nested callbacks or unused imports).

    4. Integrate with Your Workflow

    They run automatically in your IDE, during commits (via pre-commit hooks like Husky), or in CI/CD pipelines, keeping your codebase clean at every step.

    Focus on ESLint: The Standard for JavaScript and TypeScript

    ESLint is the most popular linter in the JavaScript ecosystem, and it’s widely used for React, Node.js, and TypeScript projects.

    🔧 Key Features

    • Highly configurable → Customize every rule to fit your style.
    • Plugin ecosystem → Extend with community plugins (e.g., React, Jest, security, accessibility).
    • TypeScript support → Full integration via @typescript-eslint.
    • Autofixable rules → Many issues can be automatically fixed with:
      npx eslint . --fix
      
    • Integrations → Works with VS Code, WebStorm, GitHub Actions, and almost every CI system.

    Configuring ESLint

    You can set up ESLint in your project with:

    npx eslint --init
    

    If you’re using ESLint v8.23 or later, this command will create a Flat Config file named eslint.config.mjs (instead of the older .eslintrc.json format). This modern format uses JavaScript modules and provides better performance and clarity.

    Here’s a simple example configuration you might use in a Next.js + TypeScript project:

    // eslint.config.mjs
    import js from '@eslint/js'
    import tseslint from 'typescript-eslint'
    import prettier from 'eslint-config-prettier'
    import { FlatCompat } from '@eslint/eslintrc'
    
    const compat = new FlatCompat({ baseDirectory: import.meta.dirname })
    
    export default [
      js.configs.recommended, // Core JavaScript best practices
      ...tseslint.configs.recommended, // TypeScript-specific linting
      ...compat.extends('next/core-web-vitals'), // Next.js and React recommendations
      prettier, // Avoid rule conflicts with Prettier
      {
        files: ['**/*.{js,jsx,ts,tsx}'],
        rules: {
          'no-console': ['warn', { allow: ['warn', 'error'] }],
          '@typescript-eslint/no-unused-vars': [
            'warn',
            { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
          ],
          semi: ['error', 'always'],
        },
      },
    ]
    

    Explanation

    Let’s break down what’s happening:

    js.configs.recommended

    Loads ESLint’s recommended core rules, covering basic JavaScript issues such as:

    • Unused variables
    • Unreachable code
    • Missing return statements
    • Syntax errors

    tseslint.configs.recommended

    Adds TypeScript-aware rules, for example, catching:

    • Unused imports
    • Incorrect type usage
    • Variables declared but never used in .ts/.tsx files

    compat.extends('next/core-web-vitals')

    Applies Next.js and React rules, including:

    • Accessibility best practices (a11y)
    • React Hooks usage checks
    • Warnings for performance issues (like using <img> instead of <Image>)

    prettier

    Turns off formatting-related lint rules that would otherwise conflict with Prettier.

    rules

    Custom project-specific adjustments:

    • 'no-console': ['warn'] → allows console.warn and console.error, but warns about other console calls.
    • '@typescript-eslint/no-unused-vars' → warns about variables or parameters that are declared but not used (ignores ones starting with _).
    • 'semi': ['error', 'always'] → enforces the use of semicolons.

    This setup gives you:

    • Reliable JavaScript + TypeScript linting
    • Next.js best practices built in
    • Automatic compatibility with Prettier
    • Simple, clear rules that make your codebase consistent, readable, and bug-free

    Once this file is in place, you can lint your project with:

    npm run lint
    

    or automatically fix fixable issues:

    npm run lint -- --fix
    

    ESLint vs Other Linters

    FeatureESLintTSlint (deprecated)JSHintStandardJS
    Language SupportJavaScript, TypeScriptTypeScriptJavaScriptJavaScript
    Configurable✅ Yes⚠️ Limited⚠️ Limited❌ No (fixed style)
    Plugin Ecosystem🟢 Large🔴 Small🔴 Small🔴 Minimal
    Autofix Support✅ Yes⚠️ Partial⚠️ Partial✅ Yes
    Actively Maintained🟢 Yes🔴 No🟡 Rarely🟢 Yes

    Since TSlint was officially deprecated in favor of ESLint, nearly all modern JavaScript and TypeScript projects use ESLint today.

    Best Practices for Using ESLint

    Conclusion

    Linters are essential for keeping codebases clean, consistent, and error-free, especially in collaborative projects. Among them, ESLint stands out as the most flexible and powerful tool for JavaScript and TypeScript developers.

    Whether you’re working solo or in a large team, adding ESLint to your workflow ensures your code remains professional, readable, and maintainable for the long run.

    Want to see ESLint in action? Check out its official documentation or try adding it to your project with npx eslint --init.