Add x:Code directive support for inline C# in XAML#34715
Conversation
Implements x:Code as a new source generator pipeline that extracts inline C# code blocks from XAML and emits them as bare partial classes (no usings). The pipeline runs between CodeBehind and InitializeComponent generation so IC and XEXPR can resolve x:Code members. Key changes: - New XCodeCodeWriter for generating the partial class output - ComputeXCodeSource extraction in GeneratorHelpers - Pipeline wired via RegisterSourceOutput in XamlGenerator - All IC visitors skip x:Code elements (SkipChildren + skip lists) - XmlName.xCode added to known directives - Diagnostics: MAUIX2015 (not child of root), MAUIX2016 (no x:Class) - Reuses MAUIX2012 for EnablePreviewFeatures gating Requires EnablePreviewFeatures and XAML Source Generator (XSG). Closes #34712 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34715Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34715" |
There was a problem hiding this comment.
Pull request overview
Adds x:Code support to the MAUI XAML Source Generator (XSG) so inline C# members declared in XAML can be emitted into the generated partial class, and updates SourceGen visitors/tests accordingly.
Changes:
- Introduces an
x:Codesource-gen pipeline that extractsx:Codeblocks and emits a.xcode.cspartial class beforeInitializeComponentgeneration. - Updates multiple SourceGen visitors to ignore
x:Codeelements during IC generation. - Adds new diagnostics (MAUIX2015/MAUIX2016) and unit coverage in SourceGen + Xaml.UnitTests.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Controls/tests/Xaml.UnitTests/Issues/Maui34712.xaml.cs | Adds Xaml.UnitTests coverage for SourceGen success and Runtime/XamlC unsupported behavior. |
| src/Controls/tests/Xaml.UnitTests/Issues/Maui34712.sgen.xaml | Adds a SourceGen-only XAML repro using x:Code. |
| src/Controls/tests/SourceGen.UnitTests/XCodeTests.cs | Adds SourceGen unit tests for x:Code output and diagnostics. |
| src/Controls/src/Xaml/XmlName.cs | Introduces XmlName.xCode. |
| src/Controls/src/SourceGen/XamlGenerator.cs | Wires a new incremental pipeline stage: CB → x:Code → IC. |
| src/Controls/src/SourceGen/XCodeCodeWriter.cs | Adds a writer that emits the .xcode.cs partial class wrapper. |
| src/Controls/src/SourceGen/Visitors/SetResourcesVisitor.cs | Skips x:Code nodes during resource visitation. |
| src/Controls/src/SourceGen/Visitors/SetPropertiesVisitor.cs | Skips x:Code nodes during property setting and child traversal. |
| src/Controls/src/SourceGen/Visitors/SetNamescopesAndRegisterNames.cs | Skips x:Code nodes during namescope/name registration. |
| src/Controls/src/SourceGen/Visitors/SetFieldsForXNamesVisitor.cs | Skips x:Code nodes during x:Name field generation. |
| src/Controls/src/SourceGen/Visitors/ExpandMarkupsVisitor.cs | Skips x:Code nodes during markup expansion. |
| src/Controls/src/SourceGen/Visitors/CreateValuesVisitor.cs | Skips x:Code nodes during value creation. |
| src/Controls/src/SourceGen/TrackingNames.cs | Adds tracking names for x:Code pipeline stages. |
| src/Controls/src/SourceGen/GeneratorHelpers.cs | Adds IsXCodeElement and ComputeXCodeSource extraction logic. |
| src/Controls/src/SourceGen/Descriptors.cs | Adds MAUIX2015/MAUIX2016 descriptors. |
| src/Controls/src/SourceGen/AnalyzerReleases.Unshipped.md | Documents the new diagnostics. |
| @@ -0,0 +1,70 @@ | |||
| using System; | |||
| using System.Linq; | |||
There was a problem hiding this comment.
using System.Linq; is unused in this file. Since warnings are treated as errors in this repo, this will fail the build (CS8019). Remove the unused using.
| using System.Linq; |
| var (xamlItem, xmlnsCache) = input; | ||
| if (xamlItem?.Root == null || xamlItem.ProjectItem == null) | ||
| return null; | ||
|
|
||
| var root = xamlItem.Root; | ||
| var nsmgr = xamlItem.Nsmgr; |
There was a problem hiding this comment.
xmlnsCache and nsmgr are assigned but never used. With warnings treated as errors in this repo, this will fail the build (CS0219). Use _ for the unused tuple value and remove the unused nsmgr local (or use it for namespace-aware queries).
| var (xamlItem, xmlnsCache) = input; | |
| if (xamlItem?.Root == null || xamlItem.ProjectItem == null) | |
| return null; | |
| var root = xamlItem.Root; | |
| var nsmgr = xamlItem.Nsmgr; | |
| var (xamlItem, _) = input; | |
| if (xamlItem?.Root == null || xamlItem.ProjectItem == null) | |
| return null; | |
| var root = xamlItem.Root; |
| // Find all x:Code child elements of the root | ||
| var codeBlocks = new List<string>(); | ||
|
|
||
| foreach (XmlNode child in root.ChildNodes) | ||
| { | ||
| cancellationToken.ThrowIfCancellationRequested(); | ||
|
|
||
| if (child.LocalName != "Code") | ||
| continue; | ||
| if (child.NamespaceURI != XamlParser.X2006Uri && child.NamespaceURI != XamlParser.X2009Uri) | ||
| continue; | ||
|
|
||
| codeBlocks.Add(child.InnerText); | ||
| } | ||
|
|
||
| if (codeBlocks.Count == 0) |
There was a problem hiding this comment.
MAUIX2015 (XCodeNotChildOfRoot) is defined, but ComputeXCodeSource currently just ignores any x:Code that isn’t an immediate root child (it won’t be extracted, and visitors will skip it), resulting in silent no-op instead of the intended diagnostic. Consider scanning the full document for x:Code elements and reporting Descriptors.XCodeNotChildOfRoot for any non-root occurrences.
| // Find all x:Code child elements of the root | |
| var codeBlocks = new List<string>(); | |
| foreach (XmlNode child in root.ChildNodes) | |
| { | |
| cancellationToken.ThrowIfCancellationRequested(); | |
| if (child.LocalName != "Code") | |
| continue; | |
| if (child.NamespaceURI != XamlParser.X2006Uri && child.NamespaceURI != XamlParser.X2009Uri) | |
| continue; | |
| codeBlocks.Add(child.InnerText); | |
| } | |
| if (codeBlocks.Count == 0) | |
| // Find all x:Code elements in the document, collecting only root-level ones for generation | |
| var codeBlocks = new List<string>(); | |
| void VisitNode(XmlNode node) | |
| { | |
| cancellationToken.ThrowIfCancellationRequested(); | |
| if (node.NodeType == XmlNodeType.Element && | |
| node.LocalName == "Code" && | |
| (node.NamespaceURI == XamlParser.X2006Uri || node.NamespaceURI == XamlParser.X2009Uri)) | |
| { | |
| // x:Code must be an immediate child of the root element | |
| if (node.ParentNode == root) | |
| { | |
| codeBlocks.Add(node.InnerText); | |
| } | |
| else if (projItem.RelativePath is string path) | |
| { | |
| Location location; | |
| if (node is IXmlLineInfo lineInfo && lineInfo.HasLineInfo()) | |
| { | |
| location = LocationHelpers.LocationCreate(path, lineInfo, node.Name); | |
| } | |
| else | |
| { | |
| location = LocationHelpers.LocationCreate(path, new XmlLineInfo(), string.Empty); | |
| } | |
| diagnostics.Add(Diagnostic.Create(Descriptors.XCodeNotChildOfRoot, location)); | |
| } | |
| } | |
| foreach (XmlNode child in node.ChildNodes) | |
| { | |
| VisitNode(child); | |
| } | |
| } | |
| VisitNode(root); | |
| if (codeBlocks.Count == 0 && diagnostics.Count == 0) |
🧪 PR Test EvaluationOverall Verdict: The test suite is solid for the happy path and error diagnostics, but MAUIX2015 (XCodeNotChildOfRoot) — explicitly listed in the commit message as a supported diagnostic — appears to be neither raised in the implementation nor covered by any test.
📊 Expand Full EvaluationPR Test Evaluation ReportPR: #34715 — Add x:Code directive support for inline C# in XAML Overall VerdictTests are well-structured and cover the main functionality, but MAUIX2015 ( 1. Fix Coverage — ✅The tests exercise the key code paths introduced by the fix:
The tests would fail if the fix were reverted. 2. Edge Cases & Gaps —
|
| Changed Component | Covered By |
|---|---|
XCodeCodeWriter.GenerateXCode |
XCode_GeneratesPartialClassWithMethod, XCode_MultipleBlocks_AreConcatenated |
GeneratorHelpers.ComputeXCodeSource |
XCode_WithoutCDATA_Works, XCode_NoXClass_ReportsDiagnostic, XCode_WithoutPreviewFeatures_ReportsDiagnostic, XCode_NoCodeBlocks_NoOutput |
XamlGenerator pipeline wiring |
XCodeSucceedsWithSourceGen, XCodeSourceGenProducesNoDiagnostics |
| Visitor skip logic | Indirectly covered by XCodeSourceGenProducesNoDiagnostics (IC runs without errors) |
Descriptors.MAUIX2015 |
❌ Not tested |
Recommendations
-
🔴 Implement and test MAUIX2015 — Add the code to emit
Descriptors.XCodeNotChildOfRootwhen anx:Codeelement is found nested inside a non-root element, and add a test caseXCode_NestedInNonRoot_ReportsDiagnostictoXCodeTests.csthat verifiesd.Id == "MAUIX2015"is emitted. If this scenario intentionally silently ignores nested x:Code, remove the descriptor and the entry inAnalyzerReleases.Unshipped.md. -
🟡 Add test for empty x:Code block — Verify that
(x:Code)(![CDATA[]])(/x:Code)doesn't produce an empty partial class that causes compiler warnings. -
🟡 Add test for x:Code members accessing x:Name'd elements — The existing test XAML already has
(Label x:Name="testLabel" /)but no test calls a method that uses it. Add a method likeGetLabelText() => testLabel.Textto thex:Codeblock and verify it's accessible, confirming the IC + x:Code pipeline interaction works end-to-end.
Warning
⚠️ Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
dc.services.visualstudio.com
To allow these domains, add them to the network.allowed list in your workflow frontmatter:
network:
allowed:
- defaults
- "dc.services.visualstudio.com"See Network Configuration for more information.
Note
🔒 Integrity filtering filtered 1 item
Integrity filtering activated and filtered the following item during workflow execution.
This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.
- pr:Add x:Code directive support for inline C# in XAML #34715 (
pull_request_read: Resource 'pr:Add x:Code directive support for inline C# in XAML #34715' has lower integrity than agent requires. Agent would need to drop integrity tags [unapproved:all approved:all] to trust this resource.)
🧪 Test evaluation by Evaluate PR Tests
Using directives (e.g. 'using System.Net.Http;') inside x:Code blocks
are now extracted and placed at the top of the generated file, outside
the namespace/class declarations. Using statements ('using var x = ...')
are correctly left inside the class body.
Duplicate using directives across multiple x:Code blocks are deduped.
Supports regular, static, and alias using directives.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add docs/specs/XamlXCode.md covering syntax, placement rules, using directive promotion, code generation pipeline, diagnostics, constraints, XEXPR relationship, and WPF parity notes. - Update Maui34712 XAML test to exercise using directives inside x:Code (using System.Globalization for CultureInfo). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🧪 PR Test EvaluationOverall Verdict: The tests are well-structured and use the right test types (SourceGen unit tests + XAML tests) for a new x:Code feature. The main gap is
📊 Expand Full EvaluationPR Test Evaluation ReportPR: #34715 — Add x:Code directive support for inline C# in XAML Overall VerdictGood breadth of coverage for a new feature (9 unit tests + 4 XAML tests), but 1. Fix Coverage — ✅The tests exercise the core code paths added by the fix:
2. Edge Cases & Gaps —
|
| if (rootType == null || rootClrNamespace == null) | ||
| return null; |
There was a problem hiding this comment.
No diagnostic in this case? I think this should either never be possible (and throw), or this deserves a diagnostic.
| if (GeneratorHelpers.IsXCodeElement(node)) | ||
| return; |
There was a problem hiding this comment.
This makes me wonder, should we add CodeElement and have a visitor that identifies and converts the x:Code elements into this element? that way we wouldn't need to have all those annoying SkipChildren changes + it would communicate the intent much better.
| sourceProductionContext.ReportDiagnostic(diag); | ||
|
|
||
| if (!string.IsNullOrEmpty(xcode.Source)) | ||
| sourceProductionContext.AddSource(GetHintName(xcode.ProjectItem, "xcode"), xcode.Source); |
There was a problem hiding this comment.
what if there are multiple x:Code blocks in one XAML item? If I understand the code, we're not merging them into one big xcode, are we? I suppose there's nothing stopping the devs to do it and while I suppose the "hint name" is really just a hint and hopefully roslyn can handle de-duplication, let's add at least 1 test that covers this (that this doesn't produce a warning/error or that we don't overwrite the first xcode block).
| <x:Code><![CDATA[ | ||
| int _count; | ||
| ]]></x:Code> | ||
| <Label Text="Test" /> | ||
| <x:Code><![CDATA[ | ||
| void Increment() => _count++; | ||
| ]]></x:Code> |
There was a problem hiding this comment.
oh, so I was wrong in an earlier comment, we are merging all the x:Code elements into a single partial class body.
1. Emit MAUIX2016 when x:Class is present but malformed (ParseXmlns returns null), instead of silently returning null. 2. Strip x:Code elements from the node tree in InitializeComponentCodeWriter before IC visitors run. This replaces the scattered IsXCodeElement checks in 6 visitors with a single StripXCodeElements call, making the intent clearer and keeping visitor code clean. 3. Multiple x:Code blocks are already covered by the existingmerged XCode_MultipleBlocks_AreConcatenated test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🧪 PR Test EvaluationOverall Verdict: Tests are well-structured and cover the happy path thoroughly, but there is a notable gap: the newly defined diagnostic
📊 Expand Full EvaluationPR Test Evaluation ReportPR: #34715 — Overall VerdictThe test suite is solid for the happy path (SourceGen code generation) and covers several important edge cases (no CDATA, multiple blocks, using directive promotion, duplicate deduplication, diagnostics). However, 1. Fix Coverage — ✅The 2. Edge Cases & Gaps —
|
The generated .xcode.cs file is a separate compilation unit and needs its own 'using System' to resolve DateTime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🧪 PR Test EvaluationOverall Verdict: ✅ Tests are adequate The PR adds
📊 Expand Full EvaluationPR Test Evaluation ReportPR: #34715 — x:Code support for XAML Source Generator Overall Verdict✅ Tests are adequate 9 unit tests directly exercise the new 1. Fix Coverage — ✅The unit tests in
All significant code paths in the new files have test coverage that would fail if the fix were reverted. 2. Edge Cases & Gaps —
|
|
/azp run maui-pr-uitests, maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
Adds a >=net-maui-11.0 monikered subsection to xamlc.md under XAML Source Generation, covering the new x:Code directive. The directive lets a small block of C# live inline in a XAML file, with the source generator emitting it into the page's partial class. Notes the EnablePreviewFeatures requirement, the x:Class requirement, the root child placement rule, and the MAUIX2015/MAUIX2016 analyzer diagnostics. Upstream: dotnet/maui#34715 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump actions/upload-artifact from 7.0.0 to 7.0.1 (#3335) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 7.0.0 to 7.0.1. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@bbbca2d...043fb46) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * What's new (.NET 11 Preview 4): document new features (#3329) * What's new (.NET 11 Preview 4): document new features Add .NET 11 Preview 4 entries to the What's new page: - CoreCLR is the default runtime on all .NET MAUI platforms - x:Code directive for inline C# in XAML - Compiled bindings inside DataTemplates (regression fix + diagnostics) - Material 3 styling for Android ImageButton, DatePicker, Entry, Slider - MonochromeFile support for Android adaptive icons - dotnet watch for Android - dotnet watch for iOS (incl. MtouchLink=None known issue) - Xcode 26.4 Stable, HTTP digest auth in NSUrlSessionHandler, CoreCLR default on Apple platforms Also adds the Preview 4 release notes link and bumps ms.date. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * What's new (.NET 11 Preview 4): add media assets Add the Material 3 controls screenshot and the dotnet watch GIFs referenced from docs/whats-new/dotnet-11.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bump actions/download-artifact from 8.0.0 to 8.0.1 (#3338) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 8.0.0 to 8.0.1. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](actions/download-artifact@70fc10c...3e5f45b) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 8.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump github/gh-aw from 0.53.5 to 0.73.0 (#3337) Bumps [github/gh-aw](https://github.com/github/gh-aw) from 0.53.5 to 0.73.0. - [Release notes](https://github.com/github/gh-aw/releases) - [Changelog](https://github.com/github/gh-aw/blob/main/CHANGELOG.md) - [Commits](github/gh-aw@ffb8573...4d44d0e) --- updated-dependencies: - dependency-name: github/gh-aw dependency-version: 0.73.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump actions/github-script from 8.0.0 to 9.0.0 (#3336) Bumps [actions/github-script](https://github.com/actions/github-script) from 8.0.0 to 9.0.0. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](actions/github-script@v8...3a2844b) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Document Shell and ToolbarItem badges (.NET 11) (#3333) In .NET MAUI 11 (Preview 4), BaseShellItem (Tab, ShellContent, FlyoutItem) and ToolbarItem gain BadgeText, BadgeColor, and BadgeTextColor bindable properties for displaying badges on navigation surfaces. - docs/fundamentals/shell/tabs.md: new Tab badges section with platform notes for Android (BadgeDrawable), iOS/MacCatalyst (UITabBarItem.BadgeValue), and Windows (InfoBadge). - docs/fundamentals/shell/flyout.md: new FlyoutItem badges section cross-linking to the tabs platform notes. - docs/user-interface/toolbaritem.md: new Display a badge on a ToolbarItem section with toolbar-specific platform notes (iOS 26+ UIBarButtonItem.Badge requirement, primary-only). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Document Editor.ReturnCommand wired to completion (.NET 11) (#3331) In .NET MAUI 11 (Preview 4), Editor.ReturnCommand and ReturnCommandParameter are now invoked from SendCompleted, so a command bound to ReturnCommand fires when the Completed event would be raised. This adds a >=net-maui-11.0 monikered note to the Editor properties summary and a small example after the Completed event sample. Upstream: dotnet/maui#32167 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Document MonochromeFile attribute for Android themed icons (.NET 11) (#3330) Adds a >=net-maui-11.0 monikered subsection to app-icons.md describing the new MonochromeFile attribute on <MauiIcon>, which lets single-project Android adaptive icons use a dedicated glyph for the themed (monochrome) icon layer on Android 13+ instead of reusing the foreground image. Upstream: dotnet/maui#34569 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Document x:Code directive for inline C# in XAML (.NET 11) (#3332) Adds a >=net-maui-11.0 monikered subsection to xamlc.md under XAML Source Generation, covering the new x:Code directive. The directive lets a small block of C# live inline in a XAML file, with the source generator emitting it into the page's partial class. Notes the EnablePreviewFeatures requirement, the x:Class requirement, the root child placement rule, and the MAUIX2015/MAUIX2016 analyzer diagnostics. Upstream: dotnet/maui#34715 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Document Material 3 expansion to ImageButton, Switch, and Shell (.NET 11) (#3334) In .NET MAUI 11 (Preview 4), the Android handlers for ImageButton, Switch, and Shell tab bars adopt Material 3 styling when the UseMaterial3 project flag is enabled. Adds three new monikered subsections to material-design.md under Platform availability, matching the existing per-control pattern. Upstream: - ImageButton: dotnet/maui#33649 - Switch: dotnet/maui#33132 - Shell: dotnet/maui#33427 Screenshots for the new controls can be added in a follow-up. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: David Ortinau <david.ortinau@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Description
Implements the
x:CodeXAML directive, allowing inline C# method definitions directly in XAML files. This complements the existing XEXPR (C# Expressions) feature.Fixes #34712
What it does
x:Codelets you define methods inline in XAML:Constraints
EnablePreviewFeatures— same gate as XEXPRx:Classon the root elementArchitecture
x:Code is implemented as a third source generator pipeline alongside CodeBehind (CB) and InitializeComponent (IC):
ComputeXCodeSource()extracts code blocks from parsed XAMLXCodeCodeWriteremits a bare partial class (namespace + class + verbatim code, no usings)GeneratorHelpers.IsXCodeElement()Output hint name format:
{path}_{FileName}.xaml.xcode.csDiagnostics
EnablePreviewFeaturesnot set (reused from XEXPR)x:ClassTests