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
3 changes: 3 additions & 0 deletions src/rxiv_maker/engines/operations/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from ...validators.latex_error_parser import LaTeXErrorParser
from ...validators.math_validator import MathValidator
from ...validators.reference_validator import ReferenceValidator
from ...validators.section_validator import SectionValidator
from ...validators.syntax_validator import SyntaxValidator

VALIDATORS_AVAILABLE = True
Expand All @@ -42,6 +43,7 @@
from rxiv_maker.validators.latex_error_parser import LaTeXErrorParser # type: ignore
from rxiv_maker.validators.math_validator import MathValidator # type: ignore
from rxiv_maker.validators.reference_validator import ReferenceValidator # type: ignore
from rxiv_maker.validators.section_validator import SectionValidator # type: ignore
from rxiv_maker.validators.syntax_validator import SyntaxValidator # type: ignore

VALIDATORS_AVAILABLE = True
Expand Down Expand Up @@ -95,6 +97,7 @@ def validate_all(self) -> bool:
print()

validators = [
("Structure", SectionValidator),
("Citations", CitationValidator),
("Cross-references", ReferenceValidator),
("Figures", FigureValidator),
Expand Down
139 changes: 139 additions & 0 deletions src/rxiv_maker/validators/section_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""Section validator for checking recommended manuscript sections.

This validator checks if manuscripts include important sections that are
commonly required by journals, such as:
- Data Availability
- Code Availability
- Author Contributions
- Acknowledgements
- Funding
- Competing Interests

Missing sections generate warnings (not errors) to help authors ensure
their manuscript is complete before submission.
"""

import re

from ..utils.file_helpers import find_manuscript_md
from .base_validator import BaseValidator, ValidationError, ValidationLevel, ValidationResult


class SectionValidator(BaseValidator):
"""Validator for checking recommended manuscript sections."""

# Required sections that should generate warnings if missing
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment states "Required sections" but the constant is named "RECOMMENDED_SECTIONS". The comment should say "Recommended sections" to match the variable name and the actual behavior (warnings, not errors).

Suggested change
# Required sections that should generate warnings if missing
# Recommended sections that should generate warnings if missing

Copilot uses AI. Check for mistakes.
RECOMMENDED_SECTIONS = {
"data availability": {
"patterns": [
r"##\s+Data\s+Availability",
r"##\s+Data\s+and\s+Code\s+Availability",
],
"suggestion": "Add a '## Data Availability' section describing where your data can be accessed",
},
"code availability": {
"patterns": [
r"##\s+Code\s+Availability",
r"##\s+Data\s+and\s+Code\s+Availability",
r"##\s+Software\s+Availability",
],
"suggestion": "Add a '## Code Availability' section with links to code repositories",
},
"author contributions": {
"patterns": [
r"##\s+Author\s+Contributions?",
r"##\s+Contributions?",
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern r"##\s+Contributions?" is too broad and could match any section titled "## Contributions" or "## Contribution" without the word "Author". Consider making this pattern more specific to avoid false positives, or remove it if only "Author Contributions" should be matched.

Suggested change
r"##\s+Contributions?",

Copilot uses AI. Check for mistakes.
],
"suggestion": "Add an '## Author Contributions' section describing each author's role",
},
"acknowledgements": {
"patterns": [
r"##\s+Acknowledgements?",
r"##\s+Acknowledgments?",
],
"suggestion": "Add an '## Acknowledgements' section to thank contributors",
},
"funding": {
"patterns": [
r"##\s+Funding",
r"##\s+Financial\s+Support",
r"##\s+Grant\s+Information",
],
"suggestion": "Add a '## Funding' section declaring funding sources or stating 'This research received no external funding.'",
},
"competing interests": {
"patterns": [
r"##\s+Competing\s+Interests?",
r"##\s+Conflicts?\s+of\s+Interest",
r"##\s+Disclosure",
],
"suggestion": "Add a '## Competing Interests' section, even if just to state 'The authors declare no competing interests.'",
},
}

def validate(self) -> ValidationResult:
"""Check for recommended manuscript sections.

Returns:
ValidationResult with warnings for missing sections
"""
errors: list[ValidationError] = []
metadata = {
"missing_sections": [],
"found_sections": [],
}

# Find manuscript file
try:
manuscript_file = find_manuscript_md(self.manuscript_path)
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Path object returned by find_manuscript_md should be converted to a string before passing to file_path parameter. The ValidationError.file_path is typed as str | None, but find_manuscript_md returns a Path object. For consistency with other validators and proper type handling, convert the Path to string using str().

Copilot uses AI. Check for mistakes.
except (FileNotFoundError, ValueError) as e:
return ValidationResult(
validator_name=self.name,
errors=[
self._create_error(
level=ValidationLevel.ERROR,
message=f"Could not find manuscript file: {e}",
)
],
metadata=metadata,
)

# Read manuscript content
content = self._read_file_safely(manuscript_file)
if content is None:
return ValidationResult(
validator_name=self.name,
errors=[
self._create_error(
level=ValidationLevel.ERROR,
message=f"Could not read manuscript file: {manuscript_file}",
)
],
metadata=metadata,
)

# Check for each recommended section
for section_name, section_info in self.RECOMMENDED_SECTIONS.items():
found = False
for pattern in section_info["patterns"]:
if re.search(pattern, content, re.IGNORECASE):
found = True
metadata["found_sections"].append(section_name)
break

if not found:
metadata["missing_sections"].append(section_name)
errors.append(
self._create_error(
level=ValidationLevel.WARNING,
message=f"Recommended section '{section_name.title()}' not found",
file_path=manuscript_file,
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Path object returned by find_manuscript_md should be converted to a string before passing to file_path parameter. The ValidationError.file_path is typed as str | None, and should match the expected type. Convert using str(manuscript_file).

Suggested change
file_path=manuscript_file,
file_path=str(manuscript_file),

Copilot uses AI. Check for mistakes.
suggestion=section_info["suggestion"],
)
)

return ValidationResult(
validator_name=self.name,
errors=errors,
metadata=metadata,
)
Comment on lines +22 to +139
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new SectionValidator lacks test coverage. The repository has comprehensive test coverage for other validators (see tests/unit/test_validators.py), and this new validator should have corresponding tests to verify pattern matching, warning generation, and edge cases like missing files or combined sections.

Copilot uses AI. Check for mistakes.
Loading