neon-sprawl/.cursor/rules/gdscript-style.md

88 lines
5.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
description: GDScript naming, formatting, and Godot-idiomatic patterns (Godot docs style guide).
globs: "**/*.gd"
alwaysApply: true
---
# GDScript style (Neon Sprawl)
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
- **Functions, variables, signals:** `snake_case`.
- **Classes** (`class_name`, custom resources): `PascalCase`.
- **Constants** (`const`): `ALL_CAPS` when they are true fixed values; otherwise match Godot/node conventions used nearby.
- **“Private” / internal:** leading underscore `_like_this` for members meant as implementation detail (not enforced by the language).
## Types and API
- Add **static types** on parameters, locals, and return values where reasonable (`-> void`, `var x: int`, etc.).
- Prefer **`@export`** (Godot 4) over legacy `export` syntax.
- Use **`@onready`** for node references set after the scene tree is ready when that pattern fits.
## Formatting
- **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.
- **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.
- No trailing whitespace.
## Structure
- **`class_name`** only when the type must be referenced globally; otherwise anonymous `extends` is fine.
- 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 `_`.
- **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 repos local **pre-push** hook from the repo root to enforce those checks automatically:
`./scripts/install-git-hooks.sh`
## GdUnit test layout (AAA)
**Mandatory** for every **new or changed** test function in **`client/test/**/*.gd`** (GdUnit suites, `test_*` methods).
1. **`# Arrange`**, **`# Act`**, and **`# Assert`** — each appears **once** per test, in that order, as a full-line comment at the same indentation as the test body. **No blank line is required** after those comments; the next statement may follow immediately.
2. **Arrange** — Transports, clients, `enqueue`/`connect` setup, test data.
3. **Act** — The call(s) under test only (e.g. `request_cast`, `call` into SUT). No `assert_that` / `assert_*` in Act.
4. **Assert** — All expectations; parse or inspect results here when parsing exists only to support assertions.
`gdlint` / `gdformat` do not enforce AAA text; **review and agents** do. Match the spirit of server **`xut`** snippets: three labeled phases, one scenario per test.
## Comments
- Use `#` comments; document non-obvious **why**, not what the next line literally does.
- Doc comments: Godot 4 supports `##` above declarations for the in-editor help—use for public-ish APIs when helpful.
## Anti-patterns
- Avoid `get_node()` string paths repeated everywhere; use `@onready`, exports, or groups when scale demands it.
- Prefer typed collections and enums over “magic” string/int state when it stays readable.