"""Unit tests for the pylint checkers in :mod:`pylint.extensions.check_docs`,
in particular the parameter documentation checker `ParamDocChecker`
"""
from __future__ import division, print_function, absolute_import

import unittest
import sys

import astroid
from astroid import test_utils
from pylint.testutils import CheckerTestCase, Message, set_config

from pylint.extensions.check_docs import ParamDocChecker, space_indentation


class ParamDocCheckerTest(CheckerTestCase):
    """Tests for pylint_plugin.ParamDocChecker"""
    CHECKER_CLASS = ParamDocChecker

    def test_space_indentation(self):
        self.assertEqual(space_indentation('abc'), 0)
        self.assertEqual(space_indentation(''), 0)
        self.assertEqual(space_indentation('  abc'), 2)
        self.assertEqual(space_indentation('\n  abc'), 0)
        self.assertEqual(space_indentation('   \n  abc'), 3)

    def test_missing_func_params_in_sphinx_docstring(self):
        """Example of a function with missing Sphinx parameter documentation in
        the docstring
        """
        node = test_utils.extract_node("""
        def function_foo(x, y, z):
            '''docstring ...

            :param x: bla

            :param int z: bar
            '''
            pass
        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('y',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('x, y',))
        ):
            self.checker.visit_functiondef(node)

    def test_missing_func_params_in_google_docstring(self):
        """Example of a function with missing Google style parameter
        documentation in the docstring
        """
        node = test_utils.extract_node("""
        def function_foo(x, y, z):
            '''docstring ...

            Args:
                x: bla
                z (int): bar

            some other stuff
            '''
            pass
        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('y',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('x, y',))
        ):
            self.checker.visit_functiondef(node)

    def test_missing_func_params_in_numpy_docstring(self):
        """Example of a function with missing NumPy style parameter
        documentation in the docstring
        """
        node = test_utils.extract_node("""
        def function_foo(x, y, z):
            '''docstring ...

            Parameters
            ----------
            x:
                bla
            z: int
                bar

            some other stuff
            '''
            pass
        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('y',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('x, y',))
        ):
            self.checker.visit_functiondef(node)

    def test_tolerate_no_param_documentation_at_all(self):
        """Example of a function with no parameter documentation at all

        No error message is emitted.
        """
        node = test_utils.extract_node("""
        def function_foo(x, y):
            '''docstring ...

            missing parameter documentation'''
            pass
        """)
        with self.assertNoMessages():
            self.checker.visit_functiondef(node)

    @set_config(accept_no_param_doc=False)
    def test_don_t_tolerate_no_param_documentation_at_all(self):
        """Example of a function with no parameter documentation at all

        No error message is emitted.
        """
        node = test_utils.extract_node("""
        def function_foo(x, y):
            '''docstring ...

            missing parameter documentation'''
            pass
        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('x, y',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('x, y',))
        ):
            self.checker.visit_functiondef(node)

    def _visit_methods_of_class(self, node):
        """Visit all methods of a class node

        :param node: class node
        :type node: :class:`astroid.scoped_nodes.Class`
        """
        for body_item in node.body:
            if (isinstance(body_item, astroid.FunctionDef)
                    and hasattr(body_item, 'name')):
                self.checker.visit_functiondef(body_item)

    def test_missing_method_params_in_sphinx_docstring(self):
        """Example of a class method with missing parameter documentation in
        the Sphinx style docstring
        """
        node = test_utils.extract_node("""
        class Foo(object):
            def method_foo(self, x, y):
                '''docstring ...

                missing parameter documentation

                :param x: bla
                '''
                pass
        """)
        method_node = node.body[0]
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=method_node,
                args=('y',)),
            Message(
                msg_id='missing-type-doc',
                node=method_node,
                args=('x, y',))
        ):
            self._visit_methods_of_class(node)

    def test_missing_method_params_in_google_docstring(self):
        """Example of a class method with missing parameter documentation in
        the Google style docstring
        """
        node = test_utils.extract_node("""
        class Foo(object):
            def method_foo(self, x, y):
                '''docstring ...

                missing parameter documentation

                Args:
                    x: bla
                '''
                pass
        """)
        method_node = node.body[0]
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=method_node,
                args=('y',)),
            Message(
                msg_id='missing-type-doc',
                node=method_node,
                args=('x, y',))
        ):
            self._visit_methods_of_class(node)

    def test_missing_method_params_in_numpy_docstring(self):
        """Example of a class method with missing parameter documentation in
        the Numpy style docstring
        """
        node = test_utils.extract_node("""
        class Foo(object):
            def method_foo(self, x, y):
                '''docstring ...

                missing parameter documentation

                Parameters
                ----------
                x:
                    bla
                '''
                pass
        """)
        method_node = node.body[0]
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=method_node,
                args=('y',)),
            Message(
                msg_id='missing-type-doc',
                node=method_node,
                args=('x, y',))
        ):
            self._visit_methods_of_class(node)

    def test_existing_func_params_in_sphinx_docstring(self):
        """Example of a function with correctly documented parameters and
        return values (Sphinx style)
        """
        node = test_utils.extract_node("""
        def function_foo(xarg, yarg, zarg):
            '''function foo ...

            :param xarg: bla xarg
            :type xarg: int

            :param yarg: bla yarg
            :type yarg: float

            :param int zarg: bla zarg

            :return: sum
            :rtype: float
            '''
            return xarg + yarg
        """)
        with self.assertNoMessages():
            self.checker.visit_functiondef(node)

    def test_existing_func_params_in_google_docstring(self):
        """Example of a function with correctly documented parameters and
        return values (Google style)
        """
        node = test_utils.extract_node("""
        def function_foo(xarg, yarg, zarg):
            '''function foo ...

            Args:
                xarg (int): bla xarg
                yarg (float): bla
                    bla yarg
        
                zarg (int): bla zarg

            Returns:
                float: sum
            '''
            return xarg + yarg
        """)
        with self.assertNoMessages():
            self.checker.visit_functiondef(node)

    def test_existing_func_params_in_numpy_docstring(self):
        """Example of a function with correctly documented parameters and
        return values (Numpy style)
        """
        node = test_utils.extract_node("""
        def function_foo(xarg, yarg, zarg):
            '''function foo ...

            Parameters
            ----------
            xarg: int
                bla xarg
            yarg: float
                bla yarg

            zarg: int
                bla zarg

            Returns
            -------
            float
                sum
            '''
            return xarg + yarg
        """)
        with self.assertNoMessages():
            self.checker.visit_functiondef(node)

    def test_wrong_name_of_func_params_in_sphinx_docstring(self):
        """Example of functions with inconsistent parameter names in the
        signature and in the Sphinx style documentation
        """
        node = test_utils.extract_node("""
        def function_foo(xarg, yarg, zarg):
            '''function foo ...

            :param xarg1: bla xarg
            :type xarg: int

            :param yarg: bla yarg
            :type yarg1: float

            :param str zarg1: bla zarg
            '''
            return xarg + yarg
        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('xarg, xarg1, zarg, zarg1',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('yarg, yarg1, zarg, zarg1',)),
        ):
            self.checker.visit_functiondef(node)

        node = test_utils.extract_node("""
        def function_foo(xarg, yarg):
            '''function foo ...

            :param yarg1: bla yarg
            :type yarg1: float

            For the other parameters, see bla.
            '''
            return xarg + yarg
        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('yarg1',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('yarg1',))
        ):
            self.checker.visit_functiondef(node)

    def test_wrong_name_of_func_params_in_google_docstring(self):
        """Example of functions with inconsistent parameter names in the
        signature and in the Google style documentation
        """
        node = test_utils.extract_node("""
        def function_foo(xarg, yarg, zarg):
            '''function foo ...

            Args:
                xarg1 (int): bla xarg
                yarg (float): bla yarg

                zarg1 (str): bla zarg
            '''
            return xarg + yarg
        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('xarg, xarg1, zarg, zarg1',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('xarg, xarg1, zarg, zarg1',)),
        ):
            self.checker.visit_functiondef(node)

        node = test_utils.extract_node("""
        def function_foo(xarg, yarg):
            '''function foo ...

            Args:
                yarg1 (float): bla yarg

            For the other parameters, see bla.
            '''
            return xarg + yarg
        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('yarg1',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('yarg1',))
        ):
            self.checker.visit_functiondef(node)

    def test_wrong_name_of_func_params_in_numpy_docstring(self):
        """Example of functions with inconsistent parameter names in the
        signature and in the Numpy style documentation
        """
        node = test_utils.extract_node("""
        def function_foo(xarg, yarg, zarg):
            '''function foo ...

            Parameters
            ----------
            xarg1: int
                bla xarg
            yarg: float
                bla yarg

            zarg1: str
                bla zarg
            '''
            return xarg + yarg
        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('xarg, xarg1, zarg, zarg1',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('xarg, xarg1, zarg, zarg1',)),
        ):
            self.checker.visit_functiondef(node)

        node = test_utils.extract_node("""
        def function_foo(xarg, yarg):
            '''function foo ...

            Parameters
            ----------
            yarg1: float
                bla yarg

            For the other parameters, see bla.
            '''
            return xarg + yarg
        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('yarg1',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('yarg1',))
        ):
            self.checker.visit_functiondef(node)

    def test_see_sentence_for_func_params_in_sphinx_docstring(self):
        """Example for the usage of "For the other parameters, see" to avoid
        too many repetitions, e.g. in functions or methods adhering to a
        given interface (Sphinx style)
        """
        node = test_utils.extract_node("""
        def function_foo(xarg, yarg):
            '''function foo ...

            :param yarg: bla yarg
            :type yarg: float

            For the other parameters, see :func:`bla`
            '''
            return xarg + yarg
        """)
        with self.assertNoMessages():
            self.checker.visit_functiondef(node)

    def test_see_sentence_for_func_params_in_google_docstring(self):
        """Example for the usage of "For the other parameters, see" to avoid
        too many repetitions, e.g. in functions or methods adhering to a
        given interface (Google style)
        """
        node = test_utils.extract_node("""
        def function_foo(xarg, yarg):
            '''function foo ...

            Args:
                yarg (float): bla yarg

            For the other parameters, see :func:`bla`
            '''
            return xarg + yarg
        """)
        with self.assertNoMessages():
            self.checker.visit_functiondef(node)

    def test_see_sentence_for_func_params_in_numpy_docstring(self):
        """Example for the usage of "For the other parameters, see" to avoid
        too many repetitions, e.g. in functions or methods adhering to a
        given interface (Numpy style)
        """
        node = test_utils.extract_node("""
        def function_foo(xarg, yarg):
            '''function foo ...

            Parameters
            ----------
            yarg: float
                bla yarg

            For the other parameters, see :func:`bla`
            '''
            return xarg + yarg
        """)
        with self.assertNoMessages():
            self.checker.visit_functiondef(node)

    def test_constr_params_in_class_sphinx(self):
        """Example of a class with missing constructor parameter documentation
        (Sphinx style)

        Everything is completely analogous to functions.
        """
        node = test_utils.extract_node("""
        class ClassFoo(object):
            '''docstring foo

            :param y: bla
            
            missing constructor parameter documentation
            '''

            def __init__(self, x, y):
                pass

        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('x',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('x, y',))
        ):
            self._visit_methods_of_class(node)

    def test_constr_params_in_class_google(self):
        """Example of a class with missing constructor parameter documentation
        (Google style)

        Everything is completely analogous to functions.
        """
        node = test_utils.extract_node("""
        class ClassFoo(object):
            '''docstring foo

            Args:
                y: bla
            
            missing constructor parameter documentation
            '''

            def __init__(self, x, y):
                pass

        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('x',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('x, y',))
        ):
            self._visit_methods_of_class(node)

    def test_constr_params_in_class_numpy(self):
        """Example of a class with missing constructor parameter documentation
        (Numpy style)

        Everything is completely analogous to functions.
        """
        node = test_utils.extract_node("""
        class ClassFoo(object):
            '''docstring foo

            Parameters
            ----------
            y:
                bla
            
            missing constructor parameter documentation
            '''

            def __init__(self, x, y):
                pass

        """)
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('x',)),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('x, y',))
        ):
            self._visit_methods_of_class(node)

    @unittest.skipIf(sys.version_info[0] != 3, "Enabled on Python 3")
    def test_kwonlyargs_are_taken_in_account(self):
        node = test_utils.extract_node('''
        def my_func(arg, *, kwonly, missing_kwonly):
            """The docstring

            :param int arg: The argument.
            :param bool kwonly: A keyword-arg.
            """
        ''')
        with self.assertAddsMessages(
            Message(
                msg_id='missing-param-doc',
                node=node,
                args=('missing_kwonly', )),
            Message(
                msg_id='missing-type-doc',
                node=node,
                args=('missing_kwonly', ))):
            self.checker.visit_functiondef(node)


if __name__ == '__main__':
    unittest.main()
