patch.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /**
  2. * @author Toru Nagashima <https://github.com/mysticatea>
  3. * See LICENSE file in root directory for full license.
  4. */
  5. "use strict"
  6. const getLinters = require("../internal/get-linters")
  7. const { toRuleIdLocation } = require("../internal/utils")
  8. const quotedName = /'(.+?)'/u
  9. /**
  10. * Get the severity of a given rule.
  11. * @param {object} config The config object to check.
  12. * @param {string} ruleId The rule ID to check.
  13. * @returns {number} The severity of the rule.
  14. */
  15. function getSeverity(config, ruleId) {
  16. const rules = config && config.rules
  17. const ruleOptions = rules && rules[ruleId]
  18. const severity = Array.isArray(ruleOptions) ? ruleOptions[0] : ruleOptions
  19. switch (severity) {
  20. case 2:
  21. case "error":
  22. return 2
  23. case 1:
  24. case "warn":
  25. return 1
  26. default:
  27. return 0
  28. }
  29. }
  30. /**
  31. * Get the comment which is at a given message location.
  32. * @param {Message} message The message to get.
  33. * @param {SourceCode|undefined} sourceCode The source code object to get.
  34. * @returns {Comment|undefined} The gotten comment.
  35. */
  36. function getCommentAt(message, sourceCode) {
  37. if (sourceCode != null) {
  38. const loc = { line: message.line, column: message.column - 1 }
  39. const index = sourceCode.getIndexFromLoc(loc)
  40. const options = { includeComments: true }
  41. const comment = sourceCode.getTokenByRangeStart(index, options)
  42. if (
  43. comment != null &&
  44. (comment.type === "Line" || comment.type === "Block")
  45. ) {
  46. return comment
  47. }
  48. }
  49. return undefined
  50. }
  51. /**
  52. * Check whether a given message is a `reportUnusedDisableDirectives` error.
  53. * @param {Message} message The message.
  54. * @returns {boolean} `true` if the message is a `reportUnusedDisableDirectives` error.
  55. */
  56. function isUnusedDisableDirectiveError(message) {
  57. return (
  58. !message.fatal &&
  59. !message.ruleId &&
  60. message.message.includes("eslint-disable")
  61. )
  62. }
  63. /**
  64. * Create `eslint-comments/no-unused-disable` error.
  65. * @param {string} ruleId The ruleId.
  66. * @param {number} severity The severity of the rule.
  67. * @param {Message} message The original message.
  68. * @param {Comment|undefined} comment The directive comment.
  69. * @returns {Message} The created error.
  70. */
  71. function createNoUnusedDisableError(ruleId, severity, message, comment) {
  72. const clone = Object.assign({}, message)
  73. const match = quotedName.exec(message.message)
  74. const targetRuleId = match && match[1]
  75. clone.ruleId = ruleId
  76. clone.severity = severity
  77. clone.message = targetRuleId
  78. ? `'${targetRuleId}' rule is disabled but never reported.`
  79. : "ESLint rules are disabled but never reported."
  80. clone.suggestions = []
  81. if (comment != null) {
  82. if (targetRuleId) {
  83. const loc = toRuleIdLocation(comment, targetRuleId)
  84. clone.line = loc.start.line
  85. clone.column = loc.start.column + 1
  86. clone.endLine = loc.end.line
  87. clone.endColumn = loc.end.column + 1
  88. } else {
  89. clone.endLine = comment.loc.end.line
  90. clone.endColumn = comment.loc.end.column + 1
  91. }
  92. // Remove the whole node if it is the only rule, otherwise
  93. // don't try to fix because it is quite complicated.
  94. if (!comment.value.includes(",") && !comment.value.includes("--")) {
  95. // We can't use the typical `fixer` helper because we are injecting
  96. // this message after the fixes are resolved.
  97. clone.suggestions = [
  98. {
  99. desc: "Remove `eslint-disable` comment.",
  100. fix: {
  101. range: comment.range,
  102. text: comment.value.includes("\n") ? "\n" : "",
  103. },
  104. },
  105. ]
  106. }
  107. }
  108. return clone
  109. }
  110. /**
  111. * Convert `reportUnusedDisableDirectives` errors to `eslint-comments/no-unused-disable` errors.
  112. * @param {Message[]} messages The original messages.
  113. * @param {SourceCode|undefined} sourceCode The source code object.
  114. * @param {string} ruleId The rule ID to convert.
  115. * @param {number} severity The severity of the rule.
  116. * @param {boolean} keepAsIs The flag to keep original errors as is.
  117. * @returns {Message[]} The converted messages.
  118. */
  119. function convert(messages, sourceCode, ruleId, severity, keepAsIs) {
  120. for (let i = messages.length - 1; i >= 0; --i) {
  121. const message = messages[i]
  122. if (!isUnusedDisableDirectiveError(message)) {
  123. continue
  124. }
  125. const newMessage = createNoUnusedDisableError(
  126. ruleId,
  127. severity,
  128. message,
  129. getCommentAt(message, sourceCode)
  130. )
  131. if (keepAsIs) {
  132. messages.splice(i + 1, 0, newMessage)
  133. } else {
  134. messages.splice(i, 1, newMessage)
  135. }
  136. }
  137. return messages
  138. }
  139. module.exports = (ruleId = "eslint-comments/no-unused-disable") => {
  140. for (const Linter of getLinters()) {
  141. const verify0 = Linter.prototype._verifyWithoutProcessors
  142. Object.defineProperty(Linter.prototype, "_verifyWithoutProcessors", {
  143. value: function _verifyWithoutProcessors(
  144. textOrSourceCode,
  145. config,
  146. filenameOrOptions
  147. ) {
  148. const severity = getSeverity(config, ruleId)
  149. if (severity === 0) {
  150. return verify0.call(
  151. this,
  152. textOrSourceCode,
  153. config,
  154. filenameOrOptions
  155. )
  156. }
  157. const options =
  158. typeof filenameOrOptions === "string"
  159. ? { filename: filenameOrOptions }
  160. : filenameOrOptions || {}
  161. const reportUnusedDisableDirectives = Boolean(
  162. options.reportUnusedDisableDirectives
  163. )
  164. const messages = verify0.call(
  165. this,
  166. textOrSourceCode,
  167. config,
  168. Object.assign({}, options, {
  169. reportUnusedDisableDirectives: true,
  170. })
  171. )
  172. return convert(
  173. messages,
  174. this.getSourceCode(),
  175. ruleId,
  176. severity,
  177. reportUnusedDisableDirectives
  178. )
  179. },
  180. configurable: true,
  181. writable: true,
  182. })
  183. }
  184. }