Cocoa Coding Style Enforcement

Some time back, the New York Times's iOS team published Objectively Stylish, an Objective-C style guide. You've probably worked on a project before where every team member has their own unique coding style. While one can argue the relative merits of various stylistic choices, it's hard to argue against having a uniform style. For this reason, most projects should just pick a style and stick with it. But how can you make sure your chosen coding style is being maintained? Through the use of several common open source tools, you can easily enforce your project's coding style.

The main tool that all of this revolves around is Uncrustify. Uncrustify is a highly customizable code beautifier for C-like languages. Being primarily a command line utility, the easiest way to install and integrate Uncrustify into Xcode is to install the BBUncrustifyPlugin-Xcode plugin. You can download the latest release as a zip file here. After downloading and unzipping the plugin, simply move UncrustifyPlugin.xcplugin into ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins and restart Xcode.

After restarting Xcode you should notice several new options in the Edit menu:

Xcode edit menu

These new options should be fairly self explanatory. They allow you to beautify code with various degrees of scope within Xcode. BBUncrustifyPlugin-Xcode comes with a default set of style standards. If you want to customize these standards you can either go digging in Uncrustify’s configuration file or you can use UncrustifyX’s easy-to-use graphical user interface.

Customizing Your Style

UncrustifyX can act as a standalone graphical interface to beautify your code, but its ability to easily customize Uncrustify’s configuration file is of greater interest to us. You can download the latest UncrustifyX release here. After unzipping UncrustifyX and moving UncrustifyX.app to /Applications you will get a fourth new option added to Xcode’s Edit menu:

Xcode edit menu

Pick this option and you should be presented with a modal telling you that a custom configuration was not found and giving you the option to create one. Proceeding will copy the bundled Uncrustify configuration file to ~/.uncrustifyconfig and open it in UncrustifyX for customization. Let’s compare some of the recommendations from the NYTimes Objective-C Style Guide to the options provided in UncrustifyX. First, let's take a look at what they have to say about spacing.

  • Indent using 4 spaces. Never indent with tabs. Be sure to set this preference in Xcode.
  • Method braces and other braces (if/else/switch/while etc.) always open on the same line as the statement but close on a new line.

For example:

if (user.isHappy) {
//Do something
}
else {
//Do something else
}

Indent using 4 spaces can be set by selecting Indent with tabs to 0 meaning “spaces only”. You’ll also notice that UncrustifyX has a built-in documentation viewer. You can click the “i” button next to each option to read what it does and what its valid values are.

The default brace style configuration included with BBUncrustifyPlugin-Xcode should already match the style guide. If you’re interested how this is accomplished the following options are set to “Remove”:

  • Newline between function signature and open brace
  • Newline between if and open brace
  • Newline between else and open brace
  • Newline between else if and open brace
  • Newline between while and open brace
  • Newline between switch and open brace

You can switch your brace indent style to Allman style by changing all of the preceding options to “Add”. Now we'll take a look at the style guide's recommendations for conditionals.

Conditional bodies should always use braces even when a conditional body could be written without braces (e.g., it is one line only) to prevent errors. These errors include adding a second line and expecting it to be part of the if-statement. Another, even more dangerous defect may happen where the line "inside" the if-statement is commented out, and the next line unwittingly becomes part of the if-statement. In addition, this style is more consistent with all other conditionals, and therefore more easily scannable.

For example:

if (!error) {
    return success;
}

Not:

if (!error)
    return success;

or

if (!error) return success;

You can force braces to be added for single line statements by setting the following options to "Add":

  • Braces on single-line do statement
  • Braces on single-line else statement
  • Braces on single-line for statement
  • Braces on single-line while statement

What no if statement? As of the time of this writing, there is a mislabeling in UncrustifyX. The option labeled "Braces on single-line else statement" also covers if statements.

When you're happy with your rules, you can save them by choosing "Export". You should save your configuration file to .uncrustifyconfig in your home directory. OS X will warn you that the file will be hidden because of the dot but you can safely click "Use '.'" to save anyway.

Enforcement

What we have so far is a code beautifier with some customized rules. This is great, but our real end goal is to make sure that code is never commited which violates these rules. For the enforcement of our rules, we will employ the use of git hooks. GitHub user David Martin has already done the hard work for us of creating a pre-commit-script that does exactly what we want located here. Save the file at the path .git/hooks/pre-commit in the git repository you want to add style enforcement to. Open the pre-commit script you just saved in an editor, because you'll need to modify the following variables from the settings section.

Setting Value
UNCRUSTIFY "/Applications/UncrustifyX.app/Contents/Resources/uncrustify"
CONFIG "$HOME/.uncrustifyconfig"
SOURCE_LANGUAGE "OC"
FILE_EXTS ".c .h .cpp .hpp .m .mm"

You can use the uncrustify binary that comes with UncrustifyX assuming you copied UncrustifyX to /Applications. You can also easily install a standalone uncrustify executable using Homebrew by executing:

$ brew install uncrustify

In which case, you'll want to set UNCRUSTIFY to /usr/local/bin/uncrustify. You will also want to make sure your script is executable by executing:

$ chmod 755 .git/hooks/pre-commit

The script has a dependency on another script called canonicalize_filename.sh which you can download here. Copy canonicalize_filename.sh into .git/hooks/canonicalize_filename.sh and set its permissions to executable as well.

$ chmod 755 .git/hooks/canonicalize_filename.sh

Now that everything is in place, we're ready to break some rules and hope to get caught. I'll try to commit the following simple test.m file which violates the style standards we set above.

#import <Foundation/Foundation.h>

int main(int argc, char *argv[]) {
    @autoreleasepool {
        NSUInteger i = 1;

        if (i < 1)
            NSLog(@"Hello");

        if (i < 2)
        {
            NSLog(@"World");
        }
    }
}

When attempting to commit, you should get an error similar to what follows.

$ git add test.m 
$ git commit -m 'Test commit'
Parsing: test.m as language OC

The following differences were found between the code to commit and the uncrustify rules:

--- a/test.m    2013-12-29 04:01:46.000000000 -0500
+++ b/test.m    2013-12-29 04:24:42.000000000 -0500
@@ -1,15 +1,15 @@
 #import <Foundation/Foundation.h>

 int main(int argc, char *argv[]) {
-    @autoreleasepool {
-        NSUInteger i = 1;
-        
-        if (i < 1)
-            NSLog(@"Hello");
-        
-        if (i < 2)
-        {
-            NSLog(@"World");
-        }
-    }
+   @autoreleasepool {
+       NSUInteger i = 1;
+
+       if (i < 1) {
+           NSLog(@"Hello");
+       }
+
+       if (i < 2) {
+           NSLog(@"World");
+       }
+   }
 }

You can apply these changes with:
 git apply /tmp/pre-commit-uncrustify-1388309082.patch
(may need to be called from the root directory of your repository)
Aborting commit. Apply changes and commit again or skip checking with --no-verify (not recommended).
$

This pre-commit script is very helpful in that it generates patch files to make the necessary changes for you. In this case, I can follow the error's recommendation and apply the patch.

$ git apply /tmp/pre-commit-uncrustify-1388309082.patch

Your patch file will have a different random filename so just refer to the error output. After applying the patch you should be able to inspect the test.m file and see that it has been beautified and now conforms to the style rules.

#import <Foundation/Foundation.h>

int main(int argc, char *argv[]) {
    @autoreleasepool {
        NSUInteger i = 1;

        if (i < 1) {
            NSLog(@"Hello");
        }

        if (i < 2) {
            NSLog(@"World");
        }
    }
}

After applying the patch, you should now be able to commit to the repository without an error. Congratulations, your style is now being enforced in your local git repository. If you want to enforce the style on a shared git repository, you can follow the exact same procedure, except instead of naming your git hook script .git/hooks/pre-commit, you'll name it .git/hooks/pre-push and it will be located on the remote server instead of your local repository. In this scenario, you'll be able to commit style violations, but the server will not accept them being pushed to it.

Posted on Dec 30, 2013
Written by Emlyn Murphy