initial commit of actions

This commit is contained in:
2026-01-31 18:56:04 +01:00
commit 949ece5785
44660 changed files with 12034344 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Toru Nagashima
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,36 @@
# eslint-plugin-eslint-comments
[![npm version](https://img.shields.io/npm/v/eslint-plugin-eslint-comments.svg)](https://www.npmjs.com/package/eslint-plugin-eslint-comments)
[![Downloads/month](https://img.shields.io/npm/dm/eslint-plugin-eslint-comments.svg)](http://www.npmtrends.com/eslint-plugin-eslint-comments)
[![Build Status](https://github.com/mysticatea/eslint-plugin-eslint-comments/workflows/CI/badge.svg)](https://github.com/mysticatea/eslint-plugin-eslint-comments/actions)
[![codecov](https://codecov.io/gh/mysticatea/eslint-plugin-eslint-comments/branch/master/graph/badge.svg)](https://codecov.io/gh/mysticatea/eslint-plugin-eslint-comments)
[![Dependency Status](https://david-dm.org/mysticatea/eslint-plugin-eslint-comments.svg)](https://david-dm.org/mysticatea/eslint-plugin-eslint-comments)
Additional ESLint rules for ESLint directive comments (e.g. `//eslint-disable-line`).
## 📖 Usage
- [Documentation](https://mysticatea.github.io/eslint-plugin-eslint-comments)
## 🚥 Semantic Versioning Policy
`eslint-plugin-eslint-comments` follows [semantic versioning](http://semver.org/) and [ESLint's Semantic Versioning Policy](https://github.com/eslint/eslint#semantic-versioning-policy).
## 📰 Changelog
- [GitHub Releases](https://github.com/mysticatea/eslint-plugin-eslint-comments/releases)
## 🍻 Contributing
Welcome contributing!
Please use GitHub's Issues/PRs.
### Development Tools
- `npm test` runs tests and measures coverage.
- `npm run build` updates `README.md`, `index.js`, and the header of all rule's documents.
- `npm run clean` removes the coverage of the last `npm test` command.
- `npm run coverage` shows the coverage of the last `npm test` command.
- `npm run lint` runs ESLint for this codebase.
- `npm run watch` runs tests and measures coverage when source code are changed.

View File

@@ -0,0 +1,8 @@
/** DON'T EDIT THIS FILE WHICH WAS CREATED BY 'scripts/generate-index.js'. */
"use strict"
module.exports = {
configs: require("./lib/configs"),
rules: require("./lib/rules"),
utils: require("./lib/utils"),
}

View File

@@ -0,0 +1,6 @@
/** DON'T EDIT THIS FILE; was created by scripts. */
"use strict"
module.exports = {
recommended: require("./configs/recommended"),
}

View File

@@ -0,0 +1,13 @@
/** DON'T EDIT THIS FILE; was created by scripts. */
"use strict"
module.exports = {
plugins: ["eslint-comments"],
rules: {
"eslint-comments/disable-enable-pair": "error",
"eslint-comments/no-aggregating-enable": "error",
"eslint-comments/no-duplicate-disable": "error",
"eslint-comments/no-unlimited-disable": "error",
"eslint-comments/no-unused-enable": "error",
},
}

View File

@@ -0,0 +1,211 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"
const utils = require("./utils")
const DELIMITER = /[\s,]+/gu
const pool = new WeakMap()
module.exports = class DisabledArea {
/**
* Get singleton instance for the given source code.
*
* @param {eslint.SourceCode} sourceCode - The source code to get.
* @returns {DisabledArea} The singleton object for the source code.
*/
static get(sourceCode) {
let retv = pool.get(sourceCode.ast)
if (retv == null) {
retv = new DisabledArea()
retv._scan(sourceCode)
pool.set(sourceCode.ast, retv)
}
return retv
}
/**
* Constructor.
*/
constructor() {
this.areas = []
this.duplicateDisableDirectives = []
this.unusedEnableDirectives = []
this.numberOfRelatedDisableDirectives = new Map()
}
/**
* Make disabled area.
*
* @param {Token} comment - The comment token to disable.
* @param {object} location - The start location to disable.
* @param {string[]|null} ruleIds - The ruleId names to disable.
* @param {string} kind - The kind of disable-comments.
* @returns {void}
* @private
*/
_disable(comment, location, ruleIds, kind) {
if (ruleIds) {
for (const ruleId of ruleIds) {
if (this._getArea(ruleId, location) != null) {
this.duplicateDisableDirectives.push({ comment, ruleId })
}
this.areas.push({
comment,
ruleId,
kind,
start: location,
end: null,
})
}
} else {
if (this._getArea(null, location) != null) {
this.duplicateDisableDirectives.push({ comment, ruleId: null })
}
this.areas.push({
comment,
ruleId: null,
kind,
start: location,
end: null,
})
}
}
/**
* Close disabled area.
*
* @param {Token} comment - The comment token to enable.
* @param {object} location - The start location to enable.
* @param {string[]|null} ruleIds - The ruleId names to enable.
* @param {string} kind - The kind of disable-comments.
* @returns {void}
* @private
*/
_enable(comment, location, ruleIds, kind) {
const relatedDisableDirectives = new Set()
if (ruleIds) {
for (const ruleId of ruleIds) {
let used = false
for (let i = this.areas.length - 1; i >= 0; --i) {
const area = this.areas[i]
if (
area.end === null &&
area.kind === kind &&
area.ruleId === ruleId
) {
relatedDisableDirectives.add(area.comment)
area.end = location
used = true
}
}
if (!used) {
this.unusedEnableDirectives.push({ comment, ruleId })
}
}
} else {
let used = false
for (let i = this.areas.length - 1; i >= 0; --i) {
const area = this.areas[i]
if (area.end === null && area.kind === kind) {
relatedDisableDirectives.add(area.comment)
area.end = location
used = true
}
}
if (!used) {
this.unusedEnableDirectives.push({ comment, ruleId: null })
}
}
this.numberOfRelatedDisableDirectives.set(
comment,
relatedDisableDirectives.size
)
}
/**
* Gets the area of the given ruleId and location.
*
* @param {string|null} ruleId - The ruleId name to get.
* @param {object} location - The location to get.
* @returns {object|null} The area of the given ruleId and location.
* @private
*/
_getArea(ruleId, location) {
for (let i = this.areas.length - 1; i >= 0; --i) {
const area = this.areas[i]
if (
(area.ruleId === null || area.ruleId === ruleId) &&
utils.lte(area.start, location) &&
(area.end === null || utils.lte(location, area.end))
) {
return area
}
}
return null
}
/**
* Scan the source code and setup disabled area list.
*
* @param {eslint.SourceCode} sourceCode - The source code to scan.
* @returns {void}
* @private
*/
_scan(sourceCode) {
for (const comment of sourceCode.getAllComments()) {
const directiveComment = utils.parseDirectiveComment(comment)
if (directiveComment == null) {
continue
}
const kind = directiveComment.kind
if (
kind !== "eslint-disable" &&
kind !== "eslint-enable" &&
kind !== "eslint-disable-line" &&
kind !== "eslint-disable-next-line"
) {
continue
}
const ruleIds = directiveComment.value
? directiveComment.value.split(DELIMITER)
: null
if (kind === "eslint-disable") {
this._disable(comment, comment.loc.start, ruleIds, "block")
} else if (kind === "eslint-enable") {
this._enable(comment, comment.loc.start, ruleIds, "block")
} else if (kind === "eslint-disable-line") {
const line = comment.loc.start.line
const start = { line, column: 0 }
const end = { line: line + 1, column: -1 }
this._disable(comment, start, ruleIds, "line")
this._enable(comment, end, ruleIds, "line")
} else if (kind === "eslint-disable-next-line") {
const line = comment.loc.start.line
const start = { line: line + 1, column: 0 }
const end = { line: line + 2, column: -1 }
this._disable(comment, start, ruleIds, "line")
this._enable(comment, end, ruleIds, "line")
}
}
}
}

View File

@@ -0,0 +1,33 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"
const path = require("path")
const needle = `${path.sep}node_modules${path.sep}eslint${path.sep}`
module.exports = () => {
const eslintPaths = new Set(
Object.keys(require.cache)
.filter(id => id.includes(needle))
.map(id => id.slice(0, id.indexOf(needle) + needle.length))
)
const linters = []
for (const eslintPath of eslintPaths) {
try {
const linter = require(eslintPath).Linter
if (linter) {
linters.push(linter)
}
} catch (error) {
if (error.code !== "MODULE_NOT_FOUND") {
throw error
}
}
}
return linters
}

View File

@@ -0,0 +1,152 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"
const escapeStringRegexp = require("escape-string-regexp")
const LINE_PATTERN = /[^\r\n\u2028\u2029]*(?:\r\n|[\r\n\u2028\u2029]|$)/gu
const DIRECTIVE_PATTERN = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u
const LINE_COMMENT_PATTERN = /^eslint-disable-(next-)?line$/u
module.exports = {
/**
* Make the location ignoring `eslint-disable` comments.
*
* @param {object} location - The location to convert.
* @returns {object} Converted location.
*/
toForceLocation(location) {
return {
start: {
line: location.start.line,
column: -1,
},
end: location.end,
}
},
/**
* Calculate the location of the given rule in the given comment token.
*
* @param {Token} comment - The comment token to calculate.
* @param {string|null} ruleId - The rule name to calculate.
* @returns {object} The location of the given information.
*/
toRuleIdLocation(comment, ruleId) {
if (ruleId == null) {
return module.exports.toForceLocation(comment.loc)
}
const lines = comment.value.match(LINE_PATTERN)
//eslint-disable-next-line require-unicode-regexp
const ruleIdPattern = new RegExp(
`([\\s,]|^)${escapeStringRegexp(ruleId)}(?:[\\s,]|$)`
)
{
const m = ruleIdPattern.exec(lines[0])
if (m != null) {
const start = comment.loc.start
return {
start: {
line: start.line,
column: 2 + start.column + m.index + m[1].length,
},
end: {
line: start.line,
column:
2 +
start.column +
m.index +
m[1].length +
ruleId.length,
},
}
}
}
for (let i = 1; i < lines.length; ++i) {
const m = ruleIdPattern.exec(lines[i])
if (m != null) {
const start = comment.loc.start
return {
start: {
line: start.line + i,
column: m.index + m[1].length,
},
end: {
line: start.line + i,
column: m.index + m[1].length + ruleId.length,
},
}
}
}
/*istanbul ignore next : foolproof */
return comment.loc
},
/**
* Checks `a` is less than `b` or `a` equals `b`.
*
* @param {{line: number, column: number}} a - A location to compare.
* @param {{line: number, column: number}} b - Another location to compare.
* @returns {boolean} `true` if `a` is less than `b` or `a` equals `b`.
*/
lte(a, b) {
return a.line < b.line || (a.line === b.line && a.column <= b.column)
},
/**
* Parse the given comment token as a directive comment.
*
* @param {Token} comment - The comment token to parse.
* @returns {{kind: string, value: string, description: string | null}|null} The parsed data of the given comment. If `null`, it is not a directive comment.
*/
parseDirectiveComment(comment) {
const { text, description } = divideDirectiveComment(comment.value)
const match = DIRECTIVE_PATTERN.exec(text)
if (!match) {
return null
}
const directiveText = match[1]
const lineCommentSupported = LINE_COMMENT_PATTERN.test(directiveText)
if (comment.type === "Line" && !lineCommentSupported) {
return null
}
if (
lineCommentSupported &&
comment.loc.start.line !== comment.loc.end.line
) {
// disable-line comment should not span multiple lines.
return null
}
const directiveValue = text.slice(match.index + directiveText.length)
return {
kind: directiveText,
value: directiveValue.trim(),
description,
}
},
}
/**
* Divides and trims description text and directive comments.
* @param {string} value The comment text to strip.
* @returns {{text: string, description: string | null}} The stripped text.
*/
function divideDirectiveComment(value) {
const divided = value.split(/\s-{2,}\s/u)
const text = divided[0].trim()
return {
text,
description: divided.length > 1 ? divided[1].trim() : null,
}
}

View File

@@ -0,0 +1,14 @@
/** DON'T EDIT THIS FILE; was created by scripts. */
"use strict"
module.exports = {
"disable-enable-pair": require("./rules/disable-enable-pair"),
"no-aggregating-enable": require("./rules/no-aggregating-enable"),
"no-duplicate-disable": require("./rules/no-duplicate-disable"),
"no-restricted-disable": require("./rules/no-restricted-disable"),
"no-unlimited-disable": require("./rules/no-unlimited-disable"),
"no-unused-disable": require("./rules/no-unused-disable"),
"no-unused-enable": require("./rules/no-unused-enable"),
"no-use": require("./rules/no-use"),
"require-description": require("./rules/require-description"),
}

View File

@@ -0,0 +1,69 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"
const DisabledArea = require("../internal/disabled-area")
const utils = require("../internal/utils")
module.exports = {
meta: {
docs: {
description:
"require a `eslint-enable` comment for every `eslint-disable` comment",
category: "Best Practices",
recommended: true,
url:
"https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/disable-enable-pair.html",
},
fixable: null,
schema: [
{
type: "object",
properties: {
allowWholeFile: {
type: "boolean",
},
},
additionalProperties: false,
},
],
type: "suggestion",
},
create(context) {
const allowWholeFile =
context.options[0] && context.options[0].allowWholeFile
const sourceCode = context.getSourceCode()
const disabledArea = DisabledArea.get(sourceCode)
return {
Program(node) {
if (allowWholeFile && node.body.length === 0) {
return
}
for (const area of disabledArea.areas) {
if (area.end != null) {
continue
}
if (
allowWholeFile &&
utils.lte(area.start, node.loc.start)
) {
continue
}
context.report({
loc: utils.toRuleIdLocation(area.comment, area.ruleId),
message: area.ruleId
? "Requires 'eslint-enable' directive for '{{ruleId}}'."
: "Requires 'eslint-enable' directive.",
data: area,
})
}
},
}
},
}

View File

@@ -0,0 +1,47 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"
const DisabledArea = require("../internal/disabled-area")
const utils = require("../internal/utils")
module.exports = {
meta: {
docs: {
description:
"disallow a `eslint-enable` comment for multiple `eslint-disable` comments",
category: "Best Practices",
recommended: true,
url:
"https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-aggregating-enable.html",
},
fixable: null,
schema: [],
type: "suggestion",
},
create(context) {
const sourceCode = context.getSourceCode()
const disabledArea = DisabledArea.get(sourceCode)
return {
Program() {
for (const entry of disabledArea.numberOfRelatedDisableDirectives) {
const comment = entry[0]
const count = entry[1]
if (count >= 2) {
context.report({
loc: utils.toForceLocation(comment.loc),
message:
"This `eslint-enable` comment affects {{count}} `eslint-disable` comments. An `eslint-enable` comment should be for an `eslint-disable` comment.",
data: { count },
})
}
}
},
}
},
}

View File

@@ -0,0 +1,42 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"
const DisabledArea = require("../internal/disabled-area")
const utils = require("../internal/utils")
module.exports = {
meta: {
docs: {
description: "disallow duplicate `eslint-disable` comments",
category: "Best Practices",
recommended: true,
url:
"https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-duplicate-disable.html",
},
fixable: null,
schema: [],
type: "problem",
},
create(context) {
const sourceCode = context.getSourceCode()
const disabledArea = DisabledArea.get(sourceCode)
return {
Program() {
for (const item of disabledArea.duplicateDisableDirectives) {
context.report({
loc: utils.toRuleIdLocation(item.comment, item.ruleId),
message: item.ruleId
? "'{{ruleId}}' rule has been disabled already."
: "ESLint rules have been disabled already.",
data: item,
})
}
},
}
},
}

View File

@@ -0,0 +1,62 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"
const ignore = require("ignore")
const DisabledArea = require("../internal/disabled-area")
const utils = require("../internal/utils")
module.exports = {
meta: {
docs: {
description:
"disallow `eslint-disable` comments about specific rules",
category: "Stylistic Issues",
recommended: false,
url:
"https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-restricted-disable.html",
},
fixable: null,
schema: {
type: "array",
items: { type: "string" },
uniqueItems: true,
},
type: "suggestion",
},
create(context) {
const sourceCode = context.getSourceCode()
const disabledArea = DisabledArea.get(sourceCode)
if (context.options.length === 0) {
return {}
}
const ig = ignore()
for (const pattern of context.options) {
ig.add(pattern)
}
return {
Program() {
for (const area of disabledArea.areas) {
if (area.ruleId == null || ig.ignores(area.ruleId)) {
context.report({
loc: utils.toRuleIdLocation(
area.comment,
area.ruleId
),
message: "Disabling '{{ruleId}}' is not allowed.",
data: {
ruleId: area.ruleId || String(context.options),
},
})
}
}
},
}
},
}

View File

@@ -0,0 +1,57 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"
const utils = require("../internal/utils")
module.exports = {
meta: {
docs: {
description:
"disallow `eslint-disable` comments without rule names",
category: "Best Practices",
recommended: true,
url:
"https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-unlimited-disable.html",
},
fixable: null,
schema: [],
type: "suggestion",
},
create(context) {
const sourceCode = context.getSourceCode()
return {
Program() {
for (const comment of sourceCode.getAllComments()) {
const directiveComment = utils.parseDirectiveComment(
comment
)
if (directiveComment == null) {
continue
}
const kind = directiveComment.kind
if (
kind !== "eslint-disable" &&
kind !== "eslint-disable-line" &&
kind !== "eslint-disable-next-line"
) {
continue
}
if (!directiveComment.value) {
context.report({
loc: utils.toForceLocation(comment.loc),
message:
"Unexpected unlimited '{{kind}}' comment. Specify some rule names to disable.",
data: { kind: directiveComment.kind },
})
}
}
},
}
},
}

View File

@@ -0,0 +1,34 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"
// Patch `Linter#verify` to work.
require("../utils/patch")()
module.exports = {
meta: {
docs: {
description: "disallow unused `eslint-disable` comments",
category: "Best Practices",
recommended: false,
url:
"https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-unused-disable.html",
},
fixable: null,
schema: [],
type: "problem",
},
create() {
// This rule patches `Linter#verify` method and:
//
// 1. enables `reportUnusedDisableDirectives` option.
// 2. verifies the code.
// 3. converts `reportUnusedDisableDirectives` errors to `no-unused-disable` errors.
//
// So this rule itself does nothing.
return {}
},
}

View File

@@ -0,0 +1,42 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"
const DisabledArea = require("../internal/disabled-area")
const utils = require("../internal/utils")
module.exports = {
meta: {
docs: {
description: "disallow unused `eslint-enable` comments",
category: "Best Practices",
recommended: true,
url:
"https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-unused-enable.html",
},
fixable: null,
schema: [],
type: "problem",
},
create(context) {
const sourceCode = context.getSourceCode()
const disabledArea = DisabledArea.get(sourceCode)
return {
Program() {
for (const item of disabledArea.unusedEnableDirectives) {
context.report({
loc: utils.toRuleIdLocation(item.comment, item.ruleId),
message: item.ruleId
? "'{{ruleId}}' rule is re-enabled but it has not been disabled."
: "ESLint rules are re-enabled but those have not been disabled.",
data: item,
})
}
},
}
},
}

View File

@@ -0,0 +1,74 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"
const utils = require("../internal/utils")
module.exports = {
meta: {
docs: {
description: "disallow ESLint directive-comments",
category: "Stylistic Issues",
recommended: false,
url:
"https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/no-use.html",
},
fixable: null,
schema: [
{
type: "object",
properties: {
allow: {
type: "array",
items: {
enum: [
"eslint",
"eslint-disable",
"eslint-disable-line",
"eslint-disable-next-line",
"eslint-enable",
"eslint-env",
"exported",
"global",
"globals",
],
},
additionalItems: false,
uniqueItems: true,
},
},
additionalProperties: false,
},
],
type: "suggestion",
},
create(context) {
const sourceCode = context.getSourceCode()
const allowed = new Set(
(context.options[0] && context.options[0].allow) || []
)
return {
Program() {
for (const comment of sourceCode.getAllComments()) {
const directiveComment = utils.parseDirectiveComment(
comment
)
if (directiveComment == null) {
continue
}
if (!allowed.has(directiveComment.kind)) {
context.report({
loc: utils.toForceLocation(comment.loc),
message: "Unexpected ESLint directive comment.",
})
}
}
},
}
},
}

View File

@@ -0,0 +1,78 @@
/**
* @author Yosuke Ota <https://github.com/ota-meshi>
* See LICENSE file in root directory for full license.
*/
"use strict"
const utils = require("../internal/utils")
module.exports = {
meta: {
docs: {
description:
"require include descriptions in ESLint directive-comments",
category: "Stylistic Issues",
recommended: false,
url:
"https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/require-description.html",
},
fixable: null,
schema: [
{
type: "object",
properties: {
ignore: {
type: "array",
items: {
enum: [
"eslint",
"eslint-disable",
"eslint-disable-line",
"eslint-disable-next-line",
"eslint-enable",
"eslint-env",
"exported",
"global",
"globals",
],
},
additionalItems: false,
uniqueItems: true,
},
},
additionalProperties: false,
},
],
type: "suggestion",
},
create(context) {
const sourceCode = context.getSourceCode()
const ignores = new Set(
(context.options[0] && context.options[0].ignore) || []
)
return {
Program() {
for (const comment of sourceCode.getAllComments()) {
const directiveComment = utils.parseDirectiveComment(
comment
)
if (directiveComment == null) {
continue
}
if (ignores.has(directiveComment.kind)) {
continue
}
if (!directiveComment.description) {
context.report({
loc: utils.toForceLocation(comment.loc),
message:
"Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.",
})
}
}
},
}
},
}

View File

@@ -0,0 +1,6 @@
/** DON'T EDIT THIS FILE; was created by scripts. */
"use strict"
module.exports = {
patch: require("./utils/patch"),
}

View File

@@ -0,0 +1,201 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"
const getLinters = require("../internal/get-linters")
const { toRuleIdLocation } = require("../internal/utils")
const quotedName = /'(.+?)'/u
/**
* Get the severity of a given rule.
* @param {object} config The config object to check.
* @param {string} ruleId The rule ID to check.
* @returns {number} The severity of the rule.
*/
function getSeverity(config, ruleId) {
const rules = config && config.rules
const ruleOptions = rules && rules[ruleId]
const severity = Array.isArray(ruleOptions) ? ruleOptions[0] : ruleOptions
switch (severity) {
case 2:
case "error":
return 2
case 1:
case "warn":
return 1
default:
return 0
}
}
/**
* Get the comment which is at a given message location.
* @param {Message} message The message to get.
* @param {SourceCode|undefined} sourceCode The source code object to get.
* @returns {Comment|undefined} The gotten comment.
*/
function getCommentAt(message, sourceCode) {
if (sourceCode != null) {
const loc = { line: message.line, column: message.column - 1 }
const index = sourceCode.getIndexFromLoc(loc)
const options = { includeComments: true }
const comment = sourceCode.getTokenByRangeStart(index, options)
if (
comment != null &&
(comment.type === "Line" || comment.type === "Block")
) {
return comment
}
}
return undefined
}
/**
* Check whether a given message is a `reportUnusedDisableDirectives` error.
* @param {Message} message The message.
* @returns {boolean} `true` if the message is a `reportUnusedDisableDirectives` error.
*/
function isUnusedDisableDirectiveError(message) {
return (
!message.fatal &&
!message.ruleId &&
message.message.includes("eslint-disable")
)
}
/**
* Create `eslint-comments/no-unused-disable` error.
* @param {string} ruleId The ruleId.
* @param {number} severity The severity of the rule.
* @param {Message} message The original message.
* @param {Comment|undefined} comment The directive comment.
* @returns {Message} The created error.
*/
function createNoUnusedDisableError(ruleId, severity, message, comment) {
const clone = Object.assign({}, message)
const match = quotedName.exec(message.message)
const targetRuleId = match && match[1]
clone.ruleId = ruleId
clone.severity = severity
clone.message = targetRuleId
? `'${targetRuleId}' rule is disabled but never reported.`
: "ESLint rules are disabled but never reported."
clone.suggestions = []
if (comment != null) {
if (targetRuleId) {
const loc = toRuleIdLocation(comment, targetRuleId)
clone.line = loc.start.line
clone.column = loc.start.column + 1
clone.endLine = loc.end.line
clone.endColumn = loc.end.column + 1
} else {
clone.endLine = comment.loc.end.line
clone.endColumn = comment.loc.end.column + 1
}
// Remove the whole node if it is the only rule, otherwise
// don't try to fix because it is quite complicated.
if (!comment.value.includes(",") && !comment.value.includes("--")) {
// We can't use the typical `fixer` helper because we are injecting
// this message after the fixes are resolved.
clone.suggestions = [
{
desc: "Remove `eslint-disable` comment.",
fix: {
range: comment.range,
text: comment.value.includes("\n") ? "\n" : "",
},
},
]
}
}
return clone
}
/**
* Convert `reportUnusedDisableDirectives` errors to `eslint-comments/no-unused-disable` errors.
* @param {Message[]} messages The original messages.
* @param {SourceCode|undefined} sourceCode The source code object.
* @param {string} ruleId The rule ID to convert.
* @param {number} severity The severity of the rule.
* @param {boolean} keepAsIs The flag to keep original errors as is.
* @returns {Message[]} The converted messages.
*/
function convert(messages, sourceCode, ruleId, severity, keepAsIs) {
for (let i = messages.length - 1; i >= 0; --i) {
const message = messages[i]
if (!isUnusedDisableDirectiveError(message)) {
continue
}
const newMessage = createNoUnusedDisableError(
ruleId,
severity,
message,
getCommentAt(message, sourceCode)
)
if (keepAsIs) {
messages.splice(i + 1, 0, newMessage)
} else {
messages.splice(i, 1, newMessage)
}
}
return messages
}
module.exports = (ruleId = "eslint-comments/no-unused-disable") => {
for (const Linter of getLinters()) {
const verify0 = Linter.prototype._verifyWithoutProcessors
Object.defineProperty(Linter.prototype, "_verifyWithoutProcessors", {
value: function _verifyWithoutProcessors(
textOrSourceCode,
config,
filenameOrOptions
) {
const severity = getSeverity(config, ruleId)
if (severity === 0) {
return verify0.call(
this,
textOrSourceCode,
config,
filenameOrOptions
)
}
const options =
typeof filenameOrOptions === "string"
? { filename: filenameOrOptions }
: filenameOrOptions || {}
const reportUnusedDisableDirectives = Boolean(
options.reportUnusedDisableDirectives
)
const messages = verify0.call(
this,
textOrSourceCode,
config,
Object.assign({}, options, {
reportUnusedDisableDirectives: true,
})
)
return convert(
messages,
this.getSourceCode(),
ruleId,
severity,
reportUnusedDisableDirectives
)
},
configurable: true,
writable: true,
})
}
}

View File

@@ -0,0 +1,83 @@
{
"name": "eslint-plugin-eslint-comments",
"version": "3.2.0",
"description": "Additional ESLint rules for ESLint directive comments.",
"engines": {
"node": ">=6.5.0"
},
"main": "index.js",
"files": [
"lib"
],
"peerDependencies": {
"eslint": ">=4.19.1"
},
"dependencies": {
"escape-string-regexp": "^1.0.5",
"ignore": "^5.0.5"
},
"devDependencies": {
"@mysticatea/eslint-plugin": "^13.0.0",
"@types/node": "^14.0.1",
"@vuepress/plugin-pwa": "^1.0.1",
"babel-eslint": "^10.0.1",
"codecov": "^3.3.0",
"cross-spawn": "^6.0.5",
"eslint": "^7.0.0",
"eslint4b": "^7.0.0",
"fs-extra": "^8.0.1",
"mocha": "^6.1.4",
"nyc": "^14.1.1",
"opener": "^1.4.3",
"rimraf": "^2.6.2",
"semver": "^7.3.2",
"string-replace-loader": "^2.1.1",
"vue-eslint-editor": "^1.1.0",
"vuepress": "^1.0.1"
},
"scripts": {
"preversion": "npm test",
"version": "node scripts/update && git add .",
"postversion": "git push && git push --tags",
"clean": "rimraf .nyc_output coverage docs/.vuepress/dist",
"docs:build": "vuepress build docs",
"docs:watch": "vuepress dev docs",
"docs:deploy": "node scripts/deploy",
"lint": "eslint lib scripts tests",
"pretest": "npm run -s lint",
"test": "nyc npm run -s test:mocha",
"test:ci": "nyc npm run -s test:mocha",
"test:mocha": "mocha \"tests/lib/**/*.js\" --reporter dot --timeout 8000",
"watch": "npm run -s test:mocha -- --watch --growl",
"coverage": "nyc report --reporter lcov && opener coverage/lcov-report/index.html",
"codecov": "nyc report --reporter text-lcov | codecov --pipe --disable=gcov"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mysticatea/eslint-plugin-eslint-comments.git"
},
"keywords": [
"eslint",
"eslintplugin",
"eslint-plugin",
"plugin",
"comment",
"comments",
"directive",
"global",
"globals",
"exported",
"eslint-env",
"eslint-enable",
"eslint-disable",
"eslint-disable-line",
"eslint-disable-next-line"
],
"author": "Toru Nagashima",
"license": "MIT",
"bugs": {
"url": "https://github.com/mysticatea/eslint-plugin-eslint-comments/issues"
},
"homepage": "https://github.com/mysticatea/eslint-plugin-eslint-comments#readme",
"funding": "https://github.com/sponsors/mysticatea"
}