Add x:Code directive support for inline C# in XAML#34715
Add x:Code directive support for inline C# in XAML#34715StephaneDelcroix wants to merge 5 commits intomainfrom
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 <[email protected]>
|
🚀 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 <[email protected]>
- 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 <[email protected]>
🧪 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 <[email protected]>
🧪 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 <[email protected]>
🧪 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). |
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