Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions src/Controls/src/SourceGen/CodeBehindGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 8 additions & 4 deletions src/Controls/src/SourceGen/PrePost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@ class PrePost : IDisposable
/// <param name="iXmlLineInfo"></param>
/// <param name="fileName"></param>
/// <returns></returns>
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() =>
Expand Down
4 changes: 3 additions & 1 deletion src/Controls/src/SourceGen/ProjectItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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);
}
6 changes: 3 additions & 3 deletions src/Controls/src/SourceGen/Visitors/SetPropertiesVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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};");
}

Expand Down Expand Up @@ -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});");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System;
using System.IO;
using System.Linq;
using NUnit.Framework;

Expand Down Expand Up @@ -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 = $$"""

//------------------------------------------------------------------------------
// <auto-generated>
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 =
"""
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.TestPage">
<ContentPage.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles\Colors.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ContentPage.Resources>
</ContentPage>
""";

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));
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System;
using System.IO;
using System.Linq;
using NUnit.Framework;

Expand Down Expand Up @@ -36,8 +38,8 @@ public TestPage()
}
""";

var expected =
"""
var testXamlFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
var expected = $$"""

//------------------------------------------------------------------------------
// <auto-generated>
Expand Down Expand Up @@ -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 };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Linq;
using NUnit.Framework;

Expand Down Expand Up @@ -43,8 +44,8 @@ public TestPage()
}
""";

var expected =
"""
var testXamlFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
var expected = $$"""

//------------------------------------------------------------------------------
// <auto-generated>
Expand Down Expand Up @@ -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},
Expand All @@ -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<global::Microsoft.Maui.Controls.Setter>)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},
Expand All @@ -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<global::Microsoft.Maui.Controls.Setter>)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter3);
#line default
var resourceDictionary = __root.Resources;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -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<CodeBehindGenerator>(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<CodeBehindGenerator>(compilation, xamlFile);
var generated = result.Results.SingleOrDefault().GeneratedSources.SingleOrDefault(gs => gs.HintName.EndsWith(".xsg.cs")).SourceText?.ToString();

return (result, generated);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading