Skip to content
7 changes: 5 additions & 2 deletions rewrite/rewrite/python/format/auto_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

from .blank_lines import BlankLinesVisitor
from .normalize_format import NormalizeFormatVisitor
from .normalize_line_breaks_visitor import NormalizeLineBreaksVisitor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we still need to call the visitor.

from .normalize_tabs_or_spaces import NormalizeTabsOrSpacesVisitor
from .remove_trailing_whitespace_visitor import RemoveTrailingWhitespaceVisitor
from .spaces_visitor import SpacesVisitor
from .normalize_tabs_or_spaces import NormalizeTabsOrSpacesVisitor
from .. import TabsAndIndentsStyle
from .. import TabsAndIndentsStyle, GeneralFormatStyle
from ..style import BlankLinesStyle, SpacesStyle, IntelliJ
from ..visitor import PythonVisitor
from ... import Recipe, Tree, Cursor
Expand Down Expand Up @@ -33,5 +34,7 @@ def visit(self, tree: Optional[Tree], p: P, parent: Optional[Cursor] = None) ->
cu.get_style(TabsAndIndentsStyle) or IntelliJ.tabs_and_indents(),
self._stop_after
).visit(tree, p, self._cursor.fork())
tree = NormalizeLineBreaksVisitor(cu.get_style(GeneralFormatStyle) or GeneralFormatStyle(False),
self._stop_after).visit(tree, p, self._cursor.fork())
tree = RemoveTrailingWhitespaceVisitor(self._stop_after).visit(tree, self._cursor.fork())
return tree
67 changes: 67 additions & 0 deletions rewrite/rewrite/python/format/normalize_line_breaks_visitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from __future__ import annotations

from typing import Optional, TypeVar, Union

from rewrite import Tree, P, Cursor, list_map
from rewrite.java import J, Space, Comment, TextComment
from rewrite.python import PythonVisitor, PySpace, GeneralFormatStyle, PyComment
from rewrite.visitor import T

J2 = TypeVar('J2', bound=J)


class NormalizeLineBreaksVisitor(PythonVisitor):
def __init__(self, style: GeneralFormatStyle, stop_after: Tree = None):
self._stop_after = stop_after
self._stop = False
self._style = style

def visit_space(self, space: Optional[Space], loc: Optional[Union[PySpace.Location, Space.Location]],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here we will need an override for visit_marker, because the base visitor class doesn't know about specific markers and thus won't visit the Space of TrailingComma.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test case with a trailing comma for this visitor? I don't see the visit_marker override anywhere.

p: P) -> Space:
if not space or space is Space.EMPTY or not space.whitespace:
return space
s = space.with_whitespace(_normalize_new_lines(space.whitespace, self._style.use_crlf_new_lines))

def process_comment(comment: Comment) -> Comment:
if comment.multiline:
if isinstance(comment, PyComment):
comment = comment.with_suffix(_normalize_new_lines(comment.suffix, self._style.use_crlf_new_lines))
# TODO: Call PyComment Visitor, but this is not implemented yet....
return comment
elif isinstance(comment, TextComment):
comment = comment.with_text(_normalize_new_lines(comment.text, self._style.use_crlf_new_lines))

return comment.with_suffix(_normalize_new_lines(comment.suffix, self._style.use_crlf_new_lines))

return s.with_comments(list_map(process_comment, s.comments))

def post_visit(self, tree: T, _: object) -> Optional[T]:
if self._stop_after and tree == self._stop_after:
self._stop = True
return tree

def visit(self, tree: Optional[Tree], p: P, parent: Optional[Cursor] = None) -> Optional[T]:
return tree if self._stop else super().visit(tree, p, parent)


STR = TypeVar('STR', bound=Optional[str])


def _normalize_new_lines(text: STR, use_crlf: bool) -> STR:
"""
Normalize the line breaks in the given text to either use of CRLF or LF.
:param text: The text to normalize.
:param use_crlf: Whether to use CRLF line breaks.
:return: The text with normalized line breaks.
"""
if text is None or '\n' not in text:
return text

normalized = []
for i, c in enumerate(text):
if use_crlf and c == '\n' and (i == 0 or text[i - 1] != '\r'):
normalized.append('\r\n')
elif use_crlf or c != '\r':
normalized.append(c)
return ''.join(normalized)
14 changes: 13 additions & 1 deletion rewrite/rewrite/python/style.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations

from abc import ABC
from dataclasses import dataclass, replace

from ..style import Style, NamedStyles
Expand Down Expand Up @@ -450,6 +449,19 @@ def with_minimum(self, minimum: Minimum) -> BlankLinesStyle:
return self if minimum is self._minimum else replace(self, _minimum=minimum)


@dataclass(frozen=True)
class GeneralFormatStyle(PythonStyle):
_use_crlf_new_lines: bool

@property
def use_crlf_new_lines(self) -> bool:
return self._use_crlf_new_lines

def with_use_crlf_new_lines(self, use_crlf_new_lines: bool) -> GeneralFormatStyle:
return self if use_crlf_new_lines is self._use_crlf_new_lines else replace(self,
_use_crlf_new_lines=use_crlf_new_lines)


class IntelliJ(NamedStyles):
@classmethod
def spaces(cls) -> SpacesStyle:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import unittest

from rewrite.python import GeneralFormatStyle
from rewrite.python.format.normalize_line_breaks_visitor import NormalizeLineBreaksVisitor
from rewrite.test import from_visitor, RecipeSpec, rewrite_run, python


class TestNormalizeLineBreaksVisitor(unittest.TestCase):

def setUp(self):
# language=python
self.windows = (
"class Test:\r\n"
" # some comment\r\n"
" def test(self):\r\n"
" print()\r\n"
"\r\n"
)
# language=python
self.linux = (
"class Test:\n"
" # some comment\n"
" def test(self):\n"
" print()\n"
"\n"
)

@staticmethod
def normalize_line_breaks(use_crlf: bool) -> RecipeSpec:
style = GeneralFormatStyle(_use_crlf_new_lines=use_crlf)
return RecipeSpec().with_recipe(from_visitor(NormalizeLineBreaksVisitor(style)))

def test_windows_to_linux(self):
rewrite_run(
python(
self.windows,
self.linux
),
spec=self.normalize_line_breaks(use_crlf=False)
)

def test_linux_to_windows(self):
rewrite_run(
python(
self.linux,
self.windows
),
spec=self.normalize_line_breaks(use_crlf=True)
)
Loading