Skip to content

Commit 0688baf

Browse files
authoredOct 21, 2021
Merge pull request #142 from github/dev
Update docs
2 parents 3940bba + c7be1b5 commit 0688baf

21 files changed

+603
-291
lines changed
 

‎README.md

+22
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ $ npm install --save-dev eslint eslint-plugin-github
1313
Add `github` to your list of plugins in your ESLint config.
1414

1515
JSON ESLint config example:
16+
1617
```json
1718
{
1819
"plugins": ["github"]
@@ -22,6 +23,7 @@ JSON ESLint config example:
2223
Extend the configs you wish to use.
2324

2425
JSON ESLint config example:
26+
2527
```json
2628
{
2729
"extends": ["plugin:github/recommended"]
@@ -38,3 +40,23 @@ The available configs are:
3840
- Recommended rules for every application.
3941
- `typescript`
4042
- Useful rules when writing TypeScript.
43+
44+
### Rules
45+
46+
- [Array Foreach](./docs/rules/array-foreach.md)
47+
- [Async Currenttarget](./docs/rules/async-currenttarget.md)
48+
- [Async Preventdefault](./docs/rules/async-preventdefault.md)
49+
- [Authenticity Token](./docs/rules/authenticity-token.md)
50+
- [Get Attribute](./docs/rules/get-attribute.md)
51+
- [JS Class Name](./docs/rules/js-class-name.md)
52+
- [No Blur](./docs/rules/no-blur.md)
53+
- [No D None](./docs/rules/no-d-none.md)
54+
- [No Dataset](./docs/rules/no-dataset.md)
55+
- [No Implicit Buggy Globals](./docs/rules/no-implicit-buggy-globals.md)
56+
- [No Inner HTML](./docs/rules/no-inner-html.md)
57+
- [No InnerText](./docs/rules/no-innerText.md)
58+
- [No Then](./docs/rules/no-then.md)
59+
- [No Useless Passive](./docs/rules/no-useless-passive.md)
60+
- [Prefer Observers](./docs/rules/prefer-observers.md)
61+
- [Require Passive Events](./docs/rules/require-passive-events.md)
62+
- [Unescaped HTML Literal](./docs/rules/unescaped-html-literal.md)

‎docs/rules/array-foreach.md

+27-32
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,20 @@
1-
# Array.forEach
1+
# Array Foreach
22

33
Prefer `for...of` statement instead of `Array.forEach`.
44

5-
```js
6-
// bad
7-
els.forEach(el => {
8-
el
9-
})
10-
11-
// good
12-
for (const el of els) {
13-
el
14-
}
15-
```
16-
17-
## Why disallow `forEach`
5+
## Rule Details
186

197
Here's a summary of why `forEach` is disallowed, and why we prefer `for...of` for almost any use-case of `forEach`:
208

21-
- Allowing `forEach` encourages **layering of "bad practices"**, such as using `Array.from()` (which is less performant than using `for...of`).
22-
- When more requirements are added on, `forEach` typically gets **chained** with other methods like `filter` or `map`, causing multiple iterations over the same Array. Encouraging `for` loops discourages chaining and encourages single-iteration logic (e.g. using a `continue` instead of `filter`).
23-
- `for` loops are considered "more readable" and have **clearer intent**.
24-
- `for...of` loops offer the **most flexibility** for iteration (especially vs `Array.from`).
25-
26-
For more detail, here is a breakdown of each of those points:
27-
28-
### Layering of bad practices
9+
- Allowing `forEach` encourages **layering of "bad practices"**, such as using `Array.from()` (which is less performant than using `for...of`).
10+
- When more requirements are added on, `forEach` typically gets **chained** with other methods like `filter` or `map`, causing multiple iterations over the same Array. Encouraging `for` loops discourages chaining and encourages single-iteration logic (e.g. using a `continue` instead of `filter`).
11+
- `for` loops are considered "more readable" and have **clearer intent**.
12+
- `for...of` loops offer the **most flexibility** for iteration (especially vs `Array.from`).
2913

3014
Typically developers will reach for a `forEach` when they want to iterate over a set of items. However not all "iterables" have access to Array methods. So a developer might convert their iterable to an Array by using `Array.from(iter).forEach()`. This code has introduced performance problems, where a `for...of` loop would be more performant.
3115

3216
`forEach` does not do anything special with the Array - it does not create a new array or does not aid in encapsulation (except for introducing a new lexical scope within the callback, which isn't a benefit considering we use `let`/`const`). We don't dissallow `map`/`filter`/`reduce` because they have a tangible effect - they create a new array - which would take _more_ code and be _less_ readable to do with a `for...of` loop, the exception being as more requirements are added, and we start chaining array methods together...
3317

34-
### Chaining
35-
3618
Often when using a method like `forEach` - when coming back to add new code, let's say to filter certain elements from the Array before operating on them, a developer is implicitly encouraged to use Array's method chaining to achieve this result. For example if we wanted to filter out bad apples from an Array of Apples, if the code already uses `forEach`, then its a simple addition to add `filter()`:
3719

3820
```diff
@@ -52,22 +34,18 @@ The problem we now have is that we're iterating multiple times over the items in
5234

5335
Chaning isn't always necessarily bad. Chaining can advertise a series of transformations that are independant from one another, and therefore aid readability. Additionally, sometimes the "goto-style" behaviour of `continue` in for loops can hamper readability. For small Arrays, performance is not going to be of concern, but caution should be applied where there is a potentially unbounded Array (such as iterating over a fetched users list) as performance can easily become a bottleneck when unchecked.
5436

55-
### Hiding Intent
56-
57-
The `forEach` method passes more than just the current item it is iterating over. The signature of the `forEach` callback method is `(cur: T, i: Number, all: []T) => void` and it can _additionally_ override the `receiver` (`this` value), meaning that often the _intent_ of what the callback does is hidden. To put this another way, there is _no way_ to know what the following code operates on without reading the implementation: `forEach(polishApple)`.
37+
The `forEach` method passes more than just the current item it is iterating over. The signature of the `forEach` callback method is `(cur: T, i: Number, all: []T) => void` and it can _additionally_ override the `receiver` (`this` value), meaning that often the _intent_ of what the callback does is hidden. To put this another way, there is _no way_ to know what the following code operates on without reading the implementation: `forEach(polishApple)`.
5838

5939
The `for` loop avoids this issue. Calls are explicit within the `for` loop, as they are not passed around. For example:
6040

6141
```js
62-
for(const apple of apples) {
42+
for (const apple of apples) {
6343
polishApple(apple)
6444
}
6545
```
6646

6747
We know this code can only possibly mutate `apple`, as the return value is discarded, there is no `receiver` (`this` value) as `.call()` is not used, and it cannot operate on the whole array of `apples` because it is not passed as an argument. In this respect, we can establish what the intent of `polishApple(apple)` is far more than `forEach(polishApple)`. It is too easy for `forEach` to obscure the intent.
6848

69-
### Flexibility
70-
7149
While `forEach` provides a set of arguments to the callback, it is still overall _less flexible_ than a `for` loop. A `for` loop can conditionally call the callback, can pass additional arguments to the callback (which would otherwise need to be hoisted or curried), can opt to change the `receiver` (`this` value) or not pass any `receiver` at all. This extra flexibility is the reason we almost always prefer to use `for` loops over any of the Array iteration methods.
7250

7351
A good example of how `for` loops provide flexibility, where `forEach` constrains it, is to see how an iteration would be refactored to handle async work. Consider the following...
@@ -98,7 +76,24 @@ Compare this to the `for` loop, which has a much simpler path to refactoring:
9876
}
9977
```
10078

79+
See also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
80+
81+
👎 Examples of **incorrect** code for this rule:
82+
83+
```js
84+
els.forEach(el => {
85+
el
86+
})
87+
```
88+
89+
👍 Examples of **correct** code for this rule:
90+
91+
```js
92+
for (const el of els) {
93+
el
94+
}
95+
```
10196

102-
## See Also
97+
## Version
10398

104-
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
99+
4.3.2

‎docs/rules/async-currenttarget.md

+23-18
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1-
# `event.currentTarget` in an async function
1+
# Async Currenttarget
2+
3+
## Rule Details
24

35
Accessing `event.currentTarget` inside an `async function()` will likely be `null` as `currentTarget` is mutated as the event is propagated.
46

7+
1. A `click` event is dispatched
8+
2. The handler is invoked once with the expected `currentTarget`
9+
3. An `await` defers the execution
10+
4. The event dispatch continues, `event.currentTarget` is modified to point to the current target of another event handler and nulled out at the end of the dispatch
11+
5. The async function resumes
12+
6. `event.currentTarget` is now `null`
13+
14+
If you're using `async`, you'll need to synchronously create a reference to `currentTarget` before any async activity.
15+
16+
👎 Examples of **incorrect** code for this rule:
17+
518
```js
6-
// bad
7-
document.addEventListener('click', async function(event) {
19+
document.addEventListener('click', async function (event) {
820
// event.currentTarget will be an HTMLElement
921
const url = event.currentTarget.getAttribute('data-url')
1022
const data = await fetch(url)
@@ -15,25 +27,15 @@ document.addEventListener('click', async function(event) {
1527
})
1628
```
1729

18-
1. A `click` event is dispatched
19-
2. The handler is invoked once with the expected `currentTarget`
20-
3. An `await` defers the execution
21-
4. The event dispatch continues, `event.currentTarget` is modified to point to the current target of another event handler and nulled out at the end of the dispatch
22-
5. The async function resumes
23-
6. `event.currentTarget` is now `null`
24-
25-
## Solutions
26-
27-
If you're using `async`, you'll need to synchronously create a reference to `currentTarget` before any async activity.
30+
👍 Examples of **correct** code for this rule:
2831

2932
```js
30-
// good
31-
document.addEventListener('click', function(event) {
33+
document.addEventListener('click', function (event) {
3234
const currentTarget = event.currentTarget
3335
const url = currentTarget.getAttribute('data-url')
3436

3537
// call async IIFE
36-
;(async function() {
38+
;(async function () {
3739
const data = await fetch(url)
3840

3941
const text = currentTarget.getAttribute('data-text')
@@ -45,8 +47,7 @@ document.addEventListener('click', function(event) {
4547
Alternatively, extract a function to create an element reference.
4648

4749
```js
48-
// good
49-
document.addEventListener('click', function(event) {
50+
document.addEventListener('click', function (event) {
5051
fetchData(event.currentTarget)
5152
})
5253

@@ -57,3 +58,7 @@ async function fetchData(el) {
5758
// ...
5859
}
5960
```
61+
62+
## Version
63+
64+
4.3.2

‎docs/rules/async-preventdefault.md

+21-17
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
1-
# `event.preventDefault()` in an async function
1+
# Async Preventdefault
22

33
Using `event.preventDefault()` inside an `async function()` won't likely work as you'd expect because synchronous nature of event dispatch.
44

5-
```js
6-
// bad
7-
document.addEventListener('click', async function(event) {
8-
event.preventDefault()
9-
10-
const data = await fetch()
11-
// ...
12-
})
13-
```
5+
## Rule Details
146

157
1. A `click` event is dispatched
168
2. This handler is scheduled but not ran immediately because its marked async.
179
3. The event dispatch completes and nothing has called `preventDefault()` _yet_ and the default click behavior occurs.
1810
4. The async function is scheduled and runs.
1911
5. Calling `preventDefault()` is now a no-op as the synchronous event dispatch has already completed.
2012

21-
## Solutions
22-
2313
If you're using `async`, you likely need to wait on a promise in the event handler. In this case you can split the event handler in two parts, one synchronous and asynchronous.
2414

15+
👎 Examples of **incorrect** code for this rule:
16+
2517
```js
26-
// good
27-
document.addEventListener('click', function(event) {
18+
document.addEventListener('click', async function (event) {
19+
const data = await fetch()
20+
21+
event.preventDefault()
22+
})
23+
```
24+
25+
👍 Examples of **correct** code for this rule:
26+
27+
```js
28+
document.addEventListener('click', function (event) {
2829
// preventDefault in a regular function
2930
event.preventDefault()
3031

@@ -41,15 +42,18 @@ async function loadData(el) {
4142
This could also be done with an async IIFE.
4243

4344
```js
44-
// good
45-
document.addEventListener('click', function(event) {
45+
document.addEventListener('click', function (event) {
4646
// preventDefault in a regular function
4747
event.preventDefault()
4848

4949
// call async IIFE
50-
;(async function() {
50+
;(async function () {
5151
const data = await fetch()
5252
// ...
5353
})()
5454
})
5555
```
56+
57+
## Version
58+
59+
4.3.2

‎docs/rules/authenticity-token.md

+18-10
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
# `<input name="authenticity_token">`
1+
# Authenticity Token
2+
3+
## Rule Details
24

35
The Rails `form_tag` helper creates a `<form>` element with a `<input name="authenticity_token">` child element. The authenticity-token input tag contains a [Cross-Site Request Forgery (CSRF)](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29) token that is verified by the Rails app when the form is submitted.
46

57
An attacker who is able to steal a user's CSRF token can perform a CSRF attack against that user. To reduce this risk, GitHub uses per-form CSRF tokens. This means that a form's method and action are embedded in that form's CSRF token. When the form is submitted, the Rails application verifies that the request's path and method match those of the CSRF token: A stolen token for the `POST /preview` endpoint will not be accepted for the `DELETE /github/github` endpoint.
68

7-
## CSRF tokens in JavaScript
8-
99
Requests initiated by JavaScript using XHR or Fetch still need to include a CSRF token. Prior to our use of per-form tokens, a common pattern for getting a valid CSRF token to include in a request was
1010

11+
Unless the JavaScript's request is for the same method/action as the form from which it takes the CSRF token, this CSRF token will _not_ be accepted by the Rails application.
12+
13+
The preferred way to make an HTTP request with JavaScript is to use the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) API to serialize the input elements of a form:
14+
15+
👎 Examples of **incorrect** code for this rule:
16+
1117
```js
1218
const csrfToken = this.closest('form').elements['authenticity_token'].value
1319
```
1420

15-
Unless the JavaScript's request is for the same method/action as the form from which it takes the CSRF token, this CSRF token will _not_ be accepted by the Rails application.
16-
17-
The preferred way to make an HTTP request with JavaScript is to use the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) API to serialize the input elements of a form:
21+
👍 Examples of **correct** code for this rule:
1822

1923
```erb
2024
<%= form_tag "/my/endpoint" do %>
@@ -24,13 +28,13 @@ The preferred way to make an HTTP request with JavaScript is to use the [`FormDa
2428
```
2529

2630
```js
27-
on('click', '.js-my-button', function(e) {
31+
on('click', '.js-my-button', function (e) {
2832
const form = this.closest('form')
2933

3034
fetch(form.action, {
3135
method: form.method,
3236
body: new FormData(form)
33-
}).then(function() {
37+
}).then(function () {
3438
alert('Success!')
3539
})
3640

@@ -45,14 +49,18 @@ An alternate, but less preferred approach is to include the a signed CSRF url in
4549
```
4650

4751
```js
48-
on('click', '.js-my-button', function(e) {
52+
on('click', '.js-my-button', function (e) {
4953
csrfRequest(this.getAttribute('data-url'), {
5054
method: 'PUT',
5155
body: data
52-
}).then(function() {
56+
}).then(function () {
5357
alert('Success!')
5458
})
5559

5660
e.preventDefault()
5761
})
5862
```
63+
64+
## Version
65+
66+
4.3.2

‎docs/rules/get-attribute.md

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
1-
# getAttribute
1+
# Get Attribute
2+
3+
## Rule Details
24

35
As HTML attributes are case insensitive, prefer using lowercase.
46

7+
👎 Examples of **incorrect** code for this rule:
8+
59
```js
6-
// bad
710
el.getAttribute('autoComplete')
11+
```
12+
13+
```js
814
el.getAttribute('dataFoo')
15+
```
16+
17+
👍 Examples of **correct** code for this rule:
918

10-
// good
19+
```js
1120
el.getAttribute('autocomplete')
21+
```
22+
23+
```js
1224
el.getAttribute('data-foo')
1325
```
26+
27+
## Version
28+
29+
4.3.2

‎docs/rules/js-class-name.md

+19-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# JS prefixed class names
1+
# JS Class Name
22

33
JavaScript should only query and handle events for `js-` prefixed class names.
44

5-
## Statically declared
5+
## Rule Details
66

77
The key benefit is that these symbols can be easily searched for.
88

@@ -12,20 +12,30 @@ Since its easy for humans to cross reference usage sites and implementation, so
1212

1313
In order to trust this system, all `js-` class names MUST be statically written as string literals. This means no dynamically constructed strings by interpolation. For the same reason, `obj.send("can_#{sym}?")` makes you feel bad deep down inside, so should `querySelector("js-" + sym)`.
1414

15-
### Alternatives
16-
1715
Typically dynamically constructed `js-` classes are often mixing static symbols and user data together. Like `"js-org-#{org.login}"`. In this case, separating into a `data-` attribute would be a better solution.
1816

1917
```html
20-
<div class="js-org-update" data-org-name="<%= org.login %>">
18+
<div class="js-org-update" data-org-name="<%= org.login %>"></div>
2119
```
2220

2321
Allows you to select elements by `js-org-update` and still filter by the `data-org-name` attribute if you need to. Both `js-org-update` and `data-org-name` are clearly static symbols that are easy to search for.
2422

25-
## Formatting
26-
2723
`js-` classes must start with `js-` (obviously) and only contain lowercase letters and numbers separated by `-`s. The ESLint [`github/js-class-name`](https://github.com/github/eslint-plugin-github/blob/master/lib/rules/js-class-name.js) rule enforces this style.
2824

29-
## See Also
30-
3125
[@defunkt's original proposal from 2010](https://web.archive.org/web/20180902223055/http://ozmm.org/posts/slightly_obtrusive_javascript.html).
26+
27+
👎 Examples of **incorrect** code for this rule:
28+
29+
```js
30+
const el = document.querySelector('.js-Foo')
31+
```
32+
33+
👍 Examples of **correct** code for this rule:
34+
35+
```js
36+
const el = document.querySelector('.js-foo')
37+
```
38+
39+
## Version
40+
41+
4.3.2

‎docs/rules/no-blur.md

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
1-
# No `element.blur()`
1+
# No Blur
22

33
Do not use `element.blur()`. Blurring an element causes the focus position to be reset causing accessibility issues when using keyboard or voice navigation. Instead, restore focus by calling `element.focus()` on a prior element.
44

5+
## Rule Details
6+
7+
- [Use of `blur()` is discouraged by WHATWG HTML spec](https://html.spec.whatwg.org/multipage/interaction.html#dom-blur)
8+
9+
👎 Examples of **incorrect** code for this rule:
10+
511
```js
6-
// bad
712
menu.addEventListener('close', () => {
813
input.blur()
914
})
15+
```
16+
17+
👍 Examples of **correct** code for this rule:
1018

11-
// good
19+
```js
1220
menu.addEventListener('open', () => {
1321
const previouslyFocusedElement = document.activeElement
1422

@@ -20,6 +28,6 @@ menu.addEventListener('open', () => {
2028
})
2129
```
2230

23-
## See Also
31+
## Version
2432

25-
- [Use of `blur()` is discouraged by WHATWG HTML spec](https://html.spec.whatwg.org/multipage/interaction.html#dom-blur)
33+
4.3.2

‎docs/rules/no-d-none.md

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
1-
# No `d-none`
1+
# No D None
2+
3+
## Rule Details
24

35
Ideally JavaScript behaviors should not rely on Primer CSS when the `hidden` property can be used.
46

5-
```html
6-
<div hidden>
7+
👎 Examples of **incorrect** code for this rule:
8+
9+
```js
10+
div.classList.add('d-none')
711
```
812

13+
👍 Examples of **correct** code for this rule:
14+
915
```js
1016
div.hidden = false
1117
```
18+
19+
## Version
20+
21+
4.3.2

‎docs/rules/no-dataset.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1-
# No `dataset`
1+
# No Dataset
2+
3+
## Rule Details
24

35
Due to [camel-case transformations](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset#Name_conversion), using dataset is not easily greppable. Instead, use `el.getAttribute('data-what-ever')`.
6+
7+
👎 Examples of **incorrect** code for this rule:
8+
9+
```js
10+
el.dataset.coolThing
11+
```
12+
13+
👍 Examples of **correct** code for this rule:
14+
15+
```js
16+
el.getAttribute('data-cool-thing')
17+
```
18+
19+
## Version
20+
21+
4.3.2

‎docs/rules/no-flowfixme.md

-1
This file was deleted.
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# No Implicit Buggy Globals
2+
3+
## Rule Details
4+
5+
👎 Examples of **incorrect** code for this rule:
6+
7+
```js
8+
var foo = 1
9+
```
10+
11+
👍 Examples of **correct** code for this rule:
12+
13+
```js
14+
;(function () {
15+
const foo = 1
16+
})()
17+
```
18+
19+
## Version
20+
21+
4.3.2

‎docs/rules/no-inner-html.md

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
1-
# No `innerHTML`
1+
# No Inner HTML
2+
3+
## Rule Details
24

35
Using `innerHTML` poses a potential security risk. Prefer using `textContent` to set text to an element.
46

7+
https://github.com/github/paste-markdown/security/advisories/GHSA-gpfj-4j6g-c4w9
8+
9+
👎 Examples of **incorrect** code for this rule:
10+
511
```js
6-
// bad
712
function setContent(element, content) {
813
element.innerHTML = content
914
}
15+
```
16+
17+
👍 Examples of **correct** code for this rule:
1018

11-
// good
19+
```js
1220
function setContent(element, content) {
1321
element.textContent = content
1422
}
1523
```
1624

17-
## See Also
25+
## Version
1826

19-
https://github.com/github/paste-markdown/security/advisories/GHSA-gpfj-4j6g-c4w9
27+
4.3.2

‎docs/rules/no-innerText.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# No InnerText
2+
3+
## Rule Details
4+
5+
👎 Examples of **incorrect** code for this rule:
6+
7+
```js
8+
const el = document.createElement('div')
9+
el.innerText = 'foo'
10+
```
11+
12+
👍 Examples of **correct** code for this rule:
13+
14+
```js
15+
const el = document.createElement('div')
16+
el.textContent = 'foo'
17+
```
18+
19+
## Version
20+
21+
4.3.2

‎docs/rules/no-then.md

+19-14
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,36 @@
1-
# No `Promise.then`
1+
# No Then
2+
3+
## Rule Details
24

35
Yes, you should use promises, but prefer `async`/`await` syntax instead of `Promise.then()` callback chaining.
46

7+
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
8+
9+
👎 Examples of **incorrect** code for this rule:
10+
511
```js
6-
// bad
712
function getProcessedData(url) {
8-
return downloadData(url)
9-
.catch(e => {
10-
return downloadFallbackData(url)
11-
})
12-
.then(v => {
13-
return processDataInWorker(v)
14-
})
13+
return downloadData(url).catch(e => {
14+
console.log('Error occured!', e)
15+
})
1516
}
17+
```
18+
19+
👍 Examples of **correct** code for this rule:
1620

17-
// good
21+
```js
1822
async function getProcessedData(url) {
1923
let v
2024
try {
2125
v = await downloadData(url)
2226
} catch (e) {
23-
v = await downloadFallbackData(url)
27+
console.log('Error occured!', e)
28+
return
2429
}
25-
return processDataInWorker(v)
30+
return v
2631
}
2732
```
2833

29-
## See Also
34+
## Version
3035

31-
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
36+
4.3.2

‎docs/rules/no-useless-passive.md

+26-10
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,36 @@ This rule disallows setting `passive: true` for events on which it will have no
44

55
Events where `passive: true` has an effect are: `touchstart`, `touchmove`, `wheel`, and `mousewheel`.
66

7+
## Rule Details
8+
9+
Adding `passive: true` informs the browser that this event will not be calling `preventDefault` and as such is safe to asynchronously dispatch, freeing up the main thread for lag-free operation. However many events are not cancel-able and as such setting `passive: true` will have no effect on the browser.
10+
11+
It is safe to leave the option set, but this may have a negative effect on code authors, as they might believe setting `passive: true` has a positive effect on their operations, leading to a false-confidence in code with `passive: true`. As such, removing the option where it has no effect demonstrates to the code author that this code will need to avoid expensive operations as this might have a detrimental affect on UI performance.
12+
13+
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners
14+
15+
👎 Examples of **incorrect** code for this rule:
16+
717
```js
818
// bad (passive has no effect here)
9-
window.addEventListener('scroll', () => { /* ... */ }, { passive: true })
10-
11-
// good
12-
window.addEventListener('scroll', () => { /* ... */ })
19+
window.addEventListener(
20+
'scroll',
21+
() => {
22+
console.log('Scroll event fired!')
23+
},
24+
{passive: true}
25+
)
1326
```
1427

15-
## Why?
16-
17-
Adding `passive: true` informs the browser that this event will not be calling `preventDefault` and as such is safe to asynchronously dispatch, freeing up the main thread for lag-free operation. However many events are not cancel-able and as such setting `passive: true` will have no effect on the browser.
28+
👍 Examples of **correct** code for this rule:
1829

19-
It is safe to leave the option set, but this may have a negative effect on code authors, as they might believe setting `passive: true` has a positive effect on their operations, leading to a false-confidence in code with `passive: true`. As such, removing the option where it has no effect demonstrates to the code author that this code will need to avoid expensive operations as this might have a detrimental affect on UI performance.
30+
```js
31+
// good
32+
window.addEventListener('scroll', () => {
33+
console.log('Scroll event fired!')
34+
})
35+
```
2036

21-
## See Also
37+
## Version
2238

23-
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners
39+
4.3.2

‎docs/rules/prefer-observers.md

+31-21
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,56 @@
22

33
Some events, such as `scroll` and `resize` have traditionally caused performance issues on web pages, as they are high frequency events, firing many times per second as the user interacts with the page viewport.
44

5-
## Scroll vs IntersectionObserver
5+
## Rule Details
66

77
Typically `scroll` events are used to determine if an element is intersecting a viewport, which can result in expensive operations such as layout calculations, which has a detrimental affect on UI performance. Recent developments in web standards have introduced the `IntersectionObserver`, which is a more performant mechanism for determining if an element is intersecting the viewport.
88

9+
Similarly, `resize` events are typically used to determine if the viewport is smaller or larger than a certain size, similar to CSS media break points. Similar to the `IntersectionObserver` the `ResizeObserver` also exists as a more performant mechanism for observing changes to the viewport size.
10+
911
```js
10-
// bad, expensive, error-prone code to determine if the element is in the viewport;
11-
window.addEventListener('scroll', () => {
12-
const isIntersecting = checkIfElementIntersects(element.getBoundingClientRect(), window.innerHeight, document.clientHeight)
13-
element.classList.toggle('intersecting', isIntersecting)
12+
// bad, low-performing code to determine if the element is less than 500px large
13+
window.addEventListener('resize', () => {
14+
element.classList.toggle('size-small', element.getBoundingClientRect().width < 500)
1415
})
1516

16-
// good - more performant and less error-prone
17-
const observer = new IntersectionObserver(entries => {
18-
for(const {target, isIntersecting} of entries) {
19-
target.classList.toggle(target, isIntersecting)
17+
// good - more performant, only fires when the actual elements change size
18+
const observer = new ResizeObserver(entries => {
19+
for (const {target, contentRect} of entries) {
20+
target.classList.toggle('size-small', contentRect.width < 500)
2021
}
2122
})
2223
observer.observe(element)
2324
```
2425

25-
## Resize vs ResizeObserver
26+
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
27+
https://developer.mozilla.org/en-US/docs/Web/API/Resize_Observer_API
2628

27-
Similarly, `resize` events are typically used to determine if the viewport is smaller or larger than a certain size, similar to CSS media break points. Similar to the `IntersectionObserver` the `ResizeObserver` also exists as a more performant mechanism for observing changes to the viewport size.
29+
👎 Examples of **incorrect** code for this rule:
2830

2931
```js
30-
// bad, low-performing code to determine if the element is less than 500px large
31-
window.addEventListener('resize', () => {
32-
element.classList.toggle('size-small', element.getBoundingClientRect().width < 500)
32+
// bad, expensive, error-prone code to determine if the element is in the viewport;
33+
window.addEventListener('scroll', () => {
34+
const isIntersecting = checkIfElementIntersects(
35+
element.getBoundingClientRect(),
36+
window.innerHeight,
37+
document.clientHeight
38+
)
39+
element.classList.toggle('intersecting', isIntersecting)
3340
})
41+
```
3442

35-
// good - more performant, only fires when the actual elements change size
36-
const observer = new ResizeObserver(entries => {
37-
for(const {target, contentRect} of entries) {
38-
target.classList.toggle('size-small', contentRect.width < 500)
43+
👍 Examples of **correct** code for this rule:
44+
45+
```js
46+
// good - more performant and less error-prone
47+
const observer = new IntersectionObserver(entries => {
48+
for (const {target, isIntersecting} of entries) {
49+
target.classList.toggle(target, isIntersecting)
3950
}
4051
})
4152
observer.observe(element)
4253
```
4354

44-
## See Also
55+
## Version
4556

46-
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
47-
https://developer.mozilla.org/en-US/docs/Web/API/Resize_Observer_API
57+
4.3.2

‎docs/rules/require-passive-events.md

+26-10
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,36 @@
22

33
This rule enforces adding `passive: true` to high frequency event listeners (`touchstart`, `touchmove`, `wheel`, `mousewheel`).
44

5+
## Rule Details
6+
7+
Adding these events listeners can block the main thread as it waits to find out if the callbacks call `preventDefault`. This can cause large amounts UI lag, which will be noticeable for users.
8+
9+
Adding `passive: true` informs the browser that this event will not be calling `preventDefault` and as such is safe to asynchronously dispatch, freeing up the main thread for lag-free operation.
10+
11+
See also: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners
12+
13+
👎 Examples of **incorrect** code for this rule:
14+
515
```js
616
// bad
7-
window.addEventListener('touchstart', () => { /* ... */ })
8-
9-
// good
10-
window.addEventListener('touchstart', () => { /* ... */ }, { passive: true })
17+
window.addEventListener('touchstart', () => {
18+
/* ... */
19+
})
1120
```
1221

13-
## Why?
14-
15-
Adding these events listeners can block the main thread as it waits to find out if the callbacks call `preventDefault`. This can cause large amounts UI lag, which will be noticeable for users.
22+
👍 Examples of **correct** code for this rule:
1623

17-
Adding `passive: true` informs the browser that this event will not be calling `preventDefault` and as such is safe to asynchronously dispatch, freeing up the main thread for lag-free operation.
24+
```js
25+
// good
26+
window.addEventListener(
27+
'touchstart',
28+
() => {
29+
/* ... */
30+
},
31+
{passive: true}
32+
)
33+
```
1834

19-
## See Also
35+
## Version
2036

21-
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners
37+
4.3.2

‎docs/rules/unescaped-html-literal.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
1-
# Avoid unescaped HTML string literals
1+
# Unescaped HTML Literal
2+
3+
## Rule Details
24

35
Constructing raw HTML with string literals is error prone and may lead to security issues.
46

57
Instead use [`lit-html`](https://github.com/Polymer/lit-html)'s `html` tagged template literal to safely construct HTML literal strings. Alternatively, you can use document builder APIs like `document.createElement`.
68

9+
👎 Examples of **incorrect** code for this rule:
10+
711
```js
8-
// bad
912
const title = `<h1>Hello ${name}!</h1>`
13+
```
14+
15+
👍 Examples of **correct** code for this rule:
1016

17+
```js
1118
// good
1219
const title = html`<h1>Hello ${name}!</h1>`
20+
```
1321

22+
```js
1423
// also good
1524
const title = document.createElement('h1')
1625
title.textContent = `Hello ${name}!`
1726
```
1827

19-
## See Also
28+
## Version
2029

21-
https://github.com/Polymer/lit-html
30+
4.3.2

‎lib/index.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ module.exports = {
1010
'no-d-none': require('./rules/no-d-none'),
1111
'no-dataset': require('./rules/no-dataset'),
1212
'no-implicit-buggy-globals': require('./rules/no-implicit-buggy-globals'),
13-
'no-innerText': require('./rules/no-innerText'),
1413
'no-inner-html': require('./rules/no-inner-html'),
14+
'no-innerText': require('./rules/no-innerText'),
1515
'no-then': require('./rules/no-then'),
16-
'unescaped-html-literal': require('./rules/unescaped-html-literal'),
1716
'no-useless-passive': require('./rules/no-useless-passive'),
1817
'prefer-observers': require('./rules/prefer-observers'),
19-
'require-passive-events': require('./rules/require-passive-events')
18+
'require-passive-events': require('./rules/require-passive-events'),
19+
'unescaped-html-literal': require('./rules/unescaped-html-literal')
2020
},
2121
configs: {
22-
internal: require('./configs/internal'),
2322
browser: require('./configs/browser'),
23+
internal: require('./configs/internal'),
2424
recommended: require('./configs/recommended'),
2525
typescript: require('./configs/typescript')
2626
}

‎package-lock.json

+235-124
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.