From 4d5d7b30a97f8ee4c7064b290f981585ad258e89 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Tue, 11 Jul 2023 06:31:39 -0400 Subject: [PATCH] Fix errors in diagnostic formats and log exceptions from future ones --- Core/Analysis/XmlCoreDiagnostics.cs | 6 +- Core/Analysis/XmlDiagnosticDescriptor.cs | 17 +++-- Core/StringSyntaxAttribute.cs | 71 +++++++++++++++++++ Editor/Tagging/XmlSyntaxValidationTagger.cs | 12 ++++ .../XmlSyntaxValidationTaggerProvider.cs | 5 +- 5 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 Core/StringSyntaxAttribute.cs diff --git a/Core/Analysis/XmlCoreDiagnostics.cs b/Core/Analysis/XmlCoreDiagnostics.cs index f9a4318..9efeade 100644 --- a/Core/Analysis/XmlCoreDiagnostics.cs +++ b/Core/Analysis/XmlCoreDiagnostics.cs @@ -8,7 +8,7 @@ class XmlCoreDiagnostics public static XmlDiagnosticDescriptor IncompleteAttributeValue = new ( nameof (IncompleteAttributeValue), "Incomplete attribute value", - "The value of attribute '{0}' ended unexpectedly.", + "The value of attribute '{0}' ended unexpectedly due to character '{1}'.", XmlDiagnosticSeverity.Error ); @@ -29,7 +29,7 @@ class XmlCoreDiagnostics public static XmlDiagnosticDescriptor IncompleteAttribute = new ( nameof (IncompleteAttribute), "Incomplete attribute", - "Attribute is incomplete due to unexpected character '{O}'.", + "Attribute is incomplete due to unexpected character '{0}'.", XmlDiagnosticSeverity.Error ); @@ -64,7 +64,7 @@ class XmlCoreDiagnostics public static XmlDiagnosticDescriptor MalformedTagOpening = new ( nameof (MalformedTagOpening), "Malformed tag", - "Tag is malformed due to unexpected character '{O}'.", + "Tag is malformed due to unexpected character '{0}'.", XmlDiagnosticSeverity.Error ); diff --git a/Core/Analysis/XmlDiagnosticDescriptor.cs b/Core/Analysis/XmlDiagnosticDescriptor.cs index 90f9071..adb219d 100644 --- a/Core/Analysis/XmlDiagnosticDescriptor.cs +++ b/Core/Analysis/XmlDiagnosticDescriptor.cs @@ -3,6 +3,7 @@ using System; +using System.Diagnostics.CodeAnalysis; namespace MonoDevelop.Xml.Analysis { @@ -10,10 +11,12 @@ public class XmlDiagnosticDescriptor { public string Id { get; } public string Title { get; } + + [StringSyntax (StringSyntaxAttribute.CompositeFormat)] public string? Message { get; } public XmlDiagnosticSeverity Severity { get; } - public XmlDiagnosticDescriptor (string id, string title, string? message, XmlDiagnosticSeverity severity) + public XmlDiagnosticDescriptor (string id, string title, [StringSyntax (StringSyntaxAttribute.CompositeFormat)] string? message, XmlDiagnosticSeverity severity) { Title = title ?? throw new ArgumentNullException (nameof (title)); Id = id ?? throw new ArgumentNullException (nameof (id)); @@ -28,9 +31,15 @@ public XmlDiagnosticDescriptor (string id, string title, XmlDiagnosticSeverity s internal string GetFormattedMessage (object[]? args) { - combinedMsg ??= (combinedMsg = Title + Environment.NewLine + Message); - if (args != null && args.Length > 0) { - return string.Format (combinedMsg, args); + try { + combinedMsg ??= (combinedMsg = Title + Environment.NewLine + Message); + if (args != null && args.Length > 0) { + return string.Format (combinedMsg, args); + } + } catch (FormatException ex) { + // this is likely to be called from somewhere other than where the diagnostic was constructed + // so ensure the error has enough info to track it down + throw new FormatException ($"Error formatting message for diagnostic {Id}", ex); } return combinedMsg; } diff --git a/Core/StringSyntaxAttribute.cs b/Core/StringSyntaxAttribute.cs new file mode 100644 index 0000000..ee4bfcf --- /dev/null +++ b/Core/StringSyntaxAttribute.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ +#if !NET7_0_OR_GREATER + /// Specifies the syntax used in a string. + [AttributeUsage (AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + sealed class StringSyntaxAttribute : Attribute + { + /// Initializes the with the identifier of the syntax used. + /// The syntax identifier. + public StringSyntaxAttribute(string syntax) + { + Syntax = syntax; + Arguments = Array.Empty(); + } + + /// Initializes the with the identifier of the syntax used. + /// The syntax identifier. + /// Optional arguments associated with the specific syntax employed. + public StringSyntaxAttribute(string syntax, params object?[] arguments) + { + Syntax = syntax; + Arguments = arguments; + } + + /// Gets the identifier of the syntax used. + public string Syntax { get; } + + /// Optional arguments associated with the specific syntax employed. + public object?[] Arguments { get; } + + /// The syntax identifier for strings containing composite formats for string formatting. + public const string CompositeFormat = nameof(CompositeFormat); + + /// The syntax identifier for strings containing date format specifiers. + public const string DateOnlyFormat = nameof(DateOnlyFormat); + + /// The syntax identifier for strings containing date and time format specifiers. + public const string DateTimeFormat = nameof(DateTimeFormat); + + /// The syntax identifier for strings containing format specifiers. + public const string EnumFormat = nameof(EnumFormat); + + /// The syntax identifier for strings containing format specifiers. + public const string GuidFormat = nameof(GuidFormat); + + /// The syntax identifier for strings containing JavaScript Object Notation (JSON). + public const string Json = nameof(Json); + + /// The syntax identifier for strings containing numeric format specifiers. + public const string NumericFormat = nameof(NumericFormat); + + /// The syntax identifier for strings containing regular expressions. + public const string Regex = nameof(Regex); + + /// The syntax identifier for strings containing time format specifiers. + public const string TimeOnlyFormat = nameof(TimeOnlyFormat); + + /// The syntax identifier for strings containing format specifiers. + public const string TimeSpanFormat = nameof(TimeSpanFormat); + + /// The syntax identifier for strings containing URIs. + public const string Uri = nameof(Uri); + + /// The syntax identifier for strings containing XML. + public const string Xml = nameof(Xml); + } +#endif +} \ No newline at end of file diff --git a/Editor/Tagging/XmlSyntaxValidationTagger.cs b/Editor/Tagging/XmlSyntaxValidationTagger.cs index 4c5aab3..049fe65 100644 --- a/Editor/Tagging/XmlSyntaxValidationTagger.cs +++ b/Editor/Tagging/XmlSyntaxValidationTagger.cs @@ -13,6 +13,9 @@ using MonoDevelop.Xml.Analysis; using MonoDevelop.Xml.Editor.Parsing; +using MonoDevelop.Xml.Editor.Logging; +using Microsoft.Extensions.Logging; +using MonoDevelop.Xml.Logging; namespace MonoDevelop.MSBuild.Editor { @@ -20,6 +23,7 @@ class XmlSyntaxValidationTagger : ITagger, IDisposable { readonly XmlBackgroundParser parser; readonly JoinableTaskContext joinableTaskContext; + readonly ILogger logger; ParseCompletedEventArgs? lastArgs; public XmlSyntaxValidationTagger (ITextBuffer buffer, XmlSyntaxValidationTaggerProvider provider) @@ -27,6 +31,7 @@ public XmlSyntaxValidationTagger (ITextBuffer buffer, XmlSyntaxValidationTaggerP parser = provider.ParserProvider.GetParser (buffer); parser.ParseCompleted += ParseCompleted; joinableTaskContext = provider.JoinableTaskContext; + logger = provider.LoggerFactory.GetLogger (buffer); } void ParseCompleted (object? sender, ParseCompletedEventArgs args) @@ -48,6 +53,9 @@ public void Dispose () } public IEnumerable> GetTags (NormalizedSnapshotSpanCollection spans) + => logger.InvokeAndLogExceptions (() => GetTagsInternal (spans)); + + IEnumerable> GetTagsInternal (NormalizedSnapshotSpanCollection spans) { //this may be assigned from another thread so capture a consistent value var args = lastArgs; @@ -84,6 +92,10 @@ static string GetErrorTypeName (XmlDiagnosticSeverity severity) return PredefinedErrorTypeNames.SyntaxError; case XmlDiagnosticSeverity.Warning: return PredefinedErrorTypeNames.Warning; + case XmlDiagnosticSeverity.Suggestion: + return PredefinedErrorTypeNames.HintedSuggestion; + case XmlDiagnosticSeverity.None: + return PredefinedErrorTypeNames.Suggestion; } throw new ArgumentException ($"Unknown DiagnosticSeverity value {severity}", nameof (severity)); } diff --git a/Editor/Tagging/XmlSyntaxValidationTaggerProvider.cs b/Editor/Tagging/XmlSyntaxValidationTaggerProvider.cs index 3db6000..5330e70 100644 --- a/Editor/Tagging/XmlSyntaxValidationTaggerProvider.cs +++ b/Editor/Tagging/XmlSyntaxValidationTaggerProvider.cs @@ -12,6 +12,7 @@ using Microsoft.VisualStudio.Utilities; using MonoDevelop.Xml.Editor; +using MonoDevelop.Xml.Editor.Logging; using MonoDevelop.Xml.Editor.Parsing; namespace MonoDevelop.MSBuild.Editor @@ -25,12 +26,14 @@ class XmlSyntaxValidationTaggerProvider : ITaggerProvider { public JoinableTaskContext JoinableTaskContext { get; } public XmlParserProvider ParserProvider { get; } + public IEditorLoggerFactory LoggerFactory { get; } [ImportingConstructor] - public XmlSyntaxValidationTaggerProvider (JoinableTaskContext joinableTaskContext, XmlParserProvider parserProvider) + public XmlSyntaxValidationTaggerProvider (JoinableTaskContext joinableTaskContext, XmlParserProvider parserProvider, IEditorLoggerFactory loggerFactory) { JoinableTaskContext = joinableTaskContext; ParserProvider = parserProvider; + LoggerFactory = loggerFactory; } public ITagger CreateTagger (ITextBuffer buffer) where T : ITag