Repository: eslint/eslint
File: lib/rules/no-console.js (JavaScript)

Code before:
        }

        /**
         * 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);
                }
            }
        };
    }

Review comment (bug):
One edge case I think we should address: removing a statement can break ASI after the previous statement. 

For example, these are three statements:

```js
foo()
console.log();
[1, 2, 3].forEach(a => doSomething(a))
```

Removing the `console.log();` statement should leave two statements, but in this case these two would become one because `[` causes continuation after `foo()` ([astexplorer.net](https://astexplorer.net/#/gist/7b462b8b4391b6da1ea6964362b97a41/237e4182700537f7fabfd0cf666d70b4d5025caf)).

To address this edge case, I think it's fine to just not provide the suggestion if token after the expression statement that's going to be removed exists and is one of tokens that can cause continuation (we could borrow [this logic](https://github.com/eslint/eslint/blob/c0b11ddb9f8aacc64c3933b9f278939aa7bea481/lib/rules/semi.js#L264-L276) from the `semi` rule) and token before exists and is neither of `:`, `;`, `{` punctuators.

Edit: mistake, `++` and `--` can be continued with `+`, `-`, or `/`.

Code after:
                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)
                    ? [{