messgo is a PHP Mess Detector (phpmd) port for Go: it is written in Go and analyzes Go source code, applying phpmd's rule catalog, ruleset format, message templates, CLI surface, and report renderers — adapted faithfully to Go semantics.
Where phpmd parses PHP via pdepend, messgo parses Go via the standard-library
go/ast. By default it uses idiomatic Go principles (the bundled go
ruleset), but a fuller set of checks that more closely emulates standard phpmd
rules can be optionally enabled.
Install with Go to put messgo on your PATH:
go install github.com/quality-gates/messgo/cmd/messgo@latestPrebuilt macOS archives for stable versions are available from GitHub Releases.
Or build it from a checkout of this repo:
go build -o messgo ./cmd/messgoThe simplest way to start is the same command messgo uses to check itself in
CI — point it at a directory using the bundled go ruleset, with plain text
output, skipping test files:
./messgo ./internal text go --ignore-testsThat's the whole pattern. The command is always:
messgo <paths> <format> <ruleset[,...]> [options]- paths — comma-separated files or directories. Directories are walked;
vendor/,node_modules/, and.git/are skipped. - format —
text,xml,json,html,ansi,github,gitlab,checkstyle, orsarif. - ruleset — one or more rulesets (see Rulesets). Start with
go.
text format prints one violation per line as file:line Rule message:
internal/cli/cli.go:131 ShortVariable Avoid variables with short names like a. Configured minimum length is 3.
internal/cli/cli.go:157 ShortVariable Avoid variables with short names like ok. Configured minimum length is 3.
Exit codes match phpmd exactly:
| Code | Meaning |
|---|---|
| 0 | Clean — no violations |
| 1 | Error (e.g. bad arguments, parse failure) |
| 2 | Violations found |
This makes messgo drop straight into a build script or CI step: a non-zero exit fails the job.
messgo runs on itself in CI. Here is the exact job from this repo's
.github/workflows/ci.yml — copy it as a starting point:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
messgo:
name: Run messgo
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- name: Build messgo
run: go build -o messgo ./cmd/messgo
- name: Run self-analysis
run: ./messgo ./internal text go,codesize --ignore-testsBecause messgo exits 2 when it finds violations, the Run self-analysis
step fails the job automatically — no extra scripting needed.
messgo ./... text codesize # one ruleset, all packages
messgo ./internal,./cmd json naming,unusedcode # multiple paths and rulesets
messgo main.go xml codesize,design,cleancode --minimumpriority 2
messgo ./... text codesize,design --only CyclomaticComplexity,GlobalVariable # just these rules
messgo ./... text go --disable LongVariable # everything in go except one rule--only (alias --enable) and --disable filter by rule name within the
ruleset(s) you load. --only keeps just the listed rules; --disable drops
them; combine them to whitelist-then-subtract. Rule names are the ones shown in
output (e.g. CyclomaticComplexity, ElseExpression). Note --only selects
from rules already in the chosen ruleset — it won't pull in a rule the ruleset
doesn't include.
| Option | Effect |
|---|---|
--minimumpriority <n> |
Only run rules with priority ≤ n. |
--maximumpriority <n> |
Only run rules with priority ≥ n. |
--reportfile <file> |
Write the report to a file instead of stdout. |
--suffixes <list> |
File extensions to scan (default: go). |
--exclude <list> |
Path substrings to exclude. |
--enable, --only <list> |
Run only these rules (comma-separated rule names), selected from the loaded ruleset(s). |
--disable <list> |
Skip these rules (comma-separated rule names). |
--ignore-tests |
Skip *_test.go files. |
--strict |
Also report suppressed violations. |
--color |
Colorize text output. |
--verbose, -v |
Verbose diagnostics. |
--ignore-errors-on-exit |
Exit 0 even if parse errors occurred. |
--ignore-violations-on-exit |
Exit 0 even if violations were found. |
--version |
Print version. |
--help, -h |
Show help. |
Pass rulesets by name (comma-separated), or pass a path to your own phpmd-format ruleset XML file.
| Ruleset | What it checks |
|---|---|
go |
Recommended default. Pulls in all rulesets below, but tunes the rules whose PHP defaults misfire on idiomatic Go: drops ShortVariable, Design/ExitExpression, Design/CountInLoopExpression, Design/GlobalVariable, CleanCode/ElseExpression, CleanCode/BooleanArgumentFlag, and UnusedCode/UnusedFormalParameter, and raises LongVariable's maximum. On this codebase go reports ~19 findings versus ~441 at raw PHP defaults. |
codesize |
CyclomaticComplexity, NPathComplexity, ExcessiveMethodLength, ExcessiveClassLength, ExcessiveParameterList, ExcessivePublicCount, TooManyFields, TooManyMethods, TooManyPublicMethods, ExcessiveClassComplexity |
naming |
ShortClassName, LongClassName, ShortVariable, LongVariable, ShortMethodName, ConstantNamingConventions, BooleanGetMethodName, ConstructorWithNameAsEnclosingClass |
unusedcode |
UnusedPrivateField, UnusedLocalVariable, UnusedPrivateMethod, UnusedFormalParameter |
cleancode |
BooleanArgumentFlag, ElseExpression, IfStatementAssignment, DuplicatedArrayKey |
design |
ExitExpression, GotoStatement, CountInLoopExpression, DevelopmentCodeFragment, EmptyCatchBlock, CouplingBetweenObjects, GlobalVariable, LackOfCohesionOfMethods |
controversial |
CamelCaseClassName, CamelCaseMethodName, CamelCasePropertyName, CamelCaseParameterName, CamelCaseVariableName |
opinionated |
Opt-in, not part of idiomatic Go. Bundles the rules the go ruleset deliberately drops because they fight Go conventions: ElseExpression (else is idiomatic), BooleanArgumentFlag (bool params fill Go's stdlib), UnusedFormalParameter (unused params are required to satisfy interfaces and handler signatures), and GlobalVariable (mutable package-level state). Run them if you want a stricter, more PHP-flavoured style. |
Rules with a direct Go analog reproduce phpmd's behavior and message templates exactly; rules that are intrinsically PHP-specific are either adapted to the nearest Go idiom or omitted (Go's compiler already enforces several of them).
The opinionated ruleset is the home for checks that come from phpmd's
PHP/OO heritage but are not idiomatic Go. They stay available — run
messgo ./... text opinionated, or go,opinionated to combine — but the
default go ruleset leaves them off so a clean run reflects idiomatic Go.
GlobalVariable is mutation-aware: by default it reports only package-level
variables that are actually mutated somewhere in the package (reassigned,
incremented, written through, or address-taken), analysed across all files of
the package. Effectively-constant globals — sentinel errors, compiled regexps,
lookup tables — stay silent so the genuinely risky shared state isn't drowned
out. Set its report-immutable=true property to also surface read-only globals.
LackOfCohesionOfMethods computes the LCOM4 cohesion metric per struct
type: methods are linked when they use a common field or call one another
through the receiver, and the metric is the number of disconnected method
groups that result. A value above the maximum property (default 1) means the
type bundles unrelated responsibilities and could be split, one type per group.
To stay quiet on idiomatic Go, methods that touch no state (pure helpers,
interface stubs) are ignored, as are trivial getters/setters — so a plain data
carrier with one accessor per field does not score LCOM4 = number of fields. A
call to a getter counts as a use of the field it wraps.
Ruleset XML supports phpmd's <rule ref="..."> form, <exclude name="..."/>
children, and single-rule property/priority overrides — so you can compose your
own tuned ruleset the same way phpmd does, then pass its path as the ruleset
argument.
go test ./...The suite includes metric tests pinned to numbers produced by real phpmd 2.15.0 (cyclomatic complexity 12, NPath 324 on a reference function), plus per-ruleset behavioral tests and CLI/exit-code tests.