diff --git a/lib/galaxy/tool_util/linters/output.py b/lib/galaxy/tool_util/linters/output.py index 37ebab7343d4..4068bf4d9d42 100644 --- a/lib/galaxy/tool_util/linters/output.py +++ b/lib/galaxy/tool_util/linters/output.py @@ -1,5 +1,6 @@ """This module contains a linting functions for tool outputs.""" +import ast from typing import TYPE_CHECKING from packaging.version import Version @@ -76,6 +77,24 @@ def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): names.add(name) +class OutputsFilterExpression(Linter): + @classmethod + def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): + tool_xml = getattr(tool_source, "xml_tree", None) + if not tool_xml: + return + labels = set() + for filter in tool_xml.findall("./outputs//filter"): + try: + ast.parse(filter.text, mode="eval") + except Exception as e: + lint_ctx.error( + f"Filter '{filter.text}' is no valid expression: {str(e)}", + linter=cls.name(), + node=filter, + ) + + class OutputsLabelDuplicatedFilter(Linter): @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): diff --git a/test/unit/tool_util/test_tool_linters.py b/test/unit/tool_util/test_tool_linters.py index 7e6d3b62ec93..c0aad892546b 100644 --- a/test/unit/tool_util/test_tool_linters.py +++ b/test/unit/tool_util/test_tool_linters.py @@ -638,15 +638,31 @@ - a condition + a and condition - another condition + another or condition """ +# check if filters are valid python expressions +OUTPUTS_FILTER_EXPRESSION = """ + + + + an invalid condition + an and condition + + + another invalid condition + another or condition + + + +""" + # tool xml for repeats linter REPEATS = """ @@ -1678,6 +1694,25 @@ def test_outputs_duplicated_name_label(lint_ctx): assert len(lint_ctx.error_messages) == 1 +def test_outputs_filter_expression(lint_ctx): + """ """ + tool_source = get_xml_tool_source(OUTPUTS_FILTER_EXPRESSION) + run_lint_module(lint_ctx, output, tool_source) + assert "2 outputs found." in lint_ctx.info_messages + assert len(lint_ctx.info_messages) == 1 + assert not lint_ctx.valid_messages + assert not lint_ctx.warn_messages + assert ( + "Filter 'another invalid condition' is no valid expression: invalid syntax (, line 1)" + in lint_ctx.error_messages + ) + assert ( + "Filter 'another invalid condition' is no valid expression: invalid syntax (, line 1)" + in lint_ctx.error_messages + ) + assert len(lint_ctx.error_messages) == 2 + + def test_stdio_default_for_default_profile(lint_ctx): tool_source = get_xml_tool_source(STDIO_DEFAULT_FOR_DEFAULT_PROFILE) run_lint_module(lint_ctx, stdio, tool_source) @@ -2145,7 +2180,7 @@ def test_skip_by_module(lint_ctx): def test_list_linters(): linter_names = Linter.list_listers() # make sure to add/remove a test for new/removed linters if this number changes - assert len(linter_names) == 132 + assert len(linter_names) == 133 assert "Linter" not in linter_names # make sure that linters from all modules are available for prefix in [