Skip to content

Commit d82f913

Browse files
simonrozsivalSimon Rozsival
andauthored
[XSG] Use absolute file paths when generating #line (#31519)
* [XSG] Use absolute file paths when generating #line * Fix snapshot tests * Use TargetPath * Fix hint name * Fix test build * Fix test --------- Co-authored-by: Simon Rozsival <srozsival@microsoft.com>
1 parent a17b617 commit d82f913

File tree

10 files changed

+95
-30
lines changed

10 files changed

+95
-30
lines changed

src/Controls/src/SourceGen/CodeBehindGenerator.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,11 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
9696
initContext.RegisterSourceOutput(xamlSourceProviderForCB, static (sourceProductionContext, provider) =>
9797
{
9898
var (xamlItem, xmlnsCache, typeCache, compilation) = provider;
99-
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, '_');
10099

101100
try
102101
{
103102
var code = CodeBehindCodeWriter.GenerateXamlCodeBehind(xamlItem, compilation, sourceProductionContext.ReportDiagnostic, sourceProductionContext.CancellationToken, xmlnsCache, typeCache);
104-
sourceProductionContext.AddSource(fileName, code);
103+
sourceProductionContext.AddSource(GetHintName(xamlItem?.ProjectItem, "sg"), code);
105104
}
106105
catch (Exception e)
107106
{
@@ -115,7 +114,10 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
115114
{
116115
var (xamlItem, xmlnsCache, typeCache, compilation) = provider;
117116

118-
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, '_');
117+
if (xamlItem?.ProjectItem?.RelativePath is not string relativePath)
118+
{
119+
throw new InvalidOperationException("Xaml item or target path is null");
120+
}
119121

120122
if (!ShouldGenerateSourceGenInitializeComponent(xamlItem, xmlnsCache, compilation))
121123
return;
@@ -125,7 +127,7 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
125127
if (xamlItem != null && xamlItem.Exception != null)
126128
{
127129
var lineInfo = xamlItem.Exception is XamlParseException xpe ? xpe.XmlInfo : new XmlLineInfo();
128-
var location = LocationCreate(fileName, lineInfo, string.Empty);
130+
var location = LocationCreate(relativePath, lineInfo, string.Empty);
129131
sourceProductionContext.ReportDiagnostic(Diagnostic.Create(Descriptors.XamlParserError, location, xamlItem.Exception.Message));
130132
}
131133
return;
@@ -137,14 +139,13 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
137139
return;
138140

139141
var code = InitializeComponentCodeWriter.GenerateInitializeComponent(xamlItem, compilation, sourceProductionContext, xmlnsCache, typeCache);
140-
sourceProductionContext.AddSource(fileName, code);
142+
sourceProductionContext.AddSource(GetHintName(xamlItem.ProjectItem, "xsg"), code);
141143
}
142144
catch (Exception e)
143145
{
144146
var location = xamlItem?.ProjectItem?.RelativePath is not null ? Location.Create(xamlItem.ProjectItem.RelativePath, new TextSpan(), new LinePositionSpan()) : null;
145147
sourceProductionContext.ReportDiagnostic(Diagnostic.Create(Descriptors.XamlParserError, location, e.Message));
146148
}
147-
148149
});
149150

150151
// Register the CSS pipeline
@@ -184,6 +185,19 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
184185
});
185186
}
186187

188+
private static string GetHintName(ProjectItem? projectItem, string suffix)
189+
{
190+
if (projectItem?.RelativePath is not string relativePath)
191+
{
192+
throw new InvalidOperationException("Project item or target path is null");
193+
}
194+
195+
var prefix = Path.GetDirectoryName(relativePath).Replace(Path.DirectorySeparatorChar, '_').Replace(':', '_');
196+
var fileNameNoExtension = Path.GetFileNameWithoutExtension(relativePath);
197+
var kind = projectItem.Kind.ToLowerInvariant() ?? "unknown-kind";
198+
return $"{prefix}{fileNameNoExtension}.{kind}.{suffix}.cs";
199+
}
200+
187201
private static string? GenerateGlobalXmlns(SourceProductionContext sourceProductionContext, AssemblyCaches xmlnsCache)
188202
{
189203
if (xmlnsCache.GlobalGeneratedXmlnsDefinitions.Count == 0)

src/Controls/src/SourceGen/PrePost.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@ class PrePost : IDisposable
1515
/// <param name="iXmlLineInfo"></param>
1616
/// <param name="fileName"></param>
1717
/// <returns></returns>
18-
public static PrePost NewLineInfo(IndentedTextWriter codeWriter, IXmlLineInfo iXmlLineInfo, string? fileName)
18+
public static PrePost NewLineInfo(IndentedTextWriter codeWriter, IXmlLineInfo iXmlLineInfo, ProjectItem? projectItem)
1919
{
20-
static void LineInfo(IndentedTextWriter codeWriter, IXmlLineInfo iXmlLineInfo, string? fileName)
21-
=> codeWriter.WriteLineNoTabs($"#line {(iXmlLineInfo.LineNumber != -1 ? iXmlLineInfo.LineNumber : 1)} \"{fileName}\"");
20+
// Emit #line with an absolute path since relative paths have undefined behavior (https://github.com/dotnet/roslyn/issues/71202#issuecomment-1874649780)
21+
static void LineInfo(IndentedTextWriter codeWriter, IXmlLineInfo iXmlLineInfo, ProjectItem? projectItem)
22+
{
23+
var lineNumber = iXmlLineInfo.LineNumber != -1 ? iXmlLineInfo.LineNumber : 1;
24+
codeWriter.WriteLineNoTabs($"#line {lineNumber} \"{projectItem?.TargetPath}\"");
25+
}
2226

2327
static void LineDefault(IndentedTextWriter codeWriter, IXmlLineInfo iXmlLineInfo)
2428
=> codeWriter.WriteLineNoTabs("#line default");
2529

26-
return new(() => LineInfo(codeWriter, iXmlLineInfo, fileName), () => LineDefault(codeWriter, iXmlLineInfo));
30+
return new(() => LineInfo(codeWriter, iXmlLineInfo, projectItem), () => LineDefault(codeWriter, iXmlLineInfo));
2731
}
2832

2933
public static PrePost NoBlock() =>

src/Controls/src/SourceGen/ProjectItem.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
namespace Microsoft.Maui.Controls.SourceGen;
88
record ProjectItem(AdditionalText AdditionalText, AnalyzerConfigOptions Options)
99
{
10+
private readonly AdditionalText _additionalText = AdditionalText;
11+
1012
public string Configuration
1113
=> Options.GetValueOrDefault("build_property.Configuration", "Debug");
1214

@@ -66,5 +68,5 @@ public string? TargetFramework
6668
=> Options.GetValueOrNull("build_property.targetFramework");
6769

6870
public string? TargetPath
69-
=> Options.GetValueOrDefault("build_metadata.additionalfiles.TargetPath", AdditionalText.Path);
71+
=> Options.GetValueOrDefault("build_metadata.additionalfiles.TargetPath", _additionalText.Path);
7072
}

src/Controls/src/SourceGen/Visitors/SetPropertiesVisitor.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -474,14 +474,14 @@ static void Set(IndentedTextWriter writer, LocalVariable parentVar, string local
474474

475475
if (node is ValueNode valueNode)
476476
{
477-
using (context.ProjectItem.EnableLineInfo ? PrePost.NewLineInfo(writer, (IXmlLineInfo)node, context.ProjectItem.RelativePath) : PrePost.NoBlock())
477+
using (context.ProjectItem.EnableLineInfo ? PrePost.NewLineInfo(writer, (IXmlLineInfo)node, context.ProjectItem) : PrePost.NoBlock())
478478
{
479479
var valueString = valueNode.ConvertTo(property, context, parentVar);
480480
writer.WriteLine($"{parentVar.Name}.{EscapeIdentifier(localName)} = {valueString};");
481481
}
482482
}
483483
else if (node is ElementNode elementNode)
484-
using (context.ProjectItem.EnableLineInfo ? PrePost.NewLineInfo(writer, (IXmlLineInfo)node, context.ProjectItem.RelativePath) : PrePost.NoBlock())
484+
using (context.ProjectItem.EnableLineInfo ? PrePost.NewLineInfo(writer, (IXmlLineInfo)node, context.ProjectItem) : PrePost.NoBlock())
485485
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};");
486486
}
487487

@@ -610,7 +610,7 @@ static void Add(IndentedTextWriter writer, LocalVariable parentVar, XmlName prop
610610
if (HasDoubleImplicitConversion(context.Variables[valueNode].Type, itemType, context, out var conv))
611611
cast = "(" + conv!.ReturnType.ToFQDisplayString() + ")";
612612

613-
using (context.ProjectItem.EnableLineInfo ? PrePost.NewLineInfo(writer, (IXmlLineInfo)valueNode, context.ProjectItem.RelativePath) : PrePost.NoBlock())
613+
using (context.ProjectItem.EnableLineInfo ? PrePost.NewLineInfo(writer, (IXmlLineInfo)valueNode, context.ProjectItem) : PrePost.NoBlock())
614614
writer.WriteLine($"{parentObj}.Add(({itemType.ToFQDisplayString()}){cast}{context.Variables[valueNode].Name});");
615615
}
616616

src/Controls/tests/SourceGen.UnitTests/InitializeComponent/CompiledBindings.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System;
2+
using System.IO;
13
using System.Linq;
24
using NUnit.Framework;
35

@@ -50,9 +52,8 @@ public struct Bar
5052
public string Title { get; set; } = "Title";
5153
}
5254
""";
53-
54-
var expected =
55-
"""
55+
var testXamlFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
56+
var expected = $$"""
5657
5758
//------------------------------------------------------------------------------
5859
// <auto-generated>
@@ -81,7 +82,7 @@ private partial void InitializeComponent()
8182
#if !_MAUIXAML_SG_NAMESCOPE_DISABLE
8283
global::Microsoft.Maui.Controls.Internals.NameScope.SetNameScope(__root, iNameScope);
8384
#endif
84-
#line 1 "Test.xaml"
85+
#line 1 "{{testXamlFilePath}}"
8586
bindingExtension.Path = "Foo.Bar.Title";
8687
#line default
8788
var bindingBase = CreateTypedBindingFrom_bindingExtension(bindingExtension);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq;
4+
using NUnit.Framework;
5+
6+
namespace Microsoft.Maui.Controls.SourceGen.UnitTests.InitializeComponent;
7+
8+
public class LineInfoTests : SourceGenXamlInitializeComponentTestBase
9+
{
10+
[Test]
11+
public void DiagnosticShowsLocationInInputXamlFile()
12+
{
13+
var xaml =
14+
"""
15+
<?xml version="1.0" encoding="UTF-8"?>
16+
<ContentPage
17+
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
18+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
19+
x:Class="Test.TestPage">
20+
<ContentPage.Resources>
21+
<ResourceDictionary>
22+
<ResourceDictionary.MergedDictionaries>
23+
<ResourceDictionary Source="Resources/Styles\Colors.xaml" />
24+
</ResourceDictionary.MergedDictionaries>
25+
</ResourceDictionary>
26+
</ContentPage.Resources>
27+
</ContentPage>
28+
""";
29+
30+
var (result, _) = RunGenerator(xaml, string.Empty);
31+
32+
var generatedCode = result.GeneratedTrees.Single(tree => Path.GetFileName(tree.FilePath) == "Test.xaml.xsg.cs").ToString();
33+
var expectedFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
34+
Assert.IsTrue(generatedCode.Contains(@$"#line 9 ""{expectedFilePath}""", StringComparison.Ordinal));
35+
}
36+
}

src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SetBinding.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System;
2+
using System.IO;
13
using System.Linq;
24
using NUnit.Framework;
35

@@ -36,8 +38,8 @@ public TestPage()
3638
}
3739
""";
3840

39-
var expected =
40-
"""
41+
var testXamlFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
42+
var expected = $$"""
4143
4244
//------------------------------------------------------------------------------
4345
// <auto-generated>
@@ -66,7 +68,7 @@ private partial void InitializeComponent()
6668
#if !_MAUIXAML_SG_NAMESCOPE_DISABLE
6769
global::Microsoft.Maui.Controls.Internals.NameScope.SetNameScope(__root, iNameScope);
6870
#endif
69-
#line 1 "Test.xaml"
71+
#line 1 "{{testXamlFilePath}}"
7072
bindingExtension.Path = "Title";
7173
#line default
7274
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 };

src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.IO;
23
using System.Linq;
34
using NUnit.Framework;
45

@@ -43,8 +44,8 @@ public TestPage()
4344
}
4445
""";
4546

46-
var expected =
47-
"""
47+
var testXamlFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
48+
var expected = $$"""
4849
4950
//------------------------------------------------------------------------------
5051
// <auto-generated>
@@ -97,7 +98,7 @@ private partial void InitializeComponent()
9798
#if !_MAUIXAML_SG_NAMESCOPE_DISABLE
9899
global::Microsoft.Maui.Controls.Internals.INameScope iNameScope2 = new global::Microsoft.Maui.Controls.Internals.NameScope();
99100
#endif
100-
#line 8 "Test.xaml"
101+
#line 8 "{{testXamlFilePath}}"
101102
var xamlServiceProvider1 = new global::Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider(this);
102103
var iProvideValueTarget1 = new global::Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider(
103104
new object?[] {setter, style1, __root},
@@ -118,16 +119,16 @@ private partial void InitializeComponent()
118119
xamlServiceProvider1.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IXamlTypeResolver), new global::Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver(xmlNamespaceResolver1, typeof(global::Test.TestPage).Assembly));
119120
setter.Property = ((global::Microsoft.Maui.Controls.IExtendedTypeConverter)new global::Microsoft.Maui.Controls.BindablePropertyConverter()).ConvertFromInvariantString("TextColor", xamlServiceProvider1) as global::Microsoft.Maui.Controls.BindableProperty;
120121
#line default
121-
#line 8 "Test.xaml"
122+
#line 8 "{{testXamlFilePath}}"
122123
setter.Value = "Pink";
123124
#line default
124125
var setter2 = new global::Microsoft.Maui.Controls.Setter {Property = global::Microsoft.Maui.Controls.Label.TextColorProperty, Value = global::Microsoft.Maui.Graphics.Color.Parse("Pink")};
125126
if (global::Microsoft.Maui.VisualDiagnostics.GetSourceInfo(setter2!) == null)
126127
global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(setter2!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 8, 14);
127-
#line 8 "Test.xaml"
128+
#line 8 "{{testXamlFilePath}}"
128129
((global::System.Collections.Generic.ICollection<global::Microsoft.Maui.Controls.Setter>)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter2);
129130
#line default
130-
#line 9 "Test.xaml"
131+
#line 9 "{{testXamlFilePath}}"
131132
var xamlServiceProvider2 = new global::Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider(this);
132133
var iProvideValueTarget2 = new global::Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider(
133134
new object?[] {setter1, style1, __root},
@@ -148,13 +149,13 @@ private partial void InitializeComponent()
148149
xamlServiceProvider2.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IXamlTypeResolver), new global::Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver(xmlNamespaceResolver2, typeof(global::Test.TestPage).Assembly));
149150
setter1.Property = ((global::Microsoft.Maui.Controls.IExtendedTypeConverter)new global::Microsoft.Maui.Controls.BindablePropertyConverter()).ConvertFromInvariantString("IsVisible", xamlServiceProvider2) as global::Microsoft.Maui.Controls.BindableProperty;
150151
#line default
151-
#line 1 "Test.xaml"
152+
#line 1 "{{testXamlFilePath}}"
152153
setter1.Value = "True";
153154
#line default
154155
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")!};
155156
if (global::Microsoft.Maui.VisualDiagnostics.GetSourceInfo(setter3!) == null)
156157
global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(setter3!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 9, 14);
157-
#line 9 "Test.xaml"
158+
#line 9 "{{testXamlFilePath}}"
158159
((global::System.Collections.Generic.ICollection<global::Microsoft.Maui.Controls.Setter>)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter3);
159160
#line default
160161
var resourceDictionary = __root.Resources;

src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SourceGenXamlInitializeComponentTests.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System;
2+
using System.IO;
13
using System.Linq;
24
using Microsoft.CodeAnalysis;
35
using Microsoft.CodeAnalysis.CSharp;
@@ -16,7 +18,9 @@ protected record AdditionalXamlFile(string Path, string Content, string? Relativ
1618
{
1719
var compilation = CreateMauiCompilation();
1820
compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(code));
19-
var result = RunGenerator<CodeBehindGenerator>(compilation, new AdditionalXamlFile("Test.xaml", xaml, TargetFramework: targetFramework, NoWarn: noWarn));
21+
var workingDirectory = Environment.CurrentDirectory;
22+
var xamlFile = new AdditionalXamlFile(Path.Combine(workingDirectory, "Test.xaml"), xaml, RelativePath: "Test.xaml", TargetFramework: targetFramework, NoWarn: noWarn);
23+
var result = RunGenerator<CodeBehindGenerator>(compilation, xamlFile);
2024
var generated = result.Results.SingleOrDefault().GeneratedSources.SingleOrDefault(gs => gs.HintName.EndsWith(".xsg.cs")).SourceText?.ToString();
2125

2226
return (result, generated);

src/Controls/tests/SourceGen.UnitTests/SourceGeneratorDriver.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ private static MetadataReference[] GetMauiReferences()
9494
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.Private.CoreLib.dll")),
9595
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.Runtime.dll")),
9696
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.ObjectModel.dll")),
97+
MetadataReference.CreateFromFile(typeof(Uri).Assembly.Location), //System.Private.Uri
9798
MetadataReference.CreateFromFile(typeof(Color).Assembly.Location), //Graphics
9899
MetadataReference.CreateFromFile(typeof(Button).Assembly.Location), //Controls
99100
MetadataReference.CreateFromFile(typeof(BindingExtension).Assembly.Location), //Xaml

0 commit comments

Comments
 (0)