Skip to content

fix: annotate WmlToHtmlConverter.Charts.cs for nullable reference types - #665

Merged
JSv4 merged 2 commits into
mainfrom
fix/648-nullable-htmlconverter-charts
Sep 2, 2026
Merged

fix: annotate WmlToHtmlConverter.Charts.cs for nullable reference types#665
JSv4 merged 2 commits into
mainfrom
fix/648-nullable-htmlconverter-charts

Conversation

@JSv4

@JSv4 JSv4 commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

First of two PRs for #648 (WmlToHtmlConverter.cs + .Charts.cs, 604 sites total). The issue itself asks for this split: "WmlToHtmlConverter.Charts.cs first as a separate PR — 55 sites, self-contained, and it proves the approach on the smaller half." This PR does exactly that; the much larger WmlToHtmlConverter.cs (549 sites) is the follow-up. #648 stays open until that lands.

Removes the #nullable disable header from WmlToHtmlConverter.Charts.cs (the DOCX chart → inline SVG renderer) and resolves the resulting 56 warnings to zero. No public API is touched — every changed member is private static.

Notable decisions

  • ProcessChart's return type widens to XElement? and ReadCachedChartSeries's to CachedChartSeries? — both already returned null on unsupported or malformed chart data. ProcessChart's only caller (ProcessImage in WmlToHtmlConverter.cs) already null-checks the result, so this is a pure signature-matches-reality change.
  • The series LINQ pipeline switches from .Where(x => x != null && ...) to .OfType<CachedChartSeries>().Where(...)OfType both filters nulls and narrows the element type, so nothing downstream needs a !.
  • The small internal helpers (ReadChartTitle, ReadCachedText, ReadChartColor, ReadIntAttribute, ReadDoubleAttribute, ReadChartFontSize, ReadCachedPoints) already null-checked their XElement parameter internally via ?. — their signatures now say so (XElement?) instead of claiming non-null and relying on undocumented caller discipline.
  • Two ! assertions preserve real invariants that aren't statically provable from within this file alone (each has an inline comment): ReadChartColor's final rgb is proven non-null by the IsHexColor checks immediately above it, and ReadCachedPoints' double.Parse call only ever sees Text values that already passed the same TryParse check one LINQ stage earlier (IsHexColor itself lives in WmlToHtmlConverter.cs, out of scope for this PR, so it can't gain a [NotNullWhen] attribute here).

Also updates CLAUDE.md's "legacy files" bookkeeping (2 remain, down from 3, now that this file no longer carries the header) and the projected warning count if both remaining files were stripped. While re-measuring I also found a fresh --no-incremental build on this branch reports 115/691 library/test warnings, one lower on each than the 116/692 the prior PR (#664) recorded on main — the discrepancy is reproducible (measured twice) but I didn't track down its cause, so CLAUDE.md now states the measured values without claiming why they moved.

Validation

  • dotnet build Docxodus/Docxodus.csproj --no-incremental — 0 warnings from this file, 115 total (matches the updated baseline).
  • dotnet test --filter "FullyQualifiedName~HCO08 or FullyQualifiedName~HCO09" — 18/18 passed (the chart-specific test suite: clustered/stacked/percent-stacked/doughnut/pie/line charts, generated and fixture-based).
  • dotnet test --filter "FullyQualifiedName~HcTests" — 139/139 passed (includes the HC043-Chart.docx fixture through the general HTML-conversion suite).
  • Full Docxodus.Tests suite — 3923 passed, 3 skipped, 0 failed.
  • git status --short TestFiles/ clean — no fixture pollution.
  • ./scripts/build-wasm.sh — succeeded, within size budget.
  • npm run build + npx tsc --noEmit — clean.
  • npm test (full Playwright suite, 671 tests across chromium) — 666 passed, 5 pre-existing failures (the tabs-visual.spec.ts screenshot tests, which fail on this host because it lacks Times New Roman and are green in CI — unrelated to this change), 11 skipped.

No new tests: this is a pure nullable-annotation pass with no behavior change, so the existing suite is the correct gate.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VSnwkK1Nx6zZb2RnnoxKdx

JSv4 and others added 2 commits September 2, 2026 06:35
First of two PRs for #648 (per the issue's own staged plan: Charts.cs
is self-contained and proves the approach before the much larger
WmlToHtmlConverter.cs). Removes the #nullable disable header from
WmlToHtmlConverter.Charts.cs and resolves the resulting 56 warnings to
zero.

ProcessChart's return type widens to XElement? and ReadCachedChartSeries's
to CachedChartSeries? -- both already returned null on unsupported/malformed
charts; ProcessChart's sole caller (ProcessImage in WmlToHtmlConverter.cs)
already null-checks the result. The series LINQ pipeline switches from a
`Where(x => x != null && ...)` null filter to `OfType<CachedChartSeries>()`,
which narrows the element type as well as filtering, so no `!` is needed on
any downstream use of the series list.

The small internal helpers (ReadChartTitle, ReadCachedText, ReadChartColor,
ReadIntAttribute, ReadDoubleAttribute, ReadChartFontSize, ReadCachedPoints)
all already null-check their XElement parameter internally via `?.`; their
signatures now say so (XElement?) instead of claiming non-null and relying
on undocumented caller discipline.

Two `!` assertions preserve real invariants that aren't statically provable
from within this file alone (each with an inline comment): ReadChartColor's
final `rgb` is proven non-null by the IsHexColor branches above it, and
ReadCachedPoints' double.Parse call only ever sees Text values that already
passed the same TryParse check one LINQ stage earlier. Neither converts a
would-be crash into a silent skip.

No new tests: pure nullable-annotation pass, no behavior change. Validated
by dotnet build (0 warnings from this file, 116 total unchanged), the
chart-specific HCO087-098 suite (18/18) and the HC001[HC043-Chart.docx]
fixture, the full Docxodus.Tests suite (3923 passed, 3 skipped, 0 failed),
npm run build + npx tsc --noEmit, and scripts/build-wasm.sh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VSnwkK1Nx6zZb2RnnoxKdx
Reflects Docxodus/WmlToHtmlConverter.Charts.cs no longer carrying
#nullable disable: 2 legacy files remain (down from 3), and stripping
both now measures at 1,077 warnings (963 distinct CS86xx sites), down
from 1,131 (1,016).

Also updates the library/test warning baselines to what a fresh
--no-incremental build measures on this branch (115/691). That's one
lower than the 116/692 the prior PR recorded on main; the cause wasn't
tracked down, but the measurement here was taken twice and is
reproducible.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VSnwkK1Nx6zZb2RnnoxKdx
@JSv4
JSv4 force-pushed the fix/648-nullable-htmlconverter-charts branch from 5a811a3 to 61ef831 Compare September 2, 2026 11:48
@JSv4
JSv4 merged commit 4a848df into main Sep 2, 2026
14 checks passed
@JSv4
JSv4 deleted the fix/648-nullable-htmlconverter-charts branch September 2, 2026 12:08
JSv4 added a commit that referenced this pull request Sep 2, 2026
Removes the #nullable disable header from WmlToHtmlConverter.cs
(10,482 lines) and fixes every warning that surfaces once the file
is nullable-checked, following the same pass already completed for
its .Charts.cs half in #665. 523 baseline warnings -> 0.

Public surface changes:
- ProcessImage's imageHandler parameter is now
  Func<ImageInfo, XElement?>?, and settings is
  WmlToHtmlConverterSettings? settings = null.
- WmlToHtmlConverterSettings.ImageHandler / HtmlConverterSettings
  .ImageHandler (legacy settings type) widen the delegate's own
  return type to Func<ImageInfo, XElement?>?, not just the field --
  required because tools/docx2html/Program.cs's ImageHandler lambda
  already returns null on unsupported formats and decode failures,
  which only became visible once this file left #nullable disable.
- CommentInfo.Author / .Date / .Initials are now string?.

Two narrow, deliberate behavior changes, both confirmed against the
3,923-test suite with no change in outcome:
- isRtl = rPr?.Element(W.rtl) != null (was rPr.Element(...)) -- a
  run with no w:rPr now resolves to non-RTL instead of throwing.
- The three SectionAnnotation.SectionElement reads and the two
  pre-existing ones now share one invariant instead of two different
  ones: every SectionAnnotation is constructed with a resolved,
  non-null SectionElement (see InitializeSectionAnnotation), so all
  five reads use sectAnnotation?.SectionElement! rather than mixing
  ! at two sites and ?. at three.

Everything else preserves today's exact behavior, including several
Dictionary.ContainsKey/indexer calls on a nullable key left
un-guarded (via !) because that's already how the code depends on
the BCL's null-key ArgumentNullException.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VSnwkK1Nx6zZb2RnnoxKdx
JSv4 added a commit that referenced this pull request Sep 2, 2026
* fix: annotate WmlToHtmlConverter.cs for nullable reference types

Removes the #nullable disable header from WmlToHtmlConverter.cs
(10,482 lines) and fixes every warning that surfaces once the file
is nullable-checked, following the same pass already completed for
its .Charts.cs half in #665. 523 baseline warnings -> 0.

Public surface changes:
- ProcessImage's imageHandler parameter is now
  Func<ImageInfo, XElement?>?, and settings is
  WmlToHtmlConverterSettings? settings = null.
- WmlToHtmlConverterSettings.ImageHandler / HtmlConverterSettings
  .ImageHandler (legacy settings type) widen the delegate's own
  return type to Func<ImageInfo, XElement?>?, not just the field --
  required because tools/docx2html/Program.cs's ImageHandler lambda
  already returns null on unsupported formats and decode failures,
  which only became visible once this file left #nullable disable.
- CommentInfo.Author / .Date / .Initials are now string?.

Two narrow, deliberate behavior changes, both confirmed against the
3,923-test suite with no change in outcome:
- isRtl = rPr?.Element(W.rtl) != null (was rPr.Element(...)) -- a
  run with no w:rPr now resolves to non-RTL instead of throwing.
- The three SectionAnnotation.SectionElement reads and the two
  pre-existing ones now share one invariant instead of two different
  ones: every SectionAnnotation is constructed with a resolved,
  non-null SectionElement (see InitializeSectionAnnotation), so all
  five reads use sectAnnotation?.SectionElement! rather than mixing
  ! at two sites and ?. at three.

Everything else preserves today's exact behavior, including several
Dictionary.ContainsKey/indexer calls on a nullable key left
un-guarded (via !) because that's already how the code depends on
the BCL's null-key ArgumentNullException.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VSnwkK1Nx6zZb2RnnoxKdx

* docs: correct warning baselines and legacy-file count after #648

WmlToHtmlConverter.cs is annotated now, leaving FormattingAssembler.cs
as the sole remaining #nullable disable header (issue #649). Also
records the StyleCop SA1636/SA1633 interaction discovered while
re-measuring: removing one of these legacy headers promotes the
inherited Microsoft copyright comment to first-in-file, which matches
stylecop.json's copyrightText exactly and drops a warning from each
baseline independent of the CS86xx cleanup itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VSnwkK1Nx6zZb2RnnoxKdx

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
JSv4 pushed a commit that referenced this pull request Sep 2, 2026
#663-#666 continue the #650 nullable run. Two of them land on this
demo's measured path: #665 and #666 annotate WmlToHtmlConverter.cs and
.Charts.cs, which are the conversion stage of the full+HTML depth. They
are not cosmetic either — removing the #nullable disable header added 27
null-handling lines to the converter, which is real runtime code, so
this got a rebuild and the full browser run rather than a wave-through.
14/14 pass.

Measured, and the numbers are NOT republished, deliberately. A single
controlled session read 12-25% faster across most rows (revisions 50->38,
redline 67->53, full 127->106, conversion stage in isolation 60->53) —
but oneCall, which IS the redline depth's call, came back flat at 51->50.
Redline cannot drop 21% while docxDiffCompareProducts holds; the pattern
contradicts itself. Added null-guards should also cost time rather than
save it, so the direction is wrong for the change too.

That is variance, on one session, and the published figures came from a
pooled two-session controlled measurement. The README asks for pooling
before believing a movement and for labelling one that fits nothing as
the machine; publishing this would break both rules on my own page.

Also merges #663 (HtmlToWmlConverter/Core) and #664 (DocumentBuilder),
neither of which this demo calls.
JSv4 pushed a commit that referenced this pull request Sep 2, 2026
FormattingAssembler.cs is the last of the legacy #nullable disable files
and the converter resolves formatting through it, so this is on the
full+HTML path. 13 real null-guards added, not cosmetic, so it got a
rebuild and the full browser run: 14/14 pass.

No measurement this time, and that is a change of approach rather than
an omission. The #650 nullable run has now touched the converter twice
(#665/#666, #675), and measuring after each one produces a single
session apiece — which is exactly the sample size that gave an
incoherent reading last merge (redline down 21% while its own underlying
call held flat). Single sessions through a long mechanical run add noise,
not signal.

The published figures are pooled and stable and nothing here is expected
to move them: null-guards cost time rather than save it, and the effect
of thirteen of them is far below what this container can resolve. When
the run finishes, the figures are due one deliberate pooled
re-establishment rather than eleven piecemeal nudges.
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