22#pragma warning disable MA0047 // Declare types in namespaces
33#pragma warning disable MA0048 // File name must match type name
44using System . Reflection ;
5+ using System . Text . Json ;
56using Meziantou . Polyfill . Generator ;
67using Microsoft . CodeAnalysis . CSharp ;
78using Microsoft . CodeAnalysis ;
105106 throw new InvalidOperationException ( "There are duplicated polyfills:\n " + sb . ToString ( ) ) ;
106107}
107108
109+ // Detect which TFMs support each polyfill, sort by earliest supported version, then reassign indices
110+ DetectAndAssignVersions ( polyfills , compilation , GetRootPath ( ) ) ;
111+
108112polyfills = SortPolyfills ( polyfills ) ;
109113
110114var requiredTypes = polyfills . SelectMany ( p => p . PolyfillData . RequiredTypes )
@@ -271,10 +275,16 @@ string GenerateIncludePostCondition(PolyfillData data)
271275
272276 sb . AppendLine ( "public void AddSources(SourceProductionContext context)" ) ;
273277 sb . AppendLine ( "{" ) ;
274- foreach ( var polyfill in polyfills )
278+ foreach ( var group in polyfills . GroupBy ( p => p . CSharpFieldName , StringComparer . Ordinal ) )
275279 {
276- sb . AppendLine ( $ " if (({ polyfill . CSharpFieldName } & { polyfill . CSharpFieldBitMask } ul) == { polyfill . CSharpFieldBitMask } ul)") ;
277- sb . AppendLine ( $ " AddSource(context, \" { polyfill . OutputPath } \" , PolyfillContents.{ polyfill . CSharpSourceTextPropertyName } );") ;
280+ sb . AppendLine ( $ " if ({ group . Key } != 0)") ;
281+ sb . AppendLine ( " {" ) ;
282+ foreach ( var polyfill in group )
283+ {
284+ sb . AppendLine ( $ " if (({ polyfill . CSharpFieldName } & { polyfill . CSharpFieldBitMask } ul) == { polyfill . CSharpFieldBitMask } ul)") ;
285+ sb . AppendLine ( $ " AddSource(context, \" { polyfill . OutputPath } \" , PolyfillContents.{ polyfill . CSharpSourceTextPropertyName } );") ;
286+ }
287+ sb . AppendLine ( " }" ) ;
278288 }
279289 sb . AppendLine ( "}" ) ;
280290
@@ -356,7 +366,7 @@ async Task GenerateReadme()
356366 . WithParameterOptions ( SymbolDisplayParameterOptions . IncludeExtensionThis | SymbolDisplayParameterOptions . IncludeModifiers | SymbolDisplayParameterOptions . IncludeType | SymbolDisplayParameterOptions . IncludeName | SymbolDisplayParameterOptions . IncludeDefaultValue | SymbolDisplayParameterOptions . IncludeOptionalBrackets )
357367 . WithMiscellaneousOptions ( SymbolDisplayMiscellaneousOptions . AllowDefaultLiteral | SymbolDisplayMiscellaneousOptions . ExpandNullable | SymbolDisplayMiscellaneousOptions . IncludeNullableReferenceTypeModifier | SymbolDisplayMiscellaneousOptions . EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions . ExpandNullable | SymbolDisplayMiscellaneousOptions . ExpandValueTuple )
358368 ;
359- foreach ( var polyfill in polyfills . Where ( p => p . Kind is PolyfillKind . Type ) )
369+ foreach ( var polyfill in polyfills . Where ( p => p . Kind is PolyfillKind . Type ) . OrderBy ( p => p . TypeName , StringComparer . Ordinal ) )
360370 {
361371 sb . Append ( $ "- `{ polyfill . Symbol . ToDisplayString ( typeDisplayFormat ) } `\n ") ;
362372 }
@@ -372,7 +382,7 @@ async Task GenerateReadme()
372382 . WithParameterOptions ( SymbolDisplayParameterOptions . IncludeExtensionThis | SymbolDisplayParameterOptions . IncludeModifiers | SymbolDisplayParameterOptions . IncludeType | SymbolDisplayParameterOptions . IncludeName | SymbolDisplayParameterOptions . IncludeDefaultValue | SymbolDisplayParameterOptions . IncludeOptionalBrackets )
373383 . WithMiscellaneousOptions ( SymbolDisplayMiscellaneousOptions . AllowDefaultLiteral | SymbolDisplayMiscellaneousOptions . ExpandNullable | SymbolDisplayMiscellaneousOptions . IncludeNullableReferenceTypeModifier | SymbolDisplayMiscellaneousOptions . EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions . ExpandNullable | SymbolDisplayMiscellaneousOptions . ExpandValueTuple )
374384 ;
375- foreach ( var polyfill in polyfills . Where ( p => p . Kind is PolyfillKind . Method ) )
385+ foreach ( var polyfill in polyfills . Where ( p => p . Kind is PolyfillKind . Method ) . OrderBy ( p => p . TypeName , StringComparer . Ordinal ) )
376386 {
377387 sb . Append ( $ "- `{ polyfill . Symbol . ToDisplayString ( methodDisplayFormat ) } `\n ") ;
378388 }
@@ -388,7 +398,7 @@ async Task GenerateReadme()
388398 . WithParameterOptions ( SymbolDisplayParameterOptions . IncludeExtensionThis | SymbolDisplayParameterOptions . IncludeModifiers | SymbolDisplayParameterOptions . IncludeType | SymbolDisplayParameterOptions . IncludeName | SymbolDisplayParameterOptions . IncludeDefaultValue | SymbolDisplayParameterOptions . IncludeOptionalBrackets )
389399 . WithMiscellaneousOptions ( SymbolDisplayMiscellaneousOptions . AllowDefaultLiteral | SymbolDisplayMiscellaneousOptions . ExpandNullable | SymbolDisplayMiscellaneousOptions . IncludeNullableReferenceTypeModifier | SymbolDisplayMiscellaneousOptions . EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions . ExpandNullable | SymbolDisplayMiscellaneousOptions . ExpandValueTuple )
390400 ;
391- foreach ( var polyfill in polyfills . Where ( p => p . Kind is PolyfillKind . Property ) )
401+ foreach ( var polyfill in polyfills . Where ( p => p . Kind is PolyfillKind . Property ) . OrderBy ( p => p . TypeName , StringComparer . Ordinal ) )
392402 {
393403 sb . Append ( $ "- `{ polyfill . Symbol . ToDisplayString ( propertyDisplayFormat ) } `\n ") ;
394404 }
@@ -425,6 +435,150 @@ string ReadResourceAsString(string name)
425435 return sr . ReadToEnd ( ) ;
426436}
427437
438+ static void DetectAndAssignVersions ( Polyfill [ ] polyfills , CSharpCompilation currentCompilation , string rootPath )
439+ {
440+ var nugetPackagesPath = GetNuGetPackagesPath ( ) ;
441+ Console . WriteLine ( $ "NuGet packages path: { nugetPackagesPath } ") ;
442+
443+ // Define TFMs for netstandard and .NET Framework (stable, fixed versions)
444+ var tfmDefinitions = new List < ( string Name , string RefPath ) >
445+ {
446+ ( "netstandard2.0" , Path . Combine ( nugetPackagesPath , "netstandard.library" , "2.0.3" , "build" , "netstandard2.0" , "ref" ) ) ,
447+ ( "netstandard2.1" , Path . Combine ( nugetPackagesPath , "netstandard.library.ref" , "2.1.0" , "ref" , "netstandard2.1" ) ) ,
448+ ( "net462" , Path . Combine ( nugetPackagesPath , "microsoft.netframework.referenceassemblies.net462" , "1.0.3" , "build" , ".NETFramework" , "v4.6.2" ) ) ,
449+ ( "net472" , Path . Combine ( nugetPackagesPath , "microsoft.netframework.referenceassemblies.net472" , "1.0.3" , "build" , ".NETFramework" , "v4.7.2" ) ) ,
450+ ( "net48" , Path . Combine ( nugetPackagesPath , "microsoft.netframework.referenceassemblies.net48" , "1.0.3" , "build" , ".NETFramework" , "v4.8" ) ) ,
451+ } ;
452+
453+ // Dynamically discover Microsoft.NETCore.App.Ref versions from NuGet cache
454+ var netcoreAppRefPath = Path . Combine ( nugetPackagesPath , "microsoft.netcore.app.ref" ) ;
455+ if ( Directory . Exists ( netcoreAppRefPath ) )
456+ {
457+ var discoveredVersions = new SortedDictionary < int , ( string TfmName , string RefPath ) > ( ) ;
458+ foreach ( var versionDir in Directory . GetDirectories ( netcoreAppRefPath ) )
459+ {
460+ var versionName = Path . GetFileName ( versionDir ) ;
461+ var majorPart = versionName . Split ( '.' ) [ 0 ] ;
462+ if ( int . TryParse ( majorPart , out var major ) && major >= 6 && ! discoveredVersions . ContainsKey ( major ) )
463+ {
464+ var tfmName = $ "net{ major } .0";
465+ var refPath = Path . Combine ( versionDir , "ref" , tfmName ) ;
466+ if ( Directory . Exists ( refPath ) )
467+ {
468+ discoveredVersions [ major ] = ( tfmName , refPath ) ;
469+ }
470+ }
471+ }
472+
473+ foreach ( var ( _, ( tfmName , refPath ) ) in discoveredVersions )
474+ {
475+ tfmDefinitions . Add ( ( tfmName , refPath ) ) ;
476+ }
477+ }
478+
479+ var compilations = new List < ( string Name , int SortOrder , CSharpCompilation Compilation ) > ( ) ;
480+
481+ for ( var i = 0 ; i < tfmDefinitions . Count ; i ++ )
482+ {
483+ var ( name , refPath ) = tfmDefinitions [ i ] ;
484+ if ( ! Directory . Exists ( refPath ) )
485+ {
486+ Console . WriteLine ( $ "Warning: Reference assemblies not found for { name } at { refPath } ") ;
487+ continue ;
488+ }
489+
490+ var files = Directory . GetFiles ( refPath , "*.dll" ) . ToList ( ) ;
491+ var facadesPath = Path . Combine ( refPath , "Facades" ) ;
492+ if ( Directory . Exists ( facadesPath ) )
493+ {
494+ files . AddRange ( Directory . GetFiles ( facadesPath , "*.dll" ) ) ;
495+ }
496+
497+ var comp = CSharpCompilation . Create (
498+ assemblyName : $ "version-check-{ name } ",
499+ references : files . Select ( f => MetadataReference . CreateFromFile ( f ) ) ,
500+ options : new CSharpCompilationOptions ( OutputKind . DynamicallyLinkedLibrary , metadataImportOptions : MetadataImportOptions . All ) ) ;
501+
502+ compilations . Add ( ( name , i , comp ) ) ;
503+ Console . WriteLine ( $ "Loaded { files . Count } assemblies for { name } ") ;
504+ }
505+
506+ // Current runtime represents the latest .NET version
507+ var currentVersionName = $ "net{ Environment . Version . Major } .0";
508+ if ( ! compilations . Any ( c => string . Equals ( c . Name , currentVersionName , StringComparison . Ordinal ) ) )
509+ {
510+ compilations . Add ( ( currentVersionName , tfmDefinitions . Count , currentCompilation ) ) ;
511+ Console . WriteLine ( $ "Using current runtime as { currentVersionName } ") ;
512+ }
513+
514+ var allVersionNames = compilations . Select ( c => c . Name ) . ToArray ( ) ;
515+
516+ // Check each polyfill against each compilation
517+ Console . WriteLine ( $ "Checking { polyfills . Length } polyfills against { compilations . Count } TFMs...") ;
518+ foreach ( var polyfill in polyfills )
519+ {
520+ var supportedVersions = new List < string > ( ) ;
521+ foreach ( var ( name , _, comp ) in compilations )
522+ {
523+ var symbols = DocumentationCommentId . GetSymbolsForDeclarationId ( polyfill . TypeName , comp ) ;
524+ if ( symbols . Length > 0 )
525+ {
526+ supportedVersions . Add ( name ) ;
527+ }
528+ }
529+
530+ polyfill . SupportedInVersions = [ .. supportedVersions ] ;
531+ }
532+
533+ // Write cache file
534+ var cacheEntries = new SortedDictionary < string , string [ ] > ( StringComparer . Ordinal ) ;
535+ foreach ( var polyfill in polyfills )
536+ {
537+ cacheEntries [ polyfill . TypeName ] = polyfill . SupportedInVersions ;
538+ }
539+
540+ var cacheFilePath = Path . Combine ( rootPath , "Meziantou.Polyfill.Generator" , "polyfill-supported-versions.json" ) ;
541+ var jsonOptions = new JsonSerializerOptions { WriteIndented = true } ;
542+ File . WriteAllText ( cacheFilePath , JsonSerializer . Serialize ( new { versions = allVersionNames , polyfills = cacheEntries } , jsonOptions ) ) ;
543+ Console . WriteLine ( $ "Wrote version cache to { cacheFilePath } ") ;
544+
545+ // Sort by earliest supported version (ascending), then by name, and reassign Index
546+ var versionOrder = compilations . ToDictionary ( c => c . Name , c => c . SortOrder , StringComparer . Ordinal ) ;
547+ var sortedPolyfills = polyfills
548+ . OrderBy ( p => GetEarliestVersionOrder ( p . SupportedInVersions , versionOrder ) )
549+ . ThenBy ( p => p . TypeName , StringComparer . Ordinal )
550+ . ToArray ( ) ;
551+
552+ for ( var i = 0 ; i < sortedPolyfills . Length ; i ++ )
553+ {
554+ sortedPolyfills [ i ] . Index = i ;
555+ }
556+
557+ // Print version distribution summary
558+ foreach ( var group in sortedPolyfills . GroupBy ( p => GetEarliestVersionOrder ( p . SupportedInVersions , versionOrder ) ) . OrderBy ( g => g . Key ) )
559+ {
560+ var versionName = group . Key == int . MaxValue ? "unknown" : compilations . First ( c => c . SortOrder == group . Key ) . Name ;
561+ Console . WriteLine ( $ " { versionName } : { group . Count ( ) } polyfills (bits { group . Min ( p => p . Index ) } \u2013 { group . Max ( p => p . Index ) } )") ;
562+ }
563+
564+ static int GetEarliestVersionOrder ( string [ ] supportedVersions , Dictionary < string , int > versionOrder )
565+ {
566+ if ( supportedVersions . Length == 0 )
567+ return int . MaxValue ;
568+
569+ return supportedVersions . Min ( v => versionOrder . GetValueOrDefault ( v , int . MaxValue ) ) ;
570+ }
571+
572+ static string GetNuGetPackagesPath ( )
573+ {
574+ var path = Environment . GetEnvironmentVariable ( "NUGET_PACKAGES" ) ;
575+ if ( ! string . IsNullOrEmpty ( path ) )
576+ return path ;
577+
578+ return Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) , ".nuget" , "packages" ) ;
579+ }
580+ }
581+
428582static Polyfill [ ] SortPolyfills ( Polyfill [ ] items )
429583{
430584 var result = new List < Polyfill > ( items . Length ) ;
@@ -502,6 +656,8 @@ internal sealed class Polyfill
502656 public required string OutputPath { get ; set ; }
503657 public required PolyfillKind Kind { get ; set ; }
504658
659+ public string [ ] SupportedInVersions { get ; set ; } = [ ] ;
660+
505661 public string CSharpFieldName => "_bits" + ( Index / 64 ) ;
506662 public int CSharpFieldBitIndex => Index % 64 ;
507663 public ulong CSharpFieldBitMask => 1uL << ( Index % 64 ) ;
0 commit comments