Skip to content

Commit 6c167c6

Browse files
[X] report diagnostice on ambiguous type
1 parent 2c4b31a commit 6c167c6

7 files changed

Lines changed: 97 additions & 52 deletions

File tree

src/Controls/src/SourceGen/AnalyzerReleases.Unshipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
Rule ID | Category | Severity | Notes
77
--------|----------|----------|-------
88
MAUIG1001 | XamlParsing | Error | XamlParsingFailed
9+
MAUIG1002 | XamlParsing | Error | AmbiguousType

src/Controls/src/SourceGen/CodeBehindGenerator.cs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ static void GenerateXamlCodeBehind(XamlProjectItem? xamlItem, Compilation compil
369369
}
370370

371371
var uid = Crc64.ComputeHashString($"{compilation.AssemblyName}.{itemName}");
372-
if (!TryParseXaml(xamlItem, uid, compilation, xmlnsCache, typeCache, context.CancellationToken, out var accessModifier, out var rootType, out var rootClrNamespace, out var generateDefaultCtor, out var addXamlCompilationAttribute, out var hideFromIntellisense, out var XamlResourceIdOnly, out var baseType, out var namedFields))
372+
if (!TryParseXaml(xamlItem, uid, compilation, xmlnsCache, typeCache, context.CancellationToken, context.ReportDiagnostic, out var accessModifier, out var rootType, out var rootClrNamespace, out var generateDefaultCtor, out var addXamlCompilationAttribute, out var hideFromIntellisense, out var XamlResourceIdOnly, out var baseType, out var namedFields))
373373
{
374374
return;
375375
}
@@ -472,7 +472,7 @@ static void GenerateXamlCodeBehind(XamlProjectItem? xamlItem, Compilation compil
472472
context.AddSource(hintName, SourceText.From(sb.ToString(), Encoding.UTF8));
473473
}
474474

475-
static bool TryParseXaml(XamlProjectItem parseResult, string uid, Compilation compilation, AssemblyCaches xmlnsCache, IDictionary<XmlType, string> typeCache, CancellationToken cancellationToken, out string? accessModifier, out string? rootType, out string? rootClrNamespace, out bool generateDefaultCtor, out bool addXamlCompilationAttribute, out bool hideFromIntellisense, out bool xamlResourceIdOnly, out string? baseType, out IEnumerable<(string, string?, string)>? namedFields)
475+
static bool TryParseXaml(XamlProjectItem parseResult, string uid, Compilation compilation, AssemblyCaches xmlnsCache, IDictionary<XmlType, string> typeCache, CancellationToken cancellationToken, Action<Diagnostic> reportDiagnostic, out string? accessModifier, out string? rootType, out string? rootClrNamespace, out bool generateDefaultCtor, out bool addXamlCompilationAttribute, out bool hideFromIntellisense, out bool xamlResourceIdOnly, out string? baseType, out IEnumerable<(string, string?, string)>? namedFields)
476476
{
477477
accessModifier = null;
478478
rootType = null;
@@ -522,9 +522,9 @@ static bool TryParseXaml(XamlProjectItem parseResult, string uid, Compilation co
522522
return true;
523523
}
524524

525-
namedFields = GetNamedFields(root, nsmgr, compilation, xmlnsCache, typeCache, cancellationToken);
525+
namedFields = GetNamedFields(root, nsmgr, compilation, xmlnsCache, typeCache, cancellationToken, reportDiagnostic);
526526
var typeArguments = GetAttributeValue(root, "TypeArguments", XamlParser.X2006Uri, XamlParser.X2009Uri);
527-
baseType = GetTypeName(new XmlType(root.NamespaceURI, root.LocalName, typeArguments != null ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null) : null), compilation, xmlnsCache, typeCache);
527+
baseType = GetTypeName(new XmlType(root.NamespaceURI, root.LocalName, typeArguments != null ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null) : null), compilation, xmlnsCache, typeCache, reportDiagnostic);
528528
if (baseType == null)
529529
return false;
530530

@@ -554,7 +554,7 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
554554
return true;
555555
}
556556

557-
static IEnumerable<(string name, string? type, string accessModifier)> GetNamedFields(XmlNode root, XmlNamespaceManager nsmgr, Compilation compilation, AssemblyCaches xmlnsCache, IDictionary<XmlType, string> typeCache, CancellationToken cancellationToken)
557+
static IEnumerable<(string name, string? type, string accessModifier)> GetNamedFields(XmlNode root, XmlNamespaceManager nsmgr, Compilation compilation, AssemblyCaches xmlnsCache, IDictionary<XmlType, string> typeCache, CancellationToken cancellationToken, Action<Diagnostic> reportDiagnostic)
558558
{
559559
var xPrefix = nsmgr.LookupPrefix(XamlParser.X2006Uri) ?? nsmgr.LookupPrefix(XamlParser.X2009Uri);
560560
if (xPrefix == null)
@@ -586,11 +586,11 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
586586
accessModifier = "private";
587587
}
588588

589-
yield return (name ?? "", GetTypeName(xmlType, compilation, xmlnsCache, typeCache), accessModifier);
589+
yield return (name ?? "", GetTypeName(xmlType, compilation, xmlnsCache, typeCache, reportDiagnostic), accessModifier);
590590
}
591591
}
592592

593-
static string? GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCaches xmlnsCache, IDictionary<XmlType, string> typeCache)
593+
static string? GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCaches xmlnsCache, IDictionary<XmlType, string> typeCache, Action<Diagnostic> reportDiagnostic)
594594
{
595595
if (typeCache.TryGetValue(xmlType, out string returnType))
596596
{
@@ -605,12 +605,12 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
605605
else
606606
{
607607
// It's an external, non-built-in namespace URL.
608-
returnType = GetTypeNameFromCustomNamespace(xmlType, compilation, xmlnsCache);
608+
returnType = GetTypeNameFromCustomNamespace(xmlType, compilation, xmlnsCache, reportDiagnostic);
609609
}
610610

611611
if (xmlType.TypeArguments != null)
612612
{
613-
returnType = $"{returnType}<{string.Join(", ", xmlType.TypeArguments.Select(typeArg => GetTypeName(typeArg, compilation, xmlnsCache, typeCache)))}>";
613+
returnType = $"{returnType}<{string.Join(", ", xmlType.TypeArguments.Select(typeArg => GetTypeName(typeArg, compilation, xmlnsCache, typeCache, reportDiagnostic)))}>";
614614
}
615615

616616
if (returnType == null)
@@ -640,10 +640,10 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
640640
return XmlnsHelper.ParseNamespaceFromXmlns(namespaceuri);
641641
}
642642

643-
static string GetTypeNameFromCustomNamespace(XmlType xmlType, Compilation compilation, AssemblyCaches xmlnsCache)
643+
static string GetTypeNameFromCustomNamespace(XmlType xmlType, Compilation compilation, AssemblyCaches xmlnsCache, Action<Diagnostic> reportDiagnostic)
644644
{
645645
#nullable disable
646-
string typeName = xmlType.GetTypeReference<string>(xmlnsCache.XmlnsDefinitions, null,
646+
IEnumerable<string> typeNames = xmlType.GetTypeReferences<string>(xmlnsCache.XmlnsDefinitions, null,
647647
(typeInfo) =>
648648
{
649649
string typeName = typeInfo.typeName.Replace('+', '/'); //Nested types
@@ -679,7 +679,13 @@ static string GetTypeNameFromCustomNamespace(XmlType xmlType, Compilation compil
679679
return null;
680680
});
681681

682-
return typeName;
682+
if (typeNames.Distinct().Skip(1).Any())
683+
{
684+
reportDiagnostic(Diagnostic.Create(Descriptors.AmbiguousType, Location.None,
685+
new[] { xmlType.Name, xmlType.NamespaceUri }.ToArray()));
686+
687+
}
688+
return typeNames.FirstOrDefault();
683689
#nullable enable
684690
}
685691

@@ -893,9 +899,6 @@ public bool Equals(Compilation x, Compilation y)
893899
return x.ExternalReferences.OfType<PortableExecutableReference>().SequenceEqual(y.ExternalReferences.OfType<PortableExecutableReference>());
894900
}
895901

896-
public int GetHashCode(Compilation obj)
897-
{
898-
return obj.References.GetHashCode();
899-
}
902+
public int GetHashCode(Compilation obj) => obj.References.GetHashCode();
900903
}
901904
}

src/Controls/src/SourceGen/Descriptors.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ public static class Descriptors
1212
category: "XamlParsing",
1313
defaultSeverity: DiagnosticSeverity.Error,
1414
isEnabledByDefault: true);
15+
16+
public static DiagnosticDescriptor AmbiguousType = new DiagnosticDescriptor(
17+
id: "MAUIG1002",
18+
title: new LocalizableResourceString(nameof(MauiGResources.AmbiguousTypeTitle), MauiGResources.ResourceManager, typeof(MauiGResources)),
19+
messageFormat: new LocalizableResourceString(nameof(MauiGResources.AmbiguousTypeMessage), MauiGResources.ResourceManager, typeof(MauiGResources)),
20+
category: "XamlParsing",
21+
defaultSeverity: DiagnosticSeverity.Error,
22+
isEnabledByDefault: true);
1523
}
1624
}
1725

src/Controls/src/SourceGen/MauiGResources.Designer.cs

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Controls/src/SourceGen/MauiGResources.resx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<root>
3-
<!--
3+
<!--
44
Microsoft ResX Schema
55
66
Version 2.0
@@ -123,4 +123,12 @@
123123
<data name="XamlParsingFailed" xml:space="preserve">
124124
<value>XAML parsing failed</value>
125125
</data>
126-
</root>
126+
<data name="AmbiguousTypeTitle" xml:space="preserve">
127+
<value>Ambiguous Type</value>
128+
<comment>/</comment>
129+
</data>
130+
<data name="AmbiguousTypeMessage" xml:space="preserve">
131+
<value>Multiple names found in the current xml namespace. use a more specific prefix.</value>
132+
<comment>/</comment>
133+
</data>
134+
</root>

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,46 @@ public void TestCodeBehindGenerator_NotXaml()
275275

276276
Assert.That(result.Results.Single().GeneratedSources.Where(gs => gs.HintName.EndsWith(".sg.cs", StringComparison.OrdinalIgnoreCase)), Is.Empty);
277277
}
278+
279+
[Test]
280+
public void TestCodeBehindGenerator_ConflictingNames()
281+
{
282+
var code =
283+
"""
284+
using Microsoft.Maui.Controls;
285+
[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "http://schemas.microsoft.com/dotnet/2021/maui")]
286+
[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "Ns1")]
287+
[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "Ns2")]
288+
289+
namespace Ns1
290+
{
291+
public class Conflicting : Label { }
292+
}
293+
294+
namespace Ns2
295+
{
296+
public class Conflicting : Label { }
297+
}
298+
""";
299+
300+
var xaml =
301+
"""
302+
<?xml version="1.0" encoding="UTF-8"?>
303+
<ContentPage
304+
xmlns="http://schemas.microsoft.com/dotnet/maui/global"
305+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
306+
x:Class="Test.TestPage">
307+
<Conflicting x:Name="conflicting" Text="Hello MAUI!" />
308+
</ContentPage>
309+
""";
310+
311+
var compilation = SourceGeneratorDriver.CreateMauiCompilation();
312+
compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(code));
313+
314+
var result = SourceGeneratorDriver.RunGenerator<CodeBehindGenerator>(compilation, new AdditionalXamlFile("Test.xaml", xaml));
315+
316+
Assert.IsTrue(result.Diagnostics.Any());
317+
318+
//var generated = result.Results.Single().GeneratedSources.Single().SourceText.ToString();
319+
}
278320
}

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

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)