Home > Plug-in Development, Providers > Creating a new code issue and its fix for Java Script

Creating a new code issue and its fix for Java Script

March 6th, 2012

The process of adding a new CodeRush code issue and a code fix provider for the code issue in the Java Script language is the same as for any other supported language. As an example, we are going to add a code issue that will highlight the “for..in” loop with the following structure:

for (var key in obj) {
  console.log(key);
}

The body of this “for..in” loop should be wrapped into an if statement to filter unwanted properties from the prototype. So, the code fix will wrap the body of the loop into an if statement as follows:

for (var key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(key);
  }
}

Here, we are sure that the “obj[key]” belongs to the object and was not inherited.

What we need:

The controls must be placed on the DXCore plug-in design surface. So, create a new DXCore plug-in project, name it as desired, for example – CR_JavaScriptSamples. Then, drop the IssueProvider component and fill its properties as follows:

  • (required) ProviderName – “Unsafe for .. in”;
  • (optional) Description – “The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype”.

Subscribe to the following events of the issue provider component:

  • LanguageSupported – to filter supported languages;
  • CheckCodeIssues – to handle code issue checks.

In the LanguageSupported event handler, we will check if we are inside the Java Script language:

private void issueProvider1_LanguageSupported(LanguageSupportedEventArgs ea)
{
  ea.Handled = ea.LanguageID == Str.Language.JavaScript;
}

The CheckCodeIssues event handler will enumerate all elements of the scope being checked for code issues (it is always an entire source file) and look for “for..in” loop elements that have the ForEach language element type. Then, we take the variable of the loop (the ‘key’ in our sample), find all of its references and check if there are references that are passed to the “hasOwnProperty” method. If there are no references passed to the “hasOwnProperty” method call, we will highlight the “for” keyword of the loop (the first three characters).

Here’s the code of the CheckCodeIssues event handler:

private void issueProvider1_CheckCodeIssues(object sender, CheckCodeIssuesEventArgs ea)
{
  // enumerate elements of the scope and find all for..in loops...
  IEnumerable<IElement> forEachEnumerable = ea.GetEnumerable(ea.Scope, LanguageElementType.ForEach);

  foreach (IElement element in forEachEnumerable)
  {
    IForEachStatement forEachStatement = element as IForEachStatement;
    if (forEachStatement == null)
      continue;
    IVariableDeclarationStatement loopVariable = forEachStatement.LoopVariable;
    if (loopVariable == null)
      continue;

    // check if there are references passed to the hasOwnProperty method call:
    bool hasOwnPropertyCheck = HasOwnPropertyCheck(forEachStatement, loopVariable);
    if (!hasOwnPropertyCheck)
    {
      // add a warning code issue indicating  if there is no hasOwnProperty check:
      SourcePoint forEachStartPoint = forEachStatement.FirstNameRange.Start;
      SourceRange issueRange = new SourceRange(forEachStartPoint, new SourcePoint(forEachStartPoint.Line, forEachStartPoint.Offset + 3));
      ea.AddWarning(issueRange, ea.Provider.DisplayName);
    }
  }
}

private bool HasOwnPropertyCheck(IForEachStatement forEachStatement, IVariableDeclarationStatement loopVariable)
{
  IElementCollection loopVariableReferences = loopVariable.FindAllReferences(forEachStatement);
  foreach (IElement varReference in loopVariableReferences)
  {
    IMethodCallExpression parentMethodCallExp = varReference.Parent as IMethodCallExpression;
    if (parentMethodCallExp == null)
      continue;

    if (parentMethodCallExp.Name == "hasOwnProperty")
      return true;
  }
  return false;
}

Now, it’s time to add the code fix provider for the code issue. Drop the CodeProvider DXCore control on the plug-in design surface and fill its properties as follows:

  • (required) ProviderName – “Wrap Loop Body”;
  • (optional) Description – “Wraps the body of a “for…in” in an if statement to filter unwanted properties from the prototype”.

To connect the provider as the code fix for the code issue, we need to fill the CodeIssueMessage property with the message of the code issue. Note that we previously used the DisplayName of the issue provider as a message. So, we can type it right in the property grid or initialize it in the InitializePlugIn method:

public override void InitializePlugIn()
{
  base.InitializePlugIn();
  codeProvider1.CodeIssueMessage = issueProvider1.DisplayName;
}

Subscribe to the following events of the code provider:

  • CheckAvailability. Here, we will check if the code provider is able to fix the code issue.
  • Apply. Here, we will replace the for..in loop with the new one.

The code of CheckAvailability event handler is pretty simple. Just a single line of code that will make the fix available if the active loop is highlighted with the code issue:

private void codeProvider1_CheckAvailability(object sender, CheckContentAvailabilityEventArgs ea)
{
  ea.Available = CodeRush.Issues.CaretInside(issueProvider1.DisplayName);
}

The Apply event handler will take the active loop, clone it for further modification, create a new check (an if statement) for the hasOwnProperty call and move all existing code into the hasOwnProperty check.

Here’s the code of the event handler:

private void codeProvider1_Apply(object sender, ApplyContentEventArgs ea)
{
  // take the active for..in loop of the ForEach type:
  ForEach activeForEach = ea.Element as ForEach;
  if (activeForEach == null)
    return;

  // clone it for a further modification:
  ForEach newForEach = activeForEach.Clone() as ForEach;

  // create a call to the hasOwnProperty method:
  MethodReferenceExpression methodReferenceExp = new MethodReferenceExpression("hasOwnProperty");
  methodReferenceExp.Qualifier = newForEach.Expression.Clone() as Expression;
  MethodCallExpression hasOwnPropertyCall = new MethodCallExpression(methodReferenceExp);
  hasOwnPropertyCall.AddArgument(new ElementReferenceExpression(newForEach.LoopVariable.Name));

  // prepare code nodes:
  LanguageElementCollection forEachNodes = new LanguageElementCollection();
  forEachNodes.AddRange(newForEach.Nodes);

  // create a check the call to the hasOwnProperty with the source code nodes:
  If hasOwnPropertyCheck = new If(hasOwnPropertyCall, forEachNodes);
  newForEach.Nodes.Clear();
  newForEach.AddNode(hasOwnPropertyCheck);

  // generate a new code of the for..in loop:
  string newForEachCode = CodeRush.Language.GenerateElement(newForEach);

  // replace the source loop with a new one:
  activeForEach.ReplaceWith(newForEachCode, codeProvider1.DisplayName, true);
}

If you have any question on this sample or suggestions on improvements to this article, please leave them as comments to this post. The source code is attached (26,308 bytes, C#, VS2010).

—–
Products: DXCore, CodeRush Pro
Versions: 11.2 and up
VS IDEs: 2008 and up
Updated: Mar/06/2012
ID: D142

Similar Posts: