diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-04-28 21:56:22 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-04-28 21:56:22 -0500 |
| commit | 33015e0393b3aef4a6e129dec5bcf92444321e9c (patch) | |
| tree | fea7177df0712d09f7d94d24ede877b7e65c56ab | |
| parent | 5d42085dd36f14aa3385547947ccd6e6c5f03d50 (diff) | |
Add clang-tidy lint infrastructure (.clang-tidy + lint.sh)
`./lint.sh` runs clang-tidy inside the fluorine-builder Docker image so
the toolchain matches what compile_commands.json was generated with.
Strips -mno-direct-extern-access (gcc-only) before invoking clang and
skips browserview/browserdialog since QtWebEngine is Windows-only in
this fork.
Scope: src/src/ + libs/skse_log_redirector/ (everything we authored).
Vendored upstream libs follow their own coding standards and stay
out-of-scope.
Default-off the noisiest categories (fuchsia/google/llvm/altera,
pro-type-*, magic-numbers, named-parameter, function-cognitive-
complexity, identifier-length, etc.) so the surfaced warnings are
actionable.
Dockerfile: install clang-tidy alongside the build deps so the image
ships everything lint.sh needs.
Usage:
./build.sh tarball # build the image (one-time after this commit)
# and produce compile_commands.json
./lint.sh # lint everything
./lint.sh --fix # apply auto-fixes
./lint.sh src/src/foo.cpp # lint a single file
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| -rw-r--r-- | .clang-tidy | 66 | ||||
| -rw-r--r-- | docker/Dockerfile | 2 | ||||
| -rwxr-xr-x | lint.sh | 106 |
3 files changed, 174 insertions, 0 deletions
diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..ce05d5f --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,66 @@ +# Clang-tidy configuration for the Fluorine Manager Linux port. +# +# Scope: only files under src/src/ and any libs/ subdirectory we authored +# (skse_log_redirector). Vendored upstream code (libs/uibase, libs/game_*, +# libs/installer_fomod*, libs/preview_*, libs/check_fnis, libs/tool_*, +# libs/bsa*, libs/esptk, libs/archive, libs/libbsarch, libs/usvfs, +# libs/plugin_python, libs/basic_games, libs/script_extender_plugin_checker, +# libs/form43_checker, libs/diagnose_basic, libs/dds-header, libs/cmake_common, +# libs/vcpkg-registry, libs/lzokay.py, libs/winreg.py, libs/7zip, libs/lootcli, +# libs/bsapacker, libs/bsa_extractor, libs/bsa_ffi, libs/steam_appinfo_ffi) +# follows upstream MO2 / external projects and is excluded by lint.sh. +# +# Disabled checks (with rationale): +# - fuchsia / google / llvm / altera: house-style checks for projects that +# aren't this one. +# - cppcoreguidelines-pro-* / -avoid-c-arrays / -avoid-magic-numbers: too +# noisy for code that interfaces with Qt and Win32-shaped APIs. +# - modernize-use-trailing-return-type: stylistic preference. +# - readability-magic-numbers / -named-parameter / -function-cognitive- +# complexity / -identifier-length: noisy and largely subjective. +# - bugprone-easily-swappable-parameters / -branch-clone: too many false +# positives in plumbing code. +# - misc-include-cleaner / -no-recursion / -non-private-member-variables- +# in-classes: noisy and partly redundant with clangd. +Checks: > + bugprone-*, + performance-*, + modernize-*, + readability-*, + clang-analyzer-*, + misc-const-correctness, + misc-redundant-expression, + misc-throw-by-value-catch-by-reference, + misc-unused-parameters, + -modernize-use-trailing-return-type, + -modernize-avoid-c-arrays, + -modernize-use-nodiscard, + -readability-magic-numbers, + -readability-named-parameter, + -readability-function-cognitive-complexity, + -readability-identifier-length, + -readability-implicit-bool-conversion, + -readability-uppercase-literal-suffix, + -readability-braces-around-statements, + -readability-else-after-return, + -bugprone-easily-swappable-parameters, + -bugprone-branch-clone, + -bugprone-narrowing-conversions, + -performance-no-int-to-ptr, + -performance-avoid-endl + +WarningsAsErrors: '' + +# Apply checks only to our own headers — the regex matches src/src and our +# authored libs subdirs. Vendored code lives under different paths. +HeaderFilterRegex: '(^|/)(src/src/|libs/skse_log_redirector/)' + +FormatStyle: file + +CheckOptions: + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.PrivateMemberPrefix + value: m_ + - key: misc-unused-parameters.IgnoreVirtual + value: 'true' diff --git a/docker/Dockerfile b/docker/Dockerfile index 60caaf6..a96fb71 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -36,6 +36,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 python3-pip python3-pyqt6 \ # Misc zip unzip \ + # Static analysis + clang-tidy \ && rm -rf /var/lib/apt/lists/* # ── Qt 6.10 via aqtinstall ── @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# +# Run clang-tidy over the files we authored, inside the build container so +# the toolchain (gcc 15, libstdc++, Qt 6.10 headers, etc.) matches what +# generated build/compile_commands.json. +# +# Usage: +# ./lint.sh # lint all our source files (parallel) +# ./lint.sh path/to/file.cpp # lint a single file (relative to project root) +# ./lint.sh --fix # apply clang-tidy's auto-fixes in place +# +# Requires: +# - build/compile_commands.json (built by `./build.sh tarball`) +# - The fluorine-builder Docker image with clang-tidy installed. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "${SCRIPT_DIR}" + +if [ ! -f build/compile_commands.json ]; then + echo "ERROR: build/compile_commands.json not found." + echo "Run './build.sh tarball' first to generate it." + exit 1 +fi + +if command -v podman >/dev/null 2>&1; then + DOCKER=podman +elif command -v docker >/dev/null 2>&1; then + DOCKER=docker +else + echo "ERROR: neither podman nor docker found in PATH" + exit 1 +fi + +IMAGE_NAME="fluorine-builder" + +if ! ${DOCKER} images --format '{{.Repository}}' 2>/dev/null \ + | grep -qE "(^|/)${IMAGE_NAME}$"; then + echo "ERROR: ${IMAGE_NAME} image not found. Run './build.sh tarball' first." + exit 1 +fi + +if ! ${DOCKER} run --rm "${IMAGE_NAME}" which clang-tidy >/dev/null 2>&1; then + echo "WARNING: clang-tidy not in ${IMAGE_NAME}. Rebuilding image..." + ${DOCKER} build -t "${IMAGE_NAME}" docker/ +fi + +FIX_FLAG=() +SINGLE_FILE="" +for arg in "$@"; do + case "$arg" in + --fix) FIX_FLAG+=(--fix) ;; + --*) echo "unknown flag: $arg"; exit 1 ;; + *) SINGLE_FILE="$arg" ;; + esac +done + +collect_files() { + # browserview / browserdialog depend on QtWebEngine which is Windows-only + # in this fork (CMake removes them from ORGANIZER_SOURCES on Linux), so + # clang-tidy can't parse their headers. Skip them. + { + find src/src -type f \( -name '*.cpp' -o -name '*.h' \) + find libs/skse_log_redirector -type f \( -name '*.cpp' -o -name '*.h' \) + } | grep -v -E '(/moc_|/ui_|/qrc_|_autogen/|/build/|browserview|browserdialog)' +} + +if [ -n "${SINGLE_FILE}" ]; then + REL_FILES=("${SINGLE_FILE#./}") +else + mapfile -t REL_FILES < <(collect_files | sort) +fi + +if [ "${#REL_FILES[@]}" -eq 0 ]; then + echo "no files to lint" + exit 0 +fi + +CONTAINER_PATHS=() +for f in "${REL_FILES[@]}"; do + CONTAINER_PATHS+=("/src/${f}") +done + +JOBS="${BUILD_JOBS:-4}" + +echo "Linting ${#CONTAINER_PATHS[@]} files in ${IMAGE_NAME} (${JOBS} jobs)..." + +# Container builds with GCC, which uses -mno-direct-extern-access — clang in +# the same container doesn't recognise that flag, so strip it from the +# compile DB before running clang-tidy. Copy to a tmp DB so we don't disturb +# ninja's incremental build. +printf '%s\n' "${CONTAINER_PATHS[@]}" | \ + ${DOCKER} run --rm -i \ + -v "${SCRIPT_DIR}:/src:rw" \ + -w /src \ + "${IMAGE_NAME}" \ + bash -c ' + set -e + TIDY_DIR=$(mktemp -d) + trap "rm -rf $TIDY_DIR" EXIT + sed "s| -mno-direct-extern-access||g" /src/build/compile_commands.json \ + > "$TIDY_DIR/compile_commands.json" + xargs -P '"${JOBS}"' -I{} clang-tidy '"${FIX_FLAG[*]:-}"' \ + -p "$TIDY_DIR" --quiet {} + ' |
