1
2
3
4
5 package specdoc
6
7 import (
8 "cmp"
9 "fmt"
10 "go/token"
11 "io"
12 "slices"
13 "strings"
14 )
15
16
17 type Finding interface {
18 String() string
19 Decl() Decl
20 }
21
22
23
24 type Report struct {
25 TypeMismatches []TypeMismatch
26 NameMismatches []NameMismatch
27 DocOrderViolations []DocOrderViolation
28 UnknownDecls []UnknownDecl
29 UnexpectedDocs []UnexpectedDoc
30 }
31
32
33 func (r *Report) Error() string {
34 var b strings.Builder
35 r.Print(&b)
36 return b.String()
37 }
38
39
40 type Decl struct {
41 Pos token.Position
42 Recv string
43 Name string
44 }
45
46
47
48 func (d Decl) Compare(other Decl) int {
49 if c := cmp.Compare(d.Pos.Filename, other.Pos.Filename); c != 0 {
50 return c
51 }
52 if c := cmp.Compare(d.Pos.Offset, other.Pos.Offset); c != 0 {
53 return c
54 }
55 if c := cmp.Compare(d.Recv, other.Recv); c != 0 {
56 return c
57 }
58 return cmp.Compare(d.Name, other.Name)
59 }
60
61
62 func (d Decl) String() string {
63 if d.Recv != "" {
64 return fmt.Sprintf("(%s) %s", d.Recv, d.Name)
65 }
66 return d.Name
67 }
68
69
70
71 type TypeMismatch struct {
72 D Decl
73 DeclSig string
74 SpecSig string
75 Details []string
76 }
77
78 func (m TypeMismatch) Decl() Decl { return m.D }
79
80 func (m TypeMismatch) String() string {
81 var b strings.Builder
82 fmt.Fprintf(&b, "%s: %s: type mismatch\n", m.D.Pos, m.D)
83 fmt.Fprintf(&b, " decl: %s\n", m.DeclSig)
84 fmt.Fprintf(&b, " spec: %s\n", m.SpecSig)
85 for _, d := range m.Details {
86 fmt.Fprintf(&b, " - %s\n", d)
87 }
88 return b.String()
89 }
90
91
92
93 type NameMismatch struct {
94 D Decl
95 DeclSig string
96 SpecSig string
97 Details []string
98 }
99
100 func (m NameMismatch) Decl() Decl { return m.D }
101
102 func (m NameMismatch) String() string {
103 var b strings.Builder
104 fmt.Fprintf(&b, "%s: %s: name mismatch\n", m.D.Pos, m.D)
105 fmt.Fprintf(&b, " decl: %s\n", m.DeclSig)
106 fmt.Fprintf(&b, " spec: %s\n", m.SpecSig)
107 for _, d := range m.Details {
108 fmt.Fprintf(&b, " - %s\n", d)
109 }
110 return b.String()
111 }
112
113
114
115 type DocOrderViolation struct {
116 D Decl
117 Err error
118 }
119
120 func (v DocOrderViolation) Decl() Decl { return v.D }
121
122 func (v DocOrderViolation) String() string {
123 return fmt.Sprintf("%s: %s: %v\n", v.D.Pos, v.D, v.Err)
124 }
125
126
127 type UnknownDecl struct {
128 D Decl
129 }
130
131 func (u UnknownDecl) Decl() Decl { return u.D }
132
133 func (u UnknownDecl) String() string {
134 return fmt.Sprintf("%s: %s: declaration not in spec\n", u.D.Pos, u.D)
135 }
136
137
138
139 type UnexpectedDoc struct {
140 D Decl
141 }
142
143 func (u UnexpectedDoc) Decl() Decl { return u.D }
144
145 func (u UnexpectedDoc) String() string {
146 return fmt.Sprintf("%s: %s: declaration contains unexpected doc comment\n", u.D.Pos, u.D)
147 }
148
149
150 func (r *Report) Merge(other *Report) {
151 r.TypeMismatches = append(r.TypeMismatches, other.TypeMismatches...)
152 r.NameMismatches = append(r.NameMismatches, other.NameMismatches...)
153 r.DocOrderViolations = append(r.DocOrderViolations, other.DocOrderViolations...)
154 r.UnknownDecls = append(r.UnknownDecls, other.UnknownDecls...)
155 r.UnexpectedDocs = append(r.UnexpectedDocs, other.UnexpectedDocs...)
156 }
157
158
159 func (r Report) Empty() bool {
160 return len(r.TypeMismatches) == 0 &&
161 len(r.NameMismatches) == 0 &&
162 len(r.DocOrderViolations) == 0 &&
163 len(r.UnknownDecls) == 0 &&
164 len(r.UnexpectedDocs) == 0
165 }
166
167 func convertAppend[From Finding](ys []Finding, xs []From) []Finding {
168 for _, x := range xs {
169 ys = append(ys, x)
170 }
171 return ys
172 }
173
174
175 func (r Report) Findings() []Finding {
176 var items []Finding
177 items = convertAppend(items, r.TypeMismatches)
178 items = convertAppend(items, r.NameMismatches)
179 items = convertAppend(items, r.DocOrderViolations)
180 items = convertAppend(items, r.UnknownDecls)
181 items = convertAppend(items, r.UnexpectedDocs)
182 return items
183 }
184
185
186
187 func (r Report) Print(w io.Writer) {
188 items := r.Findings()
189 slices.SortStableFunc(items, func(a, b Finding) int {
190 if c := a.Decl().Compare(b.Decl()); c != 0 {
191 return c
192 }
193 return cmp.Compare(a.String(), b.String())
194 })
195
196 for _, item := range items {
197 fmt.Fprint(w, item.String())
198 }
199 }
200
View as plain text