Linting with clang-format
“Quality means doing it right even when no one is looking.”— Henry Ford as quoted on The Ultimate List of 100 Software Testing Quotes
I have been aware of DevOps for about 5 or so years but I hadn’t been using it much until recently. I have always known that one is expected to test one’s software, but the reality has been (for me at least): most projects focus on getting a working version as fast as possible and do limited testing. The pressures of deadlines are real and my experience has been that quality is mostly bolted on as an afterthought.
Linting Defined
According to Wikipedia, (see Lint (software)), linting is defined as:
Lint, or a linter, is a static code analysis tool used to flag programming errors, bugs, stylistic errors and suspicious constructs.
Apparently linters have been around since at least Yacc around 1978 so I feel a bit late to the party.
clang-format vs clang
I learned a hard lesson that took me longer than I care to admit to figure out. It is: clang DOES NOT EQUATE TO clang-format! Clang is based on LLVM and is more a compiler and toolchain and is way more than what I need. All that I require is the subset of clang that is know as clang-format. For some reason this distinction helped me get through the massive documentation available in LLVM.
Linting Tips
Strive to make code self-commenting. See this YouTube video:
https://www.youtube.com/watch?v=Bf7vDBBOBUA
Of course, that is not always possible. Split up long comments into multiple lines when such is the case.
clang-format Example
I used Red Hat Enterprise Linux (RHEL) version 8 and GitLab for this. Maybe some future post will see me putting this up on GitHub Actions (https://github.com/features/actions) but no promises. I covered how to use RHEL with Oracle VM VirtualBox previously at https://www.keypuncher.net/blog/virtualbox-red-hat-guest-on-windows-host so see that post if you need a refresher on how to get the 2 working on Windows.
Using GitLab is a big topic and is well outside the scope of this document, but I refer you to the following resources:
There are also a variety of books and courses on GitLab. For example, O’Reilly https://www.oreilly.com/, Udemy https://www.udemy.com/, and LinkedIn Learning https://www.linkedin.com/learning-login/.
Extra Packages for Enterprise Linux
Before installing clang you need to install the Extra Packages for Enterprise Linux (EPEL) -- this is done via dnf and you will need sudo access to the machine. This is accomplished as follows on a Red Hat 8 terminal:
subscription-manager repos --enable codeready-builder-for-rhel-8-$(arch)-rpms
dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
Then, to install the necessary RHEL libraries for clang-format, run this command:
sudo dnf install clang clang-tools-extra
A Single File
After clang is installed the next step is to make sure it is working as expected. To do so the following is provided.
Here is an example file we want to run through clang-format. Open your favorite code editor and enter EXACTLY this text:
// A basic Hello World C++ examples. Returns 0, and prints "Hello World!".
#include <iostream>
using namespace std;
int
main()
{
cout << "Hello World!"
<< endl;
return
0;
}
To run clang-format on this file, simply run (<filename> provides the name of the above file):
clang-format -style=google -i <filename>
The “-style=google” calls for Google style and the “-i” option says to go ahead and modify code “in place.”
This should result in a slightly modified file:
// A basic Hello World C++ examples. Returns 0, and prints "Hello World!".
#include <iostream>
using namespace std;
int main() {
cout << "Hello World!" << endl;
return 0;
}
The only changes were to whitespace:
The brace on the main function was moved up a line.
The indentation was made 2 spaces instead of 4 (tabs are disallowed by Google).
This is because we specified Google style and the Google Style C++ Guide (https://google.github.io/styleguide/cppguide.html) governs that. There are a number of other options for clang-format which we will cover shortly.
Now we are ready to get fancy with the find Linux utility.
Find
I found the Linux find command used in conjunction with clang-format useful:
find . -not -name "*.pb.cpp" -and -not -name "*.pb.h" -and -not -name ".*" -exec clang-format -style=google -i {} \;
What this command does is as follows:
find .
Calls the find command in the current directory. My C++ files happen to live in a subdirectory named src -- be sure to cd_(command) into the appropriate directory before running this command!
-not -name "*.pb.cpp" -and -not -name "*.pb.h" -and -not -name ".*"
I am using Google protocol buffers (https://protobuf.dev/) and those files are autogenerated for me so I want to ignore them. I also have some hidden files that I want to ignore so I am skipping everything that begins with a period (.)
Now that we’ve done some preprocessing it’s time to call clang-format. That’s what the last part of the command does:
-exec clang-format -style=google -i {} \;
The curly braces {}
and the backslash semicolon \;
are used in combination with the find
command to execute another command on each found file or directory. The {}
is a placeholder that represents the current file or directory, and the \;
denotes the end of the -exec
option for find
.
To further explore we will need some details on how clang-format works.
Details on clang-format
See https://clang.llvm.org/docs/ClangFormatStyleOptions.html#basedonstyle.
Styles
Styles employed include:
LLVM A style complying with the LLVM coding standards
Google A style complying with Google’s C++ style guide
Chromium A style complying with Chromium’s style guide
Mozilla A style complying with Mozilla’s style guide
WebKit A style complying with WebKit’s style guide
Microsoft A style complying with Microsoft’s style guide
GNU A style complying with the GNU coding standards
InheritParentConfig Not a real style, but allows to use the .clang-format file from the parent directory (or its parent if there is none). If there is no parent file found it falls back to the fallback style, and applies the changes to that.
Note that if the last one is used, InheritParentStyleConfig, then if clang-format can’t find the necessary file it will revert back to LLVM style.
--dry-run
If changes are not yet desired because a need to preview the file modifications first use:
clang-format --dry-run <file>
This is as opposed to immediately executing the changes in place with the “-i” option.
By default, clang-format
with the --dry-run
option only displays the formatted code without any indication of what might be wrong or inconsistent with the defined code style. However, you can leverage the -output-replacements-xml
option to obtain more detailed information about the changes that clang-format
would make during a dry run.
Here's an example command that combines --dry-run
with -output-replacements-xml
:
clang-format --dry-run -output-replacements-xml file.cpp
This command will output the formatted code along with XML-formatted information about the replacements that would be made to the code. Each <replacement>
element in the XML output represents a change that clang-format
would apply.
Disabling clang-format
If the need should arise to disable clang format for a number of lines you can do so with the comments “clang-format off” and a corresponding “clang-format on” as below:
// clang-format off
… code you don’t want linted
// clang-format on
I would do this very sparingly!
Documentation
A link to clang-format documentation can be found here: https://clang.llvm.org/docs/ClangFormatStyleOptions.html
Alternatives to clang-format
Alternatives for C++ include:
cppcheck: https://cppcheck.sourceforge.io/
Valgrind: https://valgrind.org/ -- this is more for detecting memory_leaks.
include-what-you-use https://include-what-you-use.org/ -- the main page suggests that this tool be used in conjunction with clang.
Clang-format can’t even do C# files, for that you need one of these:
dotnet format: https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-format
StyleCop: https://marketplace.visualstudio.com/items?itemName=ChrisDahlberg.StyleCop
Roslynator: https://marketplace.visualstudio.com/items?itemName=josefpihrt.Roslynator2022
Python linters include:
Pylint: https://pypi.org/project/pylint/
JavaScript linters:
ESLint: https://eslint.org/
JSHint: https://jshint.com/
Standard JS: https://github.com/standard
Linters for other languages are also available. See, for example,
https://github.com/caramelomartins/awesome-linters
https://github.com/github/super-linter
And that, my friends, was my deep dive into linting with clang-format!
Feedback
As always, do make a comment or write me an email if you have something to say about this post!