aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-28 21:56:22 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-28 21:56:22 -0500
commit33015e0393b3aef4a6e129dec5bcf92444321e9c (patch)
treefea7177df0712d09f7d94d24ede877b7e65c56ab
parent5d42085dd36f14aa3385547947ccd6e6c5f03d50 (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-tidy66
-rw-r--r--docker/Dockerfile2
-rwxr-xr-xlint.sh106
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 ──
diff --git a/lint.sh b/lint.sh
new file mode 100755
index 0000000..1b879e7
--- /dev/null
+++ b/lint.sh
@@ -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 {}
+ '