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=master)
|
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 |
|
10 | ## Motivation
|
11 |
|
12 | Linting and formatting rules are always a balance between
|
13 |
|
14 | - ease of reading
|
15 | - ease of refactoring
|
16 | - ease of writing.
|
17 |
|
18 | We think that
|
19 |
|
20 | - code is read more often than refactored
|
21 | - and refactored more often than written from scratch.
|
22 |
|
23 | Our linting rules have been designed with these assumptions in mind.
|
24 |
|
25 | ## Features
|
26 |
|
27 | ### Atomic changes
|
28 |
|
29 | Our 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.
|
30 |
|
31 | **Example:** I want to change a variable from `let` to `const`.
|
32 |
|
33 | ```diff
|
34 | // Bad coding style because useless whitespace changes were necessary
|
35 | -let a = 1,
|
36 | +let a = 1,
|
37 | - bbb = 2,
|
38 | + cc = 3;
|
39 | - cc = 3;
|
40 | +const bbb = 3;
|
41 | ```
|
42 |
|
43 | ```diff
|
44 | // Good coding style because only the relevant parts need to be changed
|
45 | let a = 1;
|
46 | -let bb = 2;
|
47 | +const bb = 2;
|
48 | let ccc = 3;
|
49 | ```
|
50 |
|
51 | This 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)).
|
52 |
|
53 | ### Consistent formatting
|
54 |
|
55 | For 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).
|
56 |
|
57 | We recommend combining these linting rules with [Prettier](https://prettier.io/) (see [below](#prettier)). There's also a [recommended configuration for VSCode](#vscode).
|
58 |
|
59 | ### Code smells as a warning
|
60 |
|
61 | Developers 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.
|
62 |
|
63 | This 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.
|
64 |
|
65 | If 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:
|
66 |
|
67 | ```js
|
68 | // The API returns snakecase properties
|
69 | // eslint-disable-next-line babel/camelcase
|
70 | function fetchUsers() {
|
71 | // ...
|
72 | }
|
73 | ```
|
74 |
|
75 | ### Disabling rules
|
76 |
|
77 | Try to disable as less rules as possible. In most cases it's best to just write
|
78 |
|
79 | ```js
|
80 | // eslint-disable-next-line [rule-code]
|
81 | ```
|
82 |
|
83 | where `[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.
|
84 |
|
85 | Sometimes 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:
|
86 |
|
87 | ```js
|
88 | /* eslint-disable [rule-code] */
|
89 | ```
|
90 |
|
91 | If 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.
|
92 |
|
93 | ### Different styles
|
94 |
|
95 | We 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.
|
96 |
|
97 | ### Naming conventions for properties
|
98 |
|
99 | Sometimes 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:
|
100 |
|
101 | ```js
|
102 | const options = require("eslint-config-peerigon/options.js");
|
103 |
|
104 | module.exports = {
|
105 | /* ... */
|
106 | rules: {
|
107 | // The API uses snake_case as properties
|
108 | "babel/camelcase": ["warn", {
|
109 | ...options["camelcase"],
|
110 | properties: "never"
|
111 | }]
|
112 | },
|
113 | };
|
114 | ```
|
115 |
|
116 | **In TypeScript projects:**
|
117 |
|
118 | ```js
|
119 | const options = require("eslint-config-peerigon/options.js");
|
120 |
|
121 | module.exports = {
|
122 | /* ... */
|
123 | rules: {
|
124 | // The API uses snake_case as properties
|
125 | "@typescript-eslint/naming-convention": [
|
126 | "warn",
|
127 | options["@typescript-eslint/naming-convention"].ignoreProperties,
|
128 | ...options["@typescript-eslint/naming-convention"].defaultRules,
|
129 | ],
|
130 | },
|
131 | };
|
132 | ```
|
133 |
|
134 | ## Provided configs
|
135 |
|
136 | ### [`peerigon`](base.js)
|
137 |
|
138 | **Base rules for every project. You should always add these rules.**
|
139 |
|
140 | ```
|
141 | npm i eslint eslint-config-peerigon --save-dev
|
142 | ```
|
143 |
|
144 | These 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.
|
145 |
|
146 | Add an `.eslintrc.json` to the project's root folder:
|
147 |
|
148 | ```js
|
149 | {
|
150 | "extends": [
|
151 | // Base rules for every project
|
152 | "peerigon",
|
153 | "prettier" // add this at the end of 'extends' if you're using Prettier
|
154 | ],
|
155 | // Do not search for further eslint configs in upper directories
|
156 | "root": true
|
157 | }
|
158 | ```
|
159 |
|
160 | In your `package.json`, add a `test:lint` script and run it as `posttest`:
|
161 |
|
162 | ```js
|
163 | {
|
164 | "scripts": {
|
165 | "test:lint": "eslint --cache ./src ./test",
|
166 | "posttest": "npm run test:lint"
|
167 | }
|
168 | }
|
169 | ```
|
170 |
|
171 | The 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.
|
172 |
|
173 | ### [`peerigon/node`](node.js)
|
174 |
|
175 | Special rules for Node.js >= 8.0.0 environments:
|
176 |
|
177 | ```js
|
178 | {
|
179 | "extends": [
|
180 | // Base rules with full ES2015 support
|
181 | "peerigon",
|
182 | // Rules for node
|
183 | "peerigon/node",
|
184 | "prettier" // add this if you're using Prettier
|
185 | ]
|
186 | // Setting env.node = true is not necessary, this is already done by peerigon/node
|
187 | }
|
188 | ```
|
189 |
|
190 | These rules assume that you're using CommonJS modules. In case you're using ECMAScript modules, you should set [`parserOptions.sourceType: "module"`](https://eslint.org/docs/user-guide/configuring#specifying-parser-options). We will change that once a LTS Node.js version has official support for ECMAScript modules.
|
191 |
|
192 | ### [`peerigon/react`](react.js)
|
193 |
|
194 | **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.**
|
195 |
|
196 | ```
|
197 | npm i eslint-plugin-react eslint-plugin-jsx-a11y eslint-plugin-react-hooks --save-dev
|
198 | ```
|
199 |
|
200 | Rules for [React](https://facebook.github.io/react/) development, including accessibility rules.
|
201 | These rules are also applicable in other JSX environments, like [Preact](https://github.com/developit/preact):
|
202 |
|
203 | ```js
|
204 | {
|
205 | "extends": [
|
206 | "peerigon",
|
207 | "peerigon/react",
|
208 | "prettier", // add this and...
|
209 | "prettier/react" // ...this if you're using Prettier
|
210 | ],
|
211 | "root": true
|
212 | }
|
213 | ```
|
214 |
|
215 | *We recommend using [`peerigon/styles/react-jsx-no-literals`](#peerigonstylesreact-jsx-no-literals) if you're using i18n in your project.*
|
216 | *You can use [`peerigon/styles/react-jsx-no-bind`](#peerigonstylesreact-jsx-no-bind) if you're using `memo` and `shouldComponentUpdate` a lot.*
|
217 |
|
218 | ### [`peerigon/typescript`](typescript.js)
|
219 |
|
220 | **Important: Requires [`@typescript-eslint/eslint-plugin`](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin) and [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser) as project dependency.**
|
221 |
|
222 | ```
|
223 | npm i @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev
|
224 | ```
|
225 |
|
226 | Rules for [TypeScript](https://www.typescriptlang.org/).
|
227 |
|
228 | **⚠️ 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.*
|
229 |
|
230 | ```js
|
231 | {
|
232 | "extends": [
|
233 | "peerigon",
|
234 | "peerigon/typescript",
|
235 | // Arrow functions are preferred with TypeScript
|
236 | // See https://github.com/peerigon/eslint-config-peerigon/issues/23#issuecomment-472614432
|
237 | "peerigon/styles/prefer-arrow",
|
238 | "prettier", // add this and...
|
239 | "prettier/@typescript-eslint" // ...this if you're using Prettier
|
240 | ],
|
241 | "parserOptions": {
|
242 | // Relative to the folder where eslint is executed
|
243 | // See https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/README.md#parseroptionsproject
|
244 | "project": "./tsconfig.json"
|
245 | },
|
246 | "root": true,
|
247 | }
|
248 | ```
|
249 |
|
250 | You need to add `--ext js,ts,tsx` to the `test:lint` script:
|
251 |
|
252 | ```js
|
253 | {
|
254 | "scripts": {
|
255 | "test:lint": "eslint --cache --ext js,jsx,ts,tsx ./src ./test"
|
256 | }
|
257 | }
|
258 | ```
|
259 |
|
260 | *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).*
|
261 |
|
262 | Do you see an error that looks like this?
|
263 |
|
264 | ```
|
265 | Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
|
266 | The file does not match your project config: ...
|
267 | The file must be included in at least one of the projects provided
|
268 | ```
|
269 |
|
270 | This 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`.
|
271 |
|
272 | ### [`peerigon/flowtype`](flowtype.js)
|
273 |
|
274 | **Important: Requires [`babel-eslint`](https://github.com/babel/babel-eslint) and [`eslint-plugin-flowtype`](https://github.com/gajus/eslint-plugin-flowtype) as project dependency.**
|
275 |
|
276 | ```
|
277 | npm i babel-eslint eslint-plugin-flowtype --save-dev
|
278 | ```
|
279 |
|
280 | Rules for [Flowtype](https://flowtype.org/).
|
281 |
|
282 | ```js
|
283 | {
|
284 | "extends": [
|
285 | "peerigon",
|
286 | "peerigon/flowtype",
|
287 | "prettier", // add this and...
|
288 | "prettier/flowtype" // ...this if you're using Prettier
|
289 | ],
|
290 | "root": true
|
291 | }
|
292 | ```
|
293 |
|
294 | ### [`peerigon/es5`](es5.js)
|
295 |
|
296 | Special rules for older projects:
|
297 |
|
298 | ```js
|
299 | {
|
300 | "extends": [
|
301 | // Base rules with full ES2015 support
|
302 | "peerigon",
|
303 | // Legacy rules for older projects
|
304 | "peerigon/es5"
|
305 | ],
|
306 | "root": true
|
307 | }
|
308 | ```
|
309 |
|
310 | ## Styles
|
311 |
|
312 | The following rules enable specific writing styles. Use them as you prefer.
|
313 |
|
314 | ### [`peerigon/styles/prefer-arrow`](styles/prefer-arrow.js)
|
315 |
|
316 | **Important: Requires [`eslint-plugin-prefer-arrow`](https://github.com/TristonJ/eslint-plugin-prefer-arrow) as project dependency.**
|
317 |
|
318 | ```
|
319 | npm i eslint-plugin-prefer-arrow --save-dev
|
320 | ```
|
321 |
|
322 | Enforces arrow function expressions instead of function declarations (see [#23](https://github.com/peerigon/eslint-config-peerigon/issues/23)).
|
323 | Regular functions are still allowed as methods in objects or classes.
|
324 |
|
325 | ```js
|
326 | "extends": [
|
327 | "peerigon",
|
328 | "peerigon/styles/prefer-arrow"
|
329 | ],
|
330 | ```
|
331 |
|
332 | ### [`peerigon/styles/no-default-export`](styles/no-default-export.js)
|
333 |
|
334 | Forbids 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/).
|
335 |
|
336 | ```js
|
337 | "extends": [
|
338 | "peerigon",
|
339 | "peerigon/styles/no-default-export"
|
340 | ],
|
341 | ```
|
342 |
|
343 | **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`.
|
344 |
|
345 | ### [`peerigon/styles/no-null`](styles/no-null.js)
|
346 |
|
347 | **Important: Requires [`eslint-plugin-no-null`](https://github.com/nene/eslint-plugin-no-null) as project dependency.**
|
348 |
|
349 | ```
|
350 | npm i eslint-plugin-no-null --save-dev
|
351 | ```
|
352 |
|
353 | Forbids 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**.
|
354 |
|
355 | ```js
|
356 | "extends": [
|
357 | "peerigon",
|
358 | "peerigon/styles/no-null"
|
359 | ],
|
360 | ```
|
361 |
|
362 | **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:
|
363 |
|
364 | ```js
|
365 | // eslint-disable-next-line no-null/no-null
|
366 | export const NULL = null;
|
367 | ```
|
368 |
|
369 | ### [`peerigon/styles/prefer-interface`](styles/prefer-interface.js)
|
370 |
|
371 | **Important: Use it in combination with [`peerigon/typescript`](typescript.js).**
|
372 |
|
373 | [Prefer `interface` over `type`](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-interface.md).
|
374 |
|
375 | ```js
|
376 | "extends": [
|
377 | "peerigon",
|
378 | "peerigon/typescript",
|
379 | "peerigon/styles/prefer-interface"
|
380 | ],
|
381 | ```
|
382 |
|
383 | ### [`peerigon/styles/react-jsx-no-bind`](styles/react-jsx-no-bind.js)
|
384 |
|
385 | **Important: Use it in combination with [`peerigon/react`](react.js).**
|
386 |
|
387 | Depending 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.
|
388 |
|
389 | ```js
|
390 | "extends": [
|
391 | "peerigon",
|
392 | "peerigon/react",
|
393 | "peerigon/styles/react-jsx-no-bind"
|
394 | ],
|
395 | ```
|
396 |
|
397 | ### [`peerigon/styles/react-jsx-no-literals`](styles/react-jsx-no-literals.js)
|
398 |
|
399 | **Important: Use it in combination with [`peerigon/react`](react.js).**
|
400 |
|
401 | Use this style if you're using i18n. It prevents people from putting raw strings in components.
|
402 |
|
403 | ```js
|
404 | "extends": [
|
405 | "peerigon",
|
406 | "peerigon/react",
|
407 | "peerigon/styles/react-jsx-no-literals"
|
408 | ],
|
409 | ```
|
410 |
|
411 | It disallows this:
|
412 |
|
413 | ```jsx
|
414 | const Hello = <div>test</div>;
|
415 | ```
|
416 |
|
417 | As an escape hatch, this is still allowed:
|
418 |
|
419 | ```jsx
|
420 | const Hello = <div>{'test'}</div>;
|
421 | ```
|
422 |
|
423 | ### [`peerigon/styles/prefer-array-shorthand`](styles/prefer-array-shorthand.js)
|
424 |
|
425 | **Important: Use it in combination with [`peerigon/typescript`](typescript.js).**
|
426 |
|
427 | Enforces typescript arrays to use the shorthand array-style instead of the generic style.
|
428 |
|
429 | ```js
|
430 | "extends": [
|
431 | "peerigon",
|
432 | "peerigon/typescript",
|
433 | "peerigon/styles/prefer-array-shorthand"
|
434 | ],
|
435 | ```
|
436 |
|
437 | It enforces this:
|
438 |
|
439 | ```ts
|
440 | const foo: string[] = [];
|
441 | ```
|
442 |
|
443 | instead of
|
444 |
|
445 | ```ts
|
446 | const foo: Array<string> = [];
|
447 | ```
|
448 |
|
449 | ## Prettier
|
450 |
|
451 | There is a [Prettier](https://prettier.io/) config in this repository that corresponds to our linting rules as much as possible. Add a `.prettierrc` file to your repository with the following content:
|
452 |
|
453 | ```js
|
454 | "eslint-config-peerigon/prettier"
|
455 | ```
|
456 |
|
457 | In 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:
|
458 |
|
459 | ```js
|
460 | {
|
461 | "extends": [
|
462 | "peerigon",
|
463 | "peerigon/typescript",
|
464 | "peerigon/styles/prefer-arrow",
|
465 | "peerigon/react",
|
466 | // prettier must be at the end
|
467 | "prettier",
|
468 | "prettier/@typescript-eslint",
|
469 | "prettier/react"
|
470 | ],
|
471 | "root": true,
|
472 | };
|
473 | ```
|
474 |
|
475 | This 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.
|
476 |
|
477 | ## VSCode
|
478 |
|
479 | This 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:
|
480 |
|
481 | ```json
|
482 | {
|
483 | "editor.defaultFormatter": "esbenp.prettier-vscode",
|
484 | "editor.formatOnSave": true,
|
485 | "editor.codeActionsOnSave": {
|
486 | "source.fixAll.eslint": true
|
487 | }
|
488 | }
|
489 | ```
|
490 |
|
491 | ## License
|
492 |
|
493 | Unlicense
|
494 |
|
495 | ## Sponsors
|
496 |
|
497 | [<img src="https://assets.peerigon.com/peerigon/logo/peerigon-logo-flat-spinat.png" width="150" />](https://peerigon.com)
|