Automatic JUCE-like code formatting with clang-format

When writing new code that relies on JUCE, it’s nice to follow the style conventions from the JUCE coding standards so that everything looks consistent, and to reduce cognitive overhead when jumping between JUCE files and project files.

I’m not a big fan of manually typing spaces to align function arguments and things, so one of my favourite tools is clang-format. I can just write whatever I want, without thinking about formatting, and it tidies up after me. This is great, because now I can just concentrate on the actual problems I’m trying to solve!

It’s possible to get pretty close to the JUCE code style with the ‘official’ version of clang-format, but there’s a few style rules that it can’t handle:

  • Adding multiple spaces after an inheritance colon
  • Omitting spaces before empty braces/parens, but adding a space before braces/parens that have contents
  • Adding a space after the unary logical not operator

To work around these issues, I’ve patched clang-format to add the formatting rules above. I’ve been using this patched version for a few months now and I’ve found it really helpful, so I thought that it might be helpful to other JUCE devs too. If you want to try it out, you can grab it here.

I wanted to try and get this stuff merged into the official version of clang-format, but I haven’t found the llvm mailing list very responsive. On the off-chance that any llvm devs are reading this, please take pity and review my PR!

21 Likes

Does this read .clang-format-ignore? I struggled to work out how to make the version of clang-format that comes as part of the macOS homebrew install ignore files defined in such a file and resorted to building a BASH script to emulate it, but would love to know the “correct” way to ignore formatting of certain files.

That’s a good question. The binaries on the page I linked above were built fairly recently, and other than the new formatting rules they should do everything that ‘normal’ clang-format does.

That being said, I haven’t heard of .clang-format-ignore before, and I can’t find documentation about it. clang-format is designed to be run on individual files or code provided by stdin and I don’t think it natively supports reformatting entire directories, so I don’t know how an ‘ignore’ feature would really work. If you want to ignore a file, just don’t feed it to clang-format.

I think that the approach of invoking clang-format from a script which knows how to read a .clang-format-ignore file seems like a better solution than modifying clang-format itself.

I also couldn’t find any documentation about it, but noticed a few repos that had .clang-format-ignore files in them. My script does the job, just wondered if it was something about the homebrew version or me not being able to get the search terms right!

Hey @reuk,

It seems your PR got a response a month ago: https://reviews.llvm.org/D55170 — is there any chance you work out their code review? I can try to volunteer here, though my C++ experience isn’t that good.

What do you think?

Cheers,
Eugene

I’ve updated the PR now. We’ll see how it goes…

2 Likes

Thanks for this! I extended your .clang-format file with some Objective-C settings that seemed decent, here it is if anyone is interested:

---
AccessModifierOffset: '-4'
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: 'false'
AlignConsecutiveDeclarations: 'false'
AlignEscapedNewlinesLeft: 'false'
AlignOperands: 'true'
AlignTrailingComments: 'false'
AllowAllParametersOfDeclarationOnNextLine: 'false'
AllowShortBlocksOnASingleLine: 'false'
AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: 'false'
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: 'false'
AlwaysBreakTemplateDeclarations: 'true'
BinPackArguments: 'false'
BinPackParameters: 'false'
BreakAfterJavaFieldAnnotations: 'false'
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Allman
BreakBeforeTernaryOperators: 'true'
BreakConstructorInitializersBeforeComma: 'false'
BreakStringLiterals: 'false'
ColumnLimit: '0'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
ConstructorInitializerIndentWidth: '4'
ContinuationIndentWidth: '4'
Cpp11BracedListStyle: 'false'
DerivePointerAlignment: 'false'
DisableFormat: 'false'
ExperimentalAutoDetectBinPacking: 'false'
IndentWidth: '4'
IndentWrappedFunctionNames: 'true'
KeepEmptyLinesAtTheStartOfBlocks: 'false'
Language: Cpp
MaxEmptyLinesToKeep: '1'
NamespaceIndentation: All
PointerAlignment: Left
ReflowComments: 'false'
SortIncludes: 'true'
SpaceAfterCStyleCast: 'true'
SpaceAfterLogicalNot: 'true'
SpaceBeforeAssignmentOperators: 'true'
SpaceBeforeCpp11BracedList: NonEmpty
SpaceBeforeParens: NonEmptyParentheses
SpaceInEmptyParentheses: 'false'
SpacesBeforeInheritanceColon: 2
SpacesInAngles: 'false'
SpacesInCStyleCastParentheses: 'false'
SpacesInContainerLiterals: 'true'
SpacesInParentheses: 'false'
SpacesInSquareBrackets: 'false'
Standard: Cpp11
TabWidth: '4'
UseTab: Never
---
Language: ObjC
BasedOnStyle: Chromium
AlignTrailingComments: true
BreakBeforeBraces: Allman
ColumnLimit: 0
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PointerBindsToType: false
SpacesBeforeTrailingComments: 1
TabWidth: 8
UseTab: Never
...
1 Like

So, has this been released already? Or should I build the tool myself for now? Couldn’t find the changelog on their website.

1 Like

The ‘parens spacing’ and ‘logical not spacing’ patches have been merged upstream. I have a couple more patches that I’d like to push, but I need to spend some time tidying them up first.

My recommendation for now would be to build clang-format using master on this repo: https://github.com/llvm/llvm-project (unless your package manager happens to have a really recent package).

You can use this with the config posted above, but you’ll need to adjust/remove the SpaceBeforeCpp11BracedList and SpacesBeforeInheritanceColon options.

4 Likes

I usually create complex ValueTree’s by taking advantage of the std::initializer_list parameters, like so (example taken from the docs):

ValueTree groups
{ "ParameterGroups", {},
  {
    { "Group", {{ "name", "Tone Controls" }},
      {
        { "Parameter", {{ "id", "distortion" }, { "value", 0.5 }}},
        { "Parameter", {{ "id", "reverb" },     { "value", 0.5 }}}
      }
    },
    { "Group", {{ "name", "Other Controls" }},
      {
        { "Parameter", {{ "id", "drywet" }, { "value", 0.5 }}},
        { "Parameter", {{ "id", "gain" },   { "value", 0.5 }}}
      }
    }
  }
};

Unfortunately, clang-format tends to destroy the formatting by turning the whole statement into a single line. I tried to experiment with several options (e.g. BinPackArguments, AllowAllArgumentsOnNextLine, …) with no luck. I know I can disable formatting for a specific range, but that would be less than ideal. Do you guys have any suggestion for this?

2 Likes

If I add a few commas, then I can get it to do this, which looks pretty readable to me:

ValueTree groups { "ParameterGroups",
                   {},
                   { {
                         "Group",
                         { { "name", "Tone Controls" } },
                         {
                             { "Parameter", { { "id", "distortion" }, { "value", 0.5 } } },
                             { "Parameter", { { "id", "reverb" }, { "value", 0.5 } } },
                         },
                     },
                     {
                         "Group",
                         { { "name", "Other Controls" } },
                         {
                             { "Parameter", { { "id", "drywet" }, { "value", 0.5 } } },
                             { "Parameter", { { "id", "gain" }, { "value", 0.5 } } },
                         },
                     } } };

I think the main thing you need to do to get this kind of formatting is to set the ‘ColumnLimit’ to ‘0’, and then clang-format will let you choose where to put newlines, rather than attempting to make each line as long as possible within the column limit.

1 Like

This looks great! So can the official clang format be configured to use JUCE code style formatting now? Where can I download the necessary files?

1 Like

As I mentioned above, I got a couple of patches merged, but I haven’t got the fix for spacing around curly-braces merged yet. The version of clang-format in homebrew seems to be from January, so I guess building from source is still the best way to get an up-to-date version.

The .clang-format config file I’ve been using looks like this. You could try it out with whatever clang-format binary you’re able to access, and just remove any keys that clang-format chokes on.

---
AccessModifierOffset: '-4'
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: 'false'
AlignConsecutiveDeclarations: 'false'
AlignEscapedNewlinesLeft: 'false'
AlignOperands: 'true'
AlignTrailingComments: 'false'
AllowAllParametersOfDeclarationOnNextLine: 'false'
AllowShortBlocksOnASingleLine: 'false'
AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: 'false'
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: 'false'
AlwaysBreakTemplateDeclarations: 'true'
BinPackArguments: 'false'
BinPackParameters: 'false'
BreakAfterJavaFieldAnnotations: 'false'
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Allman
BreakBeforeTernaryOperators: 'true'
BreakConstructorInitializersBeforeComma: 'false'
BreakStringLiterals: 'false'
ColumnLimit: '0'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
ConstructorInitializerIndentWidth: '4'
ContinuationIndentWidth: '4'
Cpp11BracedListStyle: 'false'
DerivePointerAlignment: 'false'
DisableFormat: 'false'
ExperimentalAutoDetectBinPacking: 'false'
IndentCaseLabels: 'true'
IndentWidth: '4'
IndentWrappedFunctionNames: 'true'
KeepEmptyLinesAtTheStartOfBlocks: 'false'
Language: Cpp
MaxEmptyLinesToKeep: '1'
NamespaceIndentation: All
PointerAlignment: Left
ReflowComments: 'false'
SortIncludes: 'true'
SpaceAfterCStyleCast: 'true'
SpaceAfterLogicalNot: 'true'
SpaceBeforeAssignmentOperators: 'true'
SpaceBeforeCpp11BracedList: NonEmpty
SpaceBeforeParens: NonEmptyParentheses
SpaceInEmptyParentheses: 'false'
SpacesBeforeInheritanceColon: 2
SpacesInAngles: 'false'
SpacesInCStyleCastParentheses: 'false'
SpacesInContainerLiterals: 'true'
SpacesInParentheses: 'false'
SpacesInSquareBrackets: 'false'
Standard: Cpp11
TabWidth: '4'
UseTab: Never

...
7 Likes

Thank you so much.

@reuk Thanks for adding the SpaceBeforeParens: NonEmptyParentheses style to clang-format!

Small bug I’m seeing using it (in clang-format 9): it doesn’t work for macros, e.g., you get
#if defined(asdf)
and not
#if defined (asdf)

1 Like

Ah, sorry to hear about that. AFAIK defining function-style macros doesn’t work if you leave a space before the parens (it turns into a non-function-style replacement):

#define MY_COOL_FN(foo, bar) ...
                  ^ no space here!

I imagine that clang-format is hard-coded to always omit spaces before parens in macro definitions as a precaution. Could you check by setting your config to use Always instead of NonEmptyParentheses? I’d expect that to be broken too.

1 Like

fwiw it looks like the clang-format on brew now has the parens and logical-not formatting options :tada:

7 Likes

Yes, indeed.
I didn’t know about the space before parens issue with macro functions, good to learn that.
I guess we can either

  • Make a pull request to make #if defined (asdf) get a space, like in JUCE
  • Ask the JUCE maintainers to switch to not having preceding spaces in macros (even for #if defined)
  • Live with the difference :smiley:
2 Likes

It has been 9 months. iiuc, many of the PRs are in clang-format already.

Would be nice if the JUCE team would have their formatting within the JUCE repo.
For personal JUCE projects, I’m now just copying latest message here and modify it to clang-format 10, which is harder to maintain I guess :slight_smile:

8 Likes

Based on the info here, I now have this in my latest JUCE project, updated to latest clang-format (as supported in CLion at least):

---
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BreakAfterJavaFieldAnnotations: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Allman
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakStringLiterals: false
ColumnLimit: 0
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
IndentCaseLabels: true
IndentWidth: 4
IndentWrappedFunctionNames: true
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
MaxEmptyLinesToKeep: 1
NamespaceIndentation: Inner
PointerAlignment: Left
ReflowComments: false
SortIncludes: true
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeParens: NonEmptyParentheses
SpaceInEmptyParentheses: false
SpaceBeforeInheritanceColon: true
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: true
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: "c++17"
TabWidth: 4
UseTab: Never
---
Language: ObjC
BasedOnStyle: Chromium
AlignTrailingComments: true
BreakBeforeBraces: Allman
ColumnLimit: 0
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Left
SpacesBeforeTrailingComments: 1
TabWidth: 4
UseTab: Never
...

11 Likes