Skip to content

Root element for generated parts should be nullable #1922

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 28, 2025

Conversation

tomjebo
Copy link
Collaborator

@tomjebo tomjebo commented Apr 25, 2025

Changes root elements for generated parts to be nullable.
Fixes #959 and #995

Copy link

github-actions bot commented Apr 25, 2025

Test Results

    56 files  ±  0      56 suites  ±0   1h 1m 53s ⏱️ + 4m 40s
 2 050 tests ±  0   2 047 ✅ ±  0   3 💤 ±0  0 ❌ ±0 
32 354 runs  +362  32 318 ✅ +362  36 💤 ±0  0 ❌ ±0 

Results for commit 4cd86d7. ± Comparison against base commit b127a66.

♻️ This comment has been updated with latest results.

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This pull request makes root elements for generated parts nullable and updates related tests and generator code accordingly. Key changes include:

  • Updating test files to use null‐forgiving operators where necessary.
  • Adding explicit null checks in sample utility and presentation helper methods.
  • Adjusting generator code with nullable annotations and a new [DisallowNull] attribute.

Reviewed Changes

Copilot reviewed 135 out of 135 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/DocumentFormat.OpenXml.Linq.Tests/OpenXmlPartRootXElementExtensionsTests.cs Removed "#nullable enable" directive and added null-forgiving operators on Document accesses.
test/DocumentFormat.OpenXml.Framework.Features.Tests/RandomParagraphIdGeneratorTests.cs Updated null-forgiving operators on Document and Body accesses.
src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs Added null-forgiving operator for Settings when appending the AttachedTemplate element.
samples/common/PowerPointUtils.cs Introduced an explicit null check for Presentation in CreatePresentationParts.
samples/common/ExampleUtilities.cs Added and adjusted null-checks and null-forgiving operators around core worksheet and workbook operations.
samples/Linq/SvgExample/StronglyTypedTools.cs Added null checks for Presentation and Slide elements in SVG addition methods.
gen/DocumentFormat.OpenXml.Generator.Models/Generators/Parts/PartWriter.cs Inserted a [DisallowNull] attribute and made the generated property return type nullable.
Comments suppressed due to low confidence (1)

test/DocumentFormat.OpenXml.Linq.Tests/OpenXmlPartRootXElementExtensionsTests.cs:11

  • Removal of '#nullable enable' may inadvertently disable nullable reference type checking in this file; consider reintroducing it if nullability enforcement is desired.
#nullable enable

@@ -127,7 +128,7 @@ public static void InsertText(SpreadsheetDocument sd, string sheetName, string t
cell.DataType = new EnumValue<CellValues>(CellValues.SharedString);

// Save the new worksheet.
worksheetPart.Worksheet.Save();
worksheetPart.Worksheet?.Save();
Copy link
Preview

Copilot AI Apr 25, 2025

Choose a reason for hiding this comment

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

Using the null-conditional operator here may silently skip the Save() call if Worksheet is null; consider throwing an exception instead to maintain consistent failure behavior.

Suggested change
worksheetPart.Worksheet?.Save();
if (worksheetPart.Worksheet == null)
{
throw new InvalidOperationException("Worksheet is null. Unable to save changes.");
}
worksheetPart.Worksheet.Save();

Copilot uses AI. Check for mistakes.

writer.Write("public ");
writer.Write(apiName);
writer.Write(" ");
writer.Write("? ");
Copy link
Preview

Copilot AI Apr 25, 2025

Choose a reason for hiding this comment

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

Applying [DisallowNull] on a property whose type is rendered as nullable (using '?') is contradictory; please ensure that the nullability annotations and attributes are consistent.

Suggested change
writer.Write("? ");
writer.Write(" ");

Copilot uses AI. Check for mistakes.

Copy link
Member

Choose a reason for hiding this comment

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

It's not contradictory (there are times you may want this), but @tomjebo why do have both? This means it may be null, but a user can't set it to that. Do we want that?

Copy link
Collaborator Author

@tomjebo tomjebo Apr 25, 2025

Choose a reason for hiding this comment

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

@twsouthwick This was my misreading of your comment here in 959. I read that as [DisallowNull] and set the type to nullable with ?. I think you must have meant or but I was guessing there was a reason for wanting both. I'll take out the attribute [DisallowNull] as I think the root element property will have to be null at some point.

Copy link
Member

Choose a reason for hiding this comment

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

It also may have been early on in nullables and I misunderstood the point of it :) for now, just setting it to be nullable is good enough

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

so if the point is only to prevent assignment to null via static code analysis then I'm thinking we shouldn't use this as a way to enforce part root element minOccurs conformance. we should allow code to set to null (assuming eventually it will be set to a valid root element as required by the schemas) and then rely on our validation metadata to correctly catch the minOccurs constraint. that's my feeling. do you agree?

Copy link
Member

Choose a reason for hiding this comment

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

that sounds reasonable for now (that's the current behavior, right?)

mikeebowen
mikeebowen previously approved these changes Apr 25, 2025
@@ -22,6 +22,11 @@ public static void AddSvg(Stream stream, string svgPath, double percentageOfCy)
PresentationPart presentationPart = presentationDocument.PresentationPart ??
throw new InvalidOperationException(@"PresentationDocument is invalid.");

if (presentationPart.Presentation is null)
{
throw new ArgumentNullException(@"Presentation root element is missing!");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit, but you don't need the @ symbol when there are no characters to be escaped in a string.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll change both lines. I just copied that from above and didn't remove the superfluous @.

@@ -88,6 +93,11 @@ public static void AddSvg(Stream stream, string svgPath, double percentageOfCy)
new Drawing.FillRectangle())),
GetShapeProperties(presentationPart, percentageOfCy));

if (slidePart.Slide is null)
{
throw new ArgumentNullException(@"Slide root element is missing!");
Copy link
Collaborator

Choose a reason for hiding this comment

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

same nit as line 27

@@ -108,6 +118,11 @@ public static TOpenXmlElement WithNamespaceDeclaration<TOpenXmlElement>(

private static Presentation.ShapeProperties GetShapeProperties(PresentationPart part, double percentageOfCy)
{
if (part.Presentation is null)
{
throw new ArgumentNullException(@"Presentation root element is missing!");
Copy link
Collaborator

Choose a reason for hiding this comment

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

same nit as line 27

twsouthwick
twsouthwick previously approved these changes Apr 25, 2025
@tomjebo tomjebo requested a review from mikeebowen April 25, 2025 21:23
@tomjebo
Copy link
Collaborator Author

tomjebo commented Apr 25, 2025

@twsouthwick @mikeebowen one more review if you have time. there was one more thing that I needed to add here and that was to enable nullability for projects that had any tests of the part root elements.

Therefore, I turned it on for DocumentFormat.OpenXml.Framework.Tests and DocumentFormat.OpenXml.Packaging.Tests. Then I fixed up the nullable violation warnings for these projects. DocumentFormat.OpenXml.Framework.Linq.Tests already had it on.

I initially turned it on for DocumentFormat.OpenXml.Tests but there were over 1700 violations and they aren't specifically testing root elements so I will defer that to a later migration.

Hopefully, now this satisfies #995 and #959.


// Get relationship ID of first slide.
string sldRelId = presentationPart
.Presentation
.SlideIdList?
.Elements<Presentation.SlideId>()
.Select(slideId => (string)slideId.RelationshipId!)
.FirstOrDefault() ?? throw new InvalidOperationException(@"Presentation has no slides.");
.FirstOrDefault() ?? throw new InvalidOperationException("Presentation has no slides.");
Copy link
Member

Choose a reason for hiding this comment

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

if you want to ensure it's there, you can just call .First() and it will throw if it's not available

twsouthwick
twsouthwick previously approved these changes Apr 25, 2025
@tomjebo tomjebo enabled auto-merge (squash) April 28, 2025 15:54
@tomjebo tomjebo merged commit e069c61 into dotnet:main Apr 28, 2025
22 checks passed
@mikeebowen mikeebowen requested a review from mkaszewiak April 28, 2025 16:04
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.

Incorrect nullability of MainDocumentPart.Document and corresponding root elements of other OpenXmlParts
4 participants