Skip to content

docs: add landing page for rules #4114

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

Merged
merged 8 commits into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/linting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ If you use [`prettier`](https://www.npmjs.com/package/prettier), there is also a

Using this config by adding it to the end of your `extends`:

```diff
```diff title=".eslintrc.js"
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
Expand All @@ -121,7 +121,7 @@ A few popular all-in-one configs are:
To use one of these complete config packages, you would replace the `extends` with the package name.
For example:

```diff
```diff title=".eslintrc.js"
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
Expand Down Expand Up @@ -154,7 +154,7 @@ Below are just a few examples:
Every plugin that is out there includes documentation on the various configurations and rules they offer.
A typical plugin might be used like:

```diff
```diff title=".eslintrc.js"
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
Expand Down
159 changes: 159 additions & 0 deletions packages/eslint-plugin/docs/rules/README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/eslint-plugin/docs/rules/comma-dangle.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ See the [ESLint documentation](https://eslint.org/docs/rules/comma-dangle) for m

## Rule Changes

```cjson
```jsonc
{
// note you must disable the base rule as it can report incorrect errors
"comma-dangle": "off",
Expand Down
55 changes: 29 additions & 26 deletions packages/eslint-plugin/docs/rules/member-delimiter-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,17 @@ type Config = BaseConfig & {

Default config:

```JSON
```json
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
},
"multilineDetection": "brackets"
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
},
"multilineDetection": "brackets"
}
```

Expand All @@ -108,7 +108,10 @@ Accepts three values (or two for `singleline`):
- `comma` - each member should be delimited with a comma (`,`).
- `semi` - each member should be delimited with a semicolon (`;`).
- `none` - each member should be delimited with nothing.
- NOTE - this is not an option for `singleline` because having no delimiter between members on a single line is a syntax error in TS.

:::note
`none` is not an option for `singleline` because having no delimiter between members on a single line is a syntax error in TS.
:::

### `requireLast`

Expand All @@ -123,24 +126,24 @@ Allows you to specify options specifically for either `interface`s or `type` def

For example, to require commas for `type`s, and semicolons for multiline `interface`s:

```JSON
```json
{
"multiline": {
"delimiter": "comma",
"requireLast": true
},
"singleline": {
"delimiter": "comma",
"multiline": {
"delimiter": "comma",
"requireLast": true
},
"singleline": {
"delimiter": "comma",
"requireLast": true
},
"overrides": {
"interface": {
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"overrides": {
"interface": {
"multiline": {
"delimiter": "semi",
"requireLast": true
}
}
}
}
}
}
```

Expand Down
4 changes: 2 additions & 2 deletions packages/eslint-plugin/docs/rules/no-inferrable-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ interface Options {

The default options are:

```JSON
```json
{
"ignoreParameters": false,
"ignoreProperties": false,
"ignoreProperties": false
}
```

Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin/docs/rules/object-curly-spacing.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ It adds support for TypeScript's object types.

## How to use

```cjson
```jsonc
{
// note you must disable the base rule as it can report incorrect errors
"object-curly-spacing": "off",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ foo.endsWith('bar');

There are no options.

```JSON
```json
{
"@typescript-eslint/prefer-string-starts-ends-with": "error"
"@typescript-eslint/prefer-string-starts-ends-with": "error"
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ When sorting numbers, this results in the classic "10 before 2" order:

This also means that `Array#sort` does not always sort consistently, as elements may have custom `#toString` implementations that are not deterministic; this trap is noted in the language specification thusly:

> NOTE 2: Method calls performed by the `ToString` abstract operations in steps 5 and 7 have the potential to cause `SortCompare` to not behave as a consistent comparison function.<br/> > https://www.ecma-international.org/ecma-262/9.0/#sec-sortcompare
:::note
Method calls performed by the `ToString` abstract operations in steps 5 and 7 have the potential to cause `SortCompare` to not behave as a consistent comparison function.

https://www.ecma-international.org/ecma-262/9.0/#sec-sortcompare
:::

## Rule Details

Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin/tests/docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('Validating rule docs', () => {
const files = fs
.readdirSync(docsRoot)
// this rule doc was left behind on purpose for legacy reasons
.filter(rule => rule !== 'camelcase.md');
.filter(rule => rule !== 'camelcase.md' && rule !== 'README.md');
const ruleFiles = Object.keys(rules)
.map(rule => `${rule}.md`)
.sort();
Expand Down
42 changes: 28 additions & 14 deletions packages/eslint-plugin/tools/generate-rules-lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,40 +51,49 @@ const returnEmojiIfTrue = <TKey extends keyof typeof emojiKey>(
obj: { [K in TKey]?: unknown },
): typeof emojiKey[TKey] | '' => (obj[key] ? emojiKey[key] : '');

const createRuleLink = (ruleName: string): string =>
`[\`@typescript-eslint/${ruleName}\`](./docs/rules/${ruleName}.md)`;
const createRuleLink = (ruleName: string, basePath: string): string =>
`[\`@typescript-eslint/${ruleName}\`](${basePath}${ruleName}.md)`;

const buildRuleRow = (rule: RuleDetails): RuleColumn => [
createRuleLink(rule.name),
const buildRuleRow = (rule: RuleDetails, basePath: string): RuleColumn => [
createRuleLink(rule.name, basePath),
rule.description,
returnEmojiIfTrue('recommended', rule),
returnEmojiIfTrue('fixable', rule),
returnEmojiIfTrue('requiresTypeChecking', rule),
];

const buildRulesTable = (rules: RuleDetails[]): string[][] => [
const buildRulesTable = (
rules: RuleDetails[],
basePath: string,
): string[][] => [
staticElements.listHeaderRow,
staticElements.listSpacerRow,
...rules
.sort(({ name: ruleNameA }, { name: ruleNameB }) =>
ruleNameA.localeCompare(ruleNameB),
)
.map(buildRuleRow),
.map(item => buildRuleRow(item, basePath)),
];

const generateRulesListMarkdown = (rules: RuleDetails[]): string =>
const generateRulesListMarkdown = (
rules: RuleDetails[],
basePath: string,
): string =>
[
'',
staticElements.rulesListKey,
'',
...buildRulesTable(rules).map(column => [...column, ' '].join('|')),
...buildRulesTable(rules, basePath).map(column =>
[...column, ' '].join('|'),
),
'',
].join('\n');

const updateRulesList = (
listName: 'base' | 'extension',
rules: RuleDetails[],
markdown: string,
basePath: string,
): string => {
const listBeginMarker = `<!-- begin ${listName} rule list -->`;
const listEndMarker = `<!-- end ${listName} rule list -->`;
Expand All @@ -100,7 +109,7 @@ const updateRulesList = (
markdown.substring(0, listStartIndex - 1),
listBeginMarker,
'',
generateRulesListMarkdown(rules), //
generateRulesListMarkdown(rules, basePath), //
markdown.substring(listEndIndex),
].join('\n');
};
Expand All @@ -119,11 +128,16 @@ const rulesDetails: RuleDetails[] = Object.entries(rules)
const baseRules = rulesDetails.filter(rule => !rule.extendsBaseRule);
const extensionRules = rulesDetails.filter(rule => rule.extendsBaseRule);

let readme = fs.readFileSync(path.resolve(__dirname, '../README.md'), 'utf8');
function updateFile(file: string, basePath: string): void {
let readme = fs.readFileSync(file, 'utf8');

readme = updateRulesList('base', baseRules, readme);
readme = updateRulesList('extension', extensionRules, readme);
readme = updateRulesList('base', baseRules, readme, basePath);
readme = updateRulesList('extension', extensionRules, readme, basePath);

readme = prettier.format(readme, { parser: 'markdown' });
readme = prettier.format(readme, { parser: 'markdown' });

fs.writeFileSync(file, readme, 'utf8');
}

fs.writeFileSync(path.resolve(__dirname, '../README.md'), readme, 'utf8');
updateFile(path.resolve(__dirname, '../README.md'), './docs/rules/');
updateFile(path.resolve(__dirname, '../docs/rules/README.md'), './');
2 changes: 1 addition & 1 deletion packages/website/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const config = {
position: 'left',
},
{
to: 'rules/ban-types',
to: 'rules/',
activeBasePath: 'rules',
label: 'Rules',
position: 'left',
Expand Down
65 changes: 53 additions & 12 deletions packages/website/sidebars/sidebar.rules.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,60 @@
const globby = require('globby');
const path = require('path');

const paths = globby.sync('*.md', {
cwd: path.join(__dirname, '../../eslint-plugin/docs/rules'),
const plugin = require('@typescript-eslint/eslint-plugin');

const rules = Object.entries(plugin.rules).map(([name, rule]) => {
return {
name: name,
meta: { ...rule.meta },
};
});

const notDeprecatedRules = rules.filter(rule => !rule.meta.deprecated);

const deprecatedRules = rules.filter(rule => rule.meta.deprecated);

const paths = globby
.sync('*.md', {
cwd: path.join(__dirname, '../../eslint-plugin/docs/rules'),
})
.map(item => {
return {
name: path.basename(item, '.md'),
};
})
.filter(item => {
return item.name !== 'README' && !rules.some(a => a.name === item.name);
});

module.exports = {
someSidebar: {
Rules: paths.map(item => {
const name = path.basename(item, '.md');
return {
type: 'doc',
id: name,
label: name,
};
}),
},
someSidebar: [
'README',
{
type: 'category',
label: 'Rules',
collapsible: true,
collapsed: false,
items: notDeprecatedRules.map(item => {
return {
type: 'doc',
id: item.name,
label: item.name,
};
}),
},
{
type: 'category',
label: 'Deprecated',
collapsible: true,
collapsed: false,
items: [...deprecatedRules, ...paths].map(item => {
return {
type: 'doc',
id: item.name,
label: item.name,
};
}),
},
],
};