diff --git a/src/Uno.Analyzers.Tests/UnoNotImplementedTests.cs b/src/Uno.Analyzers.Tests/UnoNotImplementedTests.cs
index 53b09a298429..d504045ceb74 100644
--- a/src/Uno.Analyzers.Tests/UnoNotImplementedTests.cs
+++ b/src/Uno.Analyzers.Tests/UnoNotImplementedTests.cs
@@ -10,6 +10,23 @@ namespace Uno.Analyzers.Tests
[TestClass]
public class UnoNotImplementedTests : DiagnosticVerifier
{
+ private static string UnoNotImplementedAtribute = @"
+ namespace Uno
+ {
+ [System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
+ public sealed class NotImplementedAttribute : Attribute
+ {
+ public NotImplementedAttribute() { }
+
+ public NotImplementedAttribute(params string[] platforms)
+ {
+ Platforms = platforms;
+ }
+
+ public string[]? Platforms { get; }
+ }
+ }";
+
protected override DiagnosticAnalyzer DiagnosticAnalyzer => new UnoNotImplementedAnalyzer();
public UnoNotImplementedTests() : base(LanguageNames.CSharp)
@@ -25,7 +42,7 @@ public void Nothing()
}
[TestMethod]
- public void When_LambdaIsAsyncVoid()
+ public void When_EmptyNotImplemented()
{
var test = @"
using System;
@@ -37,12 +54,6 @@ public void When_LambdaIsAsyncVoid()
namespace Uno
{
- [System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
- public sealed class NotImplementedAttribute : Attribute
- {
-
- }
-
[NotImplemented]
public class TestClass { }
}
@@ -56,7 +67,49 @@ public TypeName()
var a = new Uno.TestClass();
}
}
- }";
+ }
+
+ " + UnoNotImplementedAtribute;
+
+ var expected = new DiagnosticResult
+ {
+ Id = UnoNotImplementedAnalyzer.Rule.Id,
+ Severity = DiagnosticSeverity.Warning,
+ Message = string.Format(UnoNotImplementedAnalyzer.MessageFormat, "Uno.TestClass"),
+ Locations = new[] {
+ new DiagnosticResultLocation("Test0.cs", 21, 36)
+ }
+ };
+
+ VerifyDiagnostic(test, expected);
+ }
+
+ [TestMethod]
+ public void When_SinglePlatform_Included()
+ {
+ var test = @"
+ #define __WASM__
+
+ using System;
+
+ namespace Uno
+ {
+ [NotImplemented(""__WASM__"")]
+ public class TestClass { }
+ }
+
+ namespace ConsoleApplication1
+ {
+ class TypeName
+ {
+ public TypeName()
+ {
+ var a = new Uno.TestClass();
+ }
+ }
+ }
+
+ " + UnoNotImplementedAtribute;
var expected = new DiagnosticResult
{
@@ -64,11 +117,246 @@ public TypeName()
Severity = DiagnosticSeverity.Warning,
Message = string.Format(UnoNotImplementedAnalyzer.MessageFormat, "Uno.TestClass"),
Locations = new[] {
- new DiagnosticResultLocation("Test0.cs", 27, 36)
+ new DiagnosticResultLocation("Test0.cs", 18, 36)
}
};
VerifyDiagnostic(test, expected);
}
+
+ [TestMethod]
+ public void When_SinglePlatform_Excluded()
+ {
+ var test = @"
+ #define __WASM__
+
+ using System;
+
+ namespace Uno
+ {
+ [NotImplemented(""__SKIA__"")]
+ public class TestClass { }
+ }
+
+ namespace ConsoleApplication1
+ {
+ class TypeName
+ {
+ public TypeName()
+ {
+ var a = new Uno.TestClass();
+ }
+ }
+ }
+
+ " + UnoNotImplementedAtribute;
+
+ VerifyDiagnostic(test);
+ }
+
+ [TestMethod]
+ public void When_TwoPlatforms_Excluded()
+ {
+ var test = @"
+ #define __WASM__
+
+ using System;
+
+ namespace Uno
+ {
+ [NotImplemented(""__SKIA__"", ""__IOS__"")]
+ public class TestClass { }
+ }
+
+ namespace ConsoleApplication1
+ {
+ class TypeName
+ {
+ public TypeName()
+ {
+ var a = new Uno.TestClass();
+ }
+ }
+ }
+
+ " + UnoNotImplementedAtribute;
+
+ VerifyDiagnostic(test);
+ }
+
+ [TestMethod]
+ public void When_Generic_Excluded()
+ {
+ var test = @"
+ #define UNO_REFERENCE_API
+
+ using System;
+
+ namespace Uno
+ {
+ [NotImplemented(""__IOS__"")]
+ public class TestClass { }
+ }
+
+ namespace ConsoleApplication1
+ {
+ class TypeName
+ {
+ public TypeName()
+ {
+ var a = new Uno.TestClass();
+ }
+ }
+ }
+
+ " + UnoNotImplementedAtribute;
+
+ VerifyDiagnostic(test);
+ }
+
+ [TestMethod]
+ public void When_Generic_Partial_Excluded()
+ {
+ var test = @"
+ #define UNO_REFERENCE_API
+
+ using System;
+
+ namespace Uno
+ {
+ [NotImplemented(""__SKIA__"", ""__IOS__"")]
+ public class TestClass { }
+ }
+
+ namespace ConsoleApplication1
+ {
+ class TypeName
+ {
+ public TypeName()
+ {
+ var a = new Uno.TestClass();
+ }
+ }
+ }
+
+ " + UnoNotImplementedAtribute;
+
+ VerifyDiagnostic(test);
+ }
+
+ [TestMethod]
+ public void When_Generic_Included()
+ {
+ var test = @"
+ #define UNO_REFERENCE_API
+
+ using System;
+
+ namespace Uno
+ {
+ [NotImplemented(""__SKIA__"", ""__IOS__"", ""__WASM__"")]
+ public class TestClass { }
+ }
+
+ namespace ConsoleApplication1
+ {
+ class TypeName
+ {
+ public TypeName()
+ {
+ var a = new Uno.TestClass();
+ }
+ }
+ }
+
+ " + UnoNotImplementedAtribute;
+
+ var expected = new DiagnosticResult
+ {
+ Id = UnoNotImplementedAnalyzer.Rule.Id,
+ Severity = DiagnosticSeverity.Warning,
+ Message = string.Format(UnoNotImplementedAnalyzer.MessageFormat, "Uno.TestClass"),
+ Locations = new[] {
+ new DiagnosticResultLocation("Test0.cs", 18, 36)
+ }
+ };
+
+ VerifyDiagnostic(test, expected);
+ }
+
+
+ [TestMethod]
+ public void When_Generic_Member_Included()
+ {
+ var test = @"
+ #define UNO_REFERENCE_API
+
+ using System;
+
+ namespace Uno
+ {
+ public class TestClass {
+ [NotImplemented(""__SKIA__"", ""__IOS__"", ""__WASM__"")]
+ public int Test { get; }
+ }
+ }
+
+ namespace ConsoleApplication1
+ {
+ class TypeName
+ {
+ public TypeName()
+ {
+ var a = new Uno.TestClass().Test;
+ }
+ }
+ }
+
+ " + UnoNotImplementedAtribute;
+
+ var expected = new DiagnosticResult
+ {
+ Id = UnoNotImplementedAnalyzer.Rule.Id,
+ Severity = DiagnosticSeverity.Warning,
+ Message = string.Format(UnoNotImplementedAnalyzer.MessageFormat, "Uno.TestClass.Test"),
+ Locations = new[] {
+ new DiagnosticResultLocation("Test0.cs", 20, 36)
+ }
+ };
+
+ VerifyDiagnostic(test, expected);
+ }
+
+ [TestMethod]
+ public void When_Generic_Member_Partial_Excluded()
+ {
+ var test = @"
+ #define UNO_REFERENCE_API
+
+ using System;
+
+ namespace Uno
+ {
+ public class TestClass {
+ [NotImplemented(""__IOS__"", ""__WASM__"")]
+ public int Test { get; }
+ }
+ }
+
+ namespace ConsoleApplication1
+ {
+ class TypeName
+ {
+ public TypeName()
+ {
+ var a = new Uno.TestClass().Test;
+ }
+ }
+ }
+
+ " + UnoNotImplementedAtribute;
+
+ VerifyDiagnostic(test);
+ }
}
}
diff --git a/src/Uno.Analyzers/UnoNotImplementedAnalyzer.cs b/src/Uno.Analyzers/UnoNotImplementedAnalyzer.cs
index 294e6b09bb72..ee5d242ef1bd 100644
--- a/src/Uno.Analyzers/UnoNotImplementedAnalyzer.cs
+++ b/src/Uno.Analyzers/UnoNotImplementedAnalyzer.cs
@@ -1,6 +1,9 @@
-using System;
+#nullable enable
+
+using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
@@ -64,8 +67,9 @@ private void OnObjectCreationExpression(SyntaxNodeAnalysisContext contextAnalysi
if (namedSymbol != null && IsUnoSymbol(symbol))
{
+ var directives = GetDirectives(contextAnalysis);
- if (HasNotImplementedAttribute(notImplementedSymbol, namedSymbol))
+ if (HasNotImplementedAttribute(notImplementedSymbol, namedSymbol, directives))
{
var diagnostic = Diagnostic.Create(
SupportedDiagnostics.First(),
@@ -78,9 +82,67 @@ private void OnObjectCreationExpression(SyntaxNodeAnalysisContext contextAnalysi
}
}
- private static bool HasNotImplementedAttribute(INamedTypeSymbol notImplementedSymbol, ISymbol namedSymbol)
+ private string[] GetDirectives(SyntaxNodeAnalysisContext contextAnalysis)
{
- return namedSymbol.GetAttributes().Any(a => a.AttributeClass == notImplementedSymbol);
+ var directives = contextAnalysis.Node.GetLocation()?.SourceTree.Options.PreprocessorSymbolNames.ToArray() ?? new string[0];
+
+ if (directives.Length == 0)
+ {
+ // This case is only used during tests where explicit #define statements are
+ // present at the top of the file. In common cases, PreprocessorSymbolNames is
+ // not empty.
+
+ var directive = contextAnalysis
+ .Node
+ .GetLocation()
+ ?.SourceTree
+ .GetRoot()
+ .GetFirstDirective() as DefineDirectiveTriviaSyntax;
+
+ if (directive != null)
+ {
+ directives = new[] { directive.Name.Text };
+ }
+ }
+
+ return directives;
+ }
+
+ private static bool HasNotImplementedAttribute(INamedTypeSymbol notImplementedSymbol, ISymbol namedSymbol, string[] directives)
+ {
+ if(namedSymbol.GetAttributes().FirstOrDefault(a => Equals(a.AttributeClass, notImplementedSymbol)) is AttributeData data)
+ {
+ if (
+ data.ConstructorArguments.FirstOrDefault() is TypedConstant constant
+ && constant.Kind != TypedConstantKind.Error)
+ {
+ Debug.Assert(constant.Kind == TypedConstantKind.Array);
+
+ var notImplementedPlatforms = constant.Values.Select(v => v.Value?.ToString()).ToArray();
+
+ if (directives.Contains("UNO_REFERENCE_API")
+ && !directives.Contains("__SKIA__")
+ && !directives.Contains("__WASM__"))
+ {
+ // Uno reference API is a special case where if a member or symbol
+ // is implementer for either __SKIA__ or __WASM__, the member is considered
+ // implemented. The code may be running in either environments, and we cannot
+ // statically determine if a member will be available.
+ return notImplementedPlatforms.Any(p => p == "__SKIA__")
+ && notImplementedPlatforms.Any(p => p == "__WASM__");
+ }
+ else
+ {
+ return notImplementedPlatforms.Any(d => directives.Contains(d));
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ return false;
}
private void OnMemberAccessExpression(SyntaxNodeAnalysisContext contextAnalysis, INamedTypeSymbol notImplementedSymbol)
@@ -96,7 +158,12 @@ private void OnMemberAccessExpression(SyntaxNodeAnalysisContext contextAnalysis,
if (member.Symbol != null && IsUnoSymbol(member))
{
- if (HasNotImplementedAttribute(notImplementedSymbol, member.Symbol) || HasNotImplementedAttribute(notImplementedSymbol, member.Symbol.ContainingSymbol))
+ var directives = GetDirectives(contextAnalysis);
+
+ var isMemberNotImplemented = HasNotImplementedAttribute(notImplementedSymbol, member.Symbol, directives);
+ var isMemberOwnerNotImplemented = HasNotImplementedAttribute(notImplementedSymbol, member.Symbol.ContainingSymbol, directives);
+
+ if (isMemberNotImplemented || isMemberOwnerNotImplemented)
{
var diagnostic = Diagnostic.Create(
SupportedDiagnostics.First(),
@@ -116,8 +183,6 @@ private static bool IsUnoSymbol(SymbolInfo member)
}
private static bool IsBindableMetadata(SyntaxNodeAnalysisContext contextAnalysis)
- {
- return Path.GetFileName(contextAnalysis.Node?.GetLocation()?.SourceTree?.FilePath) == "BindableMetadata.g.cs";
- }
+ => Path.GetFileName(contextAnalysis.Node?.GetLocation()?.SourceTree?.FilePath) == "BindableMetadata.g.cs";
}
}
diff --git a/src/Uno.Foundation/NotImplementedAttribute.cs b/src/Uno.Foundation/NotImplementedAttribute.cs
index 288adf9c9c06..ee06276c5386 100644
--- a/src/Uno.Foundation/NotImplementedAttribute.cs
+++ b/src/Uno.Foundation/NotImplementedAttribute.cs
@@ -5,16 +5,29 @@
namespace Uno
{
+ ///
+ /// Marks a member or symbol as not implemented by Uno.
+ ///
[System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
public sealed class NotImplementedAttribute : Attribute
{
+ ///
+ /// Creates an instance
+ ///
public NotImplementedAttribute() { }
+ ///
+ /// Creates an instance with C# constants for which the symbol is not implemented.
+ ///
+ /// The list of not-implemented platforms
public NotImplementedAttribute(params string[] platforms)
{
Platforms = platforms;
}
+ ///
+ /// The list of platforms that are not implemented. When empty, all platforms are not implemented.
+ ///
public string[]? Platforms { get; }
}
}