Exemple #1
0
 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)
Exemple #2
0
def has_footer_comment(body):
    return m.matches(
        body,
        m.IndentedBlock(footer=[
            m.ZeroOrMore(),
            m.EmptyLine(comment=m.Comment()),
            m.ZeroOrMore()
        ]),
    )
Exemple #3
0
 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
Exemple #4
0
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