Most web applications contain custom Javascript files to perform any number of tasks within the browser in order to provide an improved experience for the end user.  Of course, our Javascript source files are just as prone to syntax errors as the code we write in C# of VB.Net.  There are various tools, such as JSLint and JavaScript Lint, that allow for syntax checking of JavaScript.  However, it might also be beneficial to have this syntax checking functionality incorporated into Team Foundation Server in order to ensure that our JavaScript syntax is correct before it can be checked in.  As such, this post discusses the creation of a custom TFS check-in policy that leverages JavaScript Lint to validate any Javascript source files that were added or modified as part of a pending changeset.

I won't go into the specifics on the creation of custom check-in policies for TFS as there is plenty of documentation on MSDN and elsewhere that covers the topic in detail.  I will however cover the code for a couple of interesting aspects of the solution, the first of which is the need to specify the path to the JavaScript Lint executable.  In order for the check-in policy to perform its work, it must have the JavaScript Lint executable specified as part of its configuration when it is installed.  This is handled through an override to the Edit method of the policy.  The override displays a custom Windows Form that allows you to browse to the appropriate path of the JavaScript Lint executable and then saves that value via a public property of the policy.  This is shown in the following code snippet.

[Serializable]
public class CheckForJavascriptChangesPolicy : PolicyBase
{
       public string JsLintPath;
        public override bool Edit(IPolicyEditArgs policyEditArgs)
        {
            GetPath frm = new GetPath();
            frm.StartPosition = FormStartPosition.CenterParent;
            if (!String.IsNullOrEmpty(JsLintPath))
                frm.SelectedPath = JsLintPath;
            if (frm.ShowDialog(policyEditArgs.Parent) == DialogResult.OK)
            {
                if (!String.IsNullOrEmpty(frm.SelectedPath))
                {
                    JsLintPath = frm.SelectedPath;
                    return true;
                }
            }

            return false;
        }

        ......
}

 

Since the derived policy class is marked as Serializable the value of the JsLintPath property will be persisted and available when the policy is evaluated during check-in. 

The other interesting piece of the custom policy is of course found in the override of the Evaluate method as shown below.

public override PolicyFailure[] Evaluate()
{
  bool jsLintFound = false;
  StringCollection policyFailureMessages = new StringCollection();

  PendingChange[] changes = PendingCheckin.PendingChanges.CheckedPendingChanges;
  foreach (PendingChange pendingChange in changes)
  {
    if ((pendingChange.ChangeType.HasFlag(ChangeType.Add) || 
          pendingChange.ChangeType.HasFlag(ChangeType.Edit)) &&
          pendingChange.FileName.EndsWith(".js"))
    {
      if (jsLintFound || System.IO.File.Exists(JsLintPath))
      {
        jsLintFound = true;
        Process jslProcess = new Process();
        jslProcess.StartInfo.FileName = JsLintPath;
        jslProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        jslProcess.StartInfo.Arguments = String.Format("-process \"{0}\"",
                                                  pendingChange.LocalItem);
        jslProcess.Start();
        jslProcess.WaitForExit();
        if (jslProcess.ExitCode != 0)
        {
          policyFailureMessages.Add(String.Format(
              "Javascript Lint check failed for {0}"
              pendingChange.LocalItem));
        }
      }
      else
      {
        policyFailureMessages.Add("Javascript Lint executable wasn't found.");
        break;
      }
    }
  }

  if (policyFailureMessages.Count > 0)
  {
    List<PolicyFailure> policyFailures = new List<PolicyFailure>();

    foreach (string message in policyFailureMessages)
    {
      policyFailures.Add(new PolicyFailure(message, this));
    }

    return policyFailures.ToArray();
  }
  else
    return new PolicyFailure[0];
}

 

Here the code effectively enumerates each item in the changeset, looking for additions or modifications with an extension of *.js.  If any are found, the JavaScript Lint command line is spun up as a separate process and run against the file.  Javascript Lint will emit a return code value other than 0 if any syntax errors are found.  If that is the case, then a new entry is added to the PolicyFailure array indicating that the check-in should not proceed.  This results in output to the Pending Changes window in the Policy Warnings pane similar to the following:

 

I've created a Visual Studio 2010 extension that installs the custom policy if you are interested in trying it out.  Of course you'll also need to download the JavaScript Lint executable.  In order to install the policy just perform the following:

  1. Download and extract the *.vsix file from the provided zip.
  2. Install the *.vsix by double clicking the file.
    1. NOTE: If Visual Studio is open when you perform the install you may need to restart Visual Studio before you can continue with the next step.
  3. From Team Explorer, right click the project where you want to enable the policy and select Team Project Settings->Source Control... from the context menu.
  4. From the Source Control Settings dialog select the Check-in Policy tab, click the Add... button, select the Check for Javascript Changes Policy and click OK.
  5. Upon initially enabling the policy, supply the local path to the Javascript Lint executable (jsl.exe).