HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpModerate

Putting everything in its place

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
itsputtingeverythingplace

Problem

A new diagnostic has seen the light of day! This one will alert you when you have a string.Format() call where the formatting placeholders are in the wrong order. The right order is defined as:


For each placeholder with index N, its value should be higher or equal to the placeholder at index N-1

If you want a more elaborate overview of what scenarios should be handled how you can take a look at the accompanying unit tests. I think I have covered all special scenarios in which placeholders can be used but since I get surprised by exotic scenarios all the time, please let me know if I forgot anything.

Unit tests Github

I omitted the tests because it would put my post way over the limited amount of characters.

Analyzer Github

```
using System.Collections.Immutable;
using System.Globalization;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace VSDiagnostics.Diagnostics.Strings.StringPlaceholdersInWrongOrder
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class StringPlaceholdersInWrongOrderAnalyzer : DiagnosticAnalyzer
{
private static readonly string Category = VSDiagnosticsResources.StringsCategory;
private const string DiagnosticId = nameof(StringPlaceholdersInWrongOrderAnalyzer);
private static readonly string Message = VSDiagnosticsResources.StringPlaceholdersInWrongOrderMessage;
private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning;
private static readonly string Title = VSDiagnosticsResources.StringPlaceholdersInWrongOrderTitle;

internal static DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, Severity, true);

public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeActi

Solution

private const string Pattern = @"(?<!\{)\{(?:\{\{)*(\d+(?::.*?)?)\}(?:\}\})*(?!\})";


That pattern is the only reason you need a helper method like this:

/// Returns the placeholder index.
internal static string Normalize(string input)
{
    var temp = input.Trim('{', '}');
    var colonIndex = temp.IndexOf(':');
    if (colonIndex > 0)
    {
        return temp.Remove(colonIndex);
    }

    return temp;
}


You could leverage named groups and, on top of a more readable regex, you get named match groups and zero string-manipulation work needed to figure out the placeholder index:

"(?\d+)(?::.*?)?)\}(?:\}\})*(?!\})"


That's it! Your index is given to you as part of the match!

Code Snippets

private const string Pattern = @"(?<!\{)\{(?:\{\{)*(\d+(?::.*?)?)\}(?:\}\})*(?!\})";
/// <returns>Returns the placeholder index.</returns>
internal static string Normalize(string input)
{
    var temp = input.Trim('{', '}');
    var colonIndex = temp.IndexOf(':');
    if (colonIndex > 0)
    {
        return temp.Remove(colonIndex);
    }

    return temp;
}
"(?<!\{)\{(?:\{\{)*((?<index>\d+)(?::.*?)?)\}(?:\}\})*(?!\})"

Context

StackExchange Code Review Q#101026, answer score: 10

Revisions (0)

No revisions yet.