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

M
Manish Vasani 已提交
3
using System;
H
Heejae Chang 已提交
4
using System.IO;
5
using System.Linq;
6
using System.Reflection;
M
Manish Vasani 已提交
7
using Microsoft.CodeAnalysis.ErrorReporting;
8
using Roslyn.Utilities;
M
Manish Vasani 已提交
9

H
heejaechang 已提交
10 11 12 13 14 15 16
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";

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
        // 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";

36 37
        private const string AnalyzerExceptionDiagnosticCategory = "Intellisense";

H
Heejae Chang 已提交
38 39 40
        // Description separator
        private static readonly string Separator = Environment.NewLine + "-----" + Environment.NewLine;

41
        public static bool IsBuiltInAnalyzer(this DiagnosticAnalyzer analyzer)
H
heejaechang 已提交
42
        {
43
            return analyzer is IBuiltInAnalyzer || analyzer is DocumentDiagnosticAnalyzer || analyzer is ProjectDiagnosticAnalyzer || analyzer.IsCompilerAnalyzer();
H
heejaechang 已提交
44 45
        }

46
        public static bool IsCompilerAnalyzer(this DiagnosticAnalyzer analyzer)
H
heejaechang 已提交
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
        {
            // TODO: find better way.
            var typeString = analyzer.GetType().ToString();
            if (typeString == CSharpCompilerAnalyzerTypeName)
            {
                return true;
            }

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

            return false;
        }
M
Manish Vasani 已提交
62

63
        public static ValueTuple<string, VersionStamp> GetAnalyzerIdAndVersion(this DiagnosticAnalyzer analyzer)
64 65 66 67
        {
            // Get the unique ID for given diagnostic analyzer.
            // note that we also put version stamp so that we can detect changed analyzer.
            var type = analyzer.GetType();
68 69
            var typeInfo = type.GetTypeInfo();
            return ValueTuple.Create(GetAssemblyQualifiedName(type), GetAnalyzerVersion(CorLightup.Desktop.GetAssemblyLocation(typeInfo.Assembly)));
70 71
        }

H
Heejae Chang 已提交
72 73
        public static string GetAnalyzerAssemblyName(this DiagnosticAnalyzer analyzer)
        {
74 75
            var typeInfo = analyzer.GetType().GetTypeInfo();
            return typeInfo.Assembly.GetName().Name;
H
Heejae Chang 已提交
76 77
        }

78
        private static string GetAssemblyQualifiedName(Type type)
79
        {
80 81 82
            // AnalyzerFileReference now includes things like versions, public key as part of its identity. 
            // so we need to consider them.
            return type.AssemblyQualifiedName;
83 84
        }

85
        internal static void OnAnalyzerException_NoTelemetryLogging(
86
            Exception ex,
M
Manish Vasani 已提交
87
            DiagnosticAnalyzer analyzer,
88
            Diagnostic diagnostic,
M
Manish Vasani 已提交
89
            AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource,
90
            ProjectId projectIdOpt)
M
Manish Vasani 已提交
91
        {
92
            if (diagnostic != null)
93
            {
94
                hostDiagnosticUpdateSource?.ReportAnalyzerDiagnostic(analyzer, diagnostic, hostDiagnosticUpdateSource?.Workspace, projectIdOpt);
95
            }
M
Manish Vasani 已提交
96

97
            if (IsBuiltInAnalyzer(analyzer))
98
            {
99 100 101 102 103 104 105 106 107
                FatalError.ReportWithoutCrashUnlessCanceled(ex);
            }
        }

        internal static void OnAnalyzerExceptionForSupportedDiagnostics(DiagnosticAnalyzer analyzer, Exception exception, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource)
        {
            if (exception is OperationCanceledException)
            {
                return;
108
            }
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127

            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,
128 129 130
                title: FeaturesResources.UserDiagnosticAnalyzerFailure,
                messageFormat: FeaturesResources.UserDiagnosticAnalyzerThrows,
                description: string.Format(FeaturesResources.UserDiagnosticAnalyzerThrowsDescription, analyzerName, e.ToString()),
131
                category: AnalyzerExceptionDiagnosticCategory,
132
                defaultSeverity: DiagnosticSeverity.Warning,
133 134 135 136
                isEnabledByDefault: true,
                customTags: WellKnownDiagnosticTags.AnalyzerException);

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

H
Heejae Chang 已提交
139
        private static VersionStamp GetAnalyzerVersion(string path)
140
        {
141
            if (path == null || !PortableShim.File.Exists(path))
142 143 144 145
            {
                return VersionStamp.Default;
            }

146
            return VersionStamp.Create(PortableShim.File.GetLastWriteTimeUtc(path));
147
        }
148 149 150 151 152 153 154 155 156

        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)
        {
157 158
            string id, message, messageFormat, description;
            if (!TryGetErrorMessage(language, fullPath, e, out id, out message, out messageFormat, out description))
159 160 161 162 163 164 165 166 167 168 169
            {
                return null;
            }

            return new DiagnosticData(
                id,
                FeaturesResources.ErrorCategory,
                message,
                messageFormat,
                severity: DiagnosticSeverity.Warning,
                isEnabledByDefault: true,
170
                description: description,
171 172 173 174 175 176 177
                warningLevel: 0,
                workspace: workspace,
                projectId: projectId);
        }

        private static bool TryGetErrorMessage(
            string language, string fullPath, AnalyzerLoadFailureEventArgs e,
178
            out string id, out string message, out string messageFormat, out string description)
179 180 181 182 183 184 185
        {
            switch (e.ErrorCode)
            {
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToLoadAnalyzer:
                    id = Choose(language, WRN_UnableToLoadAnalyzerId, WRN_UnableToLoadAnalyzerIdCS, WRN_UnableToLoadAnalyzerIdVB);
                    messageFormat = FeaturesResources.WRN_UnableToLoadAnalyzer;
                    message = string.Format(FeaturesResources.WRN_UnableToLoadAnalyzer, fullPath, e.Message);
186
                    description = CreateDescription(e.Exception);
187 188 189 190 191
                    break;
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToCreateAnalyzer:
                    id = Choose(language, WRN_AnalyzerCannotBeCreatedId, WRN_AnalyzerCannotBeCreatedIdCS, WRN_AnalyzerCannotBeCreatedIdVB);
                    messageFormat = FeaturesResources.WRN_AnalyzerCannotBeCreated;
                    message = string.Format(FeaturesResources.WRN_AnalyzerCannotBeCreated, e.TypeName, fullPath, e.Message);
192
                    description = CreateDescription(e.Exception);
193 194 195 196 197
                    break;
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.NoAnalyzers:
                    id = Choose(language, WRN_NoAnalyzerInAssemblyId, WRN_NoAnalyzerInAssemblyIdCS, WRN_NoAnalyzerInAssemblyIdVB);
                    messageFormat = FeaturesResources.WRN_NoAnalyzerInAssembly;
                    message = string.Format(FeaturesResources.WRN_NoAnalyzerInAssembly, fullPath);
198
                    description = CreateDescription(e.Exception);
199 200 201 202 203 204
                    break;
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.None:
                default:
                    id = string.Empty;
                    message = string.Empty;
                    messageFormat = string.Empty;
205
                    description = string.Empty;
206 207 208 209 210 211
                    return false;
            }

            return true;
        }

212 213 214 215 216 217
        private static string CreateDescription(Exception exception)
        {
            var aggregateException = exception as AggregateException;
            if (aggregateException != null)
            {
                var flattened = aggregateException.Flatten();
H
Heejae Chang 已提交
218
                return string.Join(Separator, flattened.InnerExceptions.Select(e => GetExceptionMessage(e)));
219 220 221 222
            }

            if (exception != null)
            {
H
Heejae Chang 已提交
223
                return string.Join(Separator, GetExceptionMessage(exception), CreateDescription(exception.InnerException));
224 225 226 227 228
            }

            return string.Empty;
        }

H
Heejae Chang 已提交
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
        private static string GetExceptionMessage(Exception exception)
        {
            var fileNotFoundException = exception as FileNotFoundException;
            if (fileNotFoundException == null)
            {
                return exception.Message;
            }

            var fusionLog = GetFusionLogIfPossible(fileNotFoundException);
            if (fusionLog == null)
            {
                return exception.Message;
            }

            return string.Join(Separator, fileNotFoundException.Message, fusionLog);
        }

        private static string GetFusionLogIfPossible(FileNotFoundException exception)
        {
            try
            {
                // since Feature is in portable layer, I am using reflection here. so that we can get
                // most detail info on desktop when analyzer is failed to load. otherwise, we either
                // don't put this information or need to do quite complex plumbing for quite simple thing
                // that is not in hot path.
                var info = exception.GetType().GetRuntimeProperty("FusionLog");
                return info.GetValue(exception) as string;
            }
            catch
            {
                return null;
            }
        }

263 264 265 266 267 268 269 270 271
        private static string Choose(string language, string noLanguageMessage, string csharpMessage, string vbMessage)
        {
            if (language == null)
            {
                return noLanguageMessage;
            }

            return language == LanguageNames.CSharp ? csharpMessage : vbMessage;
        }
H
heejaechang 已提交
272 273
    }
}