Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

- TEMPLATE: ignore nf-core components during prettier linting ([#3858](https://github.com/nf-core/tools/pull/3858))
- update json schema store URL ([#3877](https://github.com/nf-core/tools/pull/3877))
- add word boundary for input, output and topic linting ([#3894](https://github.com/nf-core/tools/pull/3894))

### Modules

Expand Down
11 changes: 8 additions & 3 deletions nf_core/components/nfcore_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def get_inputs_from_main_nf(self) -> None:
for line in input_data.split("\n"):
channel_elements: Any = []
line = line.split("//")[0] # remove any trailing comments
regex = r"\b(val|path)\s*(\(([^)]+)\)|\s*([^)\s,]+))"
regex = r"\b(val|path)\b\s*(\(([^)]+)\)|\s*([^)\s,]+))"
matches = re.finditer(regex, line)
for _, match in enumerate(matches, start=1):
input_val = None
Expand All @@ -226,6 +226,7 @@ def get_inputs_from_main_nf(self) -> None:
elif len(channel_elements) > 1:
inputs.append(channel_elements)
log.debug(f"Found {len(inputs)} inputs in {self.main_nf}")
log.debug(f"Inputs: {inputs}")
self.inputs = inputs
elif self.component_type == "subworkflows":
# get input values from main.nf after "take:"
Expand All @@ -252,8 +253,9 @@ def get_outputs_from_main_nf(self):
log.debug(f"Could not find any outputs in {self.main_nf}")
return outputs
output_data = data.split("output:")[1].split("when:")[0]
log.debug(f"Found output_data: {output_data}")
regex_emit = r"emit:\s*([^)\s,]+)"
regex_elements = r"\b(val|path|env|stdout|eval)\s*(\(([^)]+)\)|\s*([^)\s,]+))"
regex_elements = r"\b(val|path|env|stdout|eval)\b\s*(\(([^)]+)\)|\s*([^)\s,]+))"
for line in output_data.split("\n"):
match_emit = re.search(regex_emit, line)
matches_elements = re.finditer(regex_elements, line)
Expand All @@ -278,6 +280,7 @@ def get_outputs_from_main_nf(self):
elif len(channel_elements) > 1:
outputs[match_emit.group(1)].append(channel_elements)
log.debug(f"Found {len(list(outputs.keys()))} outputs in {self.main_nf}")
log.debug(f"Outputs: {outputs}")
self.outputs = outputs
elif self.component_type == "subworkflows":
outputs = []
Expand Down Expand Up @@ -306,8 +309,9 @@ def get_topics_from_main_nf(self) -> None:
self.topics = topics
return
output_data = data.split("output:")[1].split("when:")[0]
log.debug(f"Output data: {output_data}")
regex_topic = r"topic:\s*([^)\s,]+)"
regex_elements = r"\b(val|path|env|stdout|eval)\s*(\(([^)]+)\)|\s*([^)\s,]+))"
regex_elements = r"\b(val|path|env|stdout|eval)\b\s*(\(([^)]+)\)|\s*([^)\s,]+))"
for line in output_data.split("\n"):
match_topic = re.search(regex_topic, line)
matches_elements = re.finditer(regex_elements, line)
Expand All @@ -331,4 +335,5 @@ def get_topics_from_main_nf(self) -> None:
elif len(channel_elements) > 1:
topics[match_topic.group(1)].append(channel_elements)
log.debug(f"Found {len(list(topics.keys()))} topics in {self.main_nf}")
log.debug(f"Topics: {topics}")
self.topics = topics
120 changes: 119 additions & 1 deletion tests/modules/lint/test_main_nf.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

import nf_core.modules.lint
import nf_core.modules.patch
from nf_core.components.nfcore_component import NFCoreComponent
from nf_core.modules.lint.main_nf import check_container_link_line, check_process_labels

from ...test_modules import TestModules
Expand Down Expand Up @@ -154,3 +154,121 @@ def test_topics_and_emits_version_check(self):
f"Linting warned with {[x.__dict__ for x in module_lint.warned]}, expected 1 warning"
)
assert len(module_lint.passed) > 0


def test_get_inputs_no_partial_keyword_match(tmp_path):
"""Test that input parsing doesn't match keywords within larger words like 'evaluate' or 'pathogen'"""
main_nf_content = """
process TEST_PROCESS {
input:
val(meta)
path(reads)
tuple val(evaluate), path(pathogen)

output:
path("*.txt"), emit: results

script:
"echo test"
}
"""
main_nf_path = tmp_path / "main.nf"
main_nf_path.write_text(main_nf_content)

component = NFCoreComponent(
component_name="test",
repo_url=None,
component_dir=tmp_path,
repo_type="modules",
base_dir=tmp_path,
component_type="modules",
remote_component=False,
)

component.get_inputs_from_main_nf()

# Should find 3 inputs: meta, reads, and the tuple (evaluate, pathogen)
# The regex with \b should correctly identify 'val(evaluate)' and 'path(pathogen)' as valid inputs
assert len(component.inputs) == 3, f"Expected 3 inputs, got {len(component.inputs)}: {component.inputs}"
print(component.inputs)
assert {"meta": {}} in component.inputs
assert {"reads": {}} in component.inputs
# The tuple should be captured as a list of two elements
tuple_input = [{"evaluate": {}}, {"pathogen": {}}]
assert tuple_input in component.inputs


def test_get_outputs_no_partial_keyword_match(tmp_path):
"""Test that output parsing doesn't match keywords within larger words like 'evaluate' or 'pathogen'"""
main_nf_content = """
process TEST_PROCESS {
input:
val(meta)

output:
path("*.txt"), emit: results
val(evaluate_result), emit: evaluation
path(pathogen_data), emit: pathogens

script:
"echo test"
}
"""
main_nf_path = tmp_path / "main.nf"
main_nf_path.write_text(main_nf_content)

component = NFCoreComponent(
component_name="test",
repo_url=None,
component_dir=tmp_path,
repo_type="modules",
base_dir=tmp_path,
component_type="modules",
remote_component=False,
)

component.get_outputs_from_main_nf()

# Should find 3 outputs with variable names containing 'val' and 'path' substrings
# The regex with \b should correctly identify val(evaluate_result) and path(pathogen_data)
assert len(component.outputs) == 3, f"Expected 3 outputs, got {len(component.outputs)}: {component.outputs}"
assert "results" in component.outputs
assert "evaluation" in component.outputs
assert "pathogens" in component.outputs


def test_get_topics_no_partial_keyword_match(tmp_path):
"""Test that topic parsing doesn't match keywords within larger words like 'evaluate'"""
main_nf_content = """
process TEST_PROCESS {
input:
val(meta)

output:
path("*.txt"), topic: results
val(evaluate_result), topic: evaluation

script:
"echo test"
}
"""
main_nf_path = tmp_path / "main.nf"
main_nf_path.write_text(main_nf_content)

component = NFCoreComponent(
component_name="test",
repo_url=None,
component_dir=tmp_path,
repo_type="modules",
base_dir=tmp_path,
component_type="modules",
remote_component=False,
)

component.get_topics_from_main_nf()

# Should find 2 topics with variable names containing 'val' substring
# The regex with \b should correctly identify val(evaluate_result)
assert len(component.topics) == 2, f"Expected 2 topics, got {len(component.topics)}: {component.topics}"
assert "results" in component.topics
assert "evaluation" in component.topics