---
title: ESLint example
sidebar_label: ESLint
---

import AddDepsTabs from '@site/src/components/AddDepsTabs';
import HeadingApiLink from '@site/src/components/Docs/HeadingApiLink';

<HeadingApiLink to="https://github.com/moonrepo/examples/blob/master/.moon/tasks/node.yml#L59" />

In this guide, you'll learn how to integrate [ESLint](https://eslint.org/) into moon.

Begin by installing `eslint` and any plugins in your root. We suggest using the same version across
the entire repository.

<AddDepsTabs dep="eslint eslint-config-moon" dev />

## Setup

Since linting is a universal workflow, add a `lint` task to [`.moon/tasks/**/*`](../../config/tasks)
with the following parameters.

```yaml title=".moon/tasks/eslint.yml"
tasks:
  lint:
    command:
      - 'eslint'
      # Support other extensions
      - '--ext'
      - '.js,.jsx,.ts,.tsx'
      # Always fix and run extra checks
      - '--fix'
      - '--report-unused-disable-directives'
      # Dont fail if a project has nothing to lint
      - '--no-error-on-unmatched-pattern'
      # Do fail if we encounter a fatal error
      - '--exit-on-fatal-error'
      # Only 1 ignore file is supported, so use the root
      - '--ignore-path'
      - '@in(4)'
      # Run in current dir
      - '.'
    inputs:
      # Source and test files
      - 'src/**/*'
      - 'tests/**/*'
      # Other config files
      - '*.config.*'
      # Project configs, any format, any depth
      - '**/.eslintrc.*'
      # Root configs, any format
      - '/.eslintignore'
      - '/.eslintrc.*'
```

Projects can extend this task and provide additional parameters if need be, for example.

```yaml title="<project>/moon.yml"
tasks:
  lint:
    args:
      # Enable caching for this project
      - '--cache'
```

### TypeScript integration

If you're using the [`@typescript-eslint`](https://typescript-eslint.io) packages, and want to
enable type-safety based lint rules, we suggest something similar to the official
[monorepo configuration](https://typescript-eslint.io/docs/linting/monorepo).

Create a `tsconfig.eslint.json` in your repository root, extend your shared compiler options (we use
[`tsconfig.options.json`](./typescript)), and include all your project files.

```json title="tsconfig.eslint.json"
{
  "extends": "./tsconfig.options.json",
  "compilerOptions": {
    "emitDeclarationOnly": false,
    "noEmit": true
  },
  "include": ["apps/**/*", "packages/**/*"]
}
```

Append the following inputs to your `lint` task.

```yaml title=".moon/tasks/node.yml"
tasks:
  lint:
    # ...
    inputs:
      # TypeScript support
      - 'types/**/*'
      - 'tsconfig.json'
      - '/tsconfig.eslint.json'
      - '/tsconfig.options.json'
```

And lastly, add `parserOptions` to your [root-level config](#root-level).

## Configuration

### Root-level

The root-level ESLint config is _required_, as ESLint traverses upwards from each file to find
configurations, and this denotes the stopping point. It's also used to define rules for the _entire_
repository.

```js title=".eslintrc.js"
module.exports = {
  root: true, // Required!
  extends: ['moon'],
  rules: {
    'no-console': 'error',
  },

  // TypeScript support
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: 'tsconfig.eslint.json',
    tsconfigRootDir: __dirname,
  },
};
```

The `.eslintignore` file must also be defined at the root, as
[only 1 ignore file](https://eslint.org/docs/user-guide/configuring/ignoring-code#the-eslintignore-file)
can exist in a repository. We ensure this ignore file is used by passing `--ignore-path` above.

```bash title=".eslintignore"
node_modules/
*.min.js
*.map
*.snap
```

### Project-level

A project-level ESLint config can be utilized by creating a `.eslintrc.<json|js|cjs|yml>` in the
project root. This is optional, but necessary when defining rules and ignore patterns unique to the
project.

```js title="<project>/.eslintrc.js"
module.exports = {
  // Patterns to ignore (alongside the root .eslintignore)
  ignorePatterns: ['build', 'lib'],
  // Project specific rules
  rules: {
    'no-console': 'off',
  },
};
```

> The
> [`extends`](https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files)
> setting should **not** extend the root-level config, as ESLint will automatically merge configs
> while traversing upwards!

### Sharing

To share configuration across projects, you have 3 options:

- Define settings in the [root-level config](#root-level). This only applies to the parent
  repository.
- Create and publish an
  [`eslint-config`](https://eslint.org/docs/developer-guide/shareable-configs#using-a-shareable-config)
  or [`eslint-plugin`](https://eslint.org/docs/developer-guide/working-with-plugins) npm package.
  This can be used in any repository.
- A combination of 1 and 2.

For options 2 and 3, if you're utilizing package workspaces, create a local package with the
following content.

```js title="packages/eslint-config-company/index.js"
module.exports = {
  extends: ['airbnb'],
};
```

Within your root-level ESLint config, you can extend this package to inherit the settings.

```js title=".eslintrc.js"
module.exports = {
  extends: 'eslint-config-company',
};
```

> When using this approach, the package must be built and symlinked into `node_modules` _before_ the
> linter will run correctly. Take this into account when going down this path!

## FAQ

### How to lint a single file or folder?

Unfortunately, this isn't currently possible, as the `eslint` binary itself requires a file or
folder path to operate on, and in the task above we pass `.` (current directory). If this was not
passed, then nothing would be linted.

This has the unintended side-effect of not being able to filter down lintable targets by passing
arbitrary file paths. This is something we hope to resolve in the future.

To work around this limitation, you can create another lint task.

### Should we use `overrides`?

Projects should define their own rules using an ESLint config in their project root. However, if you
want to avoid touching many ESLint configs (think migrations), then
[overrides in the root](https://eslint.org/docs/user-guide/configuring/configuration-files#configuration-based-on-glob-patterns)
are a viable option. Otherwise, we highly encourage project-level configs.

```js title=".eslintrc.js"
module.exports = {
  // ...
  overrides: [
    // Only apply to apps "foo" and "bar", but not others
    {
      files: ['apps/foo/**/*', 'apps/bar/**/*'],
      rules: {
        'no-magic-numbers': 'off',
      },
    },
  ],
};
```
