Flat config: Setting up Eslint and Prettier with eslint-prettier-plugin

Mohamed Lamine Allal
11 min readNov 19, 2023

--

Eslint flat config setup with eslint-prettier-plugin, a great practical example

Overview

  • we are choosing to implement linting using eslint with prettier
    ◌ will show what’s not right following prettier
  • And to use eslint as a formatter
    ◌ through the plugin, it will use prettier
  • Setting up the new flat config 🔥
  • eslint-plugin-prettier vs eslint-config-prettier 🔥

Integration with linters possible ways

Besides eslint and or others

There are two ways

▪️ Disable linter conflicting rules with prettier formatting & run prettier separately

  • So prettier formatting doesn’t affect linter rules
  • Advantage
    ◌ formatting performance generally doesn’t get affected by the linter

▪️ Use the linter to do all through a prettier plugin

  • Make the linter show prettier formatting issues
  • Format through fix this issues
  • or use the linter as formatter (eslint support formatting)
  • Advantages
    ◌ the above
    - one solution
    - seeing formatting mistakes through the linter (red lines)
  • disadvantage
    ◌ at least for eslint
    - people saying less performance
    - personal experience: it’s perfect and it’s my go-to solution

✨ eslint

▪️ Way 1:

  • eslint-config-prettier to disable conflicting rules of eslint
  • use prettier separately
  • use prettier directly, no problems with eslint
    ◌ all work well
  • can be faster or not really against choice 2

▪️ Way 2: ✅ my choice

  • eslint-plugin-prettier
    ◌ use eslint to do all, through the prettier plugin
    ◌ Which does use eslint-config-prettier to disable conflicting rules
    ◌ and does add linting ability
    ◌ and does run prettier for us to fix things

▪️ Way 3:

  • prettier-eslint project
  • While prettier-eslint uses eslint --fix to change the output of prettier, eslint-plugin-prettier keeps the prettier output as-is and integrates it with the regular ESLint workflow.
  • running prettier alone is faster

How to pick

It’s the recommended practice to let Prettier handle formatting and ESLint for non-formatting issues

▪️ prettier-eslint ❌ is not in the same direction as that practice

  • hence prettier-eslint is not recommended anymore.

▪️ You can use eslint-plugin-prettier or eslint-config-prettier ✅

  • eslint-plugin-prettier
    ◌ does use eslint-config-prettier by default in it's recommended configuration

Choose my choice eslint-plugin-prettier . Why ? => u see linting with red when using vscode extension. it's immediate feedback. 🔥🔥

▪️ https://prettier.io/docs/en/integrating-with-linters.html

  • official doc
    ◌ suggesting using prettier alone
    - I don’t agree
  • Some of the reasoning. Which can hold.

The downsides of those plugins are:

- You end up with a lot of red squiggly lines in your editor, which gets annoying. Prettier is supposed to make you forget about formatting — and not be in your face about it!

  • [❗❗❗ I absolutely disagree — see for yourself — I see the red great. Like to see how things got formatted, and immediate feedback …]

- They are slower than running Prettier directly.

  • [❗❗ my experience say! u can’t notice any difference ]
  • And I don’t think it would affect a big project if you set vscode to show problems only per active file.
  • In the worst case, if somehow in a big project, you notice performance issues (I never had any ever). By then, I would use prettier directly.

- They’re yet one layer of indirection where things may break.

  • [🔥 agree but I never had an issue. You configure it once. And all great. Also! if an issue. you can still run prettier directly. So you know which layer it is on. So not really a point.]

Table comparing the 3

eslint-plugin-prettier in flat config 🔥

(eslint-plugin-prettier, flat config)

▪️ How to set extends

doc:

▪️ At the moment of writing [2023–09–04 and then 2023–11–11 (publishing)]

  • There is no flat config support
    ◌ you can check the doc. And you’ll find the old eslintrc config
{
"extends": ["prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error",
"arrow-body-style": "off",
"prefer-arrow-callback": "off"
}
}
  • by going to node_modules/eslint-plugin-prettier/eslint-plugin-prettier.js

we get the recommended config

const eslintPluginPrettier = {
configs: {
recommended: {
extends: ['prettier'],
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error',
'arrow-body-style': 'off',
'prefer-arrow-callback': 'off',
},
},
},
  • it does hold extends and plugins in the old array format
    ◌ not rules only
    ◌ so it’s not compatible with flat config

Two ways to set the equivalent

▪️ Using @eslint/eslintrc package and FlatCompat class

  • FlatCompat utility will help with the translation
    ◌ Doesn’t get affected by any updates
    - will work always

▪️ Understand the recommended config and apply directly [ my choice ✅ ] 🔥

  • In case of an update, we may need to recheck and update as well
    ◌ low probability that would happen
    ◌ choosing to go best with flat config style
    ◌ It’s granular. You add exactly what you need and at exactly the right part of your Flat config

✨ Using @eslint/eslintrc package and FlatCompact class

(@eslint/eslintrc, FlatCompact)

because the config is

{
"extends": ["plugin:prettier/recommended"]
}

adding on the bottom of the array will do

// prettier config all above this one are supposed to use prettier
...compat.extends("plugin:prettier/recommended"),
];

▪️ at the end or after the configs and matched files that do use prettier

  • because eslint-config-prettier is used in extends part of "plugin:prettier/recommended"
    ◌ Which does disable rules of eslint that conflicts with prettier formatting
    - because of disabling 🔥
    - it needs to be last so it would override => disable

It’s nice after u setup

to use eslint output console, for any possible errors

  • let’s disable that one rule
  • and reload the window

And la perfecta

  • it’s working

Total config testing and focusing on js files

import { FlatCompat } from '@eslint/eslintrc';
import globals from 'globals';
import prettierPlugin from 'eslint-plugin-prettier';
import eslintConfigPrettier from 'eslint-config-prettier';
import { jsonFiles } from './.eslintrc.conf.js';
import * as url from 'url';

const __filename = url.fileURLToPath(import.meta.url);
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const compat = new FlatCompat({
baseDirectory: __dirname, // optional; default: process.cwd()
resolvePluginsRelativeTo: __dirname, // optional
});
const jsRules = {
quotes: [
'error',
'single',
{ avoidEscape: true, allowTemplateLiterals: false },
],
// 'import/no-cycle': 'warn',
'class-methods-use-this': 'off',
// 'import/prefer-default-export': 'off',
'comma-dangle': 'off',
'object-curly-newline': 'off',
'operator-linebreak': 'off',
'implicit-arrow-linebreak': 'off',
'function-paren-newline': 'off',
'import/no-extraneous-dependencies': 'off',
'import/extensions': 'off',
'import/no-absolute-path': 'off',
'generator-star-spacing': 'off',
'no-prototype-builtins': 'off',
'no-underscore-dangle': 'off',
'no-plusplus': 'off',
'no-undef': 'warn',
'no-case-declarations': 'warn',
};
/** @type {import('eslint').Linter.FlatConfig[]} */
export default [
// js files
{
files: ['**/*.{mjs,cjs,js}'],
languageOptions: {
globals: {
...globals.es2021,
},
// ecmascriptVersion, and sourceType, default is right
},
// extends: ['plugin:prettier/recommended', 'airbnb-base'],
// plugins: {
// prettier: prettierPlugin
// },
rules: {
// ...eslintPluginPrettier.rules,
...jsRules,
},
},
// json files
// {
// files: jsonFiles,
// parser: 'jsonc-eslint-parser',
// extends: [
// 'plugin:jsonc/recommended-with-jsonc',
// 'plugin:prettier/recommended',
// ],
// plugins: ['prettier'],
// },
// prettier config all above this one are supposed to use prettier
...compat.extends('plugin:prettier/recommended'),
];

✨ Using flat config style, with no FlatCompat

  • We will use objects directly
  • and inject things more adequately (Granular)
  • give us better control
    ◌ Setting configs for the right block, right matching files
    ◌ and no magic like FlatCompat

— —-

The config

— — -

{
"extends": ["plugin:prettier/recommended"]
}
  • So let’s check it node_modules/eslint-plugin-prettier/eslint-plugin-prettier.js
const eslintPluginPrettier = {
configs: {
recommended: {
extends: ['prettier'],
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error',
'arrow-body-style': 'off',
'prefer-arrow-callback': 'off',
},
},
},

— — -

Let’s put it in Flat config

— — -

First the rules

  • rules are allowed in flat config
    ◌ remaining the same

plugin.configs.recommended.rules

In our Eslint config rules prop of js files matching block

rules: {
// ...eslintPluginPrettier.rules,
...jsRules,
...prettierPlugin.configs.recommended.rules
}
  • we add it at the end because the rules are a disabling one
  • And prettier rule is prettier one
    ◌ no overriding issue

Next the extends

  • no extends is allowed in flat config
extends: ['prettier'],
  • that translates to use what there is in eslint-config-prettier
  • so we check what eslint-config-prettier have to offer
  • node_modules/eslint-config-prettier/index.js
  • rules only
  • Checking the actual code source is better than just typing

Anywho. We can see. it just provides rules

  • If it was providing more.
  • We would follow in the same way
  • and add from each extends module what they offer

Let’s add the rule then,

rules: {
...jsRules,
...prettierPlugin.configs.recommended.rules,
...eslintConfigPrettier.rules
},

▪️ We add it at the end again,

  • because it’s a disabling rules set🔥
    ◌ meant to remove eslint rules that conflict with prettier formatting
    ◌ last => override => disable

👉 Quote of the day

When you understand things => it’s easy to reason

— — -

Next the plugins

— — -

plugins: ['prettier'],
  • plugins => prefix with eslint-plugin- => eslint-plugin-prettier
  • our module
  • So now it comes to how the plugins are configured in Flat config
plugins: ["jsdoc"],

=>

plugins: {
jsdoc: jsdoc
},

▪️ usage as above is straightforward

▪️ What about creating a plugin

Now we can load our plugin and check

▪️ if you are fully new

  • you would expect it to work or not
  • in case it doesn’t
    ◌ you know what to check
    ▶︎ in this case https://eslint.org/docs/latest/extend/plugins
    - processor part
    - speaking surely, if the plugin in question doesn’t mention flat config in the doc yet

Let’s do it

adding

import prettierPlugin from 'eslint-plugin-prettier';
// ...

plugins: {
prettier: prettierPlugin
},
  • That should do it. The whole config is
/** @type {import('eslint').Linter.FlatConfig[]} */
export default [
// js files
{
files: ['**/*.{mjs,cjs,js}'],
languageOptions: {
globals: {
...globals.es2021,
},
// ecmascriptVersion, and sourceType, default is right
},
// extends: ['plugin:prettier/recommended', 'airbnb-base'],
plugins: {
prettier: prettierPlugin
},
rules: {
...jsRules,
...prettierPlugin.configs.recommended.rules,
...eslintConfigPrettier.rules
},
},
// json files
// {
// files: jsonFiles,
// parser: 'jsonc-eslint-parser',
// extends: [
// 'plugin:jsonc/recommended-with-jsonc',
// 'plugin:prettier/recommended',
// ],
// plugins: ['prettier'],
// },
// prettier config all above this one are supposed to use prettier
// ...compat.extends('plugin:prettier/recommended'),
];
  • cleaner
/** @type {import('eslint').Linter.FlatConfig[]} */
export default [
// js files
{
files: ['**/*.{mjs,cjs,js}'],
languageOptions: {
globals: {
...globals.es2021,
},
// ecmascriptVersion, and sourceType, default is right
},
plugins: {
prettier: prettierPlugin
},
rules: {
...jsRules,
...prettierPlugin.configs.recommended.rules,
...eslintConfigPrettier.rules
},
}
];
  • jsRules
    ◌ my rules for js, you can have yours
const jsRules = {
quotes: [
'error',
'single',
{ avoidEscape: true, allowTemplateLiterals: false },
],
// 'import/no-cycle': 'warn',
'class-methods-use-this': 'off',
// 'import/prefer-default-export': 'off',
'comma-dangle': 'off',
'object-curly-newline': 'off',
'operator-linebreak': 'off',
'implicit-arrow-linebreak': 'off',
'function-paren-newline': 'off',
'import/no-extraneous-dependencies': 'off',
'import/extensions': 'off',
'import/no-absolute-path': 'off',
'generator-star-spacing': 'off',
'no-prototype-builtins': 'off',
'no-underscore-dangle': 'off',
'no-plusplus': 'off',
'no-undef': 'warn',
'no-case-declarations': 'warn',
};

Let’s test

  • reload window
  • eslint server starting with no issue
    ◌ config OK ✅
  • let’s check on a file
  • And bingo, working, perfect ✅

How do you name the plugins

▪️ Eslint: FlatCompat utility and its work and magic, a deep dive that demystifies Flat config and translation from eslintrc

  • Check the resume (lessons) part

▪️ eslint-plugin-{name}

  • name {name}

▪️ @{scope}/eslint-plugin

  • name @{scope}

▪️ Otherwise if @{scope}/eslint-plugin-{some_name}

  • name @{scope}/{some_name}

Examples:

  • eslint-plugin-prettier
    ◌ name prettier
  • @typescript-eslint/eslint-plugin
    ◌ name @typescript-eslint
  • For the details check my write-up on the exploration of FlatCompat eslintrc translation tool
    Deep dive Article

Which way should you pick up flat config or FlatCompat helpers

▪️ Your choice

  • whatever works

▪️ But for me

  • I wanted to understand well how things work
    ◌ we resumed this here in this article
  • And also, I want granular control, with no magic behind the scene
  • I want to insert things in a specific block or blocks
  • Without needing to have eslint do the merges
  • Still, either way, we can manage well

FlatCompat

▪️ You have to use eslint merging

▪️ Set your plugins either after or above the one, depending on the plugins settings and what they do

  • disabling properties plugins
    ◌ generally comes after (after to override and disable the one before)
  • activating and adding rules
    ◌ they come before
    ◌ to make them a base
  • So either
    ◌ you make a config a base
    ◌ Or as an overriding one
    ◌ decide depending on the plugin and the logic is simple
    -
    does it need to override
    - or act as base
    order your configs in the right order

What about if you want to do some things for just some files 🔥

✨ What is the magic or the output of FlatCompact

  • The output is basically multiple flat config objects that get appended to the object. You need to be careful and to understand relatively the overriding.

✨ So, how do we do it?

  • Mainly if you need to apply a config only to some files using FlatCompat
    ◌ The solution is to use overrides in the eslintrc config
[
// ...,
...flatCompat.config({
"overrides": [
{
"files": [
"src/**/*.{ts,mts,cts,tsx}"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"airbnb-base",
"airbnb-typescript/base"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["./tsconfig.json"],
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"rules": {
"quotes": [
"error",
"single",
{ "avoidEscape": true, "allowTemplateLiterals": false }
],
"import/no-cycle": "warn",
"class-methods-use-this": "off",
"import/prefer-default-export": "off",
"comma-dangle": "off",
"no-underscore-dangle": "off",
"no-prototype-builtins": "off",
"object-curly-newline": "off",
"operator-linebreak": "off",
"implicit-arrow-linebreak": "off",
"function-paren-newline": "off",
"import/no-extraneous-dependencies": "off",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/comma-dangle": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/indent": "off",
"@typescript-eslint/no-loop-func": "off"
}
},
// JAVASCRIPT files configuration
{
"files": [
"./jest.config.ts"
],
"parserOptions": {
"project": ["./jest.tsconfig.json"],
"ecmaVersion": "latest",
"sourceType": "module"
}
},
{
"files": [
"e2e-tests/**/*.ts"
],
"parserOptions": {
"project": ["./e2e-tests.tsconfig.json"],
"ecmaVersion": "latest",
"sourceType": "module"
}
},
{
"files": [
"e2e-tests/**/*.test.js"
],
"env": {
"jest": true,
"jasmine": true
}
}
]
})
]
  • You got the idea
  • FlatCompat does manage the overrides
  • Personally, I would always prefer the granular 100% FlatConfig way ✅ 🔥

My other Eslint Prettier articles

Helpful

--

--

Mohamed Lamine Allal
Mohamed Lamine Allal

Written by Mohamed Lamine Allal

Developper, Entrepreneur, CTO, Writer! A magic chaser! And A passionate thinker! Obsessed with Performance! And magic for life! And deep thinking!