Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-config): add full support + migration guides #39

Merged
merged 1 commit into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ coverage
out
build
dist
.next

# Misc
.DS_Store
Expand Down
9 changes: 5 additions & 4 deletions apps/backend/app.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Multipart } from "@fastify/multipart";
import fastify, { FastifyHttpOptions, FastifyRequest } from "fastify";
import { ZodTypeProvider } from "fastify-type-provider-zod";
import * as http from "node:http";
import type * as http from "node:http";
import type { Multipart } from "@fastify/multipart";
import type { FastifyHttpOptions, FastifyRequest } from "fastify";
import fastify from "fastify";
import type { ZodTypeProvider } from "fastify-type-provider-zod";
import { z } from "zod";
import { basePlugin } from "./plugins/base.js";

Expand Down
5 changes: 3 additions & 2 deletions apps/backend/plugins/base.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import assert from "node:assert/strict";
import * as fs from "node:fs";
import { test } from "node:test";

import { buildApp } from "../app.js";

void test("base", async (t) => {
Expand All @@ -19,7 +18,7 @@ void test("base", async (t) => {
form.append("foo", "bar");
form.append("file1", await fs.openAsBlob("package.json"), "package.json");

const response = await fetch(server + "/base/multipart", {
const response = await fetch(`${server}/base/multipart`, {
method: "post",
body: form,
});
Expand Down Expand Up @@ -47,6 +46,7 @@ void test("base", async (t) => {
});

assert.equal(response.statusCode, 200);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
assert.equal(response.json().healthChecks.label, "HEALTHY");
});

Expand All @@ -58,6 +58,7 @@ void test("base", async (t) => {
});

assert.equal(response.statusCode, 500);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
assert.equal(response.json().healthChecks.label2, "FAIL");
});
});
Expand Down
17 changes: 7 additions & 10 deletions apps/backend/plugins/base.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import fastifyMultipart, { FastifyMultipartBaseOptions } from "@fastify/multipart";
import { RegisterOptions } from "fastify";
import fastifyCustomHealthCheck from "fastify-custom-healthcheck";
import fastifyHelmet from "@fastify/helmet";

import type { FastifyMultipartBaseOptions } from "@fastify/multipart";
import fastifyMultipart from "@fastify/multipart";
import type { RegisterOptions } from "fastify";
import fastifyCustomHealthCheck from "fastify-custom-healthcheck";
import fp from "fastify-plugin";
import {
serializerCompiler,
validatorCompiler,
ZodTypeProvider,
} from "fastify-type-provider-zod";
import { FastifyBase } from "../types.js";
import type { ZodTypeProvider } from "fastify-type-provider-zod";
import { serializerCompiler, validatorCompiler } from "fastify-type-provider-zod";
import type { FastifyBase } from "../types.js";

async function base(
fastify: FastifyBase,
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import closeWithGrace from "close-with-grace";
import { buildApp } from "./app.js";
import dotenv from "dotenv";
import { buildApp } from "./app.js";

dotenv.config({ path: [".env.local", ".env"] });

Expand Down
2 changes: 1 addition & 1 deletion apps/backend/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type http from "node:http";
import type { FastifyInstance } from "fastify";
import http from "node:http";

export type FastifyBase = FastifyInstance<
http.Server,
Expand Down
102 changes: 102 additions & 0 deletions packages/eslint-config/MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Migrating to @lightbase/eslint-config

## From @compas/eslint-plugin

Execute the following steps to migrate in a mostly compatible way from
`@compas/eslint-plugin` to this package. The main incompatibilities ares:

- Prefer `Array<>` types in JSDoc over `[]` types.
- Renamed rules like `@compas/event-stop` to `@lightbase/compas-event-stop`.

The migration can be done as follows:

- Remove the `@compas/eslint-plugin` dependency from your package.json.
- Install this package with `npm install --save-dev --exact @lightbase/eslint-config`
- Remove `.eslintrc`, `.eslintignore`, `.prettierignore` and `.prettierrc(.js)` files.
- Remove the `prettier` key from your package.json.
- Remove all existing `lint`, `format` and `pretty` scripts from your package.json.
- Create `eslint.config.js` in the root of your project and paste the below contents.
- Apply the [Commands](./README.md#commands) section from the README.
- Apply the [IDE](./README.md#ide) section from the README.
- Run `npm run lint` and fixup the remaining issues.
- Update your CI scripts to use the `npm run lint:ci` command.

```js
import { defineConfig } from "@lightbase/eslint-config";

export default defineConfig(
{
prettier: {
globalOverride: {
useTabs: false,
printWidth: 80,
},
},
},
{
ignores: ["**/*.d.ts"],
},
);
```

## From (internal) @lightbase/eslint-plugin

In these steps we will be removing the vendored eslint-plugin and use
`@lightbase/eslint-config` instead. The result should give almost the same experience as
before. The main incompatibilities are:

- Import related rules ban CommonJS style `require`'s. Check if the tool supports `.mjs`
config files or add file specific ignores.
- Prettier is enabled with
[`experimentalTernaries`](https://prettier.io/blog/2023/11/13/curious-ternaries)

The migration can be done by following these steps:

- Remove the `vendor/eslint-plugin` directory.
- Install this package with `npm install --save-dev --exact @lightbase/eslint-config`
- Remove `.eslintrc`, `.eslintignore`, `.prettierignore` and `.prettierrc(.js)` files.
- Remove the `prettier` key from your package.json.
- Remove all existing `lint`, `format` and `pretty` scripts from your package.json.
- Create `eslint.config.mjs` in the root of your project and paste the below contents.
- Note the _.mjs_ extension.
- Apply the [Commands](./README.md#commands) section from the README.
- Apply the [IDE](./README.md#ide) section from the README.
- Run `npm run lint` and fixup the remaining issues.
- This will most likely fail a few times. In some cases, the built-in Prettier setup is
not able to auto-fix in this migration. This won't be an issue while using the new
setup.
- You might need to add TS type checking for JavaScript files by adding ` "**/*.*js"` to
your `includes` in the `tsconfig.json`.
- Update your CI scripts to use the `npm run lint:ci` command.

```js
import { defineConfig } from "@lightbase/eslint-config";

export default defineConfig(
{
prettier: {
globalOverride: {
printWidth: 110,
useTabs: false,
arrowParens: "avoid",
},
},
typescript: {
// TODO: Start enabling some type check rules. See https://typescript-eslint.io/users/configs/#recommended-type-checked
disableTypeCheckedRules: true,
},
react: {
withNextJs: true,
},
},
{
files: ["**/generated/**/*.*"],
rules: {
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-vars": "off",
},
},
);
```
137 changes: 106 additions & 31 deletions packages/eslint-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,13 @@ Opinionated but configurable ESLint config. Fully includes linting and formattin
npm install --save-dev --exact @lightbase/eslint-config
```

The following dependencies are automatically installed as part of `peerDependencies`,
however custom versions can be installed via
Some configurations require manually installed plugins. For example

```shell
npm install --save-dev --exact eslint typescript-eslint
npm install --save-dev --exact eslint-plugin-react eslint-plugin-react-hooks
```

Some configurations require manually installed plugins.

[//]: # "TODO: update example if we have a good one"

```shell
npm install --save-dev --exact eslint-plugin-jsdoc
```
This is documented below.

## Usage

Expand All @@ -35,12 +28,14 @@ import { defineConfig } from "@lightbase/eslint-config";
export default defineConfig({});
```

### Commands

Add the following scripts to your `package.json`:

```json
{
"scripts": {
"lint": "eslint . --fix --cache --cache-strategy content --cache-location .cache/eslint/ --color",
"lint": "eslint . --fix --cache --cache-strategy content --cache-location .cache/eslint/",
"lint:ci": "eslint ."
}
}
Expand All @@ -50,32 +45,16 @@ Add the following scripts to your `package.json`:

### In a CommonJS project

Note, these steps will be obsolete with ESLint v9, which at the time of writing is in
alpha.
> [!NOTE]
>
> These steps will be obsolete with ESLint v9, which at the time of writing is released
> but not yet supported by all our plugins.

- Use `eslint.config.mjs` instead of `eslint.config.js`
- Specify `--config eslint.config.mjs` in the `package.json` scripts.

## Default configuration and options

### Custom configuration

`defineConfig` accepts custom ESLint configuration as the 'rest' parameter. For example:

```js
import { defineConfig } from "@lightbase/eslint-config";

export default defineConfig(
{
// Define config options, explained below.
},
{
// Ignore the packages/ directory.
ignores: ["packages/**"],
},
);
```

### Prettier

Prettier is configured to run on all markdown, json, yaml, JavaScript and TypeScript
Expand Down Expand Up @@ -141,6 +120,19 @@ export default defineConfig({
});
```

By default, we enable the recommended type checked rules from typescript-eslint. To
disable these rules, use:

```js
import { defineConfig } from "@lightbase/eslint-config";

export default defineConfig({
typescript: {
disableTypeCheckedRules: true,
},
});
```

### Markdown

A Markdown processor is installed by default. Its purpose is to extract code-blocks and
Expand All @@ -161,6 +153,89 @@ export default defineConfig(
);
```

### React

The config optionally supports enabling React and Next.js specific rules. Add the
following dependencies:

```shell
npm install --save-dev --exact eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y
```

If you use Next.js, make sure to also add `@next/eslint-plugin-next` via:

```shell
npm install --save-dev --exact @next/eslint-plugin-next
```

React is only support in combination with Typescript (see above), and can be enabled as
follows:

```js
import { defineConfig } from "@lightbase/eslint-config";

export default defineConfig({
react: {
withNextJs: true,
},
});
```

This enables all Next.js rules and various recommended rules for React, hooks usage and
JSX accessibility.

### Globals

The config by default includes all globals for Node.js, Browser and ES2021. You can use
other predefined presets via

```js
import { defineConfig } from "@lightbase/eslint-config";

export default defineConfig({
// Make sure to include the full setup.
globals: ["browser", "serviceworker"],
});
```

This enables environment-specific globals for all files. For a stricter setup, use custom
configuration as explained below

```js
import { defineConfig } from "@lightbase/eslint-config";
import globals from "globals";

export default defineConfig(
{},
{
files: ["**/*.js"],
languageOptions: {
globals: {
...globals.es2015,
},
},
},
);
```

### Custom configuration

`defineConfig` accepts custom ESLint configuration as the 'rest' parameter. For example:

```js
import { defineConfig } from "@lightbase/eslint-config";

export default defineConfig(
{
// Define config options, explained below.
},
{
// Ignore the packages/ directory.
ignores: ["packages/**"],
},
);
```

## IDE

### WebStorm
Expand Down
Loading