utils.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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 escapeStringRegexp = require("escape-string-regexp")
  7. const LINE_PATTERN = /[^\r\n\u2028\u2029]*(?:\r\n|[\r\n\u2028\u2029]|$)/gu
  8. const DIRECTIVE_PATTERN = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u
  9. const LINE_COMMENT_PATTERN = /^eslint-disable-(next-)?line$/u
  10. module.exports = {
  11. /**
  12. * Make the location ignoring `eslint-disable` comments.
  13. *
  14. * @param {object} location - The location to convert.
  15. * @returns {object} Converted location.
  16. */
  17. toForceLocation(location) {
  18. return {
  19. start: {
  20. line: location.start.line,
  21. column: -1,
  22. },
  23. end: location.end,
  24. }
  25. },
  26. /**
  27. * Calculate the location of the given rule in the given comment token.
  28. *
  29. * @param {Token} comment - The comment token to calculate.
  30. * @param {string|null} ruleId - The rule name to calculate.
  31. * @returns {object} The location of the given information.
  32. */
  33. toRuleIdLocation(comment, ruleId) {
  34. if (ruleId == null) {
  35. return module.exports.toForceLocation(comment.loc)
  36. }
  37. const lines = comment.value.match(LINE_PATTERN)
  38. //eslint-disable-next-line require-unicode-regexp
  39. const ruleIdPattern = new RegExp(
  40. `([\\s,]|^)${escapeStringRegexp(ruleId)}(?:[\\s,]|$)`
  41. )
  42. {
  43. const m = ruleIdPattern.exec(lines[0])
  44. if (m != null) {
  45. const start = comment.loc.start
  46. return {
  47. start: {
  48. line: start.line,
  49. column: 2 + start.column + m.index + m[1].length,
  50. },
  51. end: {
  52. line: start.line,
  53. column:
  54. 2 +
  55. start.column +
  56. m.index +
  57. m[1].length +
  58. ruleId.length,
  59. },
  60. }
  61. }
  62. }
  63. for (let i = 1; i < lines.length; ++i) {
  64. const m = ruleIdPattern.exec(lines[i])
  65. if (m != null) {
  66. const start = comment.loc.start
  67. return {
  68. start: {
  69. line: start.line + i,
  70. column: m.index + m[1].length,
  71. },
  72. end: {
  73. line: start.line + i,
  74. column: m.index + m[1].length + ruleId.length,
  75. },
  76. }
  77. }
  78. }
  79. /*istanbul ignore next : foolproof */
  80. return comment.loc
  81. },
  82. /**
  83. * Checks `a` is less than `b` or `a` equals `b`.
  84. *
  85. * @param {{line: number, column: number}} a - A location to compare.
  86. * @param {{line: number, column: number}} b - Another location to compare.
  87. * @returns {boolean} `true` if `a` is less than `b` or `a` equals `b`.
  88. */
  89. lte(a, b) {
  90. return a.line < b.line || (a.line === b.line && a.column <= b.column)
  91. },
  92. /**
  93. * Parse the given comment token as a directive comment.
  94. *
  95. * @param {Token} comment - The comment token to parse.
  96. * @returns {{kind: string, value: string, description: string | null}|null} The parsed data of the given comment. If `null`, it is not a directive comment.
  97. */
  98. parseDirectiveComment(comment) {
  99. const { text, description } = divideDirectiveComment(comment.value)
  100. const match = DIRECTIVE_PATTERN.exec(text)
  101. if (!match) {
  102. return null
  103. }
  104. const directiveText = match[1]
  105. const lineCommentSupported = LINE_COMMENT_PATTERN.test(directiveText)
  106. if (comment.type === "Line" && !lineCommentSupported) {
  107. return null
  108. }
  109. if (
  110. lineCommentSupported &&
  111. comment.loc.start.line !== comment.loc.end.line
  112. ) {
  113. // disable-line comment should not span multiple lines.
  114. return null
  115. }
  116. const directiveValue = text.slice(match.index + directiveText.length)
  117. return {
  118. kind: directiveText,
  119. value: directiveValue.trim(),
  120. description,
  121. }
  122. },
  123. }
  124. /**
  125. * Divides and trims description text and directive comments.
  126. * @param {string} value The comment text to strip.
  127. * @returns {{text: string, description: string | null}} The stripped text.
  128. */
  129. function divideDirectiveComment(value) {
  130. const divided = value.split(/\s-{2,}\s/u)
  131. const text = divided[0].trim()
  132. return {
  133. text,
  134. description: divided.length > 1 ? divided[1].trim() : null,
  135. }
  136. }