diff --git a/.SONAR_VERSION b/.SONAR_VERSION new file mode 100644 index 00000000..222fa0e3 --- /dev/null +++ b/.SONAR_VERSION @@ -0,0 +1 @@ +9.32.0.97167 diff --git a/docs/description/description.json b/docs/description/description.json index 6830a125..0313badd 100644 --- a/docs/description/description.json +++ b/docs/description/description.json @@ -1521,11 +1521,6 @@ "title": "Types should be defined in named namespaces", "timeToFix": 5 }, - { - "patternId": "S3904", - "title": "Assemblies should have version information", - "timeToFix": 2 - }, { "patternId": "S3906", "title": "Event Handlers should have the correct signature", @@ -1631,11 +1626,6 @@ "title": "Assemblies should be marked as CLS compliant", "timeToFix": 1 }, - { - "patternId": "S3992", - "title": "Assemblies should explicitly specify COM visibility", - "timeToFix": 1 - }, { "patternId": "S3993", "title": "Custom attributes should be marked with \"System.AttributeUsageAttribute\"", diff --git a/docs/multiple-tests/default-rules/results.xml b/docs/multiple-tests/default-rules/results.xml index 1f6f6065..17717e4c 100644 --- a/docs/multiple-tests/default-rules/results.xml +++ b/docs/multiple-tests/default-rules/results.xml @@ -1,10 +1,8 @@ - - diff --git a/docs/multiple-tests/s1125-unresolved-types/patterns.xml b/docs/multiple-tests/s1125-unresolved-types/patterns.xml new file mode 100644 index 00000000..fbfdf508 --- /dev/null +++ b/docs/multiple-tests/s1125-unresolved-types/patterns.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/multiple-tests/s1125-unresolved-types/results.xml b/docs/multiple-tests/s1125-unresolved-types/results.xml new file mode 100644 index 00000000..c3dbb759 --- /dev/null +++ b/docs/multiple-tests/s1125-unresolved-types/results.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/multiple-tests/s1125-unresolved-types/src/Course.cs b/docs/multiple-tests/s1125-unresolved-types/src/Course.cs new file mode 100644 index 00000000..383a7e8d --- /dev/null +++ b/docs/multiple-tests/s1125-unresolved-types/src/Course.cs @@ -0,0 +1,5 @@ +public class Course +{ + public bool? Enabled { get; set; } + public bool? Visible { get; set; } +} diff --git a/docs/multiple-tests/s1125-unresolved-types/src/S1125.cs b/docs/multiple-tests/s1125-unresolved-types/src/S1125.cs new file mode 100644 index 00000000..87fff8e5 --- /dev/null +++ b/docs/multiple-tests/s1125-unresolved-types/src/S1125.cs @@ -0,0 +1,12 @@ +public class Example +{ + public bool Check(Course course, bool flag, bool? nullableFlag) + { + if (course.Enabled == true && !course.Visible.HasValue) + { + return nullableFlag == true; + } + + return flag == true; + } +} diff --git a/docs/multiple-tests/s1125-unresolved-types/src/SonarLint.xml b/docs/multiple-tests/s1125-unresolved-types/src/SonarLint.xml new file mode 100644 index 00000000..2a2e08bd --- /dev/null +++ b/docs/multiple-tests/s1125-unresolved-types/src/SonarLint.xml @@ -0,0 +1,8 @@ + + + + + S1125 + + + diff --git a/docs/multiple-tests/s1128-framework-usings/patterns.xml b/docs/multiple-tests/s1128-framework-usings/patterns.xml new file mode 100644 index 00000000..fbfdf508 --- /dev/null +++ b/docs/multiple-tests/s1128-framework-usings/patterns.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/multiple-tests/s1128-framework-usings/results.xml b/docs/multiple-tests/s1128-framework-usings/results.xml new file mode 100644 index 00000000..3e75f883 --- /dev/null +++ b/docs/multiple-tests/s1128-framework-usings/results.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/multiple-tests/s1128-framework-usings/src/S1128.cs b/docs/multiple-tests/s1128-framework-usings/src/S1128.cs new file mode 100644 index 00000000..f852cce9 --- /dev/null +++ b/docs/multiple-tests/s1128-framework-usings/src/S1128.cs @@ -0,0 +1,13 @@ +using System.Net; +using System.Text; +using System.Collections.Generic; + +public class Example +{ + public HttpStatusCode Status { get; set; } + + public string Build() + { + return new StringBuilder().Append(Status).ToString(); + } +} diff --git a/docs/multiple-tests/s1128-framework-usings/src/SonarLint.xml b/docs/multiple-tests/s1128-framework-usings/src/SonarLint.xml new file mode 100644 index 00000000..2dc6f621 --- /dev/null +++ b/docs/multiple-tests/s1128-framework-usings/src/SonarLint.xml @@ -0,0 +1,8 @@ + + + + + S1128 + + + diff --git a/docs/patterns.json b/docs/patterns.json index 6a09eee0..99b9d9f9 100644 --- a/docs/patterns.json +++ b/docs/patterns.json @@ -1804,12 +1804,6 @@ "level": "Warning", "enabled": false }, - { - "patternId": "S3904", - "category": "ErrorProne", - "level": "Error", - "enabled": false - }, { "patternId": "S3906", "category": "CodeStyle", @@ -1936,12 +1930,6 @@ "level": "Warning", "enabled": false }, - { - "patternId": "S3992", - "category": "ErrorProne", - "level": "Warning", - "enabled": false - }, { "patternId": "S3993", "category": "ErrorProne", diff --git a/src/Analyzer/CodeAnalyzer.cs b/src/Analyzer/CodeAnalyzer.cs index 08f05b4e..6a56e0ea 100644 --- a/src/Analyzer/CodeAnalyzer.cs +++ b/src/Analyzer/CodeAnalyzer.cs @@ -26,7 +26,7 @@ public class CodeAnalyzer : Codacy.Engine.Seed.CodeAnalyzer, IDisposable private readonly ImmutableArray availableAnalyzers; private readonly DiagnosticsRunner diagnosticsRunner; private readonly string tmpSonarLintFolder; - private static HashSet blacklist = new HashSet { "S1144", "S2325", "S2077", "AD0001" }; + private static HashSet blacklist = new HashSet { "S1144", "S2325", "S2077", "AD0001", "S3992", "S3904" }; public static bool IsInBlacklist(string id) { diff --git a/src/Analyzer/Runner/CompilationHelper.cs b/src/Analyzer/Runner/CompilationHelper.cs index 29f516e1..9a1455c5 100644 --- a/src/Analyzer/Runner/CompilationHelper.cs +++ b/src/Analyzer/Runner/CompilationHelper.cs @@ -1,4 +1,7 @@ +using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -6,8 +9,27 @@ namespace CodacyCSharp.Analyzer.Runner { public static class CompilationHelper { - private static readonly MetadataReference systemMetadataReference = - MetadataReference.CreateFromFile(typeof(object).Assembly.Location); + // Reference the whole runtime framework (not only System.Private.CoreLib), + // so types like System.Net.HttpStatusCode resolve and rules relying on + // the semantic model (e.g. S1128) don't report false positives. + private static readonly IReadOnlyList frameworkMetadataReferences = + GetFrameworkMetadataReferences(); + + private static IReadOnlyList GetFrameworkMetadataReferences() + { + var trustedAssemblies = AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string; + var paths = string.IsNullOrEmpty(trustedAssemblies) + ? new[] { typeof(object).Assembly.Location } + : trustedAssemblies.Split(Path.PathSeparator); + + var frameworkDirectory = Path.GetDirectoryName(typeof(object).Assembly.Location); + + return paths + // keep only framework assemblies, not the analyzer's own dependencies + .Where(path => Path.GetDirectoryName(path) == frameworkDirectory) + .Select(path => (MetadataReference)MetadataReference.CreateFromFile(path)) + .ToList(); + } public static Solution GetSolutionFromFile(string filePath) { @@ -16,7 +38,7 @@ public static Solution GetSolutionFromFile(string filePath) var file = new FileInfo(filePath); var project = workspace.CurrentSolution .AddProject("srcassembly", "srcassembly.dll", LanguageNames.CSharp) - .AddMetadataReference(systemMetadataReference); + .AddMetadataReferences(frameworkMetadataReferences); using (var fileStream = File.Open(file.FullName, FileMode.Open, FileAccess.Read)) { diff --git a/src/Analyzer/Runner/DiagnosticsRunner.cs b/src/Analyzer/Runner/DiagnosticsRunner.cs index 3b2fc96d..1953d118 100644 --- a/src/Analyzer/Runner/DiagnosticsRunner.cs +++ b/src/Analyzer/Runner/DiagnosticsRunner.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using SonarAnalyzer.Common; @@ -56,7 +57,51 @@ public Task> GetDiagnostics(Compilation compilation, options, cancellationToken); - return compilationWithAnalyzer.GetAnalyzerDiagnosticsAsync(cancellationToken); + return FilterDiagnostics(modifiedCompilation, compilationWithAnalyzer, cancellationToken); + } + + private static async Task> FilterDiagnostics( + Compilation compilation, + CompilationWithAnalyzers compilationWithAnalyzer, + CancellationToken cancellationToken) + { + var diagnostics = await compilationWithAnalyzer.GetAnalyzerDiagnosticsAsync(cancellationToken); + + return diagnostics + .Where(diagnostic => !IsS1125OnUnresolvedType(compilation, diagnostic, cancellationToken)) + .ToImmutableArray(); + } + + // S1125 can only tell `x == true` apart from a valid `bool?` comparison when the + // type of `x` is known. Files are compiled in isolation, so types declared in other + // files or packages are unresolved and the rule reports false positives on them. + private static bool IsS1125OnUnresolvedType( + Compilation compilation, + Diagnostic diagnostic, + CancellationToken cancellationToken) + { + var tree = diagnostic.Location.SourceTree; + if (diagnostic.Id != "S1125" || tree is null) + { + return false; + } + + var binaryExpression = tree.GetRoot(cancellationToken) + .FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true) + .AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + + if (binaryExpression is null) + { + return false; + } + + var semanticModel = compilation.GetSemanticModel(tree); + + return new[] { binaryExpression.Left, binaryExpression.Right } + .Select(operand => semanticModel.GetTypeInfo(operand, cancellationToken).Type) + .Any(type => type is null || type.TypeKind == TypeKind.Error); } private ReportDiagnostic GetReportDiagnosticForPattern(string diagnosticId)