AnalyzerHelper.cs 36.1 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
H
heejaechang 已提交
4

5 6
#nullable enable

M
Manish Vasani 已提交
7
using System;
8 9
using System.Collections.Generic;
using System.Collections.Immutable;
10
using System.Diagnostics;
11
using System.IO;
12
using System.Linq;
13
using System.Reflection;
J
Jonathon Marolf 已提交
14 15
using System.Threading;
using System.Threading.Tasks;
H
Heejae Chang 已提交
16
using Microsoft.CodeAnalysis.Diagnostics.Telemetry;
17
using Microsoft.CodeAnalysis.ErrorReporting;
18
using Microsoft.CodeAnalysis.Internal.Log;
19
using Microsoft.CodeAnalysis.Options;
20
using Microsoft.CodeAnalysis.Shared.Extensions;
21
using Microsoft.CodeAnalysis.Text;
22
using Roslyn.Utilities;
M
Manish Vasani 已提交
23

H
heejaechang 已提交
24 25
namespace Microsoft.CodeAnalysis.Diagnostics
{
26
    internal static partial class AnalyzerHelper
H
heejaechang 已提交
27 28 29 30
    {
        private const string CSharpCompilerAnalyzerTypeName = "Microsoft.CodeAnalysis.Diagnostics.CSharp.CSharpCompilerDiagnosticAnalyzer";
        private const string VisualBasicCompilerAnalyzerTypeName = "Microsoft.CodeAnalysis.Diagnostics.VisualBasic.VisualBasicCompilerDiagnosticAnalyzer";

31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
        // 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";

50 51
        private const string AnalyzerExceptionDiagnosticCategory = "Intellisense";

52
        public static bool IsWorkspaceDiagnosticAnalyzer(this DiagnosticAnalyzer analyzer)
53
            => analyzer is DocumentDiagnosticAnalyzer || analyzer is ProjectDiagnosticAnalyzer || analyzer == FileContentLoadAnalyzer.Instance;
54

55
        public static bool IsBuiltInAnalyzer(this DiagnosticAnalyzer analyzer)
56
            => analyzer is IBuiltInAnalyzer || analyzer.IsWorkspaceDiagnosticAnalyzer() || analyzer.IsCompilerAnalyzer();
57

58 59
        public static bool IsOpenFileOnly(this DiagnosticAnalyzer analyzer, OptionSet options)
            => analyzer is IBuiltInAnalyzer builtInAnalyzer && builtInAnalyzer.OpenFileOnly(options);
60

61 62 63
        public static ReportDiagnostic GetEffectiveSeverity(this DiagnosticDescriptor descriptor, CompilationOptions options)
        {
            return options == null
64
                ? descriptor.DefaultSeverity.ToReportDiagnostic()
65 66 67
                : descriptor.GetEffectiveSeverity(options);
        }

68
        public static bool IsCompilerAnalyzer(this DiagnosticAnalyzer analyzer)
H
heejaechang 已提交
69 70
        {
            // TODO: find better way.
71
            var typeString = analyzer.GetType().FullName;
H
heejaechang 已提交
72 73 74 75 76 77 78 79 80 81 82 83
            if (typeString == CSharpCompilerAnalyzerTypeName)
            {
                return true;
            }

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

            return false;
        }
M
Manish Vasani 已提交
84

85
        public static (string analyzerId, VersionStamp version) GetAnalyzerIdAndVersion(this DiagnosticAnalyzer analyzer)
86 87 88
        {
            // Get the unique ID for given diagnostic analyzer.
            // note that we also put version stamp so that we can detect changed analyzer.
89
            var typeInfo = analyzer.GetType().GetTypeInfo();
J
Jared Parsons 已提交
90
            return (analyzer.GetAnalyzerId(), GetAnalyzerVersion(typeInfo.Assembly.Location));
91 92
        }

H
Heejae Chang 已提交
93
        public static string GetAnalyzerAssemblyName(this DiagnosticAnalyzer analyzer)
94
            => analyzer.GetType().Assembly.GetName().Name;
H
Heejae Chang 已提交
95

96 97 98 99 100
        public static OptionSet GetAnalyzerOptionSet(this AnalyzerOptions analyzerOptions, SyntaxTree syntaxTree, CancellationToken cancellationToken)
        {
            return GetAnalyzerOptionSetAsync(analyzerOptions, syntaxTree, cancellationToken).GetAwaiter().GetResult();
        }

101 102 103
        public static async ValueTask<OptionSet> GetAnalyzerOptionSetAsync(this AnalyzerOptions analyzerOptions, SyntaxTree syntaxTree, CancellationToken cancellationToken)
        {
            var configOptions = analyzerOptions.AnalyzerConfigOptionsProvider.GetOptions(syntaxTree);
104
#pragma warning disable CS0612 // Type or member is obsolete
105
            var optionSet = await GetDocumentOptionSetAsync(analyzerOptions, syntaxTree, cancellationToken).ConfigureAwait(false);
106
#pragma warning restore CS0612 // Type or member is obsolete
107 108 109 110

            return new AnalyzerConfigOptionSet(configOptions, optionSet);
        }

111 112
        public static T GetOption<T>(this AnalyzerOptions analyzerOptions, Option<T> option, SyntaxTree syntaxTree, CancellationToken cancellationToken)
        {
113
            return GetOptionAsync<T>(analyzerOptions, option, language: null, syntaxTree, cancellationToken).GetAwaiter().GetResult();
114 115
        }

116
        public static T GetOption<T>(this AnalyzerOptions analyzerOptions, PerLanguageOption<T> option, string? language, SyntaxTree syntaxTree, CancellationToken cancellationToken)
117
        {
118
            return GetOptionAsync<T>(analyzerOptions, option, language, syntaxTree, cancellationToken).GetAwaiter().GetResult();
119 120
        }

121
        public static async ValueTask<T> GetOptionAsync<T>(this AnalyzerOptions analyzerOptions, IOption option, string? language, SyntaxTree syntaxTree, CancellationToken cancellationToken)
122
        {
123
            if (analyzerOptions.TryGetEditorConfigOption<T>(option, syntaxTree, out var value))
124 125 126 127
            {
                return value;
            }

128
#pragma warning disable CS0612 // Type or member is obsolete
129
            var optionSet = await analyzerOptions.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).ConfigureAwait(false);
130
#pragma warning restore CS0612 // Type or member is obsolete
J
Joey Robichaud 已提交
131
            return (T)optionSet?.GetOption(new OptionKey(option, language)) ?? (T)option.DefaultValue!;
132 133
        }

134
        [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/23582", OftenCompletesSynchronously = true)]
135
        public static ValueTask<OptionSet?> GetDocumentOptionSetAsync(this AnalyzerOptions analyzerOptions, SyntaxTree syntaxTree, CancellationToken cancellationToken)
136
        {
C
Cyrus Najmabadi 已提交
137
            if (!(analyzerOptions is WorkspaceAnalyzerOptions workspaceAnalyzerOptions))
J
Jonathon Marolf 已提交
138
            {
139
                return new ValueTask<OptionSet?>((OptionSet?)null);
J
Jonathon Marolf 已提交
140 141
            }

142
            return workspaceAnalyzerOptions.GetDocumentOptionSetAsync(syntaxTree, cancellationToken);
143 144
        }

145 146 147 148 149 150 151 152 153 154 155 156 157 158
        /// <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,
159 160 161
                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()),
162
                category: AnalyzerExceptionDiagnosticCategory,
163
                defaultSeverity: DiagnosticSeverity.Warning,
164 165 166 167
                isEnabledByDefault: true,
                customTags: WellKnownDiagnosticTags.AnalyzerException);

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

H
Heejae Chang 已提交
170
        private static VersionStamp GetAnalyzerVersion(string path)
171
        {
172
            if (path == null || !File.Exists(path))
173 174 175 176
            {
                return VersionStamp.Default;
            }

177
            return VersionStamp.Create(File.GetLastWriteTimeUtc(path));
178
        }
179

180
        public static DiagnosticData CreateAnalyzerLoadFailureDiagnostic(AnalyzerLoadFailureEventArgs e, string fullPath, ProjectId? projectId, string? language)
181
        {
182 183
            static string GetLanguageSpecificId(string? language, string noLanguageId, string csharpId, string vbId)
                => language == null ? noLanguageId : (language == LanguageNames.CSharp) ? csharpId : vbId;
184

185
            string id, messageFormat, message;
186 187 188 189

            switch (e.ErrorCode)
            {
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToLoadAnalyzer:
190
                    id = GetLanguageSpecificId(language, WRN_UnableToLoadAnalyzerId, WRN_UnableToLoadAnalyzerIdCS, WRN_UnableToLoadAnalyzerIdVB);
191 192
                    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);
193
                    break;
194

195
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToCreateAnalyzer:
196
                    id = GetLanguageSpecificId(language, WRN_AnalyzerCannotBeCreatedId, WRN_AnalyzerCannotBeCreatedIdCS, WRN_AnalyzerCannotBeCreatedIdVB);
197 198
                    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);
199
                    break;
200

201
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.NoAnalyzers:
202
                    id = GetLanguageSpecificId(language, WRN_NoAnalyzerInAssemblyId, WRN_NoAnalyzerInAssemblyIdCS, WRN_NoAnalyzerInAssemblyIdVB);
203 204
                    messageFormat = FeaturesResources.The_assembly_0_does_not_contain_any_analyzers;
                    message = string.Format(FeaturesResources.The_assembly_0_does_not_contain_any_analyzers, fullPath);
205
                    break;
206

207
                default:
208
                    throw ExceptionUtilities.UnexpectedValue(e.ErrorCode);
209 210
            }

211
            var description = e.Exception.CreateDiagnosticDescription();
212

213 214 215 216 217 218 219 220 221 222 223 224 225 226
            return new DiagnosticData(
                id,
                FeaturesResources.Roslyn_HostError,
                message,
                messageFormat,
                severity: DiagnosticSeverity.Warning,
                defaultSeverity: DiagnosticSeverity.Warning,
                isEnabledByDefault: true,
                description: description,
                warningLevel: 0,
                projectId: projectId,
                customTags: ImmutableArray<string>.Empty,
                properties: ImmutableDictionary<string, string>.Empty,
                language: language);
227
        }
228 229 230 231 232 233 234 235 236

        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 已提交
237

238
        public static IEnumerable<AnalyzerPerformanceInfo> ToAnalyzerPerformanceInfo(this IDictionary<DiagnosticAnalyzer, AnalyzerTelemetryInfo> analysisResult, DiagnosticAnalyzerInfoCache analyzerInfo)
H
Heejae Chang 已提交
239
        {
240
            return analysisResult.Select(kv => new AnalyzerPerformanceInfo(kv.Key.GetAnalyzerId(), analyzerInfo.IsTelemetryCollectionAllowed(kv.Key), kv.Value.ExecutionTime));
H
Heejae Chang 已提交
241 242
        }

243
        public static async Task<CompilationWithAnalyzers?> CreateCompilationWithAnalyzersAsync(
244 245 246 247
            Project project,
            IEnumerable<DiagnosticAnalyzer> analyzers,
            bool includeSuppressedDiagnostics,
            CancellationToken cancellationToken)
248
        {
249 250
            var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
            if (compilation == null)
251
            {
252
                // project doesn't support compilation
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
                return null;
            }

            // Create driver that holds onto compilation and associated analyzers
            var filteredAnalyzers = analyzers.Where(a => !a.IsWorkspaceDiagnosticAnalyzer()).ToImmutableArrayOrEmpty();

            // PERF: there is no analyzers for this compilation.
            //       compilationWithAnalyzer will throw if it is created with no analyzers which is perf optimization.
            if (filteredAnalyzers.IsEmpty)
            {
                return null;
            }

            Contract.ThrowIfFalse(project.SupportsCompilation);
            AssertCompilation(project, compilation);

269 270 271
            // in IDE, we always set concurrentAnalysis == false otherwise, we can get into thread starvation due to
            // async being used with synchronous blocking concurrency.
            var analyzerOptions = new CompilationWithAnalyzersOptions(
272
                options: new WorkspaceAnalyzerOptions(project.AnalyzerOptions, project.Solution),
273
                onAnalyzerException: null,
274 275 276 277
                analyzerExceptionFilter: GetAnalyzerExceptionFilter(),
                concurrentAnalysis: false,
                logAnalyzerExecutionTime: true,
                reportSuppressedDiagnostics: includeSuppressedDiagnostics);
278 279

            // Create driver that holds onto compilation and associated analyzers
280
            return compilation.WithAnalyzers(filteredAnalyzers, analyzerOptions);
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309

            Func<Exception, bool> GetAnalyzerExceptionFilter()
            {
                return ex =>
                {
                    if (project.Solution.Workspace.Options.GetOption(InternalDiagnosticsOptions.CrashOnAnalyzerException))
                    {
                        // if option is on, crash the host to get crash dump.
                        FatalError.ReportUnlessCanceled(ex);
                    }

                    return true;
                };
            }
        }

        [Conditional("DEBUG")]
        private static void AssertCompilation(Project project, Compilation compilation1)
        {
            // given compilation must be from given project.
            Contract.ThrowIfFalse(project.TryGetCompilation(out var compilation2));
            Contract.ThrowIfFalse(compilation1 == compilation2);
        }

        /// <summary>
        /// Return all local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer) by calculating them
        /// </summary>
        public static async Task<IEnumerable<DiagnosticData>> ComputeDiagnosticsAsync(
            DiagnosticAnalyzer analyzer,
310
            Document document,
311
            AnalysisKind kind,
312
            CompilationWithAnalyzers? compilationWithAnalyzers,
313
            TextSpan? span,
314 315
            CancellationToken cancellationToken)
        {
316 317 318 319 320 321 322 323 324 325 326 327 328 329
            var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false);

            if (analyzer == FileContentLoadAnalyzer.Instance)
            {
                return loadDiagnostic != null ?
                    SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(loadDiagnostic, document)) :
                    SpecializedCollections.EmptyEnumerable<DiagnosticData>();
            }

            if (loadDiagnostic != null)
            {
                return SpecializedCollections.EmptyEnumerable<DiagnosticData>();
            }

330 331 332
            if (analyzer is DocumentDiagnosticAnalyzer documentAnalyzer)
            {
                var diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(
333
                    documentAnalyzer, document, kind, compilationWithAnalyzers?.Compilation, cancellationToken).ConfigureAwait(false);
334

335 336 337
                return diagnostics.ConvertToLocalDiagnostics(document);
            }

338
            // quick optimization to reduce allocations.
339
            if (compilationWithAnalyzers == null || !analyzer.SupportAnalysisKind(kind))
340
            {
341
                if (kind == AnalysisKind.Syntax)
342
                {
343 344
                    Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic,
                        (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a}, {k}", compilationWithAnalyzers, document, analyzer, kind);
345 346
                }

347 348 349 350
                return SpecializedCollections.EmptyEnumerable<DiagnosticData>();
            }

            // if project is not loaded successfully then, we disable semantic errors for compiler analyzers
351
            if (kind != AnalysisKind.Syntax && analyzer.IsCompilerAnalyzer())
352 353
            {
                var isEnabled = await document.Project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false);
354

355
                Logger.Log(FunctionId.Diagnostics_SemanticDiagnostic, (a, d, e) => $"{a}, ({d.Id}, {d.Project.Id}), Enabled:{e}", analyzer, document, isEnabled);
356

357
                if (!isEnabled)
358
                {
359
                    return SpecializedCollections.EmptyEnumerable<DiagnosticData>();
360 361 362
                }
            }

363 364 365 366
            // REVIEW: more unnecessary allocations just to get diagnostics per analyzer
            var singleAnalyzer = ImmutableArray.Create(analyzer);

            switch (kind)
367
            {
368 369 370 371 372 373
                case AnalysisKind.Syntax:
                    var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
                    if (tree == null)
                    {
                        return SpecializedCollections.EmptyEnumerable<DiagnosticData>();
                    }
374

375
                    var diagnostics = await compilationWithAnalyzers.GetAnalyzerSyntaxDiagnosticsAsync(tree, singleAnalyzer, cancellationToken).ConfigureAwait(false);
376

377 378 379 380
                    if (diagnostics.IsDefaultOrEmpty)
                    {
                        Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, (d, a, t) => $"{d.Id}, {d.Project.Id}, {a}, {t.Length}", document, analyzer, tree);
                    }
381

382 383
                    Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationWithAnalyzers.Compilation).Count());
                    return diagnostics.ConvertToLocalDiagnostics(document);
384

385 386 387 388 389 390
                case AnalysisKind.Semantic:
                    var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
                    if (model == null)
                    {
                        return SpecializedCollections.EmptyEnumerable<DiagnosticData>();
                    }
391

392
                    diagnostics = await compilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(model, span, singleAnalyzer, cancellationToken).ConfigureAwait(false);
393

394 395
                    Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationWithAnalyzers.Compilation).Count());
                    return diagnostics.ConvertToLocalDiagnostics(document);
396

397 398
                default:
                    throw ExceptionUtilities.UnexpectedValue(kind);
399 400 401
            }
        }

402
        public static async Task<ImmutableArray<Diagnostic>> ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(
403
            DocumentDiagnosticAnalyzer analyzer,
404
            Document document,
405 406 407
            AnalysisKind kind,
            Compilation? compilation,
            CancellationToken cancellationToken)
408 409 410
        {
            cancellationToken.ThrowIfCancellationRequested();

411
            ImmutableArray<Diagnostic> diagnostics;
412 413
            try
            {
C
Cyrus Najmabadi 已提交
414
                var analyzeAsync = kind switch
415
                {
C
Cyrus Najmabadi 已提交
416 417 418 419
                    AnalysisKind.Syntax => analyzer.AnalyzeSyntaxAsync(document, cancellationToken),
                    AnalysisKind.Semantic => analyzer.AnalyzeSemanticsAsync(document, cancellationToken),
                    _ => throw ExceptionUtilities.UnexpectedValue(kind),
                };
420

421
                diagnostics = (await analyzeAsync.ConfigureAwait(false)).NullToEmpty();
422 423 424 425 426 427 428 429 430 431

#if DEBUG
                // since all DocumentDiagnosticAnalyzers are from internal users, we only do debug check. also this can be expensive at runtime
                // since it requires await. if we find any offender through NFW, we should be able to fix those since all those should
                // from intern teams.
                await VerifyDiagnosticLocationsAsync(diagnostics, document.Project, cancellationToken).ConfigureAwait(false);
#endif
            }
            catch (Exception e) when (!IsCanceled(e, cancellationToken))
            {
432 433 434 435 436 437
                diagnostics = ImmutableArray.Create(CreateAnalyzerExceptionDiagnostic(analyzer, e));
            }

            if (compilation != null)
            {
                diagnostics = CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilation).ToImmutableArrayOrEmpty();
438
            }
439 440

            return diagnostics;
441 442
        }

443
        public static async Task<ImmutableArray<Diagnostic>> ComputeProjectDiagnosticAnalyzerDiagnosticsAsync(
444
            ProjectDiagnosticAnalyzer analyzer,
445
            Project project,
446 447
            Compilation? compilation,
            CancellationToken cancellationToken)
448 449 450
        {
            cancellationToken.ThrowIfCancellationRequested();

451
            ImmutableArray<Diagnostic> diagnostics;
452 453
            try
            {
454
                diagnostics = (await analyzer.AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false)).NullToEmpty();
455 456 457 458 459 460 461 462 463
#if DEBUG
                // since all ProjectDiagnosticAnalyzers are from internal users, we only do debug check. also this can be expensive at runtime
                // since it requires await. if we find any offender through NFW, we should be able to fix those since all those should
                // from intern teams.
                await VerifyDiagnosticLocationsAsync(diagnostics, project, cancellationToken).ConfigureAwait(false);
#endif
            }
            catch (Exception e) when (!IsCanceled(e, cancellationToken))
            {
464
                diagnostics = ImmutableArray.Create(CreateAnalyzerExceptionDiagnostic(analyzer, e));
465 466
            }

467
            // Apply filtering from compilation options (source suppressions, ruleset, etc.)
468
            if (compilation != null)
469
            {
470
                diagnostics = CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilation).ToImmutableArrayOrEmpty();
471 472
            }

473 474 475 476 477 478
            return diagnostics;
        }

        private static bool IsCanceled(Exception ex, CancellationToken cancellationToken)
        {
            return (ex as OperationCanceledException)?.CancellationToken == cancellationToken;
479 480 481 482 483 484 485 486 487 488 489 490
        }

        private static async Task VerifyDiagnosticLocationsAsync(ImmutableArray<Diagnostic> diagnostics, Project project, CancellationToken cancellationToken)
        {
            foreach (var diagnostic in diagnostics)
            {
                await VerifyDiagnosticLocationAsync(diagnostic.Id, diagnostic.Location).ConfigureAwait(false);

                if (diagnostic.AdditionalLocations != null)
                {
                    foreach (var location in diagnostic.AdditionalLocations)
                    {
491
                        await VerifyDiagnosticLocationAsync(diagnostic.Id, location).ConfigureAwait(false);
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
                    }
                }
            }

            async Task VerifyDiagnosticLocationAsync(string id, Location location)
            {
                switch (location.Kind)
                {
                    case LocationKind.None:
                    case LocationKind.MetadataFile:
                    case LocationKind.XmlFile:
                        // ignore these kinds
                        break;
                    case LocationKind.SourceFile:
                        {
J
Jared Parsons 已提交
507
                            RoslynDebug.Assert(location.SourceTree != null);
D
dotnet-bot 已提交
508 509 510 511 512 513 514 515 516 517 518
                            if (project.GetDocument(location.SourceTree) == null)
                            {
                                // Disallow diagnostics with source locations outside this project.
                                throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_in_file_1_which_is_not_part_of_the_compilation_being_analyzed, id, location.SourceTree.FilePath), "diagnostic");
                            }

                            if (location.SourceSpan.End > location.SourceTree.Length)
                            {
                                // Disallow diagnostics with source locations outside this project.
                                throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_outside_of_the_given_file, id, location.SourceSpan, location.SourceTree.FilePath), "diagnostic");
                            }
519
                        }
D
dotnet-bot 已提交
520
                        break;
521 522
                    case LocationKind.ExternalFile:
                        {
D
dotnet-bot 已提交
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
                            var filePath = location.GetLineSpan().Path;
                            var document = TryGetDocumentWithFilePath(filePath);
                            if (document == null)
                            {
                                // this is not a roslyn file. we don't care about this file.
                                return;
                            }

                            // this can be potentially expensive since it will load text if it is not already loaded.
                            // but, this text is most likely already loaded since producer of this diagnostic (Document/ProjectDiagnosticAnalyzers)
                            // should have loaded it to produce the diagnostic at the first place. once loaded, it should stay in memory until
                            // project cache goes away. when text is already there, await should return right away.
                            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
                            if (location.SourceSpan.End > text.Length)
                            {
                                // Disallow diagnostics with locations outside this project.
                                throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_outside_of_the_given_file, id, location.SourceSpan, filePath), "diagnostic");
                            }
541
                        }
D
dotnet-bot 已提交
542
                        break;
543 544 545 546 547
                    default:
                        throw ExceptionUtilities.Unreachable;
                }
            }

548
            Document? TryGetDocumentWithFilePath(string path)
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
            {
                foreach (var documentId in project.Solution.GetDocumentIdsWithFilePath(path))
                {
                    if (documentId.ProjectId == project.Id)
                    {
                        return project.GetDocument(documentId);
                    }
                }

                return null;
            }
        }

        public static IEnumerable<DiagnosticData> ConvertToLocalDiagnostics(this IEnumerable<Diagnostic> diagnostics, Document targetDocument, TextSpan? span = null)
        {
            var project = targetDocument.Project;

            if (project.SupportsCompilation)
            {
                return ConvertToLocalDiagnosticsWithCompilation();
            }

            return ConvertToLocalDiagnosticsWithoutCompilation();

            IEnumerable<DiagnosticData> ConvertToLocalDiagnosticsWithoutCompilation()
            {
                Contract.ThrowIfTrue(project.SupportsCompilation);

                foreach (var diagnostic in diagnostics)
                {
                    var location = diagnostic.Location;
                    if (location.Kind != LocationKind.ExternalFile)
                    {
                        continue;
                    }

                    var lineSpan = location.GetLineSpan();

                    var documentIds = project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path);
                    if (documentIds.IsEmpty || documentIds.All(id => id != targetDocument.Id))
                    {
                        continue;
                    }

593
                    yield return DiagnosticData.Create(diagnostic, targetDocument);
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
                }
            }

            IEnumerable<DiagnosticData> ConvertToLocalDiagnosticsWithCompilation()
            {
                Contract.ThrowIfFalse(project.SupportsCompilation);

                foreach (var diagnostic in diagnostics)
                {
                    var document = project.GetDocument(diagnostic.Location.SourceTree);
                    if (document == null || document != targetDocument)
                    {
                        continue;
                    }

                    if (span.HasValue && !span.Value.IntersectsWith(diagnostic.Location.SourceSpan))
                    {
                        continue;
                    }

614
                    yield return DiagnosticData.Create(diagnostic, document);
615 616 617 618
                }
            }
        }

619
        public static bool? IsCompilationEndAnalyzer(this DiagnosticAnalyzer analyzer, Project project, Compilation? compilation)
620 621 622 623 624 625 626 627
        {
            if (!project.SupportsCompilation)
            {
                return false;
            }

            Contract.ThrowIfNull(compilation);

628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
            try
            {
                // currently, this is only way to see whether analyzer has compilation end analysis or not.
                // also, analyzer being compilation end analyzer or not is dynamic. so this can return different value based on
                // given compilation or options.
                //
                // but for now, this is what we decided in design meeting until we decide how to deal with compilation end analyzer
                // long term
                var context = new CollectCompilationActionsContext(compilation, project.AnalyzerOptions);
                analyzer.Initialize(context);

                return context.IsCompilationEndAnalyzer;
            }
            catch
            {
                // analyzer.initialize can throw. when that happens, we will try again next time.
                // we are not logging anything here since it will be logged by CompilationWithAnalyzer later
                // in the error list
                return null;
            }
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708
        }

        /// <summary>
        /// Right now, there is no API compiler will tell us whether DiagnosticAnalyzer has compilation end analysis or not
        /// 
        /// </summary>
        private class CollectCompilationActionsContext : AnalysisContext
        {
            private readonly Compilation _compilation;
            private readonly AnalyzerOptions _analyzerOptions;

            public CollectCompilationActionsContext(Compilation compilation, AnalyzerOptions analyzerOptions)
            {
                _compilation = compilation;
                _analyzerOptions = analyzerOptions;
            }

            public bool IsCompilationEndAnalyzer { get; private set; } = false;

            public override void RegisterCompilationAction(Action<CompilationAnalysisContext> action)
            {
                if (action == null)
                {
                    return;
                }

                IsCompilationEndAnalyzer = true;
            }

            public override void RegisterCompilationStartAction(Action<CompilationStartAnalysisContext> action)
            {
                if (action == null)
                {
                    return;
                }

                var nestedContext = new CollectNestedCompilationContext(_compilation, _analyzerOptions, CancellationToken.None);
                action(nestedContext);

                IsCompilationEndAnalyzer |= nestedContext.IsCompilationEndAnalyzer;
            }

            #region not used
            public override void RegisterCodeBlockAction(Action<CodeBlockAnalysisContext> action) { }
            public override void RegisterCodeBlockStartAction<TLanguageKindEnum>(Action<CodeBlockStartAnalysisContext<TLanguageKindEnum>> action) { }
            public override void RegisterSemanticModelAction(Action<SemanticModelAnalysisContext> action) { }
            public override void RegisterSymbolAction(Action<SymbolAnalysisContext> action, ImmutableArray<SymbolKind> symbolKinds) { }
            public override void RegisterSyntaxNodeAction<TLanguageKindEnum>(Action<SyntaxNodeAnalysisContext> action, ImmutableArray<TLanguageKindEnum> syntaxKinds) { }
            public override void RegisterSyntaxTreeAction(Action<SyntaxTreeAnalysisContext> action) { }
            public override void ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags analysisMode) { }
            public override void EnableConcurrentExecution() { }
            public override void RegisterOperationAction(Action<OperationAnalysisContext> action, ImmutableArray<OperationKind> operationKinds) { }
            public override void RegisterOperationBlockAction(Action<OperationBlockAnalysisContext> action) { }
            public override void RegisterOperationBlockStartAction(Action<OperationBlockStartAnalysisContext> action) { }
            public override void RegisterSymbolStartAction(Action<SymbolStartAnalysisContext> action, SymbolKind symbolKind) { }
            #endregion

            private class CollectNestedCompilationContext : CompilationStartAnalysisContext
            {
                public bool IsCompilationEndAnalyzer { get; private set; } = false;

709 710
                public CollectNestedCompilationContext(Compilation compilation, AnalyzerOptions options, CancellationToken cancellationToken)
                    : base(compilation, options, cancellationToken)
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
                {
                }

                public override void RegisterCompilationEndAction(Action<CompilationAnalysisContext> action)
                {
                    if (action == null)
                    {
                        return;
                    }

                    IsCompilationEndAnalyzer = true;
                }

                #region not used
                public override void RegisterCodeBlockAction(Action<CodeBlockAnalysisContext> action) { }
                public override void RegisterCodeBlockStartAction<TLanguageKindEnum>(Action<CodeBlockStartAnalysisContext<TLanguageKindEnum>> action) { }
                public override void RegisterSemanticModelAction(Action<SemanticModelAnalysisContext> action) { }
                public override void RegisterSymbolAction(Action<SymbolAnalysisContext> action, ImmutableArray<SymbolKind> symbolKinds) { }
                public override void RegisterSyntaxNodeAction<TLanguageKindEnum>(Action<SyntaxNodeAnalysisContext> action, ImmutableArray<TLanguageKindEnum> syntaxKinds) { }
                public override void RegisterSyntaxTreeAction(Action<SyntaxTreeAnalysisContext> action) { }
                public override void RegisterOperationAction(Action<OperationAnalysisContext> action, ImmutableArray<OperationKind> operationKinds) { }
                public override void RegisterOperationBlockAction(Action<OperationBlockAnalysisContext> action) { }
                public override void RegisterOperationBlockStartAction(Action<OperationBlockStartAnalysisContext> action) { }
                public override void RegisterSymbolStartAction(Action<SymbolStartAnalysisContext> action, SymbolKind symbolKind) { }
                #endregion
            }
        }
H
heejaechang 已提交
738
    }
S
Sam Harwell 已提交
739
}