docs: merge coding conventions into CONTRIBUTING.md

Consolidate the standalone `CONVENTION.md` into `CONTRIBUTING.md` so that
all contributor guidelines live in a single, discoverable file.

Added sections:
- Language, build system, and formatting rules (Google style, clang-format)
- Naming conventions (PascalCase types, snake_case_ members, lowerCamelCase methods)
- Include ordering and header self-sufficiency requirements
- Class/module design patterns (AModule hierarchy, SafeSignal threading)
- JSON configuration and string handling guidelines
- GTK/Glib and OpenGL patterns
- Platform portability and RAII conventions
- Thread-safety rules (GTK single-threaded, background thread marshalling)
- Unsafe patterns to avoid (C-style casts, unbounded buffer copies)
- Singleton lifetime guidance (store config by value, no dangling refs)docs: merge coding conventions into CONTRIBUTING.md
This commit is contained in:
Viktar Lukashonak
2026-07-14 09:29:06 +03:00
parent 5c979152de
commit 52a5f74cd2
+142
View File
@@ -43,3 +43,145 @@ add its man page and a line in [`.github/wiki/mapping.json`](.github/wiki/mappin
- Build and test against the module(s) you touched.
Have fun :)
---
# Coding Conventions
## 1. Language & Build
- **Standard**: C++20.
- **Build system**: Meson (`meson.build`). Project version is defined there.
- **Compiler flags**: Added via `add_project_arguments()` in Meson. Feature flags use `HAVE_*` / `WANT_*` prefixes (e.g. `-DHAVE_NIRI`, `-DHAVE_HYPRLAND`, `-DHAVE_LIBUDEV`).
## 2. Formatting
- **Tool**: `.clang-format` is checked in. **Never bypass it.**
- **Style**: Google base style.
- **Indent**: 2 spaces. No tabs.
- **Column limit**: 100.
- **Braces**: K&R (opening brace on the same line).
- **Declaration alignment**: Disabled (`AlignConsecutiveDeclarations: false`).
- **Pointer/reference alignment**: Left (`const Json::Value& config`, `int* ptr`, not `int *ptr`).
## 3. Naming
### Files
- Match the primary exported class exactly: `AAppIconLabel.hpp`, `workspaces.cpp`, `backlight_backend.hpp`.
- Corresponding header and source should live in predictable paths:
- `include/<module/path>.hpp`
- `src/<module/path>.cpp`
### Types
- **Classes / Structs**: `PascalCase`.
- Abstract base classes are prefixed with `A` (e.g., `AModule`, `ALabel`, `AIconLabel`, `AAppIconLabel`).
- **Enums / Enum classes**: `PascalCase` name.
- Enumerators: `UPPER_SNAKE_CASE` (e.g., `SCROLL_DIR::NONE`, `KillSignalAction::RELOAD`, `ChangeType::Increase`).
- **Concepts / Type aliases**: `PascalCase`.
### Variables
- **Member variables**: `snake_case_` with a **trailing underscore**.
- Examples: `config_`, `bar_`, `label_`, `app_icon_size_`, `distance_scrolled_y_`, `on_updated_cb_`.
- **Function parameters & locals**: `snake_case` (no trailing underscore).
- Examples: `workspace_data`, `should_refresh`, `app_identifier`, `preferred_device`.
- **Static / constexpr constants**: `UPPER_SNAKE_CASE` or descriptive `kPascalCase`.
- Examples: `MODULE_CLASS`, `EPOLL_MAX_EVENTS`, `kExecFailureExitCode`.
### Functions & Methods
- **Free functions**: `snake_case`.
- Examples: `sanitize_string()`, `rewrite_string()`, `get_total_memory()`, `best_device()`.
- **Class methods**: `lowerCamelCase`.
- Examples: `update()`, `tooltipEnabled()`, `handleScroll()`, `getScrollDir()`, `resolveFormat()`, `setBrightness()`.
- **Virtual overrides**: Mark with `override` (and `final` where applicable). Header signatures often use a trailing return type:
```cpp
auto update() -> void override;
auto refresh(int should_refresh) -> void;
```
### Namespaces
- All lowercase, nested by module path:
```cpp
namespace waybar { }
namespace waybar::modules::niri { }
namespace waybar::util { }
```
- Close every namespace with a comment:
```cpp
} // namespace waybar::modules::niri
```
## 4. Includes & Headers
- Use `#pragma once` in all project headers.
- Include order in `.cpp` files:
1. Corresponding header first.
2. Blank line.
3. External library headers (`<fmt/...>`, `<spdlog/...>`, `<gtkmm/...>`, `<json/json.h>`).
4. Standard library headers (`<algorithm>`, `<vector>`, `<memory>`).
5. Blank line.
6. Other project headers (`"util/..."`, `"modules/..."`).
- Do not use `using namespace` in headers. In `.cpp` files it is acceptable for narrow scopes (e.g., `using namespace std::literals::chrono_literals;`).
- Headers that expose standard-library types in their public interface (e.g. `std::chrono::milliseconds` as a return type or `std::vector<T>` as a member) must `#include` the corresponding standard header directly. Do not rely on transitive includes from other headers.
## 5. Class & Module Design
### Base Class Patterns
- All UI modules ultimately derive from `AModule` (and often `ALabel` or `AIconLabel`).
- Accept configuration in constructors:
```cpp
MyModule(const Json::Value& config, const std::string& name, const std::string& id, ...);
```
### Signals & Threading
- Use `Glib::Dispatcher` (via `waybar::SafeSignal`) to marshal work to the GTK main thread.
- Use `sigc::signal` for normal GTK++ signals.
- If a scope must not be interrupted by `pthread_cancel`, guard it with `waybar::util::CancellationGuard`.
### State / IPC
- Modules that talk to a compositor often implement a small `EventHandler` interface (`onEvent(...)`) and delegate to a singleton backend (e.g., `gIPC`).
### RAII
- Prefer `std::unique_ptr` with custom deleters over raw `new/delete` for C-API resources (see `ScopedFd`, `UdevDeleter`, `UdevDeviceDeleter`, `ScopeGuard`).
## 6. JSON Configuration
- Every module receives `const Json::Value& config` (usually as the first constructor argument).
- Always validate node type before reading:
```cpp
if (config_["sort-by-id"].isBool()) { ... }
if (config.isMember("window-rewrite-default") && config["window-rewrite-default"].isString()) { ... }
```
- Use `waybar::util::JsonParser` if you need to pre-process JSON with non-standard escape sequences.
## 7. String & UI Formatting
- Use `fmt::format` / `fmt::join` for all string composition.
- Use `fmt::dynamic_format_arg_store<fmt::format_context>` when building arguments dynamically.
- Custom `fmt::formatter` specializations are allowed for domain types (e.g., `Glib::ustring`, project enums).
- Sanitize arbitrary text before inserting into Pango markup with `waybar::util::sanitize_string`.
- Use `waybar::util::rewriteString` for user-configurable regex rewrites.
- Truncate UTF-8 safely with `waybar::util::utf8_truncate` / `utf8_width`.
## 8. Error Handling & Logging
- Use `spdlog` for all logging:
- `spdlog::error("Context: {}", e.what());`
- `spdlog::warn("Deprecated key '{}', prefer '{}'", old, replacement);`
- `spdlog::debug("State changed to {}", value);`
- Throw `std::runtime_error` (or similar) for fatal initialization failures that should bubble up to `main()`.
## 9. GTK / Glib Patterns
- Prefer gtkmm-3.0 types (`Gtk::Button`, `Gtk::Label`, `Gdk::Pixbuf`, `Glib::RefPtr`, `Glib::ustring`) over raw C GTK APIs.
- Access the default icon theme through thread-safe wrappers if off the main thread (`DefaultGtkIconThemeWrapper`).
- Tooltips and labels should respect the module `tooltip` toggle (see `tooltipEnabled()` in `AModule`).
## 10. Platform Portability
- Isolate platform-specific code in dedicated files (e.g., `linux.cpp`, `bsd.cpp`).
- Use preprocessor guards for platform differences (`#if defined(__FreeBSD__)`, `#if defined(HAVE_LIBNL)`).
- Keep the common interface in a shared header or base class.
## 11. Thread Safety & Cross-Thread Communication
- GTK is strictly single-threaded. Never emit raw `sigc::signal` from background threads.
- Use `waybar::SafeSignal<T...>` to marshal events from worker threads to the GTK main loop.
- When a module manages background threads, use `std::mutex`, `std::recursive_mutex`, or atomic variables to protect shared state, and ensure the destructor joins or synchronizes with those threads before destroying resources.
## 12. Unsafe Patterns to Avoid
- Do not use `strcpy`, `strcat`, or `sprintf` into fixed-size buffers (e.g. `char buf[PATH_MAX]`). Prefer `std::string`, `std::vector<char>`, or `std::array` with bounds-safe operations.
- When passing a `std::vector<char>` buffer to a C API that expects a mutable `char*` string, always ensure the buffer is null-terminated and clamp the written length to `size() - 1`. Never use `std::copy` from an unbounded source into a fixed-size buffer.
## 13. Singleton Lifetime
- Singletons or objects with process-wide lifetime must not store references (`&`) or pointers to objects with shorter lifetime (e.g., configuration trees, GTK widgets, or bar instances) unless they are explicitly notified of destruction. Prefer storing configuration by value (`Json::Value`, `std::string`, etc.) if the singleton outlives the config loader.