# eslint-flat-config-airbnb

A mostly reasonable approach to JavaScript and React using Airbnb's ESLint config, updated for ESLint 9+

This fork of the [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) updates the rules and configurations to support ESLint 9.0+ while maintaining the core principles of clean, consistent, and modern JavaScript code.

## Why this fork?
- The original [guide](https://github.com/airbnb/javascript) is widely adopted but does not yet support ESLint 9.0+.
- This version ensures compatibility with the latest ESLint rules and best practices.
- Updates may include deprecations, rule refinements, and modern JavaScript features.

## Installation & Usage
To use this updated guide in your project:

```
npm install --save-dev eslint-flat-config-airbnb
```
We export multiple ESLint configurations for your usage. Extend it in your eslint.config.mjs (or *.js, *.cjs, *.ts, *.mts, *cts).

### eslint-flat-config-airbnb

Our default export contains all of our ESLint and React rules. It requires `eslint`.

```
// eslint.config.mjs
import globals from 'globals';
import tseslint from 'typescript-eslint';
import airbnb from 'eslint-flat-config-airbnb';
import stylistic from '@stylistic/eslint-plugin';

export default [
    {
        ignores: ['node_modules', 'build', 'dist'],
    },
    {
        files: ['src/**/*.{js,mjs,cjs,ts}'],
    },
    ...tseslint.configs.recommended,
    ...airbnb,
    {
        languageOptions: {
            globals: globals.node,
        },
        plugins: {
            '@stylistic': stylistic,
        },
        settings: {
            'import/resolver': {
                node: {
                    extensions: ['.js', '.jsx', '.ts', '.tsx'],
                },
            },
        },
    },
    {
        rules: {
            'no-underscore-dangle': ['error', { allow: ['_id', '__dirname', '__type'] }],
            '@stylistic/template-curly-spacing': ['error', 'always'],
            '@stylistic/object-curly-spacing': ['error', 'always'],
            '@stylistic/computed-property-spacing': ['error', 'always'],
            'no-use-before-define': ['error', { functions: false }],
            'prefer-const': 1,
            complexity: ['error', { max: 15 }],
        },
    },
];
```

### base

Our `base` export contains all ESLint rules, including ECMAScript6+.
`base` also exports these configs - `bestPractices`, `errors`, `es6`, `node`, `strict`, `style`, `imports`, `variables` which are javasScript objects.

```
// eslint.config.mjs
import globals from 'globals';
import { base } from 'eslint-flat-config-airbnb';
import stylistic from '@stylistic/eslint-plugin';

export default [
    {
        ignores: ['node_modules', 'build', 'dist'],
    },
    ...base,
    {
        languageOptions: {
            globals: globals.node,
        },
        plugins: {
            '@stylistic': stylistic,
        },
        rules: {
            'no-underscore-dangle': ['error', { allow: ['_id', '__dirname', '__type'] }],
            '@stylistic/template-curly-spacing': ['error', 'always'],
        }
    },
];
```

#### importing sub-rules from base

```
// eslint.config.mjs
import globals from 'globals';
import { style } from 'eslint-flat-config-airbnb/base';
import stylistic from '@stylistic/eslint-plugin';

export default [
    {
        ignores: ['node_modules', 'build', 'dist'],
    },
    style,
    {
        languageOptions: {
            globals: globals.node,
        },
        plugins: {
            '@stylistic': stylistic,
        },
        rules: {
            'no-underscore-dangle': ['error', { allow: ['_id', '__dirname', '__type'] }],
            '@stylistic/template-curly-spacing': ['error', 'always'],
        }
    },
];
```

### react

Our `react` export contains all React rules.
`react` also exports these configs - `reactA11y`, `reactHooks`, `reactCore` which are javasScript objects.

```
// eslint.config.mjs
import globals from 'globals';
import { react } from 'eslint-flat-config-airbnb';
import stylistic from '@stylistic/eslint-plugin';

export default [
    {
        ignores: ['node_modules', 'build', 'dist'],
    },
    ...react,
    {
        languageOptions: {
            globals: globals.node,
        },
        plugins: {
            '@stylistic': stylistic,
        },
        rules: {
            'no-underscore-dangle': ['error', { allow: ['_id', '__dirname', '__type'] }],
            '@stylistic/template-curly-spacing': ['error', 'always'],
        }
    },
];
```

#### importing sub-rules from react

```
// eslint.config.mjs
import globals from 'globals';
import { reactCore } from 'eslint-flat-config-airbnb/react';
import stylistic from '@stylistic/eslint-plugin';

export default [
    {
        ignores: ['node_modules', 'build', 'dist'],
    },
    reactCore,
    {
        languageOptions: {
            globals: globals.node,
        },
        plugins: {
            '@stylistic': stylistic,
        },
        rules: {
            'no-underscore-dangle': ['error', { allow: ['_id', '__dirname', '__type'] }],
            '@stylistic/template-curly-spacing': ['error', 'always'],
        }
    },
];
```

#### Custom eslint.config.mjs used across nodejs repo's

```
import globals from 'globals';
import { base } from 'eslint-flat-config-airbnb';
import stylistic from '@stylistic/eslint-plugin';

export default [
    {
        ignores: ['node_modules', 'build', 'dist'],
    },
    {
        files: ['src/**/*.{js,mjs,cjs,ts}'],
    },
    ...base,
    {
        languageOptions: {
            globals: globals.node,
        },
        plugins: {
            '@stylistic': stylistic,
        },
        settings: {
            'import/resolver': {
                node: {
                    extensions: ['.js', '.jsx', '.ts', '.tsx'],
                },
            },
        },
    },
    {
        rules: {
            'import/no-extraneous-dependencies': ['error'],
            'no-underscore-dangle': ['error', { allow: ['_id', '__dirname', '__type'] }],
            '@stylistic/indent': ['error', 4],
            '@stylistic/template-curly-spacing': ['error', 'always'],
            '@stylistic/computed-property-spacing': ['error', 'always'],
            'no-use-before-define': ['error', { functions: false }],
            'prefer-const': 1,
            '@stylistic/max-len': ['error', { code: 200 }],
            complexity: ['error', { max: 15 }],
        },
    },
    {
        files: ['eslint.config.mjs'],
        rules: {
            'import/no-extraneous-dependencies': 'off',
        },
    },
];
```

## Troubleshoot Guide

**Q1:** Can you use flat config for projects using commonjs (i.e., using ```module.exports```)?<br>
**Ans:** Yes. Change file format to ```.mjs``` and use ES6 import and export statements in ```eslint.config.mjs```.

**Q2:** Getting warning: ```Warning: React version was set to "detect" in eslint-plugin-react settings, but the "react" package is not installed. Assuming latest React version for linting.```<br>
**Ans:** If you are not using react package in your project, you can use ```base``` rules instead as described in [above](#base) section.

**Q3:** Getting error: ```ReferenceError: stylistic is not defined``` or for any other eslint plugin instead of ```stylistic```.<br>
**Ans:** It is likely because there are some rule(s) defined in ```rules``` object in ```eslint.config.mjs``` and its corresponding package is not imported and added in ```plugins``` separately in the file even though it is installed with ```eslint-flat-config-airbnb``` package.

**Q4:** Getting error: ```ConfigError: Config (unnamed): Key "plugins": Cannot redefine plugin "import".``` or for any other eslint plugin instead of ```import```.<br>
**Ans:** Flat config doesn't support multiple declaration of ```plugins``` keyword. If there are mutiple packages which are importing with ```plugins``` keyword, remove them until atmost one declaration is present in config namespace.

```
// eslint.config.mjs
import globals from 'globals';
import { base } from 'eslint-flat-config-airbnb';
import stylistic from '@stylistic/eslint-plugin';
import importPlugin from 'eslint-plugin-import';

// eslint-disable-next-line no-unused-vars
const { plugins, ...importConfigWithoutPlugin } = importPlugin.flatConfigs.recommended;

export default [
    {
        ignores: ['node_modules', 'build', 'dist'],
    },
    ...base,
    importConfigWithoutPlugin,
    {
        languageOptions: {
            globals: globals.node,
        },
        plugins: {
            '@stylistic': stylistic,
        },
        rules: {
            'no-underscore-dangle': ['error', { allow: ['_id', '__dirname', '__type'] }],
            '@stylistic/template-curly-spacing': ['error', 'always'],
        }
    },
];
```

**OR**

```
// eslint.config.mjs
/* eslint-disable no-unused-vars */
import globals from 'globals';
import { bestPractices, errors, es6, node, strict, style, imports, variables } from 'eslint-flat-config-airbnb/base';
import stylistic from '@stylistic/eslint-plugin';
import importPlugin from 'eslint-plugin-import';
import nodePlugin from 'eslint-plugin-n';

const { plugins, ...importConfigWithoutPlugin } = importPlugin.flatConfigs.recommended;
const { plugins: bestPracticesPlugins, ...bestPracticesConfigWithoutPlugin } = bestPractices;
const { plugins: errorsPlugins, ...errorsConfigWithoutPlugin } = errors;
const { plugins: es6Plugins, ...es6ConfigWithoutPlugin } = es6;
const { plugins: nodePlugins, ...nodeConfigWithoutPlugin } = node;
const { plugins: stylePlugins, ...styleConfigWithoutPlugin } = style;
const { plugins: importsPlugins, ...importsConfigWithoutPlugin } = imports;

export default [
    {
        ignores: ['node_modules', 'build', 'dist'],
    },
    importConfigWithoutPlugin,
    bestPracticesConfigWithoutPlugin,
    errorsConfigWithoutPlugin,
    es6ConfigWithoutPlugin,
    nodeConfigWithoutPlugin,
    styleConfigWithoutPlugin,
    importsConfigWithoutPlugin,
    strict,
    variables,
    {
        languageOptions: {
            parserOptions: { // set correct parser 
                ecmaVersion: 'latest',
                sourceType: 'module',
            },
        },
    },
    {
        languageOptions: {
            globals: globals.node,
        },
        plugins: { // re-import plugins separately
            '@stylistic': stylistic, // for bestPracticesConfigWithoutPlugin, errorsConfigWithoutPlugin, es6ConfigWithoutPlugin, styleConfigWithoutPlugin
            import: importPlugin, // for importConfigWithoutPlugin, importsConfigWithoutPlugin
            n: nodePlugin, // nodeConfigWithoutPlugin
        },
        rules: {
            'no-underscore-dangle': ['error', { allow: ['_id', '__dirname', '__type'] }],
        }
    },
];
```
