Skip to content

Commit f35154b

Browse files
authored
Add reportUnusedDisableDirectives option to vue/comment-directive rule (#1167)
* Add reportUnusedDisableDirectives option to `vue/comment-directive` rule * Update comment-directive.md * Update comment-directive.md
1 parent b89adfa commit f35154b

File tree

4 files changed

+550
-71
lines changed

4 files changed

+550
-71
lines changed

Diff for: docs/rules/comment-directive.md

+37-3
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ It supports usage of the following comments:
2121
We can't write HTML comments in tags.
2222
:::
2323

24-
This rule doesn't throw any warning.
25-
2624
## :book: Rule Details
2725

2826
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).
@@ -88,9 +86,45 @@ The `eslint-disable`-like comments can include descriptions to explain why the c
8886

8987
</eslint-code-block>
9088

89+
## :wrench: Options
90+
91+
```json
92+
{
93+
"vue/comment-directive": ["error", {
94+
"reportUnusedDisableDirectives": false
95+
}]
96+
}
97+
```
98+
99+
- `reportUnusedDisableDirectives` ... If `true`, to report unused `eslint-disable` HTML comments. default `false`
100+
101+
### `{ "reportUnusedDisableDirectives": true }`
102+
103+
<eslint-code-block :rules="{'vue/comment-directive': ['error', {reportUnusedDisableDirectives: true} ], 'vue/max-attributes-per-line': ['error']}">
104+
105+
```vue
106+
<template>
107+
<!-- ✓ GOOD -->
108+
<!-- eslint-disable-next-line vue/max-attributes-per-line -->
109+
<div a="1" b="2" c="3" d="4" />
110+
111+
<!-- ✗ BAD -->
112+
<!-- eslint-disable-next-line vue/max-attributes-per-line -->
113+
<div a="1" />
114+
</template>
115+
```
116+
117+
</eslint-code-block>
118+
119+
::: warning Note
120+
Unused reports cannot be suppressed with `eslint-disable` HTML comments.
121+
:::
122+
91123
## :books: Further reading
92124

93-
- [Disabling rules with inline comments](https://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments)
125+
- [Disabling rules with inline comments]
126+
127+
[Disabling rules with inline comments]: https://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments
94128

95129
## :mag: Implementation
96130

Diff for: lib/processor.js

+128-31
Original file line numberDiff line numberDiff line change
@@ -3,64 +3,161 @@
33
*/
44
'use strict'
55

6+
/**
7+
* @typedef {import('eslint').Linter.LintMessage} LintMessage
8+
*/
9+
/**
10+
* @typedef {object} GroupState
11+
* @property {Set<string>} GroupState.disableAllKeys
12+
* @property {Map<string, string[]>} GroupState.disableRuleKeys
13+
*/
14+
615
module.exports = {
716
preprocess(code) {
817
return [code]
918
},
1019

20+
/**
21+
* @param {LintMessage[][]} messages
22+
* @returns {LintMessage[]}
23+
*/
1124
postprocess(messages) {
1225
const state = {
26+
/** @type {GroupState} */
1327
block: {
14-
disableAll: false,
15-
disableRules: new Set()
28+
disableAllKeys: new Set(),
29+
disableRuleKeys: new Map()
1630
},
31+
/** @type {GroupState} */
1732
line: {
18-
disableAll: false,
19-
disableRules: new Set()
33+
disableAllKeys: new Set(),
34+
disableRuleKeys: new Map()
2035
}
2136
}
37+
const usedDisableDirectiveKeys = []
38+
/** @type {Map<string,LintMessage>} */
39+
const unusedDisableDirectiveReports = new Map()
2240

2341
// Filter messages which are in disabled area.
24-
return messages[0].filter((message) => {
42+
const filteredMessages = messages[0].filter((message) => {
2543
if (message.ruleId === 'vue/comment-directive') {
26-
const rules = message.message.split(' ')
27-
const type = rules.shift()
28-
const group = rules.shift()
29-
switch (type) {
30-
case '--':
31-
state[group].disableAll = true
44+
const directiveType = message.messageId
45+
const data = message.message.split(' ')
46+
switch (directiveType) {
47+
case 'disableBlock':
48+
state.block.disableAllKeys.add(data[1])
49+
break
50+
case 'disableLine':
51+
state.line.disableAllKeys.add(data[1])
52+
break
53+
case 'enableBlock':
54+
state.block.disableAllKeys.clear()
55+
break
56+
case 'enableLine':
57+
state.line.disableAllKeys.clear()
3258
break
33-
case '++':
34-
state[group].disableAll = false
59+
case 'disableBlockRule':
60+
addDisableRule(state.block.disableRuleKeys, data[1], data[2])
3561
break
36-
case '-':
37-
for (const rule of rules) {
38-
state[group].disableRules.add(rule)
39-
}
62+
case 'disableLineRule':
63+
addDisableRule(state.line.disableRuleKeys, data[1], data[2])
4064
break
41-
case '+':
42-
for (const rule of rules) {
43-
state[group].disableRules.delete(rule)
44-
}
65+
case 'enableBlockRule':
66+
state.block.disableRuleKeys.delete(data[1])
67+
break
68+
case 'enableLineRule':
69+
state.line.disableRuleKeys.delete(data[1])
4570
break
4671
case 'clear':
47-
state.block.disableAll = false
48-
state.block.disableRules.clear()
49-
state.line.disableAll = false
50-
state.line.disableRules.clear()
72+
state.block.disableAllKeys.clear()
73+
state.block.disableRuleKeys.clear()
74+
state.line.disableAllKeys.clear()
75+
state.line.disableRuleKeys.clear()
76+
break
77+
default:
78+
// unused eslint-disable comments report
79+
unusedDisableDirectiveReports.set(messageToKey(message), message)
5180
break
5281
}
5382
return false
5483
} else {
55-
return !(
56-
state.block.disableAll ||
57-
state.line.disableAll ||
58-
state.block.disableRules.has(message.ruleId) ||
59-
state.line.disableRules.has(message.ruleId)
60-
)
84+
const disableDirectiveKeys = []
85+
if (state.block.disableAllKeys.size) {
86+
disableDirectiveKeys.push(...state.block.disableAllKeys)
87+
}
88+
if (state.line.disableAllKeys.size) {
89+
disableDirectiveKeys.push(...state.line.disableAllKeys)
90+
}
91+
if (state.block.disableRuleKeys.has(message.ruleId)) {
92+
disableDirectiveKeys.push(
93+
...state.block.disableRuleKeys.get(message.ruleId)
94+
)
95+
}
96+
if (state.line.disableRuleKeys.has(message.ruleId)) {
97+
disableDirectiveKeys.push(
98+
...state.line.disableRuleKeys.get(message.ruleId)
99+
)
100+
}
101+
102+
if (disableDirectiveKeys.length) {
103+
// Store used eslint-disable comment key
104+
usedDisableDirectiveKeys.push(...disableDirectiveKeys)
105+
return false
106+
} else {
107+
return true
108+
}
61109
}
62110
})
111+
112+
if (unusedDisableDirectiveReports.size) {
113+
for (const key of usedDisableDirectiveKeys) {
114+
// Remove used eslint-disable comments
115+
unusedDisableDirectiveReports.delete(key)
116+
}
117+
// Reports unused eslint-disable comments
118+
filteredMessages.push(...unusedDisableDirectiveReports.values())
119+
filteredMessages.sort(compareLocations)
120+
}
121+
122+
return filteredMessages
63123
},
64124

65125
supportsAutofix: true
66126
}
127+
128+
/**
129+
* @param {Map<string, string[]>} disableRuleKeys
130+
* @param {string} rule
131+
* @param {string} key
132+
*/
133+
function addDisableRule(disableRuleKeys, rule, key) {
134+
let keys = disableRuleKeys.get(rule)
135+
if (keys) {
136+
keys.push(key)
137+
} else {
138+
keys = [key]
139+
disableRuleKeys.set(rule, keys)
140+
}
141+
}
142+
143+
/**
144+
* @param {LintMessage} message
145+
* @returns {string} message key
146+
*/
147+
function messageToKey(message) {
148+
return `line:${message.line},column${
149+
// -1 because +1 by ESLint's `report-translator`.
150+
message.column - 1
151+
}`
152+
}
153+
154+
/**
155+
* Compares the locations of two objects in a source file
156+
* @param {{line: number, column: number}} itemA The first object
157+
* @param {{line: number, column: number}} itemB The second object
158+
* @returns {number} A value less than 1 if itemA appears before itemB in the source file, greater than 1 if
159+
* itemA appears after itemB in the source file, or 0 if itemA and itemB have the same location.
160+
*/
161+
function compareLocations(itemA, itemB) {
162+
return itemA.line - itemB.line || itemA.column - itemB.column
163+
}

0 commit comments

Comments
 (0)