// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Immutable; using System.Reflection; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics { internal static class DiagnosticAnalysisContextHelpers { internal static void VerifyArguments(Action action) { VerifyAction(action); } internal static void VerifyArguments(Action action, ImmutableArray symbolKinds) { VerifyAction(action); VerifySymbolKinds(symbolKinds); } internal static void VerifyArguments(Action action, ImmutableArray syntaxKinds) where TLanguageKindEnum : struct { VerifyAction(action); VerifySyntaxKinds(syntaxKinds); } internal static void VerifyArguments(Diagnostic diagnostic, Compilation compilationOpt, Func isSupportedDiagnostic) { if (diagnostic is DiagnosticWithInfo) { // Compiler diagnostic, skip validations. return; } if (diagnostic == null) { throw new ArgumentNullException(nameof(diagnostic)); } if (compilationOpt != null) { VerifyDiagnosticLocationsInCompilation(diagnostic, compilationOpt); } if (!isSupportedDiagnostic(diagnostic)) { throw new ArgumentException(string.Format(CodeAnalysisResources.UnsupportedDiagnosticReported, diagnostic.Id), nameof(diagnostic)); } if (!UnicodeCharacterUtilities.IsValidIdentifier(diagnostic.Id)) { // Disallow invalid diagnostic IDs. // Note that the parsing logic in Csc/Vbc MSBuild tasks to decode command line compiler output relies on diagnostics having a valid ID. // See https://github.com/dotnet/roslyn/issues/4376 for details. throw new ArgumentException(string.Format(CodeAnalysisResources.InvalidDiagnosticIdReported, diagnostic.Id), nameof(diagnostic)); } } internal static void VerifyDiagnosticLocationsInCompilation(Diagnostic diagnostic, Compilation compilation) { VerifyDiagnosticLocationInCompilation(diagnostic.Id, diagnostic.Location, compilation); if (diagnostic.AdditionalLocations != null) { foreach (var location in diagnostic.AdditionalLocations) { VerifyDiagnosticLocationInCompilation(diagnostic.Id, diagnostic.Location, compilation); } } } internal static void VerifyIOperationFeatureFlag(bool isIOperationFeatureEnabled) { if (!isIOperationFeatureEnabled) { throw new InvalidOperationException(CodeAnalysisResources.IOperationFeatureDisabled); } } private static void VerifyDiagnosticLocationInCompilation(string id, Location location, Compilation compilation) { if (location.IsInSource && !compilation.ContainsSyntaxTree(location.SourceTree)) { // Disallow diagnostics with source locations outside this compilation. throw new ArgumentException(string.Format(CodeAnalysisResources.InvalidDiagnosticLocationReported, id, location.SourceTree.FilePath), "diagnostic"); } } private static void VerifyAction(Action action) { if (action == null) { throw new ArgumentNullException(nameof(action)); } // Disallow async methods to be registered. if (action.GetMethodInfo().IsDefined(typeof(AsyncStateMachineAttribute))) { throw new ArgumentException(CodeAnalysisResources.AsyncAnalyzerActionCannotBeRegistered, nameof(action)); } } private static void VerifySymbolKinds(ImmutableArray symbolKinds) { if (symbolKinds.IsDefault) { throw new ArgumentNullException(nameof(symbolKinds)); } if (symbolKinds.IsEmpty) { throw new ArgumentException(CodeAnalysisResources.ArgumentCannotBeEmpty, nameof(symbolKinds)); } } private static void VerifySyntaxKinds(ImmutableArray syntaxKinds) where TLanguageKindEnum : struct { if (syntaxKinds.IsDefault) { throw new ArgumentNullException(nameof(syntaxKinds)); } if (syntaxKinds.IsEmpty) { throw new ArgumentException(CodeAnalysisResources.ArgumentCannotBeEmpty, nameof(syntaxKinds)); } } internal static void VerifyArguments(TKey key, AnalysisValueProvider valueProvider) where TKey : class { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (valueProvider == null) { throw new ArgumentNullException(nameof(valueProvider)); } } } }