/**
							 | 
						|
								 * @fileoverview enforce or disallow capitalization of the first letter of a comment
							 | 
						|
								 * @author Kevin Partington
							 | 
						|
								 */
							 | 
						|
								"use strict";
							 | 
						|
								
							 | 
						|
								//------------------------------------------------------------------------------
							 | 
						|
								// Requirements
							 | 
						|
								//------------------------------------------------------------------------------
							 | 
						|
								
							 | 
						|
								const LETTER_PATTERN = require("./utils/patterns/letters");
							 | 
						|
								const astUtils = require("./utils/ast-utils");
							 | 
						|
								
							 | 
						|
								//------------------------------------------------------------------------------
							 | 
						|
								// Helpers
							 | 
						|
								//------------------------------------------------------------------------------
							 | 
						|
								
							 | 
						|
								const DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN,
							 | 
						|
								    WHITESPACE = /\s/gu,
							 | 
						|
								    MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/u; // TODO: Combine w/ max-len pattern?
							 | 
						|
								
							 | 
						|
								/*
							 | 
						|
								 * Base schema body for defining the basic capitalization rule, ignorePattern,
							 | 
						|
								 * and ignoreInlineComments values.
							 | 
						|
								 * This can be used in a few different ways in the actual schema.
							 | 
						|
								 */
							 | 
						|
								const SCHEMA_BODY = {
							 | 
						|
								    type: "object",
							 | 
						|
								    properties: {
							 | 
						|
								        ignorePattern: {
							 | 
						|
								            type: "string"
							 | 
						|
								        },
							 | 
						|
								        ignoreInlineComments: {
							 | 
						|
								            type: "boolean"
							 | 
						|
								        },
							 | 
						|
								        ignoreConsecutiveComments: {
							 | 
						|
								            type: "boolean"
							 | 
						|
								        }
							 | 
						|
								    },
							 | 
						|
								    additionalProperties: false
							 | 
						|
								};
							 | 
						|
								const DEFAULTS = {
							 | 
						|
								    ignorePattern: "",
							 | 
						|
								    ignoreInlineComments: false,
							 | 
						|
								    ignoreConsecutiveComments: false
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Get normalized options for either block or line comments from the given
							 | 
						|
								 * user-provided options.
							 | 
						|
								 * - If the user-provided options is just a string, returns a normalized
							 | 
						|
								 *   set of options using default values for all other options.
							 | 
						|
								 * - If the user-provided options is an object, then a normalized option
							 | 
						|
								 *   set is returned. Options specified in overrides will take priority
							 | 
						|
								 *   over options specified in the main options object, which will in
							 | 
						|
								 *   turn take priority over the rule's defaults.
							 | 
						|
								 * @param {Object|string} rawOptions The user-provided options.
							 | 
						|
								 * @param {string} which Either "line" or "block".
							 | 
						|
								 * @returns {Object} The normalized options.
							 | 
						|
								 */
							 | 
						|
								function getNormalizedOptions(rawOptions, which) {
							 | 
						|
								    return Object.assign({}, DEFAULTS, rawOptions[which] || rawOptions);
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Get normalized options for block and line comments.
							 | 
						|
								 * @param {Object|string} rawOptions The user-provided options.
							 | 
						|
								 * @returns {Object} An object with "Line" and "Block" keys and corresponding
							 | 
						|
								 * normalized options objects.
							 | 
						|
								 */
							 | 
						|
								function getAllNormalizedOptions(rawOptions = {}) {
							 | 
						|
								    return {
							 | 
						|
								        Line: getNormalizedOptions(rawOptions, "line"),
							 | 
						|
								        Block: getNormalizedOptions(rawOptions, "block")
							 | 
						|
								    };
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Creates a regular expression for each ignorePattern defined in the rule
							 | 
						|
								 * options.
							 | 
						|
								 *
							 | 
						|
								 * This is done in order to avoid invoking the RegExp constructor repeatedly.
							 | 
						|
								 * @param {Object} normalizedOptions The normalized rule options.
							 | 
						|
								 * @returns {void}
							 | 
						|
								 */
							 | 
						|
								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;
							 | 
						|
								        }
							 | 
						|
								    });
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								//------------------------------------------------------------------------------
							 | 
						|
								// Rule Definition
							 | 
						|
								//------------------------------------------------------------------------------
							 | 
						|
								
							 | 
						|
								/** @type {import('../shared/types').Rule} */
							 | 
						|
								module.exports = {
							 | 
						|
								    meta: {
							 | 
						|
								        type: "suggestion",
							 | 
						|
								
							 | 
						|
								        docs: {
							 | 
						|
								            description: "Enforce or disallow capitalization of the first letter of a comment",
							 | 
						|
								            recommended: false,
							 | 
						|
								            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);
							 | 
						|
								
							 | 
						|
								        //----------------------------------------------------------------------
							 | 
						|
								        // Helpers
							 | 
						|
								        //----------------------------------------------------------------------
							 | 
						|
								
							 | 
						|
								        /**
							 | 
						|
								         * Checks whether a comment is an inline comment.
							 | 
						|
								         *
							 | 
						|
								         * For the purpose of this rule, a comment is inline if:
							 | 
						|
								         * 1. The comment is preceded by a token on the same line; and
							 | 
						|
								         * 2. The command is followed by a token on the same line.
							 | 
						|
								         *
							 | 
						|
								         * Note that the comment itself need not be single-line!
							 | 
						|
								         *
							 | 
						|
								         * Also, it follows from this definition that only block comments can
							 | 
						|
								         * be considered as possibly inline. This is because line comments
							 | 
						|
								         * would consume any following tokens on the same line as the comment.
							 | 
						|
								         * @param {ASTNode} comment The comment node to check.
							 | 
						|
								         * @returns {boolean} True if the comment is an inline comment, false
							 | 
						|
								         * otherwise.
							 | 
						|
								         */
							 | 
						|
								        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
							 | 
						|
								            );
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        /**
							 | 
						|
								         * Determine if a comment follows another comment.
							 | 
						|
								         * @param {ASTNode} comment The comment to check.
							 | 
						|
								         * @returns {boolean} True if the comment follows a valid comment.
							 | 
						|
								         */
							 | 
						|
								        function isConsecutiveComment(comment) {
							 | 
						|
								            const previousTokenOrComment = sourceCode.getTokenBefore(comment, { includeComments: true });
							 | 
						|
								
							 | 
						|
								            return Boolean(
							 | 
						|
								                previousTokenOrComment &&
							 | 
						|
								                ["Block", "Line"].includes(previousTokenOrComment.type)
							 | 
						|
								            );
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        /**
							 | 
						|
								         * Check a comment to determine if it is valid for this rule.
							 | 
						|
								         * @param {ASTNode} comment The comment node to process.
							 | 
						|
								         * @param {Object} options The options for checking this comment.
							 | 
						|
								         * @returns {boolean} True if the comment is valid, false otherwise.
							 | 
						|
								         */
							 | 
						|
								        function isCommentValid(comment, options) {
							 | 
						|
								
							 | 
						|
								            // 1. Check for default ignore pattern.
							 | 
						|
								            if (DEFAULT_IGNORE_PATTERN.test(comment.value)) {
							 | 
						|
								                return true;
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            // 2. Check for custom ignore pattern.
							 | 
						|
								            const commentWithoutAsterisks = comment.value
							 | 
						|
								                .replace(/\*/gu, "");
							 | 
						|
								
							 | 
						|
								            if (options.ignorePatternRegExp && options.ignorePatternRegExp.test(commentWithoutAsterisks)) {
							 | 
						|
								                return true;
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            // 3. Check for inline comments.
							 | 
						|
								            if (options.ignoreInlineComments && isInlineComment(comment)) {
							 | 
						|
								                return true;
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            // 4. Is this a consecutive comment (and are we tolerating those)?
							 | 
						|
								            if (options.ignoreConsecutiveComments && isConsecutiveComment(comment)) {
							 | 
						|
								                return true;
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            // 5. Does the comment start with a possible URL?
							 | 
						|
								            if (MAYBE_URL.test(commentWithoutAsterisks)) {
							 | 
						|
								                return true;
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            // 6. Is the initial word character a letter?
							 | 
						|
								            const commentWordCharsOnly = commentWithoutAsterisks
							 | 
						|
								                .replace(WHITESPACE, "");
							 | 
						|
								
							 | 
						|
								            if (commentWordCharsOnly.length === 0) {
							 | 
						|
								                return true;
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            const firstWordChar = commentWordCharsOnly[0];
							 | 
						|
								
							 | 
						|
								            if (!LETTER_PATTERN.test(firstWordChar)) {
							 | 
						|
								                return true;
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            // 7. Check the case of the initial word character.
							 | 
						|
								            const isUppercase = firstWordChar !== firstWordChar.toLocaleLowerCase(),
							 | 
						|
								                isLowercase = firstWordChar !== firstWordChar.toLocaleUpperCase();
							 | 
						|
								
							 | 
						|
								            if (capitalize === "always" && isLowercase) {
							 | 
						|
								                return false;
							 | 
						|
								            }
							 | 
						|
								            if (capitalize === "never" && isUppercase) {
							 | 
						|
								                return false;
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            return true;
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        /**
							 | 
						|
								         * Process a comment to determine if it needs to be reported.
							 | 
						|
								         * @param {ASTNode} comment The comment node to process.
							 | 
						|
								         * @returns {void}
							 | 
						|
								         */
							 | 
						|
								        function processComment(comment) {
							 | 
						|
								            const options = normalizedOptions[comment.type],
							 | 
						|
								                commentValid = isCommentValid(comment, options);
							 | 
						|
								
							 | 
						|
								            if (!commentValid) {
							 | 
						|
								                const messageId = capitalize === "always"
							 | 
						|
								                    ? "unexpectedLowercaseComment"
							 | 
						|
								                    : "unexpectedUppercaseComment";
							 | 
						|
								
							 | 
						|
								                context.report({
							 | 
						|
								                    node: null, // Intentionally using loc instead
							 | 
						|
								                    loc: comment.loc,
							 | 
						|
								                    messageId,
							 | 
						|
								                    fix(fixer) {
							 | 
						|
								                        const match = comment.value.match(LETTER_PATTERN);
							 | 
						|
								
							 | 
						|
								                        return fixer.replaceTextRange(
							 | 
						|
								
							 | 
						|
								                            // Offset match.index by 2 to account for the first 2 characters that start the comment (// or /*)
							 | 
						|
								                            [comment.range[0] + match.index + 2, comment.range[0] + match.index + 3],
							 | 
						|
								                            capitalize === "always" ? match[0].toLocaleUpperCase() : match[0].toLocaleLowerCase()
							 | 
						|
								                        );
							 | 
						|
								                    }
							 | 
						|
								                });
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        //----------------------------------------------------------------------
							 | 
						|
								        // Public
							 | 
						|
								        //----------------------------------------------------------------------
							 | 
						|
								
							 | 
						|
								        return {
							 | 
						|
								            Program() {
							 | 
						|
								                const comments = sourceCode.getAllComments();
							 | 
						|
								
							 | 
						|
								                comments.filter(token => token.type !== "Shebang").forEach(processComment);
							 | 
						|
								            }
							 | 
						|
								        };
							 | 
						|
								    }
							 | 
						|
								};
							 |