-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable nullable everywhere #954
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#if !NETSTANDARD2_1 | ||
|
||
namespace System.Diagnostics.CodeAnalysis; | ||
|
||
[AttributeUsage( AttributeTargets.Parameter )] | ||
internal sealed class NotNullWhenAttribute : Attribute { | ||
public bool ReturnValue { get; } | ||
|
||
public NotNullWhenAttribute( bool returnValue ) { | ||
ReturnValue = returnValue; | ||
} | ||
} | ||
|
||
#endif | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.IO; | ||
using System.Linq; | ||
using D2L.CodeStyle.TestAnalyzers.Common; | ||
|
@@ -42,7 +43,7 @@ public override void Initialize( AnalysisContext context ) { | |
} | ||
|
||
private static void OnCompilationStart( CompilationStartAnalysisContext context ) { | ||
if( !TryLoadNUnitTypes( context.Compilation, out NUnitTypes types ) ) { | ||
if( !TryLoadNUnitTypes( context.Compilation, out NUnitTypes? types ) ) { | ||
return; | ||
} | ||
|
||
|
@@ -53,7 +54,7 @@ private static void OnCompilationStart( CompilationStartAnalysisContext context | |
context: ctx, | ||
bannedCategories: bannedCategories, | ||
types: types, | ||
syntax: ctx.Node as MethodDeclarationSyntax | ||
syntax: (MethodDeclarationSyntax)ctx.Node | ||
), | ||
SyntaxKind.MethodDeclaration | ||
); | ||
|
@@ -69,9 +70,12 @@ private static void OnCompilationEnd( CompilationAnalysisContext context, NUnitT | |
return; | ||
} | ||
|
||
// TODO: can this actually be null? | ||
var syntaxReference = attribute.ApplicationSyntaxReference!; | ||
Comment on lines
+73
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment is just there as a hint that this |
||
|
||
context.ReportDiagnostic( Diagnostic.Create( | ||
Diagnostics.NUnitCategory, | ||
attribute.ApplicationSyntaxReference.GetSyntax( context.CancellationToken ).GetLocation(), | ||
syntaxReference.GetSyntax( context.CancellationToken ).GetLocation(), | ||
$"Assemblies cannot be categorized as any of [{string.Join( ", ", ProhibitedAssemblyCategories )}], but saw '{category}'." | ||
) ); | ||
} ); | ||
|
@@ -85,7 +89,7 @@ MethodDeclarationSyntax syntax | |
) { | ||
SemanticModel model = context.SemanticModel; | ||
|
||
IMethodSymbol method = model.GetDeclaredSymbol( syntax, context.CancellationToken ); | ||
IMethodSymbol? method = model.GetDeclaredSymbol( syntax, context.CancellationToken ); | ||
if( method == null ) { | ||
return; | ||
} | ||
|
@@ -117,8 +121,8 @@ private static bool IsTestMethod( | |
IMethodSymbol method | ||
) { | ||
foreach( AttributeData attribute in method.GetAttributes() ) { | ||
INamedTypeSymbol attributeType = attribute.AttributeClass; | ||
if( types.TestAttributes.Contains( attributeType ) ) { | ||
INamedTypeSymbol? attributeType = attribute.AttributeClass; | ||
if( attributeType != null && types.TestAttributes.Contains( attributeType ) ) { | ||
return true; | ||
} | ||
} | ||
|
@@ -159,7 +163,7 @@ private static void VisitCategories( | |
Action<string, AttributeData> visitor | ||
) { | ||
foreach( AttributeData attribute in symbol.GetAttributes() ) { | ||
INamedTypeSymbol attributeType = attribute.AttributeClass; | ||
INamedTypeSymbol? attributeType = attribute.AttributeClass; | ||
if( types.CategoryAttribute.Equals( attributeType, SymbolEqualityComparer.Default ) ) { | ||
VisitCategoryAttribute( attribute, visitor ); | ||
continue; | ||
|
@@ -189,11 +193,16 @@ Action<string, AttributeData> visitor | |
|
||
TypedConstant arg = args[0]; | ||
|
||
if( arg.Type == null ) { | ||
return; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Behaviour change that I think is reasonable (ditto below)? |
||
|
||
if( arg.Type.SpecialType != SpecialType.System_String ) { | ||
return; | ||
} | ||
|
||
string category = arg.Value as string; | ||
string category = (string?)arg.Value!; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We know that arg.Type is System.String. I'm not sure if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure |
||
|
||
visitor( category, attribute ); | ||
} | ||
|
||
|
@@ -208,11 +217,16 @@ Action<string, AttributeData> visitor | |
|
||
TypedConstant arg = namedArg.Value; | ||
|
||
if( arg.Type == null ) { | ||
continue; | ||
} | ||
|
||
if( arg.Type.SpecialType != SpecialType.System_String ) { | ||
continue; | ||
} | ||
|
||
string categoryCsv = arg.Value as string; | ||
string categoryCsv = (string?)arg.Value!; | ||
|
||
foreach( string category in categoryCsv.Split( ',' ) ) { | ||
visitor( category.Trim(), attribute ); | ||
} | ||
|
@@ -221,24 +235,29 @@ Action<string, AttributeData> visitor | |
|
||
private static bool TryLoadNUnitTypes( | ||
Compilation compilation, | ||
out NUnitTypes types | ||
[NotNullWhen( true )] | ||
out NUnitTypes? types | ||
) { | ||
INamedTypeSymbol categoryAttribute = compilation.GetTypeByMetadataName( "NUnit.Framework.CategoryAttribute" ); | ||
INamedTypeSymbol? categoryAttribute = compilation.GetTypeByMetadataName( "NUnit.Framework.CategoryAttribute" ); | ||
if( categoryAttribute == null || categoryAttribute.TypeKind == TypeKind.Error ) { | ||
types = null; | ||
return false; | ||
} | ||
|
||
ImmutableHashSet<INamedTypeSymbol> testAttributes = ImmutableHashSet | ||
.Create<INamedTypeSymbol>( | ||
SymbolEqualityComparer.Default, | ||
compilation.GetTypeByMetadataName( "NUnit.Framework.TestAttribute" ), | ||
compilation.GetTypeByMetadataName( "NUnit.Framework.TestCaseAttribute" ), | ||
compilation.GetTypeByMetadataName( "NUnit.Framework.TestCaseSourceAttribute" ), | ||
compilation.GetTypeByMetadataName( "NUnit.Framework.TheoryAttribute" ) | ||
); | ||
ImmutableHashSet<INamedTypeSymbol> testAttributes = new[] { | ||
compilation.GetTypeByMetadataName( "NUnit.Framework.TestAttribute" ), | ||
compilation.GetTypeByMetadataName( "NUnit.Framework.TestCaseAttribute" ), | ||
compilation.GetTypeByMetadataName( "NUnit.Framework.TestCaseSourceAttribute" ), | ||
compilation.GetTypeByMetadataName( "NUnit.Framework.TheoryAttribute" ) | ||
}.Where( x => x != null )! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the null-forgiving op necessary on the result of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, well known problem. They've talked about extending nullable analysis more to handle LINQ stuff but nothing very concrete. Also related to covariance headaches (e.g. The |
||
.ToImmutableHashSet<INamedTypeSymbol>( SymbolEqualityComparer.Default ); | ||
|
||
INamedTypeSymbol testFixtureAttribute = compilation.GetTypeByMetadataName( "NUnit.Framework.TestFixtureAttribute" ); | ||
INamedTypeSymbol? testFixtureAttribute = compilation.GetTypeByMetadataName( "NUnit.Framework.TestFixtureAttribute" ); | ||
|
||
if( testFixtureAttribute == null ) { | ||
types = null; | ||
return false; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (reasonable imo) behaviour change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (clearly its not crashing today, so maybe in practice not, but I suppose NUnit could rename / move these types in the future.) |
||
|
||
types = new NUnitTypes( categoryAttribute, testAttributes, testFixtureAttribute ); | ||
return true; | ||
|
@@ -268,15 +287,19 @@ AnalyzerOptions options | |
StringComparer.Ordinal | ||
); | ||
|
||
AdditionalText bannedListFile = options.AdditionalFiles.FirstOrDefault( | ||
AdditionalText? bannedListFile = options.AdditionalFiles.FirstOrDefault( | ||
file => Path.GetFileName( file.Path ) == "BannedTestCategoriesList.txt" | ||
); | ||
|
||
if( bannedListFile == null ) { | ||
return bannedList.ToImmutableHashSet(); | ||
} | ||
|
||
SourceText allowedListText = bannedListFile.GetText(); | ||
SourceText? allowedListText = bannedListFile.GetText(); | ||
|
||
if( allowedListText == null ) { | ||
throw new Exception( "Couldn't read config" ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure how this would happen so I didn't try hard on this exception; I figure its better than the NullReferenceException we'd otherwise have. The IDE/CLI will tell you which analyzer threw an exception so I figure this is enough hint for now. |
||
} | ||
|
||
foreach( TextLine line in allowedListText.Lines ) { | ||
bannedList.Add( line.ToString().Trim() ); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other project brings in a package for this but I'm pretty meh on that.
In the future I'm going to make a folder for common source for analyzers and some Directory.Build.targets magic to
<Compile Include="..." LinkBase="Common" />
-in some shared analyzer code and this can go in there.