|
// 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.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Represents a diagnostic, such as a compiler error or a warning, along with the location where it occurred.
/// </summary>
[DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
public abstract partial class Diagnostic : IEquatable<Diagnostic?>, IFormattable
{
internal const string CompilerDiagnosticCategory = "Compiler";
/// <summary>
/// The default warning level, which is also used for non-error diagnostics.
/// </summary>
internal const int DefaultWarningLevel = 4;
/// <summary>
/// The warning level used for hidden and info diagnostics. Because these diagnostics interact with other editor features, we want them to always be produced unless /warn:0 is set.
/// </summary>
internal const int InfoAndHiddenWarningLevel = 1;
/// <summary>
/// The maximum warning level represented by a large value of 9999.
/// </summary>
internal const int MaxWarningLevel = 9999;
/// <summary>
/// Creates a <see cref="Diagnostic"/> instance.
/// </summary>
/// <param name="descriptor">A <see cref="DiagnosticDescriptor"/> describing the diagnostic</param>
/// <param name="location">An optional primary location of the diagnostic. If null, <see cref="Location"/> will return <see cref="Location.None"/>.</param>
/// <param name="messageArgs">Arguments to the message of the diagnostic</param>
/// <returns>The <see cref="Diagnostic"/> instance.</returns>
public static Diagnostic Create(
DiagnosticDescriptor descriptor,
Location? location,
params object?[]? messageArgs)
{
return Create(descriptor, location, null, null, messageArgs);
}
/// <summary>
/// Creates a <see cref="Diagnostic"/> instance.
/// </summary>
/// <param name="descriptor">A <see cref="DiagnosticDescriptor"/> describing the diagnostic.</param>
/// <param name="location">An optional primary location of the diagnostic. If null, <see cref="Location"/> will return <see cref="Location.None"/>.</param>
/// <param name="properties">
/// An optional set of name-value pairs by means of which the analyzer that creates the diagnostic
/// can convey more detailed information to the fixer. If null, <see cref="Properties"/> will return
/// <see cref="ImmutableDictionary{TKey, TValue}.Empty"/>.
/// </param>
/// <param name="messageArgs">Arguments to the message of the diagnostic.</param>
/// <returns>The <see cref="Diagnostic"/> instance.</returns>
public static Diagnostic Create(
DiagnosticDescriptor descriptor,
Location? location,
ImmutableDictionary<string, string?>? properties,
params object?[]? messageArgs)
{
return Create(descriptor, location, null, properties, messageArgs);
}
/// <summary>
/// Creates a <see cref="Diagnostic"/> instance.
/// </summary>
/// <param name="descriptor">A <see cref="DiagnosticDescriptor"/> describing the diagnostic.</param>
/// <param name="location">An optional primary location of the diagnostic. If null, <see cref="Location"/> will return <see cref="Location.None"/>.</param>
/// <param name="additionalLocations">
/// An optional set of additional locations related to the diagnostic.
/// Typically, these are locations of other items referenced in the message.
/// If null, <see cref="AdditionalLocations"/> will return an empty list.
/// </param>
/// <param name="messageArgs">Arguments to the message of the diagnostic.</param>
/// <returns>The <see cref="Diagnostic"/> instance.</returns>
public static Diagnostic Create(
DiagnosticDescriptor descriptor,
Location? location,
IEnumerable<Location>? additionalLocations,
params object?[]? messageArgs)
{
return Create(descriptor, location, additionalLocations, properties: null, messageArgs: messageArgs);
}
/// <summary>
/// Creates a <see cref="Diagnostic"/> instance.
/// </summary>
/// <param name="descriptor">A <see cref="DiagnosticDescriptor"/> describing the diagnostic.</param>
/// <param name="location">An optional primary location of the diagnostic. If null, <see cref="Location"/> will return <see cref="Location.None"/>.</param>
/// <param name="additionalLocations">
/// An optional set of additional locations related to the diagnostic.
/// Typically, these are locations of other items referenced in the message.
/// If null, <see cref="AdditionalLocations"/> will return an empty list.
/// </param>
/// <param name="properties">
/// An optional set of name-value pairs by means of which the analyzer that creates the diagnostic
/// can convey more detailed information to the fixer. If null, <see cref="Properties"/> will return
/// <see cref="ImmutableDictionary{TKey, TValue}.Empty"/>.
/// </param>
/// <param name="messageArgs">Arguments to the message of the diagnostic.</param>
/// <returns>The <see cref="Diagnostic"/> instance.</returns>
public static Diagnostic Create(
DiagnosticDescriptor descriptor,
Location? location,
IEnumerable<Location>? additionalLocations,
ImmutableDictionary<string, string?>? properties,
params object?[]? messageArgs)
{
return Create(descriptor, location, effectiveSeverity: descriptor.DefaultSeverity, additionalLocations, properties, messageArgs);
}
/// <summary>
/// Creates a <see cref="Diagnostic"/> instance.
/// </summary>
/// <param name="descriptor">A <see cref="DiagnosticDescriptor"/> describing the diagnostic.</param>
/// <param name="location">An optional primary location of the diagnostic. If null, <see cref="Location"/> will return <see cref="Location.None"/>.</param>
/// <param name="effectiveSeverity">Effective severity of the diagnostic.</param>
/// <param name="additionalLocations">
/// An optional set of additional locations related to the diagnostic.
/// Typically, these are locations of other items referenced in the message.
/// If null, <see cref="AdditionalLocations"/> will return an empty list.
/// </param>
/// <param name="properties">
/// An optional set of name-value pairs by means of which the analyzer that creates the diagnostic
/// can convey more detailed information to the fixer. If null, <see cref="Properties"/> will return
/// <see cref="ImmutableDictionary{TKey, TValue}.Empty"/>.
/// </param>
/// <param name="messageArgs">Arguments to the message of the diagnostic.</param>
/// <returns>The <see cref="Diagnostic"/> instance.</returns>
public static Diagnostic Create(
DiagnosticDescriptor descriptor,
Location? location,
DiagnosticSeverity effectiveSeverity,
IEnumerable<Location>? additionalLocations,
ImmutableDictionary<string, string?>? properties,
params object?[]? messageArgs)
{
if (descriptor == null)
{
throw new ArgumentNullException(nameof(descriptor));
}
var warningLevel = GetDefaultWarningLevel(effectiveSeverity);
return SimpleDiagnostic.Create(
descriptor,
severity: effectiveSeverity,
warningLevel: warningLevel,
location: location ?? Location.None,
additionalLocations: additionalLocations,
messageArgs: messageArgs,
properties: properties);
}
/// <summary>
/// Creates a <see cref="Diagnostic"/> instance which is localizable.
/// </summary>
/// <param name="id">An identifier for the diagnostic. For diagnostics generated by the compiler, this will be a numeric code with a prefix such as "CS1001".</param>
/// <param name="category">The category of the diagnostic. For diagnostics generated by the compiler, the category will be "Compiler".</param>
/// <param name="message">The diagnostic message text.</param>
/// <param name="severity">The diagnostic's effective severity.</param>
/// <param name="defaultSeverity">The diagnostic's default severity.</param>
/// <param name="isEnabledByDefault">True if the diagnostic is enabled by default</param>
/// <param name="warningLevel">The warning level, greater than 0 if severity is <see cref="DiagnosticSeverity.Warning"/>; otherwise 0.</param>
/// <param name="title">An optional short localizable title describing the diagnostic.</param>
/// <param name="description">An optional longer localizable description for the diagnostic.</param>
/// <param name="helpLink">An optional hyperlink that provides more detailed information regarding the diagnostic.</param>
/// <param name="location">An optional primary location of the diagnostic. If null, <see cref="Location"/> will return <see cref="Location.None"/>.</param>
/// <param name="additionalLocations">
/// An optional set of additional locations related to the diagnostic.
/// Typically, these are locations of other items referenced in the message.
/// If null, <see cref="AdditionalLocations"/> will return an empty list.
/// </param>
/// <param name="customTags">
/// An optional set of custom tags for the diagnostic. See <see cref="WellKnownDiagnosticTags"/> for some well known tags.
/// If null, <see cref="CustomTags"/> will return an empty list.
/// </param>
/// <param name="properties">
/// An optional set of name-value pairs by means of which the analyzer that creates the diagnostic
/// can convey more detailed information to the fixer. If null, <see cref="Properties"/> will return
/// <see cref="ImmutableDictionary{TKey, TValue}.Empty"/>.
/// </param>
/// <returns>The <see cref="Diagnostic"/> instance.</returns>
public static Diagnostic Create(
string id,
string category,
LocalizableString message,
DiagnosticSeverity severity,
DiagnosticSeverity defaultSeverity,
bool isEnabledByDefault,
int warningLevel,
LocalizableString? title = null,
LocalizableString? description = null,
string? helpLink = null,
Location? location = null,
IEnumerable<Location>? additionalLocations = null,
IEnumerable<string>? customTags = null,
ImmutableDictionary<string, string?>? properties = null)
{
return Create(id, category, message, severity, defaultSeverity, isEnabledByDefault, warningLevel, false,
title, description, helpLink, location, additionalLocations, customTags, properties);
}
/// <summary>
/// Creates a <see cref="Diagnostic"/> instance which is localizable.
/// </summary>
/// <param name="id">An identifier for the diagnostic. For diagnostics generated by the compiler, this will be a numeric code with a prefix such as "CS1001".</param>
/// <param name="category">The category of the diagnostic. For diagnostics generated by the compiler, the category will be "Compiler".</param>
/// <param name="message">The diagnostic message text.</param>
/// <param name="severity">The diagnostic's effective severity.</param>
/// <param name="defaultSeverity">The diagnostic's default severity.</param>
/// <param name="isEnabledByDefault">True if the diagnostic is enabled by default</param>
/// <param name="warningLevel">The warning level, greater than 0 if severity is <see cref="DiagnosticSeverity.Warning"/>; otherwise 0.</param>
/// <param name="isSuppressed">Flag indicating whether the diagnostic is suppressed by a source suppression.</param>
/// <param name="title">An optional short localizable title describing the diagnostic.</param>
/// <param name="description">An optional longer localizable description for the diagnostic.</param>
/// <param name="helpLink">An optional hyperlink that provides more detailed information regarding the diagnostic.</param>
/// <param name="location">An optional primary location of the diagnostic. If null, <see cref="Location"/> will return <see cref="Location.None"/>.</param>
/// <param name="additionalLocations">
/// An optional set of additional locations related to the diagnostic.
/// Typically, these are locations of other items referenced in the message.
/// If null, <see cref="AdditionalLocations"/> will return an empty list.
/// </param>
/// <param name="customTags">
/// An optional set of custom tags for the diagnostic. See <see cref="WellKnownDiagnosticTags"/> for some well known tags.
/// If null, <see cref="CustomTags"/> will return an empty list.
/// </param>
/// <param name="properties">
/// An optional set of name-value pairs by means of which the analyzer that creates the diagnostic
/// can convey more detailed information to the fixer. If null, <see cref="Properties"/> will return
/// <see cref="ImmutableDictionary{TKey, TValue}.Empty"/>.
/// </param>
/// <returns>The <see cref="Diagnostic"/> instance.</returns>
public static Diagnostic Create(
string id,
string category,
LocalizableString message,
DiagnosticSeverity severity,
DiagnosticSeverity defaultSeverity,
bool isEnabledByDefault,
int warningLevel,
bool isSuppressed,
LocalizableString? title = null,
LocalizableString? description = null,
string? helpLink = null,
Location? location = null,
IEnumerable<Location>? additionalLocations = null,
IEnumerable<string>? customTags = null,
ImmutableDictionary<string, string?>? properties = null)
{
if (id == null)
{
throw new ArgumentNullException(nameof(id));
}
if (category == null)
{
throw new ArgumentNullException(nameof(category));
}
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
return SimpleDiagnostic.Create(id, title ?? string.Empty, category, message, description ?? string.Empty, helpLink ?? string.Empty,
severity, defaultSeverity, isEnabledByDefault, warningLevel, location ?? Location.None, additionalLocations, customTags, properties, isSuppressed);
}
internal static Diagnostic Create(CommonMessageProvider messageProvider, int errorCode)
{
return Create(new DiagnosticInfo(messageProvider, errorCode));
}
internal static Diagnostic Create(CommonMessageProvider messageProvider, int errorCode, params object[] arguments)
{
return Create(new DiagnosticInfo(messageProvider, errorCode, arguments));
}
internal static Diagnostic Create(DiagnosticInfo info)
{
return new DiagnosticWithInfo(info, Location.None);
}
/// <summary>
/// Gets the diagnostic descriptor, which provides a description about a <see cref="Diagnostic"/>.
/// </summary>
public abstract DiagnosticDescriptor Descriptor { get; }
/// <summary>
/// Gets the diagnostic identifier. For diagnostics generated by the compiler, this will be a numeric code with a prefix such as "CS1001".
/// </summary>
public abstract string Id { get; }
/// <summary>
/// Gets the category of diagnostic. For diagnostics generated by the compiler, the category will be "Compiler".
/// </summary>
internal virtual string Category { get { return this.Descriptor.Category; } }
/// <summary>
/// Get the culture specific text of the message.
/// </summary>
public abstract string GetMessage(IFormatProvider? formatProvider = null);
/// <summary>
/// Gets the default <see cref="DiagnosticSeverity"/> of the diagnostic's <see cref="DiagnosticDescriptor"/>.
/// </summary>
/// <remarks>
/// To get the effective severity of the diagnostic, use <see cref="Severity"/>.
/// </remarks>
public virtual DiagnosticSeverity DefaultSeverity { get { return this.Descriptor.DefaultSeverity; } }
/// <summary>
/// Gets the effective <see cref="DiagnosticSeverity"/> of the diagnostic.
/// </summary>
/// <remarks>
/// To get the default severity of diagnostic's <see cref="DiagnosticDescriptor"/>, use <see cref="DefaultSeverity"/>.
/// To determine if this is a warning treated as an error, use <see cref="IsWarningAsError"/>.
/// </remarks>
public abstract DiagnosticSeverity Severity { get; }
/// <summary>
/// Gets the warning level. This is 0 for diagnostics with severity <see cref="DiagnosticSeverity.Error"/>,
/// otherwise an integer greater than zero.
/// </summary>
public abstract int WarningLevel { get; }
/// <summary>
/// Returns true if the diagnostic has a source suppression, i.e. an attribute or a pragma suppression.
/// </summary>
public abstract bool IsSuppressed { get; }
/// <summary>
/// Gets the <see cref="SuppressionInfo"/> for suppressed diagnostics, i.e. <see cref="IsSuppressed"/> = true.
/// Otherwise, returns null.
/// </summary>
public SuppressionInfo? GetSuppressionInfo(Compilation compilation)
{
if (!IsSuppressed)
{
return null;
}
AttributeData? attribute;
var suppressMessageState = new SuppressMessageAttributeState(compilation);
if (!suppressMessageState.IsDiagnosticSuppressed(
this,
out attribute))
{
attribute = null;
}
var programmaticSuppressions = this.ProgrammaticSuppressionInfo?.Suppressions ?? ImmutableArray<Suppression>.Empty;
return new SuppressionInfo(this.Id, attribute, programmaticSuppressions);
}
/// <summary>
/// Returns true if this diagnostic is enabled by default by the author of the diagnostic.
/// </summary>
internal virtual bool IsEnabledByDefault { get { return this.Descriptor.IsEnabledByDefault; } }
/// <summary>
/// Returns true if this is a warning treated as an error; otherwise false.
/// </summary>
/// <remarks>
/// True implies <see cref="DefaultSeverity"/> = <see cref="DiagnosticSeverity.Warning"/>
/// and <see cref="Severity"/> = <see cref="DiagnosticSeverity.Error"/>.
/// </remarks>
public bool IsWarningAsError
{
get
{
return this.DefaultSeverity == DiagnosticSeverity.Warning &&
this.Severity == DiagnosticSeverity.Error;
}
}
/// <summary>
/// Gets the primary location of the diagnostic, or <see cref="Location.None"/> if no primary location.
/// </summary>
public abstract Location Location { get; }
/// <summary>
/// Gets an array of additional locations related to the diagnostic.
/// Typically these are the locations of other items referenced in the message.
/// </summary>
public abstract IReadOnlyList<Location> AdditionalLocations { get; }
/// <summary>
/// Gets custom tags for the diagnostic.
/// </summary>
internal virtual ImmutableArray<string> CustomTags { get { return this.Descriptor.ImmutableCustomTags; } }
/// <summary>
/// Gets property bag for the diagnostic. it will return <see cref="ImmutableDictionary{TKey, TValue}.Empty"/>
/// if there is no entry. This can be used to put diagnostic specific information you want
/// to pass around. for example, to corresponding fixer.
/// </summary>
public virtual ImmutableDictionary<string, string?> Properties
=> ImmutableDictionary<string, string?>.Empty;
string IFormattable.ToString(string? ignored, IFormatProvider? formatProvider)
{
return DiagnosticFormatter.Instance.Format(this, formatProvider);
}
public override string ToString()
{
return DiagnosticFormatter.Instance.Format(this, CultureInfo.CurrentUICulture);
}
public sealed override bool Equals(object? obj)
=> obj is Diagnostic diagnostic && Equals(diagnostic);
public abstract override int GetHashCode();
public abstract bool Equals(Diagnostic? obj);
private string GetDebuggerDisplay()
{
switch (this.Severity)
{
case InternalDiagnosticSeverity.Unknown:
// If we called ToString before the diagnostic was resolved,
// we would risk infinite recursion (e.g. if we were still computing
// member lists).
return "Unresolved diagnostic at " + this.Location;
case InternalDiagnosticSeverity.Void:
// If we called ToString on a void diagnostic, the MessageProvider
// would complain about the code.
return "Void diagnostic at " + this.Location;
default:
return ToString();
}
}
/// <summary>
/// Create a new instance of this diagnostic with the Location property changed.
/// </summary>
internal abstract Diagnostic WithLocation(Location location);
/// <summary>
/// Create a new instance of this diagnostic with the Severity property changed.
/// </summary>
internal abstract Diagnostic WithSeverity(DiagnosticSeverity severity);
/// <summary>
/// Create a new instance of this diagnostic with the suppression info changed.
/// </summary>
internal abstract Diagnostic WithIsSuppressed(bool isSuppressed);
/// <summary>
/// Create a new instance of this diagnostic with the given programmatic suppression info.
/// </summary>
internal Diagnostic WithProgrammaticSuppression(ProgrammaticSuppressionInfo programmaticSuppressionInfo)
{
RoslynDebug.Assert(this.ProgrammaticSuppressionInfo == null);
RoslynDebug.Assert(programmaticSuppressionInfo != null);
return new DiagnosticWithProgrammaticSuppression(this, programmaticSuppressionInfo);
}
internal virtual ProgrammaticSuppressionInfo? ProgrammaticSuppressionInfo { get { return null; } }
// compatibility
internal virtual int Code { get { return 0; } }
internal virtual IReadOnlyList<object?> Arguments
{
get { return SpecializedCollections.EmptyReadOnlyList<object?>(); }
}
/// <summary>
/// Returns true if the diagnostic location (or any additional location) is within the given tree and intersects with the filterSpanWithinTree, if non-null.
/// </summary>
[PerformanceSensitive(
"https://github.com/dotnet/roslyn/issues/26778",
Constraint = "In most cases, AdditionalLocations is empty.",
AllowCaptures = false)]
internal bool HasIntersectingLocation(SyntaxTree tree, TextSpan? filterSpanWithinTree = null)
{
if (isLocationWithinSpan(Location, tree, filterSpanWithinTree))
{
return true;
}
if (AdditionalLocations is null || AdditionalLocations.Count == 0)
{
// Avoid possible enumerator allocations if there are no additional locations.
return false;
}
foreach (var location in AdditionalLocations)
{
if (isLocationWithinSpan(location, tree, filterSpanWithinTree))
{
return true;
}
}
return false;
static bool isLocationWithinSpan(Location location, SyntaxTree tree, TextSpan? filterSpan)
{
if (location.SourceTree != tree)
{
return false;
}
return !filterSpan.HasValue || filterSpan.GetValueOrDefault().IntersectsWith(location.SourceSpan);
}
}
internal Diagnostic? WithReportDiagnostic(ReportDiagnostic reportAction)
{
switch (reportAction)
{
case ReportDiagnostic.Suppress:
// Suppressed diagnostic.
return null;
case ReportDiagnostic.Error:
return this.WithSeverity(DiagnosticSeverity.Error);
case ReportDiagnostic.Default:
return this;
case ReportDiagnostic.Warn:
return this.WithSeverity(DiagnosticSeverity.Warning);
case ReportDiagnostic.Info:
return this.WithSeverity(DiagnosticSeverity.Info);
case ReportDiagnostic.Hidden:
return this.WithSeverity(DiagnosticSeverity.Hidden);
default:
throw ExceptionUtilities.UnexpectedValue(reportAction);
}
}
/// <summary>
/// Gets the default warning level for a diagnostic severity. Warning levels are used with the <c>/warn:N</c>
/// command line option to suppress diagnostics over a severity of interest. When N is 0, only error severity
/// messages are produced by the compiler. Values greater than 0 indicated that warnings up to and including
/// level N should also be included.
/// </summary>
/// <remarks>
/// <see cref="DiagnosticSeverity.Info"/> and <see cref="DiagnosticSeverity.Hidden"/> are treated as warning
/// level 1. In other words, these diagnostics which typically interact with editor features are enabled unless
/// the special <c>/warn:0</c> option is set.
/// </remarks>
/// <param name="severity">A <see cref="DiagnosticSeverity"/> value.</param>
/// <returns>The default compiler warning level for <paramref name="severity"/>.</returns>
internal static int GetDefaultWarningLevel(DiagnosticSeverity severity)
{
switch (severity)
{
case DiagnosticSeverity.Error:
return 0;
case DiagnosticSeverity.Warning:
default:
return 1;
}
}
/// <summary>
/// Returns true if a diagnostic is not configurable, i.e. cannot be suppressed or filtered or have its severity changed.
/// For example, compiler errors are always non-configurable.
/// </summary>
internal virtual bool IsNotConfigurable()
{
return AnalyzerManager.HasNotConfigurableTag(this.CustomTags);
}
/// <summary>
/// Returns true if this is an error diagnostic which cannot be suppressed and is guaranteed to break the build.
/// Only diagnostics which have default severity error and are tagged as NotConfigurable fall in this bucket.
/// This includes all compiler error diagnostics and specific analyzer error diagnostics that are marked as not configurable by the analyzer author.
/// </summary>
internal bool IsUnsuppressableError()
=> DefaultSeverity == DiagnosticSeverity.Error && IsNotConfigurable();
/// <summary>
/// Returns true if this is a unsuppressed diagnostic with an effective error severity.
/// </summary>
internal bool IsUnsuppressedError
=> Severity == DiagnosticSeverity.Error && !IsSuppressed;
}
/// <summary>
/// This type is attached to diagnostics for required language version and should only be used
/// on such diagnostics, as they are recognized by <see cref="Compilation.GetRequiredLanguageVersion"/>.
/// </summary>
internal abstract class RequiredLanguageVersion : IFormattable
{
public abstract override string ToString();
string IFormattable.ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
}
}
|