Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Add this configuration to your `.pre-commit-config.yaml` file:
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
rev: v1.4.1 # Use the tag or commit you want
rev: v1.4.2 # Use the tag or commit you want
hooks:
- id: clang-format
args: [--style=Google] # Other coding style: LLVM, GNU, Chromium, Microsoft, Mozilla, WebKit.
Expand All @@ -71,7 +71,7 @@ To use custom configurations like `.clang-format` and `.clang-tidy`:
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
rev: v1.4.1
rev: v1.4.2
hooks:
- id: clang-format
args: [--style=file] # Loads style from .clang-format file
Expand All @@ -80,7 +80,7 @@ repos:
```

> [!TIP]
> The `rev` tag (e.g. `v1.4.1`) is the **project** version, not the clang tool version. Each release bundles a default version of `clang-format` and `clang-tidy` — check the [release notes](https://github.com/cpp-linter/cpp-linter-hooks/releases) to see which tool version a given `rev` ships with. To pin an exact tool version independently of the project release, use `--version` as shown below.
> The `rev` tag (e.g. `v1.4.2`) is the **project** version, not the clang tool version. Each release bundles a default version of `clang-format` and `clang-tidy` — check the [release notes](https://github.com/cpp-linter/cpp-linter-hooks/releases) to see which tool version a given `rev` ships with. To pin an exact tool version independently of the project release, use `--version` as shown below.

### Custom Clang Tool Version

Expand All @@ -89,7 +89,7 @@ To use specific versions of clang-format and clang-tidy (using Python wheel pack
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
rev: v1.4.1
rev: v1.4.2
hooks:
- id: clang-format
args: [--style=file, --version=21] # Specifies version
Expand All @@ -113,7 +113,7 @@ automatically — no configuration needed for most projects:
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
rev: v1.4.1
rev: v1.4.2
hooks:
- id: clang-tidy
args: [--checks=.clang-tidy]
Expand Down Expand Up @@ -228,7 +228,7 @@ Use -header-filter=.* to display errors from all non-system headers. Use -system
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
rev: v1.4.1 # requires the version that introduced --fix
rev: v1.4.2 # requires the version that introduced --fix
hooks:
- id: clang-tidy
args: [--checks=.clang-tidy, --fix]
Expand All @@ -247,7 +247,7 @@ repos:

```yaml
- repo: https://github.com/cpp-linter/cpp-linter-hooks
rev: v1.4.1
rev: v1.4.2
hooks:
- id: clang-format
args: [--style=file, --version=21]
Expand All @@ -262,7 +262,7 @@ or `-j`:

```yaml
- repo: https://github.com/cpp-linter/cpp-linter-hooks
rev: v1.4.1
rev: v1.4.2
hooks:
- id: clang-tidy
args: [--checks=.clang-tidy, --version=21, --jobs=4]
Expand Down Expand Up @@ -291,7 +291,7 @@ This approach ensures that only modified files are checked, further speeding up
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
rev: v1.4.1
rev: v1.4.2
hooks:
- id: clang-format
args: [--style=file, --version=21, --verbose] # Shows processed files
Expand Down
1 change: 1 addition & 0 deletions cpp_linter_hooks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Pre-commit hooks for clang-format and clang-tidy."""
4 changes: 4 additions & 0 deletions cpp_linter_hooks/clang_format.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Pre-commit hook wrapper for clang-format."""

import subprocess
import sys
from argparse import ArgumentParser
Expand All @@ -14,6 +16,7 @@


def run_clang_format(args=None) -> Tuple[int, str]:
"""Run clang-format with hook-specific arguments removed."""
hook_args, other_args = parser.parse_known_args(args)
_, version_error = resolve_install_with_diagnostics(
"clang-format", hook_args.version, hook_args.verbose
Expand Down Expand Up @@ -65,6 +68,7 @@ def _print_verbose_info(command: list, retval: int, output: str) -> None:


def main() -> int:
"""Run clang-format as a command-line entry point."""
retval, output = run_clang_format() # pragma: no cover

# Print output for errors, but not for dry-run mode
Expand Down
16 changes: 16 additions & 0 deletions cpp_linter_hooks/clang_tidy.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Pre-commit hook wrapper for clang-tidy."""

from concurrent.futures import ThreadPoolExecutor
import subprocess
import sys
Expand Down Expand Up @@ -43,6 +45,7 @@


def _positive_int(value: str) -> int:
"""Parse a positive integer for the --jobs option."""
jobs = int(value)
if jobs < 1:
raise ArgumentTypeError("--jobs must be greater than 0")
Expand All @@ -61,13 +64,15 @@ def _positive_int(value: str) -> int:


def _find_compile_commands() -> Optional[str]:
"""Return the first common directory containing compile_commands.json."""
for d in COMPILE_DB_SEARCH_DIRS:
if (Path(d) / "compile_commands.json").exists():
return d
return None


def _compile_commands_not_found_message(path: Optional[str] = None) -> str:
"""Build a user-facing message for missing compile_commands.json files."""
if path is None:
return "No compile_commands.json was found in common build directories.\n\n" + (
COMPILE_COMMANDS_HINT
Expand Down Expand Up @@ -113,6 +118,7 @@ def _resolve_compile_db(


def _looks_like_compile_db_error(output: str) -> bool:
"""Return whether clang-tidy output indicates a compile database problem."""
lower_output = output.lower()
compile_db_error = "compile_commands.json" in lower_output and any(
pattern in lower_output
Expand All @@ -135,6 +141,7 @@ def _looks_like_compile_db_error(output: str) -> bool:


def _looks_like_msvc_error(output: str) -> bool:
"""Return whether clang-tidy output indicates an MSVC setup problem."""
lower_output = output.lower()
cl_driver_error = "cl.exe" in lower_output and any(
pattern in lower_output
Expand All @@ -155,6 +162,7 @@ def _looks_like_msvc_error(output: str) -> bool:


def _append_guidance(output: str) -> str:
"""Append troubleshooting guidance when clang-tidy output matches known errors."""
hints: List[str] = []
if _looks_like_compile_db_error(output) and COMPILE_COMMANDS_HINT not in output:
hints.append(COMPILE_COMMANDS_HINT)
Expand Down Expand Up @@ -183,10 +191,12 @@ def _exec_clang_tidy(command) -> Tuple[int, str]:


def _looks_like_source_file(path: str) -> bool:
"""Return whether a path has a recognized C or C++ source suffix."""
return Path(path).suffix.lower() in SOURCE_FILE_SUFFIXES


def _split_source_files(args: List[str]) -> Tuple[List[str], List[str]]:
"""Split clang-tidy options from trailing source file arguments."""
split_idx = len(args)
source_files: List[str] = []
for idx in range(len(args) - 1, -1, -1):
Expand All @@ -198,13 +208,17 @@ def _split_source_files(args: List[str]) -> Tuple[List[str], List[str]]:


def _combine_outputs(results: List[Tuple[int, str]]) -> str:
"""Join non-empty clang-tidy outputs from multiple executions."""
return "\n".join(output.rstrip("\n") for _, output in results if output)


def _exec_parallel_clang_tidy(
command_prefix: List[str], source_files: List[str], jobs: int
) -> Tuple[int, str]:
"""Run clang-tidy over source files in parallel and combine the results."""

def run_file(source_file: str) -> Tuple[int, str]:
"""Run clang-tidy for a single source file."""
return _exec_clang_tidy(command_prefix + [source_file])

with ThreadPoolExecutor(max_workers=min(jobs, len(source_files))) as executor:
Expand All @@ -215,6 +229,7 @@ def run_file(source_file: str) -> Tuple[int, str]:


def run_clang_tidy(args=None) -> Tuple[int, str]:
"""Run clang-tidy with hook-specific argument handling."""
hook_args, other_args = parser.parse_known_args(args)
_, version_error = resolve_install_with_diagnostics(
"clang-tidy", hook_args.version, hook_args.verbose
Expand Down Expand Up @@ -266,6 +281,7 @@ def run_clang_tidy(args=None) -> Tuple[int, str]:


def main() -> int:
"""Run clang-tidy as a command-line entry point."""
retval, output = run_clang_tidy()
if retval != 0:
print(output)
Expand Down
6 changes: 6 additions & 0 deletions cpp_linter_hooks/util.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Shared helpers for resolving and installing clang tool wheels."""

import sys
import shutil
import subprocess
Expand Down Expand Up @@ -35,10 +37,12 @@ def get_version_from_dependency(tool: str) -> Optional[str]:


def _versions_for_tool(tool: str) -> List[str]:
"""Return supported Python wheel versions for a clang tool."""
return CLANG_FORMAT_VERSIONS if tool == "clang-format" else CLANG_TIDY_VERSIONS


def _default_version_for_tool(tool: str) -> Optional[str]:
"""Return the default Python wheel version for a clang tool."""
return (
DEFAULT_CLANG_FORMAT_VERSION
if tool == "clang-format"
Expand All @@ -47,6 +51,7 @@ def _default_version_for_tool(tool: str) -> Optional[str]:


def _supported_versions_message(tool: str) -> str:
"""Build a user-facing list of supported wheel versions for a clang tool."""
versions = ", ".join(_versions_for_tool(tool))
return f"Supported {tool} wheel versions: {versions}"

Expand All @@ -66,6 +71,7 @@ def _resolve_version(versions: List[str], user_input: Optional[str]) -> Optional

# define a function to parse version strings into tuples for comparison
def parse_version(v: str):
"""Convert a dotted version string into an integer tuple."""
return tuple(map(int, v.split(".")))

# return the latest version
Expand Down
2 changes: 1 addition & 1 deletion examples/cmake/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ If you already have a CMake project, you need **two files**:
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
rev: v1.4.0
rev: v1.4.2
hooks:
- id: clang-format
args: [--style=file, --version=21]
Expand Down
Loading