Added XAML namespace upgrade step (#1349)

* Added XAML namespace upgrade step as well as the following fixes:
* Remove duplicate Compile item <Compile Include="MauiProgram.cs" />
* Remove "Xamarin.Forms" from qualified names (Xamarin.Forms.Shell => Shell)
* Replace PlatformConfiguration specific namespaces with Maui versions

* Updated MauiSample test assets and added ApplicableComponents to our Maui analyzers

* Addressed PR check failures by adding spacing and async suggestions

* Upgrade MicrosoftCodeAnalysisVersion to 4.0.0 and switch to making changes to XAML files through Roslyn.
Introduced MauiWorkloadUpgradeStep to ensure the MAUI workload is installed.

* Target net7.0 in MAUI migration and build against latest 7.0.100
Avoid repeated failed workload upgrade steps

* Pinning .NET SDK to RC2 release (7.0.100-rc.2.22477.23)

* Updated TFMs in tests to net7.0

* WarningLevel is now being persisted during the upgrade

* Temporarily disable MAUI E2E tests

* Fixed spacing analyzer issues

* 3rd party signing for MessagePack.dll and MessagePack.Annotations.dll
This commit is contained in:
Marco Goertz 2022-11-07 17:39:17 -08:00 коммит произвёл GitHub
Родитель 3c1aa55c0a
Коммит 72cfd8d28d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
33 изменённых файлов: 461 добавлений и 58 удалений

Просмотреть файл

@ -8,11 +8,7 @@
<DebugType>embedded</DebugType>
<DebugSymbols>true</DebugSymbols>
<!--
Use version 3.8.0 of CodeAnalysis assemblies to preserve compatibility
with Visual Studio 2019 16.8
-->
<MicrosoftCodeAnalysisVersion>3.8.0</MicrosoftCodeAnalysisVersion>
<MicrosoftCodeAnalysisVersion>4.0.0</MicrosoftCodeAnalysisVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(DotNetBuildFromSource)' == 'true'">

Просмотреть файл

@ -8,6 +8,8 @@
<FileSignInfo Include="Autofac.Extensions.DependencyInjection.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="AutoMapper.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="DiffPlex.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="MessagePack.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="MessagePack.Annotations.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="Newtonsoft.Json.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="Serilog.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="Serilog.Extensions.Hosting.dll" CertificateName="3PartySHA2" />

Просмотреть файл

@ -1,10 +1,10 @@
{
"sdk": {
"version": "7.0.100-rc.1.22431.12",
"version": "7.0.100-rc.2.22477.23",
"rollForward": "feature"
},
"tools": {
"dotnet": "7.0.100-rc.1.22431.12"
"dotnet": "7.0.100-rc.2.22477.23"
},
"msbuild-sdks": {
"Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.22513.2",

Просмотреть файл

@ -14,8 +14,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.9.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion)" />
<!-- In a real extension, Upgrade Assistant abstractions would be referenced as a NuGet package -->
<!-- To enable building and testing with the latest Upgrade Assistant changes, samples in this repo use -->

Просмотреть файл

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks>net7.0;net6.0</TargetFrameworks>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<IsPackable>true</IsPackable>
<PackAsTool>true</PackAsTool>
<ToolCommandName>upgrade-assistant</ToolCommandName>
<Description>A tool to assist developers in upgrading .NET Framework applications to .NET 6.</Description>
<Description>A tool to assist developers in upgrading .NET Framework applications to .NET.</Description>
<PackageId>upgrade-assistant</PackageId>
<PackageReleaseNotes>A changelog is available at https://github.com/dotnet/upgrade-assistant/blob/main/CHANGELOG.md.</PackageReleaseNotes>
<PackageIcon>icon.png</PackageIcon>
@ -23,7 +23,8 @@
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autofac">
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonVersion)" />
<PackageReference Include="Autofac">
<Version>6.1.0</Version>
</PackageReference>
<PackageReference Include="Autofac.Extensions.DependencyInjection">

Просмотреть файл

@ -67,6 +67,9 @@ namespace Microsoft.DotNet.UpgradeAssistant
await tcs.Task.ConfigureAwait(false);
// Async output may still be pending (see Process.HasExited)
process.WaitForExit();
if (process.ExitCode != args.SuccessCode)
{
const string Message = "[{Tool}] Error: Exited with non-success code: {ExitCode}";

Просмотреть файл

@ -34,6 +34,10 @@ namespace Microsoft.DotNet.UpgradeAssistant
public static readonly TargetFrameworkMoniker Net60_Android_31 = Net60_Android with { PlatformVersion = new Version(31, 0) };
public static readonly TargetFrameworkMoniker Net60_iOS = Net60 with { Platform = Platforms.IOS };
public static readonly TargetFrameworkMoniker Net60_iOS_13_5 = Net60_iOS with { PlatformVersion = new Version(13, 5) };
public static readonly TargetFrameworkMoniker Net70 = Net60 with { FrameworkVersion = new Version(7, 0) };
public static readonly TargetFrameworkMoniker Net70_Android = Net70 with { Platform = Platforms.Android };
public static readonly TargetFrameworkMoniker Net70_iOS = Net70 with { Platform = Platforms.IOS };
public static readonly TargetFrameworkMoniker Net70_Windows = Net70 with { Platform = Platforms.Windows };
#pragma warning restore CA1707 // Identifiers should not contain underscores

Просмотреть файл

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Automapper">

Просмотреть файл

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<PackExtension>true</PackExtension>
<IncludeBuildOutput>false</IncludeBuildOutput>
<Description>Provides data for loose assembly lookup based on NuGet.org</Description>

Просмотреть файл

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<PackExtension>true</PackExtension>
<Description>Provides some services for the .NET Upgrade Assistant to identify loose dependencies based off of available NuGet feeds.</Description>
<AssemblyName>Microsoft.DotNet.UpgradeAssistant.Extensions.LooseAssembly</AssemblyName>

Просмотреть файл

@ -55,7 +55,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
{
projectproperties.RemoveProjectProperty("TargetFramework");
file.SetPropertyValue("UseMaui", "true");
file.SetPropertyValue("TargetFrameworks", "net6.0-android;net6.0-ios");
file.SetPropertyValue("TargetFrameworks", "net7.0-android;net7.0-ios");
await file.SaveAsync(token).ConfigureAwait(false);
Logger.LogInformation("Added TFMs to .NET MAUI project");
return new UpgradeStepApplyResult(UpgradeStepStatus.Complete, $"Added TFMs to .NET MAUI Head project");
@ -130,11 +130,11 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
var propertyValue = Enum.Parse(typeof(ProjectComponents), componentFlagProperty);
if (ProjectComponents.XamarinAndroid.CompareTo(propertyValue) == 0)
{
return TargetFrameworkMoniker.Net60_Android_31;
return TargetFrameworkMoniker.Net70_Android;
}
else
{
return TargetFrameworkMoniker.Net60_iOS_13_5;
return TargetFrameworkMoniker.Net70_iOS;
}
}

Просмотреть файл

@ -20,12 +20,14 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
services.Services.AddTransient<IUpgradeReadyCheck, XamarinFormsVersionCheck>();
services.Services.AddTransient<ITargetFrameworkSelectorFilter, MauiTargetFrameworkSelectorFilter>();
services.Services.AddTransient<IComponentIdentifier, MauiComponentIdentifier>();
services.Services.AddUpgradeStep<MauiWorkloadUpgradeStep>();
services.Services.AddUpgradeStep<MauiPlatformTargetFrameworkUpgradeStep>();
services.Services.AddUpgradeStep<MauiAddProjectPropertiesStep>();
services.Services.AddTransient<DiagnosticAnalyzer, UsingXamarinFormsAnalyzerAnalyzer>();
services.Services.AddTransient<DiagnosticAnalyzer, UsingXamarinEssentialsAnalyzer>();
services.Services.AddTransient<CodeFixProvider, UsingXamarinFormsAnalyzerCodeFixProvider>();
services.Services.AddTransient<CodeFixProvider, UsingXamarinEssentialsAnalyzerCodeFixProvider>();
services.Services.AddUpgradeStep<XamlNamespaceUpgradeStep>();
}
}
}

Просмотреть файл

@ -25,26 +25,26 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
if (tfm.Components.HasFlag(ProjectComponents.XamarinAndroid))
{
_logger.LogInformation("Project {Name} is of type Xamarin.Android, migration to .NET MAUI requires to be least net6.0-android.", tfm.Project);
tfm.TryUpdate(TargetFrameworkMoniker.Net60_Android);
_logger.LogInformation("Project {Name} is of type Xamarin.Android, migration to .NET MAUI recommends net7.0-android.", tfm.Project);
tfm.TryUpdate(TargetFrameworkMoniker.Net70_Android);
}
if (tfm.Components.HasFlag(ProjectComponents.XamariniOS))
{
_logger.LogInformation("Project {Name} is of type Xamarin.iOS, migration to .NET MAUI requires to be least net6.0-ios.", tfm.Project);
tfm.TryUpdate(TargetFrameworkMoniker.Net60_iOS);
_logger.LogInformation("Project {Name} is of type Xamarin.iOS, migration to .NET MAUI recommends net7.0-ios.", tfm.Project);
tfm.TryUpdate(TargetFrameworkMoniker.Net70_iOS);
}
if (tfm.Components.HasFlag(ProjectComponents.MauiAndroid))
{
_logger.LogInformation("Project {Name} is of type .NET MAUI Target:Android, migration to .NET MAUI requires to be least net6.0-android.", tfm.Project);
tfm.TryUpdate(TargetFrameworkMoniker.Net60_Android);
_logger.LogInformation("Project {Name} is of type .NET MAUI Target:Android, migration to .NET MAUI recommends net7.0-android.", tfm.Project);
tfm.TryUpdate(TargetFrameworkMoniker.Net70_Android);
}
if (tfm.Components.HasFlag(ProjectComponents.MauiiOS))
{
_logger.LogInformation("Project {Name} is of type .NET MAUI Target:iOS, migration to .NET MAUI requires to be least net6.0-ios.", tfm.Project);
tfm.TryUpdate(TargetFrameworkMoniker.Net60_iOS);
_logger.LogInformation("Project {Name} is of type .NET MAUI Target:iOS, migration to .NET MAUI recommends net7.0-ios.", tfm.Project);
tfm.TryUpdate(TargetFrameworkMoniker.Net70_iOS);
}
if (tfm.Components.HasFlag(ProjectComponents.Maui))
@ -56,7 +56,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
else
{
_logger.LogInformation("Project {Name} is of type .NET MAUI Target: MAUI head, migration to .NET MAUI requires to be multiplatform", tfm.Project);
tfm.TryUpdate(TargetFrameworkMoniker.Net60_Android);
tfm.TryUpdate(TargetFrameworkMoniker.Net70_Android);
}
}
}

Просмотреть файл

@ -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.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using static System.FormattableString;
namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
{
public class MauiWorkloadUpgradeStep : UpgradeStep
{
private static readonly IReadOnlyDictionary<string, ProjectComponents> MauiWorkloadMap = new Dictionary<string, ProjectComponents>
{
{ "maui-android", ProjectComponents.MauiAndroid },
{ "maui-ios", ProjectComponents.MauiiOS },
{ "maui", ProjectComponents.MauiAndroid | ProjectComponents.MauiiOS },
};
private static bool? _infoSucceeded;
private static bool? _installSucceeded;
private readonly IProcessRunner _runner;
public override string Title => "Install .NET MAUI Workload";
public override string Description => "Check the .NET SDK for the MAUI workload and install it if necessary.";
public MauiWorkloadUpgradeStep(ILogger<MauiWorkloadUpgradeStep> logger, IProcessRunner runner)
: base(logger)
{
_runner = runner ?? throw new ArgumentNullException(nameof(runner));
}
public override IEnumerable<string> DependsOn { get; } = new[]
{
WellKnownStepIds.BackupStepId,
WellKnownStepIds.TryConvertProjectConverterStepId,
};
public override IEnumerable<string> DependencyOf { get; } = new[]
{
typeof(XamlNamespaceUpgradeStep).FullName,
};
// Install or update the MAUI workload
protected override async Task<UpgradeStepApplyResult> ApplyImplAsync(IUpgradeContext context, CancellationToken token)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (_installSucceeded.HasValue)
{
return new UpgradeStepApplyResult(UpgradeStepStatus.Skipped, ".NET MAUI workload install has already been run");
}
_installSucceeded = await RunDotnetCommandAsync(context, "workload install maui", (_, message) => LogLevel.Information, token).ConfigureAwait(false);
if (!_installSucceeded.Value)
{
Logger.LogError("Command 'dotnet workload install maui' failed!");
return new UpgradeStepApplyResult(UpgradeStepStatus.Failed, ".NET MAUI workload installation failed!");
}
return new UpgradeStepApplyResult(UpgradeStepStatus.Complete, $".NET MAUI workload installation succeeded.");
}
// Check if the right MAUI workload is installed
protected async override Task<UpgradeStepInitializeResult> InitializeImplAsync(IUpgradeContext context, CancellationToken token)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (_installSucceeded.HasValue)
{
return new UpgradeStepInitializeResult(UpgradeStepStatus.Skipped, ".NET MAUI workload install has already been run", _installSucceeded.Value ? BuildBreakRisk.None : BuildBreakRisk.High);
}
var project = context.CurrentProject.Required();
var components = await project.GetComponentsAsync(token).ConfigureAwait(false);
var workloads = ProjectComponents.None;
if (!_infoSucceeded.HasValue)
{
// We only need to display the dotnet info debug information once
_infoSucceeded = await RunDotnetCommandAsync(context, "--info", (_, message) => LogLevel.Debug, token).ConfigureAwait(false);
}
var result = await RunDotnetCommandAsync(context, "workload list", (_, message) =>
{
var workload = message.Split(' ').First();
if (MauiWorkloadMap.TryGetValue(workload, out var component))
{
workloads |= component;
}
return LogLevel.Information;
}, token).ConfigureAwait(false);
if (!result)
{
Logger.LogError("Failed to run 'dotnet workload install maui' command!");
return new UpgradeStepInitializeResult(UpgradeStepStatus.Failed, "Failed to list .NET MAUI workloads", BuildBreakRisk.High);
}
else if (workloads.HasFlag(components & (ProjectComponents.MauiAndroid | ProjectComponents.MauiiOS)))
{
Logger.LogInformation($".NET MAUI workloads installed: {workloads}");
return new UpgradeStepInitializeResult(UpgradeStepStatus.Complete, ".NET MAUI workload(s) already installed", BuildBreakRisk.None);
}
else
{
Logger.LogInformation(".NET MAUI workload needs to be installed");
return new UpgradeStepInitializeResult(UpgradeStepStatus.Incomplete, ".NET MAUI workload needs to be installed", BuildBreakRisk.High);
}
}
// Check if this is a MAUI conversion
protected override async Task<bool> IsApplicableImplAsync(IUpgradeContext context, CancellationToken token)
{
if (context?.CurrentProject is null)
{
return false;
}
var project = context.CurrentProject.Required();
var components = await project.GetComponentsAsync(token).ConfigureAwait(false);
if (components.HasFlag(ProjectComponents.MauiAndroid) || components.HasFlag(ProjectComponents.MauiiOS) || components.HasFlag(ProjectComponents.Maui))
{
return true;
}
if (!string.IsNullOrWhiteSpace(context.Properties.GetPropertyValue("componentFlag")))
{
return true;
}
return false;
}
/// <summary>
/// Run specified `dotnet workload` command.
/// </summary>
public Task<bool> RunDotnetCommandAsync(IUpgradeContext context, string command, Func<bool, string, LogLevel> getMessageLogLevel, CancellationToken token)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return _runner.RunProcessAsync(new ProcessInfo
{
Command = "dotnet",
Arguments = command,
EnvironmentVariables = context.GlobalProperties,
Name = "dotnet",
GetMessageLogLevel = getMessageLogLevel,
}, token);
}
}
}

Просмотреть файл

@ -12,7 +12,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisVersion)" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="5.0.0" />
</ItemGroup>

Просмотреть файл

@ -19,7 +19,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui {
// 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", "16.0.0.0")]
[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 {
@ -60,6 +60,15 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui {
}
}
/// <summary>
/// Looks up a localized string similar to Remove Xamarin.Forms namespace.
/// </summary>
internal static string NamespaceXamarinFormsTitle {
get {
return ResourceManager.GetString("NamespaceXamarinFormsTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Namespace &apos;{0}&apos; should not be referenced in .NET MAUI projects.
/// </summary>

Просмотреть файл

@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="NamespaceXamarinFormsTitle" xml:space="preserve">
<value>Remove Xamarin.Forms namespace</value>
</data>
<data name="UsingXamarinEssentialsAnalyzerMessageFormat" xml:space="preserve">
<value>Namespace '{0}' should not be referenced in .NET MAUI projects</value>
</data>

Просмотреть файл

@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Editing;
namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
{
[ApplicableComponents(ProjectComponents.Maui)]
[ExportCodeFixProvider(LanguageNames.CSharp, Name = "Using Microsoft.Maui.Essentials code fixer")]
public class UsingXamarinEssentialsAnalyzerCodeFixProvider : CodeFixProvider
{

Просмотреть файл

@ -34,10 +34,11 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction(AnalyzeUsingStatements, SyntaxKind.UsingDirective);
context.RegisterSyntaxNodeAction(AnalyzeUsingDirectives, SyntaxKind.UsingDirective);
context.RegisterSyntaxNodeAction(AnalyzeQualifiedNames, SyntaxKind.QualifiedName);
}
private void AnalyzeUsingStatements(SyntaxNodeAnalysisContext context)
private void AnalyzeUsingDirectives(SyntaxNodeAnalysisContext context)
{
var usingDirective = (UsingDirectiveSyntax)context.Node;
@ -54,5 +55,32 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
context.ReportDiagnostic(diagnostic);
}
}
private void AnalyzeQualifiedNames(SyntaxNodeAnalysisContext context)
{
var qualifiedNameNode = (QualifiedNameSyntax)context.Node;
if (qualifiedNameNode is null)
{
return;
}
var parentNode = qualifiedNameNode.Parent;
while (parentNode is not null)
{
if (parentNode.IsKind(SyntaxKind.UsingDirective) || parentNode.IsKind(SyntaxKind.UsingStatement))
{
return;
}
parentNode = parentNode.Parent;
}
var qualifiedName = qualifiedNameNode.ToString();
if (DisallowedNamespaces.Any(name => qualifiedName.Equals(name, StringComparison.Ordinal)))
{
var diagnostic = Diagnostic.Create(Rule, qualifiedNameNode.GetLocation(), qualifiedName);
context.ReportDiagnostic(diagnostic);
}
}
}
}

Просмотреть файл

@ -2,16 +2,19 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
{
[ApplicableComponents(ProjectComponents.Maui)]
[ExportCodeFixProvider(LanguageNames.CSharp)]
public class UsingXamarinFormsAnalyzerCodeFixProvider : CodeFixProvider
{
@ -39,16 +42,30 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
return;
}
// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeAction.Create(
Resources.UsingXamarinFormsTitle,
cancellationToken => ReplaceNodeAsync(context.Document, node, cancellationToken),
nameof(Resources.UsingXamarinFormsTitle)),
context.Diagnostics);
// Register the appropriate code action that will invoke the fix
switch (node.RawKind)
{
case (int)SyntaxKind.UsingDirective:
case (int)SyntaxKind.UsingStatement:
context.RegisterCodeFix(
CodeAction.Create(
Resources.UsingXamarinFormsTitle,
cancellationToken => ReplaceUsingStatementAsync(context.Document, node, cancellationToken),
nameof(Resources.UsingXamarinFormsTitle)),
context.Diagnostics);
break;
case (int)SyntaxKind.QualifiedName:
context.RegisterCodeFix(
CodeAction.Create(
Resources.NamespaceXamarinFormsTitle,
cancellationToken => RemoveNamespaceQualifierAsync(context.Document, node, cancellationToken),
nameof(Resources.NamespaceXamarinFormsTitle)),
context.Diagnostics);
break;
}
}
private static async Task<Document> ReplaceNodeAsync(Document document, SyntaxNode node, CancellationToken cancellationToken)
private static async Task<Document> ReplaceUsingStatementAsync(Document document, SyntaxNode node, CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var documentRoot = (CompilationUnitSyntax)editor.OriginalRoot;
@ -63,5 +80,17 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
return editor.GetChangedDocument();
}
private static async Task<Document> RemoveNamespaceQualifierAsync(Document document, SyntaxNode node, CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
if (node.Parent is not null)
{
editor.ReplaceNode(node.Parent, node.Parent.ChildNodes().Last());
}
return editor.GetChangedDocument();
}
}
}

Просмотреть файл

@ -0,0 +1,151 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.Logging;
using static System.Net.WebRequestMethods;
namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui
{
public class XamlNamespaceUpgradeStep : UpgradeStep
{
private static readonly IReadOnlyDictionary<string, string> XamarinToMauiReplacementMap = new Dictionary<string, string>
{
{ "http://xamarin.com/schemas/2014/forms", "http://schemas.microsoft.com/dotnet/2021/maui" },
{ "http://xamarin.com/schemas/2020/toolkit", "http://schemas.microsoft.com/dotnet/2022/maui/toolkit" },
{ "clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core", "clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;assembly=Microsoft.Maui.Controls" },
{ "clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core", "clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls" },
{ "clr-namespace:Xamarin.Forms.PlatformConfiguration.macOSSpecific;assembly=Xamarin.Forms.Core", "clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.macOSSpecific;assembly=Microsoft.Maui.Controls" },
{ "clr-namespace:Xamarin.Forms.PlatformConfiguration.TizenSpecific;assembly=Xamarin.Forms.Core", "clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.TizenSpecific;assembly=Microsoft.Maui.Controls" },
{ "clr-namespace:Xamarin.Forms.PlatformConfiguration.WindowsSpecific;assembly=Xamarin.Forms.Core", "clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;assembly=Microsoft.Maui.Controls" },
};
private readonly IPackageRestorer _restorer;
public override string Title => "Update XAML Namespaces";
public override string Description => "Updates XAML namespaces to .NET MAUI";
public XamlNamespaceUpgradeStep(IPackageRestorer restorer, ILogger<XamlNamespaceUpgradeStep> logger)
: base(logger)
{
_restorer = restorer;
}
public override IEnumerable<string> DependsOn { get; } = new[]
{
WellKnownStepIds.TemplateInserterStepId,
};
public override IEnumerable<string> DependencyOf { get; } = new[]
{
WellKnownStepIds.NextProjectStepId,
};
protected override async Task<UpgradeStepApplyResult> ApplyImplAsync(IUpgradeContext context, CancellationToken token)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var project = context.CurrentProject.Required();
var roslynProject = GetBestRoslynProject(project.GetRoslynProject());
var solution = roslynProject.Solution;
foreach (var file in GetXamlDocuments(roslynProject))
{
var sourceText = await file.GetTextAsync(token).ConfigureAwait(false);
var text = sourceText.ToString();
// Make replacements...
foreach (var key in XamarinToMauiReplacementMap.Keys)
{
text = text.Replace(key, XamarinToMauiReplacementMap[key]);
}
var newText = SourceText.From(text, encoding: sourceText.Encoding);
solution = solution.WithAdditionalDocumentText(file.Id, newText);
}
var status = context.UpdateSolution(solution) ? UpgradeStepStatus.Complete : UpgradeStepStatus.Failed;
// Remove MauiProgram.cs added by MauiHeadTemplates.json from project file manually again if necessary
// because WorkAroundRoslynIssue36781 doesn't think it's a duplicate item - but it is.
var projectFile = project.GetFile();
if (projectFile.RemoveItem(new ProjectItemDescriptor(ProjectItemType.Compile) { Include = "MauiProgram.cs" }))
{
await projectFile.SaveAsync(token).ConfigureAwait(false);
}
return new UpgradeStepApplyResult(status, $"Updated XAML namespaces to .NET MAUI");
}
protected override async Task<UpgradeStepInitializeResult> InitializeImplAsync(IUpgradeContext context, CancellationToken token)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return await Task.Run(() =>
{
// With updated TFMs and UseMaui, we need to restore packages
var project = context.CurrentProject.Required();
var roslynProject = GetBestRoslynProject(project.GetRoslynProject());
var hasXamlFiles = GetXamlDocuments(roslynProject).Any();
if (hasXamlFiles)
{
Logger.LogInformation(".NET MAUI project has XAML files that may need to be updated");
return new UpgradeStepInitializeResult(UpgradeStepStatus.Incomplete, ".NET MAUI project has XAML files that may need to be updated", BuildBreakRisk.High);
}
else
{
Logger.LogInformation(".NET MAUI project does not contain any XAML files");
return new UpgradeStepInitializeResult(UpgradeStepStatus.Complete, ".NET MAUI project does not contain any XAML files", BuildBreakRisk.None);
}
});
}
protected override async Task<bool> IsApplicableImplAsync(IUpgradeContext context, CancellationToken token)
{
if (context is null)
{
return false;
}
if (context.CurrentProject is null)
{
return false;
}
var project = context.CurrentProject.Required();
var components = await project.GetComponentsAsync(token).ConfigureAwait(false);
if (components.HasFlag(ProjectComponents.MauiAndroid) || components.HasFlag(ProjectComponents.MauiiOS) || components.HasFlag(ProjectComponents.Maui))
{
return true;
}
return false;
}
private static IEnumerable<TextDocument> GetXamlDocuments(Project project)
=> project.AdditionalDocuments.Where(d => d.FilePath?.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase) == true);
private static Project GetBestRoslynProject(Project project)
=> project.Solution.Projects
.Where(p => p.FilePath == project.FilePath)
.OrderByDescending(p => p.AdditionalDocumentIds.Count)
.First();
}
}

Просмотреть файл

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

Просмотреть файл

@ -37,6 +37,9 @@ namespace Microsoft.DotNet.UpgradeAssistant
public const string Net60_Windows_10_0_19041_0 = "net6.0-windows10.0.19041.0";
public const string Net60_Android = "net6.0-android";
public const string Net60_iOS = "net6.0-ios";
public const string Net70 = "net7.0";
public const string Net70_Android = "net7.0-android";
public const string Net70_iOS = "net7.0-ios";
public const string STS = Net50;
public const string Preview = Net60;
@ -72,6 +75,9 @@ namespace Microsoft.DotNet.UpgradeAssistant
{ Net60_Windows_10_0_19041_0, TargetFrameworkMoniker.Net60_Windows with { PlatformVersion = new Version(10, 0, 19041, 0) } },
{ Net60_Android, TargetFrameworkMoniker.Net60_Android },
{ Net60_iOS, TargetFrameworkMoniker.Net60_iOS },
{ Net70, TargetFrameworkMoniker.Net70 },
{ Net70_Android, TargetFrameworkMoniker.Net70_Android },
{ Net70_iOS, TargetFrameworkMoniker.Net70_iOS },
};
[return: NotNullIfNotNull("input")]

Просмотреть файл

@ -12,11 +12,11 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Maui.Tests
{
public class MauiProjectTargetFrameworkSelectorFilterTests
{
[InlineData(ProjectComponents.XamarinAndroid, Net60_Android, true)]
[InlineData(ProjectComponents.XamariniOS, Net60_iOS, true)]
[InlineData(ProjectComponents.MauiAndroid, Net60_Android, true)]
[InlineData(ProjectComponents.MauiiOS, Net60_iOS, true)]
[InlineData(ProjectComponents.Maui, Net60_Android, true)]
[InlineData(ProjectComponents.XamarinAndroid, Net70_Android, true)]
[InlineData(ProjectComponents.XamariniOS, Net70_iOS, true)]
[InlineData(ProjectComponents.MauiAndroid, Net70_Android, true)]
[InlineData(ProjectComponents.MauiiOS, Net70_iOS, true)]
[InlineData(ProjectComponents.Maui, Net70_Android, true)]
[Theory]
public void ProcessTests(ProjectComponents components, string expectedTfmString, bool tryUpdate)
{

Просмотреть файл

@ -8,6 +8,7 @@ using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AutoMapper.Configuration.Annotations;
using Microsoft.DotNet.UpgradeAssistant;
using Microsoft.DotNet.UpgradeAssistant.Cli;
using Xunit;
@ -44,8 +45,10 @@ namespace Integration.Tests
*/
[InlineData("WpfSample/vb", "WpfApp1.sln", "")]
[InlineData("WCFSample", "ConsoleApp.csproj", "")]
[InlineData("MauiSample/droid", "EwDavidForms.sln", "EwDavidForms.Android.csproj")]
[InlineData("MauiSample/ios", "EwDavidForms.sln", "EwDavidForms.iOS.csproj")]
// TODO: [mgoertz] Re-enable after .NET 7 GA
// [InlineData("MauiSample/droid", "EwDavidForms.sln", "EwDavidForms.Android.csproj")]
// [InlineData("MauiSample/ios", "EwDavidForms.sln", "EwDavidForms.iOS.csproj")]
[Theory]
public async Task UpgradeTest(string scenarioPath, string inputFileName, string entrypoint)
{

Просмотреть файл

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks>net7.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Remove="IntegrationScenarios\**" />

Просмотреть файл

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="EwDavidForms.App">
<Application.Resources>

Просмотреть файл

@ -12,7 +12,5 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Compile Include="MauiProgram.cs" />
</ItemGroup>
<ItemGroup />
</Project>

Просмотреть файл

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="EwDavidForms.MainPage">

Просмотреть файл

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="EwDavidForms.App">
<Application.Resources>

Просмотреть файл

@ -12,7 +12,5 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Compile Include="MauiProgram.cs" />
</ItemGroup>
<ItemGroup />
</Project>

Просмотреть файл

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="EwDavidForms.MainPage">

Просмотреть файл

@ -3,6 +3,7 @@
<TargetFramework>netstandard1.1</TargetFramework>
<OutputType>Library</OutputType>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Net.Http" Version="4.3.4" />