From 87ba4ab7382097c2789953ef88975f126091ad98 Mon Sep 17 00:00:00 2001 From: David Zuber Date: Fri, 30 Dec 2016 00:17:59 +0000 Subject: [PATCH] Make all functions handle regions --- README.rst | 8 +- src/reddel_server/__init__.py | 17 +- src/reddel_server/redlib.py | 140 +++++++++++++++ src/reddel_server/redprovider.py | 291 ++++++++----------------------- src/reddel_server/validators.py | 243 +++++++++++++++++++------- tests/test_redlib.py | 98 +++++++++++ tests/test_redprovider.py | 123 ++----------- tests/test_validators.py | 198 +++++++++++++++++---- 8 files changed, 690 insertions(+), 428 deletions(-) create mode 100644 src/reddel_server/redlib.py create mode 100644 tests/test_redlib.py diff --git a/README.rst b/README.rst index 4794f98..e6c7c2e 100644 --- a/README.rst +++ b/README.rst @@ -79,8 +79,10 @@ An example on how to expose a simple function to add arguments:: class MyProvider(reddel_server.ProviderBase) @reddel_server.red_src() - @reddel_server.red_type(["def"]) - def add_arg(self, red, index, arg): + @reddel_server.red_validate([reddel_server.OptionalRegionValidator(), + reddel_server.SingleNodeValidator(), + reddel_server.TypeValidator(["def"])]) + def add_arg(self, red, start, end, index, arg): red.arguments.insert(index, arg) return red @@ -93,7 +95,7 @@ Start the reddel server from Emacs:: >>> ;; make sure myprovidermod is in a directory within the PYTHONPATH >>> (epc:call-sync my-epc 'add_provider '("myprovidermod.MyProvider")) - >>> (message (epc:call-sync my-epc 'add_arg '("def foo(arg1, arg3): pass" 1 "arg2"))) + >>> (message (epc:call-sync my-epc 'add_arg '("def foo(arg1, arg3): pass" nil nil 1 "arg2"))) "def foo(arg1, arg2, arg3): pass" Redbaron provides a lossless format, so even formatting and comments are preserved. diff --git a/src/reddel_server/__init__.py b/src/reddel_server/__init__.py index dfe301e..e719884 100644 --- a/src/reddel_server/__init__.py +++ b/src/reddel_server/__init__.py @@ -61,7 +61,6 @@ * :func:`red_src ` * :func:`red_validate ` - * :func:`red_type ` These decorators are the mini framework that allows the server to tell the client what actions are available for a given piece of code. @@ -77,7 +76,6 @@ * :func:`redwraps ` * :func:`red_src ` * :func:`red_validate ` - * :func:`red_type ` * :func:`get_parents ` * :func:`get_node_of_region ` @@ -89,12 +87,19 @@ E.g. if the source is a function, reddel can report to Emacs which functions can be applied to functions and Emacs can use the information to dynamically build a UI. -Validators can transform the source as well (see :meth:`transform `). +Validators can transform the source as well. +The transformed source is passed onto the next validator when you use :func:`reddel_server.red_validate`. +All validators provided by ``reddel_server`` can be used as mix-ins. +When you create your own validator and you inherit from multiple builtin ones then +they are effectively combined since all of them perform the appropriate super call. See: * :class:`ValidatorInterface ` - * :class:`BaronTypeValidator ` + * :class:`OptionalRegionValidator ` + * :class:`MandatoryRegionValidator ` + * :class:`SingleNodeValidator ` + * :class:`TypeValidator ` ---------- Exceptions @@ -118,6 +123,7 @@ from .exceptions import * from .provider import * +from .redlib import * from .redprovider import * from .server import * from .validators import * @@ -126,6 +132,7 @@ server.__all__ + provider.__all__ + validators.__all__ + - redprovider.__all__) + redprovider.__all__ + + redlib.__all__) __version__ = "0.1.0" diff --git a/src/reddel_server/redlib.py b/src/reddel_server/redlib.py new file mode 100644 index 0000000..dded03c --- /dev/null +++ b/src/reddel_server/redlib.py @@ -0,0 +1,140 @@ +"""RedBaron specific library of functions""" +import collections + +import redbaron + +__all__ = ['Position', 'Parent', 'get_parents', 'get_node_of_region'] + + +class Position(collections.namedtuple('Position', ['row', 'column'])): + """Describes a position in the source code by line number and character position in that line. + + :param row: the line number + :type row: :class:`int` + :param column: the position in the line + :type column: :class:`int` + """ + __slots__ = () + + +class Parent(collections.namedtuple('Parent', ['identifier', 'start', 'end'])): + """Represents a node type with the bounding location. + + :param identifier: the node type + :type identifier: :class:`str` + :param start: the position where the node begins in the code + :type start: :class:`Position` + :param end: the position where the node ends in the code + :type end: :class:`Position` + """ + __slots__ = () + + +def get_parents(red): + """Yield the parents of the given red node + + :param red: the red baron source + :type red: :class:`redbaron.base_nodes.Node` | :class:`redbaron.RedBaron` + :returns: each parent of the given node + :rtype: Generator[:class:`redbaron.base_nodes.Node`] + :raises: None + """ + current = red.parent + while current: + yield current + current = current.parent + + +def get_node_of_region(red, start, end): + """Get the node that contains the given region + + :param red: the red baron source + :type red: :class:`redbaron.RedBaron` + :param start: position of the beginning of the region + :type start: :class:`Position` + :param end: position of the end of the region + :type end: :class:`Position` + :returns: the node that contains the region + :rtype: :class:`redbaron.base_nodes.Node` + + First the nodes at start and end are gathered. Then the common parent is selected. + If the common parent is a list, the minimum slice is used. + This makes it easier for the user because he can partially select nodes and + still gets what he most likely intended to get. + + For example if your region partially selects several lines in a for loop + and you want to extract them (``|`` shows the region bounds):: + + for i in range(10): + a = 1 + 2 + b |= 4 + c = 5 + d =| 7 + + then we expect to get back:: + + b = 4 + c = 5 + d = 7 + + Note that the leading tab is missing because it doesn't belong to the ``b = 4`` node. + + .. doctest:: + + >>> import redbaron + >>> import reddel_server + >>> testsrc = ("for i in range(10):\\n" + ... " a = 1 + 2\\n" + ... " b = 4\\n" + ... " c = 5\\n" + ... " d = 7\\n") + >>> start = (3, 7) + >>> end = (5, 8) + >>> reddel_server.get_node_of_region(redbaron.RedBaron(testsrc), start, end).dumps() + 'b = 4\\n c = 5\\n d = 7' + + You can also partially select a list: + + .. doctest:: + + >>> testsrc = "[1, 2, 3, 4, 5, 6]" + >>> start = (1, 8) # "3" + >>> end = (1, 14) # "5" + >>> reddel_server.get_node_of_region(redbaron.RedBaron(testsrc), start, end).dumps() + '3, 4, 5' + + """ + snode = red.find_by_position(start) + enode = red.find_by_position(end) + if snode == enode: + return snode + snodes = [snode] + list(get_parents(snode)) + enodes = [enode] + list(get_parents(enode)) + previous = red + for snode, enode in list(zip(reversed(snodes), reversed(enodes))): + # both list of parents should end with the root node + # so we iterate over the parents in reverse until we + # reach a parent that is not common. + # the previous parent then has to encapsulate both + if snode != enode: + if hasattr(previous, "node_list"): + # For lists we want the minimum slice of the list. + # E.g. the region spans over 2 functions in a big module + # the common parent would be the root node. + # We only want the part containing the 2 functions and not everything. + # Unfortunately we might loose some information if we slice the proxy list. + # When we use node_list then we keep things like new lines etc. + try: + sindex = previous.node_list.index(snode) + eindex = previous.node_list.index(enode) + except ValueError: + # if one of the nodes is not in the list, it means it's not part of + # previous value. E.g. previous is a function definition and snode + # is part of the arguments while enode is part of the function body. + # in that case we take the whole previous node (e.g. the whole function) + pass + else: + previous = redbaron.NodeList(previous.node_list[sindex:eindex + 1]) + break + previous = snode + return previous diff --git a/src/reddel_server/redprovider.py b/src/reddel_server/redprovider.py index 64fd79f..e8364ab 100644 --- a/src/reddel_server/redprovider.py +++ b/src/reddel_server/redprovider.py @@ -1,154 +1,19 @@ """RedBaron specific functionality for reddel""" from __future__ import absolute_import -import collections import functools import redbaron from . import provider +from . import redlib from . import validators -__all__ = ['Position', 'Parent', 'get_parents', 'get_node_of_region', 'RedBaronProvider', - 'red_src', 'red_type', 'red_validate', 'redwraps'] +__all__ = ['RedBaronProvider', 'red_src', 'red_validate', 'redwraps'] _RED_FUNC_ATTRS = ['red', 'validators'] -class Position(collections.namedtuple('Position', ['row', 'column'])): - """Describes a position in the source code by line number and character position in that line. - - :param row: the line number - :type row: :class:`int` - :param column: the position in the line - :type column: :class:`int` - """ - __slots__ = () - - -class Parent(collections.namedtuple('Parent', ['identifier', 'start', 'end'])): - """Represents a node type with the bounding location. - - :param identifier: the node type - :type identifier: :class:`str` - :param start: the position where the node begins in the code - :type start: :class:`Position` - :param end: the position where the node ends in the code - :type end: :class:`Position` - """ - __slots__ = () - - -def get_parents(red): - """Yield the parents of the given red node - - :param red: the red baron source - :type red: :class:`redbaron.base_nodes.Node` | :class:`redbaron.RedBaron` - :returns: each parent of the given node - :rtype: Generator[:class:`redbaron.base_nodes.Node`] - :raises: None - """ - current = red.parent - while current: - yield current - current = current.parent - - -def get_node_of_region(red, start, end): - """Get the node that contains the given region - - :param red: the red baron source - :type red: :class:`redbaron.RedBaron` - :param start: position of the beginning of the region - :type start: :class:`Position` - :param end: position of the end of the region - :type end: :class:`Position` - :returns: the node that contains the region - :rtype: :class:`redbaron.base_nodes.Node` - - First the nodes at start and end are gathered. Then the common parent is selected. - If the common parent is a list, the minimum slice is used. - This makes it easier for the user because he can partially select nodes and - still gets what he most likely intended to get. - - For example if your region partially selects several lines in a for loop - and you want to extract them (``|`` shows the region bounds):: - - for i in range(10): - a = 1 + 2 - b |= 4 - c = 5 - d =| 7 - - then we expect to get back:: - - b = 4 - c = 5 - d = 7 - - Note that the leading tab is missing because it doesn't belong to the ``b = 4`` node. - - .. doctest:: - - >>> import redbaron - >>> import reddel_server - >>> testsrc = ("for i in range(10):\\n" - ... " a = 1 + 2\\n" - ... " b = 4\\n" - ... " c = 5\\n" - ... " d = 7\\n") - >>> start = (3, 7) - >>> end = (5, 8) - >>> reddel_server.get_node_of_region(redbaron.RedBaron(testsrc), start, end).dumps() - 'b = 4\\n c = 5\\n d = 7' - - You can also partially select a list: - - .. doctest:: - - >>> testsrc = "[1, 2, 3, 4, 5, 6]" - >>> start = (1, 8) # "3" - >>> end = (1, 14) # "5" - >>> reddel_server.get_node_of_region(redbaron.RedBaron(testsrc), start, end).dumps() - '3, 4, 5' - - """ - snode = red.find_by_position(start) - enode = red.find_by_position(end) - if snode == enode: - return snode - snodes = [snode] + list(get_parents(snode)) - enodes = [enode] + list(get_parents(enode)) - previous = red - for snode, enode in list(zip(reversed(snodes), reversed(enodes))): - # both list of parents should end with the root node - # so we iterate over the parents in reverse until we - # reach a parent that is not common. - # the previous parent then has to encapsulate both - if snode != enode: - if hasattr(previous, "node_list"): - # For lists we want the minimum slice of the list. - # E.g. the region spans over 2 functions in a big module - # the common parent would be the root node. - # We only want the part containing the 2 functions and not everything. - # Unfortunately we might loose some information if we slice the proxy list. - # When we use node_list then we keep things like new lines etc. - try: - sindex = previous.node_list.index(snode) - eindex = previous.node_list.index(enode) - except ValueError: - # if one of the nodes is not in the list, it means it's not part of - # previous value. E.g. previous is a function definition and snode - # is part of the arguments while enode is part of the function body. - # in that case we take the whole previous node (e.g. the whole function) - pass - else: - previous = redbaron.NodeList(previous.node_list[sindex:eindex + 1]) - break - previous = snode - return previous - - def redwraps(towrap): """Use this when creating decorators instead of :func:`functools.wraps` @@ -274,19 +139,20 @@ def red_validate(validators): import reddel_server - validator = reddel_server.BaronTypeValidator(["def"], single=True) + validator1 = reddel_server.SingleNodeValidator() + validator2 = reddel_server.TypeValidator(["def"]) class MyProvider(reddel_server.ProviderBase): @reddel_server.red_src() - @reddel_server.red_validate([validator]) - def foo(self, red): + @reddel_server.red_validate([validator1, validator2]) + def foo(self, red, start, end): assert red.type == 'def' provider = MyProvider(reddel_server.Server()) - provider.foo("def bar(): pass") # works + provider.foo("def bar(): pass", start=None, end=None) # works try: - provider.foo("1+1") + provider.foo("1+1", start=None, end=None) except reddel_server.ValidationException: pass else: @@ -300,46 +166,18 @@ def foo(self, red): """ def red_validate_dec(func): @redwraps(func) - def wrapped(self, red, *args, **kwargs): + def wrapped(self, red, start, end, *args, **kwargs): transformed = red for v in validators: - v(transformed) - transformed = v.transform(transformed) - return func(self, transformed, *args, **kwargs) + v(transformed, start, end) + transformed, start, end = v.transform(transformed, start, end) + return func(self, transformed, start, end, *args, **kwargs) wrapped.validators = wrapped.validators or [] wrapped.validators.extend(validators) return wrapped return red_validate_dec -def red_type(identifiers, single=True): - """Create decorator that checks if the root is identified by identifier - - Simple shortcut for doing: - - .. testsetup:: - - import reddel_server - - identifiers = ['def', 'ifelseblock'] - single=True - - .. testcode:: - - reddel_server.red_validate([reddel_server.BaronTypeValidator(identifiers, single=single)]) - - See :func:`reddel_server.red_validate`. - - :param identifiers: red baron node identifiers - :type identifiers: list of :class:`str` - :param single: expect a single node in the initial node list. Pass on the first node. - :type single: :class:`bool` - :returns: the decorator - :rtype: :data:`types.FunctionType` - """ - return red_validate([validators.BaronTypeValidator(identifiers, single=single)]) - - class RedBaronProvider(provider.ProviderBase): """Provider for inspecting and transforming source code via redbaron.""" @@ -349,11 +187,11 @@ def analyze(self, red, deep=2, with_formatting=False): .. table:: - +--------------+----------------+---------------+------------------+ - | source input | outputs source | allowed types | only single node | - +==============+================+===============+==================+ - | Yes | No | Any | No | - +--------------+----------------+---------------+------------------+ + +--------------+----------------+--------+------------------+---------------+ + | source input | outputs source | region | only single node | allowed types | + +==============+================+========+==================+===============+ + | Yes | No | No | No | Any | + +--------------+----------------+--------+------------------+---------------+ :param red: the red baron source :type red: :class:`redbaron.RedBaron` @@ -387,20 +225,26 @@ def analyze(self, red, deep=2, with_formatting=False): return "\n".join(red.__help__(deep=deep, with_formatting=False)) @red_src() - @red_type(["def"]) - def rename_arg(self, red, oldname, newname): + @red_validate([validators.OptionalRegionValidator(), + validators.SingleNodeValidator(), + validators.TypeValidator(["def"])]) + def rename_arg(self, red, start, end, oldname, newname): """Rename a argument .. table:: - +--------------+----------------+---------------+------------------+ - | source input | outputs source | allowed types | only single node | - +==============+================+===============+==================+ - | Yes | Yes | def | Yes | - +--------------+----------------+---------------+------------------+ + +--------------+----------------+----------+------------------+---------------+ + | source input | outputs source | region | only single node | allowed types | + +==============+================+==========+==================+===============+ + | Yes | Yes | Optional | Yes | def | + +--------------+----------------+----------+------------------+---------------+ :param red: the red baron source :type red: :class:`redbaron.RedBaron` + :param start: the start position of the selected region, if any. + :type start: :class:`reddel_server.Position` | ``None`` + :param end: the end position of the selected region, if any. + :type end: :class:`reddel_server.Position` | ``None`` :param oldname: name of the argument to rename :type oldname: :class:`str` :param newname: new name for the argument @@ -418,7 +262,7 @@ def rename_arg(self, red, oldname, newname): ... arg2 = arg2 or "" ... return arg2 + func(arg1, "arg2 arg2") + kwarg2 ... \"\"\" - >>> print(p.rename_arg(src, "arg2", "renamed")) + >>> print(p.rename_arg(src, None, None, "arg2", "renamed")) def foo(arg1, renamed, kwarg2=1): # arg2 renamed = renamed or "" return renamed + func(arg1, "arg2 arg2") + kwarg2 @@ -437,20 +281,26 @@ def foo(arg1, renamed, kwarg2=1): # arg2 return red @red_src(dump=False) - @red_type(["def"]) - def get_args(self, red): + @red_validate([validators.OptionalRegionValidator(), + validators.SingleNodeValidator(), + validators.TypeValidator(["def"])]) + def get_args(self, red, start, end): """Return a list of args and their default value (if any) as source code .. table:: - +--------------+----------------+---------------+------------------+ - | source input | outputs source | allowed types | only single node | - +==============+================+===============+==================+ - | Yes | No | def | Yes | - +--------------+----------------+---------------+------------------+ + +--------------+----------------+----------+------------------+---------------+ + | source input | outputs source | region | only single node | allowed types | + +==============+================+==========+==================+===============+ + | Yes | No | Optional | Yes | def | + +--------------+----------------+----------+------------------+---------------+ :param red: the red baron source :type red: :class:`redbaron.RedBaron` + :param start: the start position of the selected region, if any. + :type start: :class:`reddel_server.Position` | ``None`` + :param end: the end position of the selected region, if any. + :type end: :class:`reddel_server.Position` | ``None`` :returns: list of argument name and default value. :rtype: :class:`list` of :class:`tuple` or :class:`str` and :class:`str` | ``None`` @@ -465,7 +315,7 @@ def get_args(self, red): ... arg2 = arg2 or "" ... return arg2 + func(arg1, "arg2 arg2") + kwarg2 ... \"\"\" - >>> p.get_args(src) + >>> p.get_args(src, None, None) [('arg1', None), ('arg2', None), ('kwarg1', 'None'), ('kwarg2', '1'), ('kwarg3', "'None'")] """ @@ -483,20 +333,26 @@ def get_args(self, red): return args @red_src() - @red_type(["def"]) - def add_arg(self, red, index, arg): + @red_validate([validators.OptionalRegionValidator(), + validators.SingleNodeValidator(), + validators.TypeValidator(["def"])]) + def add_arg(self, red, start, end, index, arg): """Add a argument at the given index .. table:: - +--------------+----------------+---------------+------------------+ - | source input | outputs source | allowed types | only single node | - +==============+================+===============+==================+ - | Yes | Yes | def | Yes | - +--------------+----------------+---------------+------------------+ + +--------------+----------------+----------+------------------+---------------+ + | source input | outputs source | region | only single node | allowed types | + +==============+================+==========+==================+===============+ + | Yes | Yes | Optional | Yes | Yes | + +--------------+----------------+----------+------------------+---------------+ :param red: the red baron source :type red: :class:`redbaron.RedBaron` + :param start: the start position of the selected region, if any. + :type start: :class:`reddel_server.Position` | ``None`` + :param end: the end position of the selected region, if any. + :type end: :class:`reddel_server.Position` | ``None`` :param index: position of the argument. ``0`` would mean to put the argument in the front. :type index: :class:`int` :param arg: the argument to add @@ -514,7 +370,7 @@ def add_arg(self, red, index, arg): ... arg2 = arg2 or "" ... return arg2 + func(arg1, "arg2 arg2") + kwarg2 ... \"\"\" - >>> print(p.add_arg(src, 3, "kwarg3=123")) + >>> print(p.add_arg(src, start=None, end=None, index=3, arg="kwarg3=123")) def foo(arg1, arg2, kwarg2=1, kwarg3=123): arg2 = arg2 or "" return arg2 + func(arg1, "arg2 arg2") + kwarg2 @@ -525,21 +381,24 @@ def foo(arg1, arg2, kwarg2=1, kwarg3=123): return red @red_src(dump=False) - def get_parents(self, red, pos): + @red_validate([validators.MandatoryRegionValidator()]) + def get_parents(self, red, start, end): """Return a list of parents (scopes) relative to the given position .. table:: - +--------------+----------------+---------------+------------------+ - | source input | outputs source | allowed types | only single node | - +==============+================+===============+==================+ - | Yes | No | Any | No | - +--------------+----------------+---------------+------------------+ + +--------------+----------------+-----------+------------------+---------------+ + | source input | outputs source | region | only single node | allowed types | + +==============+================+===========+==================+===============+ + | Yes | No | Mandatory | No | Any | + +--------------+----------------+-----------+------------------+---------------+ :param red: the red baron source :type red: :class:`redbaron.RedBaron` - :param pos: the position to query - :type pos: :class:`Position` + :param start: the start position of the selected region. + :type start: :class:`reddel_server.Position` + :param end: the end position of the selected region. + :type end: :class:`reddel_server.Position` :returns: a list of parents starting with the element at position first. :rtype: :class:`list` of the parents. A parent is represented by a :class:`Parent` of the @@ -559,7 +418,7 @@ def get_parents(self, red, pos): ... except: ... func(subfunc(arg1="asdf")) ... \"\"\" - >>> pprint.pprint(p.get_parents(src, reddel_server.Position(7, 32))) + >>> pprint.pprint(p.get_parents(src, reddel_server.Position(7, 32), reddel_server.Position(7, 32))) [Parent(identifier='string', start=Position(row=7, column=31), end=Position(row=7, column=36)), Parent(identifier='call_argument', start=..., end=...), Parent(identifier='call', start=..., end=...), @@ -573,15 +432,15 @@ def get_parents(self, red, pos): """ parents = [] - current = red.find_by_position(pos) + current = redlib.get_node_of_region(red, start, end) while current != red: region = current.absolute_bounding_box nodetype = current.type - tl = Position(*region.top_left.to_tuple()) - br = Position(*region.bottom_right.to_tuple()) + tl = redlib.Position(*region.top_left.to_tuple()) + br = redlib.Position(*region.bottom_right.to_tuple()) current = current.parent # if previous bounding box is the same take the parent higher in the hierachy if parents and parents[-1][1] == tl and parents[-1][2] == br: parents.pop() - parents.append(Parent(nodetype, tl, br)) + parents.append(redlib.Parent(nodetype, tl, br)) return parents diff --git a/src/reddel_server/validators.py b/src/reddel_server/validators.py index 9f975a5..fd71ad9 100644 --- a/src/reddel_server/validators.py +++ b/src/reddel_server/validators.py @@ -3,11 +3,15 @@ import abc +import redbaron import six from . import exceptions +from . import redlib -__all__ = ['BaronTypeValidator', 'ValidatorInterface'] + +__all__ = ['ValidatorInterface', 'OptionalRegionValidator', 'MandatoryRegionValidator', + 'SingleNodeValidator', 'TypeValidator'] class ValidatorInterface(six.with_metaclass(abc.ABCMeta, object)): @@ -28,13 +32,15 @@ class ValidatorInterface(six.with_metaclass(abc.ABCMeta, object)): import reddel_server class MyValidator(reddel_server.ValidatorInterface): - def __call__(self, red): + def __call__(self, red, start=None, end=None): + if not (start and end): + raise reddel_server.ValidationException("Expected a region.") if len(red) != 1: raise reddel_server.ValidationException("Expected only a single root node.") val = MyValidator() - val(redbaron.RedBaron("a=2")) + val(redbaron.RedBaron("a=2"), reddel_server.Position(1, 1), reddel_server.Position(1, 3)) try: val(redbaron.RedBaron("a=2+1\\nb=3")) @@ -47,30 +53,170 @@ def __call__(self, red): This transformation is used in :func:`reddel_server.red_validate`. """ @abc.abstractmethod - def __call__(self, red): # pragma: no cover + def __call__(self, red, start=None, end=None): # pragma: no cover """Validate the given redbaron source :param red: the source :type red: :class:`redbaron.RedBaron` + :param start: the start position of the selected region, if any. + :type start: :class:`reddel_server.Position` | ``None`` + :param end: the end position of the selected region, if any. + :type end: :class:`reddel_server.Position` | ``None`` :raises: :class:`ValidationException ` """ pass - def transform(self, red): # pragma: no cover + def transform(self, red, start=None, end=None): # pragma: no cover """Transform the given red baron The base implementation just returns the source. - See :meth:`reddel_server.BaronTypeValidator.transform` for an example. + See :meth:`reddel_server.TypeValidator.transform` for an example. :param red: a red baron source or other nodes - :returns: the transformed source + :param start: the start position of the selected region, if any. + :type start: :class:`reddel_server.Position` | ``None`` + :param end: the end position of the selected region, if any. + :type end: :class:`reddel_server.Position` | ``None`` + :returns: the transformed source, start and end """ - return red + return red, start, end + +class OptionalRegionValidator(ValidatorInterface): + """Used for functions that either use the given source code or only the + specified region if a region is specified. -class BaronTypeValidator(ValidatorInterface): - """Validate that the given :class:`redbaron.RedBaron` - contains the correct nodes and optionally that there is only a single root node. + If a region is specified the source is transformed to only contain the region. + + Examples: + + .. doctest:: + + >>> from redbaron import RedBaron + >>> import reddel_server + >>> val1 = reddel_server.OptionalRegionValidator() + >>> src, start, end = val1.transform(RedBaron('def foo(): pass'), start=None, end=None) + >>> src.dumps(), start, end + ('def foo(): pass\\n', None, None) + >>> src, start, end = val1.transform(RedBaron('a=1\\nb=1'), start=(2,1), end=(2,3)) + >>> src.dumps(), start, end + ('b=1', Position(row=1, column=1), Position(row=1, column=3)) + + """ + def __call__(self, red, start=None, end=None): + """Validate the given redbaron source + + :param red: the source + :type red: :class:`redbaron.RedBaron` + :param start: the start position of the selected region, if any. + :type start: :class:`reddel_server.Position` | ``None`` + :param end: the end position of the selected region, if any. + :type end: :class:`reddel_server.Position` | ``None`` + :raises: :class:`ValidationException ` + """ + return super(OptionalRegionValidator, self).__call__(red, start, end) + + def transform(self, red, start=None, end=None): + """Extract the region from red if any region is specified + + :param red: a red baron source or other nodes + :param start: the start position of the selected region, if any. + :type start: :class:`reddel_server.Position` | ``None`` + :param end: the end position of the selected region, if any. + :type end: :class:`reddel_server.Position` | ``None`` + :returns: the transformed source, start and end + """ + red, start, end = super(OptionalRegionValidator, self).transform(red, start, end) + if start and end: + red = redlib.get_node_of_region(red, start, end) + bbox = red.bounding_box + start = redlib.Position(*bbox.top_left.to_tuple()) + end = redlib.Position(*bbox.bottom_right.to_tuple()) + return red, start, end + + +class MandatoryRegionValidator(ValidatorInterface): + """Used for functions that expect a region""" + def __call__(self, red, start=None, end=None): + """Validate that a region is specified + + :param red: the source + :type red: :class:`redbaron.RedBaron` + :param start: the start position of the selected region, if any. + :type start: :class:`reddel_server.Position` | ``None`` + :param end: the end position of the selected region, if any. + :type end: :class:`reddel_server.Position` | ``None`` + :raises: :class:`ValidationException ` if start or end is ``None``. + """ + super(MandatoryRegionValidator, self).__call__(red, start, end) + if not (start and end): + raise exceptions.ValidationException("No region specified.") + + +class SingleNodeValidator(ValidatorInterface): + """Validate that only one single node is provided. + + If a list of nodes is provided, validate that it contains only one element. + Transform the source to only a single node. + + .. doctest:: + + >>> from redbaron import RedBaron + >>> import reddel_server + >>> val1 = reddel_server.SingleNodeValidator() + >>> val1(redbaron.RedBaron("a=1+1")) + >>> val1.transform(redbaron.RedBaron("a=1+1")) + (a=1+1, None, None) + >>> try: + ... val1(redbaron.RedBaron("a=1+1\\nb=2")) + ... except reddel_server.ValidationException: + ... pass + ... else: + ... assert False, "Validator should have raised" + + By default, when creating a :class:`redbaron.RedBaron` source, + you always get a list even for a single expression. + If you always want the single node, this validator will handle the transformation. + """ + def __call__(self, red, start=None, end=None): + """Validate the given redbaron source + + :param red: the source + :type red: :class:`redbaron.RedBaron` + :param start: the start position of the selected region, if any. + :type start: :class:`reddel_server.Position` | ``None`` + :param end: the end position of the selected region, if any. + :type end: :class:`reddel_server.Position` | ``None`` + :raises: :class:`ValidationException ` + """ + super(SingleNodeValidator, self).__call__(red, start, end) + if start and end: + red = redlib.get_node_of_region(red, start, end) + if isinstance(red, (redbaron.NodeList, redbaron.ProxyList)) and len(red) > 1: + raise exceptions.ValidationException("Expected single node but got: %s" % red) + + def transform(self, red, start=None, end=None): + """Extract the single node red is a list + + :param red: a red baron source or other nodes + :param start: the start position of the selected region, if any. + :type start: :class:`reddel_server.Position` | ``None`` + :param end: the end position of the selected region, if any. + :type end: :class:`reddel_server.Position` | ``None`` + :returns: the transformed source, start and end + """ + red, start, end = super(SingleNodeValidator, self).transform(red, start, end) + if not (start and end) and isinstance(red, (redbaron.NodeList, redbaron.ProxyList)): + red = red[0] + return red, start, end + + +class TypeValidator(ValidatorInterface): + """Validate that the given source contains the correct type of nodes. + + If a region is specified, only the region is checked. + If a list of nodes is given, e.g. a :class:`redbaron.RedBaron` object, + all nodes will be checked. Examples: @@ -79,74 +225,49 @@ class BaronTypeValidator(ValidatorInterface): from redbaron import RedBaron import reddel_server - val1 = reddel_server.BaronTypeValidator(['def'], single=True) + val1 = reddel_server.TypeValidator(['def']) + # valid val1(RedBaron('def foo(): pass')) + val1(RedBaron('def foo(): pass\\ndef bar(): pass')) + # invalid - invalid_sources = [RedBaron('def foo(): pass\\ndef bar(): pass'), # two are invalid - RedBaron('a=1')] # not a function definition - for src in invalid_sources: - try: - val1(src) - except reddel_server.ValidationException: - pass - else: - assert False, "Validator should have raised: %s" % src + try: + val1(RedBaron('def foo(): pass\\na=1+1')) + except reddel_server.ValidationException: + pass + else: + assert False, "Validator should have raised" """ - def __init__(self, identifiers, single=False): + def __init__(self, identifiers): """Initialize the validator :param identifiers: allowed identifiers for the redbaron source :type identifiers: sequence of :class:`str` - :param single: If more than one top level node is valid - :type single: :class:`bool` """ + super(TypeValidator, self).__init__() self.identifiers = identifiers - self.single = single - """True if only a single root node is accepted as input source. - :meth:`reddel_server.BaronTypeValidator.transform` will then return - the first node. - """ - def __call__(self, red): + def __call__(self, red, start=None, end=None): """Validate the given redbaron source + :param red: the source + :type red: :class:`redbaron.RedBaron` + :param start: the start position of the selected region, if any. + :type start: :class:`reddel_server.Position` | ``None`` + :param end: the end position of the selected region, if any. + :type end: :class:`reddel_server.Position` | ``None`` :raises: :class:`ValidationException ` """ - if self.single: - count = len(red) - if count != 1: - raise exceptions.ValidationException("Expected a single node but got %s" % count) + super(TypeValidator, self).__call__(red, start, end) + if start and end: + red = redlib.get_node_of_region(red, start, end) + if not isinstance(red, (redbaron.NodeList, redbaron.ProxyList)): + red = (red,) + for node in red: identifiers = node.generate_identifiers() if not any(i in identifiers for i in self.identifiers): raise exceptions.ValidationException("Expected identifier %s but got %s" % (self.identifiers, identifiers)) - - def transform(self, red): - """When :data:`reddel_server.BaronTypeValidator.single` is ``True`` return the first node. - - When creating a :class:`redbaron.RedBaron` from a source, - the root is a list of nodes: - - .. doctest:: - - >>> import redbaron - >>> src = redbaron.RedBaron("a=1") - >>> src - 0 a=1 - - - When :data:`reddel_server.BaronTypeValidator.single` is True - this function will return: - - .. doctest:: - - >>> src[0] - a=1 - - """ - if self.single: - return red[0] - return red diff --git a/tests/test_redlib.py b/tests/test_redlib.py new file mode 100644 index 0000000..8f5fb28 --- /dev/null +++ b/tests/test_redlib.py @@ -0,0 +1,98 @@ +"""Test redbaron specific library functions""" +import types + +import redbaron + +import reddel_server + + +def test_get_parents(): + """Test retrieving all parents from a node""" + testsrc = redbaron.RedBaron("def foo(): a = 1+1") + testnode = testsrc.find_by_position((1, 18)) + parents = reddel_server.get_parents(testnode) + expected = [testnode.parent, testnode.parent.parent, testnode.parent.parent.parent, + testnode.parent.parent.parent.parent] + assert expected == list(parents) + + +def test_get_parents_generator(): + """Test that the function returns a generator""" + testsrc = redbaron.RedBaron("1+1") + parentgen = reddel_server.get_parents(testsrc) + assert isinstance(parentgen, types.GeneratorType) + + +def test_get_node_of_region_bad_region(): + """Test get_node_of_region with an invalid region""" + testsrc = redbaron.RedBaron("1+1") + start = reddel_server.Position(0, 1) + end = reddel_server.Position(1, 3) + assert testsrc == reddel_server.get_node_of_region(testsrc, start, end) + +def test_get_node_of_region_simple(): + """Test get_node_of_region for when start and end are part of a simple expression""" + testsrc = redbaron.RedBaron("1+1") + start = reddel_server.Position(1, 1) + end = reddel_server.Position(1, 3) + expected = testsrc[0] + assert expected == reddel_server.get_node_of_region(testsrc, start, end) + +def test_get_node_of_region_same(): + """Test get_node_of_region for when start and end are the same nodes""" + testsrc = redbaron.RedBaron("lambda: 1+1") + start = reddel_server.Position(1, 1) + end = reddel_server.Position(1, 2) + expected = testsrc[0] + assert expected == reddel_server.get_node_of_region(testsrc, start, end) + + +def test_get_node_of_region_same_level(): + """Test get_node_of_region for when the nodes for start and end are on the same level""" + testsrc = redbaron.RedBaron("1+1\n2*2") + start = reddel_server.Position(1, 2) + end = reddel_server.Position(2, 2) + assert reddel_server.get_node_of_region(testsrc, start, end).dumps() == testsrc.dumps() + + +def test_get_node_of_region_different_level(): + """Test get_node_of_region for when the nodes for start and end are on different levels""" + testsrc = redbaron.RedBaron("def foo(arg): a = 1+1") + start = reddel_server.Position(1, 9) # "arg" + end = reddel_server.Position(1, 20) # "+" + assert reddel_server.get_node_of_region(testsrc, start, end) == testsrc[0] # the function definition + + +def test_get_node_of_region_slice_list(): + """Test get_node_of_region for when the nodes are only a slice in a node list""" + testsrc = redbaron.RedBaron("1+1\n" + "a=1\n" + "for i in range(10):\n" + " b=i\n" + "c=3") + start = reddel_server.Position(2, 3) # a="1" + end = reddel_server.Position(4, 6) # b"="1 + expected = redbaron.NodeList(testsrc.node_list[2:5]) + assert expected.dumps() == reddel_server.get_node_of_region(testsrc, start, end).dumps() + + +def test_get_node_of_region_slice_for_loop(): + """Test get_node_of_region for when the nodes slice a for loop body""" + testsrc = redbaron.RedBaron("for i in range(10):\n" + " a = 1 + 2\n" + " b = 4\n" + " c = 5\n" + " d = 7\n") + start = reddel_server.Position(3, 7) + end = reddel_server.Position(5, 8) + expected = "b = 4\n c = 5\n d = 7" + assert expected == reddel_server.get_node_of_region(testsrc, start, end).dumps() + + +def test_get_node_of_region_slice_list_declaration(): + """Test get_node_of_region for when the nodes slice a list declaration""" + testsrc = redbaron.RedBaron("[1, 2, 3, 4, 5, 6]") + start = reddel_server.Position(1, 8) + end = reddel_server.Position(1, 14) + expected = "3, 4, 5" + assert expected == reddel_server.get_node_of_region(testsrc, start, end).dumps() diff --git a/tests/test_redprovider.py b/tests/test_redprovider.py index c429700..5320ccf 100644 --- a/tests/test_redprovider.py +++ b/tests/test_redprovider.py @@ -15,98 +15,6 @@ ] -def test_get_parents(): - """Test retrieving all parents from a node""" - testsrc = redbaron.RedBaron("def foo(): a = 1+1") - testnode = testsrc.find_by_position((1, 18)) - parents = reddel_server.get_parents(testnode) - expected = [testnode.parent, testnode.parent.parent, testnode.parent.parent.parent, - testnode.parent.parent.parent.parent] - assert expected == list(parents) - - -def test_get_parents_generator(): - """Test that the function returns a generator""" - testsrc = redbaron.RedBaron("1+1") - parentgen = reddel_server.get_parents(testsrc) - assert isinstance(parentgen, types.GeneratorType) - - -def test_get_node_of_region_bad_region(): - """Test get_node_of_region with an invalid region""" - testsrc = redbaron.RedBaron("1+1") - start = reddel_server.Position(0, 1) - end = reddel_server.Position(1, 3) - assert testsrc == reddel_server.get_node_of_region(testsrc, start, end) - -def test_get_node_of_region_simple(): - """Test get_node_of_region for when start and end are part of a simple expression""" - testsrc = redbaron.RedBaron("1+1") - start = reddel_server.Position(1, 1) - end = reddel_server.Position(1, 3) - expected = testsrc[0] - assert expected == reddel_server.get_node_of_region(testsrc, start, end) - -def test_get_node_of_region_same(): - """Test get_node_of_region for when start and end are the same nodes""" - testsrc = redbaron.RedBaron("lambda: 1+1") - start = reddel_server.Position(1, 1) - end = reddel_server.Position(1, 2) - expected = testsrc[0] - assert expected == reddel_server.get_node_of_region(testsrc, start, end) - - -def test_get_node_of_region_same_level(): - """Test get_node_of_region for when the nodes for start and end are on the same level""" - testsrc = redbaron.RedBaron("1+1\n2*2") - start = reddel_server.Position(1, 2) - end = reddel_server.Position(2, 2) - assert reddel_server.get_node_of_region(testsrc, start, end).dumps() == testsrc.dumps() - - -def test_get_node_of_region_different_level(): - """Test get_node_of_region for when the nodes for start and end are on different levels""" - testsrc = redbaron.RedBaron("def foo(arg): a = 1+1") - start = reddel_server.Position(1, 9) # "arg" - end = reddel_server.Position(1, 20) # "+" - assert reddel_server.get_node_of_region(testsrc, start, end) == testsrc[0] # the function definition - - -def test_get_node_of_region_slice_list(): - """Test get_node_of_region for when the nodes are only a slice in a node list""" - testsrc = redbaron.RedBaron("1+1\n" - "a=1\n" - "for i in range(10):\n" - " b=i\n" - "c=3") - start = reddel_server.Position(2, 3) # a="1" - end = reddel_server.Position(4, 6) # b"="1 - expected = redbaron.NodeList(testsrc.node_list[2:5]) - assert expected.dumps() == reddel_server.get_node_of_region(testsrc, start, end).dumps() - - -def test_get_node_of_region_slice_for_loop(): - """Test get_node_of_region for when the nodes slice a for loop body""" - testsrc = redbaron.RedBaron("for i in range(10):\n" - " a = 1 + 2\n" - " b = 4\n" - " c = 5\n" - " d = 7\n") - start = reddel_server.Position(3, 7) - end = reddel_server.Position(5, 8) - expected = "b = 4\n c = 5\n d = 7" - assert expected == reddel_server.get_node_of_region(testsrc, start, end).dumps() - - -def test_get_node_of_region_slice_list_declaration(): - """Test get_node_of_region for when the nodes slice a list declaration""" - testsrc = redbaron.RedBaron("[1, 2, 3, 4, 5, 6]") - start = reddel_server.Position(1, 8) - end = reddel_server.Position(1, 14) - expected = "3, 4, 5" - assert expected == reddel_server.get_node_of_region(testsrc, start, end).dumps() - - def test_redwraps_add_attrs(): """Test that attibutes are added.""" @reddel_server.redwraps(None) @@ -185,7 +93,7 @@ def foo(self, src): def test_red_validate_attr(): """Test that the validators attribute is applied.""" - validator = reddel_server.BaronTypeValidator(["def"], single=True) + validator = reddel_server.OptionalRegionValidator() @reddel_server.red_validate([validator]) def foo(*args, **kwargs): pass # pragma: nocover @@ -195,37 +103,37 @@ def foo(*args, **kwargs): def test_red_validate_invalid(): """Test that invalid values raise""" - validator = reddel_server.BaronTypeValidator(["def"]) + validator = reddel_server.TypeValidator(['def']) @reddel_server.red_validate([validator]) - def foo(self, src): + def foo(self, src, start, end): pass # pragma: nocover src = redbaron.RedBaron("1+1") with pytest.raises(reddel_server.ValidationException): - foo(None, src) + foo(None, src, None, None) def test_red_validate_valid(): """Test that valid values pass""" - validator = reddel_server.BaronTypeValidator(["def"], single=True) + validator = reddel_server.TypeValidator(["def"]) @reddel_server.red_validate([validator]) - def foo(self, src): + def foo(self, src, start, end): pass src = redbaron.RedBaron("def foo(): pass") - foo(None, src) + foo(None, src, None, None) def test_red_validate_transform(): """Test that values get transformed""" - validator = reddel_server.BaronTypeValidator(["def"], single=True) + validator = reddel_server.SingleNodeValidator() src = redbaron.RedBaron("def foo(): pass") @reddel_server.red_validate([validator]) - def foo(self, src): + def foo(self, src, start, end): return src - transformed = foo(None, src) + transformed = foo(None, src, None, None) assert src[0] == transformed @@ -265,7 +173,7 @@ def test_RedBaronProvider_rename_arg(redbaronprovider): newarg = 1 + newarg return newarg\n""" - assert redbaronprovider.rename_arg(src, "arg1", "newarg") == expected + assert redbaronprovider.rename_arg(src, None, None, "arg1", "newarg") == expected def test_RedBaronProvider_rename_arg_raise(redbaronprovider): @@ -276,7 +184,7 @@ def test_RedBaronProvider_rename_arg_raise(redbaronprovider): return arg1""" with pytest.raises(ValueError): - redbaronprovider.rename_arg(src, "arg", "newarg") + redbaronprovider.rename_arg(src, None, None, "arg", "newarg") def test_RedBaronProvider_get_args(redbaronprovider): @@ -288,13 +196,13 @@ def test_RedBaronProvider_get_args(redbaronprovider): ("kwarg2", '3'), ("kwarg3", "None")] - assert redbaronprovider.get_args(src) == expected + assert redbaronprovider.get_args(src, None, None) == expected def test_RedBaronProvider_add_arg(redbaronprovider): src = "def foo(arg1): pass\n" expected = "def foo(arg1, kwarg2=None): pass\n" - assert redbaronprovider.add_arg(src, 1, "kwarg2=None") == expected + assert expected == redbaronprovider.add_arg(src, None, None, 1, "kwarg2=None") def test_RedBaronProvider_get_parents(redbaronprovider): @@ -310,4 +218,5 @@ def bar(): ('def', (3, 9), (6, 0)), ('ifelseblock', (2, 5), (6, 0)), ('def', (1, 1), (6, 0))] - assert redbaronprovider.get_parents(src, reddel_server.Position(5, 21)) == expected + result = redbaronprovider.get_parents(src, reddel_server.Position(5, 21), reddel_server.Position(5, 21)) + assert expected == result diff --git a/tests/test_validators.py b/tests/test_validators.py index 1affd96..7eff71b 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -4,39 +4,165 @@ import reddel_server -DEF_SRC = """def foo(bar): - spam = bar + 1 - return spam -""" - -DEF_SRC2 = DEF_SRC + """ -def foo2(bar): - pass -""" - -@pytest.mark.parametrize('identifiers,single,src,valid', [ - [['def', 'defnode'], True, DEF_SRC, True], - [['def', 'defnode'], True, DEF_SRC2, False], - [['name'], True, DEF_SRC, False], - [['def'], False, DEF_SRC2, True] -]) -def test_barontype_call(identifiers, single, src, valid): - val = reddel_server.BaronTypeValidator(identifiers, single=single) - red = redbaron.RedBaron(src) - if valid: - val(red) - else: - with pytest.raises(reddel_server.ValidationException): - val(red) - -def test_barontype_transform_single(): - val = reddel_server.BaronTypeValidator((), single=True) - red = redbaron.RedBaron(DEF_SRC) - transformed = val.transform(red) - assert transformed is red[0] - -def test_barontype_transform_singleOff(): - val = reddel_server.BaronTypeValidator((), single=False) - red = redbaron.RedBaron(DEF_SRC) - transformed = val.transform(red) - assert transformed is red + +def test_OptionalRegionValidator_call(): + """Test that it's valid to not specify a region""" + testsrc = redbaron.RedBaron("1+1") + validator = reddel_server.OptionalRegionValidator() + validator(testsrc) + + +def test_OptionalRegionValidator_transform_region(): + """Test that the region is extracted when specified""" + testsrc = redbaron.RedBaron("a=1+1\nb=5") + start = reddel_server.Position(1, 1) + end = reddel_server.Position(1, 4) + validator = reddel_server.OptionalRegionValidator() + result = validator.transform(testsrc, start, end) + expected = (testsrc[0], reddel_server.Position(1, 1), reddel_server.Position(1, 5)) + assert expected == result, "Expected that the region was extracted and the bounding box was updated." + + +def test_OptionalRegionValidator_transform_no_region(): + """Test that there is no tranformation without any region""" + testsrc = redbaron.RedBaron("a=1+1\nb=5") + validator = reddel_server.OptionalRegionValidator() + result = validator.transform(testsrc) + expected = (testsrc, None, None) + assert expected == result + + +def test_MandatoryRegionValidator_no_region(): + """Test that the validator raises without a region""" + testsrc = redbaron.RedBaron("1+1") + validator = reddel_server.MandatoryRegionValidator() + with pytest.raises(reddel_server.ValidationException): + validator(testsrc, start=(1, 1), end=None) + + +def test_MandatoryRegionValidator_region(): + """Test that there has to be a region""" + testsrc = redbaron.RedBaron("1+1") + validator = reddel_server.MandatoryRegionValidator() + validator(testsrc, start=reddel_server.Position(1, 1), end=reddel_server.Position(1, 3)) + + +def test_SingleNodeValidator_no_region_invalid(): + """Test that the validator raises when there is more than one node and no region""" + testsrc = redbaron.RedBaron("1+1\n2+2") + validator = reddel_server.SingleNodeValidator() + with pytest.raises(reddel_server.ValidationException): + validator(testsrc) + + +def test_SingleNodeValidator_no_region_valid(): + """Test that the validator does not raise when there is one node and no region""" + testsrc = redbaron.RedBaron("1+1") + validator = reddel_server.SingleNodeValidator() + validator(testsrc) + + +def test_SingleNodeValidator_no_region_single_node_valid(): + """Test that the validator does not raise when there is one node and no region""" + testsrc = redbaron.RedBaron("for i in range(10):\n\ta=1\n\tb=2")[0] + validator = reddel_server.SingleNodeValidator() + validator(testsrc) + + +def test_SingleNodeValidator_region_invalid(): + """Test that the validator raises when there is more than one node in the region""" + testsrc = redbaron.RedBaron("1+1\n2+2") + validator = reddel_server.SingleNodeValidator() + with pytest.raises(reddel_server.ValidationException): + validator(testsrc, start=reddel_server.Position(1, 1), end=reddel_server.Position(2, 3)) + + +def test_SingleNodeValidator_region_valid(): + """Test that the validator does not raise when there is one node in the region""" + testsrc = redbaron.RedBaron("1+1\n2+2") + validator = reddel_server.SingleNodeValidator() + validator(testsrc, start=reddel_server.Position(2, 1), end=reddel_server.Position(2, 3)) + + +def test_SingleNodeValidator_transform_no_region_no_list(): + """Test that there is no transformation if there is no list""" + testsrc = redbaron.RedBaron("1+1")[0] + validator = reddel_server.SingleNodeValidator() + assert (testsrc, None, None) == validator.transform(testsrc, start=None, end=None) + + +def test_SingleNodeValidator_transform_region_no_list(): + """Test that there is no transformation if there is a region""" + testsrc = redbaron.RedBaron("1+1") + validator = reddel_server.SingleNodeValidator() + expected = (testsrc, (1, 1), (1, 3)) + assert expected == validator.transform(testsrc, start=reddel_server.Position(1, 1), + end=reddel_server.Position(1, 3)) + + +def test_SingleNodeValidator_transform_no_region_list(): + """Test the transformation when there is no region""" + testsrc = redbaron.RedBaron("1+1") + validator = reddel_server.SingleNodeValidator() + expected = "1+1" + assert expected == validator.transform(testsrc)[0].dumps() + + +def test_TypeValidator_valid_no_region_no_list(): + """Test a valid source that is not a list without a region""" + testsrc = redbaron.RedBaron("def foo(): pass")[0] + validator = reddel_server.TypeValidator(['def']) + validator(testsrc) + + +def test_TypeValidator_valid_no_region_list(): + """Test a valid source that is a list without a region""" + testsrc = redbaron.RedBaron("def foo(): pass\ndef bar(): pass") + validator = reddel_server.TypeValidator(['def']) + validator(testsrc) + + +def test_TypeValidator_valid_region_list(): + """Test a valid source that is a list with a region""" + testsrc = redbaron.RedBaron("a=1\ndef foo(): pass\ndef bar(): pass") + validator = reddel_server.TypeValidator(['def']) + validator(testsrc, start=reddel_server.Position(2, 1), end=reddel_server.Position(3, 1)) + + +def test_TypeValidator_valid_region_no_list(): + """Test a valid source where the region specifies a single node""" + testsrc = redbaron.RedBaron("a=1\ndef foo(): pass\nb=2") + validator = reddel_server.TypeValidator(['def']) + validator(testsrc, start=reddel_server.Position(2, 1), end=reddel_server.Position(2, 1)) + + +def test_TypeValidator_invalid_no_region_no_list(): + """Test that the validator raises for invalid sources without a region and list""" + testsrc = redbaron.RedBaron("1+1")[0] + validator = reddel_server.TypeValidator(['def']) + with pytest.raises(reddel_server.ValidationException): + validator(testsrc) + + +def test_TypeValidator_invalid_no_region_list(): + """Test that the validator raises for invalid sources without a region but a list""" + testsrc = redbaron.RedBaron("def foo(): pass\na=1") + validator = reddel_server.TypeValidator(['def']) + with pytest.raises(reddel_server.ValidationException): + validator(testsrc) + + +def test_TypeValidator_invalid_region_list(): + """Test that the validator raises for invalid sources with a region and list""" + testsrc = redbaron.RedBaron("def foo():\n\ta=1\n\tdef bar(): pass") + validator = reddel_server.TypeValidator(['def']) + with pytest.raises(reddel_server.ValidationException): + validator(testsrc, start=reddel_server.Position(2, 3), end=reddel_server.Position(3, 3)) + + +def test_TypeValidator_invalid_region_no_list(): + """Test that the validator raises for invalid sources with a region and no list""" + testsrc = redbaron.RedBaron("def foo():\n\ta=1") + validator = reddel_server.TypeValidator(['def']) + with pytest.raises(reddel_server.ValidationException): + validator(testsrc, start=reddel_server.Position(2, 3), end=reddel_server.Position(2, 4))