AnalyzerHelper.cs 37.2 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
        public static OptionSet GetAnalyzerOptionSet(this AnalyzerOptions analyzerOptions, SyntaxTree syntaxTree, CancellationToken cancellationToken)
        {
98 99
            var optionSetAsync = GetAnalyzerOptionSetAsync(analyzerOptions, syntaxTree, cancellationToken);
            if (optionSetAsync.IsCompleted)
100
                return optionSetAsync.Result;
101 102

            return optionSetAsync.AsTask().GetAwaiter().GetResult();
103 104
        }

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

            return new AnalyzerConfigOptionSet(configOptions, optionSet);
        }

115
        public static T GetOption<T>(this AnalyzerOptions analyzerOptions, ILanguageSpecificOption<T> option, SyntaxTree syntaxTree, CancellationToken cancellationToken)
116
        {
117 118
            var optionAsync = GetOptionAsync<T>(analyzerOptions, option, language: null, syntaxTree, cancellationToken);
            if (optionAsync.IsCompleted)
119
                return optionAsync.Result;
120 121

            return optionAsync.AsTask().GetAwaiter().GetResult();
122 123
        }

124
        public static T GetOption<T>(this AnalyzerOptions analyzerOptions, IPerLanguageOption<T> option, string? language, SyntaxTree syntaxTree, CancellationToken cancellationToken)
125
        {
126 127
            var optionAsync = GetOptionAsync<T>(analyzerOptions, option, language, syntaxTree, cancellationToken);
            if (optionAsync.IsCompleted)
128
                return optionAsync.Result;
129 130

            return optionAsync.AsTask().GetAwaiter().GetResult();
131 132
        }

133
        public static async ValueTask<T> GetOptionAsync<T>(this AnalyzerOptions analyzerOptions, IOption option, string? language, SyntaxTree syntaxTree, CancellationToken cancellationToken)
134
        {
135
            if (analyzerOptions.TryGetEditorConfigOption<T>(option, syntaxTree, out var value))
136 137 138 139
            {
                return value;
            }

140
#pragma warning disable CS0612 // Type or member is obsolete
141
            var optionSet = await analyzerOptions.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).ConfigureAwait(false);
142
#pragma warning restore CS0612 // Type or member is obsolete
J
Joey Robichaud 已提交
143
            return (T)optionSet?.GetOption(new OptionKey(option, language)) ?? (T)option.DefaultValue!;
144 145
        }

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

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

157 158 159 160 161 162 163 164 165 166 167 168 169 170
        /// <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,
171 172 173
                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()),
174
                category: AnalyzerExceptionDiagnosticCategory,
175
                defaultSeverity: DiagnosticSeverity.Warning,
176 177 178 179
                isEnabledByDefault: true,
                customTags: WellKnownDiagnosticTags.AnalyzerException);

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

H
Heejae Chang 已提交
182
        private static VersionStamp GetAnalyzerVersion(string path)
183
        {
184
            if (path == null || !File.Exists(path))
185 186 187 188
            {
                return VersionStamp.Default;
            }

189
            return VersionStamp.Create(File.GetLastWriteTimeUtc(path));
190
        }
191

192
        public static DiagnosticData CreateAnalyzerLoadFailureDiagnostic(AnalyzerLoadFailureEventArgs e, string fullPath, ProjectId? projectId, string? language)
193
        {
194 195
            static string GetLanguageSpecificId(string? language, string noLanguageId, string csharpId, string vbId)
                => language == null ? noLanguageId : (language == LanguageNames.CSharp) ? csharpId : vbId;
196

197
            string id, messageFormat, message;
198 199 200 201

            switch (e.ErrorCode)
            {
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToLoadAnalyzer:
202
                    id = GetLanguageSpecificId(language, WRN_UnableToLoadAnalyzerId, WRN_UnableToLoadAnalyzerIdCS, WRN_UnableToLoadAnalyzerIdVB);
203 204
                    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);
205
                    break;
206

207
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToCreateAnalyzer:
208
                    id = GetLanguageSpecificId(language, WRN_AnalyzerCannotBeCreatedId, WRN_AnalyzerCannotBeCreatedIdCS, WRN_AnalyzerCannotBeCreatedIdVB);
209 210
                    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);
211
                    break;
212

213
                case AnalyzerLoadFailureEventArgs.FailureErrorCode.NoAnalyzers:
214
                    id = GetLanguageSpecificId(language, WRN_NoAnalyzerInAssemblyId, WRN_NoAnalyzerInAssemblyIdCS, WRN_NoAnalyzerInAssemblyIdVB);
215 216
                    messageFormat = FeaturesResources.The_assembly_0_does_not_contain_any_analyzers;
                    message = string.Format(FeaturesResources.The_assembly_0_does_not_contain_any_analyzers, fullPath);
217
                    break;
218

219
                default:
220
                    throw ExceptionUtilities.UnexpectedValue(e.ErrorCode);
221 222
            }

223
            var description = e.Exception.CreateDiagnosticDescription();
224

225 226 227 228 229 230 231 232 233 234 235 236 237 238
            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);
239
        }
240 241 242 243 244 245 246 247 248

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

250
        public static IEnumerable<AnalyzerPerformanceInfo> ToAnalyzerPerformanceInfo(this IDictionary<DiagnosticAnalyzer, AnalyzerTelemetryInfo> analysisResult, DiagnosticAnalyzerInfoCache analyzerInfo)
251
            => analysisResult.Select(kv => new AnalyzerPerformanceInfo(kv.Key.GetAnalyzerId(), analyzerInfo.IsTelemetryCollectionAllowed(kv.Key), kv.Value.ExecutionTime));
H
Heejae Chang 已提交
252

253
        public static async Task<CompilationWithAnalyzers?> CreateCompilationWithAnalyzersAsync(
254 255 256 257
            Project project,
            IEnumerable<DiagnosticAnalyzer> analyzers,
            bool includeSuppressedDiagnostics,
            CancellationToken cancellationToken)
258
        {
259 260
            var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
            if (compilation == null)
261
            {
262
                // project doesn't support compilation
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
                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);

279 280 281
            // 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(
282
                options: new WorkspaceAnalyzerOptions(project.AnalyzerOptions, project.Solution),
283
                onAnalyzerException: null,
284 285 286 287
                analyzerExceptionFilter: GetAnalyzerExceptionFilter(),
                concurrentAnalysis: false,
                logAnalyzerExecutionTime: true,
                reportSuppressedDiagnostics: includeSuppressedDiagnostics);
288 289

            // Create driver that holds onto compilation and associated analyzers
290
            return compilation.WithAnalyzers(filteredAnalyzers, analyzerOptions);
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319

            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,
320
            Document document,
321
            AnalysisKind kind,
322
            CompilationWithAnalyzers? compilationWithAnalyzers,
323
            Func<Project, ISkippedAnalyzersInfo>? getSkippedAnalyzersInfo,
324
            TextSpan? span,
325 326
            CancellationToken cancellationToken)
        {
327 328 329 330 331 332 333 334 335 336 337 338 339 340
            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>();
            }

341 342 343
            if (analyzer is DocumentDiagnosticAnalyzer documentAnalyzer)
            {
                var diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(
344
                    documentAnalyzer, document, kind, compilationWithAnalyzers?.Compilation, cancellationToken).ConfigureAwait(false);
345

346 347 348
                return diagnostics.ConvertToLocalDiagnostics(document);
            }

349
            // quick optimization to reduce allocations.
350
            if (compilationWithAnalyzers == null || !analyzer.SupportAnalysisKind(kind))
351
            {
352
                if (kind == AnalysisKind.Syntax)
353
                {
354 355
                    Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic,
                        (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a}, {k}", compilationWithAnalyzers, document, analyzer, kind);
356 357
                }

358 359 360 361
                return SpecializedCollections.EmptyEnumerable<DiagnosticData>();
            }

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

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

368
                if (!isEnabled)
369
                {
370
                    return SpecializedCollections.EmptyEnumerable<DiagnosticData>();
371 372 373
                }
            }

374 375
            // REVIEW: more unnecessary allocations just to get diagnostics per analyzer
            var singleAnalyzer = ImmutableArray.Create(analyzer);
376
            var skippedAnalyzerInfo = getSkippedAnalyzersInfo?.Invoke(document.Project) ?? SkippedHostAnalyzersInfo.Default;
377
            ImmutableArray<string> filteredIds;
378 379

            switch (kind)
380
            {
381 382 383 384 385 386
                case AnalysisKind.Syntax:
                    var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
                    if (tree == null)
                    {
                        return SpecializedCollections.EmptyEnumerable<DiagnosticData>();
                    }
387

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

390 391 392 393
                    if (diagnostics.IsDefaultOrEmpty)
                    {
                        Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, (d, a, t) => $"{d.Id}, {d.Project.Id}, {a}, {t.Length}", document, analyzer, tree);
                    }
394 395 396 397
                    else if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out filteredIds))
                    {
                        diagnostics = diagnostics.Filter(filteredIds);
                    }
398

399 400
                    Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationWithAnalyzers.Compilation).Count());
                    return diagnostics.ConvertToLocalDiagnostics(document);
401

402 403 404 405 406 407
                case AnalysisKind.Semantic:
                    var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
                    if (model == null)
                    {
                        return SpecializedCollections.EmptyEnumerable<DiagnosticData>();
                    }
408

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

411 412 413 414 415
                    if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out filteredIds))
                    {
                        diagnostics = diagnostics.Filter(filteredIds);
                    }

416 417
                    Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationWithAnalyzers.Compilation).Count());
                    return diagnostics.ConvertToLocalDiagnostics(document);
418

419 420
                default:
                    throw ExceptionUtilities.UnexpectedValue(kind);
421 422 423
            }
        }

424
        public static async Task<ImmutableArray<Diagnostic>> ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(
425
            DocumentDiagnosticAnalyzer analyzer,
426
            Document document,
427 428 429
            AnalysisKind kind,
            Compilation? compilation,
            CancellationToken cancellationToken)
430 431 432
        {
            cancellationToken.ThrowIfCancellationRequested();

433
            ImmutableArray<Diagnostic> diagnostics;
434 435
            try
            {
C
Cyrus Najmabadi 已提交
436
                var analyzeAsync = kind switch
437
                {
C
Cyrus Najmabadi 已提交
438 439 440 441
                    AnalysisKind.Syntax => analyzer.AnalyzeSyntaxAsync(document, cancellationToken),
                    AnalysisKind.Semantic => analyzer.AnalyzeSemanticsAsync(document, cancellationToken),
                    _ => throw ExceptionUtilities.UnexpectedValue(kind),
                };
442

443
                diagnostics = (await analyzeAsync.ConfigureAwait(false)).NullToEmpty();
444 445 446 447 448 449 450 451 452 453

#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))
            {
454 455 456 457 458 459
                diagnostics = ImmutableArray.Create(CreateAnalyzerExceptionDiagnostic(analyzer, e));
            }

            if (compilation != null)
            {
                diagnostics = CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilation).ToImmutableArrayOrEmpty();
460
            }
461 462

            return diagnostics;
463 464
        }

465
        public static async Task<ImmutableArray<Diagnostic>> ComputeProjectDiagnosticAnalyzerDiagnosticsAsync(
466
            ProjectDiagnosticAnalyzer analyzer,
467
            Project project,
468 469
            Compilation? compilation,
            CancellationToken cancellationToken)
470 471 472
        {
            cancellationToken.ThrowIfCancellationRequested();

473
            ImmutableArray<Diagnostic> diagnostics;
474 475
            try
            {
476
                diagnostics = (await analyzer.AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false)).NullToEmpty();
477 478 479 480 481 482 483 484 485
#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))
            {
486
                diagnostics = ImmutableArray.Create(CreateAnalyzerExceptionDiagnostic(analyzer, e));
487 488
            }

489
            // Apply filtering from compilation options (source suppressions, ruleset, etc.)
490
            if (compilation != null)
491
            {
492
                diagnostics = CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilation).ToImmutableArrayOrEmpty();
493 494
            }

495 496 497 498
            return diagnostics;
        }

        private static bool IsCanceled(Exception ex, CancellationToken cancellationToken)
499
            => (ex as OperationCanceledException)?.CancellationToken == cancellationToken;
500 501 502 503 504 505 506 507 508 509 510

        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)
                    {
511
                        await VerifyDiagnosticLocationAsync(diagnostic.Id, location).ConfigureAwait(false);
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
                    }
                }
            }

            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 已提交
527
                            RoslynDebug.Assert(location.SourceTree != null);
D
dotnet-bot 已提交
528 529 530 531 532 533 534 535 536 537 538
                            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");
                            }
539
                        }
D
dotnet-bot 已提交
540
                        break;
541 542
                    case LocationKind.ExternalFile:
                        {
D
dotnet-bot 已提交
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
                            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");
                            }
561
                        }
D
dotnet-bot 已提交
562
                        break;
563 564 565 566 567
                    default:
                        throw ExceptionUtilities.Unreachable;
                }
            }

568
            Document? TryGetDocumentWithFilePath(string path)
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
            {
                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;
                    }

613
                    yield return DiagnosticData.Create(diagnostic, targetDocument);
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
                }
            }

            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;
                    }

634
                    yield return DiagnosticData.Create(diagnostic, document);
635 636 637 638
                }
            }
        }

639
        public static bool? IsCompilationEndAnalyzer(this DiagnosticAnalyzer analyzer, Project project, Compilation? compilation)
640 641 642 643 644 645 646 647
        {
            if (!project.SupportsCompilation)
            {
                return false;
            }

            Contract.ThrowIfNull(compilation);

648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
            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;
            }
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 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
        }

        /// <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;

729 730
                public CollectNestedCompilationContext(Compilation compilation, AnalyzerOptions options, CancellationToken cancellationToken)
                    : base(compilation, options, cancellationToken)
731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
                {
                }

                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 已提交
758
    }
S
Sam Harwell 已提交
759
}