---
title: ESLint Config
---

import { Tabs, TabItem } from "@astrojs/starlight/components";

ESLint Config (hereinafter the "Application") is an `npm` package containing our highly customised set of ESLint rules. Every rule has been explicitly set - there are no default configurations loaded.

## 1. User Documentation

This section is for those interacting with a live instance of the Application.

### 1.1. Installation

To use this linter configuration in your own project, you must be using Node.js. Install the package and the necessary dependency:

<Tabs>
  <TabItem label="npm">
    ```bash
    npm install -D eslint @nhcarrigan/eslint-config
    ```
  </TabItem>
  <TabItem label="yarn">
    ```bash
    yarn add --dev eslint @nhcarrigan/eslint-config
    ```
  </TabItem>
  <TabItem label="pnpm">
    ```bash
    pnpm install --save-dev eslint @nhcarrigan/eslint-config
    ```
  </TabItem>
</Tabs>

:::caution[Warning!]
This package requires ESLint v9 and uses a flat config!
:::

### 1.2. Usage

To begin using this package, you'll need to create an `eslint.config.js` file in the root of your project. To load our configuration exactly as is with no changes, you can copy-paste this snippet:

```js
import NaomisConfig from "@nhcarrigan/eslint-config";

export default [...NaomisConfig];
```

Then, to run it, you'll likely want to define a `package.json` script that covers your files. Our configuration can run against:

- `src/**/*.ts`
- `src/**/*.tsx`
- `e2e/**/*.spec.ts` (These should be Playwright tests)
- `test/**/*.spec.ts` (These should be Vitest tests)

An example script for all of these might be:

```json
{
  "lint": "eslint src e2e test --max-warnings 0"
}
```

### 1.3. Overriding Rules

You may need to tweak some of the rules to fit your needs. It is *highly* recommended that you only adjust rules on a per-file basis. You can extend the configuration by adding your rules *after* you spread our config:

```js
import NaomisConfig from "@nhcarrigan/eslint-config";

export default [
  ...NaomisConfig,
  {
    files: ["test/**/*.spec.ts"],
    rules: {
      "import/no-extraneous-dependencies": "off",
      "max-nested-callbacks": "off",
      "@typescript-eslint/consistent-type-assertions": "off",
      "max-lines-per-function": "off",
    },
  },
];
```

Note that the `files` key allows you to specify which files receive the rules updates. If omitted, the rule changes will apply to *all* of your files (including those that our configuration might not check!).

## 2. Technical Documentation

This section is for those interested in running their own instance of the Application.

This section is not applicable to this project.

## 3. Legal Documentation

This section is for expansions to our legal policies specific to the Application.

This section is not applicable to this project.

## 4. Contributing Documentation

This section is for documentation related to contributing to the Application's codebase.

:::danger[WAIT!]
The addition of new rules or packages MUST be discussed on an issue and approved by the NHCarrigan team *before* a pull request is submitted. Pull requests without a related issue will be systematically closed.
:::

### 4.1. Adding or Updating Rules

When adding new rules to an existing plugin, or changing existing rules, you should find the appropriate file for that plugin within the `src/rules` directory. Each plugin has its own file.

Most of these should be self-explanatory, but there are a couple of caveats:

- The `eslint.ts` file has a `disabledEslintRules` object. This is the **only** place where rules are explicitly turned off, and these are the rules that conflict with an enabled `@typescript-eslint` rule. If you are adding a new `@typescript-eslint` rule that has a documented conflict, be sure to add the relevant `eslint` rule here.
- The `typescriptEslint.ts` file has a `typescriptEslintRulesWithTypes` object. Certain `@typescript-eslint` rules require type definitions to be generated. However, our project structure excludes tests from being compiled (because they are not production code), so those do not get type definitions. As such, rules that *need* type definitions must go in this object so that they can be run only on `src/**/*.ts`.

### 4.2. Adding Plugins

Adding a new plugin is a much more ambitious change. First, ensure that you are using `pnpm` and you install the plugin **as a pinned dependency**:

```bash
pnpm install --save-exact <plugin>
```

Additionally, if the plugin relies on another tool (such as Vitest), add that to the peer dependencies restricted to greater than the current major version.

Once your plugin is installed, you will need to create the rules file in `src/rules` for that plugin. Define a rules object named `<plugin name>Rules`, annotate it as `Linter.RulesRecord`, and export it. This is where your rules will go. HOWEVER:

Before you start adding rules, navigate to the `src/index.ts` file. Import your new rules object, **and** import the newly installed plugin.

Find the appropriate region for the *type* of files your plugin should run on (e.g. TypeScript, Playwright). The plugin will need to be added to the `plugins` property of that object, and here's the catch:

:::tip
The key name you assign your plugin in the `config.plugins` object will become the key prefix for **all of the plugin rules**.

Say you install a package called `@naomi/eslint-plugin-example`, and it has a rule called `test-rule`. You might load the plugin in the config like so:

```json
{
    "example": naomisExamplePlugin
}
```

THIS MEANS THAT ALL RULES FOR THIS PLUGIN ARE PREFIXED WITH `example`! Our `test-rule` would then be added to the new ruleset as `example/test-rule`.
:::

Add your rules to the `rules` property of the same config object you added the plugin to.

If your new plugin should run on multiple file-sets, you'll need to add it to each of the objects for that file set.

### 4.3. Error or Warn?

As a general principle, we want:

- "Error" for rules that should not be violated even in development for temporary testing
- "Warn" for rules that are safe to ignore while working on the code, but should not be checked in to `main`.

There are a couple of caveats:

- Stylistic rules should always be "warn".
- Test rules should always be "warn".

With the exception of the `disabledEslintRules` object mentioned earlier, **no rule should be set to `off`**. We do not load any default configurations, so every rule is off by default.

### 4.4. Adding Tests

If you have not added a new plugin, the tests *should* be flexible enough to continue passing as is. If you HAVE added a new plugin:

- If it is a plugin that falls under the "all rules are warn" category, you'll need to create a `<plugin>.spec.ts` file and assert that all rules are set to "warn". You can refer to `playwright.spec.ts` for an example of this.
- If it is a plugin that runs on all file sets, an assertion for it will need to be added to the `baseProperties` function in `config.spec.ts`.
- If it is a plugin that runs on a subset of files, an assertion for it will need to be added to the appropriate `it` block for the file subset configs.

In any case, you will likely need to update the number of assertions and the length of the plugins objects.

### 4.5. Testing Changes

Your very first step is to confirm that `pnpm lint` works. This will start by compiling the TypeScript so it can load the updated configuration.

It will then run the new linter configuration *against* the linter project. If your configuration *works*, you should either see a successful run, or a failed run with specific linter issues.

If you instead see a failed run with ESlint errors (not linter errors, but package errors), your configuration has failed to parse and will need to be adjusted.

Once that has passed successfully (including resolving any linter flags), you can proceed to running `pnpm test` to ensure the existing unit tests pass (and any tests you added to cover new plugins).

As always, feel free to reach out to us in our [Discord community](https://chat.nhcarrigan.com) if you have any questions!~
