| # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html |
| # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE |
| # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt |
| |
| from __future__ import annotations |
| |
| from typing import TYPE_CHECKING |
| |
| from astroid import nodes |
| |
| from pylint.checkers import BaseRawFileChecker |
| |
| if TYPE_CHECKING: |
| from pylint.lint import PyLinter |
| |
| |
| def is_line_commented(line: bytes) -> bool: |
| """Checks if a `# symbol that is not part of a string was found in line.""" |
| comment_idx = line.find(b"#") |
| if comment_idx == -1: |
| return False |
| if comment_part_of_string(line, comment_idx): |
| return is_line_commented(line[:comment_idx] + line[comment_idx + 1 :]) |
| return True |
| |
| |
| def comment_part_of_string(line: bytes, comment_idx: int) -> bool: |
| """Checks if the symbol at comment_idx is part of a string.""" |
| if ( |
| line[:comment_idx].count(b"'") % 2 == 1 |
| and line[comment_idx:].count(b"'") % 2 == 1 |
| ) or ( |
| line[:comment_idx].count(b'"') % 2 == 1 |
| and line[comment_idx:].count(b'"') % 2 == 1 |
| ): |
| return True |
| return False |
| |
| |
| class CommentChecker(BaseRawFileChecker): |
| name = "empty-comment" |
| msgs = { |
| "R2044": ( |
| "Line with empty comment", |
| "empty-comment", |
| ( |
| "Used when a # symbol appears on a line not followed by an actual comment" |
| ), |
| ) |
| } |
| options = () |
| |
| def process_module(self, node: nodes.Module) -> None: |
| with node.stream() as stream: |
| for line_num, line in enumerate(stream): |
| line = line.rstrip() |
| if line.endswith(b"#"): |
| if not is_line_commented(line[:-1]): |
| self.add_message("empty-comment", line=line_num + 1) |
| |
| |
| def register(linter: PyLinter) -> None: |
| linter.register_checker(CommentChecker(linter)) |