Skip to content

About

Opinionated eslint configs

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

@moso/eslint-config

npm coverage license

Flat ESLint config for JavaScript, TypeScript, Vue, React, and more.

Features

  • ESLint Flat config with reasonable but opinionated defaults
  • Stylistic with single quotes, semi enabled, sorted imports, and dangling commas
  • Aimed to be used without Prettier
  • Designed to work with JSX, TypeScript, Vue, React, Astro, Nuxt, and Next.js out of the box
  • Applies Functional, Perfectionist, and e18e by default
  • Lints for JSDoc, JSON, RegEx, TOML, and YAML
  • Ships its own small @moso-ruleset with security and hygiene rules
  • Every config can be enabled/disabled
  • Respects .gitignore by default
  • Requires Node.js >= v22.22.3/v24.x, ESLint v10+
  • Enhanced security by Socket's Bun Security Scanner

Note

Note regarding TypeScript v7 See the FAQ

Configs

This section contains a list of the plugins used in the named configs that ship with this shareable config.

- ✅ Enabled by default
- ☑️ Enabled by auto-detect
- 🌟 Enabled unless lessOpinionated: true
- 🟨 Can be enabled manually
- 🎨 Stylistic rules enabled
- 💭 Type-aware rules available by setting the path to your tsconfig.json
- ℹ️ Accessibility rules available with a11y: true

Plugin 💼 🎨 💭
Astro

eslint-plugin-astro, astro-eslint-parser, eslint-plugin-jsx-a11y

☑️ℹ️ 🎨
Baseline

eslint-plugin-baseline-js

✅ 💭
e18e

@e18e/eslint-plugin

🌟
ESLint Comments

eslint-plugin-eslint-comments

✅
Functional

eslint-plugin-functional

🌟 🎨
Import Lite

eslint-plugin-import-lite

✅ 🎨
JavaScript

@eslint/js, eslint-plugin-de-morgan, eslint-plugin-unused-imports

✅
JSDoc

eslint-plugin-jsdoc

🟨 🎨
JSONC

eslint-plugin-jsonc

🟨 🎨
JSX

@stylistic/eslint-plugin, eslint-plugin-jsx-a11y

✅ℹ️ 🎨
Next.js

@next/eslint-plugin-next

☑️
Node

eslint-plugin-n

✅
Perfectionist

eslint-plugin-perfectionist

🌟
Promises

eslint-plugin-promise

✅
React

@eslint-react, @eslint-react/dom, @eslint-react/hooks-extra, @eslint-react/naming-convention, @eslint-react/web-api, eslint-plugin-react-hooks, eslint-plugin-react-you-might-not-need-an-effect, eslint-plugin-jsx-a11y

☑️ℹ️ 🎨 💭
RegExp

eslint-plugin-regexp

✅
Stylistic

@stylistic/eslint-plugin

✅ 🎨
TailwindCSS

eslint-plugin-better-tailwindcss

☑️ 🎨
Test

@vitest/eslint-plugin, eslint-plugin-no-only-tests

✅
TOML

eslint-plugin-toml

✅ 🎨
TypeScript

@typescript-eslint, eslint-plugin-erasable-syntax-only

☑️ 🎨 💭
Unicorn

eslint-plugin-unicorn

✅
Vue

eslint-plugin-vue, eslint-plugin-vuejs-accessibility

☑️ℹ️ 🎨 💭
YAML

eslint-plugin-yml

✅ 🎨

Custom rules

This config ships its own small plugin, registered as @moso. Each rule has full documentation next to its implementation.

Rule Description Default Fixable
avoid-barrel-files Disallow authoring barrel files ✅
no-bidi Detect and stop trojan source attacks ✅ 🔧
no-force-cast-via-top-type Disallow T as any as Q casts ✅
no-import-duplicates Fix duplicate specifiers within an import ✅ 🔧
no-import-from-dist Prevent importing from dist folders ✅
no-import-node-modules-by-path Prevent importing node_modules by path ✅
no-invisible-characters Disallow invisible characters ✅ 🔧
no-redundant-variable Disallow variables that are immediately returned 🔧
no-string-interpolation Disallow multiline expressions in template literals
no-top-level-await Prevent top-level await ✅
no-unneeded-array-flat-map Disallow flatMap with identity callbacks 🔧
prefer-early-return Prefer guard clauses over wrapped function bodies 🔧
prefer-fetch Enforce fetch over legacy HTTP clients
prefer-reduce-over-chaining Prefer one .reduce() pass over .map().filter() chains ✅
prefer-strict-number-guards Enforce the syntactic patterns that keep numeric code analyzable

Usage

Install

# npm
npm install -D eslint @moso/eslint-config

# pnpm
pnpm add -D eslint @moso/eslint-config

# bun
bun add -d eslint @moso/eslint-config

Create an eslint.config.* in the root of your project. You can use .js or .ts; .mjs/.cjs also works.

Note

If you use .ts, you will need to install jiti as a developer dependency:

# npm
npm install -D jiti

# pnpm
pnpm add -D jiti

# bun
bun add -d jiti
// eslint.config.*
import moso from '@moso/eslint-config';

export default moso();
Combine with legacy config:

If you still use some configs from the legacy .eslintrc format, you can use the @eslint/eslintrc package to convert them to the flat config.

// eslint.config.*
import moso from '@moso/eslint-config';
import { FlatCompat } from '@eslint/eslintrc';

const compat = new FlatCompat()

export default moso(
    {
        ignores: [],
    },

    // Legacy config
    ...compat.config({
        extends: [
            'eslint:recommended',
            // Other extends...
        ],
    })

    // Other flat configs...
);

[!NOTE] .eslintignore no longer works in Flat config, see customization for more details.

Add script for package.json

For example:

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

VS Code support (auto fix)

Details

Install VS Code ESLint extension

Add the following settings to your .vscode/settings.json:

{
  // Disable the default formatter, use eslint instead
  "prettier.enable": false,
  "editor.formatOnSave": false,

  // Auto fix
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit",
    "source.organizeImports": "never"
  },

    // You can silence specific rules in your IDE, but still auto fix them
  "eslint.rules.customizations": [
    { "rule": "@stylistic/*", "severity": "off", "fixable": true },
    { "rule": "*-indent", "severity": "off", "fixable": true },
    { "rule": "*-spacing", "severity": "off", "fixable": true },
    { "rule": "*-spaces", "severity": "off", "fixable": true },
    { "rule": "*-order", "severity": "off", "fixable": true },
    { "rule": "*-dangle", "severity": "off", "fixable": true },
    { "rule": "*-newline", "severity": "off", "fixable": true },
    { "rule": "*quotes", "severity": "off", "fixable": true },
    { "rule": "*semi", "severity": "off", "fixable": true }
  ],

  // Enable eslint for all supported languages
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact",
    "vue",
    "json",
    "jsonc",
    "yaml"
  ]
}

Customization

Since v1.0.0, this config has been migrated to ESLint Flat config. It provides much better organization and composition. And speed!

Normally you only need to import the moso preset:

// eslint.config.*
import moso from '@moso/eslint-config';

export default moso();

Configuration Options

Configure integrations by passing options to the main function:

// eslint.config.*
import moso from '@moso/eslint-config';

export default moso({
    // Stylistic rules (enabled by default)
    stylistic: false, // disable entirely
    stylistic: {
        indent: 4, // 2, or 'tab'
        jsx: true, // or false to disable JSX support
        quotes: 'single', // or 'double'
        semi: true, // or false
    },

    // Auto-detected, or enable explicitly
    astro: true,
    react: true,
    typescript: true,
    vue: true,

    // JavaScript Baseline (enabled by default, reported as warnings)
    baseline: false, // disable entirely
    baseline: 'newly', // 'widely' (default), 'newly', or a year like 2023
    baseline: {
        baseline: 'widely',
        ignoreFeatures: [], // skip web-features by ID, supports RegExp via '/.../'
    },

    // JSDoc support
    jsdoc: true, // enable explicitly with reasonable defaults

    // File format support
    jsonc: false,
    yaml: false,

    // Replaces .eslintignore (no longer supported)
    ignores: [
        '**/dist',
        '**/node_modules',
    ],
});

Overriding Rules

Pass additional flat config objects as arguments. Rules are scoped to specific file types:

// eslint.config.*
import moso from '@moso/eslint-config';

export default moso(
    {
        // First argument: moso config options
        vue: true,
        typescript: true,
    },
    {
        // Additional arguments: standard ESLint flat configs
        files: ['**/*.vue'],
        rules: {
            'vue/multi-word-component-names': ['error', { ignores: [] }],
        },
    },
    {
        // You can pass multiple config objects
        files: ['**/*.ts'],
        rules: {
            '@typescript-eslint/no-explicit-any': 'warn',
        },
    },
);

Advanced Composition

The config returns a FlatConfigComposer object from eslint-flat-config-utils, enabling chainable methods:

// eslint.config.*
import moso from '@moso/eslint-config';

export default moso()
    .prepend({
        // Configs before the main config
    })
    .override('moso/javascript/rules', {
        rules: {
            'no-var': 'off',
        },
    })
    .removeRules(
        'no-console',
        'no-debugger',
    );
Fine-grained config imports

You can import and compose individual configs directly. Only use this if you need granular control:

import {
    combine,
    comments,
    ignores,
    imports,
    javascript,
    jsdoc,
    jsonc,
    node,
    sortPackageJson,
    sortTsconfig,
    stylistic,
    typescript,
    unicorn,
    vue,
    yaml,
} from '@moso/eslint-config';

export default combine(
    comments(),
    ignores(),
    imports(),
    javascript(/* options */),
    jsdoc(),
    jsonc(),
    node(),
    stylistic(/* options */),
    typescript(/* options */),
    unicorn(),
    vue(/* options */),
    yaml(),
);

Optional configs

JSDoc

Disabled by default, will have to be enabled manually.

There are many ways JSDoc support can be configured. However, the most simple way is just enabling it which will use very reasonable defaults:

// eslint.config.*
import moso from '@moso/eslint-config';

export default moso({
    jsdoc: true,
});

This will enable the preset recommended, which is recommended starting rules for enforcing proper tag values, common tags exist, and tags are formatted and styled consistently.

Individual rules can be tweaked with the overrides property:

{
    jsdoc: {
        overrides: {
            'jsdoc/require-description': 'off',
        },
    },
}

Frameworks

Framework support is auto-detected based on installed packages, but can be enabled explicitly. Dependencies are not bundled and will be prompted for installation.

Note

Note: Framework support is just as opinionated as the rest of the config. If your favorite framework is missing, then you can always extend the config yourself.

Astro

Auto-detected if you have Astro installed. Enable explicitly:

// eslint.config.*
import moso from '@moso/eslint-config';

export default moso({
    astro: true,
});

Install dependencies when prompted, or manually:

# npm
npm install -D eslint-plugin-astro astro-eslint-parser

# pnpm
pnpm add -D eslint-plugin-astro astro-eslint-parser

# Bun
bun add -d eslint-plugin-astro astro-eslint-parser

Note

Since Astro can be plugged with other frameworks as well, you'll be prompted to install the required packages for linting those as well. Example: If you have installed @astrojs/react, you'll be prompted to install the required packages to lint React.

React

Auto-detected if you have React, Next.js, Nextra, Remix, Gatsby, or the @astrojs/react Astro framework integration installed. Enable explicitly:

// eslint.config.*
import moso from '@moso/eslint-config';

export default moso({
    react: true,
});

Install dependencies when prompted, or manually:

# npm
npm install -D @eslint-react/eslint-plugin eslint-plugin-react-hooks eslint-plugin-react-refresh eslint-plugin-react-you-might-not-need-an-effect

# pnpm
pnpm add -D @eslint-react/eslint-plugin eslint-plugin-react-hooks eslint-plugin-react-refresh eslint-plugin-react-you-might-not-need-an-effect

# Bun
bun add -d @eslint-react/eslint-plugin eslint-plugin-react-hooks eslint-plugin-react-refresh eslint-plugin-react-you-might-not-need-an-effect

Vue

Auto-detected if you have Vue, Nuxt, VitePress, or the @astrojs/vue Astro framework integration installed. Enable explicitly:

// eslint.config.*
import moso from '@moso/eslint-config';

export default moso({
    vue: true,
});

Install dependencies when prompted, or manually (the Vue parser and SFC processors are already bundled as dependencies):

# npm
npm install -D eslint-plugin-vue

# pnpm
pnpm add -D eslint-plugin-vue

# Bun
bun add -d eslint-plugin-vue

Caution

Since Vue 2 has reached EOL, this config does not support Vue 2. If you need support for Vue 2, you'll need to disable the imported configs from Vue 3, and replace them with the Vue 2 ones. You can see an inspiring example on eslint-plugin-vue. I recommend upgrading to Vue 3 if possible.

Typed Linting

You can optionally enable typed linting. These are also known as "rules that require types", or simply "type-aware rules". This enables for much deeper insight into your code.

You enable them by passing your project's root directory to the projectRoot option inside typescript.

Warning

Enabling these rules will come with a slight performance cost, explained here.

To make things even more complicated, if you have enabled typed linting but disabled @stylistic, it will also disable the type-aware rules considered stylistic.

// eslint.config.*
import moso from '@moso/eslint-config';

export default moso(
    {
        typescript: {
            projectRoot: import.meta.dirname,

            // If you wish to override any of these rules, use `overridesTypeAware`:
            overridesTypeAware: {
                '@typescript-eslint/no-deprecated': 'off',
            },
        },
    },
);

Note

TypeScript will still be auto-detected if you have the typescript-package installed, and any non-type-aware @typescript-eslint-rules will still be applied. Typed Linting is considered as an extra option.

Disable type-aware

You can disable the type-aware layer entirely, or exclude certain globs or specific files from it:

// eslint.config.*
import moso from '@moso/eslint-config';

export default moso({
    typescript: {
        projectRoot: import.meta.dirname,

        // Kill switch: keep regular TypeScript rules, drop the type-aware layer
        disableTypeAwareRules: true,

        // ...or keep it, but exclude globs or specific files from it
        ignoresTypeAware: [
            '**/*.{js,jsx}',
            '**/components/User.tsx',
        ],
    },
});

Editor specific non-fixes

Some rules are deemed as 'non-fixable' when inside your editor with ESLint integration:

Before v1.0.0, they used to be hard disabled. But with a helper, they are now just marked as 'non-fixable'. They are re-applied when you're linting through a terminal, or by using Lint Staged.

// eslint.config.*
import moso from '@moso/eslint-config';

export default moso({
    isInEditor: false,
});

Lint Staged

Linting and auto-fixing before every commit is easy, you just add the following to your package.json:

{
    "simple-git-hooks": {
        "pre-commit": "npx lint-staged" // or pnx/bunx
    },
    "nano-staged": {
        "*": "eslint --fix"
    }
}

and then

# npm
npm install -D nano-staged simple-git-hooks

# pnpm
pnpm add -D nano-staged simple-git-hooks

# Bun
bun add -d nano-staged simple-git-hooks


# to activate the hooks:
npx simple-git-hooks # npm

pnx simple-git-hooks  # pnpm

bunx simple-git-hooks # bun

FAQ

ESLint v9, older Node.js versions?

ESLint v9 reached EOL on August 6th, 2026. The world of development moves fast, and as with Vue v2.x, I don't intend to support versions that have reached EOL unless absolutely necessary. The same goes with older Node.js versions.

Even though ESLint v10 supports Node.js v20.19.0 (as of current status), this version has also reached EOL. The rest of their requirements mention v22.13.0 and >= 24. However, the minimum requirements of some of the configs that this sharable config apply rules from have a minimum requirement of v22.22.3, this project will stay within this range.

TypeScript 7?

TypeScript 7.0 does not ship a compiler API (a new one supposedly arrives in 7.1), so typescript-eslint - and therefore all TypeScript linting in this config - cannot run against it. TypeScript's supported setup is running TypeScript 6 side-by-side through the @typescript/typescript6 compatibility package, aliased so tooling that imports typescript keeps working:

# npm
npm install -D typescript@npm:@typescript/typescript6

# pnpm
pnpm add -D typescript@npm:@typescript/typescript6

# Bun
bun add -d typescript@npm:@typescript/typescript6

Optionally keep TypeScript 7's own tsc around as well:

{
    "devDependencies": {
        "@typescript/native": "npm:typescript@^7.0.2",
        "typescript": "npm:@typescript/typescript6@^6.0.2"
    }
}

With the alias in place, this config works unchanged. Without it, the config detects TypeScript >7 and fails fast: an explicit typescript: true throws with these instructions, while auto-detection prints a warning and degrades to JavaScript-only linting instead of crashing inside the parser.

Keep in mind, this is temporary until an API is provided by TypeScript for v7.

Note

As per v8.65.0, @typescript-eslint will also display a warning if TypeScript v7 is detected.

I want it less opinionated

No problem. I've extracted the things that I've deemed very opinionated in each integration, and made a setting that helps you disable all of it in one go.

// eslint.config.*
import moso from '@moso/eslint-config';

export default moso({
    lessOpinionated: true,
});

Note

The above will also disable functional and perfectionist completely. If you want to keep these enabled, you'll have to re-enable them explicitly, as demonstrated below

{
    // Disable opinionated rules
    lessOpinionated: true,

    // Re-enable functional - a string sets the enforcement level directly
    functional: 'lite', // 'lite' | 'recommended' | 'strict'

    // ...or as an object:
    functional: {
        functionalEnforcement: 'recommended',
    },

    // Re-enable `perfectionist`
    perfectionist: true,
}

Prettier? dprint?

You can still use these to format files that aren't linted with this config, however, I strongly recommend you only format your code with ESLint, as Prettier and other AST-reading-then-reprint projects tend to ignore stuff like the original line breaks and might also cause inconsistent diffs when committing code.

How to format CSS?

You will need to install and configure stylelint yourself, unfortunately.

v3.2.0 added linting support for TailwindCSS, however.

I prefer this or that rule

No worries, you can always override the rules locally in your project to fit your needs. If that doesn't cut it, you're welcome to fork this project and maintain your own config.

Inspiration

This ESLint config is heavily inspired by (and uses some of the same logic of):

- @antfu/eslint-config
- @rebeccastevens/eslint-config
- @eslint-sukka/eslint-config

Anthony Fu's config inspired me to take the journey, but this project has since evolved into a personal project. Especially Rebecca's config and strict(er) approach to coding has pushed me towards a stricter coding environment, with Functional and Azat's Perfectionist and De Morgan enabled.

A personal thank-you to Anthony, Rebecca, Sukka, and Azat, and everyone else who contributed to their projects.

Some rules are the same, however, there are some differences:

  • My own opinionated rules added
  • Stylistic, Perfectionist, and Functional enabled per default
  • More freedom to disable every individual config, or only disable the opinionated parts
  • Deprecated Vue 2 support
  • Simplification in some areas
  • Better type-aware checks
  • Plugin memoization
  • Optional a11y support for JSX, React and Vue
  • No dangerous plugin renaming (except for node)

License

BSD 3-Clause License

About

Opinionated eslint configs

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages