clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

main
atlas / tools / depcheck / report.go
report.go 3.9K
1package main23import (4 "go/ast"5 "go/token"6 "strings"78 "golang.org/x/tools/go/analysis"9)1011// Every rule's failure message names the contract it enforces and cites the12// issue section, so a violation teaches the boundary instead of merely13// blocking it (issue #5 §9). contractf is the one place that shape is built:14// "<what you did wrong> (issue #5 §<section>): <why the boundary exists>".15func contractf(violation, section, why string) string {16 return violation + " (issue #5 §" + section + "): " + why17}1819// The escape hatch. A boundary crossing that is genuinely correct is annotated20// in place:21//22// someCall() //depcheck:allow netconfine the workbench proxies its own origin check23//24// The pragma may sit on the offending line or on the line above it. It must25// name a rule (or "all") and carry a written reason — an unexplained pragma is26// itself a finding. Accepted divergences elsewhere in this tree live in27// docs/decisions/ with their reasons; this is the source-level twin of that28// discipline.29const pragma = "//depcheck:allow"3031// knownRules is used to reject typo'd pragmas, which would otherwise silently32// suppress nothing and read as if they did.33var knownRules = map[string]bool{34 "all": true,35 "laneimports": true,36 "cleanroom": true,37 "hostenv": true,38 "netconfine": true,39 "semconvlit": true,40}4142type allowance struct {43 rule string44 reason string45 pos token.Pos46}4748// reporter reports diagnostics for one analysis pass, honoring pragmas.49type reporter struct {50 pass *analysis.Pass51 allow map[int][]allowance // by line within the file set52}5354func newReporter(pass *analysis.Pass) *reporter {55 r := &reporter{pass: pass, allow: make(map[int][]allowance)}56 for _, f := range pass.Files {57 for _, group := range f.Comments {58 for _, c := range group.List {59 text, ok := strings.CutPrefix(c.Text, pragma)60 if !ok {61 continue62 }63 line := pass.Fset.Position(c.Pos()).Line64 rule, reason, _ := strings.Cut(strings.TrimSpace(text), " ")65 r.allow[line] = append(r.allow[line], allowance{66 rule: rule,67 reason: strings.TrimSpace(reason),68 pos: c.Pos(),69 })70 }71 }72 }73 return r74}7576// checkPragmas reports malformed pragmas addressed to this pass's analyzer.77// Doing it here rather than in a rule of its own keeps the pragma honest78// without a fifth analyzer whose only subject is the other four.79func (r *reporter) checkPragmas() {80 name := r.pass.Analyzer.Name81 for _, as := range r.allow {82 for _, a := range as {83 switch {84 case !knownRules[a.rule]:85 // Unknown rules are reported once, by the first analyzer, so86 // the message does not appear four times.87 if name == "laneimports" {88 r.pass.Reportf(a.pos, "%s", contractf(89 "unknown depcheck rule "+quote(a.rule)+" in a "+pragma+" pragma",90 "9",91 "a pragma that names no rule suppresses nothing while reading as if it did; the rules are laneimports, cleanroom, hostenv, netconfine, semconvlit, or all",92 ))93 }94 case a.reason == "" && (a.rule == name || a.rule == "all" && name == "laneimports"):95 r.pass.Reportf(a.pos, "%s", contractf(96 pragma+" needs a written reason",97 "6",98 "every accepted divergence carries a reason a reviewer can weigh; an unexplained allowance is a silent hole in the rule",99 ))100 }101 }102 }103}104105func (r *reporter) suppressed(pos token.Pos) bool {106 line := r.pass.Fset.Position(pos).Line107 for _, candidate := range []int{line, line - 1} {108 for _, a := range r.allow[candidate] {109 if a.reason == "" {110 continue111 }112 if a.rule == "all" || a.rule == r.pass.Analyzer.Name {113 return true114 }115 }116 }117 return false118}119120func (r *reporter) reportf(pos token.Pos, msg string) {121 if r.suppressed(pos) {122 return123 }124 r.pass.Reportf(pos, "%s", msg)125}126127// importPos points a diagnostic at the import spec so the fix is where the128// finger points.129func importPos(spec *ast.ImportSpec) token.Pos { return spec.Path.Pos() }130131func quote(s string) string { return `"` + s + `"` }