Skip to content

Improved rule list doc #1860

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 13 commits into from
Apr 22, 2022
2 changes: 2 additions & 0 deletions .markdownlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ no-inline-html:
- badge
- eslint-code-block
- sup
- rules-table
- span

# enforce consistency
code-block-style:
Expand Down
90 changes: 90 additions & 0 deletions docs/.vuepress/components/rules-table.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<template>
<div>
<div v-if="kindMarks.length > 1" class="filter-tool">
Highlight:
<label v-for="kindMark in kindMarks" :key="kindMark">
<input type="checkbox" :value="kindMark" v-model="checkedKindMarks" />
<span class="emoji">{{ kindMark }}</span>
</label>
</div>
<div class="table-root" ref="tableRoot">
<slot />
</div>
</div>
</template>

<script>
const emojis = ['3️⃣', '2️⃣', '⚠️']
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intl.Segmenter is required to properly split these emojis, but FireFox doesn't support it, so we'll handle it ourselves.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter


export default {
data() {
return {
kindMarks: [],
checkedKindMarks: []
}
},
mounted() {
this.setup()
},
watch: {
checkedKindMarks: {
handler: 'filterTable',
deep: true
}
},
methods: {
setup() {
const kindMarks = new Set()
const table = this.getTable()
for (const row of table.rows) {
row.cells[3].classList.add('emoji')
const trimmed = row.cells[3].textContent.trim()
if (!trimmed) {
continue
}
const chars =
trimmed.match(new RegExp(`${emojis.join('|')}|.`, 'ug')) || []
for (const mark of chars) {
kindMarks.add(mark)
}
}
this.kindMarks = [...kindMarks]
},
getTable() {
return this.$refs.tableRoot.querySelector('table')
},
filterTable() {
const table = this.getTable()
if (this.checkedKindMarks.length === 0) {
table.classList.remove('highlighting')
return
}
table.classList.add('highlighting')
for (const row of table.rows) {
if (row.cells[0].tagName === 'TH') {
row.classList.add('highlight')
continue
}
const rowMarks = row.cells[3].textContent
const highlight = this.checkedKindMarks.every((mark) =>
rowMarks.includes(mark)
)
row.classList.toggle('highlight', highlight)
}
}
}
}
</script>

<style scoped>
.table-root ::v-deep .highlighting tr {
opacity: 0.3;
}
.table-root ::v-deep .highlighting .highlight {
opacity: 1;
}
.filter-tool label {
display: inline-flex;
margin-right: 0.5ex;
}
</style>
6 changes: 6 additions & 0 deletions docs/.vuepress/styles/index.styl
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@
}
}
}

.theme-container.rule-list .theme-default-content
max-width: 1280px;

.emoji
font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Apple Color Emoji", "Noto Color Emoji", "Noto Emoji", sans-serif
645 changes: 272 additions & 373 deletions docs/rules/README.md

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@
"eslint-plugin-unicorn": "^40.1.0",
"eslint-plugin-vue": "file:.",
"espree": "^9.0.0",
"lodash": "^4.17.21",
"markdownlint-cli": "^0.31.1",
"mocha": "^7.1.2",
"nyc": "^15.1.0",
Expand Down
42 changes: 42 additions & 0 deletions tools/lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module.exports = { getPresetIds, formatItems }

const presetCategories = {
base: null,
essential: 'base',
'vue3-essential': 'base',
'strongly-recommended': 'essential',
'vue3-strongly-recommended': 'vue3-essential',
recommended: 'strongly-recommended',
'vue3-recommended': 'vue3-strongly-recommended'
// 'use-with-caution': 'recommended',
// 'vue3-use-with-caution': 'vue3-recommended'
}

function formatItems(items, suffix) {
if (items.length === 1) {
return `${items[0]}${suffix ? ` ${suffix[0]}` : ''}`
}
if (items.length === 2) {
return `${items.join(' and ')}${suffix ? ` ${suffix[1]}` : ''}`
}
return `all of ${items.slice(0, -1).join(', ')} and ${[...items].pop()}${
suffix ? ` ${suffix[1]}` : ''
}`
}

function getPresetIds(categoryIds) {
const subsetCategoryIds = []
for (const categoryId of categoryIds) {
for (const [subsetCategoryId, supersetCategoryId] of Object.entries(
presetCategories
)) {
if (supersetCategoryId === categoryId) {
subsetCategoryIds.push(subsetCategoryId)
}
}
}
if (subsetCategoryIds.length === 0) {
return categoryIds
}
return [...new Set([...categoryIds, ...getPresetIds(subsetCategoryIds)])]
}
156 changes: 130 additions & 26 deletions tools/update-docs-rules-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@
const fs = require('fs')
const path = require('path')
const rules = require('./lib/rules')
const categories = require('./lib/categories')
const { getPresetIds, formatItems } = require('./lib/utils')

const VUE3_EMOJI = ':three:'
const VUE2_EMOJI = ':two:'

// -----------------------------------------------------------------------------
const categorizedRules = rules.filter(
(rule) =>
rule.meta.docs.categories &&
!rule.meta.docs.extensionRule &&
!rule.meta.deprecated
)
const uncategorizedRules = rules.filter(
(rule) =>
!rule.meta.docs.categories &&
Expand All @@ -24,16 +33,23 @@ const uncategorizedExtensionRule = rules.filter(
)
const deprecatedRules = rules.filter((rule) => rule.meta.deprecated)

function toRuleRow(rule) {
const TYPE_MARK = {
problem: ':warning:',
suggestion: ':hammer:',
layout: ':lipstick:'
}

function toRuleRow(rule, kindMarks = []) {
const mark = [
rule.meta.fixable ? ':wrench:' : '',
rule.meta.hasSuggestions ? ':bulb:' : '',
rule.meta.deprecated ? ':warning:' : ''
].join('')
const kindMark = [...kindMarks, TYPE_MARK[rule.meta.type]].join('')
const link = `[${rule.ruleId}](./${rule.name}.md)`
const description = rule.meta.docs.description || '(no description)'

return `| ${link} | ${description} | ${mark} |`
return `| ${link} | ${description} | ${mark} | ${kindMark} |`
}

function toDeprecatedRuleRow(rule) {
Expand All @@ -46,25 +62,97 @@ function toDeprecatedRuleRow(rule) {
return `| ${link} | ${replacedBy || '(no replacement)'} |`
}

// -----------------------------------------------------------------------------
let rulesTableContent = categories
.map(
(category) => `
## ${category.title.vuepress}

Enforce all the rules in this category, as well as all higher priority rules, with:
const categoryGroups = [
{
title: 'Base Rules (Enabling Correct ESLint Parsing)',
description:
'Rules in this category are enabled for all presets provided by eslint-plugin-vue.',
categoryIdForVue3: 'base',
categoryIdForVue2: 'base',
useMark: false
},
{
title: 'Priority A: Essential (Error Prevention)',
categoryIdForVue3: 'vue3-essential',
categoryIdForVue2: 'essential',
useMark: true
},
{
title: 'Priority B: Strongly Recommended (Improving Readability)',
categoryIdForVue3: 'vue3-strongly-recommended',
categoryIdForVue2: 'strongly-recommended',
useMark: true
},
{
title: 'Priority C: Recommended (Potentially Dangerous Patterns)',
categoryIdForVue3: 'vue3-recommended',
categoryIdForVue2: 'recommended',
useMark: true
}
]

\`\`\`json
{
"extends": "plugin:vue/${category.categoryId}"
}
\`\`\`
// -----------------------------------------------------------------------------
let rulesTableContent = categoryGroups
.map((group) => {
const rules = categorizedRules.filter(
(rule) =>
rule.meta.docs.categories.includes(group.categoryIdForVue3) ||
rule.meta.docs.categories.includes(group.categoryIdForVue2)
)
let content = `
## ${group.title}
`

| Rule ID | Description | |
|:--------|:------------|:---|
${category.rules.map(toRuleRow).join('\n')}
if (group.description) {
content += `
${group.description}
`
}
if (group.useMark) {
const presetsForVue3 = getPresetIds([group.categoryIdForVue3]).map(
(categoryId) => `\`"plugin:vue/${categoryId}"\``
)
const presetsForVue2 = getPresetIds([group.categoryIdForVue2]).map(
(categoryId) => `\`"plugin:vue/${categoryId}"\``
)
content += `
- ${VUE3_EMOJI} Indicates that the rule is for Vue 3 and is included in ${formatItems(
presetsForVue3,
['preset', 'presets']
)}.
- ${VUE2_EMOJI} Indicates that the rule is for Vue 2 and is included in ${formatItems(
presetsForVue2,
['preset', 'presets']
)}.
`
)
}

content += `
<rules-table>

| Rule ID | Description | | |
|:--------|:------------|:--:|:--:|
${rules
.map((rule) => {
const mark = group.useMark
? [
rule.meta.docs.categories.includes(group.categoryIdForVue3)
? [VUE3_EMOJI]
: [],
rule.meta.docs.categories.includes(group.categoryIdForVue2)
? [VUE2_EMOJI]
: []
].flat()
: []
return toRuleRow(rule, mark)
})
.join('\n')}

</rules-table>
`

return content
})
.join('')

// -----------------------------------------------------------------------------
Expand All @@ -90,9 +178,13 @@ For example:
}
if (uncategorizedRules.length > 0) {
rulesTableContent += `
| Rule ID | Description | |
|:--------|:------------|:---|
${uncategorizedRules.map(toRuleRow).join('\n')}
<rules-table>

| Rule ID | Description | | |
|:--------|:------------|:--:|:--:|
${uncategorizedRules.map((rule) => toRuleRow(rule)).join('\n')}

</rules-table>
`
}
if (uncategorizedExtensionRule.length > 0) {
Expand All @@ -101,9 +193,13 @@ if (uncategorizedExtensionRule.length > 0) {

The following rules extend the rules provided by ESLint itself and apply them to the expressions in the \`<template>\`.

| Rule ID | Description | |
|:--------|:------------|:---|
${uncategorizedExtensionRule.map(toRuleRow).join('\n')}
<rules-table>

| Rule ID | Description | | |
|:--------|:------------|:--:|:--:|
${uncategorizedExtensionRule.map((rule) => toRuleRow(rule)).join('\n')}

</rules-table>
`
}

Expand All @@ -127,6 +223,7 @@ fs.writeFileSync(
readmeFilePath,
`---
sidebarDepth: 0
pageClass: rule-list
---

# Available rules
Expand All @@ -139,5 +236,12 @@ sidebarDepth: 0
:bulb: Indicates that some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
:::

${rulesTableContent}`
Mark indicating rule type:

- <span class="emoji">:warning:</span> Possible Problems: These rules relate to possible logic errors in code.
- :hammer: Suggestions: These rules suggest alternate ways of doing things.
- :lipstick: Layout & Formatting: These rules care about how the code looks rather than how it executes.

${rulesTableContent.trim()}
`
)
Loading