|
18 | 18 | import file_utils |
19 | 19 | import plain_spec |
20 | 20 | from plain2code_exceptions import ( |
| 21 | + ImportedModuleWithFunctionalitiesError, |
21 | 22 | MissingFunctionalitiesError, |
22 | 23 | ModuleDoesNotExistError, |
23 | 24 | PlainSyntaxError, |
@@ -156,17 +157,27 @@ def process_code_variables(plain_source, code_variables): |
156 | 157 | process_section_code_variables(requirement, code_variables) |
157 | 158 |
|
158 | 159 |
|
159 | | -def check_if_functional_requirements_are_specified(plain_source, non_functional_requirements): |
160 | | - if plain_source[plain_spec.NON_FUNCTIONAL_REQUIREMENTS] is not None and hasattr( |
161 | | - plain_source[plain_spec.NON_FUNCTIONAL_REQUIREMENTS], "children" |
162 | | - ): |
163 | | - non_functional_requirements.extend(plain_source[plain_spec.NON_FUNCTIONAL_REQUIREMENTS].children) |
| 160 | +def has_functional_specs_section(plain_source) -> bool: |
| 161 | + """Whether the module declares a ***functional specs*** section (even if it is empty).""" |
| 162 | + return plain_spec.FUNCTIONAL_REQUIREMENTS in plain_source |
| 163 | + |
| 164 | + |
| 165 | +def count_functionalities(plain_source) -> int: |
| 166 | + """Number of functionalities under the ***functional specs*** section (0 if absent or empty).""" |
| 167 | + section = plain_source.get(plain_spec.FUNCTIONAL_REQUIREMENTS) |
| 168 | + return len(section.children) if section is not None else 0 |
164 | 169 |
|
165 | | - found_functional_requirements = plain_spec.FUNCTIONAL_REQUIREMENTS in plain_source |
166 | | - if found_functional_requirements and len(non_functional_requirements) == 0: |
167 | | - raise PlainSyntaxError("Plain syntax error: functionality with no implementation reqs specified.") |
168 | 170 |
|
169 | | - return found_functional_requirements |
| 171 | +def validate_functionalities_have_implementation_reqs(plain_source) -> None: |
| 172 | + """Raise if the module has functionalities but no implementation reqs are specified.""" |
| 173 | + implementation_reqs = plain_source[plain_spec.NON_FUNCTIONAL_REQUIREMENTS] |
| 174 | + has_implementation_reqs = ( |
| 175 | + implementation_reqs is not None |
| 176 | + and hasattr(implementation_reqs, "children") |
| 177 | + and len(implementation_reqs.children) > 0 |
| 178 | + ) |
| 179 | + if not has_implementation_reqs: |
| 180 | + raise PlainSyntaxError("Plain syntax error: functionality with no implementation reqs specified.") |
170 | 181 |
|
171 | 182 |
|
172 | 183 | def _is_acceptance_test_heading(token) -> tuple[bool, str | None]: |
@@ -390,8 +401,11 @@ def process_imports( |
390 | 401 | module_name, code_variables, template_dirs, imported_modules, modules_trace |
391 | 402 | ) |
392 | 403 |
|
393 | | - if check_if_functional_requirements_are_specified(plain_file_parse_result.plain_source, []): |
394 | | - raise PlainSyntaxError("Plain syntax error: Imported module must not contain functionalities.") |
| 404 | + if has_functional_specs_section(plain_file_parse_result.plain_source): |
| 405 | + raise ImportedModuleWithFunctionalitiesError( |
| 406 | + f"Module {module_name} is imported but contains functional specs. " |
| 407 | + f"Imported modules may only provide definitions, implementation reqs, and test reqs." |
| 408 | + ) |
395 | 409 |
|
396 | 410 | for specification_heading in plain_file_parse_result.plain_source: |
397 | 411 | if specification_heading not in plain_spec.ALLOWED_IMPORT_SPECIFICATION_HEADINGS: |
@@ -731,24 +745,21 @@ def plain_file_parser( # noqa: C901 |
731 | 745 | f"Plain syntax error: Not all required concepts were defined. {missing_required_concepts_msg}." |
732 | 746 | ) |
733 | 747 |
|
734 | | - fr_section = plain_file_parse_result.plain_source.get(plain_spec.FUNCTIONAL_REQUIREMENTS) |
735 | | - |
736 | | - if fr_section is None: |
737 | | - # Case 1: no ***functional specs*** header at all. Not a syntax error. |
| 748 | + if not has_functional_specs_section(plain_file_parse_result.plain_source): |
| 749 | + # No ***functional specs*** section at all: valid as an import, but not renderable. Usage error. |
738 | 750 | raise MissingFunctionalitiesError( |
739 | 751 | f"Module {module_name} does not have any functionality specified. " |
740 | 752 | f"At least one functionality is required for rendering." |
741 | 753 | ) |
742 | 754 |
|
743 | | - if len(fr_section.children) == 0: |
744 | | - # Case 2: ***functional specs*** header present but empty. Treated as a syntax error. |
| 755 | + if count_functionalities(plain_file_parse_result.plain_source) == 0: |
| 756 | + # ***functional specs*** section present but empty: invalid in every role. Syntax error. |
745 | 757 | raise PlainSyntaxError( |
746 | 758 | f"Plain syntax error: Module '{module_name}' has an empty " |
747 | 759 | f"'{plain_spec.FUNCTIONAL_REQUIREMENTS}' section. At least one functionality must be specified." |
748 | 760 | ) |
749 | 761 |
|
750 | | - # Functionalities exist: keep the existing "functionality with no implementation reqs specified" check. |
751 | | - check_if_functional_requirements_are_specified(plain_file_parse_result.plain_source, []) |
| 762 | + validate_functionalities_have_implementation_reqs(plain_file_parse_result.plain_source) |
752 | 763 |
|
753 | 764 | exported_definitions = process_required_modules( |
754 | 765 | plain_file_parse_result.required_modules, |
|
0 commit comments