|                                                                                                                                                                                                               |  | /** * @fileoverview Rule to flag use of console object * @author Nicholas C. Zakas */
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "suggestion",
        docs: {            description: "Disallow the use of `console`",            recommended: false,            url: "https://eslint.org/docs/latest/rules/no-console"        },
        schema: [            {                type: "object",                properties: {                    allow: {                        type: "array",                        items: {                            type: "string"                        },                        minItems: 1,                        uniqueItems: true                    }                },                additionalProperties: false            }        ],
        hasSuggestions: true,
        messages: {            unexpected: "Unexpected console statement.",            removeConsole: "Remove the console.{{ propertyName }}()."        }    },
    create(context) {        const options = context.options[0] || {};        const allowed = options.allow || [];        const sourceCode = context.sourceCode;
        /**         * Checks whether the given reference is 'console' or not.         * @param {eslint-scope.Reference} reference The reference to check.         * @returns {boolean} `true` if the reference is 'console'.         */        function isConsole(reference) {            const id = reference.identifier;
            return id && id.name === "console";        }
        /**         * Checks whether the property name of the given MemberExpression node         * is allowed by options or not.         * @param {ASTNode} node The MemberExpression node to check.         * @returns {boolean} `true` if the property name of the node is allowed.         */        function isAllowed(node) {            const propertyName = astUtils.getStaticPropertyName(node);
            return propertyName && allowed.includes(propertyName);        }
        /**         * Checks whether the given reference is a member access which is not         * allowed by options or not.         * @param {eslint-scope.Reference} reference The reference to check.         * @returns {boolean} `true` if the reference is a member access which         *      is not allowed by options.         */        function isMemberAccessExceptAllowed(reference) {            const node = reference.identifier;            const parent = node.parent;
            return (                parent.type === "MemberExpression" &&                parent.object === node &&                !isAllowed(parent)            );        }
        /**         * Checks if removing the ExpressionStatement node will cause ASI to         * break.         * eg.         * foo()         * console.log();         * [1, 2, 3].forEach(a => doSomething(a))         *         * Removing the console.log(); statement should leave two statements, but         * here the two statements will become one because [ causes continuation after         * foo().         * @param {ASTNode} node The ExpressionStatement node to check.         * @returns {boolean} `true` if ASI will break after removing the ExpressionStatement         *      node.         */        function maybeAsiHazard(node) {            const SAFE_TOKENS_BEFORE = /^[:;{]$/u; // One of :;{
            const UNSAFE_CHARS_AFTER = /^[-[(/+`]/u; // One of [(/+-`
            const tokenBefore = sourceCode.getTokenBefore(node);            const tokenAfter = sourceCode.getTokenAfter(node);
            return (                Boolean(tokenAfter) &&                UNSAFE_CHARS_AFTER.test(tokenAfter.value) &&                tokenAfter.value !== "++" &&                tokenAfter.value !== "--" &&                Boolean(tokenBefore) &&                !SAFE_TOKENS_BEFORE.test(tokenBefore.value)            );        }
        /**         * Checks if the MemberExpression node's parent.parent.parent is a         * Program, BlockStatement, StaticBlock, or SwitchCase node. This check         * is necessary to avoid providing a suggestion that might cause a syntax error.         *         * eg. if (a) console.log(b), removing console.log() here will lead to a         *     syntax error.         *     if (a) { console.log(b) }, removing console.log() here is acceptable.         *         * Additionally, it checks if the callee of the CallExpression node is         * the node itself.         *         * eg. foo(console.log), cannot provide a suggestion here.         * @param {ASTNode} node The MemberExpression node to check.         * @returns {boolean} `true` if a suggestion can be provided for a node.         */        function canProvideSuggestions(node) {            return (                node.parent.type === "CallExpression" &&                node.parent.callee === node &&                node.parent.parent.type === "ExpressionStatement" &&                astUtils.STATEMENT_LIST_PARENTS.has(node.parent.parent.parent.type) &&                !maybeAsiHazard(node.parent.parent)            );        }
        /**         * Reports the given reference as a violation.         * @param {eslint-scope.Reference} reference The reference to report.         * @returns {void}         */        function report(reference) {            const node = reference.identifier.parent;
            const propertyName = astUtils.getStaticPropertyName(node);
            context.report({                node,                loc: node.loc,                messageId: "unexpected",                suggest: canProvideSuggestions(node)                    ? [{                        messageId: "removeConsole",                        data: { propertyName },                        fix(fixer) {                            return fixer.remove(node.parent.parent);                        }                    }]                    : []            });        }
        return {            "Program:exit"(node) {                const scope = sourceCode.getScope(node);                const consoleVar = astUtils.getVariableByName(scope, "console");                const shadowed = consoleVar && consoleVar.defs.length > 0;
                /*                 * 'scope.through' includes all references to undefined                 * variables. If the variable 'console' is not defined, it uses                 * 'scope.through'.                 */                const references = consoleVar                    ? consoleVar.references                    : scope.through.filter(isConsole);
                if (!shadowed) {                    references                        .filter(isMemberAccessExceptAllowed)                        .forEach(report);                }            }        };    }};
 |