Skip to content

Commit

Permalink
Merge pull request #510 from heejaechang/diagnosticProperty
Browse files Browse the repository at this point in the history
add property bag to diagnostic and remember origin of the diagnostic
  • Loading branch information
heejaechang committed Feb 18, 2015
2 parents c01808e + be0a74b commit 85cde4c
Show file tree
Hide file tree
Showing 21 changed files with 510 additions and 270 deletions.
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

0 comments on commit 85cde4c

Please sign in to comment.