From b94494b5f806434c89fb7197a6654440feb2014f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Hirsz?= Date: Thu, 2 Jan 2025 18:19:50 +0100 Subject: [PATCH] Support for RF 7.2 GROUP syntax (#733) --- robotidy/api.py | 4 +- robotidy/app.py | 12 ++++- robotidy/cli.py | 23 ++++++++-- robotidy/config.py | 22 +++++++-- robotidy/disablers.py | 7 ++- robotidy/files.py | 7 ++- robotidy/skip.py | 16 ++++++- robotidy/transformers/AlignSettingsSection.py | 8 +++- .../transformers/AlignTemplatedTestCases.py | 8 +++- .../transformers/AlignVariablesSection.py | 16 ++++++- .../transformers/GenerateDocumentation.py | 19 ++++++-- robotidy/transformers/IndentNestedKeywords.py | 13 +++++- robotidy/transformers/InlineIf.py | 19 ++++++-- robotidy/transformers/NormalizeAssignments.py | 5 ++- robotidy/transformers/NormalizeNewLines.py | 2 +- .../NormalizeSectionHeaderName.py | 9 +++- robotidy/transformers/NormalizeSeparators.py | 9 +++- robotidy/transformers/NormalizeTags.py | 17 +++++-- robotidy/transformers/OrderSettings.py | 7 ++- robotidy/transformers/OrderSettingsSection.py | 6 ++- robotidy/transformers/RemoveEmptySettings.py | 5 ++- robotidy/transformers/RenameKeywords.py | 5 ++- robotidy/transformers/RenameTestCases.py | 1 + robotidy/transformers/RenameVariables.py | 17 +++++-- robotidy/transformers/ReplaceEmptyValues.py | 7 ++- robotidy/transformers/ReplaceReturns.py | 4 +- robotidy/transformers/ReplaceWithVAR.py | 45 ++++++++++++++++--- robotidy/transformers/SmartSortKeywords.py | 7 ++- robotidy/transformers/SplitTooLongLine.py | 29 ++++++++++-- robotidy/transformers/Translate.py | 6 ++- robotidy/transformers/__init__.py | 16 +++++-- robotidy/transformers/aligners_core.py | 42 +++++++++++++---- robotidy/utils/variable_matcher.py | 7 ++- robotidy/version.py | 2 +- setup.py | 2 +- .../expected/groups.robot | 39 ++++++++++++++++ .../AlignKeywordsSection/source/groups.robot | 39 ++++++++++++++++ .../AlignKeywordsSection/test_transformer.py | 3 ++ .../expected/groups.robot | 39 ++++++++++++++++ .../AlignTestCasesSection/source/groups.robot | 39 ++++++++++++++++ .../AlignTestCasesSection/test_transformer.py | 3 ++ .../NormalizeSeparators/expected/groups.robot | 39 ++++++++++++++++ .../NormalizeSeparators/source/groups.robot | 39 ++++++++++++++++ .../NormalizeSeparators/test_transformer.py | 3 ++ .../RenameVariables/expected/groups.robot | 23 ++++++++++ .../RenameVariables/source/groups.robot | 23 ++++++++++ .../RenameVariables/test_transformer.py | 3 ++ tests/rf_versions_matrix/rf7/requirements.txt | 2 +- 48 files changed, 645 insertions(+), 73 deletions(-) create mode 100644 tests/atest/transformers/AlignKeywordsSection/expected/groups.robot create mode 100644 tests/atest/transformers/AlignKeywordsSection/source/groups.robot create mode 100644 tests/atest/transformers/AlignTestCasesSection/expected/groups.robot create mode 100644 tests/atest/transformers/AlignTestCasesSection/source/groups.robot create mode 100644 tests/atest/transformers/NormalizeSeparators/expected/groups.robot create mode 100644 tests/atest/transformers/NormalizeSeparators/source/groups.robot create mode 100644 tests/atest/transformers/RenameVariables/expected/groups.robot create mode 100644 tests/atest/transformers/RenameVariables/source/groups.robot diff --git a/robotidy/api.py b/robotidy/api.py index b24623b1..5b18236d 100644 --- a/robotidy/api.py +++ b/robotidy/api.py @@ -1,6 +1,7 @@ """ Methods for transforming Robot Framework ast model programmatically. """ + from __future__ import annotations from pathlib import Path @@ -37,7 +38,8 @@ def transform_model(model, root_dir: str, output: str | None = None, **kwargs) - """ robotidy_class = get_robotidy(root_dir, output, **kwargs) disabler_finder = disablers.RegisterDisablers( - robotidy_class.config.formatting.start_line, robotidy_class.config.formatting.end_line + robotidy_class.config.formatting.start_line, + robotidy_class.config.formatting.end_line, ) disabler_finder.visit(model) if disabler_finder.is_disabled_in_file(disablers.ALL_TRANSFORMERS): diff --git a/robotidy/app.py b/robotidy/app.py index e26b8981..245267ba 100644 --- a/robotidy/app.py +++ b/robotidy/app.py @@ -61,7 +61,10 @@ def transform_files(self): self.output_diff(model_path, old_model, new_model) changed_files += 1 except DataError as err: - click.echo(f"Failed to decode {source} with an error: {err}\nSkipping file", err=True) + click.echo( + f"Failed to decode {source} with an error: {err}\nSkipping file", + err=True, + ) changed_files = previous_changed_files skipped_files += 1 return self.formatting_result(all_files, changed_files, skipped_files, stdin) @@ -141,7 +144,12 @@ def get_line_ending(self, path: str): return f.newlines[0] return self.config.formatting.line_sep - def output_diff(self, path: str, old_model: misc.StatementLinesCollector, new_model: misc.StatementLinesCollector): + def output_diff( + self, + path: str, + old_model: misc.StatementLinesCollector, + new_model: misc.StatementLinesCollector, + ): if not self.config.show_diff: return old = [l + "\n" for l in old_model.text.splitlines()] diff --git a/robotidy/cli.py b/robotidy/cli.py index eafad1a3..e1947d70 100644 --- a/robotidy/cli.py +++ b/robotidy/cli.py @@ -55,7 +55,10 @@ "--endline", ], }, - {"name": "File exclusion", "options": ["--exclude", "--extend-exclude", "--skip-gitignore"]}, + { + "name": "File exclusion", + "options": ["--exclude", "--extend-exclude", "--skip-gitignore"], + }, skip.option_group, { "name": "Other", @@ -124,7 +127,11 @@ def print_transformer_docs(transformer): @decorators.optional_rich def print_description(name: str, target_version: int): # TODO: --desc works only for default transformers, it should also print custom transformer desc - transformers = load_transformers(TransformConfigMap([], [], []), allow_disabled=True, target_version=target_version) + transformers = load_transformers( + TransformConfigMap([], [], []), + allow_disabled=True, + target_version=target_version, + ) transformer_by_names = {transformer.name: transformer for transformer in transformers} if name == "all": for transformer in transformers: @@ -159,7 +166,11 @@ def print_transformers_list(global_config: config_module.MainConfig): table = Table(title="Transformers", header_style="bold red") table.add_column("Name", justify="left", no_wrap=True) table.add_column("Enabled") - transformers = load_transformers(TransformConfigMap([], [], []), allow_disabled=True, target_version=target_version) + transformers = load_transformers( + TransformConfigMap([], [], []), + allow_disabled=True, + target_version=target_version, + ) transformers.extend(_load_external_transformers(transformers, config.transformers_config, target_version)) for transformer in transformers: @@ -194,7 +205,11 @@ def generate_config(global_config: config_module.MainConfig): raise exceptions.MissingOptionalTomliWDependencyError() target_version = global_config.default.target_version config = global_config.default_loaded - transformers = load_transformers(TransformConfigMap([], [], []), allow_disabled=True, target_version=target_version) + transformers = load_transformers( + TransformConfigMap([], [], []), + allow_disabled=True, + target_version=target_version, + ) transformers.extend(_load_external_transformers(transformers, config.transformers_config, target_version)) toml_config = { diff --git a/robotidy/config.py b/robotidy/config.py index 59740546..1688991f 100644 --- a/robotidy/config.py +++ b/robotidy/config.py @@ -98,7 +98,12 @@ def convert_transformers_config( is_config: bool = False, ) -> list[TransformConfig]: return [ - TransformConfig(tr, force_include=force_included, custom_transformer=custom_transformer, is_config=is_config) + TransformConfig( + tr, + force_include=force_included, + custom_transformer=custom_transformer, + is_config=is_config, + ) for tr in config.get(param_name, ()) ] @@ -186,7 +191,10 @@ def from_config_file(self, config: dict, config_path: Path) -> "RawConfig": Dictionary key:values needs to be normalized and parsed to correct types. """ options_map = map_class_fields_with_their_types(self) - parsed_config = {"defined_in_config": {"defined_in_config", "config_path"}, "config_path": config_path} + parsed_config = { + "defined_in_config": {"defined_in_config", "config_path"}, + "config_path": config_path, + } for key, value in config.items(): # workaround to be able to use two option names for same action - backward compatibility change if key == "load_transformers": @@ -206,7 +214,10 @@ def from_config_file(self, config: dict, config_path: Path) -> "RawConfig": parsed_config[key] = [convert_transform_config(val, key) for val in value] elif key == "src": parsed_config[key] = tuple(value) - elif value_type in ("Pattern", Pattern): # future typing for 3.8 provides type as str + elif value_type in ( + "Pattern", + Pattern, + ): # future typing for 3.8 provides type as str parsed_config[key] = misc.validate_regex(value) else: parsed_config[key] = value @@ -273,7 +284,10 @@ def get_sources(self, sources: tuple[str, ...]) -> tuple[str, ...] | None: def get_sources_with_configs(self): sources = files.get_paths( - self.sources, self.default.exclude, self.default.extend_exclude, self.default.skip_gitignore + self.sources, + self.default.exclude, + self.default.extend_exclude, + self.default.skip_gitignore, ) for source in sources: if self.default.config: diff --git a/robotidy/disablers.py b/robotidy/disablers.py index 547b2566..6a7c3ecb 100644 --- a/robotidy/disablers.py +++ b/robotidy/disablers.py @@ -66,7 +66,12 @@ def is_line_start(node): class DisablersInFile: - def __init__(self, start_line: Optional[int], end_line: Optional[int], file_end: Optional[int] = None): + def __init__( + self, + start_line: Optional[int], + end_line: Optional[int], + file_end: Optional[int] = None, + ): self.start_line = start_line self.end_line = end_line self.file_end = file_end diff --git a/robotidy/files.py b/robotidy/files.py index c722f69e..f1e1e226 100644 --- a/robotidy/files.py +++ b/robotidy/files.py @@ -128,7 +128,12 @@ def get_path_relative_to_project_root(path: Path, root_parent: Path) -> Path: return path -def get_paths(src: tuple[str, ...], exclude: Pattern | None, extend_exclude: Pattern | None, skip_gitignore: bool): +def get_paths( + src: tuple[str, ...], + exclude: Pattern | None, + extend_exclude: Pattern | None, + skip_gitignore: bool, +): root = find_project_root(src) if skip_gitignore: gitignore = None diff --git a/robotidy/skip.py b/robotidy/skip.py index 9c859c19..0900a52a 100644 --- a/robotidy/skip.py +++ b/robotidy/skip.py @@ -114,7 +114,16 @@ def __init__(self, skip_config: SkipConfig): @staticmethod def parse_skip_settings(skip_config): - settings = {"settings", "arguments", "setup", "teardown", "timeout", "template", "return_statement", "tags"} + settings = { + "settings", + "arguments", + "setup", + "teardown", + "timeout", + "template", + "return_statement", + "tags", + } skip_settings = set() for setting in settings: if getattr(skip_config, setting): @@ -156,7 +165,10 @@ def section(self, name): documentation_option = click.option("--skip-documentation", is_flag=True, help="Skip formatting of documentation") return_values_option = click.option("--skip-return-values", is_flag=True, help="Skip formatting of return values") keyword_call_option = click.option( - "--skip-keyword-call", type=str, multiple=True, help="Keyword call name that should not be formatted" + "--skip-keyword-call", + type=str, + multiple=True, + help="Keyword call name that should not be formatted", ) keyword_call_pattern_option = click.option( "--skip-keyword-call-pattern", diff --git a/robotidy/transformers/AlignSettingsSection.py b/robotidy/transformers/AlignSettingsSection.py index 0a884f36..c4c3b727 100644 --- a/robotidy/transformers/AlignSettingsSection.py +++ b/robotidy/transformers/AlignSettingsSection.py @@ -147,7 +147,13 @@ def align_rows(self, statements, look_up): def calc_separator(self, index, up_to, indent_arg, token, look_up): if index < up_to: if self.fixed_width: - return max(self.fixed_width - len(token.value), self.formatting_config.space_count) * " " + return ( + max( + self.fixed_width - len(token.value), + self.formatting_config.space_count, + ) + * " " + ) arg_indent = self.argument_indent if indent_arg else 0 if indent_arg and index != 0: return ( diff --git a/robotidy/transformers/AlignTemplatedTestCases.py b/robotidy/transformers/AlignTemplatedTestCases.py index 00933256..79c019a8 100644 --- a/robotidy/transformers/AlignTemplatedTestCases.py +++ b/robotidy/transformers/AlignTemplatedTestCases.py @@ -110,7 +110,13 @@ def align_header(self, statement): for index, token in enumerate(statement.data_tokens[:-1]): tokens.append(token) if self.min_width: - separator = max(self.formatting_config.space_count, self.min_width - len(token.value)) * " " + separator = ( + max( + self.formatting_config.space_count, + self.min_width - len(token.value), + ) + * " " + ) else: separator = (self.widths[index] - len(token.value) + self.formatting_config.space_count) * " " tokens.append(Token(Token.SEPARATOR, separator)) diff --git a/robotidy/transformers/AlignVariablesSection.py b/robotidy/transformers/AlignVariablesSection.py index 22b69634..986637f9 100644 --- a/robotidy/transformers/AlignVariablesSection.py +++ b/robotidy/transformers/AlignVariablesSection.py @@ -46,7 +46,13 @@ class AlignVariablesSection(Transformer): To align all columns set ``up_to_column`` to 0. """ - def __init__(self, up_to_column: int = 2, skip_types: str = "", min_width: int = None, fixed_width: int = None): + def __init__( + self, + up_to_column: int = 2, + skip_types: str = "", + min_width: int = None, + fixed_width: int = None, + ): super().__init__() self.up_to_column = up_to_column - 1 self.min_width = min_width @@ -121,7 +127,13 @@ def align_rows(self, statements, look_up): def get_separator(self, index: int, up_to: int, token, look_up: dict[int, int]) -> str: if index < up_to: if self.fixed_width: - return max(self.fixed_width - len(token.value), self.formatting_config.space_count) * " " + return ( + max( + self.fixed_width - len(token.value), + self.formatting_config.space_count, + ) + * " " + ) return (look_up[index] - len(token.value)) * " " else: return self.formatting_config.separator diff --git a/robotidy/transformers/GenerateDocumentation.py b/robotidy/transformers/GenerateDocumentation.py index 43488b6c..1c72ca8a 100644 --- a/robotidy/transformers/GenerateDocumentation.py +++ b/robotidy/transformers/GenerateDocumentation.py @@ -130,7 +130,12 @@ class GenerateDocumentation(Transformer): WHITESPACE_PATTERN = re.compile(r"(\s{2,}|\t)", re.UNICODE) - def __init__(self, overwrite: bool = False, doc_template: str = "google", template_directory: str | None = None): + def __init__( + self, + overwrite: bool = False, + doc_template: str = "google", + template_directory: str | None = None, + ): self.overwrite = overwrite self.doc_template = self.load_template(doc_template, template_directory) self.args_returns_finder = ArgumentsAndReturnsVisitor() @@ -174,7 +179,11 @@ def visit_Keyword(self, node): # noqa if not self.overwrite and self.args_returns_finder.doc_exists: return node formatting = FormattingData(self.formatting_config.continuation_indent, self.formatting_config.separator) - kw_data = KeywordData(node.name, self.args_returns_finder.arguments, self.args_returns_finder.returns) + kw_data = KeywordData( + node.name, + self.args_returns_finder.arguments, + self.args_returns_finder.returns, + ) generated = self.doc_template.render(keyword=kw_data, formatting=formatting) doc_node = self.create_documentation_from_string(generated) if self.overwrite: @@ -186,7 +195,11 @@ def visit_Documentation(self, node): # noqa return None def create_documentation_from_string(self, doc_string): - new_line = [Token(Token.EOL), Token(Token.SEPARATOR, self.formatting_config.indent), Token(Token.CONTINUATION)] + new_line = [ + Token(Token.EOL), + Token(Token.SEPARATOR, self.formatting_config.indent), + Token(Token.CONTINUATION), + ] tokens = [ Token(Token.SEPARATOR, self.formatting_config.indent), Token(Token.DOCUMENTATION, "[Documentation]"), diff --git a/robotidy/transformers/IndentNestedKeywords.py b/robotidy/transformers/IndentNestedKeywords.py index 19257672..05f4939c 100644 --- a/robotidy/transformers/IndentNestedKeywords.py +++ b/robotidy/transformers/IndentNestedKeywords.py @@ -126,7 +126,11 @@ def visit_SuiteSetup(self, node): # noqa comments = misc.collect_comments_from_tokens(node.tokens, indent=None) separator = self.get_separator() new_line = misc.get_new_line() - tokens = [node.data_tokens[0], separator, *misc.join_tokens_with_token(lines[0][1], separator)] + tokens = [ + node.data_tokens[0], + separator, + *misc.join_tokens_with_token(lines[0][1], separator), + ] formatted_tokens = self.parse_keyword_lines(lines, tokens, new_line, eol=node.tokens[-1]) if self.node_was_transformed(node.tokens, formatted_tokens): node.tokens = formatted_tokens @@ -144,7 +148,12 @@ def visit_Setup(self, node): # noqa indent = node.tokens[0] separator = self.get_separator() new_line = misc.get_new_line(indent) - tokens = [indent, node.data_tokens[0], separator, *misc.join_tokens_with_token(lines[0][1], separator)] + tokens = [ + indent, + node.data_tokens[0], + separator, + *misc.join_tokens_with_token(lines[0][1], separator), + ] comment = misc.merge_comments_into_one(node.tokens) if comment: # need to add comments on first line for [Setup] / [Teardown] settings diff --git a/robotidy/transformers/InlineIf.py b/robotidy/transformers/InlineIf.py index c722fa99..ad7c5a16 100644 --- a/robotidy/transformers/InlineIf.py +++ b/robotidy/transformers/InlineIf.py @@ -179,7 +179,11 @@ def inline_if_from_branch(self, node, indent): # check for ElseIfHeader first since it's child of IfHeader class if isinstance(node.header, ElseIfHeader): header = ElseIfHeader( - [Token(Token.ELSE_IF), Token(Token.SEPARATOR, separator), Token(Token.ARGUMENT, node.header.condition)] + [ + Token(Token.ELSE_IF), + Token(Token.SEPARATOR, separator), + Token(Token.ARGUMENT, node.header.condition), + ] ) elif isinstance(node.header, IfHeader): tokens = [Token(Token.SEPARATOR, indent)] @@ -202,7 +206,10 @@ def inline_if_from_branch(self, node, indent): @staticmethod def to_inline_keyword(keyword, separator, last_token): - tokens = [Token(Token.SEPARATOR, separator), Token(Token.KEYWORD, keyword.keyword)] + tokens = [ + Token(Token.SEPARATOR, separator), + Token(Token.KEYWORD, keyword.keyword), + ] for arg in keyword.get_tokens(Token.ARGUMENT): tokens.extend([Token(Token.SEPARATOR, separator), arg]) tokens.append(last_token) @@ -321,11 +328,15 @@ def handle_inline_if_create(self, node, indent, assign): else_found = False if isinstance(node.header, InlineIfHeader): header = IfHeader.from_params( - condition=node.condition, indent=indent, separator=self.formatting_config.separator + condition=node.condition, + indent=indent, + separator=self.formatting_config.separator, ) elif isinstance(node.header, ElseIfHeader): header = ElseIfHeader.from_params( - condition=node.condition, indent=indent, separator=self.formatting_config.separator + condition=node.condition, + indent=indent, + separator=self.formatting_config.separator, ) else: header = ElseHeader.from_params(indent=indent) diff --git a/robotidy/transformers/NormalizeAssignments.py b/robotidy/transformers/NormalizeAssignments.py index 48d3afe2..824b9df4 100644 --- a/robotidy/transformers/NormalizeAssignments.py +++ b/robotidy/transformers/NormalizeAssignments.py @@ -64,7 +64,10 @@ class NormalizeAssignments(Transformer): HANDLES_SKIP = frozenset({"skip_sections"}) def __init__( - self, equal_sign_type: str = "autodetect", equal_sign_type_variables: str = "remove", skip: Skip = None + self, + equal_sign_type: str = "autodetect", + equal_sign_type_variables: str = "remove", + skip: Skip = None, ): super().__init__(skip) self.remove_equal_sign = re.compile(r"\s?=$") diff --git a/robotidy/transformers/NormalizeNewLines.py b/robotidy/transformers/NormalizeNewLines.py index 0a827b1b..4342f688 100644 --- a/robotidy/transformers/NormalizeNewLines.py +++ b/robotidy/transformers/NormalizeNewLines.py @@ -118,7 +118,7 @@ def visit_If(self, node): # noqa self.trim_empty_lines(node) return self.generic_visit(node) - visit_For = visit_While = visit_Try = visit_If + visit_For = visit_While = visit_Group = visit_Try = visit_If def visit_Statement(self, node): # noqa tokens = [] diff --git a/robotidy/transformers/NormalizeSectionHeaderName.py b/robotidy/transformers/NormalizeSectionHeaderName.py index 5ffb4190..1678f272 100644 --- a/robotidy/transformers/NormalizeSectionHeaderName.py +++ b/robotidy/transformers/NormalizeSectionHeaderName.py @@ -35,7 +35,14 @@ class NormalizeSectionHeaderName(Transformer): """ HANDLES_SKIP = frozenset({"skip_sections"}) - EN_SINGULAR_HEADERS = {"comment", "setting", "variable", "task", "test case", "keyword"} + EN_SINGULAR_HEADERS = { + "comment", + "setting", + "variable", + "task", + "test case", + "keyword", + } def __init__(self, uppercase: bool = False, skip: Skip = None): super().__init__(skip) diff --git a/robotidy/transformers/NormalizeSeparators.py b/robotidy/transformers/NormalizeSeparators.py index 3ebb3ee4..a4b66ead 100644 --- a/robotidy/transformers/NormalizeSeparators.py +++ b/robotidy/transformers/NormalizeSeparators.py @@ -33,7 +33,12 @@ class NormalizeSeparators(Transformer): } ) - def __init__(self, flatten_lines: bool = False, align_new_line: bool = False, skip: Skip = None): + def __init__( + self, + flatten_lines: bool = False, + align_new_line: bool = False, + skip: Skip = None, + ): super().__init__(skip=skip) self.indent = 0 self.flatten_lines = flatten_lines @@ -76,6 +81,8 @@ def visit_For(self, node): self.visit_Statement(node.end) return node + visit_Group = visit_For + def visit_Try(self, node): node = self.indented_block(node) if node.next: diff --git a/robotidy/transformers/NormalizeTags.py b/robotidy/transformers/NormalizeTags.py index 96eb83c5..94a8ebee 100644 --- a/robotidy/transformers/NormalizeTags.py +++ b/robotidy/transformers/NormalizeTags.py @@ -38,7 +38,12 @@ class NormalizeTags(Transformer): "titlecase": str.title, } - def __init__(self, case: str = "lowercase", normalize_case: bool = True, preserve_format: bool = False): + def __init__( + self, + case: str = "lowercase", + normalize_case: bool = True, + preserve_format: bool = False, + ): super().__init__() self.case_function = case.lower() self.normalize_case = normalize_case @@ -48,7 +53,10 @@ def __init__(self, case: str = "lowercase", normalize_case: bool = True, preserv def validate_case_function(self): if self.case_function not in self.CASE_FUNCTIONS: raise InvalidParameterValueError( - self.__class__.__name__, "case", self.case_function, "Supported cases: lowercase, uppercase, titlecase." + self.__class__.__name__, + "case", + self.case_function, + "Supported cases: lowercase, uppercase, titlecase.", ) @skip_section_if_disabled @@ -102,7 +110,10 @@ def normalize_tags_tokens_ignore_formatting(self, node, indent): tags = self.remove_duplicates(tags) comments = node.get_tokens(Token.COMMENT) if indent: - tokens = [Token(Token.SEPARATOR, self.formatting_config.indent), setting_name] + tokens = [ + Token(Token.SEPARATOR, self.formatting_config.indent), + setting_name, + ] else: tokens = [setting_name] for tag in tags: diff --git a/robotidy/transformers/OrderSettings.py b/robotidy/transformers/OrderSettings.py index 7dbca424..1fa0e77e 100644 --- a/robotidy/transformers/OrderSettings.py +++ b/robotidy/transformers/OrderSettings.py @@ -136,7 +136,12 @@ def assert_no_duplicates_in_orders(self): shared_keyword = orders["keyword_before"].intersection(orders["keyword_after"]) shared_test = orders["test_before"].intersection(orders["test_after"]) if shared_keyword: - raise SettingInBothOrdersError(self.__class__.__name__, "keyword_before", "keyword_after", shared_keyword) + raise SettingInBothOrdersError( + self.__class__.__name__, + "keyword_before", + "keyword_after", + shared_keyword, + ) if shared_test: raise SettingInBothOrdersError(self.__class__.__name__, "test_before", "test_after", shared_test) diff --git a/robotidy/transformers/OrderSettingsSection.py b/robotidy/transformers/OrderSettingsSection.py index 3defdbbb..3902a9ac 100644 --- a/robotidy/transformers/OrderSettingsSection.py +++ b/robotidy/transformers/OrderSettingsSection.py @@ -92,7 +92,11 @@ def __init__( "tags", tags_order, (Token.FORCE_TAGS, Token.DEFAULT_TAGS), - {"force_tags": Token.FORCE_TAGS, "test_tags": Token.FORCE_TAGS, "default_tags": Token.DEFAULT_TAGS}, + { + "force_tags": Token.FORCE_TAGS, + "test_tags": Token.FORCE_TAGS, + "default_tags": Token.DEFAULT_TAGS, + }, ) def parse_group_order(self, order): diff --git a/robotidy/transformers/RemoveEmptySettings.py b/robotidy/transformers/RemoveEmptySettings.py index de5d2bac..791abc9f 100644 --- a/robotidy/transformers/RemoveEmptySettings.py +++ b/robotidy/transformers/RemoveEmptySettings.py @@ -43,7 +43,10 @@ def __init__(self, work_mode: str = "overwrite_ok", more_explicit: bool = True): super().__init__() if work_mode not in ("overwrite_ok", "always"): raise InvalidParameterValueError( - self.__class__.__name__, "work_mode", work_mode, "Possible values:\n overwrite_ok\n always" + self.__class__.__name__, + "work_mode", + work_mode, + "Possible values:\n overwrite_ok\n always", ) self.work_mode = work_mode self.more_explicit = more_explicit diff --git a/robotidy/transformers/RenameKeywords.py b/robotidy/transformers/RenameKeywords.py index 7c3caac0..301a06cb 100644 --- a/robotidy/transformers/RenameKeywords.py +++ b/robotidy/transformers/RenameKeywords.py @@ -149,7 +149,10 @@ def remove_underscores_and_capitalize(self, value: str) -> str: # capitalize first letter of every word, leave rest untouched for index, word in enumerate(split_words): if not word: - if index in (0, len(split_words) - 1): # leading and trailing whitespace + if index in ( + 0, + len(split_words) - 1, + ): # leading and trailing whitespace words.append("") else: words.append(word[0].upper() + word[1:]) diff --git a/robotidy/transformers/RenameTestCases.py b/robotidy/transformers/RenameTestCases.py index cf2d356f..b1ed168a 100644 --- a/robotidy/transformers/RenameTestCases.py +++ b/robotidy/transformers/RenameTestCases.py @@ -94,6 +94,7 @@ class RenameTestCases(Transformer): No Operation ``` """ + ENABLED = False def __init__( diff --git a/robotidy/transformers/RenameVariables.py b/robotidy/transformers/RenameVariables.py index 600d42f2..d967f8f8 100644 --- a/robotidy/transformers/RenameVariables.py +++ b/robotidy/transformers/RenameVariables.py @@ -14,7 +14,12 @@ from robotidy.transformers import Transformer from robotidy.utils import misc, variable_matcher -SET_GLOBAL_VARIABLES = {"settestvariable", "settaskvariable", "setsuitevariable", "setglobalvariable"} +SET_GLOBAL_VARIABLES = { + "settestvariable", + "settaskvariable", + "setsuitevariable", + "setglobalvariable", +} SET_LOCAL_VARIABLE = "setlocalvariable" @@ -312,11 +317,15 @@ def visit_Variable(self, node): # noqa for data_token in node.data_tokens: if data_token.type == Token.VARIABLE: data_token.value = self.rename_value( - data_token.value, variable_case=self.variables_section_case, is_var=True + data_token.value, + variable_case=self.variables_section_case, + is_var=True, ) elif data_token.type == Token.ARGUMENT: data_token.value = self.rename_value( - data_token.value, variable_case=self.variables_section_case, is_var=False + data_token.value, + variable_case=self.variables_section_case, + is_var=False, ) return node @@ -451,6 +460,8 @@ def visit_While(self, node): # noqa arg.value = self.rename_value(arg.value, variable_case=VariableCase.AUTO, is_var=False) return self.generic_visit(node) + visit_Group = visit_While + @skip_if_disabled def visit_Var(self, node): # noqa if node.errors: diff --git a/robotidy/transformers/ReplaceEmptyValues.py b/robotidy/transformers/ReplaceEmptyValues.py index 41771678..00d43583 100644 --- a/robotidy/transformers/ReplaceEmptyValues.py +++ b/robotidy/transformers/ReplaceEmptyValues.py @@ -66,6 +66,11 @@ def visit_Variable(self, node): # noqa tokens.append(token) prev_token = token else: - tokens = [node.tokens[0], sep, Token(Token.ARGUMENT, node.name[0] + "{EMPTY}"), *node.tokens[1:]] + tokens = [ + node.tokens[0], + sep, + Token(Token.ARGUMENT, node.name[0] + "{EMPTY}"), + *node.tokens[1:], + ] node.tokens = tokens return node diff --git a/robotidy/transformers/ReplaceReturns.py b/robotidy/transformers/ReplaceReturns.py index 01cca30a..d2e8b4ed 100644 --- a/robotidy/transformers/ReplaceReturns.py +++ b/robotidy/transformers/ReplaceReturns.py @@ -62,7 +62,9 @@ def visit_Keyword(self, node): # noqa while node.body and isinstance(node.body[-1], (EmptyLine, Comment)): skip_lines.append(node.body.pop()) return_stmt = misc.create_statement_from_tokens( - statement=ReturnStatement, tokens=self.return_statement.tokens[2:], indent=indent + statement=ReturnStatement, + tokens=self.return_statement.tokens[2:], + indent=indent, ) node.body.append(return_stmt) node.body.extend(skip_lines) diff --git a/robotidy/transformers/ReplaceWithVAR.py b/robotidy/transformers/ReplaceWithVAR.py index 758e548e..a2cff21f 100644 --- a/robotidy/transformers/ReplaceWithVAR.py +++ b/robotidy/transformers/ReplaceWithVAR.py @@ -1,4 +1,5 @@ from __future__ import annotations + from typing import TYPE_CHECKING from robot.api.parsing import Comment, ElseHeader, ElseIfHeader, End, If, IfHeader, KeywordCall, Token @@ -220,11 +221,19 @@ def replace_set_variable(self, node, kw_name: str, indent: str, assign: list[str if len(values) > 1: var_name = "@" + var_name[1:] return Var.from_params( - name=var_name, value=values, separator=self.formatting_config.separator, indent=indent, scope=scope + name=var_name, + value=values, + separator=self.formatting_config.separator, + indent=indent, + scope=scope, ) return [ Var.from_params( - name=var_assign, value=value, separator=self.formatting_config.separator, indent=indent, scope=scope + name=var_assign, + value=value, + separator=self.formatting_config.separator, + indent=indent, + scope=scope, ) for var_assign, value in zip(assign, values) ] @@ -252,7 +261,11 @@ def replace_set_variable_scope(self, node, kw_name: str, indent: str, assign: li values = [var_name] scope = scope.upper() if self.explicit_local or scope != "local" else None return Var.from_params( - name=var_name, value=values, separator=self.formatting_config.separator, indent=indent, scope=scope + name=var_name, + value=values, + separator=self.formatting_config.separator, + indent=indent, + scope=scope, ) def replace_set_variable_if_kw(self, node, kw_name: str, indent: str, assign: list[str] | None = None): @@ -287,7 +300,11 @@ def replace_set_variable_if_kw(self, node, kw_name: str, indent: str, assign: li else: condition, value = args[:2] variable = Var.from_params( - name=var_name, value=value, separator=separator, indent=in_block_indent, scope=scope + name=var_name, + value=value, + separator=separator, + indent=in_block_indent, + scope=scope, ) if tail: if condition: @@ -325,7 +342,13 @@ def replace_catenate_kw(self, node, kw_name: str, indent: str, assign: list[str] else: separator = "${SPACE}" scope = "LOCAL" if self.explicit_local else None - return Var.from_params(name=var_name, value=values, indent=indent, value_separator=separator, scope=scope) + return Var.from_params( + name=var_name, + value=values, + indent=indent, + value_separator=separator, + scope=scope, + ) def replace_create_list_kw(self, node, kw_name: str, indent: str, assign: list[str] | None = None): assign = assign or self.get_assign_names(node.assign) @@ -340,7 +363,11 @@ def replace_create_list_kw(self, node, kw_name: str, indent: str, assign: list[s values = ["@{EMPTY}"] scope = "LOCAL" if self.explicit_local else None return Var.from_params( - name=var_name, value=values, separator=self.formatting_config.separator, indent=indent, scope=scope + name=var_name, + value=values, + separator=self.formatting_config.separator, + indent=indent, + scope=scope, ) def _split_dict_items(self, items: list[str]): @@ -378,5 +405,9 @@ def replace_create_dictionary_kw(self, node, kw_name: str, indent: str, assign: values = ["&{EMPTY}"] scope = "LOCAL" if self.explicit_local else None return Var.from_params( - name=var_name, value=values, separator=self.formatting_config.separator, indent=indent, scope=scope + name=var_name, + value=values, + separator=self.formatting_config.separator, + indent=indent, + scope=scope, ) diff --git a/robotidy/transformers/SmartSortKeywords.py b/robotidy/transformers/SmartSortKeywords.py index 6cb71e86..ed1f2757 100644 --- a/robotidy/transformers/SmartSortKeywords.py +++ b/robotidy/transformers/SmartSortKeywords.py @@ -51,7 +51,12 @@ class SmartSortKeywords(Transformer): ENABLED = False - def __init__(self, case_insensitive=True, ignore_leading_underscore=False, ignore_other_underscore=True): + def __init__( + self, + case_insensitive=True, + ignore_leading_underscore=False, + ignore_other_underscore=True, + ): super().__init__() self.ci = case_insensitive self.ilu = ignore_leading_underscore diff --git a/robotidy/transformers/SplitTooLongLine.py b/robotidy/transformers/SplitTooLongLine.py index 3c1eae30..03035d48 100644 --- a/robotidy/transformers/SplitTooLongLine.py +++ b/robotidy/transformers/SplitTooLongLine.py @@ -76,7 +76,14 @@ class SplitTooLongLine(Transformer): """ IGNORED_WHITESPACE = {Token.EOL, Token.CONTINUATION} - HANDLES_SKIP = frozenset({"skip_comments", "skip_keyword_call", "skip_keyword_call_pattern", "skip_sections"}) + HANDLES_SKIP = frozenset( + { + "skip_comments", + "skip_keyword_call", + "skip_keyword_call_pattern", + "skip_sections", + } + ) def __init__( self, @@ -176,7 +183,11 @@ def visit_Var(self, node): # noqa separator = Token(Token.SEPARATOR, self.formatting_config.separator) line = [indent, node.data_tokens[0], separator, var_name] tokens, comments = self.split_tokens( - node.tokens, line, self.split_on_every_value, indent=indent, split_types=(Token.ARGUMENT, Token.OPTION) + node.tokens, + line, + self.split_on_every_value, + indent=indent, + split_types=(Token.ARGUMENT, Token.OPTION), ) comments = [Comment([comment, EOL]) for comment in comments] node.tokens = tokens @@ -247,7 +258,14 @@ def split_to_multiple_lines(tokens, indent, separator): yield EOL first = False - def split_tokens(self, tokens, line, split_on, indent=None, split_types: tuple = (Token.ARGUMENT,)): + def split_tokens( + self, + tokens, + line, + split_on, + indent=None, + split_types: tuple = (Token.ARGUMENT,), + ): separator = Token(Token.SEPARATOR, self.formatting_config.separator) align_new_line = self.align_new_line and not split_on if align_new_line: @@ -338,7 +356,10 @@ def split_keyword_call(self, node): else: head = [] tokens, comments = self.split_tokens( - node.tokens[node.tokens.index(keyword) + 1 :], line, self.split_on_every_arg, indent + node.tokens[node.tokens.index(keyword) + 1 :], + line, + self.split_on_every_arg, + indent, ) head.extend(tokens) comment_tokens = [] diff --git a/robotidy/transformers/Translate.py b/robotidy/transformers/Translate.py index 9f46e6d7..1894d1d0 100644 --- a/robotidy/transformers/Translate.py +++ b/robotidy/transformers/Translate.py @@ -79,7 +79,11 @@ def __init__( self.language, self.settings = None, None self._bdd_mapping = None self.bdd = self.get_translated_bdd( - but_alternative, given_alternative, and_alternative, then_alternative, when_alternative + but_alternative, + given_alternative, + and_alternative, + then_alternative, + when_alternative, ) @property diff --git a/robotidy/transformers/__init__.py b/robotidy/transformers/__init__.py index eed6d66a..4b766c3d 100644 --- a/robotidy/transformers/__init__.py +++ b/robotidy/transformers/__init__.py @@ -8,6 +8,7 @@ If you don't want to run your transformer by default and only when calling robotidy with --transform YourTransformer then add ``ENABLED = False`` class attribute inside. """ + from __future__ import annotations import copy @@ -205,7 +206,10 @@ def convert_transform_config(value: str, param_name: str) -> TransformConfig: custom_transformer = param_name == "custom_transformers" is_config = param_name == "configure" return TransformConfig( - value, force_include=force_included, custom_transformer=custom_transformer, is_config=is_config + value, + force_include=force_included, + custom_transformer=custom_transformer, + is_config=is_config, ) @@ -325,14 +329,20 @@ def import_transformer(name, config: TransformConfigMap, skip) -> Iterable[Trans imported = IMPORTER.import_class_or_module(name) if inspect.isclass(imported): yield create_transformer_instance( - imported, short_name, config.get_args(name, short_name, import_path), skip + imported, + short_name, + config.get_args(name, short_name, import_path), + skip, ) else: transformers = load_transformers_from_module(imported) transformers = order_transformers(transformers, imported) for name, transformer_class in transformers.items(): yield create_transformer_instance( - transformer_class, name, config.get_args(name, short_name, import_path), skip + transformer_class, + name, + config.get_args(name, short_name, import_path), + skip, ) except DataError: similar_finder = misc.RecommendationFinder() diff --git a/robotidy/transformers/aligners_core.py b/robotidy/transformers/aligners_core.py index d8d712e5..8f8392e1 100644 --- a/robotidy/transformers/aligners_core.py +++ b/robotidy/transformers/aligners_core.py @@ -20,7 +20,6 @@ class AlignKeywordsTestsSection(Transformer): - ENABLED = False DEFAULT_WIDTH = 24 HANDLES_SKIP = frozenset( @@ -158,7 +157,7 @@ def visit_For(self, node): # noqa self.remove_auto_widths_for_context() return node - visit_While = visit_For + visit_While = visit_Group = visit_For def get_width(self, col: int, is_setting: bool, override_default_zero: bool = False) -> int: # If auto mode is enabled, use auto widths for current context (last defined widths) @@ -199,7 +198,10 @@ def visit_Documentation(self, node): # noqa len(prev_token.value) + self.formatting_config.space_count ) - len(prev_token.value) else: - separator_len = max(width - len(prev_token.value), self.formatting_config.space_count) + separator_len = max( + width - len(prev_token.value), + self.formatting_config.space_count, + ) token.value = " " * separator_len break elif token.type != Token.ARGUMENT: # ... # comment edge case @@ -237,7 +239,7 @@ def visit_ForHeader(self, node): # noqa node.tokens = [indent] + list(node.tokens[1:]) return node - visit_End = visit_ForHeader # TODO add other headers + visit_End = visit_GroupHeader = visit_ForHeader # TODO add other headers @skip_if_disabled def visit_KeywordCall(self, node): # noqa @@ -250,7 +252,13 @@ def visit_KeywordCall(self, node): # noqa def should_skip_return_values(self, line: list[Token], possible_assign: bool) -> bool: return possible_assign and self.skip.return_values and any(token.type == Token.ASSIGN for token in line) - def align_node(self, node, check_length: bool, possible_assign: bool = False, is_setting: bool = False): + def align_node( + self, + node, + check_length: bool, + possible_assign: bool = False, + is_setting: bool = False, + ): indent = Token(Token.SEPARATOR, self.indent * self.formatting_config.indent) aligned_lines = [] for line in node.lines: @@ -446,13 +454,21 @@ def align_tokens(self, tokens: list, skip_width: int, is_setting: bool): misaligned_cols += 1 while prev_overflow_len > width: column += 1 - width = self.get_width(column, override_default_zero=True, is_setting=is_setting) + width = self.get_width( + column, + override_default_zero=True, + is_setting=is_setting, + ) prev_overflow_len -= width misaligned_cols += 1 if self.too_many_misaligned_cols(misaligned_cols, prev_overflow_len, tokens, index): # check if next col fits next token with prev_overflow, if not, jump to the next column next_token = tokens[index + 1] - next_width = self.get_width(column + 1, override_default_zero=True, is_setting=is_setting) + next_width = self.get_width( + column + 1, + override_default_zero=True, + is_setting=is_setting, + ) required_width = next_width - prev_overflow_len - len(next_token.value) if required_width < min_separator: column += 1 @@ -461,7 +477,11 @@ def align_tokens(self, tokens: list, skip_width: int, is_setting: bool): else: # "overflow" while misc.round_to_four(len(token.value) + min_separator) > width: column += 1 - width += self.get_width(column, override_default_zero=True, is_setting=is_setting) + width += self.get_width( + column, + override_default_zero=True, + is_setting=is_setting, + ) separator_len = width - len(token.value) separator_len = max( min_separator, separator_len @@ -586,7 +606,11 @@ def calculate_column_widths(self): self.settings_widths[column] = max(filter_widths, default=max_width) def get_and_store_columns_widths( - self, node, widths: defaultdict, up_to: int = 0, filter_tokens: frozenset | None = None + self, + node, + widths: defaultdict, + up_to: int = 0, + filter_tokens: frozenset | None = None, ): """ Save columns widths to use them later to find the longest token in column. diff --git a/robotidy/utils/variable_matcher.py b/robotidy/utils/variable_matcher.py index 1e821e08..bea8097b 100644 --- a/robotidy/utils/variable_matcher.py +++ b/robotidy/utils/variable_matcher.py @@ -6,7 +6,12 @@ from typing import Iterator, Sequence class VariableMatches: - def __init__(self, string: str, identifiers: Sequence[str] = "$@&%", ignore_errors: bool = False): + def __init__( + self, + string: str, + identifiers: Sequence[str] = "$@&%", + ignore_errors: bool = False, + ): self.string = string self.identifiers = identifiers self.ignore_errors = ignore_errors diff --git a/robotidy/version.py b/robotidy/version.py index 54ddc8fd..80de3567 100644 --- a/robotidy/version.py +++ b/robotidy/version.py @@ -1 +1 @@ -__version__ = "4.15.0" +__version__ = "4.16.0" diff --git a/setup.py b/setup.py index 83f10431..ae1d31ae 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ include_package_data=True, python_requires=">=3.8", install_requires=[ - "robotframework>=4.0,<7.2", + "robotframework>=4.0,<7.3", "click==8.1.*", "colorama>=0.4.3,<0.4.7", "pathspec>=0.9.0,<0.12.2", diff --git a/tests/atest/transformers/AlignKeywordsSection/expected/groups.robot b/tests/atest/transformers/AlignKeywordsSection/expected/groups.robot new file mode 100644 index 00000000..9a314d52 --- /dev/null +++ b/tests/atest/transformers/AlignKeywordsSection/expected/groups.robot @@ -0,0 +1,39 @@ +*** Keywords *** +Single named group + GROUP Named + Keyword Call ${arg} + END + +Empty group + GROUP + END + +Non-named group + GROUP + Keyword Call ${arg} + END + +Nested group + GROUP 1st level + GROUP 2nd level + Keyword Call ${arg} + END + END + +Multiline group header + GROUP + ... Named + Keyword Call + END + +Inside other control structure + WHILE ${True} + GROUP Named + Keyword Call + END + END + IF ${True} + GROUP Named + Keyword Call + END + END diff --git a/tests/atest/transformers/AlignKeywordsSection/source/groups.robot b/tests/atest/transformers/AlignKeywordsSection/source/groups.robot new file mode 100644 index 00000000..baa3265c --- /dev/null +++ b/tests/atest/transformers/AlignKeywordsSection/source/groups.robot @@ -0,0 +1,39 @@ +*** Keywords *** +Single named group + GROUP Named + Keyword Call ${arg} + END + +Empty group + GROUP + END + +Non-named group + GROUP + Keyword Call ${arg} + END + +Nested group + GROUP 1st level + GROUP 2nd level + Keyword Call ${arg} + END + END + +Multiline group header + GROUP + ... Named + Keyword Call + END + +Inside other control structure + WHILE ${True} + GROUP Named + Keyword Call + END + END + IF ${True} + GROUP Named + Keyword Call + END + END diff --git a/tests/atest/transformers/AlignKeywordsSection/test_transformer.py b/tests/atest/transformers/AlignKeywordsSection/test_transformer.py index 32c5fa17..5fd5d66d 100644 --- a/tests/atest/transformers/AlignKeywordsSection/test_transformer.py +++ b/tests/atest/transformers/AlignKeywordsSection/test_transformer.py @@ -108,3 +108,6 @@ def test_align_settings_separately(self): self.compare( source="align_settings_separately.robot", config=":alignment_type=auto:align_settings_separately=True" ) + + def test_groups(self): + self.compare(source="groups.robot", target_version=">7.1.1") diff --git a/tests/atest/transformers/AlignTestCasesSection/expected/groups.robot b/tests/atest/transformers/AlignTestCasesSection/expected/groups.robot new file mode 100644 index 00000000..8467f1b7 --- /dev/null +++ b/tests/atest/transformers/AlignTestCasesSection/expected/groups.robot @@ -0,0 +1,39 @@ +*** Test Cases *** +Single named group + GROUP Named + Keyword Call ${arg} + END + +Empty group + GROUP + END + +Non-named group + GROUP + Keyword Call ${arg} + END + +Nested group + GROUP 1st level + GROUP 2nd level + Keyword Call ${arg} + END + END + +Multiline group header + GROUP + ... Named + Keyword Call + END + +Inside other control structure + WHILE ${True} + GROUP Named + Keyword Call + END + END + IF ${True} + GROUP Named + Keyword Call + END + END diff --git a/tests/atest/transformers/AlignTestCasesSection/source/groups.robot b/tests/atest/transformers/AlignTestCasesSection/source/groups.robot new file mode 100644 index 00000000..61b91670 --- /dev/null +++ b/tests/atest/transformers/AlignTestCasesSection/source/groups.robot @@ -0,0 +1,39 @@ +*** Test Cases *** +Single named group + GROUP Named + Keyword Call ${arg} + END + +Empty group + GROUP + END + +Non-named group + GROUP + Keyword Call ${arg} + END + +Nested group + GROUP 1st level + GROUP 2nd level + Keyword Call ${arg} + END + END + +Multiline group header + GROUP + ... Named + Keyword Call + END + +Inside other control structure + WHILE ${True} + GROUP Named + Keyword Call + END + END + IF ${True} + GROUP Named + Keyword Call + END + END diff --git a/tests/atest/transformers/AlignTestCasesSection/test_transformer.py b/tests/atest/transformers/AlignTestCasesSection/test_transformer.py index 3ffa36d6..aae4b5fb 100644 --- a/tests/atest/transformers/AlignTestCasesSection/test_transformer.py +++ b/tests/atest/transformers/AlignTestCasesSection/test_transformer.py @@ -136,3 +136,6 @@ def test_templated_test_with_setting_separate(self): "--transform NormalizeSeparators " "--transform NormalizeComments", ) + + def test_groups(self): + self.compare(source="groups.robot", target_version=">7.1.1") diff --git a/tests/atest/transformers/NormalizeSeparators/expected/groups.robot b/tests/atest/transformers/NormalizeSeparators/expected/groups.robot new file mode 100644 index 00000000..d762102d --- /dev/null +++ b/tests/atest/transformers/NormalizeSeparators/expected/groups.robot @@ -0,0 +1,39 @@ +*** Keywords *** +Single named group + GROUP Named + Keyword Call ${arg} + END + +Empty group + GROUP + END + +Non-named group + GROUP + Keyword Call ${arg} + END + +Nested group + GROUP 1st level + GROUP 2nd level + Keyword Call ${arg} + END + END + +Multiline group header + GROUP + ... Named + Keyword Call + END + +Inside other control structure + WHILE ${True} + GROUP Named + Keyword Call + END + END + IF ${True} + GROUP Named + Keyword Call + END + END diff --git a/tests/atest/transformers/NormalizeSeparators/source/groups.robot b/tests/atest/transformers/NormalizeSeparators/source/groups.robot new file mode 100644 index 00000000..baa3265c --- /dev/null +++ b/tests/atest/transformers/NormalizeSeparators/source/groups.robot @@ -0,0 +1,39 @@ +*** Keywords *** +Single named group + GROUP Named + Keyword Call ${arg} + END + +Empty group + GROUP + END + +Non-named group + GROUP + Keyword Call ${arg} + END + +Nested group + GROUP 1st level + GROUP 2nd level + Keyword Call ${arg} + END + END + +Multiline group header + GROUP + ... Named + Keyword Call + END + +Inside other control structure + WHILE ${True} + GROUP Named + Keyword Call + END + END + IF ${True} + GROUP Named + Keyword Call + END + END diff --git a/tests/atest/transformers/NormalizeSeparators/test_transformer.py b/tests/atest/transformers/NormalizeSeparators/test_transformer.py index 94f0e333..89404db6 100644 --- a/tests/atest/transformers/NormalizeSeparators/test_transformer.py +++ b/tests/atest/transformers/NormalizeSeparators/test_transformer.py @@ -104,3 +104,6 @@ def test_align_new_line(self): expected="cont_indent_align_new_line.robot", config=":align_new_line=True", ) + + def test_groups(self): + self.compare(source="groups.robot", target_version=">7.1.1") diff --git a/tests/atest/transformers/RenameVariables/expected/groups.robot b/tests/atest/transformers/RenameVariables/expected/groups.robot new file mode 100644 index 00000000..ec225352 --- /dev/null +++ b/tests/atest/transformers/RenameVariables/expected/groups.robot @@ -0,0 +1,23 @@ +*** Variables *** +${GLOBAL} global + + +*** Keywords *** +Not named + ${local} Keyword Call + GROUP + Keyword Call ${local} + ${local_from_group} Keyword Call ${GLOBAL} + END + Keyword Call ${local_from_group} + +Named with variable + GROUP Name with ${GLOBAL} + Keyword Call + END + GROUP Nested + VAR ${local} local + GROUP Named with ${local} + Keyword Call + END + END diff --git a/tests/atest/transformers/RenameVariables/source/groups.robot b/tests/atest/transformers/RenameVariables/source/groups.robot new file mode 100644 index 00000000..92c5b3a9 --- /dev/null +++ b/tests/atest/transformers/RenameVariables/source/groups.robot @@ -0,0 +1,23 @@ +*** Variables *** +${GLOBAL} global + + +*** Keywords *** +Not named + ${local} Keyword Call + GROUP + Keyword Call ${LOCAL} + ${local_from_group} Keyword Call ${global} + END + Keyword Call ${LOCAL_FROM_GROUP} + +Named with variable + GROUP Name with ${global} + Keyword Call + END + GROUP Nested + VAR ${local} local + GROUP Named with ${LOCAL} + Keyword Call + END + END diff --git a/tests/atest/transformers/RenameVariables/test_transformer.py b/tests/atest/transformers/RenameVariables/test_transformer.py index 695e482a..07b2526d 100644 --- a/tests/atest/transformers/RenameVariables/test_transformer.py +++ b/tests/atest/transformers/RenameVariables/test_transformer.py @@ -114,3 +114,6 @@ def test_var_syntax(self): def test_equal_sign_in_section(self): self.compare(source="equal_sign_in_section.robot") + + def test_groups(self): + self.compare(source="groups.robot", target_version=">7.1.1") diff --git a/tests/rf_versions_matrix/rf7/requirements.txt b/tests/rf_versions_matrix/rf7/requirements.txt index 535f2907..1d87f95a 100644 --- a/tests/rf_versions_matrix/rf7/requirements.txt +++ b/tests/rf_versions_matrix/rf7/requirements.txt @@ -1 +1 @@ -robotframework==7.1.1 \ No newline at end of file +robotframework==7.2rc1 \ No newline at end of file