22using System . Collections . Generic ;
33using System . IO ;
44using System . Linq ;
5- using System . Text . RegularExpressions ;
65using System . Threading ;
76using System . Threading . Tasks ;
87using ICSharpCode . CodeConverter . Util ;
98using Microsoft . CodeAnalysis ;
9+ using System . IO . Abstractions ;
1010
1111namespace ICSharpCode . CodeConverter . Shared
1212{
@@ -18,32 +18,58 @@ public class SolutionConverter
1818 private readonly List < ( string Find , string Replace , bool FirstOnly ) > _projectReferenceReplacements ;
1919 private readonly IProgress < ConversionProgress > _progress ;
2020 private readonly ILanguageConversion _languageConversion ;
21+ private readonly SolutionFileTextEditor _solutionFileTextEditor ;
2122 private readonly CancellationToken _cancellationToken ;
2223
24+ public static IFileSystem FileSystem { get ; set ; } = new FileSystem ( ) ;
25+
26+ public static SolutionConverter CreateFor < TLanguageConversion > ( IReadOnlyCollection < Project > projectsToConvert , string sourceSolutionContents )
27+ where TLanguageConversion : ILanguageConversion , new ( )
28+ {
29+ return CreateFor < TLanguageConversion > ( projectsToConvert , solutionContents : sourceSolutionContents ) ;
30+ }
31+
2332 public static SolutionConverter CreateFor < TLanguageConversion > ( IReadOnlyCollection < Project > projectsToConvert ,
2433 ConversionOptions conversionOptions = default ,
2534 IProgress < ConversionProgress > progress = null ,
26- CancellationToken cancellationToken = default ) where TLanguageConversion : ILanguageConversion , new ( )
35+ CancellationToken cancellationToken = default ,
36+ string solutionContents = "" ) where TLanguageConversion : ILanguageConversion , new ( )
2737 {
28- var conversion = new TLanguageConversion { ConversionOptions = conversionOptions } ;
29- return CreateFor ( conversion , projectsToConvert , progress , cancellationToken ) ;
38+ var conversion = new TLanguageConversion { ConversionOptions = conversionOptions } ;
39+ return CreateFor ( conversion , projectsToConvert , progress , cancellationToken , solutionContents ) ;
3040 }
3141
3242 public static SolutionConverter CreateFor ( ILanguageConversion languageConversion , IReadOnlyCollection < Project > projectsToConvert ,
3343 IProgress < ConversionProgress > progress ,
34- CancellationToken cancellationToken )
44+ CancellationToken cancellationToken , string solutionContents = "" )
3545 {
3646 languageConversion . ConversionOptions ??= new ConversionOptions ( ) ;
3747 var solutionFilePath = projectsToConvert . First ( ) . Solution . FilePath ;
38- var sourceSolutionContents = File . Exists ( solutionFilePath ) ? File . ReadAllText ( solutionFilePath ) : "" ;
39- var projectReferenceReplacements = GetProjectReferenceReplacements ( projectsToConvert , sourceSolutionContents ) ;
40- return new SolutionConverter ( solutionFilePath , sourceSolutionContents , projectsToConvert , projectReferenceReplacements , progress ?? new Progress < ConversionProgress > ( ) , cancellationToken , languageConversion ) ;
48+ var sourceSolutionContents = File . Exists ( solutionFilePath )
49+ ? FileSystem . File . ReadAllText ( solutionFilePath )
50+ : solutionContents ;
51+
52+ var projTuples = projectsToConvert . Select ( proj =>
53+ {
54+ var relativeProjPath = PathConverter . GetRelativePath ( solutionFilePath , proj . FilePath ) ;
55+ var projContents = FileSystem . File . ReadAllText ( proj . FilePath ) ;
56+
57+ return ( proj . Name , RelativeProjPath : relativeProjPath , ProjContents : projContents ) ;
58+ } ) ;
59+
60+ var solutionFileTextEditor = new SolutionFileTextEditor ( ) ;
61+ var projectReferenceReplacements = solutionFileTextEditor . GetProjectFileProjectReferenceReplacements ( projTuples , sourceSolutionContents ) ;
62+
63+ return new SolutionConverter ( solutionFilePath , sourceSolutionContents , projectsToConvert , projectReferenceReplacements ,
64+ progress ?? new Progress < ConversionProgress > ( ) , cancellationToken , languageConversion , solutionFileTextEditor ) ;
4165 }
4266
4367 private SolutionConverter ( string solutionFilePath ,
4468 string sourceSolutionContents , IReadOnlyCollection < Project > projectsToConvert ,
45- List < ( string Find , string Replace , bool FirstOnly ) > projectReferenceReplacements , IProgress < ConversionProgress > showProgressMessage ,
46- CancellationToken cancellationToken , ILanguageConversion languageConversion )
69+ List < ( string Find , string Replace , bool FirstOnly ) > projectReferenceReplacements ,
70+ IProgress < ConversionProgress > showProgressMessage ,
71+ CancellationToken cancellationToken , ILanguageConversion languageConversion ,
72+ SolutionFileTextEditor solutionFileTextEditor )
4773 {
4874 _solutionFilePath = solutionFilePath ;
4975 _sourceSolutionContents = sourceSolutionContents ;
@@ -52,15 +78,18 @@ private SolutionConverter(string solutionFilePath,
5278 _progress = showProgressMessage ;
5379 _languageConversion = languageConversion ;
5480 _cancellationToken = cancellationToken ;
81+ _solutionFileTextEditor = solutionFileTextEditor ;
5582 }
5683
57- public async Task < IAsyncEnumerable < ConversionResult > > Convert ( )
84+ public async IAsyncEnumerable < ConversionResult > Convert ( )
5885 {
5986 var projectsToUpdateReferencesOnly = _projectsToConvert . First ( ) . Solution . Projects . Except ( _projectsToConvert ) ;
6087 var solutionResult = string . IsNullOrWhiteSpace ( _sourceSolutionContents ) ? Enumerable . Empty < ConversionResult > ( ) : ConvertSolutionFile ( ) . Yield ( ) ;
6188 var convertedProjects = await ConvertProjects ( ) ;
62- return convertedProjects
63- . Concat ( UpdateProjectReferences ( projectsToUpdateReferencesOnly ) . Concat ( solutionResult ) . ToAsyncEnumerable ( ) ) ;
89+ var projectsAndSolutionResults = UpdateProjectReferences ( projectsToUpdateReferencesOnly ) . Concat ( solutionResult ) . ToAsyncEnumerable ( ) ;
90+ await foreach ( var p in convertedProjects . Concat ( projectsAndSolutionResults ) ) {
91+ yield return p ;
92+ }
6493 }
6594
6695 private async Task < IAsyncEnumerable < ConversionResult > > ConvertProjects ( )
@@ -79,63 +108,32 @@ private IAsyncEnumerable<ConversionResult> ConvertProject(Project project, IEnum
79108
80109 private IEnumerable < ConversionResult > UpdateProjectReferences ( IEnumerable < Project > projectsToUpdateReferencesOnly )
81110 {
82- return projectsToUpdateReferencesOnly
83- . Where ( p => p . FilePath != null ) //Some project types like Websites don't have a project file
84- . Select ( project => {
111+ var conversionResults = projectsToUpdateReferencesOnly
112+ . Where ( p => p . FilePath != null ) //Some project types like Websites don't have a project file
113+ . Select ( project => {
85114 var withReferencesReplaced =
86115 new FileInfo ( project . FilePath ) . ConversionResultFromReplacements ( _projectReferenceReplacements ) ;
87116 withReferencesReplaced . TargetPathOrNull = withReferencesReplaced . SourcePathOrNull ;
88117 return withReferencesReplaced ;
89- } ) . Where ( c => ! c . IsIdentity ) ;
90- }
118+ } ) ;
91119
92- private static List < ( string Find , string Replace , bool FirstOnly ) > GetProjectReferenceReplacements ( IReadOnlyCollection < Project > projectsToConvert ,
93- string sourceSolutionContents )
94- {
95- var projectReferenceReplacements = new List < ( string Find , string Replace , bool FirstOnly ) > ( ) ;
96- foreach ( var project in projectsToConvert )
97- {
98- var projFilename = Path . GetFileName ( project . FilePath ) ;
99- var newProjFilename = PathConverter . TogglePathExtension ( projFilename ) ;
100- projectReferenceReplacements . Add ( ( projFilename , newProjFilename , false ) ) ;
101- if ( ! string . IsNullOrWhiteSpace ( sourceSolutionContents ) ) projectReferenceReplacements . Add ( GetProjectGuidReplacement ( projFilename , sourceSolutionContents ) ) ;
102- }
103-
104- return projectReferenceReplacements ;
120+ return conversionResults . Where ( c => ! c . IsIdentity ) ;
105121 }
106122
107- private ConversionResult ConvertSolutionFile ( )
123+ public ConversionResult ConvertSolutionFile ( )
108124 {
109125 var projectTypeGuidMappings = _languageConversion . GetProjectTypeGuidMappings ( ) ;
110- var projectTypeReplacements = _projectsToConvert . SelectMany ( project => GetProjectTypeReplacement ( project , projectTypeGuidMappings ) ) . ToList ( ) ;
126+ var relativeProjPaths = _projectsToConvert . Select ( proj =>
127+ ( proj . Name , RelativeProjPath : PathConverter . GetRelativePath ( _solutionFilePath , proj . FilePath ) ) ) ;
128+
129+ var slnProjectReferenceReplacements = _solutionFileTextEditor . GetSolutionFileProjectReferenceReplacements ( relativeProjPaths ,
130+ _sourceSolutionContents , projectTypeGuidMappings ) ;
111131
112- var convertedSolutionContents = _sourceSolutionContents . Replace ( _projectReferenceReplacements . Concat ( projectTypeReplacements ) ) ;
132+ var convertedSolutionContents = _sourceSolutionContents . Replace ( slnProjectReferenceReplacements ) ;
113133 return new ConversionResult ( convertedSolutionContents ) {
114134 SourcePathOrNull = _solutionFilePath ,
115135 TargetPathOrNull = _solutionFilePath
116136 } ;
117137 }
118-
119- private static ( string Find , string Replace , bool FirstOnly ) GetProjectGuidReplacement ( string projFilename , string contents )
120- {
121- var projGuidRegex = new Regex ( projFilename + @""", ""({[0-9A-Fa-f\-]{32,36}})("")" ) ;
122- var projGuidMatch = projGuidRegex . Match ( contents ) ;
123- var oldGuid = projGuidMatch . Groups [ 1 ] . Value ;
124- var newGuid = GetDeterministicGuidFrom ( new Guid ( oldGuid ) ) ;
125- return ( oldGuid , newGuid . ToString ( "B" ) . ToUpperInvariant ( ) , false ) ;
126- }
127-
128- private IEnumerable < ( string , string , bool ) > GetProjectTypeReplacement ( Project project , IReadOnlyCollection < ( string , string ) > typeGuidMappings )
129- {
130- return typeGuidMappings . Select ( guidReplacement => ( $@ "Project\s*\(\s*""{ guidReplacement . Item1 } ""\s*\)\s*=\s*""{ project . Name } """, $@" Project( "" { guidReplacement . Item2 } "") = " "{project.Name}" "" , false ) ) ;
131- }
132-
133- private static Guid GetDeterministicGuidFrom ( Guid guidToConvert )
134- {
135- var codeConverterStaticGuid = new Guid ( "{B224816B-CC58-4FF1-8258-CA7E629734A0}" ) ;
136- var deterministicNewBytes = codeConverterStaticGuid . ToByteArray ( ) . Zip ( guidToConvert . ToByteArray ( ) ,
137- ( fromFirst , fromSecond ) => ( byte ) ( fromFirst ^ fromSecond ) ) ;
138- return new Guid ( deterministicNewBytes . ToArray ( ) ) ;
139- }
140138 }
141139}
0 commit comments