Пример #1
0
 def visit_FunctionDef(self, node: cst.FunctionDef) -> None:
     position = self.get_metadata(PositionProvider, node)
     self.context.scratch[node.name.value] = (
         position.start.line,
         position.start.column,
     )
     node.visit(TestingCollector(self.context))
Пример #2
0
    def visit_FunctionDef(self, node: cst.FunctionDef) -> None:
        if not any(
            QualifiedNameProvider.has_name(
                self,
                decorator.decorator,
                QualifiedName(name="builtins.classmethod", source=QualifiedNameSource.BUILTIN),
            )
            for decorator in node.decorators
        ):
            return  # If it's not a @classmethod, we are not interested.
        if not node.params.params:
            # No params, but there must be the 'cls' param.
            # Note that pyre[47] already catches this, but we also generate
            # an autofix, so it still makes sense for us to report it here.
            new_params = node.params.with_changes(params=(cst.Param(name=cst.Name(value=CLS)),))
            repl = node.with_changes(params=new_params)
            self.report(node, replacement=repl)
            return

        p0_name = node.params.params[0].name
        if p0_name.value == CLS:
            return  # All good.

        # Rename all assignments and references of the first param within the
        # function scope, as long as they are done via a Name node.
        # We rely on the parser to correctly derive all
        # assigments and references within the FunctionScope.
        # The Param node's scope is our classmethod's FunctionScope.
        scope = self.get_metadata(ScopeProvider, p0_name, None)
        if not scope:
            # Cannot autofix without scope metadata. Only report in this case.
            # Not sure how to repro+cover this in a unit test...
            # If metadata creation fails, then the whole lint fails, and if it succeeds,
            # then there is valid metadata. But many other lint rule implementations contain
            # a defensive scope None check like this one, so I assume it is necessary.
            self.report(node)
            return

        if scope[CLS]:
            # The scope already has another assignment to "cls".
            # Trying to rename the first param to "cls" as well may produce broken code.
            # We should therefore refrain from suggesting an autofix in this case.
            self.report(node)
            return

        refs: List[Union[cst.Name, cst.Attribute]] = []
        assignments = scope[p0_name.value]
        for a in assignments:
            if isinstance(a, Assignment):
                assign_node = a.node
                if isinstance(assign_node, cst.Name):
                    refs.append(assign_node)
                elif isinstance(assign_node, cst.Param):
                    refs.append(assign_node.name)
                # There are other types of possible assignment nodes: ClassDef,
                # FunctionDef, Import, etc. We deliberately do not handle those here.
            refs += [r.node for r in a.references]

        repl = node.visit(_RenameTransformer(refs, CLS))
        self.report(node, replacement=repl)
Пример #3
0
    def leave_FunctionDef(self, original_node: cst.FunctionDef,
                          updated_node: cst.FunctionDef) -> cst.FunctionDef:
        docstring = None
        docstring_node = get_docstring_node(updated_node.body)
        if docstring_node:
            if isinstance(docstring_node.value,
                          (cst.SimpleString, cst.ConcatenatedString)):
                docstring = docstring_node.value.evaluated_value
        if not docstring:
            return updated_node
        new_docstring, types = gather_types(docstring)
        if types.get(RETURN):
            updated_node = updated_node.with_changes(returns=cst.Annotation(
                cst.Name(types.pop(RETURN))), )

        if types:

            def get_annotation(p: cst.Param) -> Optional[cst.Annotation]:
                pname = p.name.value
                if types.get(pname):
                    return cst.Annotation(cst.parse_expression(types[pname]))
                return None

            updated_node = updated_node.with_changes(params=update_parameters(
                updated_node.params, get_annotation, False))

        new_docstring_node = cst.SimpleString('"""%s"""' % new_docstring)
        return updated_node.deep_replace(docstring_node,
                                         cst.Expr(new_docstring_node))
Пример #4
0
 def leave_FunctionDef(self, node: cst.FunctionDef,
                       updated_node: cst.FunctionDef) -> cst.CSTNode:
     key = self._qualifier_name()
     self.qualifier.pop()
     if key in self.function_annotations:
         annotations = self.function_annotations[key]
         # Only add new annotation if one doesn't already exist
         if not updated_node.returns:
             updated_node = updated_node.with_changes(
                 returns=annotations.returns)
         return updated_node.with_changes(params=annotations.parameters)
     return updated_node
Пример #5
0
 def leave_FunctionDef(self, node: cst.FunctionDef,
                       updated_node: cst.FunctionDef) -> cst.FunctionDef:
     returns = self.stack.pop()
     if returns is None:
         return updated_node
     if not returns:
         return updated_node.with_changes(returns=cst.Annotation(
             annotation=cst.Name(value="None")))
     last_line = node.body.body[-1]
     if not isinstance(last_line, cst.SimpleStatementLine):
         if returns and all(r.value is None or isinstance(
                 r.value, cst.Name) and r.value.value == 'None'
                            for r in returns):
             return updated_node.with_changes(returns=cst.Annotation(
                 annotation=cst.Name(value="None")))
         return updated_node
     elif not isinstance(last_line.body[-1], cst.Return):
         if returns and all(r.value is None or isinstance(
                 r.value, cst.Name) and r.value.value == 'None'
                            for r in returns):
             return updated_node.with_changes(returns=cst.Annotation(
                 annotation=cst.Name(value="None")))
         return updated_node
     if len(returns) == 1:
         rvalue = returns[0].value
         if isinstance(rvalue, cst.BaseString):
             if isinstance(
                     rvalue,
                     cst.SimpleString) and rvalue.value.startswith("b"):
                 return updated_node.with_changes(returns=cst.Annotation(
                     annotation=cst.Name(value="bytes")))
             return updated_node.with_changes(returns=cst.Annotation(
                 annotation=cst.Name(value="str")))
         if isinstance(rvalue, cst.Name):
             if rvalue.value in ("False", "True"):
                 return updated_node.with_changes(returns=cst.Annotation(
                     annotation=cst.Name(value="bool")))
             if rvalue.value == "None":
                 return updated_node.with_changes(returns=cst.Annotation(
                     annotation=cst.Name(value="None")))
         if isinstance(rvalue, cst.Integer):
             return updated_node.with_changes(returns=cst.Annotation(
                 annotation=cst.Name(value="int")))
         if isinstance(rvalue, cst.Float):
             return updated_node.with_changes(returns=cst.Annotation(
                 annotation=cst.Name(value="float")))
     elif returns and all(r.value is None or isinstance(r.value, cst.Name)
                          and r.value.value == 'None' for r in returns):
         return updated_node.with_changes(returns=cst.Annotation(
             annotation=cst.Name(value="None")))
     return updated_node
Пример #6
0
 def leave_FunctionDef(self, original_node: cst.FunctionDef,
                       updated_node: cst.FunctionDef) -> cst.FunctionDef:
     key = self._qualifier_name()
     self.qualifier.pop()
     if key in self.function_annotations:
         annotations = self.function_annotations[key]
         # Only add new annotation if one doesn't already exist
         if not updated_node.returns:
             updated_node = updated_node.with_changes(
                 returns=annotations.returns)
         # Don't override default values when annotating functions
         new_parameters = self._update_parameters(annotations, updated_node)
         return updated_node.with_changes(params=new_parameters)
     return updated_node
Пример #7
0
    def visit_FunctionDef_body(self, node: FunctionDef) -> None:
        class Visitor(m.MatcherDecoratableVisitor):
            def __init__(self):
                super().__init__()

            def visit_EmptyLine_comment(self, node: "EmptyLine") -> None:
                # FIXME too many matches on test_param_02
                if not node.comment:
                    return
                # TODO: use comment.value
                return None

        v = Visitor()
        node.visit(v)
        return None
Пример #8
0
 def leave_FunctionDef(
     self,
     original_node: cst.FunctionDef,
     updated_node: cst.FunctionDef,
 ) -> cst.FunctionDef:
     key = FunctionKey.make(self._qualifier_name(), updated_node.params)
     self.qualifier.pop()
     if key in self.annotations.functions:
         function_annotation = self.annotations.functions[key]
         # Only add new annotation if:
         # * we have matching function signatures and
         # * we are explicitly told to overwrite existing annotations or
         # * there is no existing annotation
         if not self._match_signatures(updated_node, function_annotation):
             return updated_node
         set_return_annotation = (self.overwrite_existing_annotations
                                  or updated_node.returns is None)
         if set_return_annotation and function_annotation.returns is not None:
             updated_node = self._apply_annotation_to_return(
                 function_def=updated_node,
                 annotation=function_annotation.returns,
             )
         # Don't override default values when annotating functions
         new_parameters = self._update_parameters(function_annotation,
                                                  updated_node)
         return updated_node.with_changes(params=new_parameters)
     return updated_node
Пример #9
0
    def leave_FunctionDef(
        self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
    ) -> cst.FunctionDef:
        if matchers.matches(updated_node, self.matcher):
            return updated_node.with_changes(returns=cst.Annotation(cst.Name(value="None")))

        return updated_node
Пример #10
0
 def leave_FunctionDef(self, original_node: cst.FunctionDef,
                       updated_node: cst.FunctionDef) -> cst.FunctionDef:
     key = self._qualifier_name()
     self.qualifier.pop()
     if key in self.annotations.function_annotations:
         function_annotation = self.annotations.function_annotations[key]
         # Only add new annotation if explicitly told to overwrite existing
         # annotations or if one doesn't already exist.
         if self.overwrite_existing_annotations or not updated_node.returns:
             updated_node = updated_node.with_changes(
                 returns=function_annotation.returns)
         # Don't override default values when annotating functions
         new_parameters = self._update_parameters(function_annotation,
                                                  updated_node)
         return updated_node.with_changes(params=new_parameters)
     return updated_node
Пример #11
0
 def _apply_annotation_to_return(
     self,
     function_def: cst.FunctionDef,
     annotation: cst.Annotation,
 ) -> cst.FunctionDef:
     self.annotation_counts.return_annotations += 1
     return function_def.with_changes(returns=annotation)
Пример #12
0
 def leave_FunctionDef(
     self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
 ) -> cst.CSTNode:
   key = tuple(self.stack)
   self.stack.pop()
   if key in self.annotations:
     annotations = self.annotations[key]
     params, returns = annotations
     return updated_node.with_changes(params=params, returns=returns)
   return updated_node
Пример #13
0
    def leave_FunctionDef(self, original_node: cst.FunctionDef,
                          updated_node: cst.FunctionDef):
        should_async = self.stack == self.fn_should_async
        self.fn_should_async = None
        self.stack.pop()

        if not should_async:
            return updated_node

        # mark fn as async
        return updated_node.with_changes(asynchronous=cst.Asynchronous())
Пример #14
0
 def leave_FunctionDef(
     self, node: cst.FunctionDef, updated_node: cst.FunctionDef
 ) -> cst.CSTNode:
     key = self._qualifier_name()
     self.qualifier.pop()
     if key in self.function_annotations:
         annotations = self.function_annotations[key]
         return updated_node.with_changes(
             params=annotations.parameters, returns=annotations.returns
         )
     return updated_node
Пример #15
0
    def leave_FunctionDef(self, original_node: cst.FunctionDef,
                          updated_node: cst.FunctionDef) -> cst.CSTNode:
        key = tuple(self.stack)
        self.stack.pop()

        if key in self.annotations:

            annotations = self.annotations[key]
            # Todo: add/update Docstring
            return updated_node.with_changes(params=annotations[0],
                                             returns=annotations[1])
        return updated_node
Пример #16
0
    def leave_FunctionDef(self, original_node: cst.FunctionDef,
                          updated_node: cst.FunctionDef):
        fn_ret_type = self.fn_visited[-1][0]['ret_type']
        self.fn_visited.pop()
        if fn_ret_type != "":
            fn_ret_type_resolved = self.resolve_type_alias(fn_ret_type)
            fn_ret_type = self.__name2annotation(fn_ret_type_resolved)
            if fn_ret_type is not None:
                self.all_applied_types.add((fn_ret_type_resolved, fn_ret_type))
                return updated_node.with_changes(returns=fn_ret_type)

        return updated_node
Пример #17
0
    def leave_FunctionDef(self, original_node: cst.FunctionDef,
                          updated_node: cst.FunctionDef):

        ret_type = self.module_type_annot[(self.cls_stack[-1] if
                                           len(self.cls_stack) > 0 else None,
                                           self.fn_stack[-1], None)][0]
        self.fn_stack.pop()
        if ret_type != '':
            return updated_node.with_changes(name=cst.Name(
                value=f"${ret_type}$"))
        else:
            return updated_node
Пример #18
0
 def leave_FunctionDef(
     self, original_node: FunctionDef, updated_node: FunctionDef
 ) -> Union[BaseStatement, FlattenSentinel[BaseStatement], RemovalSentinel]:
     if (self.is_visiting_subclass and m.matches(
             updated_node, m.FunctionDef(name=m.Name("has_add_permission")))
             and len(updated_node.params.params) == 2):
         old_params = updated_node.params
         updated_params = old_params.with_changes(params=(
             *old_params.params,
             parse_param("obj=None"),
         ))
         return updated_node.with_changes(params=updated_params)
     return super().leave_FunctionDef(original_node, updated_node)
Пример #19
0
    def __extract_docstring(self, node: cst.FunctionDef) -> str:
        """
        Extracts & post-processes the docstring from the provided function node.
        If a docstring is not present, an empty string is returned.

        :node: Function node
        :return: Docstring as string
        """
        # Get docstring from Function node
        docstring = node.get_docstring()

        # Return empty string if docstring undefined
        return docstring if docstring is not None else ""
Пример #20
0
    def leave_FunctionDef(self, node: cst.FunctionDef,
                          updated_node: cst.FunctionDef) -> cst.FunctionDef:
        leaving_coroutine = self.coroutine_stack.pop()
        if not leaving_coroutine:
            return updated_node

        return updated_node.with_changes(
            decorators=[
                decorator for decorator in updated_node.decorators
                if not m.matches(decorator, gen_coroutine_decorator_matcher)
            ],
            asynchronous=cst.Asynchronous(),
        )
Пример #21
0
 def leave_FunctionDef(
         self, original_node: libcst.FunctionDef,
         updated_node: libcst.FunctionDef) -> libcst.FunctionDef:
     self.count += 1
     function_position = self.get_metadata(libcst.metadata.PositionProvider,
                                           original_node).start
     body_position = self.get_metadata(libcst.metadata.PositionProvider,
                                       original_node.body).start
     function_defition_length = body_position.line - function_position.line
     # print(original_node.name.value, '\t', function_defition_length, '\t', function_position.line)
     new_fstr = self.empty_function_body(
         self.module.code_for_node(original_node), function_defition_length)
     new_fdef = self.lparser.parse_module(new_fstr).body[0]
     return updated_node.with_changes(body=new_fdef.body)
Пример #22
0
 def leave_FunctionDef(
         self, original_node: FunctionDef, updated_node: FunctionDef
 ) -> Union[BaseStatement, RemovalSentinel]:
     if (m.matches(updated_node,
                   m.FunctionDef(name=m.Name("has_add_permission")))
             and self._is_context_right):
         if len(updated_node.params.params) == 2:
             old_params = updated_node.params
             updated_params = old_params.with_changes(params=(
                 *old_params.params,
                 Param(name=Name("obj"), default=Name("None")),
             ))
             return updated_node.with_changes(params=updated_params)
     return super().leave_FunctionDef(original_node, updated_node)
Пример #23
0
 def leave_FunctionDef(
     self,
     original_node: cst.FunctionDef,
     updated_node: cst.FunctionDef,
 ) -> cst.FunctionDef:
     self.function_body_stack.pop()
     function_type_info = self.function_type_info_stack.pop()
     if updated_node.returns is None and function_type_info.returns is not None:
         return updated_node.with_changes(
             returns=_convert_annotation(
                 raw=function_type_info.returns,
                 quote_annotations=self.quote_annotations,
             )
         )
     else:
         return updated_node
Пример #24
0
 def leave_FunctionDef(
     self, original_node: FunctionDef, updated_node: FunctionDef
 ) -> Union[BaseStatement, FlattenSentinel[BaseStatement], RemovalSentinel]:
     if self.visiting_permalink_method:
         for decorator in updated_node.decorators:
             if m.matches(decorator, self.decorator_matcher):
                 AddImportsVisitor.add_needed_import(
                     context=self.context,
                     module="django.urls",
                     obj="reverse",
                 )
                 updated_decorators = list(updated_node.decorators)
                 updated_decorators.remove(decorator)
                 self.context.scratch.pop(self.ctx_key_inside_method, None)
                 return updated_node.with_changes(
                     decorators=tuple(updated_decorators))
     return super().leave_FunctionDef(original_node, updated_node)
Пример #25
0
    def leave_FunctionDef(self, original_node: cst.FunctionDef,
                          updated_node: cst.FunctionDef) -> cst.FunctionDef:
        final_node = updated_node
        if original_node is self.scope:
            # Don't want to ssa params but do want them in the name table
            for param in updated_node.params.params:
                name = param.name.value
                self.name_table[name] = name
                self.name_assignments[name] = param

            # Need to visit params to get them to be rebuilt and therfore
            # tracked to build the symbol table
            self._skip += 1
            update_params = updated_node.params.visit(self)
            self._skip -= 1
            assert not self._skip
            assert not self._assigned_names, self._assigned_names
            new_body = updated_node.body.visit(self)
            final_node = updated_node.with_changes(body=new_body,
                                                   params=update_params)
            assert not self._skip
            assert not self._assigned_names, self._assigned_names
        return final_node
Пример #26
0
    def leave_FunctionDef(
        self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
    ) -> Union[cst.BaseStatement, cst.RemovalSentinel]:
        modified_defaults: List = []
        mutable_args: List[Tuple[cst.Name, Union[cst.List, cst.Dict]]] = []

        for param in updated_node.params.params:
            if not m.matches(param,
                             m.Param(default=m.OneOf(m.List(), m.Dict()))):
                modified_defaults.append(param)
                continue

            # This line here is just for type checkers peace of mind,
            # since it cannot reason about variables from matchers result.
            if not isinstance(param.default, (cst.List, cst.Dict)):
                continue

            mutable_args.append((param.name, param.default))
            modified_defaults.append(
                param.with_changes(default=cst.Name("None"), ))

        if not mutable_args:
            return original_node

        modified_params: cst.Parameters = updated_node.params.with_changes(
            params=modified_defaults)

        initializations: List[Union[
            cst.SimpleStatementLine, cst.BaseCompoundStatement]] = [
                # We use generation by template here since construction of the
                # resulting 'if' can be burdensome due to many nested objects
                # involved. Additional line is attached so that we may control
                # exact spacing between generated statements.
                parse_template_statement(
                    DEFAULT_INIT_TEMPLATE,
                    config=self.module_config,
                    arg=arg,
                    init=init).with_changes(leading_lines=[EMPTY_LINE])
                for arg, init in mutable_args
            ]

        # Docstring should always go right after the function definition,
        # so we take special care to insert our initializations after the
        # last docstring found.
        docstrings = takewhile(is_docstring, updated_node.body.body)
        function_code = dropwhile(is_docstring, updated_node.body.body)

        # It is not possible to insert empty line after the statement line,
        # because whitespace is owned by the next statement after it.
        stmt_with_empty_line = next(function_code).with_changes(
            leading_lines=[EMPTY_LINE])

        modified_body = (
            *docstrings,
            *initializations,
            stmt_with_empty_line,
            *function_code,
        )

        return updated_node.with_changes(
            params=modified_params,
            body=updated_node.body.with_changes(body=modified_body),
        )
Пример #27
0
 def visit_FunctionDef(self, node: cst.FunctionDef) -> Optional[bool]:
     self.stack.append(node.name.value)
     docstring = node.get_docstring(clean=False)
     self.annotations[tuple(self.stack)] = (node.params, node.returns, None,
                                            docstring)
     return False  # pyi files don't support inner functions, return False to stop the traversal.
Пример #28
0
 def leave_FunctionDef(self, original_node: cst.FunctionDef,
                       updated_node: cst.FunctionDef):
     return updated_node.with_changes(returns=None)
Пример #29
0
 def leave_FunctionDef(
     self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
 ) -> Union[cst.BaseStatement, cst.RemovalSentinel]:
     return updated_node.with_changes(
         returns=None
     ) if original_node.returns is not None else updated_node
Пример #30
0
    def leave_FunctionDef(self, original_node: cst.FunctionDef,
                          updated_node: cst.FunctionDef):

        # Удаление аннотаций
        if self.delete_annotations:
            old_params = original_node.params.params
            old_default_params = original_node.params.default_params

            small_params = [
                i.with_changes(annotation=None) for i in old_params
            ]
            small_default_params = [
                i.with_changes(annotation=None) for i in old_default_params
            ]

            new_params = original_node.params.with_changes(
                params=small_params, default_params=small_default_params)

            updated_node = updated_node.with_changes(params=new_params,
                                                     returns=None)

        # Удаление многострочных комментариев
        if self.delete_docstrings:
            pass

        # Замена аргументов
        if (self.change_method_arguments and self.class_stack) or\
                (self.change_arguments and not self.class_stack):

            all_params = updated_node.params

            new_params = []
            new_default_params = []

            for argument in all_params.params:
                # Обфускация имени аргумента
                if self.can_rename(argument.name.value, 'a', 'ca'):
                    argument = argument.with_changes(
                        name=self.get_new_cst_name(argument.name))

                new_params.append(argument)

            for argument in all_params.default_params:
                # Обфкскация значения по умолчанию
                argument = argument.with_changes(
                    default=self.obf_universal(argument.default))
                # Обфускация имени аргумента
                if self.can_rename(argument.name.value, 'a', 'ca'):
                    argument = argument.with_changes(
                        name=self.get_new_cst_name(argument.name))

                new_default_params.append(argument)

            arg, kwarg = all_params.star_arg, all_params.star_kwarg
            new_all_params = all_params.with_changes(
                params=new_params, default_params=new_default_params)

            if arg is not None and not isinstance(
                    arg.name, str) and self.can_rename(arg.name.value, 'a',
                                                       'ca'):
                new_star_arg = arg.with_changes(
                    name=self.get_new_cst_name(arg.name.value))
                new_all_params = new_all_params.with_changes(
                    star_arg=new_star_arg)
            if kwarg is not None and not isinstance(
                    kwarg.name, str) and self.can_rename(
                        kwarg.name.value, 'a', 'ca'):
                new_star_kwarg = kwarg.with_changes(
                    name=self.get_new_cst_name(kwarg.name.value))
                new_all_params = new_all_params.with_changes(
                    star_kwarg=new_star_kwarg)

            updated_node = updated_node.with_changes(params=new_all_params)

        is_property = 'property' in get_function_decorators(updated_node)

        # Замена названия
        if (self.can_rename(updated_node.name.value, 'f', 'cf', 'cv') and
            ((self.class_stack and
              ((self.change_methods and self.change_fields) or
               (self.change_fields and not self.change_methods and is_property)
               or (not self.change_fields and self.change_methods
                   and not is_property))) or
             (not self.class_stack and self.change_functions))):
            updated_node = self.renamed(updated_node)

        self.function_stack.pop()

        return updated_node