Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion nf_core/modules/lint/main_nf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

log = logging.getLogger(__name__)
MAIN_NF_VERSIONS_YML_TOPIC_RULE = Path(__file__).parent / "rules" / "main_nf_versions_yml_topic.yml"
MAIN_NF_LEGACY_VERSIONS_EMIT_RULE = Path(__file__).parent / "rules" / "main_nf_legacy_versions_emit.yml"


def main_nf(
Expand Down Expand Up @@ -323,7 +324,7 @@ def main_nf(
module.passed.append(
("main_nf", "main_nf_version_emit", "Module emits each software version", module.main_nf)
)
elif "versions" in emits:
elif _has_legacy_versions_emit(lines_j, lines):
module.warned.append(
(
"main_nf",
Expand Down Expand Up @@ -972,6 +973,21 @@ def _parse_output_topics(self, line: str) -> list[str]:
return output


def _has_legacy_versions_emit(source: str, output_lines: list[str]) -> bool:
rule = astgrep.load_rule(MAIN_NF_LEGACY_VERSIONS_EMIT_RULE)
if astgrep.nextflow_available():
from ast_grep_py import SgRoot

root = SgRoot(source, "nextflow").root()
if root.find(kind="ERROR") is None:
config = {key: rule[key] for key in ("rule", "constraints", "utils") if key in rule}
return bool(root.find_all(config=config))

return any(
re.search(r"emit:\s*versions\b", line) and not re.search(r"topic:\s*versions\b", line) for line in output_lines
)


def _has_inline_versions_yml_topic_output(source: str, output_lines: list[str], command_lines: list[str]) -> bool:
rule = astgrep.load_rule(MAIN_NF_VERSIONS_YML_TOPIC_RULE)
if astgrep.nextflow_available():
Expand Down
17 changes: 17 additions & 0 deletions nf_core/modules/lint/rules/main_nf_legacy_versions_emit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ast-grep lint rule (https://ast-grep.github.io/guide/project/lint-rule.html)
id: main_nf_legacy_versions_emit
language: nextflow
severity: warning
message: "legacy `versions` output emit"
note: |
Legacy `emit: versions` outputs should migrate to version tuple outputs with
`topic: versions`. Template-backed modules that also set `topic: versions`
are intentionally allowed.
metadata:
fallback-regex: a^
rule:
kind: output_declaration
all:
- regex: 'emit:\s*versions\b'
- not:
regex: 'topic:\s*versions\b'
20 changes: 20 additions & 0 deletions tests/modules/lint/test_main_nf.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from nf_core.components.nfcore_component import NFCoreComponent
from nf_core.modules.lint.main_nf import (
_has_inline_versions_yml_topic_output,
_has_legacy_versions_emit,
_parse_output_topics,
check_container_link_line,
check_nf_module_name,
Expand Down Expand Up @@ -43,6 +44,25 @@ def test_inline_versions_yml_topic_output_matches_full_source_with_astgrep():
assert _has_inline_versions_yml_topic_output(source, [], [])


@pytest.mark.skipif(not astgrep.nextflow_available(), reason="tree-sitter-nextflow parser not available")
@pytest.mark.parametrize(
"output_line, expected",
[
('path "versions.yml", emit: versions', True),
('path "versions.yml", emit: versions, topic: versions', False),
],
)
def test_legacy_versions_emit_astgrep_detection(output_line, expected):
source = f"""
process TEST {{
output:
{output_line}
}}
"""

assert _has_legacy_versions_emit(source, []) is expected


@pytest.mark.parametrize(
"content,passed,warned,failed",
[
Expand Down
Loading