瀏覽代碼

Initial analyzer for recommending UI packages

Ian Griffiths 4 月之前
父節點
當前提交
b32eee27be
共有 17 個文件被更改,包括 899 次插入0 次删除
  1. 1 0
      Rx.NET/Source/System.Reactive.Analyzers.Test/MSTestSettings.cs
  2. 24 0
      Rx.NET/Source/System.Reactive.Analyzers.Test/System.Reactive.Analyzers.Test.csproj
  3. 12 0
      Rx.NET/Source/System.Reactive.Analyzers.Test/UwpNewPackageAnalyzerTests.cs
  4. 74 0
      Rx.NET/Source/System.Reactive.Analyzers.Test/Verifiers/AddUiFrameworkPackageAnalyzerVerifier.cs
  5. 35 0
      Rx.NET/Source/System.Reactive.Analyzers.Test/Verifiers/CSharpAnalyzerVerifier.cs
  6. 168 0
      Rx.NET/Source/System.Reactive.Analyzers.Test/WindowsFormsNewPackageAnalyzerTests.cs
  7. 22 0
      Rx.NET/Source/System.Reactive.Analyzers.Test/WindowsRuntimeNewPackageAnalyzerTests.cs
  8. 12 0
      Rx.NET/Source/System.Reactive.Analyzers.Test/WpfNewPackageAnalyzerTests.cs
  9. 2 0
      Rx.NET/Source/System.Reactive.Analyzers/AnalyzerReleases.Shipped.md
  10. 7 0
      Rx.NET/Source/System.Reactive.Analyzers/AnalyzerReleases.Unshipped.md
  11. 117 0
      Rx.NET/Source/System.Reactive.Analyzers/Analyzers/AddUiFrameworkPackageAnalyzer.cs
  12. 49 0
      Rx.NET/Source/System.Reactive.Analyzers/Analyzers/CodeAnalysisExtensions.cs
  13. 81 0
      Rx.NET/Source/System.Reactive.Analyzers/Analyzers/UiFrameworkPackages/UiFrameworkSpecificExtensionMethods.cs
  14. 90 0
      Rx.NET/Source/System.Reactive.Analyzers/Resources.Designer.cs
  15. 129 0
      Rx.NET/Source/System.Reactive.Analyzers/Resources.resx
  16. 20 0
      Rx.NET/Source/System.Reactive.Analyzers/System.Reactive.Analyzers.csproj
  17. 56 0
      Rx.NET/Source/System.Reactive.sln

+ 1 - 0
Rx.NET/Source/System.Reactive.Analyzers.Test/MSTestSettings.cs

@@ -0,0 +1 @@
+[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]

+ 24 - 0
Rx.NET/Source/System.Reactive.Analyzers.Test/System.Reactive.Analyzers.Test.csproj

@@ -0,0 +1,24 @@
+<Project Sdk="MSTest.Sdk/3.6.4">
+
+  <PropertyGroup>
+    <TargetFramework>net481</TargetFramework>
+    <LangVersion>latest</LangVersion>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <!--
+      Displays error on console in addition to the log file. Note that this feature comes with a performance impact.
+      For more information, visit https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-integration-dotnet-test#show-failure-per-test
+      -->
+    <TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest" Version="1.1.2" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\src\System.Reactive.MakeRefAssemblies\System.Reactive.MakeRefAssemblies.csproj" />
+    <ProjectReference Include="..\System.Reactive.Analyzers\System.Reactive.Analyzers.csproj" />
+  </ItemGroup>
+
+</Project>

+ 12 - 0
Rx.NET/Source/System.Reactive.Analyzers.Test/UwpNewPackageAnalyzerTests.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace System.Reactive.Analyzers.Test
+{
+    internal class UwpNewPackageAnalyzerTests
+    {
+    }
+}

+ 74 - 0
Rx.NET/Source/System.Reactive.Analyzers.Test/Verifiers/AddUiFrameworkPackageAnalyzerVerifier.cs

@@ -0,0 +1,74 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT License.
+// See the LICENSE file in the project root for more information. 
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace System.Reactive.Analyzers.Test.Verifiers
+{
+#pragma warning disable CA1812 // CA1812 wants this to be a static class, but since it has a base class, it can't be.
+    internal sealed class AddUiFrameworkPackageAnalyzerVerifier :
+        CSharpAnalyzerVerifier<AddUiFrameworkPackageAnalyzer, AddUiFrameworkPackageAnalyzerVerifier.Test, DefaultVerifier>
+#pragma warning restore CA1812
+    {
+#pragma warning disable CA1812 // CA1812 thinks this is never instantiated, even though it's used as a type argument for a parameter with a new() constrain!
+        public sealed class Test : CSharpAnalyzerTest<AddUiFrameworkPackageAnalyzer, DefaultVerifier>
+#pragma warning restore CA1812
+        {
+            public Test()
+            {
+                SolutionTransforms.Add((solution, projectId) =>
+                {
+                    // We need to make the System.Reactive.dll assembly available to the compiler.
+                    // In particular, it needs to be the reference assembly, because these tests
+                    // need to model what developers will see in real projects. (If these tests
+                    // supply the compiler with the runtime library, which is the build output of
+                    // the System.Reactive project, we get different errors in the tests than the
+                    // errors they aim to provoke.)
+                    // That's why this test project references System.Reactive.MakeRefAssemblies.
+                    // Note: this means we need to be careful never to try to load the
+                    // System.Reactive.dll assembly in this project. Being a reference assembly,
+                    // it's suitable for use by the compiler, but if the runtime tries to load it,
+                    // we'll get:
+                    //  System.BadImageFormatException: Could not load file or assembly
+                    // So that means we can't use Rx at all in this particular test project.
+                    // We can't even refer to any Rx-defined type such as Observable.
+                    var rxPath = Path.Combine(
+                        Path.GetDirectoryName(typeof(AddUiFrameworkPackageAnalyzerVerifier).Assembly.Location),
+                        "System.Reactive.dll");
+                    MetadataReference rxMetadataRef = MetadataReference.CreateFromFile(rxPath);
+                    var compilationOptions = solution.GetProject(projectId).CompilationOptions;
+                    compilationOptions = compilationOptions
+                        .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication);
+                    solution = solution
+                        .WithProjectCompilationOptions(projectId, compilationOptions)
+                        .AddMetadataReference(projectId, rxMetadataRef);
+
+                    return solution;
+                });
+
+                ReferenceAssemblies = ReferenceAssemblies.Net.Net80Windows;
+
+                // Adding a NuGet reference to Rx would more directly represent real developer
+                // scenarios, but we don't build new packages in day to day dev in the IDE, so this
+                // risks running tests against an out of date build. It also complicates things:
+                // we'd need to set up a local package feed and keep it up to date. Also, NuGet
+                // caching presumes that if the version didn't change, the package didn't change
+                // either, but our versions only change when we commit. So this also makes it quite
+                // likely that we end up running these tests against something other than what we
+                // meant to.
+                // However, if it turns out that at some point in the future we want to change over
+                // to package references, we can do it like this:
+                //
+                // var nugetConfigPath = Path.Combine(
+                //     Path.GetDirectoryName(typeof(AddUiFrameworkPackageAnalyzerVerifier).Assembly.Location),
+                //     "NuGet.Config");
+                // ReferenceAssemblies = ReferenceAssemblies
+                //     .AddPackages([new PackageIdentity("System.Reactive", "7.0.0")])
+                //     .WithNuGetConfigFilePath(nugetConfigPath);
+            }
+        }
+    }
+}

+ 35 - 0
Rx.NET/Source/System.Reactive.Analyzers.Test/Verifiers/CSharpAnalyzerVerifier.cs

@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT License.
+// See the LICENSE file in the project root for more information. 
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace System.Reactive.Analyzers.Test.Verifiers
+{
+    /// <summary>
+    /// A C# analyzer verifier with a parameterised test type.
+    /// </summary>
+    /// <typeparam name="TAnalyzer"></typeparam>
+    /// <typeparam name="TTest"></typeparam>
+    /// <typeparam name="TVerifier"></typeparam>
+    /// <remarks>
+    /// Oddly, this seems to be missing from the test libraries. The only C#-specific verifier
+    /// supplies its own Test class with no ability to customize it, and since it defaults to
+    /// a .NET Core 3.1 build, that's not very useful!
+    /// </remarks>
+    internal class CSharpAnalyzerVerifier<TAnalyzer, TTest, TVerifier> :
+        AnalyzerVerifier<AddUiFrameworkPackageAnalyzer, TTest, TVerifier>
+        where TAnalyzer : DiagnosticAnalyzer, new()
+        where TTest : CSharpAnalyzerTest<TAnalyzer, TVerifier>, new()
+        where TVerifier : IVerifier, new()
+    {
+    }
+}

+ 168 - 0
Rx.NET/Source/System.Reactive.Analyzers.Test/WindowsFormsNewPackageAnalyzerTests.cs

@@ -0,0 +1,168 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT License.
+// See the LICENSE file in the project root for more information. 
+
+using System.Reactive.Analyzers.Test.Verifiers;
+
+using Microsoft;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace System.Reactive.Analyzers.Test
+{
+    [TestClass]
+    public sealed class WindowsFormsNewPackageAnalyzerTests
+    {
+        [TestMethod]
+        public async Task DetectIObservableSubscribeOnControl()
+        {
+            var test = """
+                using System;
+                using System.Reactive.Linq;
+                using System.Reactive.Subjects;
+                
+                System.Windows.Forms.Control control = default!;
+
+                Observable.Interval(TimeSpan.FromSeconds(0.5))
+                    .SubscribeOn({|#0:control|})
+                    .Subscribe(Console.WriteLine);
+                """;
+
+            DiagnosticResult normalError = new DiagnosticResult("CS1503", Microsoft.CodeAnalysis.DiagnosticSeverity.Error)
+                    .WithLocation(0);
+            var customDiagnostic = AddUiFrameworkPackageAnalyzerVerifier.Diagnostic("RXNET0001").WithLocation(0).WithArguments("SubscribeOn");
+            await AddUiFrameworkPackageAnalyzerVerifier.VerifyAnalyzerAsync(
+                test,
+                normalError,
+                customDiagnostic);
+        }
+
+        /// <summary>
+        /// Check that we handle SubscribeOn for types that derive from Control (and not just Control itself).
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public async Task DetectIObservableSubscribeOnButton()
+        {
+            var test = """
+                using System;
+                using System.Reactive.Linq;
+                using System.Reactive.Subjects;
+                
+                System.Windows.Forms.Button button = default!;
+
+                Observable.Interval(TimeSpan.FromSeconds(0.5))
+                    .SubscribeOn({|#0:button|})
+                    .Subscribe(Console.WriteLine);
+                """;
+
+            DiagnosticResult normalError = new DiagnosticResult("CS1503", Microsoft.CodeAnalysis.DiagnosticSeverity.Error)
+                    .WithLocation(0);
+            var customDiagnostic = AddUiFrameworkPackageAnalyzerVerifier.Diagnostic("RXNET0001").WithLocation(0).WithArguments("SubscribeOn");
+            await AddUiFrameworkPackageAnalyzerVerifier.VerifyAnalyzerAsync(
+                test,
+                normalError,
+                customDiagnostic);
+        }
+
+
+        [TestMethod]
+        public async Task DetectIObservableObserveOnControl()
+        {
+            var test = """
+                using System;
+                using System.Reactive.Linq;
+                using System.Reactive.Subjects;
+                
+                System.Windows.Forms.Control control = default!;
+
+                Observable.Interval(TimeSpan.FromSeconds(0.5))
+                    .ObserveOn({|#0:control|})
+                    .Subscribe(Console.WriteLine);
+                """;
+
+            DiagnosticResult normalError = new DiagnosticResult("CS1503", Microsoft.CodeAnalysis.DiagnosticSeverity.Error)
+                    .WithLocation(0);
+            var customDiagnostic = AddUiFrameworkPackageAnalyzerVerifier.Diagnostic("RXNET0001").WithLocation(0).WithArguments("ObserveOn");
+            await AddUiFrameworkPackageAnalyzerVerifier.VerifyAnalyzerAsync(
+                test,
+                normalError,
+                customDiagnostic);
+        }
+
+        /// <summary>
+        /// Check that we handle ObserveOn for types that derive from Control (and not just Control itself).
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public async Task DetectIObservableObserveOnButton()
+        {
+            var test = """
+                using System;
+                using System.Reactive.Linq;
+                using System.Reactive.Subjects;
+                
+                System.Windows.Forms.Button button = default!;
+
+                Observable.Interval(TimeSpan.FromSeconds(0.5))
+                    .ObserveOn({|#0:button|})
+                    .Subscribe(Console.WriteLine);
+                """;
+
+            DiagnosticResult normalError = new DiagnosticResult("CS1503", Microsoft.CodeAnalysis.DiagnosticSeverity.Error)
+                    .WithLocation(0);
+            var customDiagnostic = AddUiFrameworkPackageAnalyzerVerifier.Diagnostic("RXNET0001").WithLocation(0).WithArguments("ObserveOn");
+            await AddUiFrameworkPackageAnalyzerVerifier.VerifyAnalyzerAsync(
+                test,
+                normalError,
+                customDiagnostic);
+        }
+
+        [TestMethod]
+        public async Task DetectConcreteObservableSubscribeOnControl()
+        {
+            var test = """
+                using System;
+                using System.Reactive.Linq;
+                using System.Reactive.Subjects;
+                
+                System.Windows.Forms.Control control = default!;
+
+                new Subject<int>()
+                    .SubscribeOn({|#0:control|})
+                    .Subscribe(Console.WriteLine);
+                """;
+
+            DiagnosticResult normalError = new DiagnosticResult("CS1503", Microsoft.CodeAnalysis.DiagnosticSeverity.Error)
+                    .WithLocation(0);
+            var customDiagnostic = AddUiFrameworkPackageAnalyzerVerifier.Diagnostic("RXNET0001").WithLocation(0).WithArguments("SubscribeOn");
+            await AddUiFrameworkPackageAnalyzerVerifier.VerifyAnalyzerAsync(
+                test,
+                normalError,
+                customDiagnostic);
+        }
+
+        [TestMethod]
+        public async Task DetectConcreteObservableObserveOnControl()
+        {
+            var test = """
+                using System;
+                using System.Reactive.Linq;
+                using System.Reactive.Subjects;
+                
+                System.Windows.Forms.Control control = default!;
+
+                new Subject<int>()
+                    .ObserveOn({|#0:control|})
+                    .Subscribe(Console.WriteLine);
+                """;
+
+            DiagnosticResult normalError = new DiagnosticResult("CS1503", Microsoft.CodeAnalysis.DiagnosticSeverity.Error)
+                    .WithLocation(0);
+            var customDiagnostic = AddUiFrameworkPackageAnalyzerVerifier.Diagnostic("RXNET0001").WithLocation(0).WithArguments("ObserveOn");
+            await AddUiFrameworkPackageAnalyzerVerifier.VerifyAnalyzerAsync(
+                test,
+                normalError,
+                customDiagnostic);
+        }
+    }
+}

+ 22 - 0
Rx.NET/Source/System.Reactive.Analyzers.Test/WindowsRuntimeNewPackageAnalyzerTests.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace System.Reactive.Analyzers.Test
+{
+    internal class WindowsRuntimeNewPackageAnalyzerTests
+    {
+        // This may need to look a bit different from the WPF and Windows Forms tests, because
+        // this covers more types.
+        // Then again, in the ADR, I recently made a change to say that actually the non-UI-framework-specific
+        // Windows Runtime functionality should actually remain in System.Reactive.
+        // There is an argumnet that anything that is inherently available as a result of having a Windows-specific TFM
+        // could remain in System.Reactive. (These _don't_ bring in a whole extra framework dependency, so we don't have
+        // the same absolute need to get rid of these as we do with WPF/Windows Forms,.) On the other hand, it would feel
+        // odd for the WPF Dispatcher support to live in in the WPF component while the CoreDispatcher is in the main
+        // System.Reactive.
+        // The IEventPattern and IAsyncInfo bits are arguably an interesting special case.
+    }
+}

+ 12 - 0
Rx.NET/Source/System.Reactive.Analyzers.Test/WpfNewPackageAnalyzerTests.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace System.Reactive.Analyzers.Test
+{
+    internal class WpfNewPackageAnalyzerTests
+    {
+    }
+}

+ 2 - 0
Rx.NET/Source/System.Reactive.Analyzers/AnalyzerReleases.Shipped.md

@@ -0,0 +1,2 @@
+; Shipped analyzer releases
+; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

+ 7 - 0
Rx.NET/Source/System.Reactive.Analyzers/AnalyzerReleases.Unshipped.md

@@ -0,0 +1,7 @@
+; Unshipped analyzer release
+; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
+### New Rules
+
+Rule ID | Category | Severity | Notes
+--------|----------|----------|-------
+RXNET0001 | NuGet | Warning | AddUiFrameworkPackageAnalyzer, [Documentation](https://github.com/dotnet/reactive)

+ 117 - 0
Rx.NET/Source/System.Reactive.Analyzers/Analyzers/AddUiFrameworkPackageAnalyzer.cs

@@ -0,0 +1,117 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT License.
+// See the LICENSE file in the project root for more information. 
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Reactive.Analyzers.UiFrameworkPackages;
+using System.Text;
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace System.Reactive.Analyzers
+{
+    /// <summary>
+    /// Detects when code using UI-framework-specific features of Rx.NET is failing to build due to
+    /// missing references to the relevant UI-framework-specific NuGet package.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// Rx 7.0 has moved all UI-frameworks-specific code out of the public API of the main
+    /// System.Reactive NuGet package. For binary compatibility it has been removed only from
+    /// the reference assemblies, and remains present in the runtime libraries, but the types
+    /// hidden in this way are present only for binary backwards compatibility. Code wishing to
+    /// continue using these types when being built against current versions of Rx needs to use
+    /// their new homes: the UI-framework-specific packages such as System.Reactive.For.Wpf.
+    /// </para>
+    /// <para>
+    /// This analyzer detects when a build failure looks likely to have been caused by code trying
+    /// to use a UI-framework-specific feature that is unavailable due to a missing package
+    /// reference. (This is most likely to have occurred because the project has been upgraded
+    /// from an earlier version of Rx in which the relevant code was accessible through the main
+    /// System.Reactive package.)
+    /// </para>
+    /// <para>
+    /// We supply this analyzer because we anticipate that one of the most likely points of
+    /// friction in the upgrade to Rx 7 is that code that was building without problems on Rx 6 or
+    /// earlier now produces surprising build errors. It will not normally be obvious what the
+    /// correct remedial action is, and so we provide this analyzer to point people in the right
+    /// direction.
+    /// </para>
+    /// <para>
+    /// The reason this needs to exist at all is that we need to remove the UI-framework-specific
+    /// types from the System.Reactive public API to fix a long standing problem. If you used Rx 5
+    /// or 6 in an application with a Windows-specific TFM, a reference (direct or transitive) to
+    /// System.Reactive would force a dependency on the Microsoft.Desktop.App framework. This
+    /// includes both WPF and Windows Forms, and for applications using self-contained deployment,
+    /// this framework dependency would add tens of megabytes to the size of the application.
+    /// </para>
+    /// </remarks>
+    [DiagnosticAnalyzer(LanguageNames.CSharp)]
+    public class AddUiFrameworkPackageAnalyzer : DiagnosticAnalyzer
+    {
+        // TODO: VB.NET?
+        public const string ReferenceToRxWindowsFormsRequiredDiagnosticId = "RXNET0001";
+
+        private static readonly LocalizableString ReferenceToRxWindowsFormsRequiredTitle = new LocalizableResourceString(
+            nameof(Resources.ReferenceToRxWindowsFormsRequiredAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
+        private static readonly LocalizableString ReferenceToRxWindowsFormsRequiredExtensionMethodMessageFormat = new LocalizableResourceString(
+            nameof(Resources.ReferenceToRxWindowsFormsRequiredAnalyzerExtensionMethodMessageFormat), Resources.ResourceManager, typeof(Resources));
+        private static readonly LocalizableString ReferenceToRxWindowsFormsRequiredDescription = new LocalizableResourceString(
+            nameof(Resources.ReferenceToRxWindowsFormsRequiredAnalyzerDescription), Resources.ResourceManager, typeof(Resources));
+        private const string PackagingCategory = "NuGet";
+        internal static readonly DiagnosticDescriptor ReferenceToRxWindowsFormsRequiredRule = new(
+            ReferenceToRxWindowsFormsRequiredDiagnosticId,
+            ReferenceToRxWindowsFormsRequiredTitle,
+            ReferenceToRxWindowsFormsRequiredExtensionMethodMessageFormat,
+            PackagingCategory,
+            DiagnosticSeverity.Warning,
+            isEnabledByDefault: true,
+            description: ReferenceToRxWindowsFormsRequiredDescription,
+            helpLinkUri: "https://github.com/dotnet/reactive");
+
+        /// <inheritdoc/>
+        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
+        [
+            //Rule,
+            ReferenceToRxWindowsFormsRequiredRule
+        ]);
+
+        /// <inheritdoc/>
+        public override void Initialize(AnalysisContext context)
+        {
+            if (context is null) {  throw new ArgumentNullException(nameof(context)); }
+
+            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+            context.EnableConcurrentExecution();
+
+            context.RegisterSemanticModelAction(AnalyzeSemanticModel);
+        }
+
+        private void AnalyzeSemanticModel(SemanticModelAnalysisContext context)
+        {
+            // Note: our goal is to do as little work as possible in cases where we won't produce a
+            // diagnostic. We expect not to need to report anything the majority of the time, so we
+            // want our impact to be minimal.
+            //
+            // We have registered for this callback, and not for syntax node ones, because we only
+            // want to run when there are errors.
+            var d = context.SemanticModel.GetDiagnostics();
+            foreach (var diag in d)
+            {
+                if (diag.Severity != DiagnosticSeverity.Error)
+                {
+                    continue;
+                }
+
+                var node = diag.Location.SourceTree?.GetRoot().FindNode(diag.Location.SourceSpan);
+
+                UiFrameworkSpecificExtensionMethods.CheckForExtensionMethods(context, node, diag);
+            }
+        }
+    }
+}

+ 49 - 0
Rx.NET/Source/System.Reactive.Analyzers/Analyzers/CodeAnalysisExtensions.cs

@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT License.
+// See the LICENSE file in the project root for more information. 
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Microsoft.CodeAnalysis;
+
+namespace System.Reactive.Analyzers
+{
+    internal static class CodeAnalysisExtensions
+    {
+        public static IEnumerable<ITypeSymbol> GetBaseTypesAndThis(this ITypeSymbol? type)
+        {
+            var current = type;
+            while (current != null)
+            {
+                yield return current;
+                current = current.BaseType;
+            }
+        }
+
+        public static bool InheritsFromOrEquals(
+                    this ITypeSymbol type, ITypeSymbol baseType, bool includeInterfaces)
+        {
+            if (!includeInterfaces)
+            {
+                return InheritsFromOrEquals(type, baseType);
+            }
+
+            return type.GetBaseTypesAndThis().Concat(type.AllInterfaces).Any(t => SymbolEqualityComparer.Default.Equals(t, baseType));
+        }
+
+        public static bool InheritsFromOrEquals(
+            this ITypeSymbol type, ITypeSymbol baseType)
+        {
+            return type.GetBaseTypesAndThis().Any(t => SymbolEqualityComparer.Default.Equals(t, baseType));
+        }
+
+        public static bool IsIObservable(this ITypeSymbol typeSymbol)
+        {
+            return typeSymbol is INamedTypeSymbol { Name: "IObservable", Arity: 1, ContainingNamespace.MetadataName: "System" }
+                || typeSymbol.AllInterfaces.Any(IsIObservable);
+        }
+    }
+}

+ 81 - 0
Rx.NET/Source/System.Reactive.Analyzers/Analyzers/UiFrameworkPackages/UiFrameworkSpecificExtensionMethods.cs

@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT License.
+// See the LICENSE file in the project root for more information. 
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace System.Reactive.Analyzers.UiFrameworkPackages
+{
+    internal static class UiFrameworkSpecificExtensionMethods
+    {
+        /// <summary>
+        /// Check whether a diagnostic looks likely to have been caused by a call to obs.ObserveOn(control),
+        /// or similar calls to UI-framework-specific extension methods.
+        /// </summary>
+        /// <param name="context">
+        /// Analyzer context from which the diagnostic was reported.
+        /// </param>
+        /// <param name="node">
+        /// The syntax node for which the diagnostic has been reported.
+        /// </param>
+        /// <param name="diag">
+        /// The compile diagnostics.
+        /// </param>
+        /// <returns>
+        /// True if the diagnostic looks likely to be due to a missing package refernce.
+        /// </returns>
+        public static bool CheckForExtensionMethods(
+            SemanticModelAnalysisContext context, SyntaxNode? node, Diagnostic diag)
+        {
+            // TODO: WPF and UAP.
+            // TODO: what if the diagnostic is in fact something else entirely?
+
+            if (node is ArgumentSyntax argument &&
+                argument.Parent?.Parent is InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax ma })
+            {
+                var matchedName = ma.Name.ToFullString() switch
+                {
+                    "ObserveOn" => "ObserveOn",
+                    "SubscribeOn" => "SubscribeOn",
+                    _ => null
+                };
+
+                if (matchedName is not null)
+                {
+                    var targetType = context.SemanticModel.GetTypeInfo(ma.Expression).Type;
+                    if (targetType is not null && targetType.IsIObservable())
+                    {
+                        // We're looking at an invocation of one of the methods for which we need to provide help.
+
+                        var argumentType = context.SemanticModel.GetTypeInfo(argument.Expression);
+
+                        var controlType = context.SemanticModel.Compilation.GetTypeByMetadataName("System.Windows.Forms.Control");
+                        //if (argumentType.Type is INamedTypeSymbol { Name: "Control", ContainingNamespace: { Name: "System.Windows.Forms" } })
+                        //if (SymbolEqualityComparer.Default.Equals(argumentType.Type, controlType))
+                        if (controlType is not null &&
+                            argumentType.Type is ITypeSymbol argumentTypeSymbol &&
+                            argumentTypeSymbol.InheritsFromOrEquals(controlType, false))
+                        {
+                            context.ReportDiagnostic(Diagnostic.Create(
+                                AddUiFrameworkPackageAnalyzer.ReferenceToRxWindowsFormsRequiredRule,
+                                diag.Location,
+                                matchedName));
+
+                            return true;
+                        }
+                    }
+                }
+            }
+
+            return false;
+        }
+    }
+}

+ 90 - 0
Rx.NET/Source/System.Reactive.Analyzers/Resources.Designer.cs

@@ -0,0 +1,90 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace System.Reactive {
+    using System;
+    
+    
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Reactive.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Add a reference to the System.Reactive.For.WindowsFormsa NuGet Package to continue using Rx.NET Windows Forms support..
+        /// </summary>
+        internal static string ReferenceToRxWindowsFormsRequiredAnalyzerDescription {
+            get {
+                return ResourceManager.GetString("ReferenceToRxWindowsFormsRequiredAnalyzerDescription", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to The &apos;{0}(Control)&apos; extension method has moved. Add a reference to the System.Reactive.For.WindowsForms NuGet package..
+        /// </summary>
+        internal static string ReferenceToRxWindowsFormsRequiredAnalyzerExtensionMethodMessageFormat {
+            get {
+                return ResourceManager.GetString("ReferenceToRxWindowsFormsRequiredAnalyzerExtensionMethodMessageFormat", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Rx.NET Windows Forms support is now in System.Reactive.For.WindowsForms.
+        /// </summary>
+        internal static string ReferenceToRxWindowsFormsRequiredAnalyzerTitle {
+            get {
+                return ResourceManager.GetString("ReferenceToRxWindowsFormsRequiredAnalyzerTitle", resourceCulture);
+            }
+        }
+    }
+}

+ 129 - 0
Rx.NET/Source/System.Reactive.Analyzers/Resources.resx

@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="ReferenceToRxWindowsFormsRequiredAnalyzerDescription" xml:space="preserve">
+    <value>Add a reference to the System.Reactive.For.WindowsFormsa NuGet Package to continue using Rx.NET Windows Forms support.</value>
+  </data>
+  <data name="ReferenceToRxWindowsFormsRequiredAnalyzerExtensionMethodMessageFormat" xml:space="preserve">
+    <value>The '{0}(Control)' extension method has moved. Add a reference to the System.Reactive.For.WindowsForms NuGet package.</value>
+  </data>
+  <data name="ReferenceToRxWindowsFormsRequiredAnalyzerTitle" xml:space="preserve">
+    <value>Rx.NET Windows Forms support is now in System.Reactive.For.WindowsForms</value>
+  </data>
+</root>

+ 20 - 0
Rx.NET/Source/System.Reactive.Analyzers/System.Reactive.Analyzers.csproj

@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <RootNamespace>System.Reactive</RootNamespace>
+
+    <Nullable>enable</Nullable>
+
+    <NeutralLanguage>en-US</NeutralLanguage>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
+  </ItemGroup>
+
+</Project>

+ 56 - 0
Rx.NET/Source/System.Reactive.sln

@@ -78,6 +78,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Reactive.For.Windows
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Reactive.MakeRefAssemblies", "src\System.Reactive.MakeRefAssemblies\System.Reactive.MakeRefAssemblies.csproj", "{20DF1979-FD74-DD5D-60B3-CE5874101E31}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analyzers", "Analyzers", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Reactive.Analyzers", "System.Reactive.Analyzers\System.Reactive.Analyzers.csproj", "{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Reactive.Analyzers.Test", "System.Reactive.Analyzers.Test\System.Reactive.Analyzers.Test.csproj", "{6428CC98-F495-47B8-A095-57EAA626FA0E}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug (with UWP)|Any CPU = Debug (with UWP)|Any CPU
@@ -624,6 +630,54 @@ Global
 		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Release|x64.Build.0 = Release|Any CPU
 		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Release|x86.ActiveCfg = Release|Any CPU
 		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Release|x86.Build.0 = Release|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug|ARM.ActiveCfg = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug|ARM.Build.0 = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug|x64.Build.0 = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Debug|x86.Build.0 = Debug|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Release|ARM.ActiveCfg = Release|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Release|ARM.Build.0 = Release|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Release|x64.ActiveCfg = Release|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Release|x64.Build.0 = Release|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Release|x86.ActiveCfg = Release|Any CPU
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1}.Release|x86.Build.0 = Release|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug|ARM.ActiveCfg = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug|ARM.Build.0 = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug|x64.Build.0 = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Debug|x86.Build.0 = Debug|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Release|ARM.ActiveCfg = Release|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Release|ARM.Build.0 = Release|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Release|x64.ActiveCfg = Release|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Release|x64.Build.0 = Release|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Release|x86.ActiveCfg = Release|Any CPU
+		{6428CC98-F495-47B8-A095-57EAA626FA0E}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -648,6 +702,8 @@ Global
 		{C3FC6098-AC7F-4825-B292-4049BC6FC76E} = {1873A545-87AA-4C22-BA1A-8A6602F65749}
 		{EB27A089-56EC-4621-BF88-E3B0DA8E6557} = {1873A545-87AA-4C22-BA1A-8A6602F65749}
 		{20DF1979-FD74-DD5D-60B3-CE5874101E31} = {1873A545-87AA-4C22-BA1A-8A6602F65749}
+		{2F93B42A-C8C8-43EF-BF45-8AF7E50489B1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+		{6428CC98-F495-47B8-A095-57EAA626FA0E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {2483F58F-A8D6-4FFE-A3C1-10F3A36DBE69}