Skip to content

Comments

Set sequenceGroup optional when all child elements are optional#39

Closed
Nuvindu wants to merge 4 commits intoballerina-platform:mainfrom
Nuvindu:sequence
Closed

Set sequenceGroup optional when all child elements are optional#39
Nuvindu wants to merge 4 commits intoballerina-platform:mainfrom
Nuvindu:sequence

Conversation

@Nuvindu
Copy link
Contributor

@Nuvindu Nuvindu commented Feb 18, 2026

Purpose

$subject

Summary

This pull request introduces logic to automatically mark sequence groups as optional when all of their child elements are optional, improving the accuracy of generated XML data binding code.

Changes

Core Implementation:

  • Updated XSDVisitorImpl.java to compute whether all child elements in a sequence are optional and conditionally append a question mark to the sequence group name when this condition is true
  • Added a new helper method to determine if all element children have minOccurs set to 0
  • Modified the applySequenceAnnotation method signature to accept a boolean flag indicating whether all children are optional

Test Coverage:

  • Added a new test case for the feature with test resources 45_sequence_with_all_optional_elements.xsd and corresponding expected output
  • Updated the test configuration to reference the correct resource path

Test Expectations:

  • Updated numerous expected test output files across multiple test schemas to reflect the new behavior where sequence groups containing only optional elements are now marked as optional (with ? suffix)
  • Affected files include complex schema test resources and multi-type test definitions, with approximately 30+ sequence group fields updated to optional status

Impact

This change ensures that generated Ballerina record types more accurately reflect the optionality constraints defined in the source XSD schemas, reducing the need for manual adjustments to the generated code and improving data binding semantics.

@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

The pull request introduces logic to mark XSD sequences as optional when all their child elements are optional. A new helper method determines if all child elements have minOccurs=0, and this flag is passed to the sequence annotation method to conditionally append a question mark to sequence names in generated Ballerina code.

Changes

Cohort / File(s) Summary
Core Implementation
xsd-core/src/main/java/io/ballerina/xsd/core/visitor/XSDVisitorImpl.java
Added areAllChildrenOptional() helper method to detect sequences where all child elements are optional. Updated applySequenceAnnotation() signature to accept a boolean flag controlling whether to append ? to sequence names when all children are optional.
Test Infrastructure
xsd-core/src/test/java/io/ballerina/xsd/core/XSDToRecordTest.java
Added new test case for 45_sequence_with_all_optional_elements pairing XSD source with expected BAL output. Fixed test resource path resolution.
Test Resources - New Test Case
xsd-core/src/test/resources/xml/45_sequence_with_all_optional_elements.xsd, xsd-core/src/test/resources/expected/45_sequence_with_all_optional_elements.bal
New XSD schema defining Item and Order types with optional and required sequence elements. Corresponding generated Ballerina record types with optional SequenceGroup fields and xmldata annotations.
Test Resources - Expected Output Updates
xsd-core/src/test/resources/expected/14_elements_with_multiple_complex_types.bal, 18_complex_schema.bal, 19_complex_type_with_extensions.bal, 23_complex_schema.bal, 24_complex_schema.bal, 25_complex_schema.bal, 26_complex_schema.bal, 27_complex_schema.bal, 28_complex_schema.bal, 32_elements_with_imports.bal, 34_elements_with_simple_types.bal
Updated numerous SequenceGroup field declarations across public record types from required to optional by adding ? suffix (e.g., SequenceGroup7 sequenceGroup7SequenceGroup7 sequenceGroup7?). Changes reflect impact of core logic modification on generated code output.

Sequence Diagram(s)

Sequence diagram generation does not apply to these changes. While the implementation introduces new control flow for determining sequence optionality, the changes primarily affect code generation logic within a single visitor method rather than introducing interactions between multiple distinct system components with a clear sequential flow that would benefit from visualization.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A sequence of changes hops through the code,
Where optional elements lighten the load,
With question marks sprouting like clover so fair,
The schemas transform with thoughtful care,
All children now optional—flexibility's key,
XSD-to-Ballerina flows wild and free!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is largely incomplete. While it includes the Purpose section, it only contains a placeholder reference to $subject. All other required template sections (Goals, Approach, User stories, Release note, Documentation, Training, Certification, Marketing, Automation tests, Security checks, Samples, Related PRs, Migrations, Test environment, Learning) are missing or empty. Complete the pull request description by filling in all required template sections. At minimum, add Goals explaining the solution, Approach describing the implementation, Automation tests section with unit and integration test details, and other relevant sections.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly and specifically describes the main change: making sequenceGroup optional when all child elements are optional, which is the core objective of this PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
xsd-core/src/main/java/io/ballerina/xsd/core/visitor/XSDVisitorImpl.java (1)

749-769: ⚠️ Potential issue | 🟠 Major

Include sequence minOccurs=0 in the optionality decision.

Right now optionality is driven only by allChildrenOptional. A sequence with minOccurs=0 but required children should also render the sequence group as optional; this currently stays required.

🔧 Suggested fix
         builder.append(WHITESPACE).append(convertToCamelCase(sequenceName));
-        if (allChildrenOptional) {
+        boolean sequenceOptional = allChildrenOptional || ZERO.equals(minOccurrence);
+        if (sequenceOptional) {
             builder.append(QUESTION_MARK);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xsd-core/src/main/java/io/ballerina/xsd/core/visitor/XSDVisitorImpl.java`
around lines 749 - 769, In applySequenceAnnotation, optionality is only decided
by allChildrenOptional so sequences with minOccurs="0" remain required; update
the optionality check that appends QUESTION_MARK to consider minOccurrence as
well (e.g., treat minOccurrence == "0" as optional) by changing the condition
that currently uses allChildrenOptional to (allChildrenOptional ||
"0".equals(minOccurrence)) so sequences with minOccurs=0 render as optional;
refer to applySequenceAnnotation, minOccurrence, ONE, and the
builder.append(QUESTION_MARK) call to locate the change.
🧹 Nitpick comments (1)
xsd-core/src/test/java/io/ballerina/xsd/core/XSDToRecordTest.java (1)

31-33: Avoid repo-root–dependent resource paths.

Hard-coding xsd-core/src/test/resources makes the test sensitive to the working directory. Consider resolving from the test classpath to avoid failures when running tests from the module directory or IDE.

🔧 Suggested refactor
 import java.nio.file.Paths;
+import java.util.Objects;
 import java.util.stream.Stream;
 
 public class XSDToRecordTest {
-    private static final Path RES_DIR = Paths.get("xsd-core/src/test/resources/").toAbsolutePath();
+    private static final Path RES_DIR = Paths.get(
+            Objects.requireNonNull(XSDToRecordTest.class.getClassLoader().getResource("")).getPath());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xsd-core/src/test/java/io/ballerina/xsd/core/XSDToRecordTest.java` around
lines 31 - 33, The test currently hard-codes RES_DIR =
Paths.get("xsd-core/src/test/resources").toAbsolutePath() which breaks when
tests are run from different working directories; change the code in
XSDToRecordTest to resolve test resources from the classpath instead (use
XSDToRecordTest.class.getResource(...) or
Thread.currentThread().getContextClassLoader().getResource/getResourceAsStream)
to locate the "xml" resource directory or individual files rather than building
a repo-root path, and update usages of RES_DIR and XML_DIR to use the URL/Path
returned by the classloader (or use getResourceAsStream for file reads) so tests
work regardless of working directory or IDE.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@xsd-core/src/main/java/io/ballerina/xsd/core/visitor/XSDVisitorImpl.java`:
- Around line 773-788: areAllChildrenOptional currently only inspects children
whose localName equals ELEMENT, so required particles like CHOICE/SEQUENCE/ANY
are ignored; update areAllChildrenOptional to treat other particle node types
(e.g., CHOICE, SEQUENCE, ANY) the same way as ELEMENT: for any child whose
localName is one of ELEMENT, CHOICE, SEQUENCE, ANY (use the existing ELEMENT,
MIN_OCCURS, ZERO constants and add CHOICE/SEQUENCE/ANY constants if needed),
check its minOccurs attribute and return false if minOccurs is absent or not
"0"; also mark that a particle was seen (replace hasElement with hasParticle or
set hasElement when any of these particle types are encountered) so the method
only returns true when at least one particle was inspected.

---

Outside diff comments:
In `@xsd-core/src/main/java/io/ballerina/xsd/core/visitor/XSDVisitorImpl.java`:
- Around line 749-769: In applySequenceAnnotation, optionality is only decided
by allChildrenOptional so sequences with minOccurs="0" remain required; update
the optionality check that appends QUESTION_MARK to consider minOccurrence as
well (e.g., treat minOccurrence == "0" as optional) by changing the condition
that currently uses allChildrenOptional to (allChildrenOptional ||
"0".equals(minOccurrence)) so sequences with minOccurs=0 render as optional;
refer to applySequenceAnnotation, minOccurrence, ONE, and the
builder.append(QUESTION_MARK) call to locate the change.

---

Nitpick comments:
In `@xsd-core/src/test/java/io/ballerina/xsd/core/XSDToRecordTest.java`:
- Around line 31-33: The test currently hard-codes RES_DIR =
Paths.get("xsd-core/src/test/resources").toAbsolutePath() which breaks when
tests are run from different working directories; change the code in
XSDToRecordTest to resolve test resources from the classpath instead (use
XSDToRecordTest.class.getResource(...) or
Thread.currentThread().getContextClassLoader().getResource/getResourceAsStream)
to locate the "xml" resource directory or individual files rather than building
a repo-root path, and update usages of RES_DIR and XML_DIR to use the URL/Path
returned by the classloader (or use getResourceAsStream for file reads) so tests
work regardless of working directory or IDE.

Comment on lines +773 to +788
private boolean areAllChildrenOptional(NodeList childNodes) {
boolean hasElement = false;
for (Node child : asIterable(childNodes)) {
if (child.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
if (!ELEMENT.equals(child.getLocalName())) {
continue;
}
hasElement = true;
Node minOccursAttr = child.getAttributes().getNamedItem(MIN_OCCURS);
if (minOccursAttr == null || !ZERO.equals(minOccursAttr.getNodeValue())) {
return false;
}
}
return hasElement;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Optionality check ignores non-element particles.

areAllChildrenOptional only looks at <element> nodes. If a sequence contains a required <choice>, <sequence>, or <any>, the method can still return true (when element children are optional), causing an incorrect optional sequence group.

🔧 Suggested fix
-        for (Node child : asIterable(childNodes)) {
-            if (child.getNodeType() != Node.ELEMENT_NODE) {
-                continue;
-            }
-            if (!ELEMENT.equals(child.getLocalName())) {
-                continue;
-            }
+        for (Node child : asIterable(childNodes)) {
+            if (child.getNodeType() != Node.ELEMENT_NODE
+                    || ANNOTATION.equals(child.getLocalName())) {
+                continue;
+            }
             hasElement = true;
             Node minOccursAttr = child.getAttributes().getNamedItem(MIN_OCCURS);
             if (minOccursAttr == null || !ZERO.equals(minOccursAttr.getNodeValue())) {
                 return false;
             }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private boolean areAllChildrenOptional(NodeList childNodes) {
boolean hasElement = false;
for (Node child : asIterable(childNodes)) {
if (child.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
if (!ELEMENT.equals(child.getLocalName())) {
continue;
}
hasElement = true;
Node minOccursAttr = child.getAttributes().getNamedItem(MIN_OCCURS);
if (minOccursAttr == null || !ZERO.equals(minOccursAttr.getNodeValue())) {
return false;
}
}
return hasElement;
private boolean areAllChildrenOptional(NodeList childNodes) {
boolean hasElement = false;
for (Node child : asIterable(childNodes)) {
if (child.getNodeType() != Node.ELEMENT_NODE
|| ANNOTATION.equals(child.getLocalName())) {
continue;
}
hasElement = true;
Node minOccursAttr = child.getAttributes().getNamedItem(MIN_OCCURS);
if (minOccursAttr == null || !ZERO.equals(minOccursAttr.getNodeValue())) {
return false;
}
}
return hasElement;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xsd-core/src/main/java/io/ballerina/xsd/core/visitor/XSDVisitorImpl.java`
around lines 773 - 788, areAllChildrenOptional currently only inspects children
whose localName equals ELEMENT, so required particles like CHOICE/SEQUENCE/ANY
are ignored; update areAllChildrenOptional to treat other particle node types
(e.g., CHOICE, SEQUENCE, ANY) the same way as ELEMENT: for any child whose
localName is one of ELEMENT, CHOICE, SEQUENCE, ANY (use the existing ELEMENT,
MIN_OCCURS, ZERO constants and add CHOICE/SEQUENCE/ANY constants if needed),
check its minOccurs attribute and return false if minOccurs is absent or not
"0"; also mark that a particle was seen (replace hasElement with hasParticle or
set hasElement when any of these particle types are encountered) so the method
only returns true when at least one particle was inspected.

@Nuvindu
Copy link
Contributor Author

Nuvindu commented Feb 22, 2026

Closing this as these changes are already applied by #40

@Nuvindu Nuvindu closed this Feb 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant