Join 34,000+ subscribers and receive articles from our
blog about software quality, testing, QA and security.

Support Resharper's StringFormatMethodAttribute


#1

It would be convenient if the various .NET SmartInspect Session methods which take a format and arguments were decorated with Resharper’s StringFormatMethodAttribute.

This would cause resharper to highlight format arguments and check that the appropriate parameters were provided. It also makes refactorings that convert string concatenations to formatted strings work better.

This can currently be worked around by using the “External Attribution” solution but it would be more convenient if SmartInspect supported it natively.

See here for more information on using Resharper attributes: http://www.jetbrains.com/resharper/webhelp/Code_Analysis__Code_Annotations.html


#2

So here is a External Attribution xml file for SmartInspect:

<?xml version="1.0" encoding="utf-8"?>
<assembly name="Gurock.SmartInspect">
    <member name="M:Gurock.SmartInspect.SmartInspectException.#ctor(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>format</argument></attribute>
    </member>
    <member name="M:Gurock.SmartInspect.Session.EnterMethod(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>methodNameFmt</argument></attribute>
    </member>
    <member name="M:Gurock.SmartInspect.Session.EnterMethod(Gurock.SmartInspect.Level,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>methodNameFmt</argument></attribute>
    </member>
    <member name="M:Gurock.SmartInspect.Session.EnterMethod(System.Object,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>methodNameFmt</argument></attribute>
    </member>
    <member name="M:Gurock.SmartInspect.Session.EnterMethod(Gurock.SmartInspect.Level,System.Object,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>methodNameFmt</argument></attribute>
    </member>
    <member name="M:Gurock.SmartInspect.Session.TrackMethod(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>methodNameFmt</argument></attribute>
    </member>
    <member name="M:Gurock.SmartInspect.Session.TrackMethod(Gurock.SmartInspect.Level,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>methodNameFmt</argument></attribute>
    </member>
    <member name="M:Gurock.SmartInspect.Session.TrackMethod(System.Object,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>methodNameFmt</argument></attribute>
    </member>
    <member name="M:Gurock.SmartInspect.Session.TrackMethod(Gurock.SmartInspect.Level,System.Object,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>methodNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LeaveMethod(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>methodNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LeaveMethod(Gurock.SmartInspect.Level,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>methodNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LeaveMethod(System.Object,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>methodNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LeaveMethod(Gurock.SmartInspect.Level,System.Object,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>methodNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.EnterThread(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>threadNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.EnterThread(Gurock.SmartInspect.Level,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>threadNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LeaveThread(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>threadNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LeaveThread(Gurock.SmartInspect.Level,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>threadNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.EnterProcess(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>processNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.EnterProcess(Gurock.SmartInspect.Level,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>processNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LeaveProcess(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>processNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LeaveProcess(Gurock.SmartInspect.Level,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>processNameFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogColored(System.Drawing.Color,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogColored(Gurock.SmartInspect.Level,System.Drawing.Color,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogDebug(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogVerbose(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogMessage(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogWarning(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogError(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogFatal(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogInternalError(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogAssert(System.Boolean,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogConditional(System.Boolean,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogConditional(Gurock.SmartInspect.Level,System.Boolean,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogArray(System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
   <member name="M:Gurock.SmartInspect.Session.LogArray(Gurock.SmartInspect.Level,System.String,System.Object[])">
        <attribute ctor="M:JetBrains.Annotations.StringFormatMethodAttribute.#ctor(System.String)"><argument>titleFmt</argument></attribute>
    </member>
</assembly>

#3

One other aspect here is that there are currently overrides with similar signatures that limit the usefulness of the resharper attributes.

For example LogMessage(string) hides any format errors in LogMessage(string, ) where the first parameter is missing.

It’d be good to give LogMessage(string, ) a different name so that warnings could be produced. I guess LogMessage(string) could also be removed (given it is a special case of LogMessage(string, ) ).


#4

Hello Chris,

Thanks for your posting. I believe this would add a dependency on a Resharper assembly, or similar, is this correct (when not using the “external attribution” solution)? Regarding the similar signatures (e.g. LogMessage): it’s not possible to remove/rename existing methods in order to stay backwards-compatible unfortunately. If Resharper support is critical for you, you could think about writing a small wrapper around the SmartInspect and Session classes with different calling conventions for the methods. We are happy to help if you have any questions in this case.

Regards,
Tobias


#5

Hi Tobias

Thankfully you don’t have to create a dependency on resharper (that would be a deal-breaker I’m sure)

You can define the required classes yourselves either in the resharper namespace or in your own namespace (as long as you let customers know what namespace to configure in resharper’s options).

Here’s an article about it http://tomasz-net.blogspot.com.au/2014/03/resharper-using-annotation-attributes.html

The class definitions are typically generated from Resharper’s options, but you can see what they look like in other people’s projects, eg: https://github.com/zeromq/netmq/blob/master/src/NetMQ/JetBrains.Annotations.cs

Cheers
Chris


#6

Hello Chris,

Thanks for the additional details. We will make sure to look into this but I cannot promise that this will be added. As mentioned earlier, if this is critical for you, I would recommend looking into writing a wrapper for the Session class with the most important functions and/or different calling conventions for the methods. We are happy to help in case you would have any questions in this case.

Regards,
Tobias