| |
| |
| |
| |
| "use strict"; |
|
|
| |
| |
| |
|
|
| const astUtils = require("./utils/ast-utils"); |
|
|
| |
| |
| |
|
|
| const DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN, |
| WHITESPACE = /\s/gu, |
| MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/u, |
| LETTER_PATTERN = /\p{L}/u; |
|
|
| |
| |
| |
| |
| |
| const SCHEMA_BODY = { |
| type: "object", |
| properties: { |
| ignorePattern: { |
| type: "string", |
| }, |
| ignoreInlineComments: { |
| type: "boolean", |
| }, |
| ignoreConsecutiveComments: { |
| type: "boolean", |
| }, |
| }, |
| additionalProperties: false, |
| }; |
| const DEFAULTS = { |
| ignorePattern: "", |
| ignoreInlineComments: false, |
| ignoreConsecutiveComments: false, |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function getNormalizedOptions(rawOptions, which) { |
| return Object.assign({}, DEFAULTS, rawOptions[which] || rawOptions); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function getAllNormalizedOptions(rawOptions = {}) { |
| return { |
| Line: getNormalizedOptions(rawOptions, "line"), |
| Block: getNormalizedOptions(rawOptions, "block"), |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| function createRegExpForIgnorePatterns(normalizedOptions) { |
| Object.keys(normalizedOptions).forEach(key => { |
| const ignorePatternStr = normalizedOptions[key].ignorePattern; |
|
|
| if (ignorePatternStr) { |
| const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`, "u"); |
|
|
| normalizedOptions[key].ignorePatternRegExp = regExp; |
| } |
| }); |
| } |
|
|
| |
| |
| |
|
|
| |
| module.exports = { |
| meta: { |
| type: "suggestion", |
|
|
| docs: { |
| description: |
| "Enforce or disallow capitalization of the first letter of a comment", |
| recommended: false, |
| frozen: true, |
| url: "https://eslint.org/docs/latest/rules/capitalized-comments", |
| }, |
|
|
| fixable: "code", |
|
|
| schema: [ |
| { enum: ["always", "never"] }, |
| { |
| oneOf: [ |
| SCHEMA_BODY, |
| { |
| type: "object", |
| properties: { |
| line: SCHEMA_BODY, |
| block: SCHEMA_BODY, |
| }, |
| additionalProperties: false, |
| }, |
| ], |
| }, |
| ], |
|
|
| messages: { |
| unexpectedLowercaseComment: |
| "Comments should not begin with a lowercase character.", |
| unexpectedUppercaseComment: |
| "Comments should not begin with an uppercase character.", |
| }, |
| }, |
|
|
| create(context) { |
| const capitalize = context.options[0] || "always", |
| normalizedOptions = getAllNormalizedOptions(context.options[1]), |
| sourceCode = context.sourceCode; |
|
|
| createRegExpForIgnorePatterns(normalizedOptions); |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function isInlineComment(comment) { |
| const previousToken = sourceCode.getTokenBefore(comment, { |
| includeComments: true, |
| }), |
| nextToken = sourceCode.getTokenAfter(comment, { |
| includeComments: true, |
| }); |
|
|
| return Boolean( |
| previousToken && |
| nextToken && |
| comment.loc.start.line === previousToken.loc.end.line && |
| comment.loc.end.line === nextToken.loc.start.line, |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| function isConsecutiveComment(comment) { |
| const previousTokenOrComment = sourceCode.getTokenBefore(comment, { |
| includeComments: true, |
| }); |
|
|
| return Boolean( |
| previousTokenOrComment && |
| ["Block", "Line"].includes(previousTokenOrComment.type), |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function isCommentValid(comment, options) { |
| |
| if (DEFAULT_IGNORE_PATTERN.test(comment.value)) { |
| return true; |
| } |
|
|
| |
| const commentWithoutAsterisks = comment.value.replace(/\*/gu, ""); |
|
|
| if ( |
| options.ignorePatternRegExp && |
| options.ignorePatternRegExp.test(commentWithoutAsterisks) |
| ) { |
| return true; |
| } |
|
|
| |
| if (options.ignoreInlineComments && isInlineComment(comment)) { |
| return true; |
| } |
|
|
| |
| if ( |
| options.ignoreConsecutiveComments && |
| isConsecutiveComment(comment) |
| ) { |
| return true; |
| } |
|
|
| |
| if (MAYBE_URL.test(commentWithoutAsterisks)) { |
| return true; |
| } |
|
|
| |
| const commentWordCharsOnly = commentWithoutAsterisks.replace( |
| WHITESPACE, |
| "", |
| ); |
|
|
| if (commentWordCharsOnly.length === 0) { |
| return true; |
| } |
|
|
| |
| const [firstWordChar] = commentWordCharsOnly; |
|
|
| if (!LETTER_PATTERN.test(firstWordChar)) { |
| return true; |
| } |
|
|
| |
| const isUppercase = |
| firstWordChar !== firstWordChar.toLocaleLowerCase(), |
| isLowercase = |
| firstWordChar !== firstWordChar.toLocaleUpperCase(); |
|
|
| if (capitalize === "always" && isLowercase) { |
| return false; |
| } |
| if (capitalize === "never" && isUppercase) { |
| return false; |
| } |
|
|
| return true; |
| } |
|
|
| |
| |
| |
| |
| |
| function processComment(comment) { |
| const options = normalizedOptions[comment.type], |
| commentValid = isCommentValid(comment, options); |
|
|
| if (!commentValid) { |
| const messageId = |
| capitalize === "always" |
| ? "unexpectedLowercaseComment" |
| : "unexpectedUppercaseComment"; |
|
|
| context.report({ |
| node: null, |
| loc: comment.loc, |
| messageId, |
| fix(fixer) { |
| const match = comment.value.match(LETTER_PATTERN); |
| const char = match[0]; |
|
|
| |
| const charIndex = comment.range[0] + match.index + 2; |
|
|
| return fixer.replaceTextRange( |
| [charIndex, charIndex + char.length], |
| capitalize === "always" |
| ? char.toLocaleUpperCase() |
| : char.toLocaleLowerCase(), |
| ); |
| }, |
| }); |
| } |
| } |
|
|
| |
| |
| |
|
|
| return { |
| Program() { |
| const comments = sourceCode.getAllComments(); |
|
|
| comments |
| .filter(token => token.type !== "Shebang") |
| .forEach(processComment); |
| }, |
| }; |
| }, |
| }; |
|
|