diff --git a/Directory.Build.props b/Directory.Build.props index 86609829..c1307957 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,5 +4,6 @@ 10.0 true true + enable diff --git a/src/D2L.CodeStyle.Analyzers/D2L.CodeStyle.Analyzers.csproj b/src/D2L.CodeStyle.Analyzers/D2L.CodeStyle.Analyzers.csproj index 1d0b724d..4cd61a0b 100644 --- a/src/D2L.CodeStyle.Analyzers/D2L.CodeStyle.Analyzers.csproj +++ b/src/D2L.CodeStyle.Analyzers/D2L.CodeStyle.Analyzers.csproj @@ -17,7 +17,6 @@ True False True - enable CA2016,Nullable false true diff --git a/src/D2L.CodeStyle.TestAnalyzers/Common/NotNullWhenAttribute.cs b/src/D2L.CodeStyle.TestAnalyzers/Common/NotNullWhenAttribute.cs new file mode 100644 index 00000000..461512e8 --- /dev/null +++ b/src/D2L.CodeStyle.TestAnalyzers/Common/NotNullWhenAttribute.cs @@ -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 diff --git a/src/D2L.CodeStyle.TestAnalyzers/Extensions/Microsoft.CodeAnalysis.cs b/src/D2L.CodeStyle.TestAnalyzers/Extensions/Microsoft.CodeAnalysis.cs index d4c6c59e..059370c2 100644 --- a/src/D2L.CodeStyle.TestAnalyzers/Extensions/Microsoft.CodeAnalysis.cs +++ b/src/D2L.CodeStyle.TestAnalyzers/Extensions/Microsoft.CodeAnalysis.cs @@ -9,7 +9,7 @@ public static class RoslynExtensions { // Copied from the non-test assembly because we do not reference it. - public static bool IsNullOrErrorType( this ITypeSymbol symbol ) { + public static bool IsNullOrErrorType( this ITypeSymbol? symbol ) { if( symbol == null ) { return true; @@ -26,6 +26,19 @@ public static bool IsNullOrErrorType( this ITypeSymbol symbol ) { return false; } + public static bool IsErrorType( this ITypeSymbol symbol ) { + + if( symbol.Kind == SymbolKind.ErrorType ) { + return true; + } + + if( symbol.TypeKind == TypeKind.Error ) { + return true; + } + + return false; + } + public static bool IsNullOrErrorType( this ISymbol symbol ) { if( symbol == null ) { diff --git a/src/D2L.CodeStyle.TestAnalyzers/NUnit/CategoryAnalyzer.cs b/src/D2L.CodeStyle.TestAnalyzers/NUnit/CategoryAnalyzer.cs index faf8fab8..b5c5582c 100644 --- a/src/D2L.CodeStyle.TestAnalyzers/NUnit/CategoryAnalyzer.cs +++ b/src/D2L.CodeStyle.TestAnalyzers/NUnit/CategoryAnalyzer.cs @@ -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: (ctx.Node as MethodDeclarationSyntax)! ), SyntaxKind.MethodDeclaration ); @@ -69,9 +70,16 @@ private static void OnCompilationEnd( CompilationAnalysisContext context, NUnitT return; } + var syntaxReference = attribute.ApplicationSyntaxReference; + + if( syntaxReference == null ) { + // ?? + return; + } + 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 +93,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 +125,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 +167,7 @@ private static void VisitCategories( Action 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 +197,16 @@ Action visitor TypedConstant arg = args[0]; - if( arg.Type.SpecialType != SpecialType.System_String ) { + if( arg.Type?.SpecialType != SpecialType.System_String ) { + return; + } + + string? category = arg.Value as string; + + if( category == null ) { return; } - string category = arg.Value as string; visitor( category, attribute ); } @@ -208,11 +221,16 @@ Action visitor TypedConstant arg = namedArg.Value; - if( arg.Type.SpecialType != SpecialType.System_String ) { + if( arg.Type?.SpecialType != SpecialType.System_String ) { + continue; + } + + string? categoryCsv = arg.Value as string; + + if( categoryCsv == null ) { continue; } - string categoryCsv = arg.Value as string; foreach( string category in categoryCsv.Split( ',' ) ) { visitor( category.Trim(), attribute ); } @@ -221,24 +239,29 @@ Action 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 testAttributes = ImmutableHashSet - .Create( - SymbolEqualityComparer.Default, - compilation.GetTypeByMetadataName( "NUnit.Framework.TestAttribute" ), - compilation.GetTypeByMetadataName( "NUnit.Framework.TestCaseAttribute" ), - compilation.GetTypeByMetadataName( "NUnit.Framework.TestCaseSourceAttribute" ), - compilation.GetTypeByMetadataName( "NUnit.Framework.TheoryAttribute" ) - ); + ImmutableHashSet 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 )! + .ToImmutableHashSet( SymbolEqualityComparer.Default ); - INamedTypeSymbol testFixtureAttribute = compilation.GetTypeByMetadataName( "NUnit.Framework.TestFixtureAttribute" ); + INamedTypeSymbol? testFixtureAttribute = compilation.GetTypeByMetadataName( "NUnit.Framework.TestFixtureAttribute" ); + + if( testFixtureAttribute == null ) { + types = null; + return false; + } types = new NUnitTypes( categoryAttribute, testAttributes, testFixtureAttribute ); return true; @@ -268,7 +291,7 @@ AnalyzerOptions options StringComparer.Ordinal ); - AdditionalText bannedListFile = options.AdditionalFiles.FirstOrDefault( + AdditionalText? bannedListFile = options.AdditionalFiles.FirstOrDefault( file => Path.GetFileName( file.Path ) == "BannedTestCategoriesList.txt" ); @@ -276,7 +299,11 @@ AnalyzerOptions options return bannedList.ToImmutableHashSet(); } - SourceText allowedListText = bannedListFile.GetText(); + SourceText? allowedListText = bannedListFile.GetText(); + + if( allowedListText == null ) { + throw new Exception( "Couldn't read config" ); + } foreach( TextLine line in allowedListText.Lines ) { bannedList.Add( line.ToString().Trim() ); diff --git a/src/D2L.CodeStyle.TestAnalyzers/NUnit/ConfigTestSetupStringsAnalyzer.cs b/src/D2L.CodeStyle.TestAnalyzers/NUnit/ConfigTestSetupStringsAnalyzer.cs index 44382406..6828a5c6 100644 --- a/src/D2L.CodeStyle.TestAnalyzers/NUnit/ConfigTestSetupStringsAnalyzer.cs +++ b/src/D2L.CodeStyle.TestAnalyzers/NUnit/ConfigTestSetupStringsAnalyzer.cs @@ -27,10 +27,10 @@ public override void Initialize( AnalysisContext context ) { private static void Register( CompilationStartAnalysisContext context ) { - INamedTypeSymbol attributeType = + INamedTypeSymbol? attributeType = context.Compilation.GetTypeByMetadataName( AttributeTypeName ); - if( attributeType.IsNullOrErrorType() ) { + if( attributeType == null || attributeType.IsErrorType() ) { return; } @@ -73,12 +73,12 @@ INamedTypeSymbol attributeType foreach( AttributeSyntax attribute in attributes ) { - ISymbol symbol = context + ISymbol? symbol = context .SemanticModel .GetSymbolInfo( attribute, context.CancellationToken ) .Symbol; - if( symbol.IsNullOrErrorType() ) { + if( symbol == null || symbol.Kind == SymbolKind.ErrorType ) { continue; } @@ -88,9 +88,9 @@ INamedTypeSymbol attributeType } SeparatedSyntaxList arguments = - attribute.ArgumentList.Arguments; + attribute.ArgumentList?.Arguments ?? default; - if( arguments.Count != 1 ) { + if( arguments == default || arguments.Count != 1 ) { continue; } diff --git a/src/D2L.CodeStyle.TestAnalyzers/ServiceLocator/CustomTestServiceLocatorAnalyzer.cs b/src/D2L.CodeStyle.TestAnalyzers/ServiceLocator/CustomTestServiceLocatorAnalyzer.cs index 421aef3e..c07cc0a5 100644 --- a/src/D2L.CodeStyle.TestAnalyzers/ServiceLocator/CustomTestServiceLocatorAnalyzer.cs +++ b/src/D2L.CodeStyle.TestAnalyzers/ServiceLocator/CustomTestServiceLocatorAnalyzer.cs @@ -28,10 +28,10 @@ public override void Initialize( AnalysisContext context ) { public static void RegisterServiceLocatorAnalyzer( CompilationStartAnalysisContext context ) { - INamedTypeSymbol factoryType = context.Compilation + INamedTypeSymbol? factoryType = context.Compilation .GetTypeByMetadataName( TestServiceLocatorFactoryType ); - if( factoryType.IsNullOrErrorType() ) { + if( factoryType == null || factoryType.IsErrorType() ) { return; }