Skip to content

Commit 6703048

Browse files
bhirszmnojek
andauthored
Add option to align new lines to previous line in SplitTooLongLine transformer (#495)
* Add option to align new lines to previous line in SplitTooLongLine transformer Co-authored-by: Mateusz Nojek <matnojek@gmail.com>
1 parent d2ff40b commit 6703048

File tree

6 files changed

+175
-16
lines changed

6 files changed

+175
-16
lines changed

docs/releasenotes/4.0.0.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,40 @@ this using ``split_single_value`` parameter (default ``False``)::
226226

227227
> robotidy -c SplitTooLongLine:split_single_value=True
228228

229+
SplitTooLongLine and aligning new line (#484)
230+
------------------------------------------------
231+
232+
It is now possible to align new line to previous line when splitting too long line. This mode works only when we are
233+
filling the line until the line length limit (with one of the ``split_on_every_arg``, ``split_on_every_value`` and
234+
``split_on_every_setting_arg`` flags). To enable it configure it using ``align_new_line``::
235+
236+
> robotidy -c SplitTooLongLine:align_new_line=True
237+
238+
Following code::
239+
240+
*** Keywords ***
241+
Keyword
242+
[Tags] longertagname1 longertagname2 longertagname3
243+
Keyword With Longer Name ${arg1} ${arg2} ${arg3} # let's assume ${arg3} does not fit under limit
244+
245+
with ``align_new_line = False`` (default) is transformed to::
246+
247+
*** Keywords ***
248+
Keyword
249+
[Tags] longertagname1 longertagname2
250+
... longertagname3
251+
Keyword With Longer Name ${arg1} ${arg2}
252+
... ${arg3}
253+
254+
and with ``align_new_line = True`` is transformed to::
255+
256+
*** Keywords ***
257+
Keyword
258+
[Tags] longertagname1 longertagname2
259+
... longertagname3
260+
Keyword With Longer Name ${arg1} ${arg2}
261+
... ${arg3}
262+
229263
Spaces in the transformer name or configuration
230264
-------------------------------------------------
231265

@@ -234,3 +268,8 @@ enclose it with quotation marks::
234268

235269
> robotidy --load-transformer "C:\\My Transformers\\Transformer.py"
236270
> robotidy --configure CustomTransformer:value="param value"
271+
272+
Bugs
273+
-----
274+
275+
- Fixed the bug where keyword name would be prefixed with continuation marks (``...``) if name was longer than line length limit (#494)

docs/source/transformers/SplitTooLongLine.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,48 @@ to split on single too long values using ``split_single_value`` option::
229229
${SINGLE_HEADER}
230230
... veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery
231231
232+
Align new line
233+
----------------
234+
235+
It is possible to align new line to previous line when splitting too long line. This mode works only when we are
236+
filling the line until line the length limit (with one of the ``split_on_every_arg``, ``split_on_every_value`` and
237+
``split_on_every_setting_arg`` flags). To enable it configure it using ``align_new_line``::
238+
239+
> robotidy -c SplitTooLongLine:align_new_line=True
240+
241+
.. tab-set::
242+
243+
.. tab-item:: Before
244+
245+
.. code:: robotframework
246+
247+
*** Keywords ***
248+
Keyword
249+
[Tags] longertagname1 longertagname2 longertagname3
250+
Keyword With Longer Name ${arg1} ${arg2} ${arg3} # let's assume ${arg3} does not fit under limit
251+
252+
.. tab-item:: After (align_new_line = False)
253+
254+
.. code:: robotframework
255+
256+
*** Keywords ***
257+
Keyword
258+
[Tags] longertagname1 longertagname2
259+
... longertagname3
260+
Keyword With Longer Name ${arg1} ${arg2}
261+
... ${arg3}
262+
263+
.. tab-item:: After (align_new_line = True)
264+
265+
.. code:: robotframework
266+
267+
*** Keywords ***
268+
Keyword
269+
[Tags] longertagname1 longertagname2
270+
... longertagname3
271+
Keyword With Longer Name ${arg1} ${arg2}
272+
... ${arg3}
273+
232274
Ignore comments
233275
----------------
234276

robotidy/transformers/SplitTooLongLine.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import re
2+
from typing import List
23

34
from robot.api.parsing import Comment, Token
45

@@ -83,6 +84,7 @@ def __init__(
8384
split_on_every_value: bool = True,
8485
split_on_every_setting_arg: bool = True,
8586
split_single_value: bool = False,
87+
align_new_line: bool = False,
8688
skip: Skip = None,
8789
):
8890
super().__init__(skip)
@@ -91,6 +93,7 @@ def __init__(
9193
self.split_on_every_value = split_on_every_value
9294
self.split_on_every_setting_arg = split_on_every_setting_arg
9395
self.split_single_value = split_single_value
96+
self.align_new_line = align_new_line
9497
self.robocop_disabler_pattern = re.compile(
9598
r"(# )+(noqa|robocop: ?(?P<disabler>disable|enable)=?(?P<rules>[\w\-,]*))"
9699
)
@@ -228,7 +231,11 @@ def split_to_multiple_lines(tokens, indent, separator):
228231

229232
def split_tokens(self, tokens, line, split_on, indent=None):
230233
separator = Token(Token.SEPARATOR, self.formatting_config.separator)
231-
cont_indent = Token(Token.SEPARATOR, self.formatting_config.continuation_indent)
234+
align_new_line = self.align_new_line and not split_on
235+
if align_new_line:
236+
cont_indent = None
237+
else:
238+
cont_indent = Token(Token.SEPARATOR, self.formatting_config.continuation_indent)
232239
split_tokens, comments = [], []
233240
# Comments with separators inside them are split into
234241
# [COMMENT, SEPARATOR, COMMENT] tokens in the AST, so in order to preserve the
@@ -240,20 +247,13 @@ def split_tokens(self, tokens, line, split_on, indent=None):
240247
if token.type == Token.SEPARATOR:
241248
last_separator = token
242249
elif token.type == Token.COMMENT:
243-
# AST splits comments with separators, e.g.
244-
#
245-
# "# Comment rest" -> ["# Comment", " ", "rest"].
246-
#
247-
# Notice the third value not starting with a hash - that's what this
248-
# condition is about:
249-
if comments and not token.value.startswith("#"):
250-
comments[-1].value += last_separator.value + token.value
251-
else:
252-
comments.append(token)
250+
self.join_split_comments(comments, token, last_separator)
253251
elif token.type == Token.ARGUMENT:
254252
if token.value == "":
255253
token.value = "${EMPTY}"
256254
if split_on or not self.col_fit_in_line(line + [separator, token]):
255+
if align_new_line and cont_indent is None: # we are yet to calculate aligned indent
256+
cont_indent = Token(Token.SEPARATOR, self.calculate_align_separator(line))
257257
line.append(EOL)
258258
split_tokens.extend(line)
259259
if indent:
@@ -266,6 +266,28 @@ def split_tokens(self, tokens, line, split_on, indent=None):
266266
split_tokens.append(EOL)
267267
return split_tokens, comments
268268

269+
@staticmethod
270+
def join_split_comments(comments: List, token: Token, last_separator: Token):
271+
"""Join split comments when splitting line.
272+
AST splits comments with separators, e.g.
273+
"# Comment rest" -> ["# Comment", " ", "rest"].
274+
Notice the third value not starting with a hash - we need to join such comment with previous comment.
275+
"""
276+
if comments and not token.value.startswith("#"):
277+
comments[-1].value += last_separator.value + token.value
278+
else:
279+
comments.append(token)
280+
281+
def calculate_align_separator(self, line: List) -> str:
282+
"""Calculate width of the separator required to align new line to previous line."""
283+
if len(line) <= 2:
284+
# line only fits one column, so we don't have anything to align it for
285+
return self.formatting_config.continuation_indent
286+
first_data_token = next((token.value for token in line if token.type != Token.SEPARATOR), "")
287+
# Decrease by 3 for ... token
288+
align_width = len(first_data_token) + len(self.formatting_config.separator) - 3
289+
return align_width * " "
290+
269291
def split_variable_def(self, node):
270292
if len(node.value) < 2 and not self.split_single_value:
271293
return node
@@ -282,10 +304,11 @@ def split_keyword_call(self, node):
282304

283305
keyword = node.get_token(Token.KEYWORD)
284306
# check if assign tokens needs to be split too
285-
line = [indent, *self.join_on_separator(node.get_tokens(Token.ASSIGN), separator), keyword]
286-
if not self.col_fit_in_line(line):
307+
assign = node.get_tokens(Token.ASSIGN)
308+
line = [indent, *self.join_on_separator(assign, separator), keyword]
309+
if assign and not self.col_fit_in_line(line):
287310
head = [
288-
*self.split_to_multiple_lines(node.get_tokens(Token.ASSIGN), indent=indent, separator=cont_indent),
311+
*self.split_to_multiple_lines(assign, indent=indent, separator=cont_indent),
289312
indent,
290313
CONTINUATION,
291314
cont_indent,
@@ -294,7 +317,6 @@ def split_keyword_call(self, node):
294317
line = []
295318
else:
296319
head = []
297-
298320
tokens, comments = self.split_tokens(
299321
node.tokens[node.tokens.index(keyword) + 1 :], line, self.split_on_every_arg, indent
300322
)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
*** Settings ***
2+
Force Tags PS_CSMON_15838 PR_PPD PR_B450
3+
... PR_B650 PR_B850 PR_CS1000
4+
... PR_CS1000L PAR_ECG PAR_IP_1
5+
... PAR_SPO2
6+
7+
8+
*** Variables *** # variables are not aligned but it will be handled by AlignVariablesSection
9+
@{LIST}
10+
... valuevaluevaluevaluevalue
11+
... valuevaluevaluevaluevalue
12+
... valuevaluevaluevaluevalue
13+
14+
15+
*** Test Cases ***
16+
Testcase with a lot of tags
17+
[Tags] Tag1 Tag10 Tag11 Tag12
18+
... Tag13 Tag2 Tag3 Tag4
19+
... Tag5 Tag6 Tag7 Tag8 Tag9
20+
... Tag14 Tag15
21+
Fail
22+
23+
24+
*** Keywords ***
25+
Keyword
26+
Keyword With Longer Name ${arg1} ${arg2}
27+
... ${arg3}
28+
29+
Keyword Longer Than Limit
30+
This Keyword Goes Beyond Limit And Next Argument Should Be In New Line
31+
... ${arg}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
*** Settings ***
2+
Force Tags PS_CSMON_15838 PR_PPD PR_B450 PR_B650 PR_B850 PR_CS1000 PR_CS1000L PAR_ECG PAR_IP_1 PAR_SPO2
3+
4+
5+
*** Variables *** # variables are not aligned but it will be handled by AlignVariablesSection
6+
@{LIST} valuevaluevaluevaluevalue valuevaluevaluevaluevalue valuevaluevaluevaluevalue
7+
8+
9+
*** Test Cases ***
10+
Testcase with a lot of tags
11+
[Tags] Tag1 Tag10 Tag11 Tag12 Tag13 Tag2 Tag3 Tag4 Tag5 Tag6 Tag7 Tag8 Tag9 Tag14 Tag15
12+
Fail
13+
14+
15+
*** Keywords ***
16+
Keyword
17+
Keyword With Longer Name ${arg1} ${arg2} ${arg3}
18+
19+
Keyword Longer Than Limit
20+
This Keyword Goes Beyond Limit And Next Argument Should Be In New Line ${arg}

tests/atest/transformers/SplitTooLongLine/test_transformer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ def test_split_settings_feed_until_line_length_skip_comments(self):
131131
config=":split_on_every_setting_arg=False:skip_comments=True",
132132
)
133133

134-
135134
@pytest.mark.parametrize(
136135
"skip_config",
137136
[
@@ -155,3 +154,9 @@ def test_split_on_single_value(self):
155154
expected="variables_split_scalar.robot",
156155
config=":split_single_value=True:line_length=80",
157156
)
157+
158+
def test_align_new_lines(self):
159+
self.compare(
160+
source="align_new_line.robot",
161+
config=":align_new_line=True:split_on_every_arg=False:split_on_every_setting_arg=False:line_length=51",
162+
)

0 commit comments

Comments
 (0)