forked from UbiquityDotNET/Ubiquity.NET.Utils
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCompilationExtensions.cs
More file actions
186 lines (166 loc) · 10.8 KB
/
CompilationExtensions.cs
File metadata and controls
186 lines (166 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
// Mostly from: https://github.com/Sergio0694/PolySharp/blob/main/src/PolySharp.SourceGenerators/Extensions/CompilationExtensions.cs
// Reformatted and adapted to support repo guidelines
#if SUPPORT_VB
using Microsoft.CodeAnalysis.VisualBasic;
#endif
namespace Ubiquity.NET.CodeAnalysis.Utils
{
/// <summary>Structure for a runtime version</summary>
/// <param name="RuntimeName">Name of the runtime</param>
/// <param name="Version">Version of the runtime</param>
[SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Simple record" )]
public readonly record struct RuntimeVersion( string RuntimeName, Version Version );
/// <summary>Extension methods for the <see cref="Compilation"/> type.</summary>
public static class CompilationExtensions
{
/// <summary>Checks whether a given compilation (assumed to be for C#) is using at least a given language version.</summary>
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
/// <param name="languageVersion">The minimum language version to check.</param>
/// <returns>Whether <paramref name="compilation"/> is using at least the specified language version.</returns>
public static bool HasLanguageVersionAtLeastEqualTo( this Compilation compilation, Microsoft.CodeAnalysis.CSharp.LanguageVersion languageVersion )
{
return compilation is not CSharpCompilation csharpCompilation
? throw new ArgumentNullException( nameof( compilation ) )
: csharpCompilation.LanguageVersion >= languageVersion;
}
// Support of VB is problematic as [RS1038](https://github.com/dotnet/roslyn/blob/main/docs/roslyn-analyzers/rules/RS1038.md)
// is aggressive and tests for ALL dependencies. So inclusion of a reference in a dependent assembly will trigger that
// To fully resolve this in a general means the language specific parts would need to pull out of this assembly and
// into a distinct one for that language. This is a bit overkill given the need to target any language other than C#
// is rather limited... Until such is needed, just leave out the VB support
#if SUPPORT_VB
/// <summary>Checks whether a given VB compilation is using at least a given language version.</summary>
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
/// <param name="languageVersion">The minimum language version to check.</param>
/// <returns>Whether <paramref name="compilation"/> is using at least the specified language version.</returns>
[SuppressMessage( "StyleCop.CSharp.NamingRules", "SA1305:Field names should not use Hungarian notation", Justification = "not Hungarian" )]
public static bool HasLanguageVersionAtLeastEqualTo( this Compilation compilation, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion languageVersion )
{
return compilation is not VisualBasicCompilation vbCompilation
? throw new ArgumentNullException( nameof( compilation ) )
: vbCompilation.LanguageVersion >= languageVersion;
}
#endif
/// <summary>Gets the runtime version by extracting the version from the assembly implementing <see cref="System.Object"/></summary>
/// <param name="self">Compilation to get the version information from</param>
/// <returns>Version of the runtime the compilation is targeting</returns>
public static RuntimeVersion GetRuntimeVersion(this Compilation self)
{
var objectType = self.GetSpecialType(SpecialType.System_Object);
var runtimeAssembly = objectType.ContainingAssembly;
return new(runtimeAssembly.Identity.Name, runtimeAssembly.Identity.Version);
}
/// <summary>Gets a value indicating whether the compilation has a minium version of the runtime</summary>
/// <param name="self">Compilation to test</param>
/// <param name="minVersion">Minimum version accepted</param>
/// <returns><see langword="true"/> if the runtime version targeted by the compilation is at least <paramref name="minVersion"/>; <see langword="false"/> otherwise</returns>
public static bool HasRuntimeVersionAtLeast(this Compilation self, RuntimeVersion minVersion)
{
var runtimeVersion = GetRuntimeVersion(self);
return runtimeVersion.RuntimeName == minVersion.RuntimeName && runtimeVersion.Version >= minVersion.Version;
}
/// <summary>Checks whether or not a type with a specified metadata name is accessible from a given <see cref="Compilation"/> instance.</summary>
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
/// <param name="fullyQualifiedMetadataName">The fully-qualified metadata type name to find.</param>
/// <returns>Whether a type with the specified metadata name can be accessed from the given compilation.</returns>
/// <remarks>
/// This method enumerates candidate type symbols to find a match in the following order:
/// 1) If only one type with the given name is found within the compilation and its referenced assemblies, check its accessibility.<br/>
/// 2) If the current <paramref name="compilation"/> defines the symbol, check its accessibility.<br/>
/// 3) Otherwise, check whether the type exists and is accessible from any of the referenced assemblies.<br/>
/// </remarks>
public static bool HasAccessibleTypeWithMetadataName( this Compilation compilation, string fullyQualifiedMetadataName )
{
if(compilation is null)
{
throw new ArgumentNullException( nameof( compilation ) );
}
if(string.IsNullOrWhiteSpace( fullyQualifiedMetadataName ))
{
throw new ArgumentException( $"'{nameof( fullyQualifiedMetadataName )}' cannot be null or whitespace.", nameof( fullyQualifiedMetadataName ) );
}
// If there is only a single matching symbol, check its accessibility
if(compilation.GetTypeByMetadataName( fullyQualifiedMetadataName ) is INamedTypeSymbol typeSymbol)
{
return compilation.IsSymbolAccessibleWithin( typeSymbol, compilation.Assembly );
}
// Otherwise, check all available types
foreach(INamedTypeSymbol currentTypeSymbol in compilation.GetTypesByMetadataName( fullyQualifiedMetadataName ))
{
if(compilation.IsSymbolAccessibleWithin( currentTypeSymbol, compilation.Assembly ))
{
return true;
}
}
return false;
}
/// <summary>Checks whether or not a type with a specified metadata name is accessible from a given <see cref="Compilation"/> instance.</summary>
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
/// <param name="fullyQualifiedMetadataName">The fully-qualified metadata type name to find.</param>
/// <param name="memberName">Name of the member</param>
/// <returns>Whether a type with the specified metadata name can be accessed from the given compilation.</returns>
/// <remarks>
/// This method enumerates candidate type symbols to find a match in the following order:
/// 1) If only one type with the given name is found within the compilation and its referenced assemblies, check its accessibility.<br/>
/// 2) If the current <paramref name="compilation"/> defines the symbol, check its accessibility.<br/>
/// 3) Otherwise, check whether the type exists and is accessible from any of the referenced assemblies.<br/>
/// </remarks>
public static bool HasAccessibleMember( this Compilation compilation, string fullyQualifiedMetadataName, string memberName )
{
// If there is only a single matching symbol, check its accessibility
if(compilation.GetTypeByMetadataName( fullyQualifiedMetadataName ) is INamedTypeSymbol typeSymbol)
{
return compilation.IsSymbolAccessibleWithin( typeSymbol, compilation.Assembly )
&& compilation.HasAccessibleMemberWithin( typeSymbol, memberName, compilation.Assembly);
}
// Otherwise, check all available types
foreach(INamedTypeSymbol currentTypeSymbol in compilation.GetTypesByMetadataName( fullyQualifiedMetadataName ))
{
if(compilation.IsSymbolAccessibleWithin( currentTypeSymbol, compilation.Assembly )
&& compilation.HasAccessibleMemberWithin( currentTypeSymbol, memberName, compilation.Assembly)
)
{
return true;
}
}
return false;
}
/// <summary>Tests if a <see cref="Compilation"/> has a type with an accessible member of a given name</summary>
/// <param name="self"><see cref="Compilation"/> to test</param>
/// <param name="typeSymbol">Type symbol for the type to test</param>
/// <param name="memberName">Name of the member to test for</param>
/// <param name="within">Symbol to test if the member is accessible within</param>
/// <param name="throughType">Symbol to use for "protected access" [default: null]</param>
/// <returns><see langword="true"/> if the member is accessible and <see langword="false"/></returns>
public static bool HasAccessibleMemberWithin(
this Compilation self,
ITypeSymbol typeSymbol,
string memberName,
ISymbol within,
ITypeSymbol? throughType = null
)
{
if(self is null)
{
throw new ArgumentNullException( nameof( self ) );
}
if(typeSymbol is null)
{
throw new ArgumentNullException( nameof( typeSymbol ) );
}
if(string.IsNullOrEmpty( memberName ))
{
throw new ArgumentException( $"'{nameof( memberName )}' cannot be null or empty.", nameof( memberName ) );
}
if(within is null)
{
throw new ArgumentNullException( nameof( within ) );
}
var memberSymbol = typeSymbol.GetMembers().Where(s=>s.Name == memberName).FirstOrDefault();
return memberSymbol is not null
&& self.IsSymbolAccessibleWithin(memberSymbol, within, throughType);
}
}
}