AnalyzerHelper.cs 14.2 KB
Newer Older
S
Sam Harwell 已提交
1
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
H
heejaechang 已提交
2

M
Manish Vasani 已提交
3
using System;
4
using System.Linq;
5
using System.Reflection;
M
Manish Vasani 已提交
6
using Microsoft.CodeAnalysis.ErrorReporting;
7
using Microsoft.CodeAnalysis.Options;
8
using Roslyn.Utilities;
9
using System.IO;
J
Jonathon Marolf 已提交
10 11
using System.Threading;
using System.Threading.Tasks;
12
using System.Collections.Generic;
H
Heejae Chang 已提交
13 14 15
using Microsoft.CodeAnalysis.Diagnostics.Log;
using Microsoft.CodeAnalysis.Diagnostics.Telemetry;
using System.Collections.Immutable;
M
Manish Vasani 已提交
16

H
heejaechang 已提交
17 18 19 20 21 22 23
namespace Microsoft.CodeAnalysis.Diagnostics
{
    internal static class AnalyzerHelper
    {
        private const string CSharpCompilerAnalyzerTypeName = "Microsoft.CodeAnalysis.Diagnostics.CSharp.CSharpCompilerDiagnosticAnalyzer";
        private const string VisualBasicCompilerAnalyzerTypeName = "Microsoft.CodeAnalysis.Diagnostics.VisualBasic.VisualBasicCompilerDiagnosticAnalyzer";

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
        // These are the error codes of the compiler warnings. 
        // Keep the ids the same so that de-duplication against compiler errors
        // works in the error list (after a build).
        internal const string WRN_AnalyzerCannotBeCreatedIdCS = "CS8032";
        internal const string WRN_AnalyzerCannotBeCreatedIdVB = "BC42376";
        internal const string WRN_NoAnalyzerInAssemblyIdCS = "CS8033";
        internal const string WRN_NoAnalyzerInAssemblyIdVB = "BC42377";
        internal const string WRN_UnableToLoadAnalyzerIdCS = "CS8034";
        internal const string WRN_UnableToLoadAnalyzerIdVB = "BC42378";

        // Shared with Compiler
        internal const string AnalyzerExceptionDiagnosticId = "AD0001";
        internal const string AnalyzerDriverExceptionDiagnosticId = "AD0002";

        // IDE only errors
        internal const string WRN_AnalyzerCannotBeCreatedId = "AD1000";
        internal const string WRN_NoAnalyzerInAssemblyId = "AD1001";
        internal const string WRN_UnableToLoadAnalyzerId = "AD1002";

43 44
        private const string AnalyzerExceptionDiagnosticCategory = "Intellisense";

45 46 47 48 49
        public static bool IsWorkspaceDiagnosticAnalyzer(this DiagnosticAnalyzer analyzer)
        {
            return analyzer is DocumentDiagnosticAnalyzer || analyzer is ProjectDiagnosticAnalyzer;
        }

50
        public static bool IsBuiltInAnalyzer(this DiagnosticAnalyzer analyzer)
H
heejaechang 已提交
51
        {
52 53 54
            return analyzer is IBuiltInAnalyzer || analyzer.IsWorkspaceDiagnosticAnalyzer() || analyzer.IsCompilerAnalyzer();
        }

55
        public static bool IsOpenFileOnly(this DiagnosticAnalyzer analyzer, Workspace workspace)
56
        {
C
CyrusNajmabadi 已提交
57
            if (analyzer is IBuiltInAnalyzer builtInAnalyzer)
58
            {
59 60 61 62 63 64
                return builtInAnalyzer.OpenFileOnly(workspace);
            }

            return false;
        }

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
        public static bool ContainsOpenFileOnlyAnalyzers(this CompilationWithAnalyzers analyzerDriverOpt, Workspace workspace)
        {
            if (analyzerDriverOpt == null)
            {
                // not Roslyn. no open file only analyzers
                return false;
            }

            foreach (var analyzer in analyzerDriverOpt.Analyzers)
            {
                if (analyzer.IsOpenFileOnly(workspace))
                {
                    return true;
                }
            }

            return false;
        }

H
Heejae Chang 已提交
84
        public static bool HasNonHiddenDescriptor(this DiagnosticAnalyzerService service, DiagnosticAnalyzer analyzer, Project project)
85
        {
86 87 88 89 90 91 92 93 94 95 96 97 98 99
            // most of analyzers, number of descriptor is quite small, so this should be cheap.
            return service.GetDiagnosticDescriptors(analyzer).Any(d => d.GetEffectiveSeverity(project.CompilationOptions) != ReportDiagnostic.Hidden);
        }

        public static ReportDiagnostic GetEffectiveSeverity(this DiagnosticDescriptor descriptor, CompilationOptions options)
        {
            return options == null
                ? descriptor.DefaultSeverity.MapSeverityToReport()
                : descriptor.GetEffectiveSeverity(options);
        }

        public static ReportDiagnostic MapSeverityToReport(this DiagnosticSeverity severity)
        {
            switch (severity)
100
            {
101 102 103 104 105 106 107 108 109
                case DiagnosticSeverity.Hidden:
                    return ReportDiagnostic.Hidden;
                case DiagnosticSeverity.Info:
                    return ReportDiagnostic.Info;
                case DiagnosticSeverity.Warning:
                    return ReportDiagnostic.Warn;
                case DiagnosticSeverity.Error:
                    return ReportDiagnostic.Error;
                default:
110
                    throw ExceptionUtilities.UnexpectedValue(severity);
111
            }
H
heejaechang 已提交
112 113
        }

114
        public static bool IsCompilerAnalyzer(this DiagnosticAnalyzer analyzer)
H
heejaechang 已提交
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
        {
            // TODO: find better way.
            var typeString = analyzer.GetType().ToString();
            if (typeString == CSharpCompilerAnalyzerTypeName)
            {
                return true;
            }

            if (typeString == VisualBasicCompilerAnalyzerTypeName)
            {
                return true;
            }

            return false;
        }
M
Manish Vasani 已提交
130

131
        public static (string analyzerId, VersionStamp version) GetAnalyzerIdAndVersion(this DiagnosticAnalyzer analyzer)
132 133 134
        {
            // Get the unique ID for given diagnostic analyzer.
            // note that we also put version stamp so that we can detect changed analyzer.
135
            var typeInfo = analyzer.GetType().GetTypeInfo();
136
            return (analyzer.GetAnalyzerId(), GetAnalyzerVersion(CorLightup.Desktop.GetAssemblyLocation(typeInfo.Assembly)));
137 138
        }

H
Heejae Chang 已提交
139 140
        public static string GetAnalyzerAssemblyName(this DiagnosticAnalyzer analyzer)
        {
141 142
            var typeInfo = analyzer.GetType().GetTypeInfo();
            return typeInfo.Assembly.GetName().Name;
H
Heejae Chang 已提交
143 144
        }

145 146
        [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/23582", OftenCompletesSynchronously = true)]
        public static ValueTask<OptionSet> GetDocumentOptionSetAsync(this AnalyzerOptions analyzerOptions, SyntaxTree syntaxTree, CancellationToken cancellationToken)
147
        {
148 149
            var workspaceAnalyzerOptions = analyzerOptions as WorkspaceAnalyzerOptions;
            if (workspaceAnalyzerOptions == null)
J
Jonathon Marolf 已提交
150
            {
151
                return new ValueTask<OptionSet>(default(OptionSet));
J
Jonathon Marolf 已提交
152 153
            }

154
            return workspaceAnalyzerOptions.GetDocumentOptionSetAsync(syntaxTree, cancellationToken);
155 156
        }

157
        internal static void OnAnalyzerException_NoTelemetryLogging(
158
            Exception ex,
M
Manish Vasani 已提交
159
            DiagnosticAnalyzer analyzer,
160
            Diagnostic diagnostic,
M
Manish Vasani 已提交
161
            AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource,
162
            ProjectId projectIdOpt)
M
Manish Vasani 已提交
163
        {
164
            if (diagnostic != null)
165
            {
166
                hostDiagnosticUpdateSource?.ReportAnalyzerDiagnostic(analyzer, diagnostic, hostDiagnosticUpdateSource?.Workspace, projectIdOpt);
167
            }
M
Manish Vasani 已提交
168

169
            if (IsBuiltInAnalyzer(analyzer))
170
            {
171 172 173 174 175 176 177 178 179
                FatalError.ReportWithoutCrashUnlessCanceled(ex);
            }
        }

        internal static void OnAnalyzerExceptionForSupportedDiagnostics(DiagnosticAnalyzer analyzer, Exception exception, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource)
        {
            if (exception is OperationCanceledException)
            {
                return;
180
            }
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

            var diagnostic = CreateAnalyzerExceptionDiagnostic(analyzer, exception);
            OnAnalyzerException_NoTelemetryLogging(exception, analyzer, diagnostic, hostDiagnosticUpdateSource, projectIdOpt: null);
        }

        /// <summary>
        /// Create a diagnostic for exception thrown by the given analyzer.
        /// </summary>
        /// <remarks>
        /// Keep this method in sync with "AnalyzerExecutor.CreateAnalyzerExceptionDiagnostic".
        /// </remarks>
        internal static Diagnostic CreateAnalyzerExceptionDiagnostic(DiagnosticAnalyzer analyzer, Exception e)
        {
            var analyzerName = analyzer.ToString();

            // TODO: It is not ideal to create a new descriptor per analyzer exception diagnostic instance.
            // However, until we add a LongMessage field to the Diagnostic, we are forced to park the instance specific description onto the Descriptor's Description field.
            // This requires us to create a new DiagnosticDescriptor instance per diagnostic instance.
            var descriptor = new DiagnosticDescriptor(AnalyzerExceptionDiagnosticId,
200 201 202
                title: FeaturesResources.User_Diagnostic_Analyzer_Failure,
                messageFormat: FeaturesResources.Analyzer_0_threw_an_exception_of_type_1_with_message_2,
                description: string.Format(FeaturesResources.Analyzer_0_threw_the_following_exception_colon_1, analyzerName, e.CreateDiagnosticDescription()),
203
                category: AnalyzerExceptionDiagnosticCategory,
204
                defaultSeverity: DiagnosticSeverity.Warning,
205 206 207 208
                isEnabledByDefault: true,
                customTags: WellKnownDiagnosticTags.AnalyzerException);

            return Diagnostic.Create(descriptor, Location.None, analyzerName, e.GetType(), e.Message);
M
Manish Vasani 已提交
209
        }
210

H
Heejae Chang 已提交
211
        private static VersionStamp GetAnalyzerVersion(string path)
212
        {
213
            if (path == null || !File.Exists(path))
214 215 216 217
            {
                return VersionStamp.Default;
            }

218
            return VersionStamp.Create(File.GetLastWriteTimeUtc(path));
219
        }
220 221 222 223 224 225 226 227 228

        public static DiagnosticData CreateAnalyzerLoadFailureDiagnostic(string fullPath, AnalyzerLoadFailureEventArgs e)
        {
            return CreateAnalyzerLoadFailureDiagnostic(null, null, null, fullPath, e);
        }

        public static DiagnosticData CreateAnalyzerLoadFailureDiagnostic(
            Workspace workspace, ProjectId projectId, string language, string fullPath, AnalyzerLoadFailureEventArgs e)
        {
C
CyrusNajmabadi 已提交
229
            if (!TryGetErrorMessage(language, fullPath, e, out var id, out var message, out var messageFormat, out var description))
230 231 232 233 234 235
            {
                return null;
            }

            return new DiagnosticData(
                id,
236
                FeaturesResources.Roslyn_HostError,
237 238 239 240
                message,
                messageFormat,
                severity: DiagnosticSeverity.Warning,
                isEnabledByDefault: true,
241
                description: description,
242 243 244 245 246 247 248
                warningLevel: 0,
                workspace: workspace,
                projectId: projectId);
        }

        private static bool TryGetErrorMessage(
            string language, string fullPath, AnalyzerLoadFailureEventArgs e,
249
            out string id, out string message, out string messageFormat, out string description)
250 251 252 253 254
        {
            switch (e.ErrorCode)
            {
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToLoadAnalyzer:
                    id = Choose(language, WRN_UnableToLoadAnalyzerId, WRN_UnableToLoadAnalyzerIdCS, WRN_UnableToLoadAnalyzerIdVB);
255 256
                    messageFormat = FeaturesResources.Unable_to_load_Analyzer_assembly_0_colon_1;
                    message = string.Format(FeaturesResources.Unable_to_load_Analyzer_assembly_0_colon_1, fullPath, e.Message);
257
                    description = e.Exception.CreateDiagnosticDescription();
258 259 260
                    break;
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToCreateAnalyzer:
                    id = Choose(language, WRN_AnalyzerCannotBeCreatedId, WRN_AnalyzerCannotBeCreatedIdCS, WRN_AnalyzerCannotBeCreatedIdVB);
261 262
                    messageFormat = FeaturesResources.An_instance_of_analyzer_0_cannot_be_created_from_1_colon_2;
                    message = string.Format(FeaturesResources.An_instance_of_analyzer_0_cannot_be_created_from_1_colon_2, e.TypeName, fullPath, e.Message);
263
                    description = e.Exception.CreateDiagnosticDescription();
264 265 266
                    break;
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.NoAnalyzers:
                    id = Choose(language, WRN_NoAnalyzerInAssemblyId, WRN_NoAnalyzerInAssemblyIdCS, WRN_NoAnalyzerInAssemblyIdVB);
267 268
                    messageFormat = FeaturesResources.The_assembly_0_does_not_contain_any_analyzers;
                    message = string.Format(FeaturesResources.The_assembly_0_does_not_contain_any_analyzers, fullPath);
269
                    description = e.Exception.CreateDiagnosticDescription();
270 271 272 273 274 275
                    break;
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.None:
                default:
                    id = string.Empty;
                    message = string.Empty;
                    messageFormat = string.Empty;
276
                    description = string.Empty;
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
                    return false;
            }

            return true;
        }

        private static string Choose(string language, string noLanguageMessage, string csharpMessage, string vbMessage)
        {
            if (language == null)
            {
                return noLanguageMessage;
            }

            return language == LanguageNames.CSharp ? csharpMessage : vbMessage;
        }
292 293 294 295 296 297 298 299 300

        public static void AppendAnalyzerMap(this Dictionary<string, DiagnosticAnalyzer> analyzerMap, IEnumerable<DiagnosticAnalyzer> analyzers)
        {
            foreach (var analyzer in analyzers)
            {
                // user might have included exact same analyzer twice as project analyzers explicitly. we consider them as one
                analyzerMap[analyzer.GetAnalyzerId()] = analyzer;
            }
        }
H
Heejae Chang 已提交
301 302 303 304 305 306 307 308 309 310

        public static IEnumerable<AnalyzerPerformanceInfo> ToAnalyzerPerformanceInfo(this IDictionary<DiagnosticAnalyzer, AnalyzerTelemetryInfo> analysisResult, IDiagnosticAnalyzerService serviceOpt = null)
        {
            return Convert(analysisResult.Select(kv => (kv.Key, kv.Value.ExecutionTime)), serviceOpt);
        }

        private static IEnumerable<AnalyzerPerformanceInfo> Convert(IEnumerable<(DiagnosticAnalyzer analyzer, TimeSpan timeSpan)> analyzerPerf, IDiagnosticAnalyzerService serviceOpt = null)
        {
            return analyzerPerf.Select(kv => new AnalyzerPerformanceInfo(kv.analyzer.GetAnalyzerId(), DiagnosticAnalyzerLogger.AllowsTelemetry(kv.analyzer, serviceOpt), kv.timeSpan));
        }
H
heejaechang 已提交
311
    }
S
Sam Harwell 已提交
312
}