forked from unoplatform/uno
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add analyzer for missing packages when using ProgressRing or MPE
- Loading branch information
1 parent
2e81c9f
commit 722b2e8
Showing
4 changed files
with
328 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
192 changes: 192 additions & 0 deletions
192
src/Uno.Analyzers.Tests/UnoMissingAssemblyAnalyzerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
using System.Threading.Tasks; | ||
using System.Collections.Generic; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Microsoft.CodeAnalysis.Testing; | ||
using Uno.Analyzers.Tests.Verifiers; | ||
using System.Collections.Immutable; | ||
|
||
namespace Uno.Analyzers.Tests; | ||
|
||
using Verify = CSharpCodeFixVerifier<UnoMissingAssemblyAnalyzer, EmptyCodeFixProvider>; | ||
|
||
[TestClass] | ||
public class UnoMissingAssemblyAnalyzerTests | ||
{ | ||
#if HAS_UNO_WINUI | ||
private static readonly ImmutableArray<PackageIdentity> _unoPackage = [new PackageIdentity("Uno.WinUI", "5.2.161")]; | ||
private static readonly ImmutableArray<PackageIdentity> _unoPackageWithLottie = [new PackageIdentity("Uno.WinUI", "5.2.161"), new PackageIdentity("Uno.WinUI.Lottie", "5.2.161")]; | ||
private static readonly ImmutableArray<PackageIdentity> _unoPackageWithMPE = [new PackageIdentity("Uno.WinUI", "5.2.161"), new PackageIdentity("Uno.WinUI.MediaPlayer.WebAssembly", "5.2.161")]; | ||
private static readonly ImmutableArray<PackageIdentity> _unoPackageWithGtk = [new PackageIdentity("Uno.WinUI", "5.2.161"), new PackageIdentity("Uno.WinUI.Runtime.Skia.Gtk", "5.2.161")]; | ||
private static readonly ImmutableArray<PackageIdentity> _unoPackageWithGtkAndMPE = [new PackageIdentity("Uno.WinUI", "5.2.161"), new PackageIdentity("Uno.WinUI.Runtime.Skia.Gtk", "5.2.161"), new PackageIdentity("Uno.WinUI.MediaPlayer.Skia.Gtk", "5.2.161")]; | ||
#else | ||
private static readonly ImmutableArray<PackageIdentity> _unoPackage = [new PackageIdentity("Uno.UI", "5.2.161")]; | ||
private static readonly ImmutableArray<PackageIdentity> _unoPackageWithLottie = [new PackageIdentity("Uno.UI", "5.2.161"), new PackageIdentity("Uno.UI.Lottie", "5.2.161")]; | ||
private static readonly ImmutableArray<PackageIdentity> _unoPackageWithMPE = [new PackageIdentity("Uno.UI", "5.2.161"), new PackageIdentity("Uno.UI.MediaPlayer.WebAssembly", "5.2.161")]; | ||
private static readonly ImmutableArray<PackageIdentity> _unoPackageWithGtk = [new PackageIdentity("Uno.UI", "5.2.161"), new PackageIdentity("Uno.UI.Runtime.Skia.Gtk", "5.2.161")]; | ||
private static readonly ImmutableArray<PackageIdentity> _unoPackageWithGtkAndMPE = [new PackageIdentity("Uno.UI", "5.2.161"), new PackageIdentity("Uno.UI.Runtime.Skia.Gtk", "5.2.161"), new PackageIdentity("Uno.UI.MediaPlayer.Skia.Gtk", "5.2.161")]; | ||
#endif | ||
|
||
private static readonly ReferenceAssemblies _net80WithUno = ReferenceAssemblies.Net.Net80.AddPackages(_unoPackage); | ||
private static readonly ReferenceAssemblies _net80WithUnoAndLottie = ReferenceAssemblies.Net.Net80.AddPackages(_unoPackageWithLottie); | ||
private static readonly ReferenceAssemblies _net80WithUnoAndMPE = ReferenceAssemblies.Net.Net80.AddPackages(_unoPackageWithMPE); | ||
private static readonly ReferenceAssemblies _net80WithUnoAndGtk = ReferenceAssemblies.Net.Net80.AddPackages(_unoPackageWithGtk); | ||
private static readonly ReferenceAssemblies _net80WithUnoAndGtkAndMPE = ReferenceAssemblies.Net.Net80.AddPackages(_unoPackageWithGtkAndMPE); | ||
|
||
private const string WasmGlobalConfig = """ | ||
is_global = true | ||
build_property.UnoRuntimeIdentifier = WebAssembly | ||
build_property.IsUnoHead = true | ||
"""; | ||
|
||
private const string SkiaGlobalConfig = """ | ||
is_global = true | ||
build_property.UnoRuntimeIdentifier = Skia | ||
build_property.IsUnoHead = true | ||
"""; | ||
|
||
private const string UnoHeadGlobalConfig = """ | ||
is_global = true | ||
build_property.IsUnoHead = true | ||
"""; | ||
|
||
[TestMethod] | ||
public async Task UseProgressRingWithLottiePackage_NoDiagnostic() | ||
{ | ||
var code = """ | ||
using Microsoft/* UWP don't rename */.UI.Xaml.Controls; | ||
public class C | ||
{ | ||
public ProgressRing M() => new ProgressRing(); | ||
} | ||
"""; | ||
|
||
var test = new Verify.Test() | ||
{ | ||
TestCode = code, | ||
FixedCode = code, | ||
ReferenceAssemblies = _net80WithUnoAndLottie, | ||
}; | ||
|
||
test.TestState.AnalyzerConfigFiles.Add(("/.globalconfig", WasmGlobalConfig)); | ||
|
||
await test.RunAsync(); | ||
} | ||
|
||
[TestMethod] | ||
public async Task UseProgressRingWithoutLottiePackage_Diagnostic() | ||
{ | ||
var code = """ | ||
// <auto-generated /> | ||
using Microsoft/* UWP don't rename */.UI.Xaml.Controls; | ||
public class C | ||
{ | ||
public ProgressRing M() => [|new ProgressRing()|]; | ||
} | ||
"""; | ||
|
||
var test = new Verify.Test() | ||
{ | ||
TestCode = code, | ||
FixedCode = code, | ||
ReferenceAssemblies = _net80WithUno, | ||
}; | ||
|
||
test.TestState.AnalyzerConfigFiles.Add(("/.globalconfig", UnoHeadGlobalConfig)); | ||
|
||
await test.RunAsync(); | ||
} | ||
|
||
[TestMethod] | ||
public async Task UseMPEWithoutPackageWasm_Diagnostic() | ||
{ | ||
var code = """ | ||
// <auto-generated /> | ||
using Microsoft.UI.Xaml.Controls; | ||
public class C | ||
{ | ||
public MediaPlayerElement M() => [|new MediaPlayerElement()|]; | ||
} | ||
"""; | ||
|
||
var test = new Verify.Test() | ||
{ | ||
TestCode = code, | ||
FixedCode = code, | ||
ReferenceAssemblies = _net80WithUno, | ||
}; | ||
|
||
test.TestState.AnalyzerConfigFiles.Add(("/.globalconfig", WasmGlobalConfig)); | ||
|
||
await test.RunAsync(); | ||
} | ||
|
||
[TestMethod] | ||
public async Task UseMPEWithPackageWasm_NoDiagnostic() | ||
{ | ||
var code = """ | ||
using Microsoft.UI.Xaml.Controls; | ||
public class C | ||
{ | ||
public MediaPlayerElement M() => new MediaPlayerElement(); | ||
} | ||
"""; | ||
|
||
var test = new Verify.Test() | ||
{ | ||
TestCode = code, | ||
FixedCode = code, | ||
ReferenceAssemblies = _net80WithUnoAndMPE, | ||
}; | ||
|
||
test.TestState.AnalyzerConfigFiles.Add(("/.globalconfig", WasmGlobalConfig)); | ||
|
||
await test.RunAsync(); | ||
} | ||
|
||
[TestMethod] | ||
public async Task UseMPEWithoutPackageGtk_Diagnostic() | ||
{ | ||
var code = """ | ||
// <auto-generated /> | ||
using Microsoft.UI.Xaml.Controls; | ||
public class C | ||
{ | ||
public MediaPlayerElement M() => [|new MediaPlayerElement()|]; | ||
} | ||
"""; | ||
|
||
var test = new Verify.Test() | ||
{ | ||
TestCode = code, | ||
FixedCode = code, | ||
ReferenceAssemblies = _net80WithUnoAndGtk, | ||
}; | ||
|
||
test.TestState.AnalyzerConfigFiles.Add(("/.globalconfig", UnoHeadGlobalConfig)); | ||
|
||
await test.RunAsync(); | ||
} | ||
|
||
[TestMethod] | ||
public async Task UseMPEWithPackageGtk_NoDiagnostic() | ||
{ | ||
var code = """ | ||
using Microsoft.UI.Xaml.Controls; | ||
public class C | ||
{ | ||
public MediaPlayerElement M() => new MediaPlayerElement(); | ||
} | ||
"""; | ||
|
||
var test = new Verify.Test() | ||
{ | ||
TestCode = code, | ||
FixedCode = code, | ||
ReferenceAssemblies = _net80WithUnoAndGtkAndMPE, | ||
}; | ||
|
||
test.TestState.AnalyzerConfigFiles.Add(("/.globalconfig", UnoHeadGlobalConfig)); | ||
|
||
await test.RunAsync(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#nullable enable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Uno.Analyzers; | ||
|
||
internal static class SymbolExtensions | ||
{ | ||
public static bool DerivesFrom(this INamedTypeSymbol? symbol, INamedTypeSymbol? expectedBaseClass) | ||
{ | ||
while (symbol is not null) | ||
{ | ||
if (symbol.Equals(expectedBaseClass, SymbolEqualityComparer.Default)) | ||
{ | ||
return true; | ||
} | ||
|
||
symbol = symbol.BaseType; | ||
} | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
#nullable enable | ||
|
||
using System; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
|
||
namespace Uno.Analyzers; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class UnoMissingAssemblyAnalyzer : DiagnosticAnalyzer | ||
{ | ||
internal const string Title = "An assembly required for a component is missing"; | ||
internal const string MessageFormat = "Using '{0}' requires '{1}' NuGet package to be referenced"; | ||
internal const string Category = "Correctness"; | ||
|
||
internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( | ||
#pragma warning disable RS2008 // Enable analyzer release tracking | ||
"Uno0007", | ||
#pragma warning restore RS2008 // Enable analyzer release tracking | ||
Title, | ||
MessageFormat, | ||
Category, | ||
DiagnosticSeverity.Warning, | ||
isEnabledByDefault: true, | ||
helpLinkUri: "https://aka.platform.uno/UNO0007" | ||
); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); | ||
|
||
context.RegisterCompilationStartAction(context => | ||
{ | ||
var assemblies = context.Compilation.ReferencedAssemblyNames.Select(a => a.Name).ToImmutableHashSet(); | ||
var progressRing = context.Compilation.GetTypeByMetadataName("Microsoft" /* UWP don't rename */ + ".UI.Xaml.Controls.ProgressRing"); | ||
var mpe = context.Compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.Controls.MediaPlayerElement"); | ||
_ = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.UnoRuntimeIdentifier", out var unoRuntimeIdentifier); | ||
_ = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.IsUnoHead", out var isUnoHead); | ||
if (isUnoHead is null || !isUnoHead.Equals("true", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
return; | ||
} | ||
context.RegisterOperationAction(context => | ||
{ | ||
var objectCreation = (IObjectCreationOperation)context.Operation; | ||
if (objectCreation.Type is not INamedTypeSymbol type) | ||
{ | ||
return; | ||
} | ||
if (type.DerivesFrom(progressRing) && !assemblies.Contains("Uno.UI.Lottie")) | ||
{ | ||
const string lottieNuGetPackageName = | ||
#if HAS_UNO_WINUI | ||
"Uno.WinUI.Lottie"; | ||
#else | ||
"Uno.UI.Lottie"; | ||
#endif | ||
context.ReportDiagnostic(Diagnostic.Create(Rule, objectCreation.Syntax.GetLocation(), "ProgressRing", lottieNuGetPackageName)); | ||
} | ||
else if (type.DerivesFrom(mpe)) | ||
{ | ||
if (unoRuntimeIdentifier?.Equals("WebAssembly", StringComparison.OrdinalIgnoreCase) == true) | ||
{ | ||
if (!assemblies.Contains("Uno.UI.MediaPlayer.WebAssembly")) | ||
{ | ||
const string wasmMPENuGetPackageName = | ||
#if HAS_UNO_WINUI | ||
"Uno.WinUI.MediaPlayer.WebAssembly"; | ||
#else | ||
"Uno.UI.MediaPlayer.WebAssembly"; | ||
#endif | ||
context.ReportDiagnostic(Diagnostic.Create(Rule, objectCreation.Syntax.GetLocation(), "MediaPlayer", wasmMPENuGetPackageName)); | ||
} | ||
} | ||
else if (assemblies.Contains("Uno.UI.Runtime.Skia.Gtk")) | ||
{ | ||
if (!assemblies.Contains("Uno.UI.MediaPlayer.Skia.Gtk")) | ||
{ | ||
const string wasmMPENuGetPackageName = | ||
#if HAS_UNO_WINUI | ||
"Uno.WinUI.MediaPlayer.Skia.Gtk"; | ||
#else | ||
"Uno.UI.MediaPlayer.Skia.Gtk"; | ||
#endif | ||
context.ReportDiagnostic(Diagnostic.Create(Rule, objectCreation.Syntax.GetLocation(), "MediaPlayer", wasmMPENuGetPackageName)); | ||
} | ||
} | ||
} | ||
}, OperationKind.ObjectCreation); | ||
}); | ||
} | ||
} |