UNPKG

20.4 kBMarkdownView Raw
1# eslint-config-peerigon
2
3**[Peerigon](https://peerigon.com/) coding rules as [ESLint](http://eslint.org/) config.**
4
5[![](https://img.shields.io/npm/v/eslint-config-peerigon.svg)](https://www.npmjs.com/package/eslint-config-peerigon)
6[![](https://img.shields.io/npm/dm/eslint-config-peerigon.svg)](https://www.npmjs.com/package/eslint-config-peerigon)
7[![Dependency Status](https://david-dm.org/peerigon/eslint-config-peerigon.svg)](https://david-dm.org/peerigon/eslint-config-peerigon?branch=main)
8[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
9
10Linting and formatting rules are always a balance between
11
12- ease of reading
13- ease of refactoring
14- ease of writing.
15
16We think that
17
18- code is read more often than refactored
19- and refactored more often than written from scratch.
20
21Our linting rules have been designed with these assumptions in mind.
22
23## Table of contents
24
25- [Quick start](#quick-start)
26- [Features](#features)
27- [Practical guide](#practical-guide)
28- [Provided configs](#provided-configs)
29- [Styles](#styles)
30
31## Quick start
32
33Recommended configuration in your `package.json`:
34
35```js
36{
37 "scripts": {
38 "test:lint": "eslint --max-warnings 0 --cache --ext js,jsx,ts,tsx --ignore-path .gitignore .",
39 "posttest": "npm run test:lint"
40 }
41}
42```
43
44There are presets for the most common setups:
45
46### Prettier + TypeScript
47
48```
49npm i eslint eslint-config-peerigon --save-dev
50```
51
52```js
53{
54 "extends": [
55 "peerigon/presets/prettier-typescript.js"
56 ],
57 "env": {
58 "node": true
59 },
60 "root": true,
61 "parserOptions": {
62 // This path is relative to the CWD where eslint is executed
63 // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/README.md#parseroptionsproject
64 "project": "./tsconfig.json",
65 "sourceType": "module"
66 }
67}
68```
69
70### Prettier + TypeScript + React
71
72```
73npm i eslint eslint-config-peerigon eslint-plugin-react eslint-plugin-jsx-a11y eslint-plugin-react-hooks --save-dev
74```
75
76```js
77{
78 "extends": [
79 "peerigon/presets/prettier-typescript-react.js"
80 ],
81 "env": {
82 "node": true,
83 "browser": true
84 },
85 "root": true,
86 "parserOptions": {
87 // This path is relative to the CWD where eslint is executed
88 // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/README.md#parseroptionsproject
89 "project": "./tsconfig.json",
90 "sourceType": "module"
91 }
92}
93```
94
95### Prettier + TypeScript + Node
96
97```
98npm i eslint eslint-config-peerigon eslint-plugin-node --save-dev
99```
100
101```js
102{
103 "extends": [
104 "peerigon/presets/prettier-typescript-node.js"
105 ],
106 "root": true,
107 "parserOptions": {
108 // This path is relative to the CWD where eslint is executed
109 // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/README.md#parseroptionsproject
110 "project": "./tsconfig.json",
111 "sourceType": "module"
112 }
113}
114```
115
116Your `package.json`:
117
118```json
119{
120 "type": "module",
121 "engines": {
122 "node": ">=14.0.0"
123 }
124}
125```
126
127## Features
128
129### Atomic changes
130
131Our formatting rules have been chosen carefully so that a change of a file is as atomic as possible. This makes it easier to review pull requests because there are no meaningless changes anymore.
132
133**Example:** I want to change a variable from `let` to `const`.
134
135```diff
136// Bad coding style because useless whitespace changes were necessary
137-let a = 1,
138+let a = 1,
139- bbb = 2,
140+ cc = 3;
141- cc = 3;
142+const bbb = 3;
143```
144
145```diff
146// Good coding style because only the relevant parts need to be changed
147let a = 1;
148-let bb = 2;
149+const bb = 2;
150let ccc = 3;
151```
152
153This is also the reason why we prefer [dangling commas](https://eslint.org/docs/rules/comma-dangle) for multiline arrays, objects and arguments although they look very unfamiliar on first sight (see [discussion](https://github.com/peerigon/eslint-config-peerigon/issues/10)).
154
155### Consistent formatting
156
157For the purpose of atomic changes, our rules are intentionally strict about formatting which are usually autofixable. You should use an editor configuration where you can apply these autofixes on demand (for instance when saving the file).
158
159We recommend combining these linting rules with [Prettier](https://prettier.io/) (see [below](#prettier)). There's also a [recommended configuration for VSCode](#vscode).
160
161### Code smells as a warning
162
163Developers take shortcuts. And that's ok because at the end of the day we have to deliver software within fixed time frames and budgets. Sometimes it's also because we don't know of a better alternative. We call these shortcuts "code smells" and our linting rules will complain about them with a warning.
164
165This means that this code is potentially problematic, but you don't have to fix it right away. You should keep the warning and come back later to refactor it (e.g. during a refactoring sprint). The amount of warnings is also a good indicator for technical debt.
166
167If you think that there is a good reason for deviating from the standard path, disable the warning and put an explanation above that comment why it's ok to disable the rule in that case, like:
168
169```js
170// The API returns snakecase properties
171/* eslint-disable babel/camelcase */
172function fetchUsers() {
173 // ...
174}
175```
176
177We use warnings instead of errors for most code issues since it's visually less distracting. We recommend to use `--max-warnings 0` as part of your test script or within your CI. These warnings can serve as a hint that the code needs to be fixed before it can be merged into the `main` branch.
178
179## Practical guide
180
181### Disabling rules
182
183Try to disable as less rules as possible. In most cases it's best to just write
184
185```js
186// eslint-disable-next-line [rule-code]
187```
188
189where `[rule-code]` is the code that is displayed along the error message. Disabling the next line is usually better because it resists [Prettier](https://prettier.io/) reformatting.
190
191Sometimes it makes sense to disable a rule within a specifc file. In that case you can put the following snippet at the beginning of the file:
192
193```js
194/* eslint-disable [rule-code] */
195```
196
197If you don't agree with a rule, please do not just disable the rule. Often there are good reasons and the current setting is the result of years of experience. It's better to create an issue here to start a discussion about the pros and cons of a rule.
198
199### Different styles
200
201We acknowledge that there are certain rules where there are no actual pros and cons or where there is no clear winner. You just have to decide for one style and stick with it. We also know that some rules make sense in one project, but don't make sense in another project. That's why we also provide a list of [accepted custom styles](#styles) (see also [this discussion](https://github.com/peerigon/eslint-config-peerigon/issues/11)) which you can pick.
202
203### Prettier
204
205In order to avoid conflicts between Prettier and our rules, you should always add **prettier rules at the end of `extends`**. For example, in a TypeScript + React project you would use the following configuration:
206
207```js
208{
209 "extends": [
210 "peerigon",
211 "peerigon/typescript",
212 "peerigon/styles/prefer-arrow",
213 "peerigon/react",
214 // prettier must be at the end
215 "prettier",
216 "prettier/react"
217 ],
218 "root": true,
219};
220```
221
222This module already lists [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) as dependency which is why you don't have to install it manually.
223
224### VSCode
225
226This is our recommended VSCode configuration using the [Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode). Adjust it to the needs of your particular project:
227
228```json
229{
230 "editor.defaultFormatter": "esbenp.prettier-vscode",
231 "editor.formatOnSave": true,
232 "editor.codeActionsOnSave": {
233 "source.fixAll.eslint": true
234 }
235}
236```
237
238### Experimental syntax using Babel
239
240If you're using Babel you should set [`requireConfigFile: true`](https://github.com/babel/babel/tree/main/eslint/babel-eslint-parser#additional-parser-configuration) in your ESLint config. ESLint will then use your `babel.config.json`.
241
242```js
243{
244 "parserOptions": { "requireConfigFile": true },
245}
246```
247
248### Naming conventions for properties
249
250Sometimes we're not in full control over the naming conventions in our codebase, for instance if data is coming from a foreign API. While it often is preferable to transform property names into camelCase, it might not be practical. In these situations you can disable the check for properties like this:
251
252```js
253const options = require("eslint-config-peerigon/options.js");
254
255module.exports = {
256 /* ... */
257 rules: {
258 // The API uses snake_case as properties
259 camelcase: [
260 "warn",
261 {
262 ...options["camelcase"],
263 properties: "never",
264 },
265 ],
266 },
267};
268```
269
270**In TypeScript projects:**
271
272```js
273const options = require("eslint-config-peerigon/options.js");
274
275module.exports = {
276 /* ... */
277 rules: {
278 // The API uses snake_case as properties
279 "@typescript-eslint/naming-convention": [
280 "warn",
281 options["@typescript-eslint/naming-convention"].ignoreProperties,
282 ...options["@typescript-eslint/naming-convention"].defaultRules,
283 ],
284 },
285};
286```
287
288## Provided configs
289
290### [`peerigon`](base.js)
291
292**Base rules for every project. You should always add these rules.**
293
294```
295npm i eslint eslint-config-peerigon --save-dev
296```
297
298These rules assume a modern project with full ES2015 support, including ES modules. For specific environments like Node.js or old JS engines, see below. The base rules do not define an `env`, so you might want to do that for yourself to enable specific globals.
299
300Add an `.eslintrc.json` to the project's root folder:
301
302```js
303{
304 "extends": [
305 // Base rules for every project
306 "peerigon",
307 "prettier" // add this at the end of 'extends' if you're using Prettier
308 ],
309 // Do not search for further eslint configs in upper directories
310 "root": true,
311 // If you're using Babel, you should set requireConfigFile: true
312 // ESLint will then use your babel.config.json.
313 // "parserOptions": { "requireConfigFile": true },
314}
315```
316
317The base rules use the `eslint-plugin-import` to resolve imports. Although it's possible to define [custom resolvers](https://github.com/benmosher/eslint-plugin-import#resolvers), it's highly discouraged to deviate from the common Node/webpack resolving algorithm. Other tools like linters and intellisense don't work reliably when you change the resolver.
318
319### [`peerigon/node`](node.js)
320
321**Important: Requires [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node).**
322
323```
324npm i eslint-plugin-node --save-dev
325```
326
327```js
328{
329 "extends": [
330 "peerigon",
331 "peerigon/node",
332 "prettier" // add this if you're using Prettier
333 ],
334 // Setting env.node = true is not necessary, this is already done by peerigon/node
335 "root": true
336}
337```
338
339[`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node) uses the ["engines" field](https://docs.npmjs.com/files/package.json#engines) and the ["type" field](https://nodejs.org/api/esm.html#esm_enabling) in your `package.json` to check for compatibility issues. We recommend the following configuration:
340
341```json
342{
343 "type": "module",
344 "engines": {
345 "node": ">=14.0.0"
346 }
347}
348```
349
350### [`peerigon/react`](react.js)
351
352**Important: Requires [`eslint-plugin-react`](https://github.com/yannickcr/eslint-plugin-react), [`eslint-plugin-jsx-a11y`](https://github.com/evcohen/eslint-plugin-jsx-a11y) and [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks) as project dependency.**
353
354```
355npm i eslint-plugin-react eslint-plugin-jsx-a11y eslint-plugin-react-hooks --save-dev
356```
357
358Rules for [React](https://facebook.github.io/react/) development, including accessibility rules.
359These rules are also applicable in other JSX environments, like [Preact](https://github.com/developit/preact):
360
361```js
362{
363 "extends": [
364 "peerigon",
365 "peerigon/react",
366 "prettier", // add this and...
367 "prettier/react" // ...this if you're using Prettier
368 ],
369 "root": true
370}
371```
372
373_We recommend using [`peerigon/styles/react-jsx-no-literals`](#peerigonstylesreact-jsx-no-literals) if you're using i18n in your project._
374_You can use [`peerigon/styles/react-jsx-no-bind`](#peerigonstylesreact-jsx-no-bind) if you're using `memo` and `shouldComponentUpdate` a lot._
375
376### [`peerigon/typescript`](typescript.js)
377
378Rules for [TypeScript](https://www.typescriptlang.org/).
379
380**⚠️ Attention:** These rules require your `tsconfig.json`. Specify the path in `parserOptions.project` (see also [here](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/README.md#parseroptionsproject) for more information). _The path should be relative to the folder where `eslint` is executed._
381
382```js
383{
384 "extends": [
385 "peerigon",
386 "peerigon/typescript",
387 // Arrow functions are preferred with TypeScript
388 // See https://github.com/peerigon/eslint-config-peerigon/issues/23#issuecomment-472614432
389 "peerigon/styles/prefer-arrow",
390 "prettier", // add this if you're using Prettier
391 ],
392 "parserOptions": {
393 // Relative to the folder where eslint is executed
394 // See https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/README.md#parseroptionsproject
395 "project": "./tsconfig.json"
396 },
397 "root": true,
398}
399```
400
401You need to add `--ext js,ts,tsx` to the `test:lint` script:
402
403```js
404{
405 "scripts": {
406 "test:lint": "eslint --max-warnings 0 --cache --ext js,jsx,ts,tsx --ignore-path .gitignore ."
407 }
408}
409```
410
411_We recommend using [`peerigon/styles/prefer-arrow`](#peerigonstylesprefer-arrow) because arrow functions (or function expressions in general) can leverage [TypeScript's contextual typing](https://www.typescriptlang.org/docs/handbook/type-inference.html#contextual-typing)._
412
413Do you see an error that looks like this?
414
415```
416Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
417The file does not match your project config: ...
418The file must be included in at least one of the projects provided
419```
420
421This is a sign that ESLint is trying to lint a file that is not included by your `tsconfig.json`. You need to adjust either `parserOptions.project` or `include` of the referenced `tsconfig.json`.
422
423### [`peerigon/jsdoc`](jsdoc.js)
424
425**Important: Requires [`eslint-plugin-jsdoc`](https://github.com/gajus/eslint-plugin-jsdoc) as project dependency.**
426
427```
428npm i eslint-plugin-jsdoc --save-dev
429```
430
431Makes sure that JSDoc annotations are written in a standard-compliant and uniform way.
432
433```js
434{
435 "extends": [
436 "peerigon",
437 "peerigon/jsdoc"
438 ],
439 "root": true
440}
441```
442
443### [`peerigon/flowtype`](flowtype.js)
444
445**Important: Requires [`babel-eslint`](https://github.com/babel/babel-eslint) and [`eslint-plugin-flowtype`](https://github.com/gajus/eslint-plugin-flowtype) as project dependency.**
446
447```
448npm i babel-eslint eslint-plugin-flowtype --save-dev
449```
450
451Rules for [Flowtype](https://flowtype.org/).
452
453```js
454{
455 "extends": [
456 "peerigon",
457 "peerigon/flowtype",
458 "prettier", // add this and...
459 "prettier/flowtype" // ...this if you're using Prettier
460 ],
461 "root": true
462}
463```
464
465### [`peerigon/es5`](es5.js)
466
467Special rules for older projects:
468
469```js
470{
471 "extends": [
472 // Base rules with full ES2015 support
473 "peerigon",
474 // Legacy rules for older projects
475 "peerigon/es5"
476 ],
477 "root": true
478}
479```
480
481## Styles
482
483The following rules enable specific writing styles. Use them as you prefer.
484
485### [`peerigon/styles/prefer-arrow`](styles/prefer-arrow.js)
486
487Enforces arrow function expressions instead of function declarations (see [#23](https://github.com/peerigon/eslint-config-peerigon/issues/23)).
488Regular functions are still allowed as methods in objects or classes.
489
490```js
491 "extends": [
492 "peerigon",
493 "peerigon/styles/prefer-arrow"
494 ],
495```
496
497### [`peerigon/styles/no-default-export`](styles/no-default-export.js)
498
499Forbids usage of `export default`. When using default exports, it becomes harder to name classes or functions consistently throughout the codebase since every module can pick its own name for the imported thing. Nicholas C. Zakas, the creator of ESLint, wrote [an article with more compelling arguments why he stopped using `export default`](https://humanwhocodes.com/blog/2019/01/stop-using-default-exports-javascript-module/).
500
501```js
502 "extends": [
503 "peerigon",
504 "peerigon/styles/no-default-export"
505 ],
506```
507
508**Please note:** This rule is disabled in `.jsx` and `.tsx` files because React components are usually exported via `export default`. [`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy) even expects the lazy loaded component to be exported as `default`.
509
510### [`peerigon/styles/no-null`](styles/no-null.js)
511
512**Important: Requires [`eslint-plugin-no-null`](https://github.com/nene/eslint-plugin-no-null) as project dependency.**
513
514```
515npm i eslint-plugin-no-null --save-dev
516```
517
518Forbids the usage of `null`. In a codebase it's often better to use a single non-value to represent _the absence of a value_. With the rise of default parameters and destructuring defaults, JavaScript developed a clear tendency towards `undefined`. [This issue](https://github.com/peerigon/eslint-config-peerigon/issues/71) summarizes the arguments (and trade-offs) of **null vs. undefined**.
519
520```js
521 "extends": [
522 "peerigon",
523 "peerigon/styles/no-null"
524 ],
525```
526
527**Please note:** If you use this rule, you will probably still need a single `null` value which you can refer to whenever you need to use `null` because of third-party code:
528
529```js
530// eslint-disable-next-line no-null/no-null
531export const NULL = null;
532```
533
534### [`peerigon/styles/prefer-interface`](styles/prefer-interface.js)
535
536**Important: Use it in combination with [`peerigon/typescript`](typescript.js).**
537
538[Prefer `interface` over `type`](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-interface.md).
539
540```js
541 "extends": [
542 "peerigon",
543 "peerigon/typescript",
544 "peerigon/styles/prefer-interface"
545 ],
546```
547
548### [`peerigon/styles/react-jsx-no-bind`](styles/react-jsx-no-bind.js)
549
550**Important: Use it in combination with [`peerigon/react`](react.js).**
551
552Depending on the way you write your components, it might be not ok to create functions during `render()`. Use it if you're using things like `React.memo()` or `shouldComponentUpdate` a lot.
553
554```js
555 "extends": [
556 "peerigon",
557 "peerigon/react",
558 "peerigon/styles/react-jsx-no-bind"
559 ],
560```
561
562### [`peerigon/styles/react-jsx-no-literals`](styles/react-jsx-no-literals.js)
563
564**Important: Use it in combination with [`peerigon/react`](react.js).**
565
566Use this style if you're using i18n. It prevents people from putting raw strings in components.
567
568```js
569 "extends": [
570 "peerigon",
571 "peerigon/react",
572 "peerigon/styles/react-jsx-no-literals"
573 ],
574```
575
576It disallows this:
577
578```jsx
579const Hello = <div>test</div>;
580```
581
582As an escape hatch, this is still allowed:
583
584```jsx
585const Hello = <div>{"test"}</div>;
586```
587
588### [`peerigon/styles/prefer-array-shorthand`](styles/prefer-array-shorthand.js)
589
590**Important: Use it in combination with [`peerigon/typescript`](typescript.js).**
591
592Enforces typescript arrays to use the shorthand array-style instead of the generic style.
593
594```js
595 "extends": [
596 "peerigon",
597 "peerigon/typescript",
598 "peerigon/styles/prefer-array-shorthand"
599 ],
600```
601
602It enforces this:
603
604```ts
605const foo: string[] = [];
606```
607
608instead of
609
610```ts
611const foo: Array<string> = [];
612```
613
614## License
615
616Unlicense
617
618## Sponsors
619
620[<img src="https://assets.peerigon.com/peerigon/logo/peerigon-logo-flat-spinat.png" width="150" />](https://peerigon.com)