Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add property bag to diagnostic and remember origin of the diagnostic #510

Merged
merged 6 commits into from
Feb 18, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions src/Compilers/Core/Portable/Diagnostic/Diagnostic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Globalization;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using System.Collections.Immutable;

namespace Microsoft.CodeAnalysis
{
Expand Down Expand Up @@ -34,7 +35,24 @@ public static Diagnostic Create(
Location location,
params object[] messageArgs)
{
return Create(descriptor, location, null, 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 properties of the diagnostic. 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>
Expand All @@ -54,6 +72,29 @@ public static Diagnostic Create(
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 properties of the diagnostic. 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)
{
if (descriptor == null)
{
Expand All @@ -67,7 +108,8 @@ public static Diagnostic Create(
warningLevel: warningLevel,
location: location ?? Location.None,
additionalLocations: additionalLocations,
messageArgs: messageArgs);
messageArgs: messageArgs,
properties: properties);
}

/// <summary>
Expand All @@ -93,6 +135,7 @@ public static Diagnostic Create(
/// 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 properties of the diagnostic. 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,
Expand All @@ -107,7 +150,8 @@ public static Diagnostic Create(
string helpLink = null,
Location location = null,
IEnumerable<Location> additionalLocations = null,
IEnumerable<string> customTags = null)
IEnumerable<string> customTags = null,
ImmutableDictionary<string, string> properties = null)
{
if (id == null)
{
Expand All @@ -125,7 +169,7 @@ public static Diagnostic Create(
}

return SimpleDiagnostic.Create(id, title ?? string.Empty, category, message, description ?? string.Empty, helpLink ?? string.Empty,
severity, defaultSeverity, isEnabledByDefault, warningLevel, location ?? Location.None, additionalLocations, customTags);
severity, defaultSeverity, isEnabledByDefault, warningLevel, location ?? Location.None, additionalLocations, customTags, properties);
}

internal static Diagnostic Create(CommonMessageProvider messageProvider, int errorCode)
Expand Down Expand Up @@ -223,6 +267,12 @@ public bool IsWarningAsError
/// </summary>
internal virtual IReadOnlyList<string> CustomTags { get { return (IReadOnlyList<string>)this.Descriptor.CustomTags; } }

/// <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 { get { return ImmutableDictionary<string, string>.Empty; } }

string IFormattable.ToString(string ignored, IFormatProvider formatProvider)
{
return DiagnosticFormatter.Instance.Format(this, formatProvider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ internal sealed class SimpleDiagnostic : Diagnostic
private readonly Location _location;
private readonly IReadOnlyList<Location> _additionalLocations;
private readonly object[] _messageArgs;
private readonly ImmutableDictionary<string, string> _properties;

private SimpleDiagnostic(
DiagnosticDescriptor descriptor,
DiagnosticSeverity severity,
int warningLevel,
Location location,
IEnumerable<Location> additionalLocations,
object[] messageArgs)
object[] messageArgs,
ImmutableDictionary<string, string> properties)
{
if ((warningLevel == 0 && severity != DiagnosticSeverity.Error) ||
(warningLevel != 0 && severity == DiagnosticSeverity.Error))
Expand All @@ -48,6 +50,7 @@ private SimpleDiagnostic(
_location = location ?? Location.None;
_additionalLocations = additionalLocations == null ? SpecializedCollections.EmptyReadOnlyList<Location>() : additionalLocations.ToImmutableArray();
_messageArgs = messageArgs ?? SpecializedCollections.EmptyArray<object>();
_properties = properties ?? ImmutableDictionary<string, string>.Empty;
}

internal static SimpleDiagnostic Create(
Expand All @@ -56,19 +59,21 @@ internal static SimpleDiagnostic Create(
int warningLevel,
Location location,
IEnumerable<Location> additionalLocations,
object[] messageArgs)
object[] messageArgs,
ImmutableDictionary<string, string> properties)
{
return new SimpleDiagnostic(descriptor, severity, warningLevel, location, additionalLocations, messageArgs);
return new SimpleDiagnostic(descriptor, severity, warningLevel, location, additionalLocations, messageArgs, properties);
}

internal static SimpleDiagnostic Create(string id, LocalizableString title, string category, LocalizableString message, LocalizableString description, string helpLink,
DiagnosticSeverity severity, DiagnosticSeverity defaultSeverity,
bool isEnabledByDefault, int warningLevel, Location location,
IEnumerable<Location> additionalLocations, IEnumerable<string> customTags)
IEnumerable<Location> additionalLocations, IEnumerable<string> customTags,
ImmutableDictionary<string, string> properties)
{
var descriptor = new DiagnosticDescriptor(id, title, message,
category, defaultSeverity, isEnabledByDefault, description, helpLink, customTags.ToImmutableArrayOrEmpty());
return new SimpleDiagnostic(descriptor, severity, warningLevel, location, additionalLocations, messageArgs: null);
return new SimpleDiagnostic(descriptor, severity, warningLevel, location, additionalLocations, messageArgs: null, properties: properties);
}

public override DiagnosticDescriptor Descriptor
Expand Down Expand Up @@ -117,6 +122,11 @@ public override IReadOnlyList<Location> AdditionalLocations
get { return _additionalLocations; }
}

public override ImmutableDictionary<string, string> Properties
{
get { return _properties; }
}

public override bool Equals(Diagnostic obj)
{
var other = obj as SimpleDiagnostic;
Expand Down Expand Up @@ -150,7 +160,7 @@ internal override Diagnostic WithLocation(Location location)

if (location != _location)
{
return new SimpleDiagnostic(_descriptor, _severity, _warningLevel, location, _additionalLocations, _messageArgs);
return new SimpleDiagnostic(_descriptor, _severity, _warningLevel, location, _additionalLocations, _messageArgs, _properties);
}

return this;
Expand All @@ -161,7 +171,7 @@ internal override Diagnostic WithSeverity(DiagnosticSeverity severity)
if (this.Severity != severity)
{
var warningLevel = GetDefaultWarningLevel(severity);
return new SimpleDiagnostic(_descriptor, severity, warningLevel, _location, _additionalLocations, _messageArgs);
return new SimpleDiagnostic(_descriptor, severity, warningLevel, _location, _additionalLocations, _messageArgs, _properties);
}

return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
// 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.Generic;
using System.Collections.Immutable;

namespace Microsoft.CodeAnalysis.Diagnostics
{
internal abstract partial class CompilerDiagnosticAnalyzer : DiagnosticAnalyzer
{
private const string Origin = "Origin";
private const string Syntactic = "Syntactic";
private const string Declaration = "Declaration";

private static readonly ImmutableDictionary<string, string> s_syntactic = ImmutableDictionary<string, string>.Empty.Add(Origin, Syntactic);
private static readonly ImmutableDictionary<string, string> s_declaration = ImmutableDictionary<string, string>.Empty.Add(Origin, Declaration);

/// <summary>
/// Per-compilation DiagnosticAnalyzer for compiler's syntax/semantic/compilation diagnostics.
/// </summary>
Expand All @@ -23,13 +31,13 @@ public void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context)
{
var semanticModel = _compilation.GetSemanticModel(context.Tree);
var diagnostics = semanticModel.GetSyntaxDiagnostics(cancellationToken: context.CancellationToken);
ReportDiagnostics(diagnostics, context.ReportDiagnostic, IsSourceLocation);
ReportDiagnostics(diagnostics, context.ReportDiagnostic, IsSourceLocation, s_syntactic);
}

public void AnalyzeSemanticModel(SemanticModelAnalysisContext context)
{
var declDiagnostics = context.SemanticModel.GetDeclarationDiagnostics(cancellationToken: context.CancellationToken);
ReportDiagnostics(declDiagnostics, context.ReportDiagnostic, IsSourceLocation);
ReportDiagnostics(declDiagnostics, context.ReportDiagnostic, IsSourceLocation, s_declaration);

var bodyDiagnostics = context.SemanticModel.GetMethodBodyDiagnostics(cancellationToken: context.CancellationToken);
ReportDiagnostics(bodyDiagnostics, context.ReportDiagnostic, IsSourceLocation);
Expand All @@ -38,25 +46,83 @@ public void AnalyzeSemanticModel(SemanticModelAnalysisContext context)
public static void AnalyzeCompilation(CompilationEndAnalysisContext context)
{
var diagnostics = context.Compilation.GetDeclarationDiagnostics(cancellationToken: context.CancellationToken);
ReportDiagnostics(diagnostics, context.ReportDiagnostic, location => !IsSourceLocation(location));
ReportDiagnostics(diagnostics, context.ReportDiagnostic, location => !IsSourceLocation(location), s_declaration);
}

private static bool IsSourceLocation(Location location)
{
return location != null && location.Kind == LocationKind.SourceFile;
}

private static void ReportDiagnostics(ImmutableArray<Diagnostic> diagnostics, Action<Diagnostic> reportDiagnostic, Func<Location, bool> locationFilter)
private static void ReportDiagnostics(
ImmutableArray<Diagnostic> diagnostics,
Action<Diagnostic> reportDiagnostic,
Func<Location, bool> locationFilter,
ImmutableDictionary<string, string> properties = null)
{
foreach (var diagnostic in diagnostics)
{
if (locationFilter(diagnostic.Location) &&
diagnostic.Severity != DiagnosticSeverity.Hidden)
{
reportDiagnostic(diagnostic);
var current = properties == null ? diagnostic : new CompilerDiagnostic(diagnostic, properties);
reportDiagnostic(current);
}
}
}

private class CompilerDiagnostic : Diagnostic
{
private readonly Diagnostic _original;
private readonly ImmutableDictionary<string, string> _properties;

public CompilerDiagnostic(Diagnostic original, ImmutableDictionary<string, string> properties)
{
_original = original;
_properties = properties;
}

#pragma warning disable RS0013 // we are delegating to delegatee so it is okay here
public override DiagnosticDescriptor Descriptor => _original.Descriptor;
#pragma warning restore RS0013

public override string Id => _original.Id;
public override DiagnosticSeverity Severity => _original.Severity;
public override int WarningLevel => _original.WarningLevel;
public override Location Location => _original.Location;
public override IReadOnlyList<Location> AdditionalLocations => _original.AdditionalLocations;
public override ImmutableDictionary<string, string> Properties => _properties;

public override string GetMessage(IFormatProvider formatProvider = null)
{
return _original.GetMessage(formatProvider);
}

public override bool Equals(object obj)
{
return _original.Equals(obj);
}

public override int GetHashCode()
{
return _original.GetHashCode();
}

public override bool Equals(Diagnostic obj)
{
return _original.Equals(obj);
}

internal override Diagnostic WithLocation(Location location)
{
return new CompilerDiagnostic(_original.WithLocation(location), _properties);
}

internal override Diagnostic WithSeverity(DiagnosticSeverity severity)
{
return new CompilerDiagnostic(_original.WithSeverity(severity), _properties);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// 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.Generic;
using System.Collections.Immutable;
using System.Diagnostics;

namespace Microsoft.CodeAnalysis.Diagnostics
{
Expand Down
Loading