def __init__(self, context: CstContext) -> None: super().__init__(context) config = self.context.config.rule_config.get(self.__class__.__name__, None) if config is None: self.rule_disabled: bool = True else: if not isinstance(config, dict) or "header" not in config: raise ValueError( "A ``header`` str config is required by AddMissingHeaderRule." ) header_str = config["header"] if not isinstance(header_str, str): raise ValueError( "A ``header`` str config is required by AddMissingHeaderRule." ) lines = header_str.split("\n") self.header_matcher: List[m.EmptyLine] = [ m.EmptyLine(comment=m.Comment(value=line)) for line in lines ] self.header_replacement: List[cst.EmptyLine] = [ cst.EmptyLine(comment=cst.Comment(value=line)) for line in lines ] if "path" in config: path_pattern = config["path"] if not isinstance(path_pattern, str): raise ValueError( "``path`` config should be a str in AddMissingHeaderRule." ) else: path_pattern = "*.py" self.rule_disabled = not self.context.file_path.match(path_pattern)
def has_footer_comment(body): return m.matches( body, m.IndentedBlock(footer=[ m.ZeroOrMore(), m.EmptyLine(comment=m.Comment()), m.ZeroOrMore() ]), )
def leave_EmptyLine( self, original_node: libcst.EmptyLine, updated_node: libcst.EmptyLine ) -> Union[libcst.EmptyLine, libcst.RemovalSentinel]: # First, find misplaced lines. for tag in self.PYRE_TAGS: if m.matches(updated_node, m.EmptyLine(comment=m.Comment(f"# pyre-{tag}"))): if self.in_module_header: # We only want to remove this if we've already found another # pyre-strict in the header (that means its duplicated). We # also don't want to move the pyre-strict since its already in # the header, so don't mark that we need to move. self.module_header_tags[tag] += 1 if self.module_header_tags[tag] > 1: return libcst.RemoveFromParent() else: return updated_node else: # This showed up outside the module header, so move it inside if self.module_header_tags[tag] < 1: self.move_strict[tag] = True return libcst.RemoveFromParent() # Now, find misnamed lines if m.matches(updated_node, m.EmptyLine(comment=m.Comment(f"# pyre {tag}"))): if self.in_module_header: # We only want to remove this if we've already found another # pyre-strict in the header (that means its duplicated). We # also don't want to move the pyre-strict since its already in # the header, so don't mark that we need to move. self.module_header_tags[tag] += 1 if self.module_header_tags[tag] > 1: return libcst.RemoveFromParent() else: return updated_node.with_changes( comment=libcst.Comment(f"# pyre-{tag}")) else: # We found an intended pyre-strict, but its spelled wrong. So, remove it # and re-add a new one in leave_Module. if self.module_header_tags[tag] < 1: self.move_strict[tag] = True return libcst.RemoveFromParent() # We found a regular comment, don't care about this. return updated_node
class GatherCommentsVisitor(ContextAwareVisitor): """ Collects all comments matching a certain regex and their line numbers. This visitor is useful for capturing special-purpose comments, for example ``noqa`` style lint suppression annotations. Standalone comments are assumed to affect the line following them, and inline ones are recorded with the line they are on. After visiting a CST, matching comments are collected in the ``comments`` attribute. """ METADATA_DEPENDENCIES = (PositionProvider, ) def __init__(self, context: CodemodContext, comment_regex: str) -> None: super().__init__(context) #: Dictionary of comments found in the CST. Keys are line numbers, #: values are comment nodes. self.comments: Dict[int, cst.Comment] = {} self._comment_matcher: Pattern[str] = re.compile(comment_regex) @m.visit(m.EmptyLine(comment=m.DoesNotMatch(None))) @m.visit(m.TrailingWhitespace(comment=m.DoesNotMatch(None))) def visit_comment( self, node: Union[cst.EmptyLine, cst.TrailingWhitespace]) -> None: comment = node.comment assert comment is not None # hello, type checker if not self._comment_matcher.match(comment.value): return line = self.get_metadata(PositionProvider, comment).start.line if isinstance(node, cst.EmptyLine): # Standalone comments refer to the next line line += 1 self.comments[line] = comment