Skip to content

Adding or changing a rule ​

Every one of the 178 rules is implemented. Most rule work is now fixing a false positive, a missed case or a fix. The workflow is the same either way: spec first, then code, done separately (see the clean-room process).

1. The spec ​

specs/<ID>.md (start from specs/_TEMPLATE.md) describes the rule in our own words. It has YAML front matter (id, group, kind: syntax|semantic, needs, php: { min, max }) and these sections:

SectionContent
SummaryWhat the rule reports and why, for users. Shown by custos explain and on the rule's page.
DetectionNumbered conditions (D1, D2…) for a report.
ExceptionsNumbered cases (E1, E2…) that are not reported.
ReportRange, severity and message.
FixWhat the quick-fix produces (F1…), if there is one.
OptionsEach option, its type, default and effect. Shown to users.
PHP versionsVersion gating.
ExamplesNew PHP examples using the fixture markup. The first block is shown on the rule's page, and the block right after it as the result of the fix.
DivergencesIntentional differences from upstream, and why.

For a behaviour change, update the spec first, in the same pull request.

2. The implementation ​

Rules live in internal/rules/<group>/<rule_snake>.go and register themselves:

go
type unnecessarySemicolon struct{}

func init() { register(unnecessarySemicolon{}) }

func (unnecessarySemicolon) ID() string { return "UnnecessarySemicolon" }

func (unnecessarySemicolon) Kinds() []syntax.NodeKind {
	return []syntax.NodeKind{syntax.KNop, syntax.KEcho}
}

func (r unnecessarySemicolon) Check(ctx *analysis.Context, n syntax.Node) {
	// follow the spec's D/E items in order; ctx.Report(span, msg, fixes...)
}
  • List only the node kinds the rule needs; the engine dispatches by kind.
  • Use ctx.Names(), ctx.Index() and ctx.TypeOf() for semantic information; they are computed lazily.
  • Look in internal/analysis/util before writing a helper. Put a new generic helper in its own file there (with tests); keep rule-specific helpers in the rule file.
  • A fix is a analysis.Fix{Title, Edits} whose Edits function returns text edits on exact byte ranges. A fix must never change behaviour or produce invalid PHP; when that cannot be guaranteed, do not offer it.
  • Messages are short and imperative: "Stray semicolon; remove it."
  • Version-gate the rule on ctx.PHP when the spec says so.

3. Fixtures and checks ​

  1. Write or extend the fixtures in testdata/rules/<ID>/: positives, false positives, .fixed.php for fixes, a sidecar per option or PHP version.
  2. make fixtures RULE=<ID>.
  3. make conformance RULE=<ID> when you have the upstream checkout. A mismatch means the spec is wrong or incomplete, or the difference is an intentional divergence to document.
  4. Touching a hot path? Add or extend a benchmark (make bench).
  5. make rules-doc, then make verify.
  6. Add a line under ## [Unreleased] in CHANGELOG.md.

Released under the MIT License. Rule catalogue modelled on Php Inspections (EA Extended); independent clean-room implementation.