NEON-26: add pre-push GDScript lint hook
Document the repo's enforced GDScript lint rules and add a local pre-push hook so gdlint/gdformat failures are caught before CI.pull/33/head
parent
ae643bc81c
commit
cdf9d59bd9
|
|
@ -8,6 +8,8 @@ alwaysApply: true
|
||||||
|
|
||||||
Follow the [Godot GDScript style guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html). Prefer clarity and consistency with existing scripts in the repo.
|
Follow the [Godot GDScript style guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html). Prefer clarity and consistency with existing scripts in the repo.
|
||||||
|
|
||||||
|
CI also enforces **`gdlint`** and **`gdformat`** (see `gdlintrc` and `.github/workflows/gdscript.yml`) on **`client/scripts/`** and **`client/test/`**. When this guide and personal preference differ, follow the **linted** version below so pushed files pass CI.
|
||||||
|
|
||||||
## Naming
|
## Naming
|
||||||
|
|
||||||
- **Functions, variables, signals:** `snake_case`.
|
- **Functions, variables, signals:** `snake_case`.
|
||||||
|
|
@ -23,18 +25,43 @@ Follow the [Godot GDScript style guide](https://docs.godotengine.org/en/stable/t
|
||||||
|
|
||||||
## Formatting
|
## Formatting
|
||||||
|
|
||||||
- **One statement per line**; break long lines for readability (Godot line length is flexible—aim for ~100 characters unless a longer string is clearer).
|
- **One statement per line**; CI enforces **max line length = 100**.
|
||||||
- **Indentation:** use **tabs** for new Godot work (editor default). Do not mix tabs and spaces in the same file; match the file if it already uses spaces.
|
- **Indentation:** use **tabs** for new Godot work (editor default). Do not mix tabs and spaces in the same file; match the file if it already uses spaces.
|
||||||
- **Blank lines:** separate functions with two blank lines; sparing single blank lines inside functions for logical groups.
|
- **Blank lines:** separate functions with two blank lines; sparing single blank lines inside functions for logical groups.
|
||||||
- **Trailing commas** in multi-line collections/function args when it improves diffs.
|
- **Trailing commas** in multi-line collections/function args when it improves diffs.
|
||||||
|
- No trailing whitespace.
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
- **`class_name`** only when the type must be referenced globally; otherwise anonymous `extends` is fine.
|
- **`class_name`** only when the type must be referenced globally; otherwise anonymous `extends` is fine.
|
||||||
- Order loosely: `extends` → `class_name` → `signals` → `enums` → `const` → `export/@export` → `onready` → other vars → `_ready` / lifecycle → public methods → private helpers.
|
- Declaration order should match `gdlint` `class-definitions-order`:
|
||||||
|
`@tool` → `class_name` → `extends` → file/class docstrings → `signal` → `enum` → `const` → static vars → `@export` vars → public vars → private vars (`_foo`) → `@onready` public vars → `@onready` private vars → everything else.
|
||||||
- Virtual overrides (`_ready`, `_process`, `_physics_process`, etc.): keep small; extract helpers with leading `_`.
|
- Virtual overrides (`_ready`, `_process`, `_physics_process`, etc.): keep small; extract helpers with leading `_`.
|
||||||
- **Scene / main script shape:** For `client/`, follow [godot-client-script-organization](godot-client-script-organization.md) — thin `main.gd`, split picking, networking, and similar concerns into child scripts.
|
- **Scene / main script shape:** For `client/`, follow [godot-client-script-organization](godot-client-script-organization.md) — thin `main.gd`, split picking, networking, and similar concerns into child scripts.
|
||||||
|
|
||||||
|
## CI-enforced lint rules
|
||||||
|
|
||||||
|
- **Names:** keep functions, local vars, loop vars, arguments, signals, and class vars in `snake_case`; keep classes / enums in `PascalCase`; keep enum elements and true constants in `ALL_CAPS`.
|
||||||
|
- **Signal callbacks:** `_on_Node_signal` style handlers are allowed; otherwise use normal `snake_case`.
|
||||||
|
- **Preload / load identifiers:** when storing a preloaded script or type-like handle, prefer `PascalCase` names such as `CameraStateScript`; keep ordinary constants in `ALL_CAPS`.
|
||||||
|
- Avoid duplicated `preload()`s for the same path in one file.
|
||||||
|
- Avoid bare expressions that are not assigned / returned / awaited.
|
||||||
|
- Remove unnecessary `pass`.
|
||||||
|
- Keep files reasonably small: CI enforces **max 1000 lines**, **max 20 public methods**, **max 10 function arguments**, and **max 6 returns** per function.
|
||||||
|
- Prefer early-return style. `gdlint` forbids `else` / `elif` branches that directly follow a `return`.
|
||||||
|
- Keep unused arguments intentional and named clearly if you truly need them.
|
||||||
|
|
||||||
|
## Local verification
|
||||||
|
|
||||||
|
- Before pushing GDScript changes, run the same tools CI uses:
|
||||||
|
`pip install "gdtoolkit==4.5.0"`
|
||||||
|
`gdlint client/scripts client/test`
|
||||||
|
`gdformat --check client/scripts client/test`
|
||||||
|
- If formatting fails, run:
|
||||||
|
`gdformat client/scripts client/test`
|
||||||
|
- Install the repo’s local **pre-push** hook from the repo root to enforce those checks automatically:
|
||||||
|
`./scripts/install-git-hooks.sh`
|
||||||
|
|
||||||
## Comments
|
## Comments
|
||||||
|
|
||||||
- Use `#` comments; document non-obvious **why**, not what the next line literally does.
|
- Use `#` comments; document non-obvious **why**, not what the next line literally does.
|
||||||
|
|
|
||||||
|
|
@ -110,4 +110,12 @@ On **Linux** (including GitHub Actions), the path must be **`gdUnit4`** with a c
|
||||||
|
|
||||||
**Camera zoom:** Input actions **`camera_zoom_in`** / **`camera_zoom_out`** (mouse wheel, **`=`** / **`-`**, keypad **+** / **−**) are defined in **`project.godot`**. On layouts where **`+`** requires **Shift**, remap or add a binding under **Project → Project Settings → Input Map** if needed.
|
**Camera zoom:** Input actions **`camera_zoom_in`** / **`camera_zoom_out`** (mouse wheel, **`=`** / **`-`**, keypad **+** / **−**) are defined in **`project.godot`**. On layouts where **`+`** requires **Shift**, remap or add a binding under **Project → Project Settings → Input Map** if needed.
|
||||||
|
|
||||||
|
**Git hook (recommended):** install the repo’s local **pre-push** GDScript lint hook from the repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/install-git-hooks.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
It runs **`gdlint client/scripts client/test`** and **`gdformat --check client/scripts client/test`** before each push, preferring the repo-local **`.venv-gd/`** tools when present.
|
||||||
|
|
||||||
**Reports:** GdUnit writes under **`reports/`** (gitignored); ignore locally generated HTML/XML when committing.
|
**Reports:** GdUnit writes under **`reports/`** (gitignored); ignore locally generated HTML/XML when committing.
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@ extends Node3D
|
||||||
## [member allow_yaw] is false (no rotate input bound). Future orbit: read relative input here,
|
## [member allow_yaw] is false (no rotate input bound). Future orbit: read relative input here,
|
||||||
## add to `_orbit_yaw_rad`, clamp with [member max_yaw_deg].
|
## add to `_orbit_yaw_rad`, clamp with [member max_yaw_deg].
|
||||||
|
|
||||||
|
## TODO(E9.M1): map throttled product telemetry (for example `camera_zoom_changed`)
|
||||||
|
## to this signal when schema exists.
|
||||||
|
signal zoom_band_changed(new_index: int, distance: float)
|
||||||
|
|
||||||
const CameraStateScript := preload("res://scripts/camera_state.gd")
|
const CameraStateScript := preload("res://scripts/camera_state.gd")
|
||||||
const ZoomBandConfigScript := preload("res://scripts/zoom_band_config.gd")
|
const ZoomBandConfigScript := preload("res://scripts/zoom_band_config.gd")
|
||||||
|
|
||||||
## TODO(E9.M1): map throttled product telemetry (e.g. `camera_zoom_changed`) to this signal when schema exists.
|
|
||||||
signal zoom_band_changed(new_index: int, distance: float)
|
|
||||||
|
|
||||||
## Tuned to match pre-NEON-25 static `Camera3D` at (12,10,12) vs player at (-5,0.9,-5).
|
## Tuned to match pre-NEON-25 static `Camera3D` at (12,10,12) vs player at (-5,0.9,-5).
|
||||||
## When [member zoom_band_config] is null or has no bands, this is the sole follow distance.
|
## When [member zoom_band_config] is null or has no bands, this is the sole follow distance.
|
||||||
@export var follow_target_path: NodePath = NodePath("../Player")
|
@export var follow_target_path: NodePath = NodePath("../Player")
|
||||||
|
|
@ -62,9 +63,7 @@ func _ready() -> void:
|
||||||
var focus0: Vector3 = t.global_position + Vector3(0.0, focus_vertical_offset, 0.0)
|
var focus0: Vector3 = t.global_position + Vector3(0.0, focus_vertical_offset, 0.0)
|
||||||
var yaw0: float = deg_to_rad(presentation_yaw_deg) + _orbit_yaw_rad
|
var yaw0: float = deg_to_rad(presentation_yaw_deg) + _orbit_yaw_rad
|
||||||
var dist0: float = _current_follow_distance()
|
var dist0: float = _current_follow_distance()
|
||||||
_smoothed_eye = desired_eye_world(
|
_smoothed_eye = desired_eye_world(focus0, dist0, deg_to_rad(pitch_elevation_deg), yaw0)
|
||||||
focus0, dist0, deg_to_rad(pitch_elevation_deg), yaw0
|
|
||||||
)
|
|
||||||
global_position = _smoothed_eye
|
global_position = _smoothed_eye
|
||||||
camera.look_at(focus0, Vector3.UP)
|
camera.look_at(focus0, Vector3.UP)
|
||||||
_sync_camera_state(focus0, dist0)
|
_sync_camera_state(focus0, dist0)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ func band_count() -> int:
|
||||||
return band_distances.size()
|
return band_distances.size()
|
||||||
|
|
||||||
|
|
||||||
## **True** when every entry is **> 0** (empty array is vacuously true; callers also check [method band_count]).
|
## **True** when every entry is **> 0**.
|
||||||
|
## Empty arrays are vacuously true; callers also check [method band_count].
|
||||||
func all_band_distances_positive() -> bool:
|
func all_band_distances_positive() -> bool:
|
||||||
for i in range(band_distances.size()):
|
for i in range(band_distances.size()):
|
||||||
if band_distances[i] <= 0.0:
|
if band_distances[i] <= 0.0:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_root="$(git rev-parse --show-toplevel)"
|
||||||
|
cd "$repo_root"
|
||||||
|
|
||||||
|
gdlint_bin=""
|
||||||
|
gdformat_bin=""
|
||||||
|
|
||||||
|
if [[ -x "$repo_root/.venv-gd/bin/gdlint" ]]; then
|
||||||
|
gdlint_bin="$repo_root/.venv-gd/bin/gdlint"
|
||||||
|
elif command -v gdlint >/dev/null 2>&1; then
|
||||||
|
gdlint_bin="$(command -v gdlint)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -x "$repo_root/.venv-gd/bin/gdformat" ]]; then
|
||||||
|
gdformat_bin="$repo_root/.venv-gd/bin/gdformat"
|
||||||
|
elif command -v gdformat >/dev/null 2>&1; then
|
||||||
|
gdformat_bin="$(command -v gdformat)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$gdlint_bin" || -z "$gdformat_bin" ]]; then
|
||||||
|
echo "pre-push: missing gdtoolkit commands (gdlint/gdformat)." >&2
|
||||||
|
echo "Install locally with:" >&2
|
||||||
|
echo " python3 -m venv .venv-gd" >&2
|
||||||
|
echo " .venv-gd/bin/pip install \"gdtoolkit==4.5.0\"" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "pre-push: running gdlint on client/scripts and client/test"
|
||||||
|
"$gdlint_bin" client/scripts client/test
|
||||||
|
|
||||||
|
echo "pre-push: running gdformat --check on client/scripts and client/test"
|
||||||
|
"$gdformat_bin" --check client/scripts client/test
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_root="$(git rev-parse --show-toplevel)"
|
||||||
|
hook_dir="$repo_root/.git/hooks"
|
||||||
|
hook_path="$hook_dir/pre-push"
|
||||||
|
|
||||||
|
mkdir -p "$hook_dir"
|
||||||
|
|
||||||
|
cat >"$hook_path" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_root="$(git rev-parse --show-toplevel)"
|
||||||
|
exec "$repo_root/scripts/git-hooks/pre-push" "$@"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x "$hook_path"
|
||||||
|
echo "Installed pre-push hook at $hook_path"
|
||||||
Loading…
Reference in New Issue