diff --git a/src/Controls/src/SourceGen/CodeBehindGenerator.cs b/src/Controls/src/SourceGen/CodeBehindGenerator.cs
index 478aef991f59..e6b5aab713dc 100644
--- a/src/Controls/src/SourceGen/CodeBehindGenerator.cs
+++ b/src/Controls/src/SourceGen/CodeBehindGenerator.cs
@@ -96,12 +96,11 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
initContext.RegisterSourceOutput(xamlSourceProviderForCB, static (sourceProductionContext, provider) =>
{
var (xamlItem, xmlnsCache, typeCache, compilation) = provider;
- var fileName = $"{(string.IsNullOrEmpty(Path.GetDirectoryName(xamlItem!.ProjectItem!.TargetPath)) ? "" : Path.GetDirectoryName(xamlItem.ProjectItem.TargetPath) + Path.DirectorySeparatorChar)}{Path.GetFileNameWithoutExtension(xamlItem.ProjectItem.TargetPath)}.{xamlItem.ProjectItem.Kind.ToLowerInvariant()}.sg.cs".Replace(Path.DirectorySeparatorChar, '_');
try
{
var code = CodeBehindCodeWriter.GenerateXamlCodeBehind(xamlItem, compilation, sourceProductionContext.ReportDiagnostic, sourceProductionContext.CancellationToken, xmlnsCache, typeCache);
- sourceProductionContext.AddSource(fileName, code);
+ sourceProductionContext.AddSource(GetHintName(xamlItem?.ProjectItem, "sg"), code);
}
catch (Exception e)
{
@@ -115,7 +114,10 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
{
var (xamlItem, xmlnsCache, typeCache, compilation) = provider;
- var fileName = $"{(string.IsNullOrEmpty(Path.GetDirectoryName(xamlItem!.ProjectItem!.TargetPath)) ? "" : Path.GetDirectoryName(xamlItem.ProjectItem.TargetPath) + Path.DirectorySeparatorChar)}{Path.GetFileNameWithoutExtension(xamlItem.ProjectItem.TargetPath)}.{xamlItem.ProjectItem.Kind.ToLowerInvariant()}.xsg.cs".Replace(Path.DirectorySeparatorChar, '_');
+ if (xamlItem?.ProjectItem?.RelativePath is not string relativePath)
+ {
+ throw new InvalidOperationException("Xaml item or target path is null");
+ }
if (!ShouldGenerateSourceGenInitializeComponent(xamlItem, xmlnsCache, compilation))
return;
@@ -125,7 +127,7 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
if (xamlItem != null && xamlItem.Exception != null)
{
var lineInfo = xamlItem.Exception is XamlParseException xpe ? xpe.XmlInfo : new XmlLineInfo();
- var location = LocationCreate(fileName, lineInfo, string.Empty);
+ var location = LocationCreate(relativePath, lineInfo, string.Empty);
sourceProductionContext.ReportDiagnostic(Diagnostic.Create(Descriptors.XamlParserError, location, xamlItem.Exception.Message));
}
return;
@@ -137,14 +139,13 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
return;
var code = InitializeComponentCodeWriter.GenerateInitializeComponent(xamlItem, compilation, sourceProductionContext, xmlnsCache, typeCache);
- sourceProductionContext.AddSource(fileName, code);
+ sourceProductionContext.AddSource(GetHintName(xamlItem.ProjectItem, "xsg"), code);
}
catch (Exception e)
{
var location = xamlItem?.ProjectItem?.RelativePath is not null ? Location.Create(xamlItem.ProjectItem.RelativePath, new TextSpan(), new LinePositionSpan()) : null;
sourceProductionContext.ReportDiagnostic(Diagnostic.Create(Descriptors.XamlParserError, location, e.Message));
}
-
});
// Register the CSS pipeline
@@ -184,6 +185,19 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
});
}
+ private static string GetHintName(ProjectItem? projectItem, string suffix)
+ {
+ if (projectItem?.RelativePath is not string relativePath)
+ {
+ throw new InvalidOperationException("Project item or target path is null");
+ }
+
+ var prefix = Path.GetDirectoryName(relativePath).Replace(Path.DirectorySeparatorChar, '_').Replace(':', '_');
+ var fileNameNoExtension = Path.GetFileNameWithoutExtension(relativePath);
+ var kind = projectItem.Kind.ToLowerInvariant() ?? "unknown-kind";
+ return $"{prefix}{fileNameNoExtension}.{kind}.{suffix}.cs";
+ }
+
private static string? GenerateGlobalXmlns(SourceProductionContext sourceProductionContext, AssemblyCaches xmlnsCache)
{
if (xmlnsCache.GlobalGeneratedXmlnsDefinitions.Count == 0)
diff --git a/src/Controls/src/SourceGen/PrePost.cs b/src/Controls/src/SourceGen/PrePost.cs
index fed5b62057b0..d28aef7b36d2 100644
--- a/src/Controls/src/SourceGen/PrePost.cs
+++ b/src/Controls/src/SourceGen/PrePost.cs
@@ -15,15 +15,19 @@ class PrePost : IDisposable
///
///
///
- public static PrePost NewLineInfo(IndentedTextWriter codeWriter, IXmlLineInfo iXmlLineInfo, string? fileName)
+ public static PrePost NewLineInfo(IndentedTextWriter codeWriter, IXmlLineInfo iXmlLineInfo, ProjectItem? projectItem)
{
- static void LineInfo(IndentedTextWriter codeWriter, IXmlLineInfo iXmlLineInfo, string? fileName)
- => codeWriter.WriteLineNoTabs($"#line {(iXmlLineInfo.LineNumber != -1 ? iXmlLineInfo.LineNumber : 1)} \"{fileName}\"");
+ // Emit #line with an absolute path since relative paths have undefined behavior (https://github.com/dotnet/roslyn/issues/71202#issuecomment-1874649780)
+ static void LineInfo(IndentedTextWriter codeWriter, IXmlLineInfo iXmlLineInfo, ProjectItem? projectItem)
+ {
+ var lineNumber = iXmlLineInfo.LineNumber != -1 ? iXmlLineInfo.LineNumber : 1;
+ codeWriter.WriteLineNoTabs($"#line {lineNumber} \"{projectItem?.TargetPath}\"");
+ }
static void LineDefault(IndentedTextWriter codeWriter, IXmlLineInfo iXmlLineInfo)
=> codeWriter.WriteLineNoTabs("#line default");
- return new(() => LineInfo(codeWriter, iXmlLineInfo, fileName), () => LineDefault(codeWriter, iXmlLineInfo));
+ return new(() => LineInfo(codeWriter, iXmlLineInfo, projectItem), () => LineDefault(codeWriter, iXmlLineInfo));
}
public static PrePost NoBlock() =>
diff --git a/src/Controls/src/SourceGen/ProjectItem.cs b/src/Controls/src/SourceGen/ProjectItem.cs
index 7a935f87ec40..63c18b382a55 100644
--- a/src/Controls/src/SourceGen/ProjectItem.cs
+++ b/src/Controls/src/SourceGen/ProjectItem.cs
@@ -7,6 +7,8 @@
namespace Microsoft.Maui.Controls.SourceGen;
record ProjectItem(AdditionalText AdditionalText, AnalyzerConfigOptions Options)
{
+ private readonly AdditionalText _additionalText = AdditionalText;
+
public string Configuration
=> Options.GetValueOrDefault("build_property.Configuration", "Debug");
@@ -66,5 +68,5 @@ public string? TargetFramework
=> Options.GetValueOrNull("build_property.targetFramework");
public string? TargetPath
- => Options.GetValueOrDefault("build_metadata.additionalfiles.TargetPath", AdditionalText.Path);
+ => Options.GetValueOrDefault("build_metadata.additionalfiles.TargetPath", _additionalText.Path);
}
diff --git a/src/Controls/src/SourceGen/Visitors/SetPropertiesVisitor.cs b/src/Controls/src/SourceGen/Visitors/SetPropertiesVisitor.cs
index b0ac5db8e972..0522c3a43415 100644
--- a/src/Controls/src/SourceGen/Visitors/SetPropertiesVisitor.cs
+++ b/src/Controls/src/SourceGen/Visitors/SetPropertiesVisitor.cs
@@ -474,14 +474,14 @@ static void Set(IndentedTextWriter writer, LocalVariable parentVar, string local
if (node is ValueNode valueNode)
{
- using (context.ProjectItem.EnableLineInfo ? PrePost.NewLineInfo(writer, (IXmlLineInfo)node, context.ProjectItem.RelativePath) : PrePost.NoBlock())
+ using (context.ProjectItem.EnableLineInfo ? PrePost.NewLineInfo(writer, (IXmlLineInfo)node, context.ProjectItem) : PrePost.NoBlock())
{
var valueString = valueNode.ConvertTo(property, context, parentVar);
writer.WriteLine($"{parentVar.Name}.{EscapeIdentifier(localName)} = {valueString};");
}
}
else if (node is ElementNode elementNode)
- using (context.ProjectItem.EnableLineInfo ? PrePost.NewLineInfo(writer, (IXmlLineInfo)node, context.ProjectItem.RelativePath) : PrePost.NoBlock())
+ using (context.ProjectItem.EnableLineInfo ? PrePost.NewLineInfo(writer, (IXmlLineInfo)node, context.ProjectItem) : PrePost.NoBlock())
writer.WriteLine($"{parentVar.Name}.{EscapeIdentifier(localName)} = ({property.Type.ToFQDisplayString()}){(HasDoubleImplicitConversion(context.Variables[elementNode].Type, property.Type, context, out var conv) ? "(" + conv!.ReturnType.ToFQDisplayString() + ")" : string.Empty)}{context.Variables[elementNode].Name};");
}
@@ -610,7 +610,7 @@ static void Add(IndentedTextWriter writer, LocalVariable parentVar, XmlName prop
if (HasDoubleImplicitConversion(context.Variables[valueNode].Type, itemType, context, out var conv))
cast = "(" + conv!.ReturnType.ToFQDisplayString() + ")";
- using (context.ProjectItem.EnableLineInfo ? PrePost.NewLineInfo(writer, (IXmlLineInfo)valueNode, context.ProjectItem.RelativePath) : PrePost.NoBlock())
+ using (context.ProjectItem.EnableLineInfo ? PrePost.NewLineInfo(writer, (IXmlLineInfo)valueNode, context.ProjectItem) : PrePost.NoBlock())
writer.WriteLine($"{parentObj}.Add(({itemType.ToFQDisplayString()}){cast}{context.Variables[valueNode].Name});");
}
diff --git a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/CompiledBindings.cs b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/CompiledBindings.cs
index 84cd5c7f4ff7..40db7a803a23 100644
--- a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/CompiledBindings.cs
+++ b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/CompiledBindings.cs
@@ -1,3 +1,5 @@
+using System;
+using System.IO;
using System.Linq;
using NUnit.Framework;
@@ -50,9 +52,8 @@ public struct Bar
public string Title { get; set; } = "Title";
}
""";
-
- var expected =
-"""
+ var testXamlFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
+ var expected = $$"""
//------------------------------------------------------------------------------
//
@@ -81,7 +82,7 @@ private partial void InitializeComponent()
#if !_MAUIXAML_SG_NAMESCOPE_DISABLE
global::Microsoft.Maui.Controls.Internals.NameScope.SetNameScope(__root, iNameScope);
#endif
-#line 1 "Test.xaml"
+#line 1 "{{testXamlFilePath}}"
bindingExtension.Path = "Foo.Bar.Title";
#line default
var bindingBase = CreateTypedBindingFrom_bindingExtension(bindingExtension);
diff --git a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/LineInfoTests.cs b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/LineInfoTests.cs
new file mode 100644
index 000000000000..58602d6b395e
--- /dev/null
+++ b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/LineInfoTests.cs
@@ -0,0 +1,36 @@
+using System;
+using System.IO;
+using System.Linq;
+using NUnit.Framework;
+
+namespace Microsoft.Maui.Controls.SourceGen.UnitTests.InitializeComponent;
+
+public class LineInfoTests : SourceGenXamlInitializeComponentTestBase
+{
+ [Test]
+ public void DiagnosticShowsLocationInInputXamlFile()
+ {
+ var xaml =
+"""
+
+
+
+
+
+
+
+
+
+
+""";
+
+ var (result, _) = RunGenerator(xaml, string.Empty);
+
+ var generatedCode = result.GeneratedTrees.Single(tree => Path.GetFileName(tree.FilePath) == "Test.xaml.xsg.cs").ToString();
+ var expectedFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
+ Assert.IsTrue(generatedCode.Contains(@$"#line 9 ""{expectedFilePath}""", StringComparison.Ordinal));
+ }
+}
diff --git a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SetBinding.cs b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SetBinding.cs
index 994df67db456..1e3e29253e94 100644
--- a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SetBinding.cs
+++ b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SetBinding.cs
@@ -1,3 +1,5 @@
+using System;
+using System.IO;
using System.Linq;
using NUnit.Framework;
@@ -36,8 +38,8 @@ public TestPage()
}
""";
- var expected =
-"""
+ var testXamlFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
+ var expected = $$"""
//------------------------------------------------------------------------------
//
@@ -66,7 +68,7 @@ private partial void InitializeComponent()
#if !_MAUIXAML_SG_NAMESCOPE_DISABLE
global::Microsoft.Maui.Controls.Internals.NameScope.SetNameScope(__root, iNameScope);
#endif
-#line 1 "Test.xaml"
+#line 1 "{{testXamlFilePath}}"
bindingExtension.Path = "Title";
#line default
var bindingBase = new global::Microsoft.Maui.Controls.Binding(bindingExtension.Path, bindingExtension.Mode, bindingExtension.Converter, bindingExtension.ConverterParameter, bindingExtension.StringFormat, bindingExtension.Source) { UpdateSourceEventName = bindingExtension.UpdateSourceEventName, FallbackValue = bindingExtension.FallbackValue, TargetNullValue = bindingExtension.TargetNullValue };
diff --git a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs
index f23faac3404e..ffbe73ea692f 100644
--- a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs
+++ b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using System.Linq;
using NUnit.Framework;
@@ -43,8 +44,8 @@ public TestPage()
}
""";
-var expected =
-"""
+ var testXamlFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
+ var expected = $$"""
//------------------------------------------------------------------------------
//
@@ -97,7 +98,7 @@ private partial void InitializeComponent()
#if !_MAUIXAML_SG_NAMESCOPE_DISABLE
global::Microsoft.Maui.Controls.Internals.INameScope iNameScope2 = new global::Microsoft.Maui.Controls.Internals.NameScope();
#endif
-#line 8 "Test.xaml"
+#line 8 "{{testXamlFilePath}}"
var xamlServiceProvider1 = new global::Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider(this);
var iProvideValueTarget1 = new global::Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider(
new object?[] {setter, style1, __root},
@@ -118,16 +119,16 @@ private partial void InitializeComponent()
xamlServiceProvider1.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IXamlTypeResolver), new global::Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver(xmlNamespaceResolver1, typeof(global::Test.TestPage).Assembly));
setter.Property = ((global::Microsoft.Maui.Controls.IExtendedTypeConverter)new global::Microsoft.Maui.Controls.BindablePropertyConverter()).ConvertFromInvariantString("TextColor", xamlServiceProvider1) as global::Microsoft.Maui.Controls.BindableProperty;
#line default
-#line 8 "Test.xaml"
+#line 8 "{{testXamlFilePath}}"
setter.Value = "Pink";
#line default
var setter2 = new global::Microsoft.Maui.Controls.Setter {Property = global::Microsoft.Maui.Controls.Label.TextColorProperty, Value = global::Microsoft.Maui.Graphics.Color.Parse("Pink")};
if (global::Microsoft.Maui.VisualDiagnostics.GetSourceInfo(setter2!) == null)
global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(setter2!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 8, 14);
-#line 8 "Test.xaml"
+#line 8 "{{testXamlFilePath}}"
((global::System.Collections.Generic.ICollection)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter2);
#line default
-#line 9 "Test.xaml"
+#line 9 "{{testXamlFilePath}}"
var xamlServiceProvider2 = new global::Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider(this);
var iProvideValueTarget2 = new global::Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider(
new object?[] {setter1, style1, __root},
@@ -148,13 +149,13 @@ private partial void InitializeComponent()
xamlServiceProvider2.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IXamlTypeResolver), new global::Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver(xmlNamespaceResolver2, typeof(global::Test.TestPage).Assembly));
setter1.Property = ((global::Microsoft.Maui.Controls.IExtendedTypeConverter)new global::Microsoft.Maui.Controls.BindablePropertyConverter()).ConvertFromInvariantString("IsVisible", xamlServiceProvider2) as global::Microsoft.Maui.Controls.BindableProperty;
#line default
-#line 1 "Test.xaml"
+#line 1 "{{testXamlFilePath}}"
setter1.Value = "True";
#line default
var setter3 = new global::Microsoft.Maui.Controls.Setter {Property = global::Microsoft.Maui.Controls.VisualElement.IsVisibleProperty, Value = (bool)new global::Microsoft.Maui.Controls.VisualElement.VisibilityConverter().ConvertFromInvariantString("True")!};
if (global::Microsoft.Maui.VisualDiagnostics.GetSourceInfo(setter3!) == null)
global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(setter3!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 9, 14);
-#line 9 "Test.xaml"
+#line 9 "{{testXamlFilePath}}"
((global::System.Collections.Generic.ICollection)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter3);
#line default
var resourceDictionary = __root.Resources;
diff --git a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SourceGenXamlInitializeComponentTests.cs b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SourceGenXamlInitializeComponentTests.cs
index c546788146d5..887984940dde 100644
--- a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SourceGenXamlInitializeComponentTests.cs
+++ b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SourceGenXamlInitializeComponentTests.cs
@@ -1,3 +1,5 @@
+using System;
+using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -16,7 +18,9 @@ protected record AdditionalXamlFile(string Path, string Content, string? Relativ
{
var compilation = CreateMauiCompilation();
compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(code));
- var result = RunGenerator(compilation, new AdditionalXamlFile("Test.xaml", xaml, TargetFramework: targetFramework, NoWarn: noWarn));
+ var workingDirectory = Environment.CurrentDirectory;
+ var xamlFile = new AdditionalXamlFile(Path.Combine(workingDirectory, "Test.xaml"), xaml, RelativePath: "Test.xaml", TargetFramework: targetFramework, NoWarn: noWarn);
+ var result = RunGenerator(compilation, xamlFile);
var generated = result.Results.SingleOrDefault().GeneratedSources.SingleOrDefault(gs => gs.HintName.EndsWith(".xsg.cs")).SourceText?.ToString();
return (result, generated);
diff --git a/src/Controls/tests/SourceGen.UnitTests/SourceGeneratorDriver.cs b/src/Controls/tests/SourceGen.UnitTests/SourceGeneratorDriver.cs
index 85259d23cb16..60fc6fdec84f 100644
--- a/src/Controls/tests/SourceGen.UnitTests/SourceGeneratorDriver.cs
+++ b/src/Controls/tests/SourceGen.UnitTests/SourceGeneratorDriver.cs
@@ -94,6 +94,7 @@ private static MetadataReference[] GetMauiReferences()
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.Private.CoreLib.dll")),
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.Runtime.dll")),
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.ObjectModel.dll")),
+ MetadataReference.CreateFromFile(typeof(Uri).Assembly.Location), //System.Private.Uri
MetadataReference.CreateFromFile(typeof(Color).Assembly.Location), //Graphics
MetadataReference.CreateFromFile(typeof(Button).Assembly.Location), //Controls
MetadataReference.CreateFromFile(typeof(BindingExtension).Assembly.Location), //Xaml