11import re
2+ from typing import List
23
34from 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 )
0 commit comments