├── Project2015To2017.Tests ├── TestFiles │ ├── Deletions │ │ ├── a.txt │ │ ├── Test1.csproj │ │ ├── Test2.csproj │ │ ├── Test3.csproj │ │ ├── Test4.csproj │ │ ├── AssemblyInfo.txt │ │ ├── EmptyFolder │ │ │ └── a.txt │ │ ├── NonEmptyFolder │ │ │ └── a.txt │ │ └── NonEmptyFolder2 │ │ │ └── a.txt │ ├── Solutions │ │ ├── TextFile.txt │ │ ├── ClassLibrary │ │ │ ├── Class1.cs │ │ │ ├── Properties │ │ │ │ └── AssemblyInfo.cs │ │ │ └── ClassLibrary.csproj │ │ └── sampleSolution.testsln │ ├── FileInclusion │ │ ├── AnotherFile.cs │ │ ├── Program.cs │ │ ├── Folder │ │ │ ├── FileIncludedByWildcard.cs │ │ │ └── FileInFolder.cs │ │ ├── WildcardFolder │ │ │ └── WildcardSubFolder │ │ │ │ └── SubFolderWildcardFile.cs │ │ ├── app.config │ │ ├── IncludedFile.cs │ │ ├── Class1.cs │ │ ├── SourceFileSubTypeCode.cs │ │ ├── SourceFileAsResource.cs │ │ ├── packages.config │ │ ├── Properties │ │ │ ├── AssemblyInfo.cs │ │ │ └── Resources.Designer.cs │ │ ├── Resources.Designer.cs │ │ ├── SourceFileWithDesigner.Designer.cs │ │ └── fileExclusion.testcsproj │ ├── AssemblyInfoHandling │ │ ├── Empty │ │ │ └── Properties │ │ │ │ └── AssemblyInfo.cs │ │ ├── ClassDataLeft │ │ │ └── Properties │ │ │ │ └── AssemblyInfo.cs │ │ └── Redundant │ │ │ └── Properties │ │ │ └── AssemblyInfo.cs │ ├── OtherTestProjects │ │ ├── test.nuspec │ │ ├── OtherTestClass.cs │ │ ├── packages.config │ │ ├── AssemblyInfo.cs │ │ └── containsTestSDK.testcsproj │ ├── AltNugetConfig │ │ ├── nuget.config │ │ └── ProjectFolder │ │ │ └── packages.config │ └── nuSpecs │ │ └── noNamespace.nuspec ├── OtherPackagesConfig │ └── packages.config ├── Project2015To2017.Tests.csproj.DotSettings ├── NuSpecReaderTest.cs ├── SolutionReaderTests.cs ├── ProjectReferenceReadTest.cs ├── AssemblyFilterDefaultTransformationTest.cs ├── DummyLogger.cs ├── AssemblyInfoReadTest.cs ├── ImportsTargetsFilterPackageReferencesTransformationTest.cs ├── PrimaryProjectPropertiesUpdateTransformationTest.cs ├── W001IllegalProjectTypeDiagnosticTest.cs ├── AssemblyFilterPackageReferencesTransformationTest.cs ├── AssemblyFilterHintedPackageReferencesTransformationTest.cs ├── ProgramTest.cs ├── NuGetPackageTransformationTest.cs ├── UnsupportedProjectTypesTest.cs ├── TargetFrameworkReplaceTransformationTest.cs └── TestProjectPackageReferenceTransformationTest.cs ├── .editorconfig ├── Project2015To2017.Core ├── Analysis │ ├── IReporterOptions.cs │ ├── LoggerReporterOptions.cs │ ├── IDiagnosticLocation.cs │ ├── IDiagnosticResult.cs │ ├── IDiagnostic.cs │ ├── DiagnosticLocation.cs │ ├── IllegalDiagnosticStateException.cs │ ├── AnalysisOptions.cs │ ├── DiagnosticResult.cs │ ├── IReporter.cs │ ├── DiagnosticSet.cs │ ├── AnalysisExtensions.cs │ ├── Diagnostics │ │ ├── W002MissingProjectFileDiagnostic.cs │ │ ├── W011UnsupportedConditionalDiagnostic.cs │ │ ├── W001IllegalProjectTypeDiagnostic.cs │ │ └── W010ConfigurationsMismatchDiagnostic.cs │ ├── ReporterBase.cs │ ├── LoggerReporter.cs │ ├── Analyzer.cs │ └── DiagnosticBase.cs ├── Definition │ ├── IReference.cs │ ├── ApplicationType.cs │ ├── PackageReference.cs │ ├── AssemblyReference.cs │ ├── ProjectReference.cs │ ├── PackageConfiguration.cs │ ├── Solution.cs │ └── Project.cs ├── Transforms │ ├── TargetTransformationExecutionMoment.cs │ ├── ITransformationWithTargetMoment.cs │ ├── ITransformationWithDependencies.cs │ ├── IModernOnlyProjectTransformation.cs │ ├── ITransformation.cs │ ├── ILegacyOnlyProjectTransformation.cs │ ├── BasicReadTransformationSet.cs │ ├── EmptyGroupRemoveTransformation.cs │ ├── TargetFrameworkReplaceTransformation.cs │ ├── PropertyDeduplicationTransformation.cs │ ├── NuGetPackageTransformation.cs │ └── PrimaryProjectPropertiesUpdateTransformation.cs ├── Caching │ ├── IProjectCache.cs │ ├── NoProjectCache.cs │ └── DefaultProjectCache.cs ├── ITransformationSet.cs ├── NoopTransformationSet.cs ├── NoopLogger.cs ├── Reading │ ├── Conditionals │ │ ├── OperandExpressionNode.cs │ │ ├── IConditionEvaluationState.cs │ │ ├── EqualExpressionNode.cs │ │ ├── NotEqualExpressionNode.cs │ │ ├── CharacterUtilities.cs │ │ ├── OrExpressionNode.cs │ │ ├── AndExpressionNode.cs │ │ ├── FunctionCallExpressionNode.cs │ │ ├── NotExpressionNode.cs │ │ ├── LessThanExpressionNode.cs │ │ ├── GreaterThanExpressionNode.cs │ │ ├── LessThanOrEqualExpressionNode.cs │ │ ├── GreaterThanOrEqualExpressionNode.cs │ │ ├── NumericComparisonExpressionNode.cs │ │ ├── GenericExpressionNode.cs │ │ └── NumericExpressionNode.cs │ ├── ConditionEvaluator.ModernCache.cs │ ├── ConditionEvaluator.LegacyCache.cs │ ├── upstream.md │ ├── ConditionEvaluationStateImpl.cs │ ├── AssemblyInfoReader.cs │ └── NuSpecReader.cs ├── Project2015To2017.Core.csproj ├── ChainTransformationSet.cs ├── ConversionOptions.cs └── UnsupportedProjectTypes.cs ├── Project2015To2017 ├── ProcessSingleItemCallback.cs ├── PatternProcessor.cs ├── Project2015To2017.csproj └── ProjectConverterExtensions.cs ├── Project2015To2017.Migrate2017.Library ├── Project2015To2017.Migrate2017.Library.csproj ├── Diagnostics │ ├── W034ReferenceAliasesDiagnostic.cs │ ├── W030LegacyDebugTypesDiagnostic.cs │ ├── W031MSBuildSdkVersionSpecificationDiagnostic.cs │ ├── W032OldLanguageVersionDiagnostic.cs │ ├── W033ObsoletePortableClassLibrariesDiagnostic.cs │ └── W020MicrosoftCSharpDiagnostic.cs ├── Transforms │ ├── AssemblyFilterPackageReferencesTransformation.cs │ ├── AssemblyFilterDefaultTransformation.cs │ ├── AssemblyFilterHintedPackageReferencesTransformation.cs │ ├── TestProjectPackageReferenceTransformation.cs │ └── XamlPagesTransformation.cs ├── Vs15DiagnosticSet.cs └── Vs15TransformationSet.cs ├── Project2015To2017.sln.DotSettings ├── Project2015To2017.Console ├── Project2015To2017.Console.csproj ├── Options.cs └── Program.cs ├── LICENSE ├── Directory.Build.props ├── Project2015To2017.Migrate2017.Tool ├── Project2015To2017.Migrate2017.Tool.csproj └── CommandLogic.cs ├── .gitattributes ├── appveyor.yml ├── README.md └── Project2015To2017.sln /Project2015To2017.Tests/TestFiles/Deletions/a.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Deletions/Test1.csproj: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Deletions/Test2.csproj: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Deletions/Test3.csproj: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Deletions/Test4.csproj: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Solutions/TextFile.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Deletions/AssemblyInfo.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Deletions/EmptyFolder/a.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/AnotherFile.cs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/Program.cs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Deletions/NonEmptyFolder/a.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Deletions/NonEmptyFolder2/a.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/OtherPackagesConfig/packages.config: -------------------------------------------------------------------------------- 1 | definitely not xml -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/AssemblyInfoHandling/Empty/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/Folder/FileIncludedByWildcard.cs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/WildcardFolder/WildcardSubFolder/SubFolderWildcardFile.cs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = tab 3 | tab_width = 4 4 | charset = utf-8 5 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/AssemblyInfoHandling/ClassDataLeft/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | public class Test 2 | { 3 | 4 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/IncludedFile.cs: -------------------------------------------------------------------------------- 1 | namespace Project2015To2017Tests.TestFiles 2 | { 3 | class IncludedFile 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/IReporterOptions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Project2015To2017.Analysis 4 | { 5 | public interface IReporterOptions 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/Folder/FileInFolder.cs: -------------------------------------------------------------------------------- 1 | namespace Project2015To2017Tests.TestFiles.Folder 2 | { 3 | class FileInFolder 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/LoggerReporterOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Project2015To2017.Analysis 2 | { 3 | public sealed class LoggerReporterOptions : IReporterOptions 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/OtherTestProjects/test.nuspec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baruchiro/CsprojToVs2017/master/Project2015To2017.Tests/TestFiles/OtherTestProjects/test.nuspec -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/OtherTestProjects/OtherTestClass.cs: -------------------------------------------------------------------------------- 1 | namespace Project2015To2017Tests.TestFiles.OtherTestProjects 2 | { 3 | public class OtherTestClass 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Project2015To2017/ProcessSingleItemCallback.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Project2015To2017 4 | { 5 | public delegate void ProcessSingleItemCallback(FileInfo file, string extension); 6 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Definition/IReference.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace Project2015To2017.Definition 4 | { 5 | public interface IReference 6 | { 7 | XElement DefinitionElement { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /Project2015To2017/PatternProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace Project2015To2017 2 | { 3 | public delegate bool PatternProcessor(ProjectConverter converter, string pattern, ProcessSingleItemCallback callback, Facility self); 4 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Transforms/TargetTransformationExecutionMoment.cs: -------------------------------------------------------------------------------- 1 | namespace Project2015To2017.Transforms 2 | { 3 | public enum TargetTransformationExecutionMoment : byte 4 | { 5 | Normal = 0, 6 | Early, 7 | Late 8 | } 9 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/AssemblyInfoHandling/Redundant/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using somenamespace; 2 | using someOtherNameSpace; 3 | 4 | //some comment 5 | 6 | /* 7 | * A multi-line comment 8 | * none of these need keeping 9 | */ -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Project2015To2017Tests.TestFiles.FileInclusion 6 | { 7 | class Class1 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Transforms/ITransformationWithTargetMoment.cs: -------------------------------------------------------------------------------- 1 | namespace Project2015To2017.Transforms 2 | { 3 | public interface ITransformationWithTargetMoment : ITransformation 4 | { 5 | TargetTransformationExecutionMoment ExecutionMoment { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/SourceFileSubTypeCode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Project2015To2017Tests.TestFiles.FileInclusion 6 | { 7 | class SourceFileSubTypeCode 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/IDiagnosticLocation.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Project2015To2017.Analysis 4 | { 5 | public interface IDiagnosticLocation 6 | { 7 | FileSystemInfo Source { get; } 8 | uint SourceLine { get; } 9 | string SourcePath { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/SourceFileAsResource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Project2015To2017Tests.TestFiles.FileInclusion 6 | { 7 | class SourceFileAsResource 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Transforms/ITransformationWithDependencies.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Project2015To2017.Transforms 4 | { 5 | public interface ITransformationWithDependencies : ITransformation 6 | { 7 | IReadOnlyCollection DependOn { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/AltNugetConfig/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Solutions/ClassLibrary/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ClassLibrary 8 | { 9 | public class Class1 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Caching/IProjectCache.cs: -------------------------------------------------------------------------------- 1 | namespace Project2015To2017.Caching 2 | { 3 | public interface IProjectCache 4 | { 5 | void Add(string key, Definition.Project project); 6 | 7 | bool TryGetValue(string key, out Definition.Project project); 8 | 9 | void Purge(); 10 | } 11 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/IDiagnosticResult.cs: -------------------------------------------------------------------------------- 1 | using Project2015To2017.Definition; 2 | 3 | namespace Project2015To2017.Analysis 4 | { 5 | public interface IDiagnosticResult 6 | { 7 | string Code { get; } 8 | IDiagnosticLocation Location { get; } 9 | string Message { get; } 10 | Project Project { get; } 11 | } 12 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Definition/ApplicationType.cs: -------------------------------------------------------------------------------- 1 | namespace Project2015To2017.Definition 2 | { 3 | public enum ApplicationType 4 | { 5 | Unknown = 0, 6 | ClassLibrary = 1, 7 | ConsoleApplication = 2, 8 | WindowsApplication = 3, 9 | TestProject = 4, 10 | AppContainerExe = 5 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/IDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Project2015To2017.Definition; 3 | 4 | namespace Project2015To2017.Analysis 5 | { 6 | public interface IDiagnostic 7 | { 8 | bool SkipForLegacyProject { get; } 9 | bool SkipForModernProject { get; } 10 | 11 | IReadOnlyList Analyze(Project project); 12 | } 13 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/ITransformationSet.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.Extensions.Logging; 3 | using Project2015To2017.Transforms; 4 | 5 | namespace Project2015To2017 6 | { 7 | public interface ITransformationSet 8 | { 9 | IReadOnlyCollection Transformations( 10 | ILogger logger, 11 | ConversionOptions conversionOptions); 12 | } 13 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Transforms/IModernOnlyProjectTransformation.cs: -------------------------------------------------------------------------------- 1 | namespace Project2015To2017.Transforms 2 | { 3 | /// 4 | /// A transformation implementing this interface is supposed to be executed 5 | /// only on projects under CPS (in ordinary situations) 6 | /// 7 | public interface IModernOnlyProjectTransformation : ITransformation 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Transforms/ITransformation.cs: -------------------------------------------------------------------------------- 1 | using Project2015To2017.Definition; 2 | 3 | namespace Project2015To2017.Transforms 4 | { 5 | public interface ITransformation 6 | { 7 | /// 8 | /// Alter the provided project in some way 9 | /// 10 | /// 11 | void Transform(Project definition); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Definition/PackageReference.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace Project2015To2017.Definition 4 | { 5 | public sealed class PackageReference : IReference 6 | { 7 | public string Id { get; set; } 8 | public string Version { get; set; } 9 | public bool IsDevelopmentDependency { get; set; } 10 | 11 | public XElement DefinitionElement { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Transforms/ILegacyOnlyProjectTransformation.cs: -------------------------------------------------------------------------------- 1 | namespace Project2015To2017.Transforms 2 | { 3 | /// 4 | /// A transformation implementing this interface is supposed to be executed 5 | /// only on projects under legacy project system (in ordinary situations) 6 | /// 7 | public interface ILegacyOnlyProjectTransformation : ITransformation 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/Project2015To2017.Tests.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | CSharp72 -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Project2015To2017.Migrate2017.Library.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.3;netstandard2.0 5 | Project2015To2017.Migrate2017 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Project2015To2017.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/OtherTestProjects/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/OtherTestProjects/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | [assembly: AssemblyTitle("Title")] 7 | [assembly: AssemblyCompany("Company.")] 8 | [assembly: AssemblyProduct("Product")] 9 | [assembly: AssemblyCopyright("Copyright © 2012-2016 Generic Inc.")] 10 | [assembly: ComVisible(false)] 11 | [assembly: CLSCompliant(true)] 12 | [assembly: AssemblyInformationalVersion("7.61.0")] 13 | [assembly: AssemblyVersion("7.0")] -------------------------------------------------------------------------------- /Project2015To2017.Core/Definition/AssemblyReference.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace Project2015To2017.Definition 4 | { 5 | // Reference 6 | public sealed class AssemblyReference : IReference 7 | { 8 | // Attributes 9 | public string Include { get; set; } 10 | 11 | // Elements 12 | public string EmbedInteropTypes { get; set; } 13 | public string HintPath { get; set; } 14 | public string Private { get; set; } 15 | public string SpecificVersion { get; set; } 16 | 17 | public XElement DefinitionElement { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | [assembly: AssemblyTitle("Title")] 7 | [assembly: AssemblyCompany("Company.")] 8 | [assembly: AssemblyProduct("Product")] 9 | [assembly: AssemblyCopyright("Copyright © 2012-2016 Generic Inc.")] 10 | [assembly: ComVisible(false)] 11 | [assembly: CLSCompliant(true)] 12 | [assembly: AssemblyInformationalVersion("7.61.0")] 13 | [assembly: AssemblyVersion("7.0")] -------------------------------------------------------------------------------- /Project2015To2017.Tests/NuSpecReaderTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Project2015To2017.Reading; 4 | 5 | namespace Project2015To2017.Tests 6 | { 7 | [TestClass] 8 | public class NuSpecReaderTest 9 | { 10 | [TestMethod] 11 | public void LoadsNuSpecWithNoNamespace() 12 | { 13 | var reader = new NuSpecReader(NoopLogger.Instance); 14 | var nuspec = reader.Read(new FileInfo(@"TestFiles\nuSpecs\dummy.csproj")); 15 | 16 | Assert.IsNotNull(nuspec); 17 | } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Caching/NoProjectCache.cs: -------------------------------------------------------------------------------- 1 | using Project2015To2017.Definition; 2 | 3 | namespace Project2015To2017.Caching 4 | { 5 | public sealed class NoProjectCache : IProjectCache 6 | { 7 | public static IProjectCache Instance => new NoProjectCache(); 8 | 9 | private NoProjectCache() 10 | { 11 | 12 | } 13 | 14 | public void Add(string key, Project project) 15 | { 16 | } 17 | 18 | public bool TryGetValue(string key, out Project project) 19 | { 20 | project = null; 21 | return false; 22 | } 23 | 24 | public void Purge() 25 | { 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/DiagnosticLocation.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Project2015To2017.Analysis 4 | { 5 | public sealed class DiagnosticLocation : IDiagnosticLocation 6 | { 7 | public uint SourceLine { get; set; } 8 | public FileSystemInfo Source { get; set; } 9 | public string SourcePath { get; set; } 10 | 11 | public DiagnosticLocation() 12 | { 13 | } 14 | 15 | public DiagnosticLocation(IDiagnosticLocation location) 16 | { 17 | this.SourceLine = location.SourceLine; 18 | this.Source = location.Source; 19 | this.SourcePath = location.SourcePath; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/IllegalDiagnosticStateException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Project2015To2017.Analysis 4 | { 5 | public sealed class IllegalDiagnosticStateException : InvalidOperationException 6 | { 7 | /// 8 | public IllegalDiagnosticStateException() 9 | { 10 | } 11 | 12 | /// 13 | public IllegalDiagnosticStateException(string message) : base(message) 14 | { 15 | } 16 | 17 | /// 18 | public IllegalDiagnosticStateException(string message, Exception innerException) : base(message, innerException) 19 | { 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/AnalysisOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Immutable; 3 | 4 | namespace Project2015To2017.Analysis 5 | { 6 | public sealed class AnalysisOptions 7 | { 8 | /// 9 | /// Including ID of diagnostics in this list will make analyzer skip their execution and therefore output 10 | /// 11 | public ImmutableHashSet Diagnostics { get; } 12 | 13 | public AnalysisOptions(IEnumerable diagnostics = null) 14 | { 15 | this.Diagnostics = (diagnostics ?? DiagnosticSet.All).ToImmutableHashSet(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/AltNugetConfig/ProjectFolder/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Project2015To2017.Core/NoopTransformationSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Extensions.Logging; 4 | using Project2015To2017.Transforms; 5 | 6 | namespace Project2015To2017 7 | { 8 | public class NoopTransformationSet : ITransformationSet 9 | { 10 | public static readonly NoopTransformationSet Instance = new NoopTransformationSet(); 11 | 12 | private NoopTransformationSet() 13 | { 14 | } 15 | 16 | public IReadOnlyCollection Transformations( 17 | ILogger logger, 18 | ConversionOptions conversionOptions) 19 | { 20 | return Array.Empty(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/OtherTestProjects/containsTestSDK.testcsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exe 6 | UnitTest 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/DiagnosticResult.cs: -------------------------------------------------------------------------------- 1 | using Project2015To2017.Definition; 2 | 3 | namespace Project2015To2017.Analysis 4 | { 5 | public sealed class DiagnosticResult : IDiagnosticResult 6 | { 7 | public string Code { get; internal set; } 8 | public string Message { get; internal set; } 9 | public Project Project { get; internal set; } 10 | public IDiagnosticLocation Location { get; internal set; } 11 | 12 | public DiagnosticResult() 13 | { 14 | } 15 | 16 | public DiagnosticResult(IDiagnosticResult result) 17 | { 18 | this.Code = result.Code; 19 | this.Message = result.Message; 20 | this.Project = result.Project; 21 | this.Location = result.Location; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/SolutionReaderTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Project2015To2017.Reading; 5 | 6 | namespace Project2015To2017.Tests 7 | { 8 | [TestClass] 9 | public class SolutionReaderTests 10 | { 11 | [TestMethod] 12 | public void ReadsSolutionFileSuccessfully() 13 | { 14 | var testFile = @"TestFiles/Solutions/sampleSolution.testsln"; 15 | 16 | var logger = new DummyLogger {MinimumLogLevel = LogLevel.Warning}; 17 | 18 | SolutionReader.Instance.Read(testFile, logger); 19 | 20 | //Should be no warnings or errors 21 | Assert.IsFalse(logger.LogEntries.Any()); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Caching/DefaultProjectCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using Project2015To2017.Definition; 3 | 4 | namespace Project2015To2017.Caching 5 | { 6 | public sealed class DefaultProjectCache : IProjectCache 7 | { 8 | private readonly ConcurrentDictionary dictionary = new ConcurrentDictionary(); 9 | 10 | public void Add(string key, Project project) 11 | { 12 | this.dictionary.AddOrUpdate(key, project, (s, p) => p); 13 | } 14 | 15 | public void Purge() 16 | { 17 | this.dictionary.Clear(); 18 | } 19 | 20 | public bool TryGetValue(string key, out Project project) 21 | { 22 | return this.dictionary.TryGetValue(key, out project); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/IReporter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Project2015To2017.Definition; 3 | 4 | namespace Project2015To2017.Analysis 5 | { 6 | public interface IReporter where TOptions : IReporterOptions 7 | { 8 | /// 9 | /// Default options for any project 10 | /// 11 | TOptions DefaultOptions { get; } 12 | 13 | /// 14 | /// Do the actual issue reporting 15 | /// 16 | /// Diagnostics to report 17 | /// Options for the reporter 18 | void Report(IReadOnlyList results, TOptions reporterOptions); 19 | 20 | TOptions CreateOptionsForProject(Project project); 21 | } 22 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/NoopLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Project2015To2017 5 | { 6 | public sealed class NoopLogger : ILogger, IDisposable 7 | { 8 | public static ILogger Instance = new NoopLogger(); 9 | 10 | private NoopLogger() 11 | { 12 | 13 | } 14 | 15 | public IDisposable BeginScope(TState state) 16 | { 17 | return this; 18 | } 19 | 20 | public void Dispose() 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | 25 | public bool IsEnabled(LogLevel logLevel) 26 | { 27 | return false; 28 | } 29 | 30 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 31 | { 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Transforms/BasicReadTransformationSet.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Project2015To2017.Transforms 5 | { 6 | public class BasicReadTransformationSet : ITransformationSet 7 | { 8 | public static readonly BasicReadTransformationSet Instance = new BasicReadTransformationSet(); 9 | 10 | private BasicReadTransformationSet() 11 | { 12 | } 13 | 14 | public IReadOnlyCollection Transformations( 15 | ILogger logger, 16 | ConversionOptions conversionOptions) 17 | { 18 | return new ITransformation[] 19 | { 20 | new NuGetPackageTransformation(), 21 | new AssemblyAttributeTransformation(logger, conversionOptions.KeepAssemblyInfo), 22 | }; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/ProjectReferenceReadTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Project2015To2017.Reading; 5 | 6 | namespace Project2015To2017.Tests 7 | { 8 | [TestClass] 9 | public class ProjectReferenceReadTest 10 | { 11 | [TestMethod] 12 | public void TransformsProjectReferences() 13 | { 14 | var project = new ProjectReader().Read(Path.Combine("TestFiles", "OtherTestProjects", "net46console.testcsproj")); 15 | 16 | Assert.AreEqual(2, project.ProjectReferences.Count); 17 | Assert.IsTrue(project.ProjectReferences.Any(x => x.Include == @"..\SomeOtherProject\SomeOtherProject.csproj" && x.Aliases == "global,one")); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/nuSpecs/noNamespace.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | title 7 | some author 8 | true 9 | a nice description. 10 | That description, shortened. 11 | some tags API 12 | $copyright$ 13 | someurl 14 | 15 | Some long 16 | text 17 | with newlines 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Definition/ProjectReference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Xml.Linq; 4 | 5 | namespace Project2015To2017.Definition 6 | { 7 | public sealed class ProjectReference : IReference 8 | { 9 | public string ProjectName { get; set; } 10 | public Guid ProjectGuid { get; set; } 11 | public string ProjectTypeGuid { get; set; } 12 | 13 | public string Include { get; set; } 14 | public string Aliases { get; set; } 15 | public bool EmbedInteropTypes { get; set; } 16 | 17 | public FileInfo ProjectFile { get; set; } 18 | public XElement DefinitionElement { get; set; } 19 | 20 | /// 21 | /// Extension of the project file, if any 22 | /// 23 | public string Extension => ProjectFile?.Extension ?? Path.GetExtension(Include); 24 | } 25 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/OperandExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Project2015To2017.Reading.Conditionals 5 | { 6 | /// 7 | /// Base class for all nodes that are operands (are leaves in the parse tree) 8 | /// 9 | internal abstract class OperandExpressionNode : GenericExpressionNode 10 | { 11 | #region REMOVE_COMPAT_WARNING 12 | 13 | internal override bool DetectAnd() 14 | { 15 | return false; 16 | } 17 | 18 | internal override bool DetectOr() 19 | { 20 | return false; 21 | } 22 | #endregion 23 | 24 | } 25 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/ConditionEvaluator.ModernCache.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Caching; 2 | 3 | namespace Project2015To2017.Reading 4 | { 5 | /// 6 | /// 7 | /// 8 | internal static partial class ConditionEvaluator 9 | { 10 | private static bool TryGetCachedOrCreateState(string condition, out ConditionEvaluationStateImpl state) 11 | { 12 | state = MemoryCache.Default[condition] as ConditionEvaluationStateImpl; 13 | 14 | if (state != null) 15 | { 16 | return true; 17 | } 18 | 19 | state = new ConditionEvaluationStateImpl(condition); 20 | 21 | MemoryCache.Default.Add(condition, state, ObjectCache.InfiniteAbsoluteExpiration); 22 | 23 | return false; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/ConditionEvaluator.LegacyCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Project2015To2017.Reading 4 | { 5 | /// 6 | /// 7 | /// 8 | internal static partial class ConditionEvaluator 9 | { 10 | private static readonly Dictionary Cache = new Dictionary(); 11 | 12 | private static bool TryGetCachedOrCreateState(string condition, out ConditionEvaluationStateImpl state) 13 | { 14 | if (Cache.TryGetValue(condition, out state)) 15 | { 16 | return true; 17 | } 18 | 19 | state = new ConditionEvaluationStateImpl(condition); 20 | 21 | Cache.Add(condition, state); 22 | 23 | return false; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/AssemblyFilterDefaultTransformationTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Project2015To2017.Definition; 5 | using Project2015To2017.Migrate2017.Transforms; 6 | 7 | namespace Project2015To2017.Tests 8 | { 9 | [TestClass] 10 | public class AssemblyFilterDefaultTransformationTest 11 | { 12 | [TestMethod] 13 | public void PreventEmptyAssemblyReferences() 14 | { 15 | var project = new Project 16 | { 17 | AssemblyReferences = new List 18 | { 19 | new AssemblyReference 20 | { 21 | Include = "System" 22 | } 23 | }, 24 | FilePath = new FileInfo("test.cs") 25 | }; 26 | 27 | new AssemblyFilterDefaultTransformation().Transform(project); 28 | 29 | Assert.AreEqual(0, project.AssemblyReferences.Count); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Project2015To2017.Console/Project2015To2017.Console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net461;netcoreapp2.1 5 | netcoreapp2.1 6 | True 7 | 8 | Project2015To2017.Cli 9 | Project2015To2017.Cli 10 | Exe 11 | csproj-to-2017 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Definition/PackageConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Xml.Linq; 4 | 5 | namespace Project2015To2017.Definition 6 | { 7 | public sealed class PackageConfiguration 8 | { 9 | public string Id { get; set; } 10 | public string Version { get; set; } 11 | public string Authors { get; set; } 12 | public string Description { get; set; } 13 | public string Copyright { get; set; } 14 | public string LicenseUrl { get; set; } 15 | public string ProjectUrl { get; set; } 16 | public string IconUrl { get; set; } 17 | public string Tags { get; set; } 18 | public string ReleaseNotes { get; set; } 19 | public bool RequiresLicenseAcceptance { get; set; } 20 | public IList Dependencies { get; set; } 21 | public FileInfo NuspecFile { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/DummyLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Project2015To2017.Tests 6 | { 7 | internal class DummyLogger : ILogger 8 | { 9 | private readonly List logs = new List(); 10 | public IReadOnlyList LogEntries => this.logs; 11 | 12 | public LogLevel MinimumLogLevel { get; set; } = LogLevel.Error; 13 | 14 | public IDisposable BeginScope(TState state) 15 | { 16 | throw new NotImplementedException(); 17 | } 18 | 19 | public bool IsEnabled(LogLevel logLevel) 20 | { 21 | return logLevel >= MinimumLogLevel; 22 | } 23 | 24 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 25 | { 26 | if (IsEnabled(logLevel)) 27 | { 28 | this.logs.Add(formatter(state, exception)); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/AssemblyInfoReadTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Project2015To2017.Reading; 4 | 5 | namespace Project2015To2017.Tests 6 | { 7 | [TestClass] 8 | public class AssemblyInfoReadTest 9 | { 10 | [TestMethod] 11 | public void FindsAttributes() 12 | { 13 | var project = new ProjectReader().Read(Path.Combine("TestFiles", "OtherTestProjects", "net46console.testcsproj")); 14 | 15 | Assert.IsNotNull(project.AssemblyAttributes.Company); 16 | Assert.IsNotNull(project.AssemblyAttributes.Copyright); 17 | Assert.IsNotNull(project.AssemblyAttributes.InformationalVersion); 18 | Assert.IsNotNull(project.AssemblyAttributes.Product); 19 | Assert.AreEqual("Title", project.AssemblyAttributes.Title); 20 | Assert.IsNotNull(project.AssemblyAttributes.Version); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Project2015To2017/Project2015To2017.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.3;netstandard2.0 5 | Project2015To2017 6 | $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Project2015To2017.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.3;netstandard2.0 5 | Project2015To2017 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Diagnostics/W034ReferenceAliasesDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Project2015To2017.Analysis; 4 | using Project2015To2017.Definition; 5 | 6 | namespace Project2015To2017.Migrate2017.Diagnostics 7 | { 8 | public sealed class W034ReferenceAliasesDiagnostic : DiagnosticBase 9 | { 10 | public W034ReferenceAliasesDiagnostic() : base(34) 11 | { 12 | } 13 | 14 | public override IReadOnlyList Analyze(Project project) 15 | { 16 | var list = new List(); 17 | 18 | foreach (var reference in project.ProjectReferences.Where(x => !string.IsNullOrEmpty(x.Aliases))) 19 | { 20 | list.Add( 21 | CreateDiagnosticResult(project, 22 | $"ProjectReference ['{reference.Include}'] aliases are a feature of low support. Consider dropping their usage.", 23 | project.FilePath) 24 | .LoadLocationFromElement(reference.DefinitionElement)); 25 | } 26 | 27 | return list; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/ChainTransformationSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Extensions.Logging; 4 | using Project2015To2017.Transforms; 5 | 6 | namespace Project2015To2017 7 | { 8 | public class ChainTransformationSet : ITransformationSet 9 | { 10 | private readonly IReadOnlyCollection sets; 11 | 12 | public ChainTransformationSet(IReadOnlyCollection sets) 13 | { 14 | this.sets = sets ?? throw new ArgumentNullException(nameof(sets)); 15 | } 16 | 17 | public ChainTransformationSet(params ITransformationSet[] sets) 18 | : this((IReadOnlyCollection) sets) 19 | { 20 | } 21 | 22 | public IReadOnlyCollection Transformations(ILogger logger, ConversionOptions conversionOptions) 23 | { 24 | var res = new List(); 25 | foreach (var set in sets) 26 | { 27 | res.AddRange(set.Transformations(logger, conversionOptions)); 28 | } 29 | 30 | return res; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/IConditionEvaluationState.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Project2015To2017.Reading.Conditionals 4 | { 5 | internal interface IConditionEvaluationState 6 | { 7 | /// 8 | /// Table of conditioned properties and their values. 9 | /// Used to populate configuration lists in some project systems. 10 | /// If this is null, as it is for command line builds, conditioned properties 11 | /// are not recorded. 12 | /// 13 | Dictionary> ConditionedPropertiesInProject { get; } 14 | 15 | /// 16 | /// May return null if the expression would expand to non-empty and it broke out early. 17 | /// Otherwise, returns the correctly expanded expression. 18 | /// 19 | string ExpandIntoStringBreakEarly(string expression); 20 | 21 | /// 22 | /// Expands the specified expression into a string. 23 | /// 24 | string ExpandIntoString(string expression); 25 | } 26 | } -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Diagnostics/W030LegacyDebugTypesDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Project2015To2017.Analysis; 3 | using Project2015To2017.Definition; 4 | 5 | namespace Project2015To2017.Migrate2017.Diagnostics 6 | { 7 | public sealed class W030LegacyDebugTypesDiagnostic : DiagnosticBase 8 | { 9 | public W030LegacyDebugTypesDiagnostic() : base(30) 10 | { 11 | } 12 | 13 | public override IReadOnlyList Analyze(Project project) 14 | { 15 | var list = new List(); 16 | 17 | foreach (var x in project.ProjectDocument.Descendants(project.XmlNamespace + "DebugType")) 18 | { 19 | if (x.Value.Equals("portable", Extensions.BestAvailableStringIgnoreCaseComparison)) 20 | continue; 21 | 22 | list.Add( 23 | CreateDiagnosticResult(project, 24 | $"Consider migrating to 'portable' debug type, cross-platform alternative to legacy Windows PDBs.", 25 | project.FilePath) 26 | .LoadLocationFromElement(x)); 27 | } 28 | 29 | return list; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Hans van Bakel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/DiagnosticSet.cs: -------------------------------------------------------------------------------- 1 | using Project2015To2017.Analysis.Diagnostics; 2 | using System.Collections.Generic; 3 | 4 | namespace Project2015To2017.Analysis 5 | { 6 | public sealed class DiagnosticSet : HashSet 7 | { 8 | public static readonly IDiagnostic W001 = new W001IllegalProjectTypeDiagnostic(); 9 | public static readonly IDiagnostic W002 = new W002MissingProjectFileDiagnostic(); 10 | public static readonly IDiagnostic W010 = new W010ConfigurationsMismatchDiagnostic(); 11 | public static readonly IDiagnostic W011 = new W011UnsupportedConditionalDiagnostic(); 12 | 13 | public static readonly DiagnosticSet NoneDefault = new DiagnosticSet(); 14 | 15 | public static readonly DiagnosticSet System = new DiagnosticSet 16 | { 17 | W001, 18 | W002, 19 | }; 20 | 21 | public static readonly DiagnosticSet GenericProjectIssues = new DiagnosticSet 22 | { 23 | W010, 24 | W011, 25 | }; 26 | 27 | public static readonly DiagnosticSet All = new DiagnosticSet(); 28 | 29 | static DiagnosticSet() 30 | { 31 | All.UnionWith(System); 32 | All.UnionWith(GenericProjectIssues); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/AnalysisExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Xml; 3 | using System.Xml.Linq; 4 | 5 | namespace Project2015To2017.Analysis 6 | { 7 | public static class AnalysisExtensions 8 | { 9 | public static string GetSourcePath(this IDiagnosticLocation self) 10 | { 11 | if (self == null) throw new ArgumentNullException(nameof(self)); 12 | 13 | return self.SourcePath ?? self.Source?.FullName; 14 | } 15 | 16 | public static IDiagnosticResult LoadLocationFromElement(this IDiagnosticResult self, XElement element) 17 | { 18 | if (self == null) throw new ArgumentNullException(nameof(self)); 19 | if (element == null) throw new ArgumentNullException(nameof(element)); 20 | 21 | if (self.Location.SourceLine != uint.MaxValue) 22 | { 23 | return self; 24 | } 25 | 26 | if (element is IXmlLineInfo elementOnLine && elementOnLine.HasLineInfo()) 27 | { 28 | return new DiagnosticResult(self) 29 | { 30 | Location = new DiagnosticLocation(self.Location) 31 | { 32 | SourceLine = (uint) elementOnLine.LineNumber 33 | } 34 | }; 35 | } 36 | 37 | return self; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/Diagnostics/W002MissingProjectFileDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using Project2015To2017.Definition; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Project2015To2017.Analysis.Diagnostics 6 | { 7 | public sealed class W002MissingProjectFileDiagnostic : DiagnosticBase 8 | { 9 | public override bool SkipForLegacyProject => true; 10 | public override bool SkipForModernProject => true; 11 | public override IReadOnlyList Analyze(Project project) => 12 | throw new InvalidOperationException("W002 is not an executable diagnostic"); 13 | 14 | public static IReadOnlyList CreateResult(ProjectReference @ref, Solution solution = null) 15 | { 16 | return new[] 17 | { 18 | new DiagnosticResult 19 | { 20 | Code = "W002", 21 | Message = 22 | $"Referenced project file '{@ref.Include}' was not found at '{@ref.ProjectFile.FullName}'.", 23 | Location = new DiagnosticLocation 24 | { 25 | Source = solution?.FilePath 26 | } 27 | } 28 | }; 29 | } 30 | 31 | public W002MissingProjectFileDiagnostic() : base(2) 32 | { 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Transforms/AssemblyFilterPackageReferencesTransformation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Project2015To2017.Definition; 4 | using Project2015To2017.Transforms; 5 | 6 | namespace Project2015To2017.Migrate2017.Transforms 7 | { 8 | public sealed class AssemblyFilterPackageReferencesTransformation : ILegacyOnlyProjectTransformation 9 | { 10 | public void Transform(Project definition) 11 | { 12 | var packageReferences = 13 | definition.PackageReferences ?? new List(); 14 | 15 | var packageIds = packageReferences 16 | .Select(x => x.Id) 17 | .ToList(); 18 | 19 | var (assemblyReferences, removeQueue) = 20 | definition 21 | .AssemblyReferences 22 | //We don't need to keep any references to package files as these are 23 | //now generated dynamically at build time 24 | .Split(assemblyReference => !packageIds.Contains(assemblyReference.Include)); 25 | 26 | foreach (var assemblyReference in removeQueue) 27 | { 28 | assemblyReference.DefinitionElement?.Remove(); 29 | } 30 | 31 | definition.AssemblyReferences = assemblyReferences; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7.3 5 | hvanbakel et. al. 6 | Project2015To2017 7 | https://github.com/hvanbakel/CsprojToVs2017 8 | https://github.com/hvanbakel/CsprojToVs2017/blob/master/LICENSE 9 | https://github.com/hvanbakel/CsprojToVs2017 10 | Copyright Hans van Bakel 11 | Tool for converting a MSBuild project file to VS2017 format and beyond. 12 | dotnet csproj fsproj vbproj msbuild conversion vs2015 vs14 vs15 vs2017 13 | 3.0.4 14 | 3.0.0.0 15 | 16 | 17 | true 18 | true 19 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Transforms/EmptyGroupRemoveTransformation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Xml.Linq; 4 | using Microsoft.Extensions.Logging; 5 | using Project2015To2017.Definition; 6 | 7 | namespace Project2015To2017.Transforms 8 | { 9 | public class EmptyGroupRemoveTransformation 10 | : ITransformationWithTargetMoment, ITransformationWithDependencies 11 | { 12 | public void Transform(Project definition) 13 | { 14 | definition.PropertyGroups = FilterNonEmpty(definition.PropertyGroups); 15 | definition.ItemGroups = FilterNonEmpty(definition.ItemGroups).ToList(); 16 | } 17 | 18 | private static IReadOnlyList FilterNonEmpty(IEnumerable groups) 19 | { 20 | var (keep, remove) = groups 21 | .Split(x => x.HasElements 22 | || (x.HasAttributes && x.Attributes().Any(a => a.Name.LocalName != "Condition"))); 23 | foreach (var element in remove) 24 | { 25 | element.Remove(); 26 | } 27 | 28 | return keep; 29 | } 30 | 31 | public TargetTransformationExecutionMoment ExecutionMoment => 32 | TargetTransformationExecutionMoment.Late; 33 | 34 | public IReadOnlyCollection DependOn => new[] 35 | { 36 | typeof(PrimaryProjectPropertiesUpdateTransformation).Name, 37 | }; 38 | } 39 | } -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Diagnostics/W031MSBuildSdkVersionSpecificationDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Project2015To2017.Analysis; 4 | using Project2015To2017.Definition; 5 | 6 | namespace Project2015To2017.Migrate2017.Diagnostics 7 | { 8 | public sealed class W031MSBuildSdkVersionSpecificationDiagnostic : DiagnosticBase 9 | { 10 | public W031MSBuildSdkVersionSpecificationDiagnostic() : base(31) 11 | { 12 | } 13 | 14 | public override IReadOnlyList Analyze(Project project) 15 | { 16 | var root = project.ProjectDocument.Root ?? throw new ArgumentNullException(nameof(project)); 17 | var sdk = root.Attribute("Sdk")?.Value?.Trim(); 18 | 19 | if (string.IsNullOrEmpty(sdk)) 20 | { 21 | return Array.Empty(); 22 | } 23 | 24 | if (!sdk.Contains("/")) 25 | { 26 | return Array.Empty(); 27 | } 28 | 29 | return new[] 30 | { 31 | CreateDiagnosticResult(project, 32 | $"You have MSBuild SDK version specified in your project file ({sdk}). This is considered a bad practice. A recommended approach would be using global.json file for centralized SDK version management.", 33 | project.FilePath) 34 | .LoadLocationFromElement(root) 35 | }; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/ReporterBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Project2015To2017.Definition; 3 | 4 | namespace Project2015To2017.Analysis 5 | { 6 | public abstract class ReporterBase : IReporter where TOptions : IReporterOptions 7 | { 8 | public abstract TOptions DefaultOptions { get; } 9 | 10 | protected abstract void Report(IDiagnosticResult result, TOptions reporterOptions); 11 | 12 | /// 13 | public void Report(IReadOnlyList results, TOptions reporterOptions) 14 | { 15 | if (results == null || results.Count == 0) 16 | { 17 | return; 18 | } 19 | 20 | foreach (var result in results) 21 | { 22 | var targetResult = result; 23 | if (result.Location?.Source != null) 24 | { 25 | var sourcePath = result.Project.TryFindBestRootDirectory() 26 | ?.GetRelativePathTo(result.Location.Source); 27 | targetResult = new DiagnosticResult(result) 28 | { 29 | Location = new DiagnosticLocation(result.Location) 30 | { 31 | SourcePath = sourcePath 32 | } 33 | }; 34 | } 35 | 36 | Report(targetResult, reporterOptions); 37 | } 38 | } 39 | 40 | public virtual TOptions CreateOptionsForProject(Project project) 41 | { 42 | return this.DefaultOptions; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Transforms/TargetFrameworkReplaceTransformation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Project2015To2017.Definition; 3 | 4 | namespace Project2015To2017.Transforms 5 | { 6 | public sealed class TargetFrameworkReplaceTransformation : ITransformationWithTargetMoment 7 | { 8 | public TargetFrameworkReplaceTransformation( 9 | IReadOnlyList targetFrameworks, 10 | bool appendTargetFrameworkToOutputPath = true) 11 | { 12 | this.TargetFrameworks = targetFrameworks; 13 | this.AppendTargetFrameworkToOutputPath = appendTargetFrameworkToOutputPath; 14 | } 15 | 16 | public void Transform(Project definition) 17 | { 18 | if (null == definition) 19 | { 20 | return; 21 | } 22 | 23 | if (this.TargetFrameworks != null && this.TargetFrameworks.Count > 0) 24 | { 25 | definition.TargetFrameworks.Clear(); 26 | foreach (var targetFramework in this.TargetFrameworks) 27 | { 28 | definition.TargetFrameworks.Add(targetFramework); 29 | } 30 | } 31 | 32 | definition.AppendTargetFrameworkToOutputPath = this.AppendTargetFrameworkToOutputPath; 33 | } 34 | 35 | public IReadOnlyList TargetFrameworks { get; } 36 | public bool AppendTargetFrameworkToOutputPath { get; } 37 | 38 | public TargetTransformationExecutionMoment ExecutionMoment => 39 | TargetTransformationExecutionMoment.Early; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Diagnostics/W032OldLanguageVersionDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Project2015To2017.Analysis; 3 | using Project2015To2017.Definition; 4 | 5 | namespace Project2015To2017.Migrate2017.Diagnostics 6 | { 7 | public sealed class W032OldLanguageVersionDiagnostic : DiagnosticBase 8 | { 9 | public W032OldLanguageVersionDiagnostic() : base(32) 10 | { 11 | } 12 | 13 | public override IReadOnlyList Analyze(Project project) 14 | { 15 | var list = new List(); 16 | 17 | foreach (var x in project.ProjectDocument.Descendants(project.XmlNamespace + "LangVersion")) 18 | { 19 | // last 2 versions + default 20 | var version = x.Value; 21 | if (version.Equals("7.2", Extensions.BestAvailableStringIgnoreCaseComparison)) continue; 22 | if (version.Equals("7.3", Extensions.BestAvailableStringIgnoreCaseComparison)) continue; 23 | if (version.Equals("latest", Extensions.BestAvailableStringIgnoreCaseComparison)) continue; 24 | if (version.Equals("default", Extensions.BestAvailableStringIgnoreCaseComparison)) continue; 25 | 26 | list.Add( 27 | CreateDiagnosticResult(project, 28 | $"Consider upgrading language version to the latest ({version}).", 29 | project.FilePath) 30 | .LoadLocationFromElement(x)); 31 | } 32 | 33 | return list; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Definition/Solution.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using NuGet.Configuration; 4 | 5 | namespace Project2015To2017.Definition 6 | { 7 | public sealed class Solution 8 | { 9 | public FileInfo FilePath { get; set; } 10 | public IReadOnlyList ProjectPaths { get; set; } 11 | public DirectoryInfo SolutionFolder => this.FilePath.Directory; 12 | public IReadOnlyList UnsupportedProjectPaths { get; set; } 13 | 14 | /// 15 | /// The directory where nuget stores its extracted packages for the solution. 16 | /// In general this is the 'packages' folder within the solution oflder, but 17 | /// it can be overridden, which is accounted for here. 18 | /// 19 | public DirectoryInfo NuGetPackagesPath 20 | { 21 | get 22 | { 23 | var solutionFolder = this.SolutionFolder.FullName; 24 | 25 | var nuGetSettings = Settings.LoadDefaultSettings(solutionFolder); 26 | var repositoryPathSetting = SettingsUtility.GetRepositoryPath(nuGetSettings); 27 | 28 | //return the explicitly set path, or if there isn't one, then assume the 'packages' folder is in the solution folder 29 | var path = repositoryPathSetting ?? Path.GetFullPath(Path.Combine(solutionFolder, "packages")); 30 | 31 | return new DirectoryInfo(Extensions.MaybeAdjustFilePath(path, solutionFolder)); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Diagnostics/W033ObsoletePortableClassLibrariesDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Immutable; 3 | using System.Linq; 4 | using Project2015To2017.Analysis; 5 | using Project2015To2017.Definition; 6 | 7 | namespace Project2015To2017.Migrate2017.Diagnostics 8 | { 9 | public sealed class W033ObsoletePortableClassLibrariesDiagnostic : DiagnosticBase 10 | { 11 | public W033ObsoletePortableClassLibrariesDiagnostic() : base(33) 12 | { 13 | } 14 | 15 | public override IReadOnlyList Analyze(Project project) 16 | { 17 | var comparison = Extensions.BestAvailableStringIgnoreCaseComparison; 18 | var pcls = project.TargetFrameworks.Where(x => x.StartsWith("portable-", comparison)).ToImmutableHashSet(); 19 | 20 | // not all profiles can be mapped to .NET Standard (thanks to Silverlight & Framework 4.0) 21 | // we could skip emitting diagnostics in such cases, but we don't 22 | // instead we suggest dropping such old targets (WinXP can still be covered by net40 in TargetFrameworks) 23 | 24 | var list = new List(pcls.Count); 25 | 26 | foreach (var pcl in pcls) 27 | { 28 | list.Add( 29 | CreateDiagnosticResult(project, 30 | $"PCL profiles are obsolete. Consider migrating to .NET Standard.", 31 | project.FilePath)); 32 | } 33 | 34 | return list; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/EqualExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | 7 | namespace Project2015To2017.Reading.Conditionals 8 | { 9 | /// 10 | /// Compares for equality 11 | /// 12 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 13 | internal sealed class EqualExpressionNode : MultipleComparisonNode 14 | { 15 | /// 16 | /// Compare numbers 17 | /// 18 | protected override bool Compare(double left, double right) 19 | { 20 | return left == right; 21 | } 22 | 23 | /// 24 | /// Compare booleans 25 | /// 26 | protected override bool Compare(bool left, bool right) 27 | { 28 | return left == right; 29 | } 30 | 31 | /// 32 | /// Compare strings 33 | /// 34 | protected override bool Compare(string left, string right) 35 | { 36 | return String.Equals(left, right, StringComparison.OrdinalIgnoreCase); 37 | } 38 | 39 | internal override string DebuggerDisplay => $"(== {this.LeftChild.DebuggerDisplay} {this.RightChild.DebuggerDisplay})"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/NotEqualExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | 7 | namespace Project2015To2017.Reading.Conditionals 8 | { 9 | /// 10 | /// Compares for inequality 11 | /// 12 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 13 | internal sealed class NotEqualExpressionNode : MultipleComparisonNode 14 | { 15 | /// 16 | /// Compare numbers 17 | /// 18 | protected override bool Compare(double left, double right) 19 | { 20 | return left != right; 21 | } 22 | 23 | /// 24 | /// Compare booleans 25 | /// 26 | protected override bool Compare(bool left, bool right) 27 | { 28 | return left != right; 29 | } 30 | 31 | /// 32 | /// Compare strings 33 | /// 34 | protected override bool Compare(string left, string right) 35 | { 36 | return !String.Equals(left, right, StringComparison.OrdinalIgnoreCase); 37 | } 38 | 39 | internal override string DebuggerDisplay => $"(!= {this.LeftChild.DebuggerDisplay} {this.RightChild.DebuggerDisplay})"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Transforms/AssemblyFilterDefaultTransformation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Linq; 3 | using Project2015To2017.Definition; 4 | using Project2015To2017.Transforms; 5 | 6 | namespace Project2015To2017.Migrate2017.Transforms 7 | { 8 | public sealed class AssemblyFilterDefaultTransformation : ILegacyOnlyProjectTransformation 9 | { 10 | public void Transform(Project definition) 11 | { 12 | if (definition.AssemblyReferences == null) 13 | { 14 | definition.AssemblyReferences = ImmutableArray.Empty; 15 | return; 16 | } 17 | 18 | var (assemblyReferences, removeQueue) = definition.AssemblyReferences 19 | .Split(IsNonDefaultIncludedAssemblyReference); 20 | 21 | foreach (var assemblyReference in removeQueue) 22 | { 23 | assemblyReference.DefinitionElement?.Remove(); 24 | } 25 | 26 | definition.AssemblyReferences = assemblyReferences; 27 | } 28 | 29 | 30 | private static bool IsNonDefaultIncludedAssemblyReference(AssemblyReference assemblyReference) 31 | { 32 | var name = assemblyReference.Include; 33 | return !new[] 34 | { 35 | "System", 36 | "System.Core", 37 | "System.Data", 38 | "System.Drawing", 39 | "System.IO.Compression.FileSystem", 40 | "System.Numerics", 41 | "System.Runtime.Serialization", 42 | "System.Xml", 43 | "System.Xml.Linq" 44 | }.Contains(name); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/CharacterUtilities.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Project2015To2017.Reading.Conditionals 5 | { 6 | internal static class CharacterUtilities 7 | { 8 | static internal bool IsNumberStart(char candidate) 9 | { 10 | return (candidate == '+' || candidate == '-' || candidate == '.' || char.IsDigit(candidate)); 11 | } 12 | 13 | static internal bool IsSimpleStringStart(char candidate) 14 | { 15 | return (candidate == '_' || char.IsLetter(candidate)); 16 | } 17 | 18 | static internal bool IsSimpleStringChar(char candidate) 19 | { 20 | return (IsSimpleStringStart(candidate) || char.IsDigit(candidate)); 21 | } 22 | 23 | static internal bool IsHexAlphabetic(char candidate) 24 | { 25 | return (candidate == 'a' || candidate == 'b' || candidate == 'c' || candidate == 'd' || candidate == 'e' || candidate == 'f' || 26 | candidate == 'A' || candidate == 'B' || candidate == 'C' || candidate == 'D' || candidate == 'E' || candidate == 'F'); 27 | } 28 | 29 | static internal bool IsHexDigit(char candidate) 30 | { 31 | return (char.IsDigit(candidate) || IsHexAlphabetic(candidate)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Tool/Project2015To2017.Migrate2017.Tool.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net461;netcoreapp2.1 5 | netcoreapp2.1 6 | True 7 | 8 | dotnet-migrate-2017 9 | Project2015To2017.Migrate2017.Tool 10 | Project2015To2017.Migrate2017.Tool 11 | Exe 12 | 13 | $(RestoreAdditionalProjectSources); 14 | https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/Diagnostics/W011UnsupportedConditionalDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Project2015To2017.Definition; 4 | using Project2015To2017.Reading; 5 | using Project2015To2017.Reading.Conditionals; 6 | 7 | namespace Project2015To2017.Analysis.Diagnostics 8 | { 9 | public sealed class W011UnsupportedConditionalDiagnostic : DiagnosticBase 10 | { 11 | public W011UnsupportedConditionalDiagnostic() : base(11) 12 | { 13 | } 14 | 15 | public override IReadOnlyList Analyze(Project project) 16 | { 17 | var list = new List(); 18 | foreach (var x in project.ProjectDocument.Descendants()) 19 | { 20 | var condition = x.Attribute("Condition"); 21 | if (condition == null) 22 | { 23 | continue; 24 | } 25 | 26 | var conditionState = ConditionEvaluator.GetConditionState(condition.Value); 27 | 28 | var pairs = conditionState.UnsupportedNodes 29 | .Select(n => n.GetType().Name.Replace("ExpressionNode", "")) 30 | .GroupBy(n => n) 31 | .Select(n => (n.Key, n.Count())); 32 | 33 | foreach (var (key, value) in pairs) 34 | { 35 | var countContext = value > 1 ? $"({value} occurrences)" : ""; 36 | list.Add(CreateDiagnosticResult(project, 37 | $"Unsupported '{key}' expression in conditional {countContext}", 38 | project.FilePath) 39 | .LoadLocationFromElement(x)); 40 | } 41 | } 42 | 43 | return list; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/ImportsTargetsFilterPackageReferencesTransformationTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Project2015To2017.Migrate2017.Transforms; 4 | using Project2015To2017.Reading; 5 | 6 | namespace Project2015To2017.Tests 7 | { 8 | [TestClass] 9 | public class ImportsTargetsFilterPackageReferencesTransformationTest 10 | { 11 | [TestMethod] 12 | public void DedupeImportsFromPackagesAlternativePackagesFolder() 13 | { 14 | var projFile = @"TestFiles\AltNugetConfig\ProjectFolder\net46console.testcsproj"; 15 | 16 | var project = new ProjectReader().Read(projFile); 17 | 18 | var transformation = new ImportsTargetsFilterPackageReferencesTransformation(); 19 | 20 | //Then attempt to clear any referencing the nuget packages folder 21 | transformation.Transform(project); 22 | 23 | var expectedRemaining = new [] 24 | { 25 | @"", 26 | @"" 27 | }; 28 | 29 | var remainingImports = project.Imports 30 | .Select(x => x.ToString()) 31 | .ToList(); 32 | 33 | //The only ones left which point to another folder 34 | Assert.AreEqual(2, remainingImports.Count); 35 | CollectionAssert.AreEqual(expectedRemaining, remainingImports); 36 | 37 | Assert.IsFalse(project.Targets.Any()); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/OrExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | 6 | namespace Project2015To2017.Reading.Conditionals 7 | { 8 | /// 9 | /// Performs logical OR on children 10 | /// Does not update conditioned properties table 11 | /// 12 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 13 | internal sealed class OrExpressionNode : OperatorExpressionNode 14 | { 15 | /// 16 | /// Evaluate as boolean 17 | /// 18 | internal override bool BoolEvaluate(IConditionEvaluationState state) 19 | { 20 | if (this.LeftChild.BoolEvaluate(state)) 21 | { 22 | // Short circuit 23 | return true; 24 | } 25 | else 26 | { 27 | return this.RightChild.BoolEvaluate(state); 28 | } 29 | } 30 | 31 | internal override string DebuggerDisplay => $"(or {this.LeftChild.DebuggerDisplay} {this.RightChild.DebuggerDisplay})"; 32 | 33 | #region REMOVE_COMPAT_WARNING 34 | private bool _possibleOrCollision = true; 35 | internal override bool PossibleOrCollision 36 | { 37 | set { this._possibleOrCollision = value; } 38 | get { return this._possibleOrCollision; } 39 | } 40 | #endregion 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/upstream.md: -------------------------------------------------------------------------------- 1 | # upstream links 2 | To support advanced and always precise Condition attribute parsing we use parts of MSBuild code licensed under MIT. This document is designated to assist in updating imported code or alike purposes. 3 | 4 | ## Code version 5 | The code was brought in on July 21st 2018. The latest commit ID for Conditionals\ directory was *f147a76*. 6 | 7 | ## ConditionEvaluator 8 | Has parts of [ConditionEvaluator](https://github.com/Microsoft/msbuild/blob/master/src/Build/Evaluation/ConditionEvaluator.cs) code in "MSBuild Conditional routine" region. 9 | 10 | Changes: 11 | * Some method doc changes (usage section was incorrect) 12 | * SinglePropertyRegex was wrapped in Lazy 13 | * IConditionEvaluationState was moved out to the outer scope and then to Conditionals\ directory 14 | 15 | ## Conditionals\ directory 16 | Most of it is based on [Microsoft.Build.Evaluation\Conditionals](https://github.com/Microsoft/msbuild/tree/master/src/Build/Evaluation/Conditionals), with some parts from [Microsoft.Build.Shared](https://github.com/Microsoft/msbuild/tree/master/src/Shared). 17 | 18 | Changes: 19 | * Removed some deprecated code for compat with old MSBuild expression parser (too many dependencies) 20 | * Removed many verify-guards so that if in doubt an exception will likely be thrown (we don't need user-oriented error reporting facilities if conditionals contain syntax errors) 21 | * Included some utility classes (CharacterUtilities, ConversionUtilities, ErrorUtilities) 22 | * Changed namespace to match new location -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/AndExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | 6 | namespace Project2015To2017.Reading.Conditionals 7 | { 8 | /// 9 | /// Performs logical AND on children 10 | /// Does not update conditioned properties table 11 | /// 12 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 13 | internal sealed class AndExpressionNode : OperatorExpressionNode 14 | { 15 | /// 16 | /// Evaluate as boolean 17 | /// 18 | internal override bool BoolEvaluate(IConditionEvaluationState state) 19 | { 20 | if (!this.LeftChild.BoolEvaluate(state)) 21 | { 22 | // Short circuit 23 | return false; 24 | } 25 | else 26 | { 27 | return this.RightChild.BoolEvaluate(state); 28 | } 29 | } 30 | 31 | internal override string DebuggerDisplay => $"(and {this.LeftChild.DebuggerDisplay} {this.RightChild.DebuggerDisplay})"; 32 | 33 | #region REMOVE_COMPAT_WARNING 34 | private bool _possibleAndCollision = true; 35 | internal override bool PossibleAndCollision 36 | { 37 | set { this._possibleAndCollision = value; } 38 | get { return this._possibleAndCollision; } 39 | } 40 | #endregion 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/ConditionEvaluationStateImpl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Project2015To2017.Reading.Conditionals; 4 | 5 | namespace Project2015To2017.Reading 6 | { 7 | internal sealed class ConditionEvaluationStateImpl : IConditionEvaluationState 8 | { 9 | /// 10 | public Dictionary> ConditionedPropertiesInProject { get; } = 11 | new Dictionary>(); 12 | 13 | public GenericExpressionNode Node { get; set; } 14 | 15 | public bool Evaluated { get; set; } 16 | 17 | public ICollection UnsupportedNodes { get; set; } = 18 | Array.Empty(); 19 | 20 | public string Condition { get; } 21 | 22 | public ConditionEvaluationStateImpl(string condition) 23 | { 24 | this.Condition = condition ?? throw new ArgumentNullException(nameof(condition)); 25 | } 26 | 27 | /// 28 | public string ExpandIntoStringBreakEarly(string expression) 29 | { 30 | return expression; 31 | } 32 | 33 | /// 34 | public string ExpandIntoString(string expression) 35 | { 36 | return expression; 37 | } 38 | 39 | public void Evaluate() 40 | { 41 | try 42 | { 43 | // it makes little sense for condition to be that short 44 | if (this.Condition.Length >= 2) 45 | { 46 | this.Node.Evaluate(this); // return value ignored 47 | } 48 | } 49 | catch (Exception) 50 | { 51 | // ignored 52 | } 53 | finally 54 | { 55 | this.Evaluated = true; 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Solutions/ClassLibrary/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ClassLibrary")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ClassLibrary")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("0efb1378-b556-4ac1-b4e7-676ed896d863")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Vs15DiagnosticSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Project2015To2017.Analysis; 5 | using Project2015To2017.Analysis.Diagnostics; 6 | using Project2015To2017.Migrate2017.Diagnostics; 7 | using static Project2015To2017.Analysis.DiagnosticSet; 8 | 9 | namespace Project2015To2017.Migrate2017 10 | { 11 | public static class Vs15DiagnosticSet 12 | { 13 | public static readonly IDiagnostic W020 = new W020MicrosoftCSharpDiagnostic(); 14 | public static readonly IDiagnostic W021 = new W021SystemNuGetPackagesDiagnostic(); 15 | 16 | public static readonly IDiagnostic W030 = new W030LegacyDebugTypesDiagnostic(); 17 | public static readonly IDiagnostic W031 = new W031MSBuildSdkVersionSpecificationDiagnostic(); 18 | public static readonly IDiagnostic W032 = new W032OldLanguageVersionDiagnostic(); 19 | public static readonly IDiagnostic W033 = new W033ObsoletePortableClassLibrariesDiagnostic(); 20 | public static readonly IDiagnostic W034 = new W034ReferenceAliasesDiagnostic(); 21 | 22 | public static readonly DiagnosticSet ModernIssues = new DiagnosticSet 23 | { 24 | W020, 25 | W021, 26 | }; 27 | 28 | public static readonly DiagnosticSet ModernizationTips = new DiagnosticSet 29 | { 30 | W030, 31 | W031, 32 | W032, 33 | W033, 34 | W034, 35 | }; 36 | 37 | public static readonly DiagnosticSet All = new DiagnosticSet(); 38 | 39 | static Vs15DiagnosticSet() 40 | { 41 | All.UnionWith(DiagnosticSet.All); 42 | All.UnionWith(ModernIssues); 43 | All.UnionWith(ModernizationTips); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/LoggerReporter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Project2015To2017.Analysis 6 | { 7 | public sealed class LoggerReporter : ReporterBase 8 | { 9 | private readonly ILogger logger; 10 | 11 | public LoggerReporter(ILogger logger) 12 | { 13 | this.logger = logger; 14 | } 15 | 16 | public override LoggerReporterOptions DefaultOptions => new LoggerReporterOptions(); 17 | 18 | /// 19 | protected override void Report(IDiagnosticResult result, LoggerReporterOptions reporterOptions) 20 | { 21 | var consoleHeader = $"{result.Code}: "; 22 | string linePadding; 23 | { 24 | var pad = new StringBuilder(consoleHeader.Length, consoleHeader.Length); 25 | pad.Append(' ', consoleHeader.Length); 26 | linePadding = pad.ToString(); 27 | } 28 | var message = result.Message.Trim(); 29 | 30 | var sourcePath = result.Location.GetSourcePath(); 31 | var sourceLine = result.Location.SourceLine; 32 | switch (sourcePath) 33 | { 34 | case string _ when sourceLine != uint.MaxValue: 35 | message = $"{sourcePath}:{sourceLine}: {message}"; 36 | break; 37 | case string _ when sourceLine == uint.MaxValue: 38 | message = $"{sourcePath}: {message}"; 39 | break; 40 | case null when sourceLine != uint.MaxValue: 41 | message = $"{sourceLine}: {message}"; 42 | break; 43 | case null when sourceLine == uint.MaxValue: 44 | default: 45 | break; 46 | } 47 | 48 | this.logger.LogInformation(consoleHeader + Environment.NewLine + message); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/FunctionCallExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Project2015To2017.Reading.Conditionals 8 | { 9 | /// 10 | /// Evaluates a function expression, such as "Exists('foo')" 11 | /// 12 | internal sealed class FunctionCallExpressionNode : OperatorExpressionNode 13 | { 14 | private readonly List _arguments; 15 | public readonly string _functionName; 16 | 17 | internal FunctionCallExpressionNode(string functionName, List arguments) 18 | { 19 | this._functionName = functionName; 20 | this._arguments = arguments; 21 | } 22 | 23 | /// 24 | /// Evaluate node as boolean 25 | /// 26 | internal override bool BoolEvaluate(IConditionEvaluationState state) 27 | { 28 | if (String.Compare(this._functionName, "exists", StringComparison.OrdinalIgnoreCase) == 0) 29 | { 30 | return true; 31 | } 32 | 33 | if (String.Compare(this._functionName, "HasTrailingSlash", StringComparison.OrdinalIgnoreCase) == 0) 34 | { 35 | // often used to append slash to path so return false to enable this codepath 36 | return false; 37 | } 38 | 39 | // We haven't implemented any other "functions" 40 | 41 | return false; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/PrimaryProjectPropertiesUpdateTransformationTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Xml.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Project2015To2017.Definition; 5 | using Project2015To2017.Transforms; 6 | 7 | namespace Project2015To2017.Tests 8 | { 9 | [TestClass] 10 | public class PrimaryProjectPropertiesUpdateTransformationTest 11 | { 12 | [TestMethod] 13 | public void OutputAppendTargetFrameworkToOutputPathTrue() 14 | { 15 | var project = new Project 16 | { 17 | IsModernProject = true, 18 | AppendTargetFrameworkToOutputPath = true, 19 | PropertyGroups = new[] { new XElement("PropertyGroup") }, 20 | FilePath = new FileInfo("test.cs") 21 | }; 22 | 23 | new PrimaryProjectPropertiesUpdateTransformation().Transform(project); 24 | 25 | var appendTargetFrameworkToOutputPath = project.Property("AppendTargetFrameworkToOutputPath"); 26 | Assert.IsNull(appendTargetFrameworkToOutputPath); 27 | } 28 | 29 | [TestMethod] 30 | public void OutputAppendTargetFrameworkToOutputPathFalse() 31 | { 32 | var project = new Project 33 | { 34 | IsModernProject = true, 35 | AppendTargetFrameworkToOutputPath = false, 36 | PropertyGroups = new[] { new XElement("PropertyGroup") }, 37 | FilePath = new FileInfo("test.cs") 38 | }; 39 | 40 | new PrimaryProjectPropertiesUpdateTransformation().Transform(project); 41 | 42 | var appendTargetFrameworkToOutputPath = project.Property("AppendTargetFrameworkToOutputPath"); 43 | Assert.IsNotNull(appendTargetFrameworkToOutputPath); 44 | Assert.AreEqual("false", appendTargetFrameworkToOutputPath.Value); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Tool/CommandLogic.cs: -------------------------------------------------------------------------------- 1 | using DotNet.Globbing; 2 | using Serilog; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | 6 | namespace Project2015To2017.Migrate2017.Tool 7 | { 8 | public class CommandLogic 9 | { 10 | private readonly PatternProcessor globProcessor = (converter, pattern, callback, self) => 11 | { 12 | Log.Verbose("Falling back to globbing"); 13 | self.DoProcessableFileSearch(); 14 | var glob = Glob.Parse(pattern); 15 | Log.Verbose("Parsed glob {Glob}", glob); 16 | foreach (var (path, extension) in self.Files) 17 | { 18 | if (!glob.IsMatch(path)) continue; 19 | var file = new FileInfo(path); 20 | callback(file, extension); 21 | } 22 | 23 | return true; 24 | }; 25 | 26 | private readonly Facility facility; 27 | 28 | public CommandLogic() 29 | { 30 | var genericLogger = new Serilog.Extensions.Logging.SerilogLoggerProvider().CreateLogger(nameof(Serilog)); 31 | facility = new Facility(genericLogger, globProcessor); 32 | } 33 | 34 | public void ExecuteEvaluate( 35 | IReadOnlyCollection items, 36 | ConversionOptions conversionOptions) 37 | { 38 | facility.ExecuteEvaluate(items, conversionOptions); 39 | } 40 | 41 | public void ExecuteMigrate( 42 | IReadOnlyCollection items, 43 | bool noBackup, 44 | ConversionOptions conversionOptions) 45 | { 46 | facility.ExecuteMigrate(items, noBackup, conversionOptions); 47 | } 48 | 49 | public void ExecuteAnalyze( 50 | IReadOnlyCollection items, 51 | ConversionOptions conversionOptions) 52 | { 53 | facility.ExecuteAnalyze(items, conversionOptions); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Project2015To2017.Console/Options.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using CommandLine; 4 | 5 | namespace Project2015To2017.Console 6 | { 7 | public class Options 8 | { 9 | [Value(0)] 10 | public IEnumerable Files { get; set; } 11 | 12 | [Option('d', "dry-run", Default = false, HelpText = "Will not update any files, just outputs all the messages")] 13 | public bool DryRun { get; set; } = false; 14 | 15 | [Option('n', "no-backup", Default = false, HelpText = "Will not create a backup folder")] 16 | public bool NoBackup { get; set; } = false; 17 | 18 | [Option('a', "assembly-info", Default = false, HelpText = "Keep Assembly Info in a file")] 19 | public bool AssemblyInfo { get; set; } = false; 20 | 21 | [Option('t', "target-frameworks", Separator = ';', HelpText = "Specific target frameworks")] 22 | public IEnumerable TargetFrameworks { get; set; } 23 | 24 | [Option('o', "output-path", Default = false, HelpText = "Will not create a subfolder with the target framework in the output path")] 25 | public bool NoTargetFrameworkToOutputPath { get; set; } = false; 26 | 27 | [Option('f', "force", Default = false, HelpText = "Will force an upgrade even though certain preconditions might not have been met")] 28 | public bool Force { get; set; } = false; 29 | 30 | public ConversionOptions ConversionOptions 31 | => new ConversionOptions 32 | { 33 | KeepAssemblyInfo = AssemblyInfo, 34 | TargetFrameworks = TargetFrameworks?.ToList(), 35 | AppendTargetFrameworkToOutputPath = !NoTargetFrameworkToOutputPath, 36 | ProjectCache = new Caching.DefaultProjectCache(), 37 | Force = this.Force 38 | }; 39 | } 40 | } -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Vs15TransformationSet.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.Extensions.Logging; 3 | using Project2015To2017.Migrate2017.Transforms; 4 | using Project2015To2017.Transforms; 5 | 6 | namespace Project2015To2017.Migrate2017 7 | { 8 | public class Vs15TransformationSet : ITransformationSet 9 | { 10 | public static readonly Vs15TransformationSet TrueInstance = new Vs15TransformationSet(); 11 | 12 | public static readonly ITransformationSet Instance = new ChainTransformationSet( 13 | BasicReadTransformationSet.Instance, 14 | TrueInstance); 15 | 16 | private Vs15TransformationSet() 17 | { 18 | } 19 | 20 | public IReadOnlyCollection Transformations( 21 | ILogger logger, 22 | ConversionOptions conversionOptions) 23 | { 24 | return new ITransformation[] 25 | { 26 | // Generic 27 | new TargetFrameworkReplaceTransformation( 28 | conversionOptions.TargetFrameworks, 29 | conversionOptions.AppendTargetFrameworkToOutputPath), 30 | new PropertyDeduplicationTransformation(), 31 | new PropertySimplificationTransformation(), 32 | new PrimaryProjectPropertiesUpdateTransformation(), 33 | new EmptyGroupRemoveTransformation(), 34 | // VS15 migration 35 | new TestProjectPackageReferenceTransformation(logger), 36 | new AssemblyFilterPackageReferencesTransformation(), 37 | new AssemblyFilterHintedPackageReferencesTransformation(), 38 | new AssemblyFilterDefaultTransformation(), 39 | new ImportsTargetsFilterPackageReferencesTransformation(), 40 | new FileTransformation(logger), 41 | new XamlPagesTransformation(logger), 42 | }; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Transforms/PropertyDeduplicationTransformation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Immutable; 3 | using System.Linq; 4 | using Project2015To2017.Definition; 5 | 6 | namespace Project2015To2017.Transforms 7 | { 8 | public sealed class PropertyDeduplicationTransformation : ITransformationWithDependencies 9 | { 10 | public void Transform(Project definition) 11 | { 12 | var props = definition 13 | .ConditionalGroups() 14 | .Select(x => ( 15 | x, 16 | x.Elements() 17 | .Where(c => !c.HasElements) 18 | .Select(c => c.Name.LocalName) 19 | .ToImmutableHashSet() 20 | )) 21 | .ToImmutableArray(); 22 | 23 | if (props.Length == 0) 24 | { 25 | return; 26 | } 27 | 28 | var intersection = props.First().Item2; 29 | foreach (var (_, nameSet) in props.Skip(1)) 30 | { 31 | intersection = intersection.Intersect(nameSet); 32 | } 33 | 34 | if (intersection.IsEmpty) 35 | { 36 | return; 37 | } 38 | 39 | foreach (var commonKey in intersection) 40 | { 41 | var properties = props.Select(x => x.Item1.Element(x.Item1.Name.Namespace + commonKey)).ToImmutableArray(); 42 | var values = properties.Select(x => x.Value).ToImmutableHashSet(); 43 | if (values.Count != 1) continue; 44 | 45 | foreach (var property in properties) 46 | { 47 | property.Remove(); 48 | } 49 | 50 | var sourceForCopy = properties.First(); 51 | definition.PrimaryPropertyGroup().Add(sourceForCopy); 52 | } 53 | } 54 | 55 | public IReadOnlyCollection DependOn => new[] 56 | { 57 | typeof(PropertySimplificationTransformation).Name, 58 | }; 59 | } 60 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/W001IllegalProjectTypeDiagnosticTest.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Project2015To2017.Analysis.Diagnostics; 4 | using Project2015To2017.Definition; 5 | using Project2015To2017.Reading; 6 | 7 | namespace Project2015To2017.Tests 8 | { 9 | [TestClass] 10 | public class W001IllegalProjectTypeDiagnosticTest 11 | { 12 | [TestMethod] 13 | public void GeneratesResultForXamarinAndroid() 14 | { 15 | var project = new Project 16 | { 17 | ProjectDocument = new XDocument( 18 | new XElement("Project", 19 | new XElement(Project.XmlLegacyNamespace + "PropertyGroup", 20 | new XElement( 21 | XName.Get("ProjectTypeGuids", Project.XmlLegacyNamespace.NamespaceName), 22 | "{EFBA0AD7-5A72-4C68-AF49-83D382785DCF}")))) 23 | }; 24 | 25 | ProjectPropertiesReader.ReadPropertyGroups(project); 26 | 27 | var results = new W001IllegalProjectTypeDiagnostic().Analyze(project); 28 | Assert.AreEqual(1, results.Count); 29 | } 30 | 31 | [TestMethod] 32 | public void DoesNotGenerateResultForValidType() 33 | { 34 | var project = new Project 35 | { 36 | ProjectDocument = new XDocument( 37 | new XElement("Project", 38 | new XElement(Project.XmlLegacyNamespace + "PropertyGroup", 39 | new XElement( 40 | XName.Get("ProjectTypeGuids", Project.XmlLegacyNamespace.NamespaceName), 41 | "{318C4C53-4319-472D-A480-6540F3D375FD}")))) 42 | }; 43 | 44 | ProjectPropertiesReader.ReadPropertyGroups(project); 45 | 46 | var results = new W001IllegalProjectTypeDiagnostic().Analyze(project); 47 | Assert.IsNotNull(results); 48 | Assert.AreEqual(0, results.Count); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/NotExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | 6 | namespace Project2015To2017.Reading.Conditionals 7 | { 8 | /// 9 | /// Performs logical NOT on left child 10 | /// Does not update conditioned properties table 11 | /// 12 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 13 | internal sealed class NotExpressionNode : OperatorExpressionNode 14 | { 15 | /// 16 | /// Evaluate as boolean 17 | /// 18 | internal override bool BoolEvaluate(IConditionEvaluationState state) 19 | { 20 | return !this.LeftChild.BoolEvaluate(state); 21 | } 22 | 23 | internal override bool CanBoolEvaluate(IConditionEvaluationState state) 24 | { 25 | return this.LeftChild.CanBoolEvaluate(state); 26 | } 27 | 28 | /// 29 | /// Returns unexpanded value with '!' prepended. Useful for error messages. 30 | /// 31 | internal override string GetUnexpandedValue(IConditionEvaluationState state) 32 | { 33 | return "!" + this.LeftChild.GetUnexpandedValue(state); 34 | } 35 | 36 | /// 37 | /// Returns expanded value with '!' prepended. Useful for error messages. 38 | /// 39 | internal override string GetExpandedValue(IConditionEvaluationState state) 40 | { 41 | return "!" + this.LeftChild.GetExpandedValue(state); 42 | } 43 | 44 | internal override string DebuggerDisplay => $"(not {this.LeftChild.DebuggerDisplay})"; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Transforms/AssemblyFilterHintedPackageReferencesTransformation.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using Project2015To2017.Definition; 4 | using Project2015To2017.Transforms; 5 | 6 | namespace Project2015To2017.Migrate2017.Transforms 7 | { 8 | public sealed class AssemblyFilterHintedPackageReferencesTransformation : ILegacyOnlyProjectTransformation 9 | { 10 | public void Transform(Project definition) 11 | { 12 | if (definition.PackageReferences == null || definition.PackageReferences.Count == 0) 13 | { 14 | return; 15 | } 16 | 17 | var projectPath = definition.ProjectFolder.FullName; 18 | 19 | var nugetRepositoryPath = definition.NuGetPackagesPath.FullName; 20 | 21 | var packageReferenceIds = definition.PackageReferences.Select(x => x.Id).ToArray(); 22 | 23 | var packagePaths = packageReferenceIds.Select(packageId => Path.Combine(nugetRepositoryPath, packageId).ToLower()) 24 | .ToArray(); 25 | 26 | var (filteredAssemblies, removeQueue) = definition.AssemblyReferences 27 | .Split(assembly => !packagePaths.Any( 28 | packagePath => AssemblyMatchesPackage(assembly, packagePath) 29 | ) 30 | ); 31 | 32 | foreach (var assemblyReference in removeQueue) 33 | { 34 | assemblyReference.DefinitionElement?.Remove(); 35 | } 36 | 37 | definition.AssemblyReferences = filteredAssemblies; 38 | 39 | bool AssemblyMatchesPackage(AssemblyReference assembly, string packagePath) 40 | { 41 | var hintPath = assembly.HintPath; 42 | if (hintPath == null) 43 | { 44 | return false; 45 | } 46 | 47 | hintPath = Extensions.MaybeAdjustFilePath(hintPath, projectPath); 48 | 49 | var fullHintPath = Path.IsPathRooted(hintPath) ? hintPath : Path.GetFullPath(Path.Combine(projectPath, hintPath)); 50 | 51 | return fullHintPath.ToLowerInvariant().StartsWith(Extensions.MaybeAdjustFilePath(packagePath, projectPath)); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/Diagnostics/W001IllegalProjectTypeDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Project2015To2017.Definition; 3 | using System.Collections.Generic; 4 | using System.Collections.Immutable; 5 | using System.Linq; 6 | 7 | namespace Project2015To2017.Analysis.Diagnostics 8 | { 9 | public sealed class W001IllegalProjectTypeDiagnostic : DiagnosticBase 10 | { 11 | private static readonly Dictionary TypeGuids = new Dictionary 12 | { 13 | ["{EFBA0AD7-5A72-4C68-AF49-83D382785DCF}"] = "Xamarin.Android", 14 | ["{6BC8ED88-2882-458C-8E55-DFD12B67127B}"] = "Xamarin.iOS", 15 | ["{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A}"] = "UAP/UWP", 16 | }; 17 | 18 | /// 19 | public override IReadOnlyList Analyze(Project project) 20 | { 21 | var list = new List(TypeGuids.Count + 1); 22 | if (project.IsWindowsFormsProject) 23 | { 24 | list.Add(CreateDiagnosticResult(project, 25 | "Windows Forms support in CPS is in early stages and support might depend on your working environment.", 26 | project.FilePath)); 27 | } 28 | 29 | // try to get project type - may not exist 30 | var typeElement = project.Property("ProjectTypeGuids"); 31 | if (typeElement == null) 32 | { 33 | return Array.Empty(); 34 | } 35 | 36 | // parse the CSV list 37 | var guidTypes = typeElement.Value 38 | .Split(';') 39 | .Select(x => x.Trim().ToUpperInvariant()) 40 | .ToImmutableHashSet(); 41 | 42 | foreach (var item in TypeGuids) 43 | { 44 | if (!guidTypes.Contains(item.Key)) continue; 45 | 46 | list.Add( 47 | CreateDiagnosticResult(project, 48 | $"Project type {item.Value} is not tested thoroughly and support might depend on your working environment.", 49 | project.FilePath) 50 | .LoadLocationFromElement(typeElement)); 51 | } 52 | 53 | return list; 54 | } 55 | 56 | public W001IllegalProjectTypeDiagnostic() : base(1) 57 | { 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/ConversionOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Immutable; 3 | using Project2015To2017.Transforms; 4 | 5 | namespace Project2015To2017 6 | { 7 | public sealed class ConversionOptions 8 | { 9 | /// 10 | /// Project cache, if any. When null no caching is used. 11 | /// 12 | public Caching.IProjectCache ProjectCache { get; set; } 13 | /// 14 | /// Whether to keep the AssemblyInfo.cs file, or to 15 | /// move the attributes into the project file 16 | /// 17 | public bool KeepAssemblyInfo { get; set; } 18 | /// 19 | /// Change the target framework to a specific framework, or to 20 | /// multi target frameworks 21 | /// 22 | public IReadOnlyList TargetFrameworks { get; set; } 23 | /// 24 | /// Append the target framework to the output path 25 | /// 26 | public bool AppendTargetFrameworkToOutputPath { get; set; } = true; 27 | /// 28 | /// A collection of transforms executed before the execution of default ones 29 | /// 30 | public IReadOnlyList PreDefaultTransforms { get; set; } = ImmutableArray.Empty; 31 | /// 32 | /// A collection of transforms executed after the execution of default ones 33 | /// 34 | public IReadOnlyList PostDefaultTransforms { get; set; } = ImmutableArray.Empty; 35 | /// 36 | /// A collection of transform class names executed despite being intended for different project system, 37 | /// like forcing run on already converted project. 38 | /// 39 | public IReadOnlyList ForceDefaultTransforms { get; set; } = ImmutableArray.Empty; 40 | 41 | /// 42 | /// Force conversion ignoring any checks we might do that prevent a conversion. 43 | /// 44 | public bool Force { get; set; } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Transforms/TestProjectPackageReferenceTransformation.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.Extensions.Logging; 3 | using Project2015To2017.Definition; 4 | using Project2015To2017.Transforms; 5 | 6 | namespace Project2015To2017.Migrate2017.Transforms 7 | { 8 | public sealed class TestProjectPackageReferenceTransformation : ILegacyOnlyProjectTransformation 9 | { 10 | private readonly ILogger logger; 11 | 12 | public TestProjectPackageReferenceTransformation(ILogger logger = null) 13 | { 14 | this.logger = logger ?? NoopLogger.Instance; 15 | } 16 | 17 | public void Transform(Project definition) 18 | { 19 | var existingPackageReferences = definition.PackageReferences; 20 | 21 | if (definition.Type != ApplicationType.TestProject || 22 | existingPackageReferences.Any(x => x.Id == "Microsoft.NET.Test.Sdk")) return; 23 | 24 | var testReferences = new[] 25 | { 26 | new PackageReference {Id = "Microsoft.NET.Test.Sdk", Version = "15.0.0"}, 27 | new PackageReference {Id = "MSTest.TestAdapter", Version = "1.1.11"}, 28 | new PackageReference {Id = "MSTest.TestFramework", Version = "1.1.11"} 29 | }; 30 | 31 | var versions = definition.TargetFrameworks? 32 | .Select(f => int.TryParse(f.Replace("net", string.Empty), out int result) ? result : default(int?)) 33 | .Where(x => x.HasValue) 34 | .Select(v => v < 100 ? v * 10 : v); 35 | 36 | if (versions != null) 37 | { 38 | if (versions.Any(v => v < 450)) 39 | { 40 | logger.LogWarning("Target framework net40 is not compatible with the MSTest NuGet packages. Please consider updating the target framework of your test project(s)"); 41 | } 42 | } 43 | 44 | var adjustedPackageReferences = existingPackageReferences 45 | .Concat(testReferences) 46 | .ToArray(); 47 | 48 | foreach (var reference in testReferences) 49 | { 50 | logger.LogInformation($"Adding NuGet reference to {reference.Id}, version {reference.Version}."); 51 | } 52 | 53 | definition.PackageReferences = adjustedPackageReferences; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Diagnostics/W020MicrosoftCSharpDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Project2015To2017.Analysis; 5 | using Project2015To2017.Definition; 6 | 7 | namespace Project2015To2017.Migrate2017.Diagnostics 8 | { 9 | public sealed class W020MicrosoftCSharpDiagnostic : DiagnosticBase 10 | { 11 | private static readonly string[] IncompatiblePrefixes = { "net1", "net2", "net3" }; 12 | 13 | /// 14 | public override IReadOnlyList Analyze(Project project) 15 | { 16 | var reference = project.AssemblyReferences.FirstOrDefault(x => string.Equals(x.Include, "Microsoft.CSharp", StringComparison.OrdinalIgnoreCase)); 17 | if (reference == null) 18 | { 19 | return Array.Empty(); 20 | } 21 | 22 | var net40Found = false; 23 | 24 | var list = new List(); 25 | foreach (var framework in project.TargetFrameworks.Where(x => x.StartsWith("net", StringComparison.OrdinalIgnoreCase))) 26 | { 27 | if (framework.StartsWith("net40")) 28 | { 29 | net40Found = true; 30 | continue; 31 | } 32 | 33 | foreach (var incompatiblePrefix in IncompatiblePrefixes) 34 | { 35 | if (!framework.StartsWith(incompatiblePrefix)) 36 | { 37 | continue; 38 | } 39 | 40 | list.Add( 41 | CreateDiagnosticResult(project, 42 | $"'Microsoft.CSharp' assembly is incompatible with TargetFramework '{incompatiblePrefix}', version no less than 4.0 is expected.", 43 | project.FilePath) 44 | .LoadLocationFromElement(reference.DefinitionElement)); 45 | } 46 | } 47 | 48 | if (!net40Found) 49 | { 50 | list.Add( 51 | CreateDiagnosticResult(project, 52 | "A better way to reference 'Microsoft.CSharp' assembly is using 'Microsoft.CSharp' NuGet package. It will simplify porting to other runtimes.", 53 | project.FilePath) 54 | .LoadLocationFromElement(reference.DefinitionElement)); 55 | } 56 | 57 | return list; 58 | } 59 | 60 | public W020MicrosoftCSharpDiagnostic() : base(20) 61 | { 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/Analyzer.cs: -------------------------------------------------------------------------------- 1 | using Project2015To2017.Definition; 2 | using Project2015To2017.Reading; 3 | using System; 4 | using Project2015To2017.Analysis.Diagnostics; 5 | 6 | namespace Project2015To2017.Analysis 7 | { 8 | public sealed class Analyzer 9 | where TReporter : class, IReporter 10 | where TReporterOptions : IReporterOptions 11 | { 12 | private readonly AnalysisOptions _options; 13 | private readonly TReporter _reporter; 14 | 15 | public Analyzer(TReporter reporter, AnalysisOptions options = null) 16 | { 17 | this._reporter = reporter ?? throw new ArgumentNullException(nameof(reporter)); 18 | this._options = options ?? new AnalysisOptions(); 19 | } 20 | 21 | public void Analyze(Project project) 22 | { 23 | if (project == null) 24 | { 25 | throw new ArgumentNullException(nameof(project)); 26 | } 27 | 28 | foreach (var diagnostic in this._options.Diagnostics) 29 | { 30 | if (diagnostic.SkipForModernProject && project.IsModernProject) 31 | { 32 | continue; 33 | } 34 | 35 | if (diagnostic.SkipForLegacyProject && !project.IsModernProject) 36 | { 37 | continue; 38 | } 39 | 40 | var reporterOptions = this._reporter.CreateOptionsForProject(project); 41 | this._reporter.Report(diagnostic.Analyze(project), reporterOptions); 42 | } 43 | } 44 | 45 | public void Analyze(Solution solution) 46 | { 47 | if (solution == null) 48 | { 49 | throw new ArgumentNullException(nameof(solution)); 50 | } 51 | 52 | if (solution.ProjectPaths == null) 53 | { 54 | return; 55 | } 56 | 57 | var projectReader = new ProjectReader(NoopLogger.Instance); 58 | foreach (var projectPath in solution.ProjectPaths) 59 | { 60 | if (!projectPath.ProjectFile.Exists) 61 | { 62 | if (this._options.Diagnostics.Contains(DiagnosticSet.W002)) 63 | { 64 | this._reporter.Report( 65 | W002MissingProjectFileDiagnostic.CreateResult(projectPath, solution), 66 | this._reporter.DefaultOptions); 67 | } 68 | continue; 69 | } 70 | 71 | var project = projectReader.Read(projectPath.ProjectFile); 72 | 73 | Analyze(project); 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Solutions/sampleSolution.testsln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary", "ClassLibrary\ClassLibrary.csproj", "{0EFB1378-B556-4AC1-B4E7-676ED896D863}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AFolder", "AFolder", "{4C39B892-D594-4976-8930-046197229232}" 9 | ProjectSection(SolutionItems) = preProject 10 | TextFile.txt = TextFile.txt 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Debug|x64 = Debug|x64 17 | Debug|x86 = Debug|x86 18 | Release|Any CPU = Release|Any CPU 19 | Release|x64 = Release|x64 20 | Release|x86 = Release|x86 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {0EFB1378-B556-4AC1-B4E7-676ED896D863}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {0EFB1378-B556-4AC1-B4E7-676ED896D863}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {0EFB1378-B556-4AC1-B4E7-676ED896D863}.Debug|x64.ActiveCfg = Debug|Any CPU 26 | {0EFB1378-B556-4AC1-B4E7-676ED896D863}.Debug|x64.Build.0 = Debug|Any CPU 27 | {0EFB1378-B556-4AC1-B4E7-676ED896D863}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {0EFB1378-B556-4AC1-B4E7-676ED896D863}.Debug|x86.Build.0 = Debug|Any CPU 29 | {0EFB1378-B556-4AC1-B4E7-676ED896D863}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {0EFB1378-B556-4AC1-B4E7-676ED896D863}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {0EFB1378-B556-4AC1-B4E7-676ED896D863}.Release|x64.ActiveCfg = Release|Any CPU 32 | {0EFB1378-B556-4AC1-B4E7-676ED896D863}.Release|x64.Build.0 = Release|Any CPU 33 | {0EFB1378-B556-4AC1-B4E7-676ED896D863}.Release|x86.ActiveCfg = Release|Any CPU 34 | {0EFB1378-B556-4AC1-B4E7-676ED896D863}.Release|x86.Build.0 = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {36F34A40-2599-425D-82D8-1084A2C9AEA5} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/DiagnosticBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using Project2015To2017.Definition; 6 | 7 | namespace Project2015To2017.Analysis 8 | { 9 | public abstract class DiagnosticBase : IEquatable, IDiagnostic 10 | { 11 | public uint Id { get; } 12 | public string DiagnosticCode { get; } 13 | 14 | public virtual bool SkipForLegacyProject => false; 15 | public virtual bool SkipForModernProject => false; 16 | 17 | protected DiagnosticBase(uint id) 18 | { 19 | this.Id = id; 20 | this.DiagnosticCode = $"W{id.ToString(CultureInfo.InvariantCulture).PadLeft(3, '0')}"; 21 | } 22 | 23 | public abstract IReadOnlyList Analyze(Project project); 24 | 25 | /// 26 | /// Report found issue using user-selected means of logging 27 | /// 28 | /// Project in which the issue was found 29 | /// Informative message about the issue 30 | /// File or directory for user reference 31 | /// File line for user reference 32 | protected DiagnosticResult CreateDiagnosticResult(Project project, string message, FileSystemInfo source = null, uint sourceLine = uint.MaxValue) 33 | { 34 | if (source == null && sourceLine != uint.MaxValue) 35 | { 36 | throw new ArgumentNullException(nameof(source)); 37 | } 38 | 39 | return new DiagnosticResult 40 | { 41 | Code = DiagnosticCode, 42 | Message = message, 43 | Location = new DiagnosticLocation 44 | { 45 | Source = source, 46 | SourceLine = sourceLine 47 | }, 48 | Project = project 49 | }; 50 | } 51 | 52 | public override int GetHashCode() 53 | { 54 | return (int)this.Id; 55 | } 56 | 57 | public override string ToString() 58 | { 59 | return this.DiagnosticCode; 60 | } 61 | 62 | public bool Equals(DiagnosticBase other) 63 | { 64 | if (other is null) return false; 65 | if (ReferenceEquals(this, other)) return true; 66 | return this.Id == other.Id; 67 | } 68 | 69 | public override bool Equals(object obj) 70 | { 71 | if (obj is null) return false; 72 | if (ReferenceEquals(this, obj)) return true; 73 | return obj is DiagnosticBase other && Equals(other); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/Solutions/ClassLibrary/ClassLibrary.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 0efb1378-b556-4ac1-b4e7-676ed896d863 8 | Library 9 | Properties 10 | ClassLibrary 11 | ClassLibrary 12 | v4.6.1 13 | 512 14 | true 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Project2015To2017.Core/UnsupportedProjectTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Project2015To2017.Definition; 4 | 5 | namespace Project2015To2017 6 | { 7 | /// 8 | /// Helper library to filter out unsupported project types 9 | /// 10 | public static class UnsupportedProjectTypes 11 | { 12 | /// 13 | /// Check for unsupported ProjectTypeGuids in project 14 | /// 15 | /// source project document to check 16 | /// 17 | public static bool IsUnsupportedProjectType(Project project) 18 | { 19 | if (project == null) throw new ArgumentNullException(nameof(project)); 20 | var xmlDocument = project.ProjectDocument; 21 | 22 | // try to get project type - may not exist 23 | var typeElement = xmlDocument.Descendants(project.XmlNamespace + "ProjectTypeGuids").FirstOrDefault(); 24 | // no matching tag found, project should be okay to convert 25 | if (typeElement == null) return false; 26 | 27 | // parse the CSV list 28 | var guidTypes = typeElement.Value.Split(';').Select(x => x.Trim()); 29 | 30 | // if any guid matches an unsupported type, return true 31 | return (from guid in guidTypes 32 | from unsupported in unsupportedGuids 33 | where guid.Equals(unsupported, StringComparison.CurrentCultureIgnoreCase) 34 | select unsupported).Any(); 35 | } 36 | 37 | /// 38 | /// Guids that cannot be converted 39 | /// 40 | /// 41 | /// Types of projects that are not supported: 42 | /// https://github.com/dotnet/project-system/blob/master/docs/feature-comparison.md 43 | /// The GUIDs taken from 44 | /// https://www.codeproject.com/Reference/720512/List-of-Visual-Studio-Project-Type-GUIDs 45 | /// Note that the list here is in upper case but project file guids are normally lower case 46 | /// This list does not include Windows Forms apps, these have no type guid 47 | /// 48 | private static readonly string[] unsupportedGuids = 49 | { 50 | "{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}", // ASP.NET 5 51 | "{603C0E0B-DB56-11DC-BE95-000D561079B0}", // ASP.NET MVC 1 52 | "{F85E285D-A4E0-4152-9332-AB1D724D3325}", // ASP.NET MVC 2 53 | "{E53F8FEA-EAE0-44A6-8774-FFD645390401}", // ASP.NET MVC 3 54 | "{E3E379DF-F4C6-4180-9B81-6769533ABE47}", // ASP.NET MVC 4 55 | "{349C5851-65DF-11DA-9384-00065B846F21}", // ASP.NET MVC 5 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/AssemblyFilterPackageReferencesTransformationTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Project2015To2017.Definition; 6 | using Project2015To2017.Migrate2017.Transforms; 7 | using Project2015To2017.Reading; 8 | 9 | namespace Project2015To2017.Tests 10 | { 11 | [TestClass] 12 | public class AssemblyFilterPackageReferencesTransformationTest 13 | { 14 | [TestMethod] 15 | public void TransformsAssemblyReferences() 16 | { 17 | var project = new ProjectReader().Read(Path.Combine("TestFiles", "OtherTestProjects", "net46console.testcsproj")); 18 | var transformation = new AssemblyFilterPackageReferencesTransformation(); 19 | 20 | transformation.Transform(project); 21 | 22 | Assert.AreEqual(12, project.AssemblyReferences.Count); 23 | Assert.IsTrue(project.AssemblyReferences.Any(x => x.Include == @"System.Xml.Linq")); 24 | Assert.IsTrue(project.AssemblyReferences.Any(x => x.Include == @"Microsoft.CSharp")); 25 | } 26 | 27 | [TestMethod] 28 | public void RemoveExtraAssemblyReferences() 29 | { 30 | var project = new Project 31 | { 32 | AssemblyReferences = new List 33 | { 34 | new AssemblyReference 35 | { 36 | Include = "Test.Package", 37 | EmbedInteropTypes = "false", 38 | HintPath = @"..\packages\Test.Package.dll", 39 | Private = "false", 40 | SpecificVersion = "false" 41 | } 42 | , 43 | new AssemblyReference 44 | { 45 | Include = "Other.Package", 46 | EmbedInteropTypes = "false", 47 | HintPath = @"..\packages\Other.Package.dll", 48 | Private = "false", 49 | SpecificVersion = "false" 50 | } 51 | }, 52 | PackageReferences = new List 53 | { 54 | new PackageReference 55 | { 56 | Id = "Test.Package", 57 | IsDevelopmentDependency = false, 58 | Version = "1.2.3" 59 | } 60 | , 61 | new PackageReference 62 | { 63 | Id = "Another.Package", 64 | IsDevelopmentDependency = false, 65 | Version = "3.2.1" 66 | } 67 | } 68 | }; 69 | 70 | var transformation = new AssemblyFilterPackageReferencesTransformation(); 71 | 72 | transformation.Transform(project); 73 | 74 | Assert.AreEqual(1, project.AssemblyReferences.Count); 75 | Assert.AreEqual(2, project.PackageReferences.Count); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/AssemblyFilterHintedPackageReferencesTransformationTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Project2015To2017.Definition; 5 | using Project2015To2017.Migrate2017.Transforms; 6 | using Project2015To2017.Reading; 7 | 8 | namespace Project2015To2017.Tests 9 | { 10 | [TestClass] 11 | public class AssemblyFilterHintedPackageReferencesTransformationTest 12 | { 13 | [TestMethod] 14 | public void HandlesNoPackagesConfig() 15 | { 16 | var project = new Project(); 17 | 18 | var transformation = new AssemblyFilterHintedPackageReferencesTransformation(); 19 | transformation.Transform(project); 20 | } 21 | 22 | [TestMethod] 23 | public void DedupeReferencesFromPackages() 24 | { 25 | var project = new Project 26 | { 27 | AssemblyReferences = new List 28 | { 29 | new AssemblyReference { 30 | Include = "Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL", 31 | HintPath = @"..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll" 32 | }, 33 | new AssemblyReference { 34 | 35 | Include = "System.Data.DataSetExtensions" 36 | }, 37 | new AssemblyReference { 38 | 39 | Include = "Owin", 40 | HintPath = @"..\packages\Owin.1.0\lib\net40\Owin.dll" 41 | } 42 | }, 43 | PackageReferences = new[] 44 | { 45 | new PackageReference { 46 | Id = "Newtonsoft.Json", 47 | Version = "1.0.0" 48 | } 49 | }, 50 | FilePath = new FileInfo(@".\dummy.csproj") 51 | }; 52 | 53 | var transformation = new AssemblyFilterHintedPackageReferencesTransformation(); 54 | 55 | transformation.Transform(project); 56 | 57 | Assert.AreEqual(2, project.AssemblyReferences.Count); 58 | } 59 | 60 | [TestMethod] 61 | public void DedupeReferencesFromPackagesAlternativePackagesFolder() 62 | { 63 | var projFile = @"TestFiles\AltNugetConfig\ProjectFolder\net46console.testcsproj"; 64 | 65 | var project = new ProjectReader().Read(projFile); 66 | 67 | var transformation = new AssemblyFilterHintedPackageReferencesTransformation(); 68 | 69 | //Then attempt to clear any referencing the nuget packages folder 70 | transformation.Transform(project); 71 | 72 | //The only one left which points to another folder 73 | Assert.AreEqual(1, project.AssemblyReferences.Count); 74 | Assert.IsTrue(project.AssemblyReferences[0].Include.StartsWith("Owin")); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Project2015To2017.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using CommandLine; 4 | using Microsoft.Extensions.Logging; 5 | using Project2015To2017.Analysis; 6 | using Project2015To2017.Definition; 7 | using Project2015To2017.Migrate2017; 8 | using Project2015To2017.Transforms; 9 | 10 | namespace Project2015To2017.Console 11 | { 12 | internal static class Program 13 | { 14 | static void Main(string[] args) 15 | { 16 | Parser.Default.ParseArguments(args) 17 | .WithParsed(ConvertProject); 18 | } 19 | 20 | private static void ConvertProject(Options options) 21 | { 22 | var conversionOptions = options.ConversionOptions; 23 | 24 | var convertedProjects = new List(); 25 | 26 | ILogger logger = new ConsoleLogger("console", (s, l) => l >= LogLevel.Information, true); 27 | 28 | logger.LogError("csproj-to-2017 is obsolete and will be removed very soon. Migrate to Project2015To2017.Migrate2017.Tool (dotnet migrate-2017) as soon as possible."); 29 | 30 | foreach (var file in options.Files) 31 | { 32 | var projects = new ProjectConverter(logger, Vs15TransformationSet.Instance, conversionOptions) 33 | .Convert(file, logger) 34 | .Where(x => x != null) 35 | .ToList(); 36 | convertedProjects.AddRange(projects); 37 | } 38 | 39 | System.Console.Out.Flush(); 40 | 41 | var analyzer = new Analyzer(new LoggerReporter(logger)); 42 | foreach (var project in convertedProjects) 43 | { 44 | analyzer.Analyze(project); 45 | } 46 | 47 | if (options.DryRun) 48 | { 49 | return; 50 | } 51 | 52 | var doBackup = !options.NoBackup; 53 | 54 | var writer = new Writing.ProjectWriter(logger, x => x.Delete(), _ => { }); 55 | foreach (var project in convertedProjects) 56 | { 57 | writer.Write(project, doBackup); 58 | } 59 | 60 | System.Console.Out.Flush(); 61 | 62 | logger.LogInformation("### Performing 2nd pass to analyze converted projects..."); 63 | 64 | conversionOptions.ProjectCache?.Purge(); 65 | 66 | convertedProjects.Clear(); 67 | 68 | foreach (var file in options.Files) 69 | { 70 | var projects = new ProjectConverter(logger, BasicReadTransformationSet.Instance, conversionOptions) 71 | .Convert(file, logger) 72 | .Where(x => x != null) 73 | .ToList(); 74 | convertedProjects.AddRange(projects); 75 | } 76 | 77 | System.Console.Out.Flush(); 78 | 79 | foreach (var project in convertedProjects) 80 | { 81 | analyzer.Analyze(project); 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/LessThanExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | 7 | namespace Project2015To2017.Reading.Conditionals 8 | { 9 | /// 10 | /// Compares for left < right 11 | /// 12 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 13 | internal sealed class LessThanExpressionNode : NumericComparisonExpressionNode 14 | { 15 | /// 16 | /// Compare numerically 17 | /// 18 | protected override bool Compare(double left, double right) 19 | { 20 | return left < right; 21 | } 22 | 23 | /// 24 | /// Compare Versions. This is only intended to compare version formats like "A.B.C.D" which can otherwise not be compared numerically 25 | /// 26 | /// 27 | protected override bool Compare(Version left, Version right) 28 | { 29 | return left < right; 30 | } 31 | 32 | /// 33 | /// Compare mixed numbers and Versions 34 | /// 35 | protected override bool Compare(Version left, double right) 36 | { 37 | if (left.Major != right) 38 | { 39 | return left.Major < right; 40 | } 41 | 42 | // If they have same "major" number, then that means we are comparing something like "6.X.Y.Z" to "6". Version treats the objects with more dots as 43 | // "larger" regardless of what those dots are (e.g. 6.0.0.0 > 6 is a true statement) 44 | return false; 45 | } 46 | 47 | /// 48 | /// Compare mixed numbers and Versions 49 | /// 50 | protected override bool Compare(double left, Version right) 51 | { 52 | if (right.Major != left) 53 | { 54 | return left < right.Major; 55 | } 56 | 57 | // If they have same "major" number, then that means we are comparing something like "6.X.Y.Z" to "6". Version treats the objects with more dots as 58 | // "larger" regardless of what those dots are (e.g. 6.0.0.0 > 6 is a true statement) 59 | return true; 60 | } 61 | 62 | internal override string DebuggerDisplay => $"(< {this.LeftChild.DebuggerDisplay} {this.RightChild.DebuggerDisplay})"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/GreaterThanExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | 7 | namespace Project2015To2017.Reading.Conditionals 8 | { 9 | /// 10 | /// Compares for left > right 11 | /// 12 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 13 | internal sealed class GreaterThanExpressionNode : NumericComparisonExpressionNode 14 | { 15 | /// 16 | /// Compare numerically 17 | /// 18 | protected override bool Compare(double left, double right) 19 | { 20 | return left > right; 21 | } 22 | 23 | /// 24 | /// Compare Versions. This is only intended to compare version formats like "A.B.C.D" which can otherwise not be compared numerically 25 | /// 26 | /// 27 | protected override bool Compare(Version left, Version right) 28 | { 29 | return left > right; 30 | } 31 | 32 | /// 33 | /// Compare mixed numbers and Versions 34 | /// 35 | protected override bool Compare(Version left, double right) 36 | { 37 | if (left.Major != right) 38 | { 39 | return left.Major > right; 40 | } 41 | 42 | // If they have same "major" number, then that means we are comparing something like "6.X.Y.Z" to "6". Version treats the objects with more dots as 43 | // "larger" regardless of what those dots are (e.g. 6.0.0.0 > 6 is a true statement) 44 | return true; 45 | } 46 | 47 | /// 48 | /// Compare mixed numbers and Versions 49 | /// 50 | protected override bool Compare(double left, Version right) 51 | { 52 | if (right.Major != left) 53 | { 54 | return left > right.Major; 55 | } 56 | 57 | // If they have same "major" number, then that means we are comparing something like "6.X.Y.Z" to "6". Version treats the objects with more dots as 58 | // "larger" regardless of what those dots are (e.g. 6.0.0.0 > 6 is a true statement) 59 | return false; 60 | } 61 | 62 | internal override string DebuggerDisplay => $"(> {this.LeftChild.DebuggerDisplay} {this.RightChild.DebuggerDisplay})"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/LessThanOrEqualExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | 7 | namespace Project2015To2017.Reading.Conditionals 8 | { 9 | /// 10 | /// Compares for left <= right 11 | /// 12 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 13 | internal sealed class LessThanOrEqualExpressionNode : NumericComparisonExpressionNode 14 | { 15 | /// 16 | /// Compare numerically 17 | /// 18 | protected override bool Compare(double left, double right) 19 | { 20 | return left <= right; 21 | } 22 | 23 | /// 24 | /// Compare Versions. This is only intended to compare version formats like "A.B.C.D" which can otherwise not be compared numerically 25 | /// 26 | /// 27 | protected override bool Compare(Version left, Version right) 28 | { 29 | return left <= right; 30 | } 31 | 32 | /// 33 | /// Compare mixed numbers and Versions 34 | /// 35 | protected override bool Compare(Version left, double right) 36 | { 37 | if (left.Major != right) 38 | { 39 | return left.Major <= right; 40 | } 41 | 42 | // If they have same "major" number, then that means we are comparing something like "6.X.Y.Z" to "6". Version treats the objects with more dots as 43 | // "larger" regardless of what those dots are (e.g. 6.0.0.0 > 6 is a true statement) 44 | return false; 45 | } 46 | 47 | /// 48 | /// Compare mixed numbers and Versions 49 | /// 50 | protected override bool Compare(double left, Version right) 51 | { 52 | if (right.Major != left) 53 | { 54 | return left <= right.Major; 55 | } 56 | 57 | // If they have same "major" number, then that means we are comparing something like "6.X.Y.Z" to "6". Version treats the objects with more dots as 58 | // "larger" regardless of what those dots are (e.g. 6.0.0.0 > 6 is a true statement) 59 | return true; 60 | } 61 | 62 | internal override string DebuggerDisplay => $"(<= {this.LeftChild.DebuggerDisplay} {this.RightChild.DebuggerDisplay})"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/GreaterThanOrEqualExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | 7 | namespace Project2015To2017.Reading.Conditionals 8 | { 9 | /// 10 | /// Compares for left >= right 11 | /// 12 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 13 | internal sealed class GreaterThanOrEqualExpressionNode : NumericComparisonExpressionNode 14 | { 15 | /// 16 | /// Compare numerically 17 | /// 18 | protected override bool Compare(double left, double right) 19 | { 20 | return left >= right; 21 | } 22 | 23 | /// 24 | /// Compare Versions. This is only intended to compare version formats like "A.B.C.D" which can otherwise not be compared numerically 25 | /// 26 | /// 27 | protected override bool Compare(Version left, Version right) 28 | { 29 | return left >= right; 30 | } 31 | 32 | /// 33 | /// Compare mixed numbers and Versions 34 | /// 35 | protected override bool Compare(Version left, double right) 36 | { 37 | if (left.Major != right) 38 | { 39 | return left.Major >= right; 40 | } 41 | 42 | // If they have same "major" number, then that means we are comparing something like "6.X.Y.Z" to "6". Version treats the objects with more dots as 43 | // "larger" regardless of what those dots are (e.g. 6.0.0.0 > 6 is a true statement) 44 | return true; 45 | } 46 | 47 | /// 48 | /// Compare mixed numbers and Versions 49 | /// 50 | protected override bool Compare(double left, Version right) 51 | { 52 | if (right.Major != left) 53 | { 54 | return left >= right.Major; 55 | } 56 | 57 | // If they have same "major" number, then that means we are comparing something like "6.X.Y.Z" to "6". Version treats the objects with more dots as 58 | // "larger" regardless of what those dots are (e.g. 6.0.0.0 > 6 is a true statement) 59 | return false; 60 | } 61 | 62 | internal override string DebuggerDisplay => $"(>= {this.LeftChild.DebuggerDisplay} {this.RightChild.DebuggerDisplay})"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Project2015To2017/ProjectConverterExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Project2015To2017.Definition; 3 | using Project2015To2017.Reading; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | 9 | namespace Project2015To2017 10 | { 11 | public static class ProjectConverterExtensions 12 | { 13 | public static IEnumerable Convert(this ProjectConverter self, string target, ILogger logger = default) 14 | { 15 | if (logger == null) logger = NoopLogger.Instance; 16 | 17 | var extension = Path.GetExtension(target) ?? throw new ArgumentNullException(nameof(target)); 18 | if (extension.Length > 0) 19 | { 20 | var file = new FileInfo(target); 21 | 22 | switch (extension) 23 | { 24 | case ".sln": 25 | { 26 | var solution = SolutionReader.Instance.Read(file, logger); 27 | foreach (var project in self.ProcessSolutionFile(solution)) 28 | { 29 | yield return project; 30 | } 31 | break; 32 | } 33 | case string s when ProjectConverter.ProjectFileMappings.ContainsKey(extension): 34 | { 35 | yield return self.ProcessProjectFile(file, null); 36 | break; 37 | } 38 | default: 39 | logger.LogCritical("Please specify a project or solution file."); 40 | break; 41 | } 42 | 43 | yield break; 44 | } 45 | 46 | // Process the only solution in given directory 47 | var solutionFiles = Directory.EnumerateFiles(target, "*.sln", SearchOption.TopDirectoryOnly).ToArray(); 48 | if (solutionFiles.Length == 1) 49 | { 50 | var solution = SolutionReader.Instance.Read(solutionFiles[0], logger); 51 | foreach (var project in self.ProcessSolutionFile(solution)) 52 | { 53 | yield return project; 54 | } 55 | 56 | yield break; 57 | } 58 | 59 | var projectsProcessed = 0; 60 | // Process all csprojs found in given directory 61 | foreach (var fileExtension in ProjectConverter.ProjectFileMappings.Keys) 62 | { 63 | var projectFiles = Directory.EnumerateFiles(target, "*" + fileExtension, SearchOption.AllDirectories).ToArray(); 64 | if (projectFiles.Length == 0) 65 | { 66 | continue; 67 | } 68 | 69 | if (projectFiles.Length > 1) 70 | { 71 | logger.LogInformation($"Multiple project files found under directory {target}:"); 72 | } 73 | 74 | logger.LogInformation(string.Join(Environment.NewLine, projectFiles)); 75 | 76 | foreach (var projectFile in projectFiles) 77 | { 78 | // todo: rewrite both directory enumerations to use FileInfo instead of raw strings 79 | yield return self.ProcessProjectFile(new FileInfo(projectFile), null); 80 | projectsProcessed++; 81 | } 82 | } 83 | 84 | if (projectsProcessed == 0) 85 | { 86 | logger.LogCritical("Please specify a project file."); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Analysis/Diagnostics/W010ConfigurationsMismatchDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using Project2015To2017.Definition; 2 | using Project2015To2017.Reading; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Xml.Linq; 7 | 8 | namespace Project2015To2017.Analysis.Diagnostics 9 | { 10 | public sealed class W010ConfigurationsMismatchDiagnostic : DiagnosticBase 11 | { 12 | /// 13 | public override IReadOnlyList Analyze(Project project) 14 | { 15 | var configurationSet = new HashSet(); 16 | var platformSet = new HashSet(); 17 | 18 | var configurationsFromProperty = ParseFromProperty("Configurations") ?? new[] { "Debug", "Release" }; 19 | var platformsFromProperty = ParseFromProperty("Platforms") ?? new[] { "AnyCPU" }; 20 | 21 | foreach (var configuration in configurationsFromProperty) 22 | { 23 | configurationSet.Add(configuration); 24 | } 25 | 26 | foreach (var platform in platformsFromProperty) 27 | { 28 | platformSet.Add(platform); 29 | } 30 | 31 | var list = new List(); 32 | foreach (var x in project.ProjectDocument.Descendants()) 33 | { 34 | var condition = x.Attribute("Condition"); 35 | if (condition == null) 36 | { 37 | continue; 38 | } 39 | 40 | var conditionValue = condition.Value; 41 | if (!conditionValue.Contains("$(Configuration)") && !conditionValue.Contains("$(Platform)")) 42 | { 43 | continue; 44 | } 45 | 46 | var conditionEvaluated = ConditionEvaluator.GetConditionValues(conditionValue); 47 | 48 | if (conditionEvaluated.TryGetValue("Configuration", out var configurations)) 49 | { 50 | foreach (var configuration in configurations) 51 | { 52 | if (!configurationSet.Contains(configuration)) 53 | { 54 | list.Add( 55 | CreateDiagnosticResult(project, 56 | $"Configuration '{configuration}' is used in project file but not mentioned in $(Configurations).", 57 | project.FilePath) 58 | .LoadLocationFromElement(x)); 59 | } 60 | } 61 | } 62 | 63 | if (conditionEvaluated.TryGetValue("Platform", out var platforms)) 64 | { 65 | foreach (var platform in platforms) 66 | { 67 | if (!platformSet.Contains(platform)) 68 | { 69 | list.Add( 70 | CreateDiagnosticResult(project, 71 | $"Platform '{platform}' is used in project file but not mentioned in $(Platforms).", 72 | project.FilePath) 73 | .LoadLocationFromElement(x)); 74 | } 75 | } 76 | } 77 | } 78 | 79 | return list; 80 | 81 | string[] ParseFromProperty(string name) => project.Property(name) 82 | ?.Value 83 | .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); 84 | } 85 | 86 | public W010ConfigurationsMismatchDiagnostic() : base(10) 87 | { 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 4.0.{build} 2 | skip_tags: true 3 | image: 4 | - Visual Studio 2017 5 | configuration: 6 | - Release 7 | pull_requests: 8 | do_not_increment_build_number: true 9 | skip_branch_with_pr: true 10 | environment: 11 | LOGGER: '/l:"C:\Program Files\AppVeyor\BuildAgent\dotnetcore\Appveyor.MSBuildLogger.dll"' 12 | LIBRARY: './Project2015To2017/Project2015To2017.csproj' 13 | LEGACYCLITOOL: './Project2015To2017.Console/Project2015To2017.Console.csproj' 14 | CLITOOL: './Project2015To2017.Migrate2017.Tool/Project2015To2017.Migrate2017.Tool.csproj' 15 | build_script: 16 | - cmd: dotnet build %LOGGER% -v m -c %configuration% 17 | - cmd: dotnet pack %LIBRARY% %LOGGER% -v m -c %configuration% --no-build 18 | - cmd: dotnet pack %LEGACYCLITOOL% %LOGGER% -v m -c %configuration% --no-build /p:Pack=true 19 | - cmd: dotnet pack %CLITOOL% %LOGGER% -v m -c %configuration% --no-build /p:Pack=true 20 | - cmd: dotnet publish %CLITOOL% %LOGGER% -v m -c %configuration% -f netcoreapp2.1 -o ./out/netcoreapp2.1 --no-build 21 | - cmd: dotnet publish %CLITOOL% %LOGGER% -v m -c %configuration% -f net461 -o ./out/net461 --no-build 22 | after_build: 23 | - cmd: 7z a ./Project2015To2017/out/dotnet-migrate-2017.zip ./Project2015To2017.Migrate2017.Tool/out/* 24 | test_script: 25 | - cmd: dotnet test Project2015To2017.Tests/Project2015To2017.Tests.csproj -c %configuration% --no-build --test-adapter-path:. --logger:Appveyor 26 | 27 | artifacts: 28 | - path: ./Project2015To2017.Console/bin/$(configuration)/Project2015To2017.Cli.**.nupkg 29 | name: global-tool-legacy 30 | - path: ./Project2015To2017.Migrate2017.Tool/bin/$(configuration)/Project2015To2017.Migrate2017.Tool.**.nupkg 31 | name: global-tool 32 | - path: ./Project2015To2017/out/dotnet-migrate-2017.zip 33 | name: out-zip 34 | - path: Project2015To2017\bin\$(configuration)\Project2015To2017.**.nupkg 35 | name: nupkg 36 | 37 | deploy: 38 | - provider: NuGet 39 | api_key: 40 | secure: i6ZaMLxF/Jbkh7SgDJuFIwpcrXl1BcNI5vd5Re33EEQV7PrZ71F6MpuIDF7glY/o 41 | skip_symbols: false 42 | artifact: nupkg 43 | 44 | - provider: NuGet 45 | api_key: 46 | secure: +o0/XtevmiegEOJLY9u6bm57gbFvGT/0mraL78vBCtEjNkdjWkZEQGxiL//UP88S 47 | skip_symbols: false 48 | artifact: global-tool-legacy 49 | 50 | - provider: NuGet 51 | api_key: 52 | secure: 5MwxrP9zjSXiJ2WUmdQSR98h+ys28LHiDzVBaFmrDCDvBhvNf+QqDaNDs/+LhVQU 53 | skip_symbols: false 54 | artifact: global-tool 55 | 56 | - provider: GitHub 57 | release: dotnet-migrate-2017-v$(APPVEYOR_BUILD_VERSION) 58 | description: 'Automatically built release v$(APPVEYOR_BUILD_VERSION).' 59 | auth_token: 60 | secure: "LYFGDMnT4oJLpCsZUkhL/oTnG3ZpQnsjaTc10DfTf/jbkXtzctRkXEu2ea3YnyqP" 61 | artifact: out-zip 62 | draft: true 63 | prerelease: true 64 | on: 65 | branch: master # deploy on tag push only 66 | 67 | # build cache to preserve files/folders between builds 68 | cache: 69 | - packages -> **\packages.config # preserve "packages" directory in the root of build folder but will reset it if packages.config is modified 70 | - '%LocalAppData%\NuGet\Cache' -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Project2015To2017Tests.TestFiles.FileInclusion { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Project2015To2017Tests.TestFiles.FileInclusion.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Project2015To2017Tests.TestFiles.FileInclusion.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Project2015To2017Tests.TestFiles.FileInclusion.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/SourceFileWithDesigner.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Project2015To2017Tests.TestFiles.FileInclusion { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class SourceFileWithDesigner { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal SourceFileWithDesigner() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Project2015To2017Tests.TestFiles.FileInclusion.SourceFileWithDesigner", typeof(SourceFileWithDesigner).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/ProgramTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Project2015To2017.Reading; 6 | using Project2015To2017.Writing; 7 | 8 | namespace Project2015To2017.Tests 9 | { 10 | [TestClass] 11 | public class ProgramTest 12 | { 13 | [TestMethod] 14 | public void ValidatesFileIsWritable() 15 | { 16 | var projectFile = Path.Combine("TestFiles", "OtherTestProjects", "readonly.testcsproj"); 17 | var copiedProjectFile = Path.Combine("TestFiles", "OtherTestProjects", $"{nameof(ValidatesFileIsWritable)}.readonly"); 18 | 19 | if (File.Exists(copiedProjectFile)) 20 | { 21 | File.SetAttributes(copiedProjectFile, FileAttributes.Normal); 22 | File.Delete(copiedProjectFile); 23 | } 24 | 25 | try 26 | { 27 | File.Copy(projectFile, copiedProjectFile); 28 | 29 | File.SetAttributes(copiedProjectFile, FileAttributes.ReadOnly); 30 | 31 | var logger = new DummyLogger(); 32 | var project = new ProjectReader(logger).Read(copiedProjectFile); 33 | 34 | Assert.IsFalse(logger.LogEntries.Any(x => x.Contains("Aborting as could not write to project file"))); 35 | 36 | var writer = new ProjectWriter(logger); 37 | 38 | writer.Write(project, makeBackups: false); 39 | 40 | Assert.IsTrue(logger.LogEntries.Any(x => x.Contains("Aborting as could not write to project file"))); 41 | } 42 | finally 43 | { 44 | if (File.Exists(copiedProjectFile)) 45 | { 46 | File.SetAttributes(copiedProjectFile, FileAttributes.Normal); 47 | File.Delete(copiedProjectFile); 48 | } 49 | } 50 | } 51 | 52 | [TestMethod] 53 | public void ValidatesFileIsWritableAfterCheckout() 54 | { 55 | var logs = new List(); 56 | 57 | var projectFile = Path.Combine("TestFiles", "OtherTestProjects", "readonly.testcsproj"); 58 | var copiedProjectFile = Path.Combine("TestFiles", "OtherTestProjects", $"{nameof(ValidatesFileIsWritableAfterCheckout)}.readonly"); 59 | 60 | if (File.Exists(copiedProjectFile)) 61 | { 62 | File.SetAttributes(copiedProjectFile, FileAttributes.Normal); 63 | File.Delete(copiedProjectFile); 64 | } 65 | 66 | try 67 | { 68 | File.Copy(projectFile, copiedProjectFile); 69 | 70 | File.SetAttributes(copiedProjectFile, FileAttributes.ReadOnly); 71 | 72 | var project = new ProjectReader().Read(copiedProjectFile); 73 | 74 | var projectWriter = new ProjectWriter(_ => { }, file => File.SetAttributes(file.FullName, FileAttributes.Normal)); 75 | 76 | projectWriter.Write(project, makeBackups: false); 77 | 78 | Assert.IsFalse(logs.Any(x => x.Contains("Aborting as could not write to project file"))); 79 | } 80 | finally 81 | { 82 | if (File.Exists(copiedProjectFile)) 83 | { 84 | File.SetAttributes(copiedProjectFile, FileAttributes.Normal); 85 | File.Delete(copiedProjectFile); 86 | } 87 | } 88 | } 89 | 90 | [TestMethod] 91 | public void ValidatesFileExists() 92 | { 93 | Assert.IsFalse(ProjectConverter.Validate(new FileInfo(Path.Combine("TestFiles", "OtherTestProjects", "nonexistent.testcsproj")), NoopLogger.Instance)); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/NumericComparisonExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | 6 | namespace Project2015To2017.Reading.Conditionals 7 | { 8 | /// 9 | /// Evaluates a numeric comparison, such as less-than, or greater-or-equal-than 10 | /// Does not update conditioned properties table. 11 | /// 12 | internal abstract class NumericComparisonExpressionNode : OperatorExpressionNode 13 | { 14 | /// 15 | /// Compare numbers 16 | /// 17 | protected abstract bool Compare(double left, double right); 18 | 19 | /// 20 | /// Compare Versions. This is only intended to compare version formats like "A.B.C.D" which can otherwise not be compared numerically 21 | /// 22 | protected abstract bool Compare(Version left, Version right); 23 | 24 | /// 25 | /// Compare mixed numbers and Versions 26 | /// 27 | protected abstract bool Compare(Version left, double right); 28 | 29 | /// 30 | /// Compare mixed numbers and Versions 31 | /// 32 | protected abstract bool Compare(double left, Version right); 33 | 34 | /// 35 | /// Evaluate as boolean 36 | /// 37 | internal override bool BoolEvaluate(IConditionEvaluationState state) 38 | { 39 | bool isLeftNum = this.LeftChild.CanNumericEvaluate(state); 40 | bool isLeftVersion = this.LeftChild.CanVersionEvaluate(state); 41 | bool isRightNum = this.RightChild.CanNumericEvaluate(state); 42 | bool isRightVersion = this.RightChild.CanVersionEvaluate(state); 43 | bool isNumeric = isLeftNum && isRightNum; 44 | bool isVersion = isLeftVersion && isRightVersion; 45 | 46 | // If the values identify as numeric, make that comparison instead of the Version comparison since numeric has a stricter definition 47 | if (isNumeric) 48 | { 49 | return Compare(this.LeftChild.NumericEvaluate(state), this.RightChild.NumericEvaluate(state)); 50 | } 51 | else if (isVersion) 52 | { 53 | return Compare(this.LeftChild.VersionEvaluate(state), this.RightChild.VersionEvaluate(state)); 54 | } 55 | 56 | // If the numbers are of a mixed type, call that specific Compare method 57 | if (isLeftNum && isRightVersion) 58 | { 59 | return Compare(this.LeftChild.NumericEvaluate(state), this.RightChild.VersionEvaluate(state)); 60 | } 61 | else if (isLeftVersion && isRightNum) 62 | { 63 | return Compare(this.LeftChild.VersionEvaluate(state), this.RightChild.NumericEvaluate(state)); 64 | } 65 | 66 | // Throw error here as this code should be unreachable 67 | ErrorUtilities.ThrowInternalErrorUnreachable(); 68 | return false; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/AssemblyInfoReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Xml.Linq; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using Microsoft.Extensions.Logging; 8 | using Project2015To2017.Definition; 9 | 10 | namespace Project2015To2017.Reading 11 | { 12 | public sealed class AssemblyInfoReader 13 | { 14 | private readonly ILogger logger; 15 | 16 | public AssemblyInfoReader(ILogger logger) 17 | { 18 | this.logger = logger; 19 | } 20 | 21 | public AssemblyAttributes Read(Project project) 22 | { 23 | var projectPath = project.ProjectFolder.FullName; 24 | 25 | var compileElements = project.ItemGroups 26 | .SelectMany(x => x.Descendants(project.XmlNamespace + "Compile")) 27 | .ToList(); 28 | 29 | var missingCount = 0u; 30 | 31 | var assemblyInfoFiles = compileElements 32 | .Attributes("Include") 33 | .Select(x => x.Value.ToString()) 34 | .Where(x => !x.Contains("*")) 35 | .Select(x => 36 | { 37 | var filePath = Path.IsPathRooted(x) ? x : Path.GetFullPath(Path.Combine(projectPath, x)); 38 | return new FileInfo(Extensions.MaybeAdjustFilePath(filePath, projectPath)); 39 | } 40 | ) 41 | .Where(IsAssemblyInfoFile) 42 | .Where(x => 43 | { 44 | if (x.Exists) 45 | { 46 | return true; 47 | } 48 | 49 | missingCount++; 50 | this.logger.LogWarning($@"AssemblyInfo file '{x.FullName}' not found"); 51 | return false; 52 | } 53 | ) 54 | .ToList(); 55 | 56 | if (assemblyInfoFiles.Count == 0) 57 | { 58 | this.logger.LogWarning($@"Could not read from assemblyinfo, no assemblyinfo file found"); 59 | 60 | return null; 61 | } 62 | 63 | if (assemblyInfoFiles.Count + missingCount > 1) 64 | { 65 | var fileList = string.Join($",{Environment.NewLine}", assemblyInfoFiles.Select(x => x.FullName)); 66 | this.logger.LogWarning($@"Could not read from assemblyinfo, multiple assemblyinfo files found:{Environment.NewLine}{fileList}"); 67 | 68 | project.HasMultipleAssemblyInfoFiles = true; 69 | return null; 70 | } 71 | 72 | var assemblyInfoFile = assemblyInfoFiles[0]; 73 | var assemblyInfoFileName = assemblyInfoFile.FullName; 74 | 75 | this.logger.LogInformation($"Reading assembly info from {assemblyInfoFileName}."); 76 | 77 | var text = File.ReadAllText(assemblyInfoFileName); 78 | 79 | var tree = CSharpSyntaxTree.ParseText(text); 80 | 81 | var root = (CompilationUnitSyntax) tree.GetRoot(); 82 | 83 | var assemblyAttributes = new AssemblyAttributes 84 | { 85 | File = assemblyInfoFile, 86 | FileContents = root 87 | }; 88 | 89 | return assemblyAttributes; 90 | } 91 | 92 | private static bool IsAssemblyInfoFile(FileInfo x) 93 | { 94 | var nameLower = x.Name.ToLower(); 95 | if (nameLower == "assemblyinfo.cs") 96 | return true; 97 | return nameLower.EndsWith(".cs") && nameLower.Contains("assemblyinfo"); 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/GenericExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | 6 | namespace Project2015To2017.Reading.Conditionals 7 | { 8 | /// 9 | /// Base class for all expression nodes. 10 | /// 11 | public abstract class GenericExpressionNode 12 | { 13 | internal abstract bool CanBoolEvaluate(IConditionEvaluationState state); 14 | internal abstract bool CanNumericEvaluate(IConditionEvaluationState state); 15 | internal abstract bool CanVersionEvaluate(IConditionEvaluationState state); 16 | internal abstract bool BoolEvaluate(IConditionEvaluationState state); 17 | internal abstract double NumericEvaluate(IConditionEvaluationState state); 18 | internal abstract Version VersionEvaluate(IConditionEvaluationState state); 19 | 20 | /// 21 | /// Returns true if this node evaluates to an empty string, 22 | /// otherwise false. 23 | /// (It may be cheaper to determine whether an expression will evaluate 24 | /// to empty than to fully evaluate it.) 25 | /// Implementations should cache the result so that calls after the first are free. 26 | /// 27 | internal virtual bool EvaluatesToEmpty(IConditionEvaluationState state) 28 | { 29 | return false; 30 | } 31 | 32 | /// 33 | /// Value after any item and property expressions are expanded 34 | /// 35 | /// 36 | internal abstract string GetExpandedValue(IConditionEvaluationState state); 37 | 38 | /// 39 | /// Value before any item and property expressions are expanded 40 | /// 41 | /// 42 | internal abstract string GetUnexpandedValue(IConditionEvaluationState state); 43 | 44 | /// 45 | /// If any expression nodes cache any state for the duration of evaluation, 46 | /// now's the time to clean it up 47 | /// 48 | internal abstract void ResetState(); 49 | 50 | /// 51 | /// The main evaluate entry point for expression trees 52 | /// 53 | /// 54 | /// 55 | internal bool Evaluate(IConditionEvaluationState state) 56 | { 57 | return BoolEvaluate(state); 58 | } 59 | 60 | /// 61 | /// Get display string for this node for use in the debugger. 62 | /// 63 | internal virtual string DebuggerDisplay { get; } 64 | 65 | 66 | #region REMOVE_COMPAT_WARNING 67 | internal virtual bool PossibleAndCollision 68 | { 69 | set { /* do nothing */ } 70 | get { return false; } 71 | } 72 | 73 | internal virtual bool PossibleOrCollision 74 | { 75 | set { /* do nothing */ } 76 | get { return false; } 77 | } 78 | 79 | internal abstract bool DetectOr(); 80 | internal abstract bool DetectAnd(); 81 | #endregion 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/NuGetPackageTransformationTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Project2015To2017.Definition; 5 | using Project2015To2017.Reading; 6 | using Project2015To2017.Transforms; 7 | 8 | namespace Project2015To2017.Tests 9 | { 10 | [TestClass] 11 | public class NuGetPackageTransformationTest 12 | { 13 | [TestMethod] 14 | public void ConvertsNuspec() 15 | { 16 | var project = new ProjectReader().Read(Path.Combine("TestFiles", "OtherTestProjects", "net46console.testcsproj")); 17 | 18 | project.AssemblyAttributes = 19 | new AssemblyAttributes { 20 | InformationalVersion = "7.0", 21 | Version = "8.0", 22 | FileVersion = "9.0", 23 | Copyright = "copyright from assembly", 24 | Description = "description from assembly", 25 | Company = "assembly author" 26 | }; 27 | 28 | new NuGetPackageTransformation().Transform(project); 29 | 30 | var transformedPackageConfig = project.PackageConfiguration; 31 | 32 | Assert.IsNull(transformedPackageConfig.Id); 33 | Assert.AreEqual("7.0", transformedPackageConfig.Version); 34 | Assert.AreEqual("some author", transformedPackageConfig.Authors); 35 | Assert.AreEqual("copyright from assembly", transformedPackageConfig.Copyright); 36 | Assert.IsTrue(transformedPackageConfig.RequiresLicenseAcceptance); 37 | Assert.AreEqual("a nice description.", transformedPackageConfig.Description); 38 | Assert.AreEqual("some tags API", transformedPackageConfig.Tags); 39 | Assert.AreEqual("someurl", transformedPackageConfig.LicenseUrl); 40 | Assert.AreEqual("Some long\n text\n with newlines", transformedPackageConfig.ReleaseNotes.Trim()); 41 | } 42 | 43 | [TestMethod] 44 | public void ConvertsNuspecWithNoInformationalVersion() 45 | { 46 | var project = new ProjectReader().Read(Path.Combine("TestFiles", "OtherTestProjects", "net46console.testcsproj")); 47 | 48 | project.AssemblyAttributes = 49 | new AssemblyAttributes { 50 | Version = "8.0", 51 | FileVersion = "9.0", 52 | Copyright = "copyright from assembly", 53 | Description = "description from assembly", 54 | Company = "assembly author" 55 | }; 56 | 57 | new NuGetPackageTransformation().Transform(project); 58 | 59 | var transformedPackageConfig = project.PackageConfiguration; 60 | 61 | Assert.AreEqual("8.0", transformedPackageConfig.Version); 62 | } 63 | 64 | [TestMethod] 65 | public void ConvertsDependencies() 66 | { 67 | var project = new ProjectReader().Read(Path.Combine("TestFiles", "OtherTestProjects", "net46console.testcsproj")); 68 | 69 | project.PackageReferences = new[] 70 | { 71 | new PackageReference 72 | { 73 | Id = "Newtonsoft.Json", 74 | Version = "10.0.2" 75 | }, 76 | new PackageReference 77 | { 78 | Id = "Other.Package", 79 | Version = "1.0.2" 80 | } 81 | }; 82 | 83 | new NuGetPackageTransformation().Transform(project); 84 | 85 | Assert.AreEqual("[10.0.2,11)", project.PackageReferences.Single(x => x.Id == "Newtonsoft.Json").Version); 86 | Assert.AreEqual("1.0.2", project.PackageReferences.Single(x => x.Id == "Other.Package").Version); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestFiles/FileInclusion/fileExclusion.testcsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {62CAD5A7-4966-4289-9203-E6843CDEE4C6} 8 | Exe 9 | Properties 10 | Net46Console 11 | Net46Console 12 | v4.6 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\Microsoft.Owin.3.1.0\lib\net45\Microsoft.Owin.dll 38 | True 39 | 40 | 41 | ..\packages\Microsoft.Owin.Host.SystemWeb.3.1.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll 42 | True 43 | 44 | 45 | ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll 46 | True 47 | 48 | 49 | ..\packages\Owin.1.0\lib\net40\Owin.dll 50 | True 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Transforms/NuGetPackageTransformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Extensions.Logging; 5 | using Project2015To2017.Definition; 6 | 7 | namespace Project2015To2017.Transforms 8 | { 9 | public sealed class NuGetPackageTransformation 10 | : ITransformationWithTargetMoment, ILegacyOnlyProjectTransformation 11 | { 12 | public void Transform(Project definition) 13 | { 14 | if (definition.PackageConfiguration == null) 15 | { 16 | return; 17 | } 18 | 19 | var packageConfig = PopulatePlaceHolders(definition); 20 | 21 | ConstrainPackageReferences(definition.PackageReferences, packageConfig); 22 | 23 | definition.PackageConfiguration = packageConfig; 24 | } 25 | 26 | private static void ConstrainPackageReferences(IReadOnlyList rawPackageReferences, PackageConfiguration packageConfig) 27 | { 28 | var dependencies = packageConfig.Dependencies; 29 | if (dependencies == null || rawPackageReferences == null) 30 | { 31 | return; 32 | } 33 | 34 | var packageIdConstraints = dependencies.Select(dependency => new 35 | { 36 | PackageId = dependency.Attribute("id").Value, 37 | Version = dependency.Attribute("version").Value 38 | }).ToArray(); 39 | 40 | foreach (var packageReference in rawPackageReferences) 41 | { 42 | var matchingPackage = packageIdConstraints.SingleOrDefault(dependency => packageReference.Id.Equals(dependency.PackageId, StringComparison.OrdinalIgnoreCase)); 43 | 44 | if (matchingPackage != null) 45 | { 46 | packageReference.Version = matchingPackage.Version; 47 | } 48 | } 49 | } 50 | 51 | private PackageConfiguration PopulatePlaceHolders(Project project) 52 | { 53 | var rawPackageConfig = project.PackageConfiguration; 54 | var assemblyAttributes = project.AssemblyAttributes; 55 | 56 | return new PackageConfiguration 57 | { 58 | //Id does not need to be specified in new project format if it is just the same as the assembly name 59 | Id = rawPackageConfig.Id == "$id$" ? null : rawPackageConfig.Id, 60 | Version = PopulatePlaceHolder("version", rawPackageConfig.Version, assemblyAttributes.InformationalVersion ?? assemblyAttributes.Version), 61 | Authors = PopulatePlaceHolder("author", rawPackageConfig.Authors, assemblyAttributes.Company), 62 | Description = PopulatePlaceHolder("description", rawPackageConfig.Description, assemblyAttributes.Description), 63 | Copyright = PopulatePlaceHolder("copyright", rawPackageConfig.Copyright, assemblyAttributes.Copyright), 64 | LicenseUrl = rawPackageConfig.LicenseUrl, 65 | ProjectUrl = rawPackageConfig.ProjectUrl, 66 | IconUrl = rawPackageConfig.IconUrl, 67 | Tags = rawPackageConfig.Tags, 68 | ReleaseNotes = rawPackageConfig.ReleaseNotes, 69 | RequiresLicenseAcceptance = rawPackageConfig.RequiresLicenseAcceptance, 70 | Dependencies = rawPackageConfig.Dependencies, 71 | NuspecFile = rawPackageConfig.NuspecFile 72 | }; 73 | } 74 | 75 | private string PopulatePlaceHolder(string placeHolder, string nuSpecValue, string assemblyAttributeValue) 76 | { 77 | if (nuSpecValue == $"${placeHolder}$") 78 | { 79 | return assemblyAttributeValue ?? nuSpecValue; 80 | } 81 | 82 | return nuSpecValue; 83 | } 84 | 85 | public TargetTransformationExecutionMoment ExecutionMoment => 86 | TargetTransformationExecutionMoment.Early; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/UnsupportedProjectTypesTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Xml.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Project2015To2017.Definition; 6 | 7 | namespace Project2015To2017.Tests 8 | { 9 | [TestClass] 10 | public class UnsupportedProjectTypesTest 11 | { 12 | /// 13 | /// Run test cases 14 | /// 15 | /// 16 | /// 17 | /// 18 | [DataRow("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}", "ASP.NET 5", true)] 19 | [DataRow("{349C5851-65DF-11DA-9384-00065B846F21};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}", "ASP.NET MVC5", true)] 20 | [DataRow("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}", "C# only", false)] 21 | [DataRow("", "No ProjectTypeGuids tag", false)] 22 | [TestMethod] 23 | public void CheckAnUnsupportedProjectTypeReturnsCorrectResult(string guidTypes, string testCase, bool expected) 24 | { 25 | var xmlDocument = CreateTestProject("ProjectTypeGuids", guidTypes); 26 | 27 | var actual = UnsupportedProjectTypes.IsUnsupportedProjectType(xmlDocument); 28 | 29 | Assert.AreEqual(expected, actual, $"Failed for {testCase}: expected {expected} but returned {actual}"); 30 | } 31 | 32 | /// 33 | /// Run test cases 34 | /// 35 | /// 36 | /// 37 | /// 38 | [DataRow("WindowsForms", "WinForms", false)] 39 | [DataRow("", "Other", false)] 40 | [TestMethod] 41 | public void CheckAnUnsupportedProjectOutputReturnsCorrectResult(string outputType, string testCase, bool expected) 42 | { 43 | var xmlDocument = CreateTestProject("MyType", outputType); 44 | 45 | var actual = UnsupportedProjectTypes.IsUnsupportedProjectType(xmlDocument); 46 | 47 | Assert.AreEqual(expected, actual, $"Failed for {testCase}: expected {expected} but returned {actual}"); 48 | } 49 | 50 | [TestMethod] 51 | public void IsUnsupportedProjectType_ThrowsExceptionIfXDocumentIsNull() 52 | { 53 | Assert.ThrowsException(() => 54 | { 55 | UnsupportedProjectTypes.IsUnsupportedProjectType(null); 56 | }); 57 | } 58 | 59 | /// 60 | /// Create a test case using the given element name + value 61 | /// 62 | /// element name to add to PropertyGroup 63 | /// value to set 64 | /// 65 | private static Project CreateTestProject(string elementName, string value) 66 | { 67 | // parse empty template 68 | var xmlDocument = XDocument.Parse(template); 69 | if (!string.IsNullOrWhiteSpace(value)) 70 | { 71 | var propertyGroup = xmlDocument.Descendants(Project.XmlLegacyNamespace + "PropertyGroup").First(); 72 | propertyGroup.Add(new XElement(Project.XmlLegacyNamespace + elementName, value)); 73 | } 74 | return new Project { ProjectDocument = xmlDocument }; 75 | } 76 | 77 | /// 78 | /// Very basic proj template to create XDocument 79 | /// 80 | const string template = "" + 81 | "" + 82 | " " + 83 | " {104B8196-D5BC-4901-B00C-FA065F5CEAD1}" + 84 | " " + 85 | ""; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Definition/Project.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Xml.Linq; 6 | using NuGet.Configuration; 7 | 8 | namespace Project2015To2017.Definition 9 | { 10 | public sealed class Project 11 | { 12 | public static readonly XNamespace XmlLegacyNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; 13 | public XNamespace XmlNamespace => this.IsModernProject ? XNamespace.None : XmlLegacyNamespace; 14 | public bool IsModernProject { get; set; } 15 | 16 | public IReadOnlyList AssemblyReferences { get; set; } 17 | public IReadOnlyList ProjectReferences { get; set; } 18 | public IReadOnlyList PackageReferences { get; set; } 19 | public PackageConfiguration PackageConfiguration { get; set; } 20 | public AssemblyAttributes AssemblyAttributes { get; set; } 21 | public IReadOnlyList PropertyGroups { get; set; } 22 | public IReadOnlyList Imports { get; set; } 23 | public IReadOnlyList Targets { get; set; } 24 | public IReadOnlyList BuildEvents { get; set; } 25 | public IReadOnlyList Configurations { get; set; } 26 | public IReadOnlyList Platforms { get; set; } 27 | public IList ItemGroups { get; set; } 28 | 29 | public XDocument ProjectDocument { get; set; } 30 | public string ProjectName { get; set; } 31 | 32 | public IList TargetFrameworks { get; } = new List(); 33 | public bool AppendTargetFrameworkToOutputPath { get; set; } = true; 34 | public ApplicationType Type { get; set; } 35 | public FileInfo FilePath { get; set; } 36 | public string CodeFileExtension { get; set; } 37 | public DirectoryInfo ProjectFolder => this.FilePath.Directory; 38 | public Guid? ProjectGuid { get; set; } 39 | 40 | public bool HasMultipleAssemblyInfoFiles { get; set; } 41 | 42 | /// 43 | /// Files or folders that should be deleted as part of the conversion 44 | /// 45 | public IReadOnlyList Deletions { get; set; } 46 | 47 | /// 48 | /// The directory where NuGet stores its extracted packages for the project. 49 | /// In general this is the 'packages' folder within the parent solution, but 50 | /// it can be overridden, which is accounted for here. 51 | /// 52 | public DirectoryInfo NuGetPackagesPath 53 | { 54 | get 55 | { 56 | var projectFolder = this.ProjectFolder.FullName; 57 | 58 | var nuGetSettings = Settings.LoadDefaultSettings(projectFolder); 59 | var repositoryPathSetting = SettingsUtility.GetRepositoryPath(nuGetSettings); 60 | 61 | //return the explicitly set path, or if there isn't one, then use the solution's path if one was provided. 62 | //Otherwise assume a solution is one level above the project and therefore so is the 'packages' folder 63 | if (repositoryPathSetting != null) 64 | { 65 | return new DirectoryInfo(repositoryPathSetting); 66 | } 67 | 68 | if (this.Solution != null) 69 | { 70 | return this.Solution.NuGetPackagesPath; 71 | } 72 | 73 | var path = Path.GetFullPath(Path.Combine(projectFolder, "..", "packages")); 74 | 75 | return new DirectoryInfo(path); 76 | } 77 | } 78 | 79 | public FileInfo PackagesConfigFile { get; set; } 80 | 81 | /// 82 | /// The solution in which this project was found, if any. 83 | /// 84 | public Solution Solution { get; set; } 85 | 86 | public IReadOnlyList AssemblyAttributeProperties { get; set; } = Array.Empty(); 87 | 88 | public bool IsWindowsFormsProject { get; set; } 89 | public bool IsWindowsPresentationFoundationProject { get; set; } 90 | 91 | public IReadOnlyList IntermediateOutputPaths { get; set; } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/NuSpecReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Xml.Linq; 5 | using Microsoft.Extensions.Logging; 6 | using Project2015To2017.Definition; 7 | 8 | namespace Project2015To2017.Reading 9 | { 10 | public sealed class NuSpecReader 11 | { 12 | private readonly ILogger logger; 13 | 14 | public NuSpecReader(ILogger logger) 15 | { 16 | this.logger = logger; 17 | } 18 | 19 | public PackageConfiguration Read(FileInfo projectFile) 20 | { 21 | var nuspecFiles = projectFile.Directory 22 | .EnumerateFiles("*.nuspec", SearchOption.TopDirectoryOnly) 23 | .ToArray(); 24 | 25 | if (nuspecFiles.Length == 0) 26 | { 27 | this.logger.LogDebug("No nuspec found, skipping package configuration."); 28 | return null; 29 | } 30 | 31 | if (nuspecFiles.Length > 1) 32 | { 33 | this.logger.LogError(@"Could not read from nuspec, multiple nuspecs found."); 34 | foreach (var item in nuspecFiles.Select(x => x.FullName)) 35 | { 36 | this.logger.LogInformation(item); 37 | } 38 | return null; 39 | } 40 | 41 | var nuspecFile = nuspecFiles[0]; 42 | 43 | this.logger.LogInformation($"Reading package info from nuspec {nuspecFile.FullName}."); 44 | 45 | XDocument nuspec; 46 | using (var filestream = File.Open(nuspecFile.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 47 | { 48 | nuspec = XDocument.Load(filestream); 49 | } 50 | 51 | var namespaces = new XNamespace[] 52 | { 53 | "", 54 | "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", 55 | "http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd", 56 | "http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd" 57 | }; 58 | 59 | var packageConfig = namespaces 60 | .Select(ns => ExtractPackageConfiguration(nuspec, ns)) 61 | .SingleOrDefault(config => config != null); 62 | 63 | if (packageConfig == null) 64 | { 65 | this.logger.LogError("Error reading package info from nuspec."); 66 | return null; 67 | } 68 | 69 | packageConfig.NuspecFile = nuspecFile; 70 | 71 | return packageConfig; 72 | } 73 | 74 | private PackageConfiguration ExtractPackageConfiguration( 75 | XDocument nuspec, XNamespace ns 76 | ) 77 | { 78 | var metadata = nuspec?.Element(ns + "package")?.Element(ns + "metadata"); 79 | 80 | if (metadata == null) 81 | { 82 | return null; 83 | } 84 | 85 | var id = metadata.Element(ns + "id")?.Value; 86 | 87 | var version = metadata.Element(ns + "version")?.Value; 88 | 89 | var dependencies = metadata.Element(ns + "dependencies") 90 | ?.Elements(ns + "dependency") 91 | .ToList(); 92 | 93 | var packageConfig = new PackageConfiguration { 94 | Id = id, 95 | Version = version, 96 | Authors = GetElement(metadata, ns + "authors"), 97 | Description = GetElement(metadata, ns + "description"), 98 | Copyright = GetElement(metadata, ns + "copyright"), 99 | LicenseUrl = GetElement(metadata, ns + "licenseUrl"), 100 | ProjectUrl = GetElement(metadata, ns + "projectUrl"), 101 | IconUrl = GetElement(metadata, ns + "iconUrl"), 102 | Tags = GetElement(metadata, ns + "tags"), 103 | ReleaseNotes = GetElement(metadata, ns + "releaseNotes"), 104 | RequiresLicenseAcceptance = metadata.Element(ns + "requireLicenseAcceptance")?.Value != null && bool.Parse(metadata.Element(ns + "requireLicenseAcceptance")?.Value), 105 | Dependencies = dependencies 106 | }; 107 | 108 | return packageConfig; 109 | } 110 | 111 | private static string GetElement(XElement metadata, XName elementName) 112 | { 113 | var value = metadata.Element(elementName)?.Value; 114 | 115 | return value; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/bpo5n2yehpqrxbc4?svg=true)](https://ci.appveyor.com/project/hvanbakel/csprojtovs2017) 2 | [![NuGet Version](https://img.shields.io/nuget/v/Project2015To2017.svg?label=Nupkg%20Version)](https://www.nuget.org/packages/Project2015To2017) 3 | [![NuGet Downloads](https://img.shields.io/nuget/dt/Project2015To2017.svg?label=Nupkg%20Downloads)](https://www.nuget.org/packages/Project2015To2017) 4 | [![Global Tool NuGet Version](https://img.shields.io/nuget/v/Project2015To2017.Cli.svg?label=Global%20Tool%20Version)](https://www.nuget.org/packages/Project2015To2017.Cli) 5 | [![Global Tool NuGet Downloads](https://img.shields.io/nuget/dt/Project2015To2017.Cli.svg?label=Global%20Tool%20Downloads)](https://www.nuget.org/packages/Project2015To2017.Cli) 6 | 7 | # Convert your old project files to the new 2017 format 8 | With the introduction of Visual Studio 2017, Microsoft added some optimizations to how a project file can be set up. However, no tooling was made available that performed this conversion as it was not necessary to do since Visual Studio 2017 would work with the old format too. 9 | 10 | This project converts an existing csproj to the new format, shortening the project file and using all the nice new features that are part of Visual Studio 2017. 11 | 12 | ## What does it fix? 13 | There are a number of things [that VS2017 handles differently](http://www.natemcmaster.com/blog/2017/03/09/vs2015-to-vs2017-upgrade/) that are performed by this tool: 14 | 1. Include files using a wildcard as opposed to specifying every single file 15 | 2. A more succinct way of defining project references 16 | 3. A more succinct way of handling NuGet package references 17 | 4. Moving some of the attributes that used to be defined in AssemblyInfo.cs into the project file 18 | 5. Defining the NuGet package definition as part of the project file 19 | 20 | ## How it works 21 | ### As a Net Core Global Tool 22 | Assuming you have net core 2.1 installed you can run this on the command line: 23 | `dotnet tool install Project2015To2017.Migrate2017.Tool --global` 24 | This will install the tool for you to use it anywhere you would like. You can then call the tool as shown in the examples below. 25 | 26 | ### As a normal file download 27 | Using the tool is simple, it is a simple command line utility that has a single argument being the project file, solution file or folder you would like to convert. 28 | When you give it a directory path, the tool will discover all csproj files nested in it. 29 | 30 | ### Examples 31 | Below examples are for the global tool, for the normal file just replace `dotnet migrate-2017 migrate` with your executable. 32 | 33 | `dotnet migrate-2017 migrate "D:\Path\To\My\TestProject.csproj"` 34 | 35 | Or 36 | 37 | `dotnet migrate-2017 migrate "D:\Path\To\My\TestProject.sln"` 38 | 39 | Or 40 | 41 | `dotnet migrate-2017 migrate "D:\Path\To\My\Directory"` 42 | 43 | After confirming this is an old style project file, it will start performing the conversion. When it has gathered all the data it needs it first creates a backup of the old files and puts them into a backup folder and then generates a new project file in the new format. 44 | 45 | ## Commands 46 | As a sub command `migrate` being shown above there are 2 more options: 47 | * `evaluate` will run evaluation of projects found given the path specified 48 | * `analyze` will run analyzers to signal issues in the project files without converting 49 | 50 | ## Flags 51 | * `target-frameworks` will set the target framework on the outputted project file 52 | * `force-transformations` allows specifying individual transforms to be run on the projects 53 | * `force` ignores checks like project type being supported and will attempt a conversion regardless 54 | * `keep-assembly-info` instructs the migrate logic to keep the assembly info file 55 | * `old-output-path` will set `AppendTargetFrameworkToOutputPath` in converted project file 56 | * `no-backup` do not create a backup folder (e.g. when your solution is under source control) 57 | -------------------------------------------------------------------------------- /Project2015To2017.Tests/TargetFrameworkReplaceTransformationTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Project2015To2017.Definition; 4 | using Project2015To2017.Transforms; 5 | 6 | namespace Project2015To2017.Tests 7 | { 8 | [TestClass] 9 | public class TargetFrameworkReplaceTransformationTest 10 | { 11 | [TestMethod] 12 | public void HandlesProjectNull() 13 | { 14 | Project project = null; 15 | var targetFrameworks = new List { "netstandard2.0" }; 16 | 17 | var transformation = new TargetFrameworkReplaceTransformation(targetFrameworks); 18 | transformation.Transform(project); 19 | 20 | Assert.IsNull(project); 21 | } 22 | 23 | [TestMethod] 24 | public void HandlesProjectTargetFrameworksEmpty() 25 | { 26 | var project = new Project(); 27 | var targetFrameworks = new List { "netstandard2.0" }; 28 | 29 | var transformation = new TargetFrameworkReplaceTransformation(targetFrameworks); 30 | transformation.Transform(project); 31 | 32 | Assert.AreEqual(1, project.TargetFrameworks.Count); 33 | Assert.AreEqual("netstandard2.0", project.TargetFrameworks[0]); 34 | } 35 | 36 | [TestMethod] 37 | public void HandlesOptionTargetFrameworksNull() 38 | { 39 | var project = new Project 40 | { 41 | TargetFrameworks = { "net46" } 42 | }; 43 | 44 | var transformation = new TargetFrameworkReplaceTransformation(null); 45 | transformation.Transform(project); 46 | 47 | Assert.AreEqual(1, project.TargetFrameworks.Count); 48 | Assert.AreEqual("net46", project.TargetFrameworks[0]); 49 | } 50 | 51 | [TestMethod] 52 | public void HandlesOptionTargetFrameworksEmpty() 53 | { 54 | var project = new Project 55 | { 56 | TargetFrameworks = { "net46" } 57 | }; 58 | 59 | var transformation = new TargetFrameworkReplaceTransformation(new List()); 60 | transformation.Transform(project); 61 | 62 | Assert.AreEqual(1, project.TargetFrameworks.Count); 63 | Assert.AreEqual("net46", project.TargetFrameworks[0]); 64 | } 65 | 66 | [TestMethod] 67 | public void HandlesOptionTargetFrameworks() 68 | { 69 | var project = new Project 70 | { 71 | TargetFrameworks = { "net46" } 72 | }; 73 | var targetFrameworks = new List { "netstandard2.0" }; 74 | 75 | var transformation = new TargetFrameworkReplaceTransformation(targetFrameworks); 76 | transformation.Transform(project); 77 | 78 | Assert.AreEqual(1, project.TargetFrameworks.Count); 79 | Assert.AreEqual("netstandard2.0", project.TargetFrameworks[0]); 80 | } 81 | 82 | [TestMethod] 83 | public void HandlesOptionTargetFrameworksMulti() 84 | { 85 | var project = new Project 86 | { 87 | TargetFrameworks = { "net46" } 88 | }; 89 | var targetFrameworks = new List { "netstandard2.0", "net47" }; 90 | 91 | var transformation = new TargetFrameworkReplaceTransformation(targetFrameworks); 92 | transformation.Transform(project); 93 | 94 | Assert.AreEqual(2, project.TargetFrameworks.Count); 95 | Assert.AreEqual("netstandard2.0", project.TargetFrameworks[0]); 96 | Assert.AreEqual("net47", project.TargetFrameworks[1]); 97 | } 98 | 99 | [TestMethod] 100 | public void HandlesOptionAppendTargetFrameworkToOutputPathTrue() 101 | { 102 | var project = new Project(); 103 | 104 | var transformation = new TargetFrameworkReplaceTransformation(null, true); 105 | transformation.Transform(project); 106 | 107 | Assert.AreEqual(true, project.AppendTargetFrameworkToOutputPath); 108 | } 109 | 110 | [TestMethod] 111 | public void HandlesOptionAppendTargetFrameworkToOutputPathFalse() 112 | { 113 | var project = new Project(); 114 | 115 | var transformation = new TargetFrameworkReplaceTransformation(null, false); 116 | transformation.Transform(project); 117 | 118 | Assert.AreEqual(false, project.AppendTargetFrameworkToOutputPath); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /Project2015To2017.Tests/TestProjectPackageReferenceTransformationTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Project2015To2017.Definition; 5 | using Project2015To2017.Migrate2017.Transforms; 6 | using Project2015To2017.Reading; 7 | 8 | namespace Project2015To2017.Tests 9 | { 10 | [TestClass] 11 | public class TestProjectPackageReferenceTransformationTest 12 | { 13 | [TestMethod] 14 | public void AddsTestPackages() 15 | { 16 | var project = new ProjectReader().Read(Path.Combine("TestFiles", "OtherTestProjects", "net46console.testcsproj")); 17 | 18 | project.Type = ApplicationType.TestProject; 19 | project.TargetFrameworks.Add("net45"); 20 | 21 | var transformation = new TestProjectPackageReferenceTransformation(); 22 | 23 | transformation.Transform(project); 24 | 25 | Assert.AreEqual(10, project.PackageReferences.Count); 26 | Assert.AreEqual(1, project.PackageReferences.Count(x => x.Id == "Microsoft.Owin.Host.HttpListener" && x.Version == "3.1.0")); 27 | Assert.AreEqual(1, project.PackageReferences.Count(x => x.Id == "Microsoft.NET.Test.Sdk" && x.Version == "15.0.0")); 28 | Assert.AreEqual(1, project.PackageReferences.Count(x => x.Id == "AutoMapper" && x.Version == "6.1.1" && x.IsDevelopmentDependency)); 29 | } 30 | 31 | [TestMethod] 32 | public void AcceptsNetStandardFramework() 33 | { 34 | var project = new ProjectReader().Read(Path.Combine("TestFiles", "OtherTestProjects", "net46console.testcsproj")); 35 | 36 | project.Type = ApplicationType.TestProject; 37 | project.TargetFrameworks.Add("netstandard2.0"); 38 | 39 | var transformation = new TestProjectPackageReferenceTransformation(); 40 | 41 | transformation.Transform(project); 42 | 43 | Assert.AreEqual(10, project.PackageReferences.Count); 44 | Assert.AreEqual(1, project.PackageReferences.Count(x => x.Id == "Microsoft.Owin.Host.HttpListener" && x.Version == "3.1.0")); 45 | Assert.AreEqual(1, project.PackageReferences.Count(x => x.Id == "Microsoft.NET.Test.Sdk" && x.Version == "15.0.0")); 46 | Assert.AreEqual(1, project.PackageReferences.Count(x => x.Id == "AutoMapper" && x.Version == "6.1.1" && x.IsDevelopmentDependency)); 47 | } 48 | 49 | [TestMethod] 50 | public void DoesNotAddTestPackagesIfExists() 51 | { 52 | var transformation = new TestProjectPackageReferenceTransformation(); 53 | 54 | var project = new ProjectReader().Read(Path.Combine("TestFiles", "OtherTestProjects", "containsTestSDK.testcsproj")); 55 | 56 | project.TargetFrameworks.Add("net45"); 57 | 58 | transformation.Transform(project); 59 | 60 | Assert.AreEqual(6, project.PackageReferences.Count); 61 | Assert.AreEqual(0, project.PackageReferences.Count(x => x.Id == "MSTest.TestAdapter")); 62 | } 63 | 64 | [TestMethod] 65 | public void TransformsPackages() 66 | { 67 | var project = new ProjectReader().Read(Path.Combine("TestFiles", "OtherTestProjects", "net46console.testcsproj")); 68 | 69 | var transformation = new TestProjectPackageReferenceTransformation(); 70 | 71 | transformation.Transform(project); 72 | 73 | Assert.AreEqual(7, project.PackageReferences.Count); 74 | Assert.AreEqual(2, project.PackageReferences.Count(x => x.IsDevelopmentDependency)); 75 | Assert.AreEqual(1, project.PackageReferences.Count(x => x.Id == "Microsoft.Owin" && x.Version == "3.1.0")); 76 | } 77 | 78 | [TestMethod] 79 | public void HandlesNonXml() 80 | { 81 | var project = new ProjectReader().Read(Path.Combine("TestFiles", "OtherTestProjects", "net46console.testcsproj")); 82 | var transformation = new TestProjectPackageReferenceTransformation(); 83 | 84 | transformation.Transform(project); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Reading/Conditionals/NumericExpressionNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | 7 | namespace Project2015To2017.Reading.Conditionals 8 | { 9 | /// 10 | /// Represents a number - evaluates as numeric. 11 | /// 12 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 13 | internal sealed class NumericExpressionNode : OperandExpressionNode 14 | { 15 | private string _value; 16 | 17 | private NumericExpressionNode() { } 18 | 19 | internal NumericExpressionNode(string value) 20 | { 21 | this._value = value; 22 | } 23 | 24 | /// 25 | /// Evaluate as boolean 26 | /// 27 | internal override bool BoolEvaluate(IConditionEvaluationState state) 28 | { 29 | // Should be unreachable: all calls check CanBoolEvaluate() first 30 | ErrorUtilities.VerifyThrow(false, "Can't evaluate a numeric expression as boolean."); 31 | return false; 32 | } 33 | 34 | /// 35 | /// Evaluate as numeric 36 | /// 37 | internal override double NumericEvaluate(IConditionEvaluationState state) 38 | { 39 | return ConversionUtilities.ConvertDecimalOrHexToDouble(this._value); 40 | } 41 | 42 | /// 43 | /// Evaluate as a Version 44 | /// 45 | internal override Version VersionEvaluate(IConditionEvaluationState state) 46 | { 47 | return Version.Parse(this._value); 48 | } 49 | 50 | /// 51 | /// Whether it can be evaluated as a boolean: never allowed for numerics 52 | /// 53 | internal override bool CanBoolEvaluate(IConditionEvaluationState state) 54 | { 55 | // Numeric expressions are never allowed to be treated as booleans. 56 | return false; 57 | } 58 | 59 | /// 60 | /// Whether it can be evaluated as numeric 61 | /// 62 | internal override bool CanNumericEvaluate(IConditionEvaluationState state) 63 | { 64 | // It is not always possible to numerically evaluate even a numerical expression - 65 | // for example, it may overflow a double. So check here. 66 | return ConversionUtilities.ValidDecimalOrHexNumber(this._value); 67 | } 68 | 69 | /// 70 | /// Whether it can be evaluated as a Version 71 | /// 72 | internal override bool CanVersionEvaluate(IConditionEvaluationState state) 73 | { 74 | // Check if the value can be formatted as a Version number 75 | // This is needed for nodes that identify as Numeric but can't be parsed as numbers (e.g. 8.1.1.0 vs 8.1) 76 | Version unused; 77 | return Version.TryParse(this._value, out unused); 78 | } 79 | 80 | /// 81 | /// Get the unexpanded value 82 | /// 83 | internal override string GetUnexpandedValue(IConditionEvaluationState state) 84 | { 85 | return this._value; 86 | } 87 | 88 | /// 89 | /// Get the expanded value 90 | /// 91 | internal override string GetExpandedValue(IConditionEvaluationState state) 92 | { 93 | return this._value; 94 | } 95 | 96 | /// 97 | /// If any expression nodes cache any state for the duration of evaluation, 98 | /// now's the time to clean it up 99 | /// 100 | internal override void ResetState() 101 | { 102 | } 103 | 104 | internal override string DebuggerDisplay => $"#\"{this._value}\")"; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Project2015To2017.Core/Transforms/PrimaryProjectPropertiesUpdateTransformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Xml.Linq; 5 | using Project2015To2017.Definition; 6 | 7 | namespace Project2015To2017.Transforms 8 | { 9 | public sealed class PrimaryProjectPropertiesUpdateTransformation : ITransformationWithTargetMoment 10 | { 11 | public void Transform(Project project) 12 | { 13 | AddTargetFrameworks(project, project.TargetFrameworks); 14 | 15 | var configurations = project.Configurations ?? Array.Empty(); 16 | if (configurations.Count != 0) 17 | // ignore default "Debug;Release" configuration set 18 | if (configurations.Count != 2 || !configurations.Contains("Debug") || 19 | !configurations.Contains("Release")) 20 | project.SetProperty("Configurations", string.Join(";", configurations)); 21 | 22 | var platforms = project.Platforms ?? Array.Empty(); 23 | if (platforms.Count != 0) 24 | // ignore default "AnyCPU" platform set 25 | if (platforms.Count != 1 || !platforms.Contains("AnyCPU")) 26 | project.SetProperty("Platforms", string.Join(";", platforms)); 27 | 28 | project.SetProperty("AppendTargetFrameworkToOutputPath", 29 | project.AppendTargetFrameworkToOutputPath ? null : "false"); 30 | 31 | project.SetProperty("ExtrasEnableWpfProjectSetup", 32 | project.IsWindowsPresentationFoundationProject ? "true" : null); 33 | project.SetProperty("ExtrasEnableWinFormsProjectSetup", 34 | project.IsWindowsFormsProject ? "true" : null); 35 | 36 | string outputType; 37 | switch (project.Type) 38 | { 39 | case ApplicationType.ConsoleApplication: 40 | outputType = "Exe"; 41 | break; 42 | case ApplicationType.WindowsApplication: 43 | outputType = "WinExe"; 44 | break; 45 | case ApplicationType.AppContainerExe: 46 | outputType = "AppContainerExe"; 47 | break; 48 | default: 49 | outputType = null; 50 | break; 51 | } 52 | 53 | project.SetProperty("OutputType", outputType); 54 | 55 | AddPackageNodes(project); 56 | 57 | var propertyGroup = project.PrimaryPropertyGroup(); 58 | 59 | if (project.BuildEvents != null && project.BuildEvents.Any()) 60 | { 61 | foreach (var buildEvent in project.BuildEvents.Select(Extensions.RemoveAllNamespaces)) 62 | { 63 | propertyGroup.Add(buildEvent); 64 | } 65 | } 66 | } 67 | 68 | private void AddPackageNodes(Project project) 69 | { 70 | var packageConfiguration = project.PackageConfiguration; 71 | if (packageConfiguration == null) 72 | { 73 | return; 74 | } 75 | 76 | //Add those properties not already covered by the project properties 77 | 78 | project.SetProperty("Authors", packageConfiguration.Authors); 79 | project.SetProperty("PackageIconUrl", packageConfiguration.IconUrl); 80 | project.SetProperty("PackageId", packageConfiguration.Id); 81 | project.SetProperty("PackageLicenseUrl", packageConfiguration.LicenseUrl); 82 | project.SetProperty("PackageProjectUrl", packageConfiguration.ProjectUrl); 83 | project.SetProperty("PackageReleaseNotes", packageConfiguration.ReleaseNotes); 84 | project.SetProperty("PackageTags", packageConfiguration.Tags); 85 | 86 | if (packageConfiguration.Id != null && packageConfiguration.Tags == null) project.SetProperty("PackageTags", "Library"); 87 | 88 | if (packageConfiguration.RequiresLicenseAcceptance) 89 | { 90 | project.SetProperty("PackageRequireLicenseAcceptance", "true"); 91 | } 92 | } 93 | 94 | private void AddTargetFrameworks(Project project, IList targetFrameworks) 95 | { 96 | if (targetFrameworks == null || targetFrameworks.Count == 0) 97 | { 98 | return; 99 | } 100 | 101 | var newElement = targetFrameworks.Count > 1 102 | ? new XElement("TargetFrameworks", string.Join(";", targetFrameworks)) 103 | : new XElement("TargetFramework", targetFrameworks[0]); 104 | 105 | project.ReplacePropertiesWith(newElement, 106 | "TargetFrameworks", "TargetFramework", "TargetFrameworkVersion"); 107 | } 108 | 109 | public TargetTransformationExecutionMoment ExecutionMoment => 110 | TargetTransformationExecutionMoment.Late; 111 | } 112 | } -------------------------------------------------------------------------------- /Project2015To2017.Migrate2017.Library/Transforms/XamlPagesTransformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Immutable; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Xml.Linq; 6 | using Microsoft.Extensions.Logging; 7 | using Project2015To2017.Definition; 8 | using Project2015To2017.Transforms; 9 | 10 | namespace Project2015To2017.Migrate2017.Transforms 11 | { 12 | public sealed class XamlPagesTransformation : ILegacyOnlyProjectTransformation 13 | { 14 | public XamlPagesTransformation(ILogger logger = null) 15 | { 16 | this.logger = logger ?? NoopLogger.Instance; 17 | } 18 | 19 | /// 20 | public void Transform(Project definition) 21 | { 22 | if (!definition.IsWindowsPresentationFoundationProject) 23 | { 24 | return; 25 | } 26 | 27 | var removeQueue = definition.ItemGroups 28 | .SelectMany(x => x.Elements()) 29 | .Where(x => XamlPageFilter(x, definition)) 30 | .ToImmutableArray(); 31 | 32 | var count = 0u; 33 | 34 | foreach (var x in removeQueue) 35 | { 36 | x.Remove(); 37 | count++; 38 | } 39 | 40 | if (count == 0) 41 | { 42 | return; 43 | } 44 | 45 | logger.LogDebug($"Removed {count} XAML items thanks to MSBuild.Sdk.Extras defaults"); 46 | } 47 | 48 | private static readonly string[] FilteredTags = {"Page", "ApplicationDefinition", "Compile", "None"}; 49 | private readonly ILogger logger; 50 | 51 | private static bool XamlPageFilter(XElement x, Project definition) 52 | { 53 | var tagLocalName = x.Name.LocalName; 54 | if (!FilteredTags.Contains(tagLocalName)) 55 | { 56 | return false; 57 | } 58 | 59 | var include = x.Attribute("Include")?.Value; 60 | var update = x.Attribute("Update")?.Value; 61 | var link = include ?? update; 62 | 63 | if (link == null) 64 | { 65 | return false; 66 | } 67 | 68 | var projectFolder = definition.ProjectFolder.FullName; 69 | var inProject = Path.GetFullPath(Path.Combine(projectFolder, link)).StartsWith(projectFolder); 70 | if (!inProject) 71 | { 72 | return false; 73 | } 74 | 75 | var fileName = Path.GetFileName(link); 76 | 77 | if (tagLocalName == "Compile") 78 | { 79 | if (fileName.EndsWith(".Designer.cs", StringComparison.OrdinalIgnoreCase)) 80 | { 81 | var autoGen = x.Element("AutoGen")?.Value ?? "true"; 82 | if (!string.Equals(autoGen, "true", StringComparison.OrdinalIgnoreCase)) 83 | { 84 | return false; 85 | } 86 | 87 | var designTime = x.Element("DesignTime")?.Value ?? "true"; 88 | if (!string.Equals(designTime, "true", StringComparison.OrdinalIgnoreCase)) 89 | { 90 | return false; 91 | } 92 | 93 | var designTimeSharedInput = x.Element("DesignTimeSharedInput")?.Value ?? "true"; 94 | if (!string.Equals(designTimeSharedInput, "true", StringComparison.OrdinalIgnoreCase)) 95 | { 96 | return false; 97 | } 98 | 99 | return true; 100 | } 101 | 102 | return link.EndsWith(".xaml.cs", StringComparison.OrdinalIgnoreCase) 103 | && x.Descendants().All(VerifyDefaultXamlCompileItem); 104 | } 105 | 106 | if (update != null) 107 | { 108 | return false; 109 | } 110 | 111 | // from now on (include != null) is invariant 112 | 113 | if (tagLocalName == "None" && fileName.EndsWith(".settings", StringComparison.OrdinalIgnoreCase)) 114 | { 115 | var generator = x.Element("Generator")?.Value ?? "SettingsSingleFileGenerator"; 116 | var lastGenOutput = x.Element("LastGenOutput")?.Value ?? ".cs"; 117 | 118 | return string.Equals(generator, "SettingsSingleFileGenerator") 119 | && lastGenOutput.EndsWith(".cs", StringComparison.OrdinalIgnoreCase); 120 | } 121 | 122 | return include.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase); 123 | 124 | bool VerifyDefaultXamlCompileItem(XElement child) 125 | { 126 | var name = child.Name.LocalName; 127 | 128 | if (child.HasElements) 129 | { 130 | return false; 131 | } 132 | 133 | if (name == "DependentUpon") 134 | { 135 | return true; 136 | } 137 | 138 | return name == "SubType" && string.Equals(child.Value, "Code", StringComparison.OrdinalIgnoreCase); 139 | } 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /Project2015To2017.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project2015To2017.Tests", "Project2015To2017.Tests\Project2015To2017.Tests.csproj", "{1AF63B37-E57F-41F1-B34F-177DBC927D97}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5897C612-2D68-4B30-875D-4D10D387219F}" 9 | ProjectSection(SolutionItems) = preProject 10 | .editorconfig = .editorconfig 11 | appveyor.yml = appveyor.yml 12 | Directory.Build.props = Directory.Build.props 13 | README.md = README.md 14 | EndProjectSection 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project2015To2017", "Project2015To2017\Project2015To2017.csproj", "{88CBADAB-10F5-4C15-A0E9-F0042DD8BC0C}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project2015To2017.Core", "Project2015To2017.Core\Project2015To2017.Core.csproj", "{D9D7DB74-7503-47B3-ABFB-E6B991082E47}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project2015To2017.Console", "Project2015To2017.Console\Project2015To2017.Console.csproj", "{89BDCF6C-0B08-4545-9423-CBBBD826226B}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project2015To2017.Migrate2017.Tool", "Project2015To2017.Migrate2017.Tool\Project2015To2017.Migrate2017.Tool.csproj", "{449F6C91-0C98-40CE-8819-D24BD6FFE2C5}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project2015To2017.Migrate2017.Library", "Project2015To2017.Migrate2017.Library\Project2015To2017.Migrate2017.Library.csproj", "{EBB75840-2B0E-4F56-B702-E256E34A40C1}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {1AF63B37-E57F-41F1-B34F-177DBC927D97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {1AF63B37-E57F-41F1-B34F-177DBC927D97}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {1AF63B37-E57F-41F1-B34F-177DBC927D97}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {1AF63B37-E57F-41F1-B34F-177DBC927D97}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {88CBADAB-10F5-4C15-A0E9-F0042DD8BC0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {88CBADAB-10F5-4C15-A0E9-F0042DD8BC0C}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {88CBADAB-10F5-4C15-A0E9-F0042DD8BC0C}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {88CBADAB-10F5-4C15-A0E9-F0042DD8BC0C}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {D9D7DB74-7503-47B3-ABFB-E6B991082E47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {D9D7DB74-7503-47B3-ABFB-E6B991082E47}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {D9D7DB74-7503-47B3-ABFB-E6B991082E47}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {D9D7DB74-7503-47B3-ABFB-E6B991082E47}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {89BDCF6C-0B08-4545-9423-CBBBD826226B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {89BDCF6C-0B08-4545-9423-CBBBD826226B}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {89BDCF6C-0B08-4545-9423-CBBBD826226B}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {89BDCF6C-0B08-4545-9423-CBBBD826226B}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {449F6C91-0C98-40CE-8819-D24BD6FFE2C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {449F6C91-0C98-40CE-8819-D24BD6FFE2C5}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {449F6C91-0C98-40CE-8819-D24BD6FFE2C5}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {449F6C91-0C98-40CE-8819-D24BD6FFE2C5}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {EBB75840-2B0E-4F56-B702-E256E34A40C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {EBB75840-2B0E-4F56-B702-E256E34A40C1}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {EBB75840-2B0E-4F56-B702-E256E34A40C1}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {EBB75840-2B0E-4F56-B702-E256E34A40C1}.Release|Any CPU.Build.0 = Release|Any CPU 56 | EndGlobalSection 57 | GlobalSection(SolutionProperties) = preSolution 58 | HideSolutionNode = FALSE 59 | EndGlobalSection 60 | GlobalSection(ExtensibilityGlobals) = postSolution 61 | SolutionGuid = {23B4DBC6-9379-43AE-BC12-3BD4BEA6B4F6} 62 | EndGlobalSection 63 | EndGlobal 64 | --------------------------------------------------------------------------------