-
Notifications
You must be signed in to change notification settings - Fork 77
/
ErrorListHelper.cs
220 lines (183 loc) · 8.13 KB
/
ErrorListHelper.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
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2024 SonarSource SA
* mailto:info 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;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.TableControl;
using Microsoft.VisualStudio.Shell.TableManager;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Suppressions;
namespace SonarLint.VisualStudio.Infrastructure.VS
{
[Export(typeof(IErrorListHelper))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class ErrorListHelper : IErrorListHelper
{
private readonly IVsUIServiceOperation vSServiceOperation;
[ImportingConstructor]
public ErrorListHelper(IVsUIServiceOperation vSServiceOperation)
{
this.vSServiceOperation = vSServiceOperation;
}
public bool TryGetRuleIdFromSelectedRow(out SonarCompositeRuleId ruleId)
{
SonarCompositeRuleId ruleIdOut = null;
var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(errorList =>
TryGetSelectedTableEntry(errorList, out var handle) && TryGetRuleId(handle, out ruleIdOut));
ruleId = ruleIdOut;
return result;
}
public bool TryGetRuleId(ITableEntryHandle handle, out SonarCompositeRuleId ruleId)
{
ruleId = null;
if (!handle.TryGetSnapshot(out var snapshot, out var index))
{
return false;
}
var errorCode = FindErrorCodeForEntry(snapshot, index);
return SonarCompositeRuleId.TryParse(errorCode, out ruleId);
}
public bool TryGetRuleIdAndSuppressionStateFromSelectedRow(out SonarCompositeRuleId ruleId, out bool isSuppressed)
{
SonarCompositeRuleId ruleIdOut = null;
var isSuppressedOut = false;
var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(errorList =>
{
if (!TryGetSelectedTableEntry(errorList, out var handle) || !TryGetRuleId(handle, out ruleIdOut))
{
return false;
}
isSuppressedOut = IsSuppressed(handle);
return true;
});
ruleId = ruleIdOut;
isSuppressed = isSuppressedOut;
return result;
}
public bool TryGetIssueFromSelectedRow(out IFilterableIssue issue)
{
IFilterableIssue issueOut = null;
var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(
errorList => TryGetSelectedSnapshotAndIndex(errorList, out var snapshot, out var index)
&& TryGetValue(snapshot, index, SonarLintTableControlConstants.IssueVizColumnName, out issueOut));
issue = issueOut;
return result;
}
public bool TryGetRoslynIssueFromSelectedRow(out IFilterableRoslynIssue filterableRoslynIssue)
{
IFilterableRoslynIssue outIssue = null;
var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(errorList =>
{
string errorCode;
if (TryGetSelectedSnapshotAndIndex(errorList, out var snapshot, out var index)
&& (errorCode = FindErrorCodeForEntry(snapshot, index)) != null
&& TryGetValue(snapshot, index, StandardTableKeyNames.DocumentName, out string filePath)
&& TryGetValue(snapshot, index, StandardTableKeyNames.Line, out int line)
&& TryGetValue(snapshot, index, StandardTableKeyNames.Column, out int column))
{
outIssue = new FilterableRoslynIssue(errorCode, filePath, line + 1, column + 1 /* error list issues are 0-based and we use 1-based line & column numbers */);
}
return outIssue != null;
});
filterableRoslynIssue = outIssue;
return result;
}
private static bool IsSuppressed(ITableEntryHandle handle)
{
return handle.TryGetSnapshot(out var snapshot, out var index)
&& TryGetValue(snapshot, index, StandardTableKeyNames.SuppressionState, out SuppressionState suppressionState)
&& suppressionState == SuppressionState.Suppressed;
}
private static string FindErrorCodeForEntry(ITableEntriesSnapshot snapshot, int index)
{
if (!TryGetValue(snapshot, index, StandardTableKeyNames.ErrorCode, out string errorCode))
{
return null;
}
if (TryGetValue(snapshot, index, StandardTableKeyNames.BuildTool, out string buildTool))
{
// For CSharp and VisualBasic the buildTool returns the name of the analyzer package.
// The prefix is required for roslyn languages as the error code is in style "S111" meaning
// unlike other languages it has no repository prefix.
return buildTool switch
{
"SonarAnalyzer.CSharp" => $"{SonarRuleRepoKeys.CSharpRules}:{errorCode}",
"SonarAnalyzer.VisualBasic" => $"{SonarRuleRepoKeys.VBNetRules}:{errorCode}",
"SonarLint" => errorCode,
_ => null
};
}
if (TryGetValue(snapshot, index, StandardTableKeyNames.HelpLink, out string helpLink))
{
if (helpLink.Contains("rules.sonarsource.com/csharp/"))
{
return $"{SonarRuleRepoKeys.CSharpRules}:{errorCode}";
}
if (helpLink.Contains("rules.sonarsource.com/vbnet/"))
{
return $"{SonarRuleRepoKeys.VBNetRules}:{errorCode}";
}
}
return null;
}
private static bool TryGetSelectedSnapshotAndIndex(IErrorList errorList, out ITableEntriesSnapshot snapshot, out int index)
{
snapshot = default;
index = default;
return TryGetSelectedTableEntry(errorList, out var handle) && handle.TryGetSnapshot(out snapshot, out index);
}
private static bool TryGetSelectedTableEntry(IErrorList errorList, out ITableEntryHandle handle)
{
handle = null;
var selectedItems = errorList?.TableControl?.SelectedEntries;
if (selectedItems == null)
{
return false;
}
foreach (var tableEntryHandle in selectedItems)
{
if (handle != null)
{
return false; // more than one selected is not supported
}
handle = tableEntryHandle;
}
return true;
}
private static bool TryGetValue<T>(ITableEntriesSnapshot snapshot, int index, string columnName, out T value)
{
value = default;
try
{
if (!snapshot.TryGetValue(index, columnName, out var objValue) || objValue == null)
{
return false;
}
value = (T)objValue;
return true;
}
catch (InvalidCastException)
{
return false;
}
}
}
}