Post

How to implement a linter and code formatter in a C/C++ project (clang-format and clang-tidy)

Guide explaining how to implement a linter and a code formatter on a C project.

How to implement a linter and code formatter in a C/C++ project (clang-format and clang-tidy)

In this article you will know how to use a linter in a C project, rather for a Linux or Windows environment:

  1. Install the linter and the formatter
    • clang-tidy is a clang-based C/C++ linter tool
    • clang-format is both a library and a stand-alone tool with the goal of automatically reformatting C/C++ sources files according to configurable style guides
  2. Learn how to use them
  3. Integrate them on a IDE (Visual Studio Code)

Table of contents

Installing utilities

To install only clang-format and clang-tidy tools without having to install the entire clang compiler, do the following:

On linux:

1
sudo apt install clang-format clang-tidy

On windows:

Looking for how to get these two tools I have found these ways (more or less simple) to install them on windows:

  • Go to clang official repository and install Windows binaries exe: LLVM-<version>-win64.exe. Once installed, in the route /installation/path/LLVM/bin you will have the 2 tools.
  • “Emulate” linux environment on Windows (MinGW, Cygwin, WSL…) and install like a linux machine.
  • On cpp-linter github repository make a python pip package to install only clang-format and clang-tidy, among other clang compiler’s tools. You need to have correct version of python installed on your PC. Use pip install clang-tools to install it.

In my case I have used the first installation option, so from now on the way you call the tools will only be validated for that option

Learning how to use the utilities

clang-tidy: Start linting your code

Create a config file to set checking rules

First, to use clang-tidy you need to implement all the checks that you want the utility to review.

The checks can be listed in two ways:

  • Creating a configuration file named .clang-tidy in your project’s directory
  • --checks=<string> comma-separated list of globs. Whose value is added to the value of the Checks option of the .clang-tidy file, if it exists.
.clang-tidy configuration file example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Checks: 'clang-diagnostic-*,
        clang-analyzer-*,
        cppcoreguidelines-*,
        modernize-*,
        -modernize-use-trailing-return-type'
        # clang-analyzer-     --> Clang Static Analyzer checks.
        # cppcoreguidelines-  --> Checks related to C++ Core Guidelines.
        # modernize-          --> Checks that advocate usage of modern (currently “modern” means “C++11”) language constructs.
WarningsAsErrors: true
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
FormatStyle: 'file' # Uses .clang-format file in the closest parent directory
CheckOptions:
  - key:    cert-dcl16-c.NewSuffixes
    value:  'L;LL;LU;LLU'
  - key:    cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField
    value:  '0'
  - key:    cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors
    value:  '1'
  - key:    cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
    value:  '1'
  - key:    google-readability-braces-around-statements.ShortStatementLines
    value:  '1'
  - key:    google-readability-function-size.StatementThreshold
    value:  '800'
  - key:    google-readability-namespace-comments.ShortNamespaceLines
    value:  '10'
  - key:    google-readability-namespace-comments.SpacesBeforeComments
    value:  '2'
  - key:    modernize-loop-convert.MaxCopySize
    value:  '16'
  - key:    modernize-loop-convert.MinConfidence
    value:  reasonable
  - key:    modernize-loop-convert.NamingStyle
    value:  CamelCase
  - key:    modernize-pass-by-value.IncludeStyle
    value:  llvm
  - key:    modernize-replace-auto-ptr.IncludeStyle
    value:  llvm
  - key:    modernize-use-nullptr.NullMacros
    value:  'NULL'

clang-tidy basic use

As an example, the following code (with namefile “test.c”) is used:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(int argc, char const *argv[])
{
  unsigned int var = 10;
  printf("The default value of \"var\" is: %i\n", var);
  return 0;
}

With this .clang-tidy file, that will apply all the possible check on the given file(s):

1
2
3
4
Checks: '*'
WarningsAsErrors: ''
AnalyzeTemporaryDtors: false
FormatStyle: 'file'

Although the code compiles and works, it has several good practice errors. And using the following command:

1
2
3
4
clang-tidy test.c --
           └─┬──┘ ├┘
             │    └─────╴> To avoid "Error while trying to load a compilation database"
             └────╴> File to make the checks of ".clang-tidy" file

On How To Setup Tooling For LLVM you can learn how to configure a compilation database

Its output will show you the correct way to code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1374 warnings generated.
/file/absolute/path/test.c:1:1: warning: system include stdio.h not allowed [llvmlibc-restrict-system-libc-headers]
#include <stdio.h>
^~~~~~~~~~~~~~~~~~
/file/absolute/path/test.c:3:14: warning: parameter 'argc' is unused [misc-unused-parameters]
int     main(int argc, char const *argv[])
                 ^
/file/absolute/path/test.c:3:32: warning: parameter 'argv' is unused [misc-unused-parameters]
int     main(int argc, char const *argv[])
                                   ^
/file/absolute/path/test.c:5:21: warning: 10 is a magic number; consider replacing it with a named constant [cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers]
        unsigned int var = 10;
                           ^
Suppressed 1369 warnings (1369 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.

As an example, focusing on the magic number warning cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers, the proper way to fix it is creating a constant variable (or a #define…) with a descriptive name and then give the value. Like this:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

const int DEFAULT_INITIAL_VALUE = 10;

int main(int argc, char const *argv[])
{
  unsigned int var = DEFAULT_INITIAL_VALUE;
  printf("The default value of \"var\" is: %i\n", var);
  return 0;
}

But if you know what are you doing you can ignore that error in the .clang-tidy file adding this:

1
2
3
4
5
6
7
8
9
Checks: '*'
WarningsAsErrors: ''
AnalyzeTemporaryDtors: false
CheckOptions:
    - key: 'cppcoreguidelines-avoid-magic-numbers.IgnoredIntegerValues'
      value: '10'
    - key: 'readability-magic-numbers.IgnoredIntegerValues'
      value: '10'
FormatStyle: 'file'

How clang-format is use

Make a config file to set a coding style

First, to use clang-format you need to stablish a coding style. You can use one of the predefined styles (GNU, Google, Chromium…) or a custom one.

To make easier a custom style or to preview any style, you can use one of these tools:

The style can be added in two ways:

  • With --style=llvm(for predefined options) and/or --style="{key: value, ...}" (for custom options)
  • Creating a style configuration file named .clang-format in your project’s directory and using --style=file
Shell command.clang-format file
clang-format --style="{BasedOnStyle: Chromium, IndentWidth: 4, ColumnLimit: 120, AccessModifierOffset: -2}" file_path
---
BasedOnStyle: Chromium
IndentWidth: 4
ColumnLimit: 120
AccessModifierOffset: -2

clang-format basic use

As an example, the following code (with namefile “test.c”) is used:

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(int argc, char const *argv[])
{
  unsigned int var = 10;
  printf("The default value of \"var\" is: %i\n", var);
  return
  0;
}

Although it compiles and works, it has several formatting bugs if you rely on the GNU standard. And using clang-format --style=gnu test.c, its output will show you the correct way to format the code:

1
2
3
4
5
6
7
8
9
10
root@62b80bb26435:/# clang-format --style=gnu test.c
#include <stdio.h>

int
main (int argc, char const *argv[])
{
  unsigned int var = 10;
  printf ("The default value of \"var\" is: %i\n", var);
  return 0;
}

Other way to see which parts of the code need to be formatted you can add -dry-run (or -n) option:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
root@62b80bb26435:/# clang-format -n --style=gnu test.c
test.c:3:4: warning: code should be clang-formatted [-Wclang-format-violations]
int     main(int argc, char const *argv[])
   ^^^^^
test.c:3:9: warning: code should be clang-formatted [-Wclang-format-violations]
int     main(int argc, char const *argv[])
            ^
test.c:4:2: warning: code should be clang-formatted [-Wclang-format-violations]
{
 ^
test.c:5:25: warning: code should be clang-formatted [-Wclang-format-violations]
                unsigned int var = 10;
                                      ^
test.c:6:8: warning: code should be clang-formatted [-Wclang-format-violations]
        printf("The default value of \"var\" is: %i\n", var);
              ^
test.c:6:55: warning: code should be clang-formatted [-Wclang-format-violations]
        printf("The default value of \"var\" is: %i\n", var);
                                                             ^
test.c:7:8: warning: code should be clang-formatted [-Wclang-format-violations]
        return
              ^
test.c:9:2: warning: code should be clang-formatted [-Wclang-format-violations]
}

This commands only shows you, in console, the correct way to format the code, but does not modify it. To automatically format the code you need to add the -i option:

1
root@62b80bb26435:/# clang-format -i --style=gnu test.c

How to integrate them in VS Code

Apart of clang-format and clang-tidy programs, you only need the C/C++ extension.

These are the setting that you need to know to setup both utilities:

  • For clang-format you have “C_Cpp -> formatting” subsettings
  • Forclang-tidy you have “C_Cpp -> Code Analysis” subsettings

As setting.json:

1
2
3
4
5
6
7
8
"C_Cpp.clang_format_style": "file"
"C_Cpp.clang_format_fallbackStyle": "Visual Studio"
"C_Cpp.clang_format_path": "/absolute/path/clang-format-14"

"C_Cpp.codeAnalysis.clangTidy.enabled": true
"C_Cpp.codeAnalysis.clangTidy.config": ""
"C_Cpp.codeAnalysis.clangTidy.fallbackConfig": ""
"C_Cpp.codeAnalysis.clangTidy.path": "/absolute/path/clang-tidy-14.exe"

On Linux normally the configuration is very staghforward once the tools are installed plug-and-play, because VS Code search the tools on the standard path. But on windows as you installed them as show on Installing utilities part, and you need to set the path of tools on extension settings.

References

Useful tools

Useful extensions

Visual Studio Code:

Some implementations on real projects

This post is licensed under CC BY 4.0 by the author.