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
1 change: 1 addition & 0 deletions .markdownlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ no-inline-html:
- badge
- eslint-code-block
- sup
- rules-table

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

<script>
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) {
for (const mark of row.cells[3].textContent.trim()) {
kindMarks.add(mark)
}
}
this.kindMarks = [...kindMarks]
},
getTable() {
return this.$refs.tableRoot.querySelector('table')
},
filterTable() {
const table = this.getTable()
if (!this.checkedKindMarks.length) {
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;
}
</style>
644 changes: 271 additions & 373 deletions docs/rules/README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/configs/no-layout-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
'vue/comma-dangle': 'off',
'vue/comma-spacing': 'off',
'vue/comma-style': 'off',
'vue/define-macros-order': 'off',
'vue/dot-location': 'off',
'vue/first-attribute-linebreak': 'off',
'vue/func-call-spacing': 'off',
Expand Down
36 changes: 36 additions & 0 deletions tools/lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const last = require('lodash/last')

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) {
if (items.length <= 2) {
return items.join(' and ')
}
return `all of ${items.slice(0, -1).join(', ')} and ${last(items)}`
}
function getPresetIds(categoryIds) {
const subsetCategoryIds = []
for (const categoryId of categoryIds) {
for (const subsetCategoryId in presetCategories) {
if (presetCategories[subsetCategoryId] === categoryId) {
subsetCategoryIds.push(subsetCategoryId)
}
}
}
if (subsetCategoryIds.length === 0) {
return categoryIds
}
return [...new Set([...categoryIds, ...getPresetIds(subsetCategoryIds)])]
}
153 changes: 127 additions & 26 deletions tools/update-docs-rules-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@
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 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 +30,26 @@ const uncategorizedExtensionRule = rules.filter(
)
const deprecatedRules = rules.filter((rule) => rule.meta.deprecated)

function toRuleRow(rule) {
function toRuleRow(rule, kindMarks = []) {
const mark = [
rule.meta.fixable ? ':wrench:' : '',
rule.meta.hasSuggestions ? ':bulb:' : '',
rule.meta.deprecated ? ':warning:' : ''
].join('')
const kindMark = [
...kindMarks,
rule.meta.type === 'problem'
? ':white_check_mark:'
: rule.meta.type === 'suggestion'
? ':hammer:'
: rule.meta.type === 'layout'
? ':lipstick:'
: ''
].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,95 @@ 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 enable 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 += `
- :green_heart: Indicates that the rule is for Vue3 and is included in ${formatItems(
presetsForVue3
)}.
- :yellow_heart: Indicates that the rule is for Vue2 and is included in ${formatItems(
presetsForVue2
)}.
`
)
}

content += `
<rules-table>

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

</rules-table>
`

return content
})
.join('')

// -----------------------------------------------------------------------------
Expand All @@ -90,9 +176,13 @@ For example:
}
if (uncategorizedRules.length) {
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) {
Expand All @@ -101,9 +191,13 @@ if (uncategorizedExtensionRule.length) {

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 Down Expand Up @@ -139,5 +233,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:

- :white_check_mark: 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()}
`
)
36 changes: 1 addition & 35 deletions tools/update-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,11 @@ For example:

const fs = require('fs')
const path = require('path')
const last = require('lodash/last')
const rules = require('./lib/rules')
const { getPresetIds, formatItems } = require('./lib/utils')

const ROOT = path.resolve(__dirname, '../docs/rules')

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) {
if (items.length <= 2) {
return items.join(' and ')
}
return `all of ${items.slice(0, -1).join(', ')} and ${last(items)}`
}

function getPresetIds(categoryIds) {
const subsetCategoryIds = []
for (const categoryId of categoryIds) {
for (const subsetCategoryId in presetCategories) {
if (presetCategories[subsetCategoryId] === categoryId) {
subsetCategoryIds.push(subsetCategoryId)
}
}
}
if (subsetCategoryIds.length === 0) {
return categoryIds
}
return [...new Set([...categoryIds, ...getPresetIds(subsetCategoryIds)])]
}

function pickSince(content) {
const fileIntro = /^---\n(.*\n)+---\n*/g.exec(content)
if (fileIntro) {
Expand Down