Skip to content

Commit

Permalink
Add reportUnusedDisableDirectives option to vue/comment-directive r…
Browse files Browse the repository at this point in the history
…ule (#1167)

* Add reportUnusedDisableDirectives option to `vue/comment-directive` rule

* Update comment-directive.md

* Update comment-directive.md
  • Loading branch information
ota-meshi committed May 30, 2020
1 parent b89adfa commit f35154b
Show file tree
Hide file tree
Showing 4 changed files with 550 additions and 71 deletions.
40 changes: 37 additions & 3 deletions docs/rules/comment-directive.md
Expand Up @@ -21,8 +21,6 @@ It supports usage of the following comments:
We can't write HTML comments in tags.
:::

This rule doesn't throw any warning.

## :book: Rule Details

ESLint doesn't provide any API to enhance `eslint-disable` functionality and ESLint rules cannot affect other rules. But ESLint provides [processors API](https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins).
Expand Down Expand Up @@ -88,9 +86,45 @@ The `eslint-disable`-like comments can include descriptions to explain why the c

</eslint-code-block>

## :wrench: Options

```json
{
"vue/comment-directive": ["error", {
"reportUnusedDisableDirectives": false
}]
}
```

- `reportUnusedDisableDirectives` ... If `true`, to report unused `eslint-disable` HTML comments. default `false`

### `{ "reportUnusedDisableDirectives": true }`

<eslint-code-block :rules="{'vue/comment-directive': ['error', {reportUnusedDisableDirectives: true} ], 'vue/max-attributes-per-line': ['error']}">

```vue
<template>
<!-- ✓ GOOD -->
<!-- eslint-disable-next-line vue/max-attributes-per-line -->
<div a="1" b="2" c="3" d="4" />
<!-- ✗ BAD -->
<!-- eslint-disable-next-line vue/max-attributes-per-line -->
<div a="1" />
</template>
```

</eslint-code-block>

::: warning Note
Unused reports cannot be suppressed with `eslint-disable` HTML comments.
:::

## :books: Further reading

- [Disabling rules with inline comments](https://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments)
- [Disabling rules with inline comments]

[Disabling rules with inline comments]: https://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments

## :mag: Implementation

Expand Down
159 changes: 128 additions & 31 deletions lib/processor.js
Expand Up @@ -3,64 +3,161 @@
*/
'use strict'

/**
* @typedef {import('eslint').Linter.LintMessage} LintMessage
*/
/**
* @typedef {object} GroupState
* @property {Set<string>} GroupState.disableAllKeys
* @property {Map<string, string[]>} GroupState.disableRuleKeys
*/

module.exports = {
preprocess(code) {
return [code]
},

/**
* @param {LintMessage[][]} messages
* @returns {LintMessage[]}
*/
postprocess(messages) {
const state = {
/** @type {GroupState} */
block: {
disableAll: false,
disableRules: new Set()
disableAllKeys: new Set(),
disableRuleKeys: new Map()
},
/** @type {GroupState} */
line: {
disableAll: false,
disableRules: new Set()
disableAllKeys: new Set(),
disableRuleKeys: new Map()
}
}
const usedDisableDirectiveKeys = []
/** @type {Map<string,LintMessage>} */
const unusedDisableDirectiveReports = new Map()

// Filter messages which are in disabled area.
return messages[0].filter((message) => {
const filteredMessages = messages[0].filter((message) => {
if (message.ruleId === 'vue/comment-directive') {
const rules = message.message.split(' ')
const type = rules.shift()
const group = rules.shift()
switch (type) {
case '--':
state[group].disableAll = true
const directiveType = message.messageId
const data = message.message.split(' ')
switch (directiveType) {
case 'disableBlock':
state.block.disableAllKeys.add(data[1])
break
case 'disableLine':
state.line.disableAllKeys.add(data[1])
break
case 'enableBlock':
state.block.disableAllKeys.clear()
break
case 'enableLine':
state.line.disableAllKeys.clear()
break
case '++':
state[group].disableAll = false
case 'disableBlockRule':
addDisableRule(state.block.disableRuleKeys, data[1], data[2])
break
case '-':
for (const rule of rules) {
state[group].disableRules.add(rule)
}
case 'disableLineRule':
addDisableRule(state.line.disableRuleKeys, data[1], data[2])
break
case '+':
for (const rule of rules) {
state[group].disableRules.delete(rule)
}
case 'enableBlockRule':
state.block.disableRuleKeys.delete(data[1])
break
case 'enableLineRule':
state.line.disableRuleKeys.delete(data[1])
break
case 'clear':
state.block.disableAll = false
state.block.disableRules.clear()
state.line.disableAll = false
state.line.disableRules.clear()
state.block.disableAllKeys.clear()
state.block.disableRuleKeys.clear()
state.line.disableAllKeys.clear()
state.line.disableRuleKeys.clear()
break
default:
// unused eslint-disable comments report
unusedDisableDirectiveReports.set(messageToKey(message), message)
break
}
return false
} else {
return !(
state.block.disableAll ||
state.line.disableAll ||
state.block.disableRules.has(message.ruleId) ||
state.line.disableRules.has(message.ruleId)
)
const disableDirectiveKeys = []
if (state.block.disableAllKeys.size) {
disableDirectiveKeys.push(...state.block.disableAllKeys)
}
if (state.line.disableAllKeys.size) {
disableDirectiveKeys.push(...state.line.disableAllKeys)
}
if (state.block.disableRuleKeys.has(message.ruleId)) {
disableDirectiveKeys.push(
...state.block.disableRuleKeys.get(message.ruleId)
)
}
if (state.line.disableRuleKeys.has(message.ruleId)) {
disableDirectiveKeys.push(
...state.line.disableRuleKeys.get(message.ruleId)
)
}

if (disableDirectiveKeys.length) {
// Store used eslint-disable comment key
usedDisableDirectiveKeys.push(...disableDirectiveKeys)
return false
} else {
return true
}
}
})

if (unusedDisableDirectiveReports.size) {
for (const key of usedDisableDirectiveKeys) {
// Remove used eslint-disable comments
unusedDisableDirectiveReports.delete(key)
}
// Reports unused eslint-disable comments
filteredMessages.push(...unusedDisableDirectiveReports.values())
filteredMessages.sort(compareLocations)
}

return filteredMessages
},

supportsAutofix: true
}

/**
* @param {Map<string, string[]>} disableRuleKeys
* @param {string} rule
* @param {string} key
*/
function addDisableRule(disableRuleKeys, rule, key) {
let keys = disableRuleKeys.get(rule)
if (keys) {
keys.push(key)
} else {
keys = [key]
disableRuleKeys.set(rule, keys)
}
}

/**
* @param {LintMessage} message
* @returns {string} message key
*/
function messageToKey(message) {
return `line:${message.line},column${
// -1 because +1 by ESLint's `report-translator`.
message.column - 1
}`
}

/**
* Compares the locations of two objects in a source file
* @param {{line: number, column: number}} itemA The first object
* @param {{line: number, column: number}} itemB The second object
* @returns {number} A value less than 1 if itemA appears before itemB in the source file, greater than 1 if
* itemA appears after itemB in the source file, or 0 if itemA and itemB have the same location.
*/
function compareLocations(itemA, itemB) {
return itemA.line - itemB.line || itemA.column - itemB.column
}

0 comments on commit f35154b

Please sign in to comment.