Files
Volkan/TOOLING.md
T
2026-09-12 00:08:03 -03:00

11 KiB

Tooling for the C/C++ Coding Style Guide

This repo ships five files that automate as much of cpp-style-guide.md as tooling can reach:

File Enforces
.clang-format All formatting: tabs, Allman braces, namespace indentation, pointer alignment, include order, line length
.clang-tidy Naming conventions for Part I (C++), plus a subset of the Language Feature Do's and Don'ts
c-api/.clang-tidy Naming conventions for Part II (C API) — snake_case instead of camelCase/PascalCase
.editorconfig Baseline tabs/whitespace for editors that don't run clang-format live
.gitattributes Forces LF line endings for source files on every OS, so diffs and formatting checks agree across Linux/Windows/Mac
.githooks/pre-commit Runs clang-format + clang-tidy automatically on every commit — pure bash, no Python or third-party framework, works on Linux/macOS/Windows

Put all of these at your repo root except c-api/.clang-tidy, which goes at the root of whatever directory holds your Part II surface (rename the folder to match your actual layout — c-api/, public-include/, etc.).


.clang-format

Covers §2 Indentation, §3 Brace Placement, §4 Pointers and References, §5 Spacing, §7 Include Directive Order, §8 Line Length, and Namespaces (indentation + no trailing brace comments), in full — running clang-format -i reformats a file to match all of these automatically. Also partially supports Class Member Organization: AllowShortFunctionsOnASingleLine: InlineOnly lets the trivial-one-liner exception stay a compact one-liner in the class body while still fully Allman-expanding anything defined out-of-class — it doesn't enforce the rest of that section (member order, where an inline template member sits within its section).

One caveat: the include-order regexes classify a header by how it's written, not what it actually is — <vector> is correctly detected as C++ standard library, but a third-party header with no slash and no extension (rare, but it happens) could be miscategorized. Spot-check unusual includes rather than trusting the grouping blindly.

.clang-tidy

Covers the naming table in §1 Naming Conventions for Part I via readability-identifier-naming, plus a handful of checks tied to specific rules in §10 Language Feature Do's and Don'ts:

  • cppcoreguidelines-owning-memory / cppcoreguidelines-no-malloc → Memory and Ownership: flags raw owning pointers and C-style allocation.
  • modernize-use-nullptr, modernize-use-override, cppcoreguidelines-pro-type-cstyle-cast → Modern C++ Features.
  • modernize-concat-nested-namespaces → Namespaces: rewrites manually nested namespace network_utils { namespace detail { ... } } into the required namespace network_utils::detail { ... } form (clang-tidy --fix applies it automatically).
  • bugprone-exception-escape → Error Handling: flags exceptions escaping noexcept contexts, including destructors.
  • GlobalVariableCase/GlobalVariablePrefix and StaticVariableCase/StaticVariablePrefix → §1: g_/s_ for mutable global and static variables. ClassMemberCase/ClassMemberPrefix covers the same s_ rule for static class data members specifically, kept separate from the m_-prefixed instance members. Constants are unaffected — GlobalConstantCase still requires PascalCase with no prefix, since a const/constexpr global isn't the mutable state these prefixes exist to flag.
  • readability-redundant-inline-specifier → Class Member Organization: flags an explicit inline keyword that's already redundant (e.g. on a function defined in the class body). A narrow, complementary check — it doesn't catch an inline body that omits the keyword, which is the common case that rule is actually about.

c-api/.clang-tidy

A second, directory-scoped .clang-tidy that overrides naming to match §13 Naming Conventions (C API) — snake_case functions/variables, UPPER_CASE enum values and macros — without touching the rest of the project. clang-tidy always walks up from the file being checked to the nearest .clang-tidy, so this is the standard way to give one subtree different rules; no per-file flags needed. InheritParentConfig: true keeps the shared checks (like bugprone-exception-escape) from the root config active here too.

.editorconfig

A fallback for editors/IDEs that display tabs-vs-spaces before clang-format has a chance to run on save. Doesn't replace .clang-format — just prevents a raw, unformatted first draft from using spaces.

.gitattributes

Forces LF line endings for source files on checkout, regardless of each contributor's OS or local core.autocrlf setting. Without this, the exact same file can end up CRLF on a Windows checkout and LF on Linux/Mac — which makes clang-format's output and the on-disk file disagree on every single line, not because of real formatting drift but purely because of line endings. This is what makes the .githooks/pre-commit diff check (and clang-format in general) behave identically across platforms.

.githooks/pre-commit

A plain bash script — no Python, no pip install, no third-party framework — that runs clang-format and clang-tidy against staged C/C++ files before each commit.

Setup (once per clone):

chmod +x .githooks/pre-commit
git config core.hooksPath .githooks

Tool availability is optional, by design:

  • If clang-format or clang-tidy isn't found on PATH, the hook prints a yellow warning and skips that check — it never blocks a commit just because a contributor hasn't installed the tools.
  • If a tool is installed and finds real formatting or lint issues, the commit is blocked with a red error and the fix command (clang-format -i ...).
  • clang-tidy additionally needs compile_commands.json to resolve #includes; if that's missing, it's treated the same as a missing binary — a warning, not a failure. Generate one with cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON (or bear -- make for non-CMake builds).

Because core.hooksPath is a local git config, each contributor runs the setup command once — it isn't automatically enabled by cloning the repo. If you want it enforced repo-wide regardless of local setup, run the same checks in CI as a second, unconditional layer.


Platform notes

The whole toolset — .clang-format, .clang-tidy, c-api/.clang-tidy, .editorconfig, .gitattributes, and .githooks/pre-commit — works unmodified on Linux, macOS, and Windows. A few specifics:

  • Linux: install the tools via your distro's package manager, e.g. apt install clang-format clang-tidy (Debian/Ubuntu) or the equivalent for your distro.
  • macOS: brew install clang-format llvm (Homebrew's llvm formula includes clang-tidy; it isn't symlinked onto PATH by default — brew --prefix llvm shows where it installed, add its bin/ to PATH).
  • Windows: install LLVM from releases.llvm.org or winget install LLVM.LLVM, which puts clang-format.exe/clang-tidy.exe on PATH. The .githooks/pre-commit script itself needs no separate installation or WSL — it runs through Git for Windows' bundled Git Bash automatically, since git.exe reads the #!/usr/bin/env bash line at the top of the hook and executes it through that shell. If a contributor doesn't have the LLVM tools installed, the hook warns and skips, exactly as on Linux/Mac.
  • Line endings: .gitattributes normalizes everything to LF on checkout regardless of platform or a contributor's core.autocrlf setting, so clang-format's output always matches the on-disk file byte-for-byte and the hook's diff check doesn't produce false positives from CRLF/LF mismatches.

What these tools can't check

Some rules in the guide are structural or naming-adjacent in ways clang-format/clang-tidy can't reach. These stay as code-review checklist items:

  • File naming — §1's PascalCase .hpp/.cpp file names and §13's snake_case .h/.c file names. Neither tool renames or checks file names.
  • One type per header — §6 and the one-template-per-header rule in Templates. No standard clang-tidy check counts top-level declarations per file.
  • The #include/forward-declare choice and the Pimpl idiom — Headers as Public API is a design decision, not a formatting or naming rule.
  • The C API symbol-prefix convention (e.g. net_manager_*) in §13 — c-api/.clang-tidy enforces the casing, but a project-specific prefix isn't something a generic naming check can require without hardcoding your library name into the config.
  • Exceptions-for-true-exceptions-only in Error Handling — whether a given throw represents a genuinely exceptional condition versus expected control flow is a judgment call no linter makes reliably.
  • The static/dynamic/runtime-load guidance in §11 — these are architectural decisions about what crosses a library boundary, not something a formatter enforces per line.
  • Doxygen comment syntax over plain // in §9 — clang-format doesn't rewrite comment styles, and no standard clang-tidy check flags a plain // comment sitting above a declaration. Doxygen itself (this project's scripts/docs.sh) will happily generate a page for an undocumented symbol; it doesn't know or care what style a comment above it uses.
  • Blank line after an early exit (return/break/continue) in §12 — neither tool has a check tied to specific statement kinds like this.
  • Member order (public/protected/private) and inline-template-member placement in Class Member Organization — no standard check inspects access-specifier ordering or where a member is positioned within a section.
  • Avoiding inline member function definitions in Class Member Organization — readability-redundant-inline-specifier (.clang-tidy) catches one narrow related mistake, a redundant explicit inline keyword, but not an inline body that omits the keyword, which is the common case.