DiagnosticExtensions.cs 13.2 KB
Newer Older
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.
P
Pilchie 已提交
2 3 4 5

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
6
using System.Globalization;
P
Pilchie 已提交
7 8 9 10 11
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.Diagnostics;
12
using Microsoft.CodeAnalysis.Emit;
13
using Microsoft.CodeAnalysis.Test.Utilities;
14
using Roslyn.Test.Utilities;
15
using Roslyn.Utilities;
16
using Xunit;
P
Pilchie 已提交
17 18 19 20 21 22

namespace Microsoft.CodeAnalysis
{
    public static class DiagnosticExtensions
    {
        private const int EN_US = 1033;
23 24

        public static Action<Exception, DiagnosticAnalyzer, Diagnostic> FailFastOnAnalyzerException = (e, a, d) => FailFast.OnFatalException(e);
25

P
Pilchie 已提交
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
        /// <summary>
        /// This is obsolete. Use Verify instead.
        /// </summary>
        public static void VerifyErrorCodes(this IEnumerable<Diagnostic> actual, params DiagnosticDescription[] expected)
        {
            Verify(actual, expected, errorCodeOnly: true);
        }

        public static void VerifyErrorCodes(this ImmutableArray<Diagnostic> actual, params DiagnosticDescription[] expected)
        {
            VerifyErrorCodes((IEnumerable<Diagnostic>)actual, expected);
        }

        internal static void Verify(this DiagnosticBag actual, params DiagnosticDescription[] expected)
        {
            Verify(actual.AsEnumerable(), expected, errorCodeOnly: false);
        }

        public static void Verify(this IEnumerable<Diagnostic> actual, params DiagnosticDescription[] expected)
        {
            Verify(actual, expected, errorCodeOnly: false);
        }

49 50 51 52 53 54 55 56 57 58
        public static void Verify(this IEnumerable<Diagnostic> actual, bool fallbackToErrorCodeOnlyForNonEnglish, params DiagnosticDescription[] expected)
        {
            Verify(actual, expected, errorCodeOnly: fallbackToErrorCodeOnlyForNonEnglish && EnsureEnglishUICulture.PreferredOrNull != null);
        }

        public static void VerifyWithFallbackToErrorCodeOnlyForNonEnglish(this IEnumerable<Diagnostic> actual, params DiagnosticDescription[] expected)
        {
            Verify(actual, true, expected);
        }

P
Pilchie 已提交
59 60
        public static void Verify(this ImmutableArray<Diagnostic> actual, params DiagnosticDescription[] expected)
        {
61
            Verify((IEnumerable<Diagnostic>)actual, expected);
P
Pilchie 已提交
62 63
        }

64
        private static void Verify(IEnumerable<Diagnostic> actual, DiagnosticDescription[] expected, bool errorCodeOnly)
P
Pilchie 已提交
65 66 67
        {
            if (expected == null)
            {
68
                throw new ArgumentException("Must specify expected errors.", nameof(expected));
P
Pilchie 已提交
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
            }

            var unmatched = actual.Select(d => new DiagnosticDescription(d, errorCodeOnly)).ToList();

            // Try to match each of the 'expected' errors to one of the 'actual' ones.
            // If any of the expected errors don't appear, fail test.
            foreach (var d in expected)
            {
                int index = unmatched.IndexOf(d);
                if (index > -1)
                {
                    unmatched.RemoveAt(index);
                }
                else
                {
                    Assert.True(false, DiagnosticDescription.GetAssertText(expected, actual));
                }
            }

            // If any 'extra' errors appear that were not in the 'expected' list, fail test.
            if (unmatched.Count > 0)
            {
                Assert.True(false, DiagnosticDescription.GetAssertText(expected, actual));
            }
        }

        public static TCompilation VerifyDiagnostics<TCompilation>(this TCompilation c, params DiagnosticDescription[] expected)
            where TCompilation : Compilation
        {
            var diagnostics = c.GetDiagnostics();
            diagnostics.Verify(expected);
            return c;
        }

103
        public static void VerifyAnalyzerOccurrenceCount<TCompilation>(
M
Manish Vasani 已提交
104 105 106
            this TCompilation c,
            DiagnosticAnalyzer[] analyzers,
            int expectedCount,
107
            Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException = null)
108 109
            where TCompilation : Compilation
        {
110
            Assert.Equal(expectedCount, c.GetAnalyzerDiagnostics(analyzers, null, onAnalyzerException).Length);
111 112 113
        }

        public static TCompilation VerifyAnalyzerDiagnostics<TCompilation>(
M
Manish Vasani 已提交
114 115 116 117
                this TCompilation c,
                DiagnosticAnalyzer[] analyzers,
                AnalyzerOptions options = null,
                Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException = null,
118
                bool logAnalyzerExceptionAsDiagnostics = true,
119
                params DiagnosticDescription[] expected)
120 121
            where TCompilation : Compilation
        {
122
            ImmutableArray<Diagnostic> diagnostics;
123
            c = c.GetAnalyzerDiagnostics(analyzers, options, onAnalyzerException, logAnalyzerExceptionAsDiagnostics, diagnostics: out diagnostics);
124 125 126 127
            diagnostics.Verify(expected);
            return c; // note this is a new compilation
        }

128
        public static ImmutableArray<Diagnostic> GetAnalyzerDiagnostics<TCompilation>(
M
Manish Vasani 已提交
129 130 131
            this TCompilation c,
            DiagnosticAnalyzer[] analyzers,
            AnalyzerOptions options = null,
132 133
            Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException = null,
            bool logAnalyzerExceptionAsDiagnostics = true)
134 135
            where TCompilation : Compilation
        {
136
            ImmutableArray<Diagnostic> diagnostics;
137
            c = GetAnalyzerDiagnostics(c, analyzers, options, onAnalyzerException, logAnalyzerExceptionAsDiagnostics, out diagnostics);
138
            return diagnostics;
139 140
        }

141
        private static TCompilation GetAnalyzerDiagnostics<TCompilation>(
142
                this TCompilation c,
143
                DiagnosticAnalyzer[] analyzers,
144
                AnalyzerOptions options,
145
                Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException,
146
                bool logAnalyzerExceptionAsDiagnostics,
147 148 149
                out ImmutableArray<Diagnostic> diagnostics)
            where TCompilation : Compilation
        {
150
            var analyzersArray = analyzers.ToImmutableArray();
151

152
            var exceptionDiagnostics = new ConcurrentSet<Diagnostic>();
153

154
            if (onAnalyzerException == null)
155
            {
156
                if (logAnalyzerExceptionAsDiagnostics)
157
                {
158 159 160 161 162 163 164 165 166 167
                    onAnalyzerException = (ex, analyzer, diagnostic) =>
                    {
                        exceptionDiagnostics.Add(diagnostic);
                    };
                }
                else
                {
                    // We want unit tests to throw if any analyzer OR the driver throws, unless the test explicitly provides a delegate.
                    onAnalyzerException = FailFastOnAnalyzerException;
                }
168
            }
M
Manish Vasani 已提交
169

170
            Compilation newCompilation;
171
            var driver = AnalyzerDriver.CreateAndAttachToCompilation(c, analyzersArray, options, AnalyzerManager.Instance, onAnalyzerException, false, out newCompilation, CancellationToken.None);
172
            var discarded = newCompilation.GetDiagnostics();
173
            diagnostics = driver.GetDiagnosticsAsync(newCompilation).Result.AddRange(exceptionDiagnostics);
B
beep boop 已提交
174

175
            return (TCompilation)newCompilation; // note this is a new compilation
176 177
        }

178 179 180 181 182 183 184 185
        /// <summary>
        /// Given a set of compiler or <see cref="IDiagnosticAnalyzer"/> generated <paramref name="diagnostics"/>, returns the effective diagnostics after applying the below filters:
        /// 1) <see cref="CompilationOptions.SpecificDiagnosticOptions"/> specified for the given <paramref name="compilation"/>.
        /// 2) <see cref="CompilationOptions.GeneralDiagnosticOption"/> specified for the given <paramref name="compilation"/>.
        /// 3) Diagnostic suppression through applied <see cref="System.Diagnostics.CodeAnalysis.SuppressMessageAttribute"/>.
        /// 4) Pragma directives for the given <paramref name="compilation"/>.
        /// </summary>
        public static IEnumerable<Diagnostic> GetEffectiveDiagnostics(this Compilation compilation, IEnumerable<Diagnostic> diagnostics)
P
Pilchie 已提交
186
        {
187
            return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilation);
188 189 190 191 192 193
        }

        /// <summary>
        /// Returns true if all the diagnostics that can be produced by this analyzer are suppressed through options.
        /// <paramref name="continueOnError"/> says whether the caller would like the exception thrown by the analyzers to be handled or not. If true - Handles ; False - Not handled.
        /// </summary>
194
        public static bool IsDiagnosticAnalyzerSuppressed(this DiagnosticAnalyzer analyzer, CompilationOptions options)
195
        {
196
            return CompilationWithAnalyzers.IsDiagnosticAnalyzerSuppressed(analyzer, options);
P
Pilchie 已提交
197 198
        }

199
        public static TCompilation VerifyEmitDiagnostics<TCompilation>(this TCompilation c, EmitOptions options, params DiagnosticDescription[] expected)
P
Pilchie 已提交
200 201
            where TCompilation : Compilation
        {
202
            var pdbStream = MonoHelpers.IsRunningOnMono() ? null : new MemoryStream();
J
jaredpar 已提交
203
            c.Emit(new MemoryStream(), pdbStream: pdbStream, options: options).Diagnostics.Verify(expected);
P
Pilchie 已提交
204 205 206
            return c;
        }

207 208 209 210 211 212
        public static TCompilation VerifyEmitDiagnostics<TCompilation>(this TCompilation c, params DiagnosticDescription[] expected)
            where TCompilation : Compilation
        {
            return VerifyEmitDiagnostics(c, EmitOptions.Default, expected);
        }

P
Pilchie 已提交
213 214 215
        public static TCompilation VerifyEmitDiagnostics<TCompilation>(this TCompilation c, IEnumerable<ResourceDescription> manifestResources, params DiagnosticDescription[] expected)
            where TCompilation : Compilation
        {
216
            var pdbStream = MonoHelpers.IsRunningOnMono() ? null : new MemoryStream();
J
jaredpar 已提交
217
            c.Emit(new MemoryStream(), pdbStream: pdbStream, manifestResources: manifestResources).Diagnostics.Verify(expected);
P
Pilchie 已提交
218 219 220 221 222 223 224
            return c;
        }

        public static string Concat(this string[] str)
        {
            return str.Aggregate(new StringBuilder(), (sb, s) => sb.AppendLine(s), sb => sb.ToString());
        }
225 226 227 228 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

        public static DiagnosticAnalyzer GetCompilerDiagnosticAnalyzer(string languageName)
        {
            return languageName == LanguageNames.CSharp ?
                (DiagnosticAnalyzer)new Diagnostics.CSharp.CSharpCompilerDiagnosticAnalyzer() :
                new Diagnostics.VisualBasic.VisualBasicCompilerDiagnosticAnalyzer();
        }

        public static ImmutableDictionary<string, ImmutableArray<DiagnosticAnalyzer>> GetCompilerDiagnosticAnalyzersMap()
        {
            var builder = ImmutableDictionary.CreateBuilder<string, ImmutableArray<DiagnosticAnalyzer>>();
            builder.Add(LanguageNames.CSharp, ImmutableArray.Create(GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp)));
            builder.Add(LanguageNames.VisualBasic, ImmutableArray.Create(GetCompilerDiagnosticAnalyzer(LanguageNames.VisualBasic)));
            return builder.ToImmutable();
        }

        public static AnalyzerReference GetCompilerDiagnosticAnalyzerReference(string languageName)
        {
            var analyzer = GetCompilerDiagnosticAnalyzer(languageName);
            return new AnalyzerImageReference(ImmutableArray.Create(analyzer), display: analyzer.GetType().FullName);
        }

        public static ImmutableDictionary<string, ImmutableArray<AnalyzerReference>> GetCompilerDiagnosticAnalyzerReferencesMap()
        {
            var builder = ImmutableDictionary.CreateBuilder<string, ImmutableArray<AnalyzerReference>>();
            builder.Add(LanguageNames.CSharp, ImmutableArray.Create(GetCompilerDiagnosticAnalyzerReference(LanguageNames.CSharp)));
            builder.Add(LanguageNames.VisualBasic, ImmutableArray.Create(GetCompilerDiagnosticAnalyzerReference(LanguageNames.VisualBasic)));
            return builder.ToImmutable();
        }
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269

        internal static string GetExpectedErrorLogHeader(string actualOutput, CommonCompiler compiler)
        {
            var expectedToolName = compiler.GetToolName();
            var expectedProductVersion = compiler.GetAssemblyVersion().ToString(fieldCount: 3);
            var fileVersion = compiler.GetAssemblyFileVersion();
            var expectedFileVersion = fileVersion.Substring(0, fileVersion.LastIndexOf('.'));

            return string.Format(@"{{
  ""version"": ""{0}"",
  ""toolInfo"": {{
    ""toolName"": ""{1}"",
    ""productVersion"": ""{2}"",
    ""fileVersion"": ""{3}""
  }},", ErrorLogger.OutputFormatVersion, expectedToolName, expectedProductVersion, expectedFileVersion);
        }
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289

        public static string Stringize(this Diagnostic e)
        {
            var retVal = string.Empty;
            if (e.Location.IsInSource)
            {
                retVal = e.Location.SourceSpan.ToString() + ": ";
            }
            else if (e.Location.IsInMetadata)
            {
                return "metadata: ";
            }
            else
            {
                return "no location: ";
            }

            retVal = e.Severity.ToString() + " " + e.Id + ": " + e.GetMessage(CultureInfo.CurrentCulture);
            return retVal;
        }
P
Pilchie 已提交
290 291
    }
}