Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

capitalized-comments.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. /**
  2. * @fileoverview enforce or disallow capitalization of the first letter of a comment
  3. * @author Kevin Partington
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const LETTER_PATTERN = require("../util/patterns/letters");
  10. const astUtils = require("../util/ast-utils");
  11. //------------------------------------------------------------------------------
  12. // Helpers
  13. //------------------------------------------------------------------------------
  14. const DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN,
  15. WHITESPACE = /\s/g,
  16. MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/, // TODO: Combine w/ max-len pattern?
  17. DEFAULTS = {
  18. ignorePattern: null,
  19. ignoreInlineComments: false,
  20. ignoreConsecutiveComments: false
  21. };
  22. /*
  23. * Base schema body for defining the basic capitalization rule, ignorePattern,
  24. * and ignoreInlineComments values.
  25. * This can be used in a few different ways in the actual schema.
  26. */
  27. const SCHEMA_BODY = {
  28. type: "object",
  29. properties: {
  30. ignorePattern: {
  31. type: "string"
  32. },
  33. ignoreInlineComments: {
  34. type: "boolean"
  35. },
  36. ignoreConsecutiveComments: {
  37. type: "boolean"
  38. }
  39. },
  40. additionalProperties: false
  41. };
  42. /**
  43. * Get normalized options for either block or line comments from the given
  44. * user-provided options.
  45. * - If the user-provided options is just a string, returns a normalized
  46. * set of options using default values for all other options.
  47. * - If the user-provided options is an object, then a normalized option
  48. * set is returned. Options specified in overrides will take priority
  49. * over options specified in the main options object, which will in
  50. * turn take priority over the rule's defaults.
  51. *
  52. * @param {Object|string} rawOptions The user-provided options.
  53. * @param {string} which Either "line" or "block".
  54. * @returns {Object} The normalized options.
  55. */
  56. function getNormalizedOptions(rawOptions, which) {
  57. if (!rawOptions) {
  58. return Object.assign({}, DEFAULTS);
  59. }
  60. return Object.assign({}, DEFAULTS, rawOptions[which] || rawOptions);
  61. }
  62. /**
  63. * Get normalized options for block and line comments.
  64. *
  65. * @param {Object|string} rawOptions The user-provided options.
  66. * @returns {Object} An object with "Line" and "Block" keys and corresponding
  67. * normalized options objects.
  68. */
  69. function getAllNormalizedOptions(rawOptions) {
  70. return {
  71. Line: getNormalizedOptions(rawOptions, "line"),
  72. Block: getNormalizedOptions(rawOptions, "block")
  73. };
  74. }
  75. /**
  76. * Creates a regular expression for each ignorePattern defined in the rule
  77. * options.
  78. *
  79. * This is done in order to avoid invoking the RegExp constructor repeatedly.
  80. *
  81. * @param {Object} normalizedOptions The normalized rule options.
  82. * @returns {void}
  83. */
  84. function createRegExpForIgnorePatterns(normalizedOptions) {
  85. Object.keys(normalizedOptions).forEach(key => {
  86. const ignorePatternStr = normalizedOptions[key].ignorePattern;
  87. if (ignorePatternStr) {
  88. const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`);
  89. normalizedOptions[key].ignorePatternRegExp = regExp;
  90. }
  91. });
  92. }
  93. //------------------------------------------------------------------------------
  94. // Rule Definition
  95. //------------------------------------------------------------------------------
  96. module.exports = {
  97. meta: {
  98. type: "suggestion",
  99. docs: {
  100. description: "enforce or disallow capitalization of the first letter of a comment",
  101. category: "Stylistic Issues",
  102. recommended: false,
  103. url: "https://eslint.org/docs/rules/capitalized-comments"
  104. },
  105. fixable: "code",
  106. schema: [
  107. { enum: ["always", "never"] },
  108. {
  109. oneOf: [
  110. SCHEMA_BODY,
  111. {
  112. type: "object",
  113. properties: {
  114. line: SCHEMA_BODY,
  115. block: SCHEMA_BODY
  116. },
  117. additionalProperties: false
  118. }
  119. ]
  120. }
  121. ],
  122. messages: {
  123. unexpectedLowercaseComment: "Comments should not begin with a lowercase character",
  124. unexpectedUppercaseComment: "Comments should not begin with an uppercase character"
  125. }
  126. },
  127. create(context) {
  128. const capitalize = context.options[0] || "always",
  129. normalizedOptions = getAllNormalizedOptions(context.options[1]),
  130. sourceCode = context.getSourceCode();
  131. createRegExpForIgnorePatterns(normalizedOptions);
  132. //----------------------------------------------------------------------
  133. // Helpers
  134. //----------------------------------------------------------------------
  135. /**
  136. * Checks whether a comment is an inline comment.
  137. *
  138. * For the purpose of this rule, a comment is inline if:
  139. * 1. The comment is preceded by a token on the same line; and
  140. * 2. The command is followed by a token on the same line.
  141. *
  142. * Note that the comment itself need not be single-line!
  143. *
  144. * Also, it follows from this definition that only block comments can
  145. * be considered as possibly inline. This is because line comments
  146. * would consume any following tokens on the same line as the comment.
  147. *
  148. * @param {ASTNode} comment The comment node to check.
  149. * @returns {boolean} True if the comment is an inline comment, false
  150. * otherwise.
  151. */
  152. function isInlineComment(comment) {
  153. const previousToken = sourceCode.getTokenBefore(comment, { includeComments: true }),
  154. nextToken = sourceCode.getTokenAfter(comment, { includeComments: true });
  155. return Boolean(
  156. previousToken &&
  157. nextToken &&
  158. comment.loc.start.line === previousToken.loc.end.line &&
  159. comment.loc.end.line === nextToken.loc.start.line
  160. );
  161. }
  162. /**
  163. * Determine if a comment follows another comment.
  164. *
  165. * @param {ASTNode} comment The comment to check.
  166. * @returns {boolean} True if the comment follows a valid comment.
  167. */
  168. function isConsecutiveComment(comment) {
  169. const previousTokenOrComment = sourceCode.getTokenBefore(comment, { includeComments: true });
  170. return Boolean(
  171. previousTokenOrComment &&
  172. ["Block", "Line"].indexOf(previousTokenOrComment.type) !== -1
  173. );
  174. }
  175. /**
  176. * Check a comment to determine if it is valid for this rule.
  177. *
  178. * @param {ASTNode} comment The comment node to process.
  179. * @param {Object} options The options for checking this comment.
  180. * @returns {boolean} True if the comment is valid, false otherwise.
  181. */
  182. function isCommentValid(comment, options) {
  183. // 1. Check for default ignore pattern.
  184. if (DEFAULT_IGNORE_PATTERN.test(comment.value)) {
  185. return true;
  186. }
  187. // 2. Check for custom ignore pattern.
  188. const commentWithoutAsterisks = comment.value
  189. .replace(/\*/g, "");
  190. if (options.ignorePatternRegExp && options.ignorePatternRegExp.test(commentWithoutAsterisks)) {
  191. return true;
  192. }
  193. // 3. Check for inline comments.
  194. if (options.ignoreInlineComments && isInlineComment(comment)) {
  195. return true;
  196. }
  197. // 4. Is this a consecutive comment (and are we tolerating those)?
  198. if (options.ignoreConsecutiveComments && isConsecutiveComment(comment)) {
  199. return true;
  200. }
  201. // 5. Does the comment start with a possible URL?
  202. if (MAYBE_URL.test(commentWithoutAsterisks)) {
  203. return true;
  204. }
  205. // 6. Is the initial word character a letter?
  206. const commentWordCharsOnly = commentWithoutAsterisks
  207. .replace(WHITESPACE, "");
  208. if (commentWordCharsOnly.length === 0) {
  209. return true;
  210. }
  211. const firstWordChar = commentWordCharsOnly[0];
  212. if (!LETTER_PATTERN.test(firstWordChar)) {
  213. return true;
  214. }
  215. // 7. Check the case of the initial word character.
  216. const isUppercase = firstWordChar !== firstWordChar.toLocaleLowerCase(),
  217. isLowercase = firstWordChar !== firstWordChar.toLocaleUpperCase();
  218. if (capitalize === "always" && isLowercase) {
  219. return false;
  220. }
  221. if (capitalize === "never" && isUppercase) {
  222. return false;
  223. }
  224. return true;
  225. }
  226. /**
  227. * Process a comment to determine if it needs to be reported.
  228. *
  229. * @param {ASTNode} comment The comment node to process.
  230. * @returns {void}
  231. */
  232. function processComment(comment) {
  233. const options = normalizedOptions[comment.type],
  234. commentValid = isCommentValid(comment, options);
  235. if (!commentValid) {
  236. const messageId = capitalize === "always"
  237. ? "unexpectedLowercaseComment"
  238. : "unexpectedUppercaseComment";
  239. context.report({
  240. node: null, // Intentionally using loc instead
  241. loc: comment.loc,
  242. messageId,
  243. fix(fixer) {
  244. const match = comment.value.match(LETTER_PATTERN);
  245. return fixer.replaceTextRange(
  246. // Offset match.index by 2 to account for the first 2 characters that start the comment (// or /*)
  247. [comment.range[0] + match.index + 2, comment.range[0] + match.index + 3],
  248. capitalize === "always" ? match[0].toLocaleUpperCase() : match[0].toLocaleLowerCase()
  249. );
  250. }
  251. });
  252. }
  253. }
  254. //----------------------------------------------------------------------
  255. // Public
  256. //----------------------------------------------------------------------
  257. return {
  258. Program() {
  259. const comments = sourceCode.getAllComments();
  260. comments.filter(token => token.type !== "Shebang").forEach(processComment);
  261. }
  262. };
  263. }
  264. };