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

Improve documentation #10

Draft
wants to merge 34 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d0d5337
Add a script to split rules into categories
nickserv Jul 22, 2020
3251c9f
Start documenting more rules
nickserv Jul 21, 2020
9f9f6e1
Move website (WIP) examples back into readme
nickserv Jul 22, 2020
b02d249
Add remaining examples for problematic features
nickserv Jul 22, 2020
2081e3e
Remove redundant suggestion
nickserv Jul 22, 2020
7fba0b8
Improve formatting of categories
nickserv Jul 22, 2020
f37f5a6
Add rule coverge script (with one exmple for now)
nickserv Jul 22, 2020
5be0869
Integrate coverage into categories script
nickserv Jul 22, 2020
1ec4a2b
Add documented counts
nickserv Jul 22, 2020
50c2bb7
Use emoji for more visual code snippets
nickserv Jul 29, 2022
e9f86a1
Merge branch 'main' into documentation
nickserv Jul 29, 2022
996d0d8
Merge branch 'main' into documentation
nickserv Jul 29, 2022
7596cde
Also restrict Promise.catch
nickserv Jul 29, 2022
559331a
Format categories.js
nickserv Jul 29, 2022
d908c35
Merge branch 'main' into documentation
nickserv Jul 29, 2022
f45f67a
Upgrade Lodash
nickserv Jul 29, 2022
c7dc23a
Add difference script
nickserv Jul 29, 2022
2c97d20
Add scripts to package.json
nickserv Jul 29, 2022
cdfbfc4
Document more ESLint rules
nickserv Jul 29, 2022
8908f2a
Merge branch 'main' into documentation
nickserv Jul 30, 2022
672b0a3
Use colored emoji in categories script
nickserv Jul 30, 2022
95ec072
Inline functions
nickserv Jul 30, 2022
e6faa4b
Only enable sort-keys for index.js
nickserv Jul 31, 2022
ed75e5a
Merge branch 'main' into documentation
nickserv Jul 31, 2022
109ec16
Merge branch 'main' into documentation
nickserv Jul 31, 2022
302cb48
Merge branch 'main' into documentation
nickserv Aug 2, 2022
d6e6bc3
Merge scripts and show rules as a table
nickserv Aug 2, 2022
dc8c479
Use functional style for status
nickserv Aug 2, 2022
4010287
Reorder variables
nickserv Aug 2, 2022
359ff46
Inline variables
nickserv Aug 2, 2022
9bac53b
Import rules directly
nickserv Aug 2, 2022
50c6d2f
Rename categories to rules
nickserv Aug 2, 2022
c25156f
Upload rules to a Notion database
nickserv Aug 2, 2022
a999cc5
Merge branch 'main' into documentation
nickserv Aug 3, 2022
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
@@ -1 +1,2 @@
node_modules
.env
240 changes: 223 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,213 @@ While configuration is not required, it's recommended you enable support for ES2

JavaScript has many problematic and difficult to understand syntax features that have been replaced by newer features. Migrating to new features can drastically improve the readability, safety, and consistency of JavaScript.

- Use ES classes with `class` instead of constructor functions with `function`.
- Use ES modules instead of globals or `"use strict"`.
- Replace `var` with `const` if you don't need to reassign the variable, and `let` if you do.
- Replace `for (const i = 0; i < array.length; i++)` and `for (const value in array)` with `for (const value of array)`.
- Only use `` for string concatenation and + for addition.
- Use spread arguments like `...args` instead of `arguments`.
- Prefer Promises over callbacks when using async errors.
- Prefer `async`/`await` over `.then()` to use Promises.
- Don't give `parseInt()` a radix, it properly defaults to 10 on modern browsers.
- Don't assign `this` to a variable, use arrow functions or `.bind()` to avoid shadowing.
- Use `===` and `!==` instead of `==` and `!=`.
- Use `fetch` or a third party library to make requests in browsers instead of `XMLHTTPRequest`. `undici` is a good alternative for Node.
#### Replace constructor functions with classes

```js
// ❌
function Animal() {}
Animal.prototype.speak = function () {
return this
}

// ✅
class Animal {
speak() {
return this
}
}
```

#### Replace globals and CJS modules with ES modules (`no-implicit-globals`)

The ES module standard makes it easy to safely reuse JavaScript code across files without leaking into the global scope, and enables useful tooling features like tree shaking and loaders.

```js
// ❌ globals
window.greeting = "Hello, world!"

// ❌ CJS
exports.greeting = "Hello, world!"

// ✅ ES
export const greeting = "Hello, world!"
```

#### Replace `var` with `let`/`const` (`no-const-assign`, `no-var`, `prefer-const`)

Variables created with `var` are hoisted to the nearest function, which can cause confusing behavior with the order of accessing variables and variables overriding each other in nearby scopes. `let` and `const` replace `var` with more predictable block scoping (typical with other programming languages). `const` should be preferred if you don't need to reassign the variable, otherwise use `let`.

```js
// ❌
var greeting = "Hello, world!"
var enabled = true
enabled = false

// ✅
const greeting = "Hello, world!"
let enabled = true
enabled = false
```

#### Use iteration with `for`

C-style `for` loops are often unnecessarily complicated and error prone for basic iteration. `for...of` can replace it in most cases, and in other cases you should prefer other iteration operators or methods.

```js
// ❌
for (const i = 0; i < array.length; i++) {
console.log(array[i])
}

// ✅
for (const value of array) {
console.log(value)
}
```

#### Replace `for...in` with `for...of` (`no-restricted-syntax`)

Unfortunately `for...in` loops include the entire prototype chain, not just iterable items in an object. This can cause confusing behavior, like logging methods of a custom array type when you only want to log array items. `for...of` is similar, but it uses iterators to only loop over iterable items.

```js
// ❌
for (const value in array)

// ✅
for (const value of array)
```

#### Replace string concatenation with template literals (`prefer-template`)

`+` can have ambiguous behavior if it's used between strings and numbers interchangeably. To avoid bugs and unwanted formatting, it's better to use template literal syntax (which also allows for custom templates) for strings and exclusively use `+` for math.

```js
// ❌
const name = "world"
console.log("Hello, " + name + "!")

// ✅
const name = "world"
console.log(`Hello, ${name}!`)
```

#### Replace the `arguments` keyword with spread arguments (`prefer-rest-params`)

`arguments` used to be the only way to get a variable number of arguments from a function dynamically, but it isn't supported in arrow functions and confusingly is not an actual Array object. The spread argument (`...`) solves this issue and works with both `function` and arrow functions.

```js
// ❌
function joinWords() {
return Array.from(arguments).join(" ")
}

// ✅
function joinWords(...args) {
return args.join(" ")
}
```

#### Prefer Promises over callbacks when using async errors

Originally, callbacks were the only basic primitive for asyncronous operations in JavaScript. However, chaining together asyncronous operations with multiple callbacks can often result in messy indentation. Callbacks also don't necessarily have consistent error handling, making error handling logic repetitive and easy to misuse. Promises on the other hand are objects that represent a future value, running listeners when complete. This makes asyncronous data processing and error handling easier and less error-prone.

```js
// ❌
function getFirstResult(callback) {
getResults((error, results) {
if (error) {
callback(error)
} else {
callback(null,results[0])
}
})
}

// ✅
async function getFirstResult() {
const results = await getResults()
return results[0]
}
```

#### Prefer `async`/`await` over `.then()` to use Promises (`no-restricted-properties`)

`async` functions allow for more convenient usage of Promise values without manually nesting or chaining Promises. Instead of using `.then()` to wait for a Promise to resolve and `.catch()` to handle errors, use the Promise in an `async` function with `await` with an ordinary `try`/`catch` clause for error handling (each Promise chain should have at least one handler).

```js
// ❌
function getExample() {
return fetch("https://example.com")
.then((response) => response.text())
.catch((error) => console.error(error))
}

// ✅
async function getExample() {
try {
const response = await fetch("https://example.com")
return response.text()
} catch (error) {
console.error(error)
}
}
```

#### Use `parseInt()` without a radix for base 10

Modern JavaScript specs require the radix/base of integer parsing to always be 10, so defensively passing `10` as the second argument is no longer necessary.

```js
// ❌
const integer = parseInt(string, 10)

// ✅
const integer = parseInt(string)
```

#### Don't assign `this` to a variable, use arrow functions or `.bind()` to avoid shadowing (`consistent-this`)

```js
// ❌
const that = this
const processedItems = items.map(function (item) {
return that.processItem(item)
})

// ✅
const processedItems = items.map((item) => this.processItem(item))
```

#### Use strict mode in all files (`strict`)

Strict mode is automatically enabled in ES modules, so you only need `"use strict"` at the top of files that don't use `import` or `export`.

```js
// ❌
console.log("Hello, world!")

// ✅ file
import greeting from "./greeting" // or if you don't have imports/exports, use "use-strict" on the first line
console.log(greeting)
```

#### Use `===` and `!==` instead of `==` and `!=` (`eqeqeq`)

JavaScript's default comparison operators (`==` and `!=`) can compare different types of primitives, leading to confusion behavior and bugs from unexpected types and other coercion issues. It's better to explicitly use `===` and `!==`, which check to see if values are equivalent and of the same type.

```js
// ❌
nameOrId == 1

// ✅
nameOrId === 1
```

#### Use `fetch` instead of `XMLHTTPRequest` (`no-restricted-globals`)

`XMLHTTPRequest` is a fairly old API for making HTTP/AJAX requests in browsers. It relies on multiple events and callbacks, making it more difficult to learn and harder to reuse. `fetch` is a more modern alternative that's entirely based on Promises, and therefore has better data management and error handling (see above).

If you're using Node 18+, you can use `fetch` globally. If not, we recommend `undici` as Node's legacy HTTP client is also fairly complex.

### Improve semantics

Expand All @@ -87,11 +282,22 @@ Some features have potentially dangerous or confusing usages and can be improved
Some features are too dangerous or confusing to be worth using.

- Avoid `eval()`, use other techniques to make code dynamic.
- Avoid `continue`, use conditional statements inside blocks instead.
- Avoid typed wrappers (Boolean/Number/String/Object/Array), use literals like `false`, `0`, `''`, `{}`, and `[]` instead.
- Avoid `with`, manually read properties or use destructuring instead.
- Avoid `void`, it's better to use `undefined` or break up multiple statements instead.
- Avoid bitwise operators like `&` and `|`, they're usually confused with the `&&` and `||` (and/or) operators, and bitwise operations are not very performant or useful in JavaScript.
- Avoid `continue`, use conditional statements inside blocks instead. (`no-continue`)
- Avoid typed wrappers (Boolean/Number/String/Object/Array), use literals like `false`, `0`, `''`, `{}`, and `[]` instead. (`no-new-wrappers`)
- Avoid `with`, manually read properties or use destructuring instead. (`no-with`)
- Avoid `void`, it's better to use `undefined` or break up multiple statements instead. (`no-void`)
- Avoid bitwise operators like `&` and `|`, they're usually confused with the `&&` and `||` (and/or) operators, and bitwise operations are not very performant or useful in JavaScript. (`no-bitwise`)
- Avoid labels (`no-labels`)

### Follow best practices

- Always define a getter for each setter (`accessor-pairs`)
- Return values in Array methods (`array-callback-return`)
- Always use `this` in class methods (`class-methods-use-this`)
- Consistently return values (`consistent-return`)
- Always provide a `default` in `case` as a fallback (`default-case`)
- Avoid using `[]` unnecessary when indexing (`dot-notation`)
- Use one class per file (`max-classes-per-file`)

## Inspiration

Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@
],
"repository": "nickmccurdy/eslint-config-modern",
"scripts": {
"test": "eslint ."
"test": "eslint .",
"rules": "node rules"
},
"devDependencies": {
"@notionhq/client": "^2.1.1",
"dotenv": "^16.0.1",
"eslint": "~6.2.0",
"eslint-config-prettier": "^6.11.0",
"prettier": "^2.7.1",
"prettier-config-nick": "^1.0.2"
},
Expand Down
91 changes: 91 additions & 0 deletions rules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* eslint camelcase: ["error", { allow: ["_id$"] }] */
"use strict"
const config = require(".")
const { Linter } = require("eslint")
const prettier = require("eslint-config-prettier")
const { readFile } = require("fs/promises")
const { Client } = require("@notionhq/client")
require("dotenv/config")

const notion = new Client({
auth: process.env.NOTION_TOKEN,
})

;(async () => {
const markdown = await readFile("README.md", "utf8")
const headingMatches = markdown.matchAll(/\((?<rules>`.*`)\)$/gmu)
const documentedRules = Array.from(headingMatches).flatMap(
({ groups: { rules } }) => {
const ruleMatches = rules.matchAll(/`(?<rule>[a-z-]+)`/gu)
return Array.from(ruleMatches).map(({ groups: { rule } }) => rule)
},
)

const rules = Object.fromEntries(
Array.from(new Linter().getRules().entries()).map(([rule, { meta }]) => [
rule,
{
category: meta.docs.category,
status:
prettier.rules[rule] === "off"
? "Prettier"
: documentedRules.includes(rule)
? "Documented"
: rule in config.rules
? "Implemented"
: "To-do",
docs: `https://eslint.org/docs/latest/rules/${rule}`,
},
]),
)

console.table(rules)

const { id } = await notion.databases.create({
parent: {
page_id: process.env.NOTION_PAGE,
},
title: [{ text: { content: "ESLint Rules" } }],
properties: {
ID: { title: {} },
Category: {
select: {
options: [
{ name: "Best Practices" },
{ name: "Stylistic Issues" },
{ name: "ECMAScript 6" },
{ name: "Node.js and CommonJS" },
{ name: "Possible Errors" },
{ name: "Variables" },
{ name: "Strict Mode" },
],
},
},
Status: {
select: {
options: [
{ name: "To-do" },
{ name: "Implemented" },
{ name: "Documented" },
{ name: "Prettier" },
],
},
},
Docs: { url: {} },
},
})

for (const [rule, { category, status, docs }] of Object.entries(rules)) {
notion.pages.create({
parent: {
database_id: id,
},
properties: {
ID: { title: [{ text: { content: rule } }] },
Category: { select: { name: category } },
Status: { select: { name: status } },
Docs: { url: docs },
},
})
}
})()
Loading