# @wise/eslint-config

A modular ESLint config used at [Wise](https://wise.com/).

[![npm (scoped)](https://img.shields.io/npm/v/@wise/eslint-config)](https://www.npmjs.com/package/@wise/eslint-config)

## Usage

1. Install the package as a dev dependency along with ESLint:

   ```sh
   pnpm add --save-dev @wise/eslint-config
   ```

2. Extend your ESLint configuration as follows:

   ```js
   // .eslint.config.js
   import baseStrict from '@wise/eslint-config/base-strict';
   import react from '@wise/eslint-config/react';
   import next from '@wise/eslint-config/next';
   import jest from '@wise/eslint-config/jest';
   import cypress from '@wise/eslint-config/cypress';
   import storybook from '@wise/eslint-config/storybook';
   import testingLibrary from '@wise/eslint-config/testing-library-react';

   export default [
     /* Recommended for all projects, includes TypeScript rules. */
     ...baseStrict, // you can import from '@wise/esltin-config/base' instead

     /* Uncomment lines in the order below as you adopt each technology */
     // ...react,
     // ...next,
     // ...jest,
     // ...cypress,
     // ...storybook,
     // ...testingLibrary,
   ];
   ```

3. If you’re using a monorepo with TypeScript, please set `parserOptions.projectService = true` [as per the docs of typescript-eslint](https://typescript-eslint.io/troubleshooting/typed-linting/monorepos/).

## Script setup

Set up your package.json scripts to run the linting on the CLI. We recommend adhering to [ESLint’s package.json conventions](https://eslint.org/docs/latest/contribute/package-json-conventions) for naming scripts.

```jsonc
// package.json
{
  "scripts": {
    /* TypeScript-specific logic may be enabled as hinted by comments below */
    "lint": "pnpm run lint:js+ts && pnpm run lint:format", // && pnpm run lint:types
    "lint:js+ts": "eslint '**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}'",
    "lint:format": "prettier \"**/*\" --check --ignore-unknown",
    // "lint:types": "tsc --noEmit",
    "lint:fix": "pnpm run lint:fix:js+ts && pnpm run lint:fix:format",
    "lint:fix:js+ts": "pnpm run lint:js+ts --fix",
    "lint:fix:format": "prettier \"**/*\" --write --ignore-unknown",
  },
}
```

## Compatibility with Prettier

Stylistic linting rules which may conflict with Prettier are turned off.

We encourage developers to **use Prettier’s opinionated defaults** with no additional configuration.

## Lint and format on commit

1. Install packages as follows:

   ```sh
   pnpm add --save-dev husky nano-staged
   ```

2. Configure scripts to be run on staged files:

   ```jsonc
   // package.json
   "nano-staged": {
     "*": "prettier --write --ignore-unknown",
     "*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}": "eslint --fix",
   }
   ```

3. Configure git hooks via Husky:

   ```sh
   pnpm exec husky
   echo "pnpm exec nano-staged" > .husky/pre-commit
   npm pkg set scripts.prepare="husky || cd ."
   ```

   _The `|| cd .` part sets the exit code to zero [in a cross-platform manner](https://stackoverflow.com/a/62817845) when `husky` isn’t found, e.g. when `devDependencies` aren’t installed._

## Auto-fix on save in your IDE

### VS Code

Extend your editor configuration as follows:

```jsonc
// .vscode/settings.json
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit",
  },
}
```

## Contributing

If you think a rule should be added or changed, please create a pull request. Releases are managed via [Changesets](https://github.com/changesets/changesets) and follow [Semantic Versioning](https://semver.org/) conventions.

## Troubleshooting

**Rules of our configs shouldn’t be turned off or overridden in general.** When encountering a false-positive, please disable it via `eslint-disable-next-line` or `eslint-disable` and consider filing an issue to the Web Guild.

### Import issues

The first time you integrate with the config, you might get a lot of import issues.

```
  1:1  warning  Run autofix to sort these imports!  simple-import-sort/imports
```

This error is auto-fixable using the `--fix` option.

#### Unable to resolve path to module (`import/no-unresolved`)

When trying to import from a `.d.ts` file, `import type` should be used over `import`, e.g.:

```diff
- import { AlertType } from "@transferwise/components/build/types/alert/Alert";
+ import type { AlertType } from "@transferwise/components/build/types/alert/Alert";
```

Furthermore, setting [`verbatimModuleSyntax` in TSConfig](https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax) is recommended to avoid these issues and deal with side-effects explicitly.

#### Using TypeScript

When using multiple configuration files in `tsconfig.json`, e.g. `extends: ["array", "of", "tsconfigs"]`, the following error may occur:

> TypeError: The "path" argument must be of type string. Received an instance of Array

This happens because [eslint-import-resolver-typescript](https://github.com/import-js/eslint-import-resolver-typescript) doesn’t support the array `extends` syntax introduced by TypeScript 5.

Please don’t extend from multiple TSConfigs at this time.

### Parsing error

When linting JavaScript without types, the following error may occur:

> You appear to be using a native ECMAScript module configuration file, which is only supported when running Babel asynchronously

This happens if your [Babel configuration](https://babeljs.io/docs/configuration#javascript-configuration-files) uses ECMAScript module syntax. By migrating to JSON or using CommonJS, the issue can be mitigated, e.g.:

```diff
  // babel.config.js
- export default { /* … */ };
+ module.exports = { /* … */ };
```

### ESLint couldn't determine the plugin uniquely

Sometimes, if you integrate, you might see an error similar to this

```
Oops! Something went wrong! :(

ESLint: 9.13.0

ConfigError: Config (unnamed): Key "plugins": Cannot redefine plugin "import".
...
```

Most of the time, removing the offending plugin (in this case, `import`) in your config file should solve the issue.

What this means is that the said plugin is already imported by a previous config. Importing them again will cause the issue.
