3

I am using an enum being exposed by a 3rd party assembly, e.g.,

public enum APIEnum
{
  Val1,
  Val2
}

. However, a number of these values cause incorrect behavior in my application. I want to generate a compiler warning if one of these "bad" enum values is used in code, e.g.,

APIEnum usedVal = APIEnum.Val2;

Compiler Warning: APIEnum.Val2 causes incorrect behavior.

My ultimate goal is to generate a warning that must be consciously #pragma'd if a bad value is used (2% of the overall cases). Otherwise, the warning occurs, and since we have Warnings as Errors, that breaks the compile until fixed or #pragma'd.

I've looked at the threads here and here on using the Obsolete attribute to solve this problem, but I fear the Obsolete will cause confusion since the value is not really obsolete.

I've considered the possibility of using a Resharper code analysis plugin to solve the problem, and that is definitely an option. I'm no expert on Resharper or how to best solve the problem via Resharper.

Community
  • 1
  • 1
mcating
  • 1,062
  • 11
  • 18

3 Answers3

2

You could create a custom Code Analysis (FxCop) rule for this, or indeed roll your own Resharper rule. A custom Code Analysis rule should be relatively simple, check out my rule which checks whether a regex compiles or not, it finds all uses of the RegexOptions enumeration. You should be able to build your own custom rule from there.

General great sites for custom Code Analysis rules:

If you get stuck while writing your own rule, don't hesitate to share the code so far to ask for more specific help.

Use the VisitAssignment Statement, then use the assignment.Target.Type.FullName to get the underlying enum type. Be sure to check Target.Type to be nor null, delegates can have a null Type.

Introspector showing Enum Type

The Methodcall will also see the Enum:

Introspector showing Assignment and Methodcall

jessehouwing
  • 106,458
  • 22
  • 256
  • 341
  • 1
    I'm voting this answer best for my question. Though I dislike the volume of code required to enforce the rule, the static code analysis is something I can GUARANTEE breaks the compile step (I found the Resharper result to end up being an ignorable nuisance when I tested that approach in the IDE). – mcating Feb 13 '13 at 20:37
2

You can use Structural Search in ReSharper. Go to ReSharper -> Options | Code Inspection -> Custom patterns, click Add Pattern, enter APIEnum.Val2 into the field Search pattern and your error description into Description. Set pattern severity to Show as error. Click Add. That's all. The only downside is if you would have another APIEnum with the same Val2 value in your project, even in the different namespace, then it would also be flagged as error.

You should also turn on ReSharper -> Options | Code Inspection -> Settings | Analyse errors in whole solution to show errors in every file.

Dmitry Osinovskiy
  • 9,999
  • 1
  • 47
  • 38
  • Thanks for the ideas. On the Resharper code inspection option, is there an easy way to transfer that rule to all developers and build machines? (TOTAL Resharper neophyte here.) – mcating Feb 11 '13 at 21:26
  • 1
    Yes, after you've added your pattern, click "Save to" on options screen and choose "Solution ... team-shared". Then add file YourSolution.sln.dotSettings to version control system and then it should work for everybody. P.S. You should also turn on `ReSharper -> Options | Code Inspection -> Settings | Analyse errors in whole solution` to show errors in every file. – Dmitry Osinovskiy Feb 12 '13 at 12:50
0

I've prototyped a solution using the FxCop approach, and I don't think FxCop can actually solve it. I tried the following code in a rule:

public class DoNotUseSpecificEnum : RuleBase
{
  private string[] _enumValsToCheck =
  {
    "APIEnum.Val2"
  };

  public DoNotUseSpecificEnum ()
    : base("DoNotUseSpecificEnum ") { }

  public override void VisitBinaryExpression(BinaryExpression binaryExpression)
  {
    if ( _enumValsToCheck.Contains(
      binaryExpression.Operand1.ToString()) 
      || _enumValsToCheck.Contains( binaryExpression.Operand2.ToString() ) )
    {
      this.Problems.Add(new Problem(base.GetResolution(),
        binaryExpression.SourceContext));
    }

    base.VisitBinaryExpression(binaryExpression);
  }
}

When I trace into the VisitBinaryExpression, I find the Operand1 value to be "1" instead of "APIEnum.Val2". This makes sense in hindsight (FxCop working on MSIL which has replaced the enum names/values with numeric literals); I just wish I'd realized it before I dug into the pain/glory of custom FxCop rules. :)

The other approach I found was using StyleCop to do syntax analysis, but this looks even harder to figure out and less popular than custom FxCop rules.

I'm going to propose the use of Resharper team-specific custom patterns as the way of managing this. I remain concerned about this approach (especially since we'll have the same rules across multiple solutions and teams), but the build machine concern is a lot smaller than I thought. TeamCity allows build-time inspection of Resharper rules out of the box, so we can at least configure our CI server to find and report uses of the enum that haven't been suppressed using Resharper syntax.

mcating
  • 1,062
  • 11
  • 18
  • The saga continues - there doesn't seem to be any way to suppress the Resharper warnings or errors generated by custom patterns for a single instance. (// ReSharper disable doesn't work with custom patterns.) I found a promising approach using #region RFail [here](http://stackoverflow.com/questions/3517278/disable-resharper-with-comment), but there weren't many examples and it didn't work when I tried it. I'm back to the drawing board... – mcating Feb 14 '13 at 08:23
  • When you look at AssignmentExpressions instead, you will get the base type of the Enum as well, you can then check the value of the int against the expected value of your enum. – jessehouwing Feb 14 '13 at 10:12
  • Thanks for the help, jessehouwing. In stepping through a prototype VisitAssignmentStatement, I am having trouble differentiating between APIEnum and AnyOtherEnum. I see that assignment.Target.Type.BaseType.FullName is "System.Enum", but I don't find a name or reference to the APIEnum type anywhere in the assignment.Target field. (I could be missing it - assignment.Target is a complicated object!) But without the string APIEnum showing up somewhere, I can't isolate the target enum type from any other enum type... – mcating Feb 14 '13 at 16:37
  • Updated my original answer with more details, I think you're almost there :). – jessehouwing Feb 14 '13 at 18:29
  • Cool - DEFINITELY getting closer. The VisitAssignment is coming together in my prototype. I've started investigating the VisitBinaryExpression as well. I found that I can get to the enum variable's raw type in a convoluted way, via ((BinaryExpression)binaryExpression.Operand1).Operand1.Type.FullName. I'll have to be careful with cast safety, but I can get to the enum's true name. Getting there! – mcating Feb 15 '13 at 03:30
  • I'm fully recognizing assignment, comparison and case statements in my FxCop rule now. The example I'm currently having problems with is the ternary operator of the form OperatorEnum val2 = (val1 == OperatorEnum.Val2) ? OperatorEnum.Val2 : OperatorEnum.Val1; . This is not causing the VisitTernaryExpression to occur. Variations of this do cause VisitBinaryExpression to occur, but this scenario is gumming me up. Ideas? – mcating Feb 15 '13 at 06:56
  • Great to hear! Try implementing `CheckBinaryExpression` instead of `VisitBinaryExpression`. Or calling `base.VisitXXXExpression` after your own checks. – jessehouwing Feb 15 '13 at 07:44
  • Or drop what you have so far on dropbox or something so I can debug it here :). – jessehouwing Feb 15 '13 at 07:45