@eslint-community/eslint-plugin-eslint-comments 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Toru Nagashima
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @eslint-community/eslint-plugin-eslint-comments
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@eslint-community/eslint-plugin-eslint-comments.svg)](https://www.npmjs.com/package/@eslint-community/eslint-plugin-eslint-comments)
4
+ [![Downloads/month](https://img.shields.io/npm/dm/@eslint-community/eslint-plugin-eslint-comments.svg)](http://www.npmtrends.com/@eslint-community/eslint-plugin-eslint-comments)
5
+ [![Build Status](https://github.com/eslint-community/eslint-plugin-eslint-comments/workflows/CI/badge.svg)](https://github.com/eslint-community/eslint-plugin-eslint-comments/actions)
6
+ [![codecov](https://codecov.io/gh/eslint-community/eslint-plugin-eslint-comments/branch/main/graph/badge.svg)](https://codecov.io/gh/eslint-community/eslint-plugin-eslint-comments)
7
+
8
+ Additional ESLint rules for ESLint directive comments (e.g. `//eslint-disable-line`).
9
+
10
+ ## 📖 Usage
11
+
12
+ - [Documentation](https://mysticatea.github.io/eslint-plugin-eslint-comments)
13
+
14
+ ## 🚥 Semantic Versioning Policy
15
+
16
+ `@eslint-community/eslint-plugin-eslint-comments` follows [semantic versioning](http://semver.org/) and [ESLint's Semantic Versioning Policy](https://github.com/eslint/eslint#semantic-versioning-policy).
17
+
18
+ ## 📰 Changelog
19
+
20
+ - [GitHub Releases](https://github.com/eslint-community/eslint-plugin-eslint-comments/releases)
21
+
22
+ ## 🍻 Contributing
23
+
24
+ Welcome contributing!
25
+
26
+ Please use GitHub's Issues/PRs.
27
+
28
+ ### Development Tools
29
+
30
+ - `npm test` runs tests and measures coverage.
31
+ - `npm run build` updates `README.md`, `index.js`, and the header of all rule's documents.
32
+ - `npm run clean` removes the coverage of the last `npm test` command.
33
+ - `npm run coverage` shows the coverage of the last `npm test` command.
34
+ - `npm run lint` runs ESLint for this codebase.
35
+ - `npm run watch` runs tests and measures coverage when source code are changed.
package/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /** DON'T EDIT THIS FILE WHICH WAS CREATED BY 'scripts/generate-index.js'. */
2
+ "use strict"
3
+
4
+ module.exports = {
5
+ configs: require("./lib/configs"),
6
+ rules: require("./lib/rules"),
7
+ utils: require("./lib/utils"),
8
+ }
@@ -0,0 +1,13 @@
1
+ /** DON'T EDIT THIS FILE; was created by scripts. */
2
+ "use strict"
3
+
4
+ module.exports = {
5
+ plugins: ["@eslint-community/eslint-comments"],
6
+ rules: {
7
+ "@eslint-community/eslint-comments/disable-enable-pair": "error",
8
+ "@eslint-community/eslint-comments/no-aggregating-enable": "error",
9
+ "@eslint-community/eslint-comments/no-duplicate-disable": "error",
10
+ "@eslint-community/eslint-comments/no-unlimited-disable": "error",
11
+ "@eslint-community/eslint-comments/no-unused-enable": "error",
12
+ },
13
+ }
package/lib/configs.js ADDED
@@ -0,0 +1,6 @@
1
+ /** DON'T EDIT THIS FILE; was created by scripts. */
2
+ "use strict"
3
+
4
+ module.exports = {
5
+ recommended: require("./configs/recommended"),
6
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ const utils = require("./utils")
8
+ const DELIMITER = /[\s,]+/gu
9
+ const pool = new WeakMap()
10
+
11
+ module.exports = class DisabledArea {
12
+ /**
13
+ * Get singleton instance for the given source code.
14
+ *
15
+ * @param {eslint.SourceCode} sourceCode - The source code to get.
16
+ * @returns {DisabledArea} The singleton object for the source code.
17
+ */
18
+ static get(sourceCode) {
19
+ let retv = pool.get(sourceCode.ast)
20
+
21
+ if (retv == null) {
22
+ retv = new DisabledArea()
23
+ retv._scan(sourceCode)
24
+ pool.set(sourceCode.ast, retv)
25
+ }
26
+
27
+ return retv
28
+ }
29
+
30
+ /**
31
+ * Constructor.
32
+ */
33
+ constructor() {
34
+ this.areas = []
35
+ this.duplicateDisableDirectives = []
36
+ this.unusedEnableDirectives = []
37
+ this.numberOfRelatedDisableDirectives = new Map()
38
+ }
39
+
40
+ /**
41
+ * Make disabled area.
42
+ *
43
+ * @param {Token} comment - The comment token to disable.
44
+ * @param {object} location - The start location to disable.
45
+ * @param {string[]|null} ruleIds - The ruleId names to disable.
46
+ * @param {string} kind - The kind of disable-comments.
47
+ * @returns {void}
48
+ * @private
49
+ */
50
+ _disable(comment, location, ruleIds, kind) {
51
+ if (ruleIds) {
52
+ for (const ruleId of ruleIds) {
53
+ if (this._getArea(ruleId, location) != null) {
54
+ this.duplicateDisableDirectives.push({ comment, ruleId })
55
+ }
56
+
57
+ this.areas.push({
58
+ comment,
59
+ ruleId,
60
+ kind,
61
+ start: location,
62
+ end: null,
63
+ })
64
+ }
65
+ } else {
66
+ if (this._getArea(null, location) != null) {
67
+ this.duplicateDisableDirectives.push({ comment, ruleId: null })
68
+ }
69
+
70
+ this.areas.push({
71
+ comment,
72
+ ruleId: null,
73
+ kind,
74
+ start: location,
75
+ end: null,
76
+ })
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Close disabled area.
82
+ *
83
+ * @param {Token} comment - The comment token to enable.
84
+ * @param {object} location - The start location to enable.
85
+ * @param {string[]|null} ruleIds - The ruleId names to enable.
86
+ * @param {string} kind - The kind of disable-comments.
87
+ * @returns {void}
88
+ * @private
89
+ */
90
+ _enable(comment, location, ruleIds, kind) {
91
+ const relatedDisableDirectives = new Set()
92
+
93
+ if (ruleIds) {
94
+ for (const ruleId of ruleIds) {
95
+ let used = false
96
+
97
+ for (let i = this.areas.length - 1; i >= 0; --i) {
98
+ const area = this.areas[i]
99
+
100
+ if (
101
+ area.end === null &&
102
+ area.kind === kind &&
103
+ area.ruleId === ruleId
104
+ ) {
105
+ relatedDisableDirectives.add(area.comment)
106
+ area.end = location
107
+ used = true
108
+ }
109
+ }
110
+
111
+ if (!used) {
112
+ this.unusedEnableDirectives.push({ comment, ruleId })
113
+ }
114
+ }
115
+ } else {
116
+ let used = false
117
+
118
+ for (let i = this.areas.length - 1; i >= 0; --i) {
119
+ const area = this.areas[i]
120
+
121
+ if (area.end === null && area.kind === kind) {
122
+ relatedDisableDirectives.add(area.comment)
123
+ area.end = location
124
+ used = true
125
+ }
126
+ }
127
+
128
+ if (!used) {
129
+ this.unusedEnableDirectives.push({ comment, ruleId: null })
130
+ }
131
+ }
132
+
133
+ this.numberOfRelatedDisableDirectives.set(
134
+ comment,
135
+ relatedDisableDirectives.size
136
+ )
137
+ }
138
+
139
+ /**
140
+ * Gets the area of the given ruleId and location.
141
+ *
142
+ * @param {string|null} ruleId - The ruleId name to get.
143
+ * @param {object} location - The location to get.
144
+ * @returns {object|null} The area of the given ruleId and location.
145
+ * @private
146
+ */
147
+ _getArea(ruleId, location) {
148
+ for (let i = this.areas.length - 1; i >= 0; --i) {
149
+ const area = this.areas[i]
150
+
151
+ if (
152
+ (area.ruleId === null || area.ruleId === ruleId) &&
153
+ utils.lte(area.start, location) &&
154
+ (area.end === null || utils.lte(location, area.end))
155
+ ) {
156
+ return area
157
+ }
158
+ }
159
+
160
+ return null
161
+ }
162
+
163
+ /**
164
+ * Scan the source code and setup disabled area list.
165
+ *
166
+ * @param {eslint.SourceCode} sourceCode - The source code to scan.
167
+ * @returns {void}
168
+ * @private
169
+ */
170
+ _scan(sourceCode) {
171
+ for (const comment of sourceCode.getAllComments()) {
172
+ const directiveComment = utils.parseDirectiveComment(comment)
173
+ if (directiveComment == null) {
174
+ continue
175
+ }
176
+
177
+ const kind = directiveComment.kind
178
+ if (
179
+ kind !== "eslint-disable" &&
180
+ kind !== "eslint-enable" &&
181
+ kind !== "eslint-disable-line" &&
182
+ kind !== "eslint-disable-next-line"
183
+ ) {
184
+ continue
185
+ }
186
+ const ruleIds = directiveComment.value
187
+ ? directiveComment.value.split(DELIMITER)
188
+ : null
189
+
190
+ if (kind === "eslint-disable") {
191
+ this._disable(comment, comment.loc.start, ruleIds, "block")
192
+ } else if (kind === "eslint-enable") {
193
+ this._enable(comment, comment.loc.start, ruleIds, "block")
194
+ } else if (kind === "eslint-disable-line") {
195
+ const line = comment.loc.start.line
196
+ const start = { line, column: 0 }
197
+ const end = { line: line + 1, column: -1 }
198
+
199
+ this._disable(comment, start, ruleIds, "line")
200
+ this._enable(comment, end, ruleIds, "line")
201
+ } else if (kind === "eslint-disable-next-line") {
202
+ const line = comment.loc.start.line
203
+ const start = { line: line + 1, column: 0 }
204
+ const end = { line: line + 2, column: -1 }
205
+
206
+ this._disable(comment, start, ruleIds, "line")
207
+ this._enable(comment, end, ruleIds, "line")
208
+ }
209
+ }
210
+ }
211
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ const path = require("path")
8
+ const needle = `${path.sep}node_modules${path.sep}eslint${path.sep}`
9
+
10
+ module.exports = () => {
11
+ const eslintPaths = new Set(
12
+ Object.keys(require.cache)
13
+ .filter(id => id.includes(needle))
14
+ .map(id => id.slice(0, id.indexOf(needle) + needle.length))
15
+ )
16
+ const linters = []
17
+
18
+ for (const eslintPath of eslintPaths) {
19
+ try {
20
+ const linter = require(eslintPath).Linter
21
+
22
+ if (linter) {
23
+ linters.push(linter)
24
+ }
25
+ } catch (error) {
26
+ if (error.code !== "MODULE_NOT_FOUND") {
27
+ throw error
28
+ }
29
+ }
30
+ }
31
+
32
+ return linters
33
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ const escapeStringRegexp = require("escape-string-regexp")
8
+ const LINE_PATTERN = /[^\r\n\u2028\u2029]*(?:\r\n|[\r\n\u2028\u2029]|$)/gu
9
+
10
+ const DIRECTIVE_PATTERN = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u
11
+ const LINE_COMMENT_PATTERN = /^eslint-disable-(next-)?line$/u
12
+
13
+ module.exports = {
14
+ /**
15
+ * Make the location ignoring `eslint-disable` comments.
16
+ *
17
+ * @param {object} location - The location to convert.
18
+ * @returns {object} Converted location.
19
+ */
20
+ toForceLocation(location) {
21
+ return {
22
+ start: {
23
+ line: location.start.line,
24
+ column: -1,
25
+ },
26
+ end: location.end,
27
+ }
28
+ },
29
+
30
+ /**
31
+ * Calculate the location of the given rule in the given comment token.
32
+ *
33
+ * @param {Token} comment - The comment token to calculate.
34
+ * @param {string|null} ruleId - The rule name to calculate.
35
+ * @returns {object} The location of the given information.
36
+ */
37
+ toRuleIdLocation(comment, ruleId) {
38
+ if (ruleId == null) {
39
+ return module.exports.toForceLocation(comment.loc)
40
+ }
41
+
42
+ const lines = comment.value.match(LINE_PATTERN)
43
+ //eslint-disable-next-line require-unicode-regexp
44
+ const ruleIdPattern = new RegExp(
45
+ `([\\s,]|^)${escapeStringRegexp(ruleId)}(?:[\\s,]|$)`
46
+ )
47
+
48
+ {
49
+ const m = ruleIdPattern.exec(lines[0])
50
+ if (m != null) {
51
+ const start = comment.loc.start
52
+ return {
53
+ start: {
54
+ line: start.line,
55
+ column: 2 + start.column + m.index + m[1].length,
56
+ },
57
+ end: {
58
+ line: start.line,
59
+ column:
60
+ 2 +
61
+ start.column +
62
+ m.index +
63
+ m[1].length +
64
+ ruleId.length,
65
+ },
66
+ }
67
+ }
68
+ }
69
+
70
+ for (let i = 1; i < lines.length; ++i) {
71
+ const m = ruleIdPattern.exec(lines[i])
72
+ if (m != null) {
73
+ const start = comment.loc.start
74
+ return {
75
+ start: {
76
+ line: start.line + i,
77
+ column: m.index + m[1].length,
78
+ },
79
+ end: {
80
+ line: start.line + i,
81
+ column: m.index + m[1].length + ruleId.length,
82
+ },
83
+ }
84
+ }
85
+ }
86
+
87
+ /*istanbul ignore next : foolproof */
88
+ return comment.loc
89
+ },
90
+
91
+ /**
92
+ * Checks `a` is less than `b` or `a` equals `b`.
93
+ *
94
+ * @param {{line: number, column: number}} a - A location to compare.
95
+ * @param {{line: number, column: number}} b - Another location to compare.
96
+ * @returns {boolean} `true` if `a` is less than `b` or `a` equals `b`.
97
+ */
98
+ lte(a, b) {
99
+ return a.line < b.line || (a.line === b.line && a.column <= b.column)
100
+ },
101
+
102
+ /**
103
+ * Parse the given comment token as a directive comment.
104
+ *
105
+ * @param {Token} comment - The comment token to parse.
106
+ * @returns {{kind: string, value: string, description: string | null}|null} The parsed data of the given comment. If `null`, it is not a directive comment.
107
+ */
108
+ parseDirectiveComment(comment) {
109
+ const { text, description } = divideDirectiveComment(comment.value)
110
+ const match = DIRECTIVE_PATTERN.exec(text)
111
+
112
+ if (!match) {
113
+ return null
114
+ }
115
+ const directiveText = match[1]
116
+ const lineCommentSupported = LINE_COMMENT_PATTERN.test(directiveText)
117
+
118
+ if (comment.type === "Line" && !lineCommentSupported) {
119
+ return null
120
+ }
121
+
122
+ if (
123
+ lineCommentSupported &&
124
+ comment.loc.start.line !== comment.loc.end.line
125
+ ) {
126
+ // disable-line comment should not span multiple lines.
127
+ return null
128
+ }
129
+
130
+ const directiveValue = text.slice(match.index + directiveText.length)
131
+
132
+ return {
133
+ kind: directiveText,
134
+ value: directiveValue.trim(),
135
+ description,
136
+ }
137
+ },
138
+ }
139
+
140
+ /**
141
+ * Divides and trims description text and directive comments.
142
+ * @param {string} value The comment text to strip.
143
+ * @returns {{text: string, description: string | null}} The stripped text.
144
+ */
145
+ function divideDirectiveComment(value) {
146
+ const divided = value.split(/\s-{2,}\s/u)
147
+ const text = divided[0].trim()
148
+ return {
149
+ text,
150
+ description: divided.length > 1 ? divided[1].trim() : null,
151
+ }
152
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ const DisabledArea = require("../internal/disabled-area")
8
+ const utils = require("../internal/utils")
9
+
10
+ module.exports = {
11
+ meta: {
12
+ docs: {
13
+ description:
14
+ "require a `eslint-enable` comment for every `eslint-disable` comment",
15
+ category: "Best Practices",
16
+ recommended: true,
17
+ url:
18
+ "https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/disable-enable-pair.html",
19
+ },
20
+ fixable: null,
21
+ schema: [
22
+ {
23
+ type: "object",
24
+ properties: {
25
+ allowWholeFile: {
26
+ type: "boolean",
27
+ },
28
+ },
29
+ additionalProperties: false,
30
+ },
31
+ ],
32
+ type: "suggestion",
33
+ },
34
+
35
+ create(context) {
36
+ const allowWholeFile =
37
+ context.options[0] && context.options[0].allowWholeFile
38
+ const sourceCode = context.getSourceCode()
39
+ const disabledArea = DisabledArea.get(sourceCode)
40
+
41
+ return {
42
+ Program(node) {
43
+ if (allowWholeFile && node.body.length === 0) {
44
+ return
45
+ }
46
+
47
+ for (const area of disabledArea.areas) {
48
+ if (area.end != null) {
49
+ continue
50
+ }
51
+ if (
52
+ allowWholeFile &&
53
+ utils.lte(area.start, node.loc.start)
54
+ ) {
55
+ continue
56
+ }
57
+
58
+ context.report({
59
+ loc: utils.toRuleIdLocation(area.comment, area.ruleId),
60
+ message: area.ruleId
61
+ ? "Requires 'eslint-enable' directive for '{{ruleId}}'."
62
+ : "Requires 'eslint-enable' directive.",
63
+ data: area,
64
+ })
65
+ }
66
+ },
67
+ }
68
+ },
69
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ const DisabledArea = require("../internal/disabled-area")
8
+ const utils = require("../internal/utils")
9
+
10
+ module.exports = {
11
+ meta: {
12
+ docs: {
13
+ description:
14
+ "disallow a `eslint-enable` comment for multiple `eslint-disable` comments",
15
+ category: "Best Practices",
16
+ recommended: true,
17
+ url:
18
+ "https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-aggregating-enable.html",
19
+ },
20
+ fixable: null,
21
+ schema: [],
22
+ type: "suggestion",
23
+ },
24
+
25
+ create(context) {
26
+ const sourceCode = context.getSourceCode()
27
+ const disabledArea = DisabledArea.get(sourceCode)
28
+
29
+ return {
30
+ Program() {
31
+ for (const entry of disabledArea.numberOfRelatedDisableDirectives) {
32
+ const comment = entry[0]
33
+ const count = entry[1]
34
+
35
+ if (count >= 2) {
36
+ context.report({
37
+ loc: utils.toForceLocation(comment.loc),
38
+ message:
39
+ "This `eslint-enable` comment affects {{count}} `eslint-disable` comments. An `eslint-enable` comment should be for an `eslint-disable` comment.",
40
+ data: { count },
41
+ })
42
+ }
43
+ }
44
+ },
45
+ }
46
+ },
47
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ const DisabledArea = require("../internal/disabled-area")
8
+ const utils = require("../internal/utils")
9
+
10
+ module.exports = {
11
+ meta: {
12
+ docs: {
13
+ description: "disallow duplicate `eslint-disable` comments",
14
+ category: "Best Practices",
15
+ recommended: true,
16
+ url:
17
+ "https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-duplicate-disable.html",
18
+ },
19
+ fixable: null,
20
+ schema: [],
21
+ type: "problem",
22
+ },
23
+
24
+ create(context) {
25
+ const sourceCode = context.getSourceCode()
26
+ const disabledArea = DisabledArea.get(sourceCode)
27
+
28
+ return {
29
+ Program() {
30
+ for (const item of disabledArea.duplicateDisableDirectives) {
31
+ context.report({
32
+ loc: utils.toRuleIdLocation(item.comment, item.ruleId),
33
+ message: item.ruleId
34
+ ? "'{{ruleId}}' rule has been disabled already."
35
+ : "ESLint rules have been disabled already.",
36
+ data: item,
37
+ })
38
+ }
39
+ },
40
+ }
41
+ },
42
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ const ignore = require("ignore")
8
+ const DisabledArea = require("../internal/disabled-area")
9
+ const utils = require("../internal/utils")
10
+
11
+ module.exports = {
12
+ meta: {
13
+ docs: {
14
+ description:
15
+ "disallow `eslint-disable` comments about specific rules",
16
+ category: "Stylistic Issues",
17
+ recommended: false,
18
+ url:
19
+ "https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-restricted-disable.html",
20
+ },
21
+ fixable: null,
22
+ schema: {
23
+ type: "array",
24
+ items: { type: "string" },
25
+ uniqueItems: true,
26
+ },
27
+ type: "suggestion",
28
+ },
29
+
30
+ create(context) {
31
+ const sourceCode = context.getSourceCode()
32
+ const disabledArea = DisabledArea.get(sourceCode)
33
+
34
+ if (context.options.length === 0) {
35
+ return {}
36
+ }
37
+
38
+ const ig = ignore()
39
+ for (const pattern of context.options) {
40
+ ig.add(pattern)
41
+ }
42
+
43
+ return {
44
+ Program() {
45
+ for (const area of disabledArea.areas) {
46
+ if (area.ruleId == null || ig.ignores(area.ruleId)) {
47
+ context.report({
48
+ loc: utils.toRuleIdLocation(
49
+ area.comment,
50
+ area.ruleId
51
+ ),
52
+ message: "Disabling '{{ruleId}}' is not allowed.",
53
+ data: {
54
+ ruleId: area.ruleId || String(context.options),
55
+ },
56
+ })
57
+ }
58
+ }
59
+ },
60
+ }
61
+ },
62
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ const utils = require("../internal/utils")
8
+
9
+ module.exports = {
10
+ meta: {
11
+ docs: {
12
+ description:
13
+ "disallow `eslint-disable` comments without rule names",
14
+ category: "Best Practices",
15
+ recommended: true,
16
+ url:
17
+ "https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-unlimited-disable.html",
18
+ },
19
+ fixable: null,
20
+ schema: [],
21
+ type: "suggestion",
22
+ },
23
+
24
+ create(context) {
25
+ const sourceCode = context.getSourceCode()
26
+
27
+ return {
28
+ Program() {
29
+ for (const comment of sourceCode.getAllComments()) {
30
+ const directiveComment = utils.parseDirectiveComment(
31
+ comment
32
+ )
33
+ if (directiveComment == null) {
34
+ continue
35
+ }
36
+
37
+ const kind = directiveComment.kind
38
+ if (
39
+ kind !== "eslint-disable" &&
40
+ kind !== "eslint-disable-line" &&
41
+ kind !== "eslint-disable-next-line"
42
+ ) {
43
+ continue
44
+ }
45
+ if (!directiveComment.value) {
46
+ context.report({
47
+ loc: utils.toForceLocation(comment.loc),
48
+ message:
49
+ "Unexpected unlimited '{{kind}}' comment. Specify some rule names to disable.",
50
+ data: { kind: directiveComment.kind },
51
+ })
52
+ }
53
+ }
54
+ },
55
+ }
56
+ },
57
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ // Patch `Linter#verify` to work.
8
+ require("../utils/patch")()
9
+
10
+ module.exports = {
11
+ meta: {
12
+ docs: {
13
+ description: "disallow unused `eslint-disable` comments",
14
+ category: "Best Practices",
15
+ recommended: false,
16
+ url:
17
+ "https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-unused-disable.html",
18
+ },
19
+ fixable: null,
20
+ schema: [],
21
+ type: "problem",
22
+ },
23
+
24
+ create() {
25
+ // This rule patches `Linter#verify` method and:
26
+ //
27
+ // 1. enables `reportUnusedDisableDirectives` option.
28
+ // 2. verifies the code.
29
+ // 3. converts `reportUnusedDisableDirectives` errors to `no-unused-disable` errors.
30
+ //
31
+ // So this rule itself does nothing.
32
+ return {}
33
+ },
34
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ const DisabledArea = require("../internal/disabled-area")
8
+ const utils = require("../internal/utils")
9
+
10
+ module.exports = {
11
+ meta: {
12
+ docs: {
13
+ description: "disallow unused `eslint-enable` comments",
14
+ category: "Best Practices",
15
+ recommended: true,
16
+ url:
17
+ "https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-unused-enable.html",
18
+ },
19
+ fixable: null,
20
+ schema: [],
21
+ type: "problem",
22
+ },
23
+
24
+ create(context) {
25
+ const sourceCode = context.getSourceCode()
26
+ const disabledArea = DisabledArea.get(sourceCode)
27
+
28
+ return {
29
+ Program() {
30
+ for (const item of disabledArea.unusedEnableDirectives) {
31
+ context.report({
32
+ loc: utils.toRuleIdLocation(item.comment, item.ruleId),
33
+ message: item.ruleId
34
+ ? "'{{ruleId}}' rule is re-enabled but it has not been disabled."
35
+ : "ESLint rules are re-enabled but those have not been disabled.",
36
+ data: item,
37
+ })
38
+ }
39
+ },
40
+ }
41
+ },
42
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ const utils = require("../internal/utils")
8
+
9
+ module.exports = {
10
+ meta: {
11
+ docs: {
12
+ description: "disallow ESLint directive-comments",
13
+ category: "Stylistic Issues",
14
+ recommended: false,
15
+ url:
16
+ "https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-use.html",
17
+ },
18
+ fixable: null,
19
+ schema: [
20
+ {
21
+ type: "object",
22
+ properties: {
23
+ allow: {
24
+ type: "array",
25
+ items: {
26
+ enum: [
27
+ "eslint",
28
+ "eslint-disable",
29
+ "eslint-disable-line",
30
+ "eslint-disable-next-line",
31
+ "eslint-enable",
32
+ "eslint-env",
33
+ "exported",
34
+ "global",
35
+ "globals",
36
+ ],
37
+ },
38
+ additionalItems: false,
39
+ uniqueItems: true,
40
+ },
41
+ },
42
+ additionalProperties: false,
43
+ },
44
+ ],
45
+ type: "suggestion",
46
+ },
47
+
48
+ create(context) {
49
+ const sourceCode = context.getSourceCode()
50
+ const allowed = new Set(
51
+ (context.options[0] && context.options[0].allow) || []
52
+ )
53
+
54
+ return {
55
+ Program() {
56
+ for (const comment of sourceCode.getAllComments()) {
57
+ const directiveComment = utils.parseDirectiveComment(
58
+ comment
59
+ )
60
+ if (directiveComment == null) {
61
+ continue
62
+ }
63
+
64
+ if (!allowed.has(directiveComment.kind)) {
65
+ context.report({
66
+ loc: utils.toForceLocation(comment.loc),
67
+ message: "Unexpected ESLint directive comment.",
68
+ })
69
+ }
70
+ }
71
+ },
72
+ }
73
+ },
74
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @author Yosuke Ota <https://github.com/ota-meshi>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ const utils = require("../internal/utils")
8
+
9
+ module.exports = {
10
+ meta: {
11
+ docs: {
12
+ description:
13
+ "require include descriptions in ESLint directive-comments",
14
+ category: "Stylistic Issues",
15
+ recommended: false,
16
+ url:
17
+ "https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/require-description.html",
18
+ },
19
+ fixable: null,
20
+ schema: [
21
+ {
22
+ type: "object",
23
+ properties: {
24
+ ignore: {
25
+ type: "array",
26
+ items: {
27
+ enum: [
28
+ "eslint",
29
+ "eslint-disable",
30
+ "eslint-disable-line",
31
+ "eslint-disable-next-line",
32
+ "eslint-enable",
33
+ "eslint-env",
34
+ "exported",
35
+ "global",
36
+ "globals",
37
+ ],
38
+ },
39
+ additionalItems: false,
40
+ uniqueItems: true,
41
+ },
42
+ },
43
+ additionalProperties: false,
44
+ },
45
+ ],
46
+ type: "suggestion",
47
+ },
48
+
49
+ create(context) {
50
+ const sourceCode = context.getSourceCode()
51
+ const ignores = new Set(
52
+ (context.options[0] && context.options[0].ignore) || []
53
+ )
54
+
55
+ return {
56
+ Program() {
57
+ for (const comment of sourceCode.getAllComments()) {
58
+ const directiveComment = utils.parseDirectiveComment(
59
+ comment
60
+ )
61
+ if (directiveComment == null) {
62
+ continue
63
+ }
64
+ if (ignores.has(directiveComment.kind)) {
65
+ continue
66
+ }
67
+ if (!directiveComment.description) {
68
+ context.report({
69
+ loc: utils.toForceLocation(comment.loc),
70
+ message:
71
+ "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.",
72
+ })
73
+ }
74
+ }
75
+ },
76
+ }
77
+ },
78
+ }
package/lib/rules.js ADDED
@@ -0,0 +1,14 @@
1
+ /** DON'T EDIT THIS FILE; was created by scripts. */
2
+ "use strict"
3
+
4
+ module.exports = {
5
+ "disable-enable-pair": require("./rules/disable-enable-pair"),
6
+ "no-aggregating-enable": require("./rules/no-aggregating-enable"),
7
+ "no-duplicate-disable": require("./rules/no-duplicate-disable"),
8
+ "no-restricted-disable": require("./rules/no-restricted-disable"),
9
+ "no-unlimited-disable": require("./rules/no-unlimited-disable"),
10
+ "no-unused-disable": require("./rules/no-unused-disable"),
11
+ "no-unused-enable": require("./rules/no-unused-enable"),
12
+ "no-use": require("./rules/no-use"),
13
+ "require-description": require("./rules/require-description"),
14
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+ "use strict"
6
+
7
+ const getLinters = require("../internal/get-linters")
8
+ const { toRuleIdLocation } = require("../internal/utils")
9
+ const quotedName = /'(.+?)'/u
10
+
11
+ /**
12
+ * Get the severity of a given rule.
13
+ * @param {object} config The config object to check.
14
+ * @param {string} ruleId The rule ID to check.
15
+ * @returns {number} The severity of the rule.
16
+ */
17
+ function getSeverity(config, ruleId) {
18
+ const rules = config && config.rules
19
+ const ruleOptions = rules && rules[ruleId]
20
+ const severity = Array.isArray(ruleOptions) ? ruleOptions[0] : ruleOptions
21
+
22
+ switch (severity) {
23
+ case 2:
24
+ case "error":
25
+ return 2
26
+
27
+ case 1:
28
+ case "warn":
29
+ return 1
30
+
31
+ default:
32
+ return 0
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Get the comment which is at a given message location.
38
+ * @param {Message} message The message to get.
39
+ * @param {SourceCode|undefined} sourceCode The source code object to get.
40
+ * @returns {Comment|undefined} The gotten comment.
41
+ */
42
+ function getCommentAt(message, sourceCode) {
43
+ if (sourceCode != null) {
44
+ const loc = { line: message.line, column: message.column - 1 }
45
+ const index = sourceCode.getIndexFromLoc(loc)
46
+ const options = { includeComments: true }
47
+ const comment = sourceCode.getTokenByRangeStart(index, options)
48
+ if (
49
+ comment != null &&
50
+ (comment.type === "Line" || comment.type === "Block")
51
+ ) {
52
+ return comment
53
+ }
54
+ }
55
+ return undefined
56
+ }
57
+
58
+ /**
59
+ * Check whether a given message is a `reportUnusedDisableDirectives` error.
60
+ * @param {Message} message The message.
61
+ * @returns {boolean} `true` if the message is a `reportUnusedDisableDirectives` error.
62
+ */
63
+ function isUnusedDisableDirectiveError(message) {
64
+ return (
65
+ !message.fatal &&
66
+ !message.ruleId &&
67
+ message.message.includes("eslint-disable")
68
+ )
69
+ }
70
+
71
+ /**
72
+ * Create `@eslint-community/eslint-comments/no-unused-disable` error.
73
+ * @param {string} ruleId The ruleId.
74
+ * @param {number} severity The severity of the rule.
75
+ * @param {Message} message The original message.
76
+ * @param {Comment|undefined} comment The directive comment.
77
+ * @returns {Message} The created error.
78
+ */
79
+ function createNoUnusedDisableError(ruleId, severity, message, comment) {
80
+ const clone = Object.assign({}, message)
81
+ const match = quotedName.exec(message.message)
82
+ const targetRuleId = match && match[1]
83
+
84
+ clone.ruleId = ruleId
85
+ clone.severity = severity
86
+ clone.message = targetRuleId
87
+ ? `'${targetRuleId}' rule is disabled but never reported.`
88
+ : "ESLint rules are disabled but never reported."
89
+ clone.suggestions = []
90
+
91
+ if (comment != null) {
92
+ if (targetRuleId) {
93
+ const loc = toRuleIdLocation(comment, targetRuleId)
94
+ clone.line = loc.start.line
95
+ clone.column = loc.start.column + 1
96
+ clone.endLine = loc.end.line
97
+ clone.endColumn = loc.end.column + 1
98
+ } else {
99
+ clone.endLine = comment.loc.end.line
100
+ clone.endColumn = comment.loc.end.column + 1
101
+ }
102
+ // Remove the whole node if it is the only rule, otherwise
103
+ // don't try to fix because it is quite complicated.
104
+ if (!comment.value.includes(",") && !comment.value.includes("--")) {
105
+ // We can't use the typical `fixer` helper because we are injecting
106
+ // this message after the fixes are resolved.
107
+ clone.suggestions = [
108
+ {
109
+ desc: "Remove `eslint-disable` comment.",
110
+ fix: {
111
+ range: comment.range,
112
+ text: comment.value.includes("\n") ? "\n" : "",
113
+ },
114
+ },
115
+ ]
116
+ }
117
+ }
118
+
119
+ return clone
120
+ }
121
+
122
+ /**
123
+ * Convert `reportUnusedDisableDirectives` errors to `@eslint-community/eslint-comments/no-unused-disable` errors.
124
+ * @param {Message[]} messages The original messages.
125
+ * @param {SourceCode|undefined} sourceCode The source code object.
126
+ * @param {string} ruleId The rule ID to convert.
127
+ * @param {number} severity The severity of the rule.
128
+ * @param {boolean} keepAsIs The flag to keep original errors as is.
129
+ * @returns {Message[]} The converted messages.
130
+ */
131
+ function convert(messages, sourceCode, ruleId, severity, keepAsIs) {
132
+ for (let i = messages.length - 1; i >= 0; --i) {
133
+ const message = messages[i]
134
+ if (!isUnusedDisableDirectiveError(message)) {
135
+ continue
136
+ }
137
+
138
+ const newMessage = createNoUnusedDisableError(
139
+ ruleId,
140
+ severity,
141
+ message,
142
+ getCommentAt(message, sourceCode)
143
+ )
144
+
145
+ if (keepAsIs) {
146
+ messages.splice(i + 1, 0, newMessage)
147
+ } else {
148
+ messages.splice(i, 1, newMessage)
149
+ }
150
+ }
151
+
152
+ return messages
153
+ }
154
+
155
+ module.exports = (
156
+ ruleId = "@eslint-community/eslint-comments/no-unused-disable"
157
+ ) => {
158
+ for (const Linter of getLinters()) {
159
+ const verify0 = Linter.prototype._verifyWithoutProcessors
160
+ Object.defineProperty(Linter.prototype, "_verifyWithoutProcessors", {
161
+ value: function _verifyWithoutProcessors(
162
+ textOrSourceCode,
163
+ config,
164
+ filenameOrOptions
165
+ ) {
166
+ const severity = getSeverity(config, ruleId)
167
+ if (severity === 0) {
168
+ return verify0.call(
169
+ this,
170
+ textOrSourceCode,
171
+ config,
172
+ filenameOrOptions
173
+ )
174
+ }
175
+
176
+ const options =
177
+ typeof filenameOrOptions === "string"
178
+ ? { filename: filenameOrOptions }
179
+ : filenameOrOptions || {}
180
+ const reportUnusedDisableDirectives = Boolean(
181
+ options.reportUnusedDisableDirectives
182
+ )
183
+ const messages = verify0.call(
184
+ this,
185
+ textOrSourceCode,
186
+ config,
187
+ Object.assign({}, options, {
188
+ reportUnusedDisableDirectives: true,
189
+ })
190
+ )
191
+ return convert(
192
+ messages,
193
+ this.getSourceCode(),
194
+ ruleId,
195
+ severity,
196
+ reportUnusedDisableDirectives
197
+ )
198
+ },
199
+ configurable: true,
200
+ writable: true,
201
+ })
202
+ }
203
+ }
package/lib/utils.js ADDED
@@ -0,0 +1,6 @@
1
+ /** DON'T EDIT THIS FILE; was created by scripts. */
2
+ "use strict"
3
+
4
+ module.exports = {
5
+ patch: require("./utils/patch"),
6
+ }
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@eslint-community/eslint-plugin-eslint-comments",
3
+ "version": "3.2.0",
4
+ "description": "Additional ESLint rules for ESLint directive comments.",
5
+ "engines": {
6
+ "node": ">=6.5.0"
7
+ },
8
+ "main": "index.js",
9
+ "files": [
10
+ "lib"
11
+ ],
12
+ "peerDependencies": {
13
+ "eslint": ">=4.19.1"
14
+ },
15
+ "dependencies": {
16
+ "escape-string-regexp": "^1.0.5",
17
+ "ignore": "^5.0.5"
18
+ },
19
+ "devDependencies": {
20
+ "@mysticatea/eslint-plugin": "^13.0.0",
21
+ "@types/node": "^14.0.1",
22
+ "@vuepress/plugin-pwa": "^1.0.1",
23
+ "babel-eslint": "^10.0.1",
24
+ "codecov": "^3.3.0",
25
+ "cross-spawn": "^6.0.5",
26
+ "eslint": "^7.0.0",
27
+ "eslint4b": "^7.0.0",
28
+ "fs-extra": "^8.0.1",
29
+ "mocha": "^6.1.4",
30
+ "nyc": "^14.1.1",
31
+ "opener": "^1.4.3",
32
+ "rimraf": "^2.6.2",
33
+ "semver": "^7.3.2",
34
+ "string-replace-loader": "^2.1.1",
35
+ "vue-eslint-editor": "^1.1.0",
36
+ "vuepress": "^1.0.1"
37
+ },
38
+ "scripts": {
39
+ "preversion": "npm test",
40
+ "version": "node scripts/update && git add .",
41
+ "postversion": "git push && git push --tags",
42
+ "clean": "rimraf .nyc_output coverage docs/.vuepress/dist",
43
+ "docs:build": "vuepress build docs",
44
+ "docs:watch": "vuepress dev docs",
45
+ "docs:deploy": "node scripts/deploy",
46
+ "lint": "eslint lib scripts tests",
47
+ "pretest": "npm run -s lint",
48
+ "test": "nyc npm run -s test:mocha",
49
+ "test:ci": "nyc npm run -s test:mocha",
50
+ "test:mocha": "mocha \"tests/lib/**/*.js\" --reporter dot --timeout 8000",
51
+ "watch": "npm run -s test:mocha -- --watch --growl",
52
+ "coverage": "nyc report --reporter lcov && opener coverage/lcov-report/index.html",
53
+ "codecov": "nyc report --reporter text-lcov | codecov --pipe --disable=gcov"
54
+ },
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "https://github.com/eslint-community/eslint-plugin-eslint-comments"
58
+ },
59
+ "keywords": [
60
+ "eslint",
61
+ "eslintplugin",
62
+ "eslint-plugin",
63
+ "plugin",
64
+ "comment",
65
+ "comments",
66
+ "directive",
67
+ "global",
68
+ "globals",
69
+ "exported",
70
+ "eslint-env",
71
+ "eslint-enable",
72
+ "eslint-disable",
73
+ "eslint-disable-line",
74
+ "eslint-disable-next-line"
75
+ ],
76
+ "author": "Toru Nagashima",
77
+ "license": "MIT",
78
+ "bugs": {
79
+ "url": "https://github.com/eslint-community/eslint-plugin-eslint-comments/issues"
80
+ },
81
+ "homepage": "https://github.com/eslint-community/eslint-plugin-eslint-comments#readme"
82
+ }