• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

abice / go-enum / 16393245158

19 Jul 2025 10:13PM UTC coverage: 64.871% (-26.9%) from 91.721%
16393245158

Pull #258

github

abice
chore: convert to options struct
Pull Request #258: chore: update go versions

92 of 144 new or added lines in 3 files covered. (63.89%)

17 existing lines in 1 file now uncovered.

578 of 891 relevant lines covered (64.87%)

1007.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.13
/generator/generator.go
1
package generator
2

3
import (
4
        "bytes"
5
        "errors"
6
        "fmt"
7
        "go/ast"
8
        "go/parser"
9
        "go/token"
10
        "net/url"
11
        "sort"
12
        "strconv"
13
        "strings"
14
        "text/template"
15
        "unicode"
16

17
        "github.com/Masterminds/sprig/v3"
18
        "golang.org/x/text/cases"
19
        "golang.org/x/text/language"
20
        "golang.org/x/tools/imports"
21
)
22

23
const (
24
        skipHolder         = `_`
25
        parseCommentPrefix = `//`
26
)
27

28
// Generator is responsible for generating validation files for the given in a go source file.
29
type Generator struct {
30
        Version   string
31
        Revision  string
32
        BuildDate string
33
        BuiltBy   string
34
        GeneratorOptions
35
        t              *template.Template
36
        knownTemplates map[string]*template.Template
37
        fileSet        *token.FileSet
38
}
39

40
// Enum holds data for a discovered enum in the parsed source
41
type Enum struct {
42
        Name    string
43
        Prefix  string
44
        Type    string
45
        Values  []EnumValue
46
        Comment string
47
}
48

49
// EnumValue holds the individual data for each enum value within the found enum.
50
type EnumValue struct {
51
        RawName      string
52
        Name         string
53
        PrefixedName string
54
        ValueStr     string
55
        ValueInt     interface{}
56
        Comment      string
57
}
58

59
// NewGenerator is a constructor method for creating a new Generator with default
60
// templates loaded.
61
func NewGenerator(options ...Option) *Generator {
87✔
62
        g := &Generator{
87✔
63
                Version:        "-",
87✔
64
                Revision:       "-",
87✔
65
                BuildDate:      "-",
87✔
66
                BuiltBy:        "-",
87✔
67
                knownTemplates: make(map[string]*template.Template),
87✔
68
                t:              template.New("generator"),
87✔
69
                fileSet:        token.NewFileSet(),
87✔
70
                GeneratorOptions: GeneratorOptions{
87✔
71
                        noPrefix:          false,
87✔
72
                        userTemplateNames: make([]string, 0),
87✔
73
                        replacementNames:  map[string]string{},
87✔
74
                        jsonPkg:           "encoding/json",
87✔
75
                },
87✔
76
        }
87✔
77

87✔
78
        // Apply all options
87✔
79
        for _, option := range options {
354✔
80
                option(g)
267✔
81
        }
267✔
82

83
        funcs := sprig.TxtFuncMap()
87✔
84

87✔
85
        funcs["stringify"] = Stringify
87✔
86
        funcs["mapify"] = Mapify
87✔
87
        funcs["unmapify"] = Unmapify
87✔
88
        funcs["namify"] = Namify
87✔
89
        funcs["offset"] = Offset
87✔
90
        funcs["quote"] = strconv.Quote
87✔
91

87✔
92
        g.t.Funcs(funcs)
87✔
93

87✔
94
        g.addEmbeddedTemplates()
87✔
95

87✔
96
        g.updateTemplates()
87✔
97

87✔
98
        return g
87✔
99
}
100

101
func (g *Generator) anySQLEnabled() bool {
540✔
102
        return g.sql || g.sqlNullStr || g.sqlint || g.sqlNullInt
540✔
103
}
540✔
104

105
// ParseAliases is used to add aliases to replace during name sanitization.
106
func ParseAliases(aliases []string) (map[string]string, error) {
27✔
107
        aliasMap := map[string]string{}
27✔
108

27✔
109
        for _, str := range aliases {
72✔
110
                kvps := strings.Split(str, ",")
45✔
111
                for _, kvp := range kvps {
117✔
112
                        parts := strings.Split(kvp, ":")
72✔
113
                        if len(parts) != 2 {
75✔
114
                                return nil, fmt.Errorf("invalid formatted alias entry %q, must be in the format \"key:value\"", kvp)
3✔
115
                        }
3✔
116
                        aliasMap[parts[0]] = parts[1]
69✔
117
                }
118
        }
119

120
        return aliasMap, nil
24✔
121
}
122

123
// GenerateFromFile is responsible for orchestrating the Code generation.  It results in a byte array
124
// that can be written to any file desired.  It has already had goimports run on the code before being returned.
125
func (g *Generator) GenerateFromFile(inputFile string) ([]byte, error) {
57✔
126
        f, err := g.parseFile(inputFile)
57✔
127
        if err != nil {
63✔
128
                return nil, fmt.Errorf("generate: error parsing input file '%s': %s", inputFile, err)
6✔
129
        }
6✔
130
        return g.Generate(f)
51✔
131
}
132

133
// Generate does the heavy lifting for the code generation starting from the parsed AST file.
134
func (g *Generator) Generate(f *ast.File) ([]byte, error) {
96✔
135
        enums := g.inspect(f)
96✔
136
        if len(enums) <= 0 {
102✔
137
                return nil, nil
6✔
138
        }
6✔
139

140
        pkg := f.Name.Name
90✔
141

90✔
142
        vBuff := bytes.NewBuffer([]byte{})
90✔
143
        err := g.t.ExecuteTemplate(vBuff, "header", map[string]interface{}{
90✔
144
                "package":   pkg,
90✔
145
                "version":   g.Version,
90✔
146
                "revision":  g.Revision,
90✔
147
                "buildDate": g.BuildDate,
90✔
148
                "builtBy":   g.BuiltBy,
90✔
149
                "buildTags": g.buildTags,
90✔
150
                "jsonpkg":   g.jsonPkg,
90✔
151
        })
90✔
152
        if err != nil {
90✔
UNCOV
153
                return nil, fmt.Errorf("failed writing header: %w", err)
×
UNCOV
154
        }
×
155

156
        // Make the output more consistent by iterating over sorted keys of map
157
        var keys []string
90✔
158
        for key := range enums {
648✔
159
                keys = append(keys, key)
558✔
160
        }
558✔
161
        sort.Strings(keys)
90✔
162

90✔
163
        var created int
90✔
164
        for _, name := range keys {
648✔
165
                ts := enums[name]
558✔
166

558✔
167
                // Parse the enum doc statement
558✔
168
                enum, pErr := g.parseEnum(ts)
558✔
169
                if pErr != nil {
576✔
170
                        continue
18✔
171
                }
172

173
                created++
540✔
174
                data := map[string]interface{}{
540✔
175
                        "enum":          enum,
540✔
176
                        "name":          name,
540✔
177
                        "lowercase":     g.lowercaseLookup,
540✔
178
                        "nocase":        g.caseInsensitive,
540✔
179
                        "nocomments":    g.noComments,
540✔
180
                        "marshal":       g.marshal,
540✔
181
                        "sql":           g.sql,
540✔
182
                        "sqlint":        g.sqlint,
540✔
183
                        "flag":          g.flag,
540✔
184
                        "names":         g.names,
540✔
185
                        "ptr":           g.ptr,
540✔
186
                        "values":        g.values,
540✔
187
                        "anySQLEnabled": g.anySQLEnabled(),
540✔
188
                        "sqlnullint":    g.sqlNullInt,
540✔
189
                        "sqlnullstr":    g.sqlNullStr,
540✔
190
                        "mustparse":     g.mustParse,
540✔
191
                        "forcelower":    g.forceLower,
540✔
192
                        "forceupper":    g.forceUpper,
540✔
193
                }
540✔
194

540✔
195
                templateName := "enum"
540✔
196
                if enum.Type == "string" {
591✔
197
                        templateName = "enum_string"
51✔
198
                }
51✔
199

200
                err = g.t.ExecuteTemplate(vBuff, templateName, data)
540✔
201
                if err != nil {
540✔
202
                        return vBuff.Bytes(), fmt.Errorf("failed writing enum data for enum: %q: %w", name, err)
×
203
                }
×
204

205
                for _, userTemplateName := range g.userTemplateNames {
669✔
206
                        err = g.t.ExecuteTemplate(vBuff, userTemplateName, data)
129✔
207
                        if err != nil {
129✔
UNCOV
208
                                return vBuff.Bytes(), fmt.Errorf("failed writing enum data for enum: %q, template: %v: %w", name, userTemplateName, err)
×
UNCOV
209
                        }
×
210
                }
211
        }
212

213
        if created < 1 {
108✔
214
                // Don't save anything if we didn't actually generate any successful enums.
18✔
215
                return nil, nil
18✔
216
        }
18✔
217

218
        formatted, err := imports.Process(pkg, vBuff.Bytes(), nil)
72✔
219
        if err != nil {
72✔
UNCOV
220
                err = fmt.Errorf("generate: error formatting code %s\n\n%s", err, vBuff.String())
×
UNCOV
221
        }
×
222
        return formatted, err
72✔
223
}
224

225
// updateTemplates will update the lookup map for validation checks that are
226
// allowed within the template engine.
227
func (g *Generator) updateTemplates() {
96✔
228
        for _, template := range g.t.Templates() {
636✔
229
                g.knownTemplates[template.Name()] = template
540✔
230
        }
540✔
231
}
232

233
// parseFile simply calls the go/parser ParseFile function with an empty token.FileSet
234
func (g *Generator) parseFile(fileName string) (*ast.File, error) {
57✔
235
        // Parse the file given in arguments
57✔
236
        return parser.ParseFile(g.fileSet, fileName, nil, parser.ParseComments)
57✔
237
}
57✔
238

239
// parseEnum looks for the ENUM(x,y,z) formatted documentation from the type definition
240
func (g *Generator) parseEnum(ts *ast.TypeSpec) (*Enum, error) {
558✔
241
        if ts.Doc == nil {
558✔
242
                return nil, errors.New("no doc on enum")
×
243
        }
×
244

245
        enum := &Enum{}
558✔
246

558✔
247
        enum.Name = ts.Name.Name
558✔
248
        enum.Type = fmt.Sprintf("%s", ts.Type)
558✔
249
        if !g.noPrefix {
813✔
250
                enum.Prefix = ts.Name.Name
255✔
251
        }
255✔
252
        if g.prefix != "" {
687✔
253
                enum.Prefix = g.prefix + enum.Prefix
129✔
254
        }
129✔
255

256
        commentPreEnumDecl, _, _ := strings.Cut(ts.Doc.Text(), `ENUM(`)
558✔
257
        enum.Comment = strings.TrimSpace(commentPreEnumDecl)
558✔
258

558✔
259
        enumDecl := getEnumDeclFromComments(ts.Doc.List)
558✔
260
        if enumDecl == "" {
564✔
261
                return nil, errors.New("failed parsing enum")
6✔
262
        }
6✔
263

264
        values := strings.Split(strings.TrimSuffix(strings.TrimPrefix(enumDecl, `ENUM(`), `)`), `,`)
552✔
265
        var (
552✔
266
                data     interface{}
552✔
267
                unsigned bool
552✔
268
        )
552✔
269
        if strings.HasPrefix(enum.Type, "u") {
594✔
270
                data = uint64(0)
42✔
271
                unsigned = true
42✔
272
        } else {
552✔
273
                data = int64(0)
510✔
274
        }
510✔
275
        for _, value := range values {
3,705✔
276
                var comment string
3,153✔
277

3,153✔
278
                // Trim and store comments
3,153✔
279
                if strings.Contains(value, parseCommentPrefix) {
3,477✔
280
                        commentStartIndex := strings.Index(value, parseCommentPrefix)
324✔
281
                        comment = value[commentStartIndex+len(parseCommentPrefix):]
324✔
282
                        comment = strings.TrimSpace(unescapeComment(comment))
324✔
283
                        // value without comment
324✔
284
                        value = value[:commentStartIndex]
324✔
285
                }
324✔
286

287
                // Make sure to leave out any empty parts
288
                if value != "" {
6,306✔
289
                        rawName := value
3,153✔
290
                        valueStr := value
3,153✔
291

3,153✔
292
                        if strings.Contains(value, `=`) {
4,329✔
293
                                // Get the value specified and set the data to that value.
1,176✔
294
                                equalIndex := strings.Index(value, `=`)
1,176✔
295
                                dataVal := strings.TrimSpace(value[equalIndex+1:])
1,176✔
296
                                if dataVal != "" {
2,172✔
297
                                        valueStr = dataVal
996✔
298
                                        rawName = value[:equalIndex]
996✔
299
                                        if enum.Type == "string" {
1,116✔
300
                                                if parsed, err := strconv.ParseInt(dataVal, 0, 64); err == nil {
228✔
301
                                                        data = parsed
108✔
302
                                                        valueStr = rawName
108✔
303
                                                }
108✔
304
                                                if q := identifyQuoted(dataVal); q != "" {
132✔
305
                                                        valueStr = trimQuotes(q, dataVal)
12✔
306
                                                }
12✔
307
                                        } else if unsigned {
1,422✔
308
                                                newData, err := strconv.ParseUint(dataVal, 0, 64)
546✔
309
                                                if err != nil {
552✔
310
                                                        err = fmt.Errorf("failed parsing the data part of enum value '%s': %w", value, err)
6✔
311
                                                        fmt.Println(err)
6✔
312
                                                        return nil, err
6✔
313
                                                }
6✔
314
                                                data = newData
540✔
315
                                        } else {
330✔
316
                                                newData, err := strconv.ParseInt(dataVal, 0, 64)
330✔
317
                                                if err != nil {
336✔
318
                                                        err = fmt.Errorf("failed parsing the data part of enum value '%s': %w", value, err)
6✔
319
                                                        fmt.Println(err)
6✔
320
                                                        return nil, err
6✔
321
                                                }
6✔
322
                                                data = newData
324✔
323
                                        }
324
                                } else {
180✔
325
                                        rawName = strings.TrimSuffix(rawName, `=`)
180✔
326
                                        fmt.Printf("Ignoring enum with '=' but no value after: %s\n", rawName)
180✔
327
                                }
180✔
328
                        }
329
                        rawName = strings.TrimSpace(rawName)
3,141✔
330
                        valueStr = strings.TrimSpace(valueStr)
3,141✔
331
                        name := cases.Title(language.Und, cases.NoLower).String(rawName)
3,141✔
332
                        prefixedName := name
3,141✔
333
                        if name != skipHolder {
6,174✔
334
                                prefixedName = enum.Prefix + name
3,033✔
335
                                prefixedName = g.sanitizeValue(prefixedName)
3,033✔
336
                                if !g.leaveSnakeCase {
3,552✔
337
                                        prefixedName = snakeToCamelCase(prefixedName)
519✔
338
                                }
519✔
339
                        }
340

341
                        ev := EnumValue{Name: name, RawName: rawName, PrefixedName: prefixedName, ValueStr: valueStr, ValueInt: data, Comment: comment}
3,141✔
342
                        enum.Values = append(enum.Values, ev)
3,141✔
343
                        data = increment(data)
3,141✔
344
                }
345
        }
346

347
        // fmt.Printf("###\nENUM: %+v\n###\n", enum)
348

349
        return enum, nil
540✔
350
}
351

352
func identifyQuoted(s string) string {
120✔
353
        s = strings.TrimSpace(s)
120✔
354
        if strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`) {
129✔
355
                return `"`
9✔
356
        }
9✔
357
        if strings.HasPrefix(s, `'`) && strings.HasSuffix(s, `'`) {
114✔
358
                return `'`
3✔
359
        }
3✔
360
        return ""
108✔
361
}
362

363
func trimQuotes(q, s string) string {
12✔
364
        return strings.TrimPrefix(strings.TrimSuffix(strings.TrimSpace(s), q), q)
12✔
365
}
12✔
366

367
func increment(d interface{}) interface{} {
3,141✔
368
        switch v := d.(type) {
3,141✔
369
        case uint64:
540✔
370
                return v + 1
540✔
371
        case int64:
2,601✔
372
                return v + 1
2,601✔
373
        }
UNCOV
374
        return d
×
375
}
376

377
func unescapeComment(comment string) string {
324✔
378
        val, err := url.QueryUnescape(comment)
324✔
379
        if err != nil {
324✔
UNCOV
380
                return comment
×
UNCOV
381
        }
×
382
        return val
324✔
383
}
384

385
// sanitizeValue will ensure the value name generated adheres to golang's
386
// identifier syntax as described here: https://golang.org/ref/spec#Identifiers
387
// identifier = letter { letter | unicode_digit }
388
// where letter can be unicode_letter or '_'
389
func (g *Generator) sanitizeValue(value string) string {
3,033✔
390
        // Keep skip value holders
3,033✔
391
        if value == skipHolder {
3,033✔
UNCOV
392
                return skipHolder
×
UNCOV
393
        }
×
394

395
        replacedValue := value
3,033✔
396
        for k, v := range g.replacementNames {
3,051✔
397
                replacedValue = strings.ReplaceAll(replacedValue, k, v)
18✔
398
        }
18✔
399

400
        nameBuilder := strings.Builder{}
3,033✔
401
        nameBuilder.Grow(len(replacedValue))
3,033✔
402

3,033✔
403
        for i, r := range replacedValue {
45,345✔
404
                // If the start character is not a unicode letter (this check includes the case of '_')
42,312✔
405
                // then we need to add an exported prefix, so tack on a 'X' at the beginning
42,312✔
406
                if i == 0 && !unicode.IsLetter(r) {
42,372✔
407
                        nameBuilder.WriteRune('X')
60✔
408
                }
60✔
409

410
                if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' {
84,210✔
411
                        nameBuilder.WriteRune(r)
41,898✔
412
                }
41,898✔
413
        }
414

415
        return nameBuilder.String()
3,033✔
416
}
417

418
func snakeToCamelCase(value string) string {
519✔
419
        parts := strings.Split(value, "_")
519✔
420
        title := cases.Title(language.Und, cases.NoLower)
519✔
421

519✔
422
        for i, part := range parts {
1,056✔
423
                parts[i] = title.String(part)
537✔
424
        }
537✔
425
        value = strings.Join(parts, "")
519✔
426

519✔
427
        return value
519✔
428
}
429

430
// getEnumDeclFromComments parses the array of comment strings and creates a single Enum Declaration statement
431
// that is easier to deal with for the remainder of parsing.  It turns multi line declarations and makes a single
432
// string declaration.
433
func getEnumDeclFromComments(comments []*ast.Comment) string {
558✔
434
        const EnumPrefix = "ENUM("
558✔
435
        var (
558✔
436
                parts          []string
558✔
437
                lines          []string
558✔
438
                store          bool
558✔
439
                enumParamLevel int
558✔
440
                filteredLines  []string
558✔
441
        )
558✔
442

558✔
443
        for _, comment := range comments {
2,634✔
444
                lines = append(lines, breakCommentIntoLines(comment)...)
2,076✔
445
        }
2,076✔
446

447
        filteredLines = make([]string, 0, len(lines))
558✔
448
        for idx := range lines {
5,034✔
449
                line := lines[idx]
4,476✔
450
                // If we're not in the enum, and this line doesn't contain the
4,476✔
451
                // start string, then move along
4,476✔
452
                if !store && !strings.Contains(line, EnumPrefix) {
5,043✔
453
                        continue
567✔
454
                }
455
                if !store {
4,467✔
456
                        // We must have had the start value in here
558✔
457
                        store = true
558✔
458
                        enumParamLevel = 1
558✔
459
                        start := strings.Index(line, EnumPrefix)
558✔
460
                        line = line[start+len(EnumPrefix):]
558✔
461
                }
558✔
462
                lineParamLevel := strings.Count(line, "(")
3,909✔
463
                lineParamLevel = lineParamLevel - strings.Count(line, ")")
3,909✔
464

3,909✔
465
                if enumParamLevel+lineParamLevel < 1 {
4,461✔
466
                        // We've ended, either with more than we need, or with just enough.  Now we need to find the end.
552✔
467
                        for lineIdx, ch := range line {
2,295✔
468
                                if ch == '(' {
1,743✔
UNCOV
469
                                        enumParamLevel = enumParamLevel + 1
×
UNCOV
470
                                        continue
×
471
                                }
472
                                if ch == ')' {
2,295✔
473
                                        enumParamLevel = enumParamLevel - 1
552✔
474
                                        if enumParamLevel == 0 {
1,104✔
475
                                                // We've found the end of the ENUM() definition,
552✔
476
                                                // Cut off the suffix and break out of the loop
552✔
477
                                                line = line[:lineIdx]
552✔
478
                                                store = false
552✔
479
                                                break
552✔
480
                                        }
481
                                }
482
                        }
483
                }
484

485
                filteredLines = append(filteredLines, line)
3,909✔
486
        }
487

488
        if enumParamLevel > 0 {
564✔
489
                fmt.Println("ENUM Parse error, there is a dangling '(' in your comment.")
6✔
490
                return ""
6✔
491
        }
6✔
492

493
        // Go over all the lines in this comment block
494
        for _, line := range filteredLines {
4,443✔
495
                _, trimmed := parseLinePart(line)
3,891✔
496
                if trimmed != "" {
6,528✔
497
                        parts = append(parts, trimmed)
2,637✔
498
                }
2,637✔
499
        }
500

501
        joined := fmt.Sprintf("ENUM(%s)", strings.Join(parts, `,`))
552✔
502
        return joined
552✔
503
}
504

505
func parseLinePart(line string) (paramLevel int, trimmed string) {
3,891✔
506
        trimmed = line
3,891✔
507
        comment := ""
3,891✔
508
        if idx := strings.Index(line, parseCommentPrefix); idx >= 0 {
4,215✔
509
                trimmed = line[:idx]
324✔
510
                comment = "//" + url.QueryEscape(strings.TrimSpace(line[idx+2:]))
324✔
511
        }
324✔
512
        trimmed = trimAllTheThings(trimmed)
3,891✔
513
        trimmed += comment
3,891✔
514
        opens := strings.Count(line, `(`)
3,891✔
515
        closes := strings.Count(line, `)`)
3,891✔
516
        if opens > 0 {
3,933✔
517
                paramLevel += opens
42✔
518
        }
42✔
519
        if closes > 0 {
3,933✔
520
                paramLevel -= closes
42✔
521
        }
42✔
522
        return
3,891✔
523
}
524

525
// breakCommentIntoLines takes the comment and since single line comments are already broken into lines
526
// we break multiline comments into separate lines for processing.
527
func breakCommentIntoLines(comment *ast.Comment) []string {
2,076✔
528
        lines := []string{}
2,076✔
529
        text := comment.Text
2,076✔
530
        if strings.HasPrefix(text, `/*`) {
2,379✔
531
                // deal with multi line comment
303✔
532
                multiline := strings.TrimSuffix(strings.TrimPrefix(text, `/*`), `*/`)
303✔
533
                lines = append(lines, strings.Split(multiline, "\n")...)
303✔
534
        } else {
2,076✔
535
                lines = append(lines, strings.TrimPrefix(text, `//`))
1,773✔
536
        }
1,773✔
537
        return lines
2,076✔
538
}
539

540
// trimAllTheThings takes off all the cruft of a line that we don't need.
541
// These lines should be pre-filtered so that we don't have to worry about
542
// the `ENUM(` prefix and the `)` suffix... those should already be removed.
543
func trimAllTheThings(thing string) string {
3,891✔
544
        preTrimmed := strings.TrimSuffix(strings.TrimSpace(thing), `,`)
3,891✔
545
        return strings.TrimSpace(preTrimmed)
3,891✔
546
}
3,891✔
547

548
// inspect will walk the ast and fill a map of names and their struct information
549
// for use in the generation template.
550
func (g *Generator) inspect(f ast.Node) map[string]*ast.TypeSpec {
96✔
551
        enums := make(map[string]*ast.TypeSpec)
96✔
552
        // Inspect the AST and find all structs.
96✔
553
        ast.Inspect(f, func(n ast.Node) bool {
17,544✔
554
                switch x := n.(type) {
17,448✔
555
                case *ast.GenDecl:
621✔
556
                        copyGenDeclCommentsToSpecs(x)
621✔
557
                case *ast.Ident:
1,530✔
558
                        if x.Obj != nil {
2,361✔
559
                                // fmt.Printf("Node: %#v\n", x.Obj)
831✔
560
                                // Make sure it's a Type Identifier
831✔
561
                                if x.Obj.Kind == ast.Typ {
1,527✔
562
                                        // Make sure it's a spec (Type Identifiers can be throughout the code)
696✔
563
                                        if ts, ok := x.Obj.Decl.(*ast.TypeSpec); ok {
1,302✔
564
                                                // fmt.Printf("Type: %+v\n", ts)
606✔
565
                                                isEnum := isTypeSpecEnum(ts)
606✔
566
                                                // Only store documented enums
606✔
567
                                                if isEnum {
1,164✔
568
                                                        // fmt.Printf("EnumType: %T\n", ts.Type)
558✔
569
                                                        enums[x.Name] = ts
558✔
570
                                                }
558✔
571
                                        }
572
                                }
573
                        }
574
                }
575
                // Return true to continue through the tree
576
                return true
17,448✔
577
        })
578

579
        return enums
96✔
580
}
581

582
// copyDocsToSpecs will take the GenDecl level documents and copy them
583
// to the children Type and Value specs.  I think this is actually working
584
// around a bug in the AST, but it works for now.
585
func copyGenDeclCommentsToSpecs(x *ast.GenDecl) {
621✔
586
        // Copy the doc spec to the type or value spec
621✔
587
        // cause they missed this... whoops
621✔
588
        if x.Doc != nil {
1,227✔
589
                for _, spec := range x.Specs {
1,212✔
590
                        switch s := spec.(type) {
606✔
591
                        case *ast.TypeSpec:
606✔
592
                                if s.Doc == nil {
1,212✔
593
                                        s.Doc = x.Doc
606✔
594
                                }
606✔
UNCOV
595
                        case *ast.ValueSpec:
×
UNCOV
596
                                if s.Doc == nil {
×
UNCOV
597
                                        s.Doc = x.Doc
×
UNCOV
598
                                }
×
599
                        }
600
                }
601
        }
602
}
603

604
// isTypeSpecEnum checks the comments on the type spec to determine if there is an enum
605
// declaration for the type.
606
func isTypeSpecEnum(ts *ast.TypeSpec) bool {
606✔
607
        isEnum := false
606✔
608
        if ts.Doc != nil {
1,212✔
609
                for _, comment := range ts.Doc.List {
2,730✔
610
                        if strings.Contains(comment.Text, `ENUM(`) {
2,682✔
611
                                isEnum = true
558✔
612
                        }
558✔
613
                }
614
        }
615

616
        return isEnum
606✔
617
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc