-
Notifications
You must be signed in to change notification settings - Fork 222
/
SonarAnalysisContextBase.cs
132 lines (115 loc) · 6.58 KB
/
SonarAnalysisContextBase.cs
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
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
using System.Collections.Concurrent;
using System.IO;
using System.Runtime.CompilerServices;
using static SonarAnalyzer.Helpers.DiagnosticDescriptorFactory;
namespace SonarAnalyzer.AnalysisContext;
public class SonarAnalysisContextBase
{
protected static readonly ConditionalWeakTable<Compilation, ConcurrentDictionary<string, bool>> FileInclusionCache = new();
protected static readonly ConditionalWeakTable<Compilation, ImmutableHashSet<string>> UnchangedFilesCache = new();
protected static readonly SourceTextValueProvider<ProjectConfigReader> ProjectConfigProvider = new(x => new ProjectConfigReader(x));
protected static readonly SourceTextValueProvider<SonarLintXmlReader> SonarLintXmlProvider = new(x => new SonarLintXmlReader(x));
protected SonarAnalysisContextBase() { }
}
public abstract class SonarAnalysisContextBase<TContext> : SonarAnalysisContextBase
{
public abstract Compilation Compilation { get; }
public abstract AnalyzerOptions Options { get; }
public abstract CancellationToken Cancel { get; }
public SonarAnalysisContext AnalysisContext { get; }
public TContext Context { get; }
protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext, TContext context)
{
AnalysisContext = analysisContext ?? throw new ArgumentNullException(nameof(analysisContext));
Context = context;
}
/// <param name="tree">Tree to decide on. Can be null for Symbol-based and Compilation-based scenarios. And we want to analyze those too.</param>
/// <param name="generatedCodeRecognizer">When set, generated trees are analyzed only when language-specific 'analyzeGeneratedCode' configuration property is also set.</param>
public bool ShouldAnalyzeTree(SyntaxTree tree, GeneratedCodeRecognizer generatedCodeRecognizer) =>
SonarLintXml() is var sonarLintXml
&& (generatedCodeRecognizer is null || sonarLintXml.AnalyzeGeneratedCode(Compilation.Language) || !tree.IsGenerated(generatedCodeRecognizer, Compilation))
&& (tree is null || (!IsUnchanged(tree) && !IsExcluded(sonarLintXml, tree.FilePath)));
/// <summary>
/// Reads configuration from SonarProjectConfig.xml file and caches the result for scope of this analysis.
/// </summary>
public ProjectConfigReader ProjectConfiguration()
{
if (Options.SonarProjectConfig() is { } sonarProjectConfig)
{
return sonarProjectConfig.GetText() is { } sourceText
// TryGetValue catches all exceptions from SourceTextValueProvider and returns false when exception is thrown
&& AnalysisContext.TryGetValue(sourceText, ProjectConfigProvider, out var cachedProjectConfigReader)
? cachedProjectConfigReader
: throw new InvalidOperationException($"File '{Path.GetFileName(sonarProjectConfig.Path)}' has been added as an AdditionalFile but could not be read and parsed.");
}
else
{
return ProjectConfigReader.Empty;
}
}
/// <summary>
/// Reads the properties from the SonarLint.xml file and caches the result for the scope of this analysis.
/// </summary>
public SonarLintXmlReader SonarLintXml()
{
if (Options.SonarLintXml() is { } sonarLintXml)
{
return sonarLintXml.GetText() is { } sourceText
&& AnalysisContext.TryGetValue(sourceText, SonarLintXmlProvider, out var sonarLintXmlReader)
? sonarLintXmlReader
: throw new InvalidOperationException($"File '{Path.GetFileName(sonarLintXml.Path)}' has been added as an AdditionalFile but could not be read and parsed.");
}
else
{
return Helpers.SonarLintXmlReader.Empty;
}
}
public bool IsTestProject()
{
var projectType = ProjectConfiguration().ProjectType;
return projectType == ProjectType.Unknown
? Compilation.IsTest() // SonarLint, NuGet or Scanner <= 5.0
: projectType == ProjectType.Test; // Scanner >= 5.1 does authoritative decision that we follow
}
public bool IsUnchanged(SyntaxTree tree) =>
UnchangedFilesCache.GetValue(Compilation, _ => CreateUnchangedFilesHashSet()).Contains(tree.FilePath);
public bool HasMatchingScope(IEnumerable<DiagnosticDescriptor> descriptors) =>
descriptors.Any(HasMatchingScope);
public bool HasMatchingScope(DiagnosticDescriptor descriptor)
{
// MMF-2297: Test Code as 1st Class Citizen is not ready on server side yet.
// ScannerRun: Only utility rules and rules with TEST-ONLY scope are executed for test projects for now.
// SonarLint & Standalone NuGet: Respect the scope as before.
return IsTestProject()
? ContainsTag(TestSourceScopeTag) && !(ProjectConfiguration().IsScannerRun && ContainsTag(MainSourceScopeTag) && !ContainsTag(UtilityTag))
: ContainsTag(MainSourceScopeTag);
bool ContainsTag(string tag) =>
descriptor.CustomTags.Contains(tag);
}
private bool IsExcluded(SonarLintXmlReader sonarLintXml, string filePath) =>
// If ProjectType is not 'Unknown' it means we are in S4NET context and all files are analyzed.
// If ProjectType is 'Unknown' then we are in SonarLint or NuGet context and we need to check if the file has been excluded from analysis through SonarLint.xml.
ProjectConfiguration().ProjectType == ProjectType.Unknown
&& !FileInclusionCache.GetValue(Compilation, _ => new()).GetOrAdd(filePath, _ => sonarLintXml.IsFileIncluded(filePath, IsTestProject()));
private ImmutableHashSet<string> CreateUnchangedFilesHashSet() =>
ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase, ProjectConfiguration().AnalysisConfig?.UnchangedFiles() ?? Array.Empty<string>());
}