· 3 min read
Measuring GDScript complexity without an AST
by Furkan Durna · Project: gdmetrics
#godot #gdscript #tooling
Godot projects grow the same way other codebases do: a few functions quietly become the ones nobody wants to touch. gdmetrics is an editor plugin and command-line tool that measures every GDScript function and ranks what to refactor first. It only reads scripts; it never changes them.
No parser to borrow
Most complexity tools walk an abstract syntax tree. Godot doesn't expose one to plugins, so gdmetrics has its own tokenizer, written in GDScript. It reads files line by line without regular expressions and produces keywords, identifiers, operators, strings and comments.
On top of that sit a few small passes:
- Functions start at
func,static funcorsignaland end when indentation returns to their level. - Control flow is tracked with stacks for indentation, blocks and lambdas; nesting depth is the size of the indentation stack.
- Cyclomatic complexity starts at 1 and adds a point for each
if,elif,for,while,match, match arm and ternary, plus logical operators when enabled. - Cognitive complexity adds 1 plus the current nesting depth for most constructs, so the same
ifcosts more the deeper it sits. Lambdas cost a flat point and their bodies don't count against the enclosing function.
The cognitive score is modeled on the published cognitive complexity idea but makes its own choices. For example, early return, break and continue inside control flow add a point.
The edge cases were most of the work
An indentation-based language without a real parser has sharp corners:
- Ternary or statement?
x if cond else yand a statementifuse the same keyword. If any token comes beforeifon the line, it's a ternary. - Match arms have no keyword. An arm is a line at the first indentation level under
matchthat has a top-level colon. Commas in patterns andwhenguards are counted only outside brackets, andtext.match(...)is rejected because a dot comes before it. - Line continuations. A trailing backslash joins lines, including when blank or comment lines follow, which matches a known Godot parser behavior.
- Strings. Multi-line strings, raw strings and the
&"...",^"..."and@"..."literal forms are handled, and a byte-order mark at the start of a file is stripped. - Mixed tabs and spaces make indentation ambiguous. Those lines are flagged instead of guessed.
Every edge case has a fixture file and an expected JSON result, and a headless test compares the two.
In the editor
The dock has one main button: Find what to fix. Results come in three lists: top fixes, the largest files, and everything. Each function gets a plain label (OK, Hard to read, Fix soon) based on configurable thresholds. Double-clicking a row opens the script at that line.
A few extras make the ranking more useful:
- Files with recent git churn are marked, since complex code that changes often is the expensive kind.
- Comment directives ignore a function, or pin it so it stays visible.
- Functions over the failure threshold can be annotated in the script editor, turned on in the config file.
In CI
The same analysis runs headless from the command line. It writes JSON, CSV and HTML reports, appends a line to a history file on every run, and can compare against a baseline to catch regressions. Exit codes are simple:
- 0: everything is within thresholds.
- 1: a function crossed the failure threshold, or the diff regressed.
- 2: the tool itself failed. It also refuses to write over protected files such as
project.godot.
A GitHub Actions template sets up Godot, runs the check and uploads the reports.
Two editions, one core
Godot 3 and 4 differ enough that one plugin can't serve both. The analysis core lives in the Godot 4 edition and is copied into the Godot 3 edition by a sync script, while the tokenizer and UI stay per version. Godot 3 needed small workarounds: early 3.x releases can't parse quit(code), so the CLI sets the exit code another way.
Limits
This is a heuristic parser, not a compiler. The README states the expected accuracy (about 90–93% on Godot 4 and 85–90% on Godot 3), and each result carries a confidence value so low-confidence functions can be checked by hand. Planned additions include Halstead metrics and a maintainability index.
The Godot 4 edition is on itch.io, and both editions are on GitHub (Godot 4, Godot 3).