Skip to content

[Enhancement] Robocop 6.0 (Robocop + Robotidy) support #411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
bhirsz opened this issue Mar 15, 2025 · 4 comments
Open

[Enhancement] Robocop 6.0 (Robocop + Robotidy) support #411

bhirsz opened this issue Mar 15, 2025 · 4 comments
Assignees
Labels
enhancement New feature or request

Comments

@bhirsz
Copy link

bhirsz commented Mar 15, 2025

Upcoming 6.0 Robocop + Robotidy (merged together) is a breaking change that will require rewriting how it's used in RobotCode.

For the linter part, I see your code here:

We will need something similar, with adjustment to new changes. I will not go in detail on what changed as it's very broad subject - I will mostly focus on achieving at least the same level of integration in Robot Code as before.

We can also use this issue as discussion point what is needed from Robocop to make it easier for RobotCode to support it.

@bhirsz
Copy link
Author

bhirsz commented Mar 15, 2025

Our 'base' for running the check for single file/model is still run_check method:

def run_check(self, model: File, file_path: Path, config: Config, in_memory_content: str | None = None) -> list[Diagnostic]:
    """
    Run all rules on file model and return list of diagnostics.

    Args:
        model: ast model of analyzed file
        filename: Path to analyzed file
        config: configuration closest to analyzed file
        in_memory_content: Used only if we are consuming stdin directly

    """
    disablers = DisablersFinder(model)
    if disablers.file_disabled:
        return []
    found_diagnostics = []
    templated = is_suite_templated(model)
    for checker in config.linter.checkers:
        if checker.disabled:
            continue
        found_diagnostics += [
            diagnostic
            for diagnostic in checker.scan_file(model, file_path, in_memory_content, templated)
            if not disablers.is_rule_disabled(diagnostic) and not diagnostic.severity < config.linter.threshold
        ]
    return found_diagnostics

To use it, you need model of file, path and configuration class. Configuration can be found using config_manager:

from robocop import config
from robocop.linter.runner import RobocopLinter
from robocop.linter.diagnostics import Diagnostics


model: File = ...
source: Path = Path(...)

config_manager = config.ConfigManager()
config = config_manager.get_config_for_source_file(source)
runner = RobocopLinter(config_manager)
diagnostics: list[Diagnostic] = runner.run_check(model, str(source), config)

@bhirsz
Copy link
Author

bhirsz commented Mar 15, 2025

ConfigManager caches already found config files, so it could be good enhancement for the future to keep it globally and only reload when toml files in project are modified. It may have huge impact on performance as we need to dynamically load rules every time we create config class. This was the case also for old implementation, but we didn't have way of caching found configs.

@bhirsz
Copy link
Author

bhirsz commented Mar 15, 2025

I will release 6.0b1 soon with above syntax (current 6.0a3 uses bit different run_check interface, but config_manager handling is the same).

@d-biehl d-biehl changed the title Robocop 6.0 (Robocop + Robotidy) support [Enhancement] Robocop 6.0 (Robocop + Robotidy) support Mar 17, 2025
@d-biehl d-biehl added the enhancement New feature or request label Mar 17, 2025
@d-biehl d-biehl self-assigned this Mar 17, 2025
@d-biehl d-biehl moved this to Backlog in RobotCode Mar 18, 2025
@bhirsz
Copy link
Author

bhirsz commented Mar 22, 2025

I have released 6.0b2 which I used for following code snippets.

Instead of mutating existing function even further, I would delegate it to another method:

    @_logger.call
    def collect(
        self,
        document: TextDocument,
        workspace_folder: WorkspaceFolder,
        extension_config: RoboCopConfig,
    ) -> List[Diagnostic]:
        from robocop import __version__
        robocop_version = create_version_from_str(__version__)
        if robocop_version >= (6, 0):
            return self.collect_new_robocop(document, workspace_folder, extension_config)
        
        from robocop.config import Config
        from robocop.rules import RuleSeverity
        from robocop.run import Robocop
        from robocop.utils.misc import is_suite_templated

But first I would consider if it's possible to move some imports into top module imports - not sure why they're inside function. But since Robocop imports rules dynamically it may have some effects I didn't consider when using it.

Then method for collecting diagnostics (I see now I also created Diagnostic class name.. so there is potentatial clash of names):

def collect_new_robocop(
    document: TextDocument, workspace_folder: WorkspaceFolder, extension_config: RoboCopConfig
) -> List[Diagnostic]:
    from robocop import config
    from robocop.linter.runner import RobocopLinter

    # We allow multiple configurations now. But it is possible to overwrite all found configs
    # with some option with 'overwrite_config'. In Robocop case it's used for parameters from CLI
    configure = extension_config.configurations if extension_config.configurations else None
    if extension_config.include or extension_config.exclude:
        exclude = set(extension_config.exclude) if extension_config.exclude else None
        include = set(extension_config.include) if extension_config.include else None
        file_filters = config.FileFiltersOptions(
            include=include, default_include=None, exclude=exclude, default_exclude=None
        )
    else:
        file_filters = None
    linter_config = config.LinterConfig(
        configure=configure,
        select=None,
        ignore=None,
        issue_format=None,
        threshold=None,
        custom_rules=None,
        reports=None,
        persistent=None,
        compare=None,
        exit_zero=None,
        return_result=True,
    )
    overwrite_config = config.Config(
        linter=linter_config,
        formatter=None,  # if some part is set to None, it's not overwritten
        file_filters=file_filters,
        language=None,
        verbose=None,
        target_version=None,
    )

    config_manager = config.ConfigManager(root=Path(workspace_folder.uri.to_path()), overwrite_config=overwrite_config)
    source = document.uri.to_path()
    config = config_manager.get_config_for_source_file(
        source
    )  # if you create manager once, you will save time as configs are cached
    runner = RobocopLinter(config_manager)
    model = self.parent.documents_cache.get_model(document, False)

    diagnostics = runner.run_check(model, source, config)
    return [
        Diagnostic(
            range=diag.range,
            # it appears Robocop and Robotcode have similar interface for diagnostic class - but verify if it's as expected
            message=diag.message,
            severity=(
                DiagnosticSeverity.INFORMATION
                if diag.severity == RuleSeverity.INFO
                else (
                    DiagnosticSeverity.WARNING
                    if diag.severity == RuleSeverity.WARNING
                    else (DiagnosticSeverity.ERROR if diag.severity == RuleSeverity.ERROR else DiagnosticSeverity.HINT)
                )
            ),
            source=self.source_name,
            code=f"{diag.name}-{diag.severity.value}{diag.rule_id}",
            # TODO: I would suggest using diag.rule.docs for example, I can also add something to Robocop
            code_description=self.get_code_description(robocop_version, diag),
        )
        for diag in diagnostics
    ]

There are some blanks / TODO and I didn't test it.. as I don't know how to setup your development environment. But if there is any issue let me know here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: Ready
Development

No branches or pull requests

2 participants