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 [