From b36999500195bd9fdfc3c8c4005dc283667a704a Mon Sep 17 00:00:00 2001 From: rajeswari1301 Date: Tue, 2 Jun 2026 11:09:33 -0500 Subject: [PATCH] add duplicate block id detection Co-authored-by: Copilot --- src/dayamlchecker/messages.py | 8 ++++ src/dayamlchecker/yaml_structure.py | 18 +++++++ tests/test_yaml_structure.py | 74 +++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/src/dayamlchecker/messages.py b/src/dayamlchecker/messages.py index 2fedc18..cc85e13 100644 --- a/src/dayamlchecker/messages.py +++ b/src/dayamlchecker/messages.py @@ -19,6 +19,7 @@ class FindingClass(StrEnum): class MessageId(StrEnum): YAML_DUPLICATE_KEY = "yaml_duplicate_key" + YAML_DUPLICATE_BLOCK_ID = "yaml_duplicate_block_id" YAML_PARSE_ERROR = "yaml_parse_error" YAML_STRING_REQUIRED = "yaml_string_required" @@ -190,6 +191,13 @@ class MessageDefinition: summary="YAML parsing error", template="{error}", ), + MessageId.YAML_DUPLICATE_BLOCK_ID: MessageDefinition( + code="EG104", + severity=Severity.ERROR, + finding_class=FindingClass.GENERAL, + summary="Duplicate block id", + template='Duplicate block id "{block_id}" - first used at line {first_line}. Docassemble will silently use the later block, which is almost certainly a mistake', + ), MessageId.YAML_STRING_REQUIRED: MessageDefinition( code="EG103", severity=Severity.ERROR, diff --git a/src/dayamlchecker/yaml_structure.py b/src/dayamlchecker/yaml_structure.py index 9251729..6b15c74 100644 --- a/src/dayamlchecker/yaml_structure.py +++ b/src/dayamlchecker/yaml_structure.py @@ -1580,6 +1580,7 @@ def find_errors_from_string( ] yaml_parser = _make_yaml_parser() prior_conditional_fields: list[dict[str, Any]] = [] + seen_ids: dict[str, int] = {} parsed_docs: list[ParsedInterviewDocument] = [] has_yaml_parse_errors = False line_number = 1 @@ -1704,6 +1705,23 @@ def find_errors_from_string( ) ) + block_id = _get_case_insensitive(doc, "id") + if isinstance(block_id, str) and block_id.strip(): + block_id_clean = block_id.strip() + block_start = line_number + 1 if line_number > 1 else line_number + if block_id_clean in seen_ids: + all_errors.append( + make_finding( + MessageId.YAML_DUPLICATE_BLOCK_ID, + file_name=input_file, + line_number=block_start, + block_id=block_id_clean, + first_line=seen_ids[block_id_clean], + ) + ) + else: + seen_ids[block_id_clean] = block_start + unmatched_refs = _find_unmatched_interview_order_references( doc, prior_conditional_fields ) diff --git a/tests/test_yaml_structure.py b/tests/test_yaml_structure.py index 241bd08..b54f1ff 100644 --- a/tests/test_yaml_structure.py +++ b/tests/test_yaml_structure.py @@ -2035,6 +2035,80 @@ def test_accessibility_html_rules_are_reported(self): f"Expected HTML accessibility parity findings, got: {errs}", ) + def test_duplicate_block_id_errors(self): + """Error: two blocks with the same id should be flagged""" + invalid = """ +id: intro +question: | + What is your name? +field: user_name +--- +id: intro +mandatory: True +question: | + Hello +""" + errs = find_errors_from_string(invalid, input_file="") + self.assertTrue( + any(e.message_id == "yaml_duplicate_block_id" for e in errs), + f"Expected duplicate id error, got: {errs}", + ) + + def test_unique_block_ids_valid(self): + """Valid: blocks with different ids should pass""" + valid = """ +id: intro +question: | + What is your name? +field: user_name +--- +id: summary +mandatory: True +question: | + Hello +""" + errs = find_errors_from_string(valid, input_file="") + self.assertFalse( + any(e.message_id == "yaml_duplicate_block_id" for e in errs), + f"Did not expect duplicate id error, got: {errs}", + ) + + def test_duplicate_id_case_sensitive(self): + """Valid: id matching is case sensitive""" + valid = """ +id: intro +question: | + What is your name? +field: user_name +--- +id: Intro +mandatory: True +question: | + Hello +""" + errs = find_errors_from_string(valid, input_file="") + self.assertFalse( + any(e.message_id == "yaml_duplicate_block_id" for e in errs), + f"Did not expect duplicate id error for different cases, got: {errs}", + ) + + def test_no_ids_valid(self): + """Valid: blocks with no ids should pass clean""" + valid = """ +question: | + What is your name? +field: user_name +--- +mandatory: True +question: | + Hello +""" + errs = find_errors_from_string(valid, input_file="") + self.assertFalse( + any(e.message_id == "yaml_duplicate_block_id" for e in errs), + f"Did not expect duplicate id error with no ids, got: {errs}", + ) + if __name__ == "__main__": unittest.main()