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

99designs / gqlgen / 12999394514

27 Jan 2025 10:16PM UTC coverage: 73.738% (-0.2%) from 73.891%
12999394514

Pull #3507

github

StevenACoffman
Regenerate

Signed-off-by: Steve Coffman <steve@khanacademy.org>
Pull Request #3507: Update gqlparser v2 to v2.5.22 to Support `@oneOf` and `@deprecated` on input values

4 of 27 new or added lines in 3 files covered. (14.81%)

2 existing lines in 1 file now uncovered.

8617 of 11686 relevant lines covered (73.74%)

545.19 hits per line

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

77.03
/codegen/templates/templates.go
1
package templates
2

3
import (
4
        "bytes"
5
        "errors"
6
        "fmt"
7
        "go/types"
8
        "io/fs"
9
        "os"
10
        "path/filepath"
11
        "reflect"
12
        "regexp"
13
        "runtime"
14
        "sort"
15
        "strconv"
16
        "strings"
17
        "sync"
18
        "text/template"
19
        "unicode"
20

21
        "github.com/99designs/gqlgen/internal/code"
22
        "github.com/99designs/gqlgen/internal/imports"
23
)
24

25
// CurrentImports keeps track of all the import declarations that are needed during the execution of a plugin.
26
// this is done with a global because subtemplates currently get called in functions. Lets aim to remove this eventually.
27
var CurrentImports *Imports
28

29
// Options specify various parameters to rendering a template.
30
type Options struct {
31
        // PackageName is a helper that specifies the package header declaration.
32
        // In other words, when you write the template you don't need to specify `package X`
33
        // at the top of the file. By providing PackageName in the Options, the Render
34
        // function will do that for you.
35
        PackageName string
36
        // Template is a string of the entire template that
37
        // will be parsed and rendered. If it's empty,
38
        // the plugin processor will look for .gotpl files
39
        // in the same directory of where you wrote the plugin.
40
        Template string
41

42
        // Use the go:embed API to collect all the template files you want to pass into Render
43
        // this is an alternative to passing the Template option
44
        TemplateFS fs.FS
45

46
        // Filename is the name of the file that will be
47
        // written to the system disk once the template is rendered.
48
        Filename        string
49
        RegionTags      bool
50
        GeneratedHeader bool
51
        // PackageDoc is documentation written above the package line
52
        PackageDoc string
53
        // FileNotice is notice written below the package line
54
        FileNotice string
55
        // Data will be passed to the template execution.
56
        Data  any
57
        Funcs template.FuncMap
58

59
        // Packages cache, you can find me on config.Config
60
        Packages *code.Packages
61
}
62

63
var (
64
        modelNamesMu sync.Mutex
65
        modelNames   = make(map[string]string, 0)
66
        goNameRe     = regexp.MustCompile("[^a-zA-Z0-9_]")
67
)
68

69
// Render renders a gql plugin template from the given Options. Render is an
70
// abstraction of the text/template package that makes it easier to write gqlgen
71
// plugins. If Options.Template is empty, the Render function will look for `.gotpl`
72
// files inside the directory where you wrote the plugin.
73
func Render(cfg Options) error {
35✔
74
        if CurrentImports != nil {
35✔
75
                panic(errors.New("recursive or concurrent call to RenderToFile detected"))
×
76
        }
77
        CurrentImports = &Imports{packages: cfg.Packages, destDir: filepath.Dir(cfg.Filename)}
35✔
78

35✔
79
        funcs := Funcs()
35✔
80
        for n, f := range cfg.Funcs {
59✔
81
                funcs[n] = f
24✔
82
        }
24✔
83

84
        t := template.New("").Funcs(funcs)
35✔
85
        t, err := parseTemplates(cfg, t)
35✔
86
        if err != nil {
35✔
87
                return err
×
88
        }
×
89

90
        roots := make([]string, 0, len(t.Templates()))
35✔
91
        for _, template := range t.Templates() {
110✔
92
                // templates that end with _.gotpl are special files we don't want to include
75✔
93
                if strings.HasSuffix(template.Name(), "_.gotpl") ||
75✔
94
                        // filter out templates added with {{ template xxx }} syntax inside the template file
75✔
95
                        !strings.HasSuffix(template.Name(), ".gotpl") {
94✔
96
                        continue
19✔
97
                }
98

99
                roots = append(roots, template.Name())
56✔
100
        }
101

102
        // then execute all the important looking ones in order, adding them to the same file
103
        sort.Slice(roots, func(i, j int) bool {
85✔
104
                // important files go first
50✔
105
                if strings.HasSuffix(roots[i], "!.gotpl") {
65✔
106
                        return true
15✔
107
                }
15✔
108
                if strings.HasSuffix(roots[j], "!.gotpl") {
35✔
UNCOV
109
                        return false
×
UNCOV
110
                }
×
111
                return roots[i] < roots[j]
35✔
112
        })
113

114
        var buf bytes.Buffer
35✔
115
        for _, root := range roots {
91✔
116
                if cfg.RegionTags {
80✔
117
                        buf.WriteString("\n// region    " + center(70, "*", " "+root+" ") + "\n")
24✔
118
                }
24✔
119
                err := t.Lookup(root).Execute(&buf, cfg.Data)
56✔
120
                if err != nil {
56✔
121
                        return fmt.Errorf("%s: %w", root, err)
×
122
                }
×
123
                if cfg.RegionTags {
80✔
124
                        buf.WriteString("\n// endregion " + center(70, "*", " "+root+" ") + "\n")
24✔
125
                }
24✔
126
        }
127

128
        var result bytes.Buffer
35✔
129
        if cfg.GeneratedHeader {
53✔
130
                result.WriteString("// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.\n\n")
18✔
131
        }
18✔
132
        if cfg.PackageDoc != "" {
35✔
133
                result.WriteString(cfg.PackageDoc + "\n")
×
134
        }
×
135
        result.WriteString("package ")
35✔
136
        result.WriteString(cfg.PackageName)
35✔
137
        result.WriteString("\n\n")
35✔
138
        if cfg.FileNotice != "" {
50✔
139
                result.WriteString(cfg.FileNotice)
15✔
140
                result.WriteString("\n\n")
15✔
141
        }
15✔
142
        result.WriteString("import (\n")
35✔
143
        result.WriteString(CurrentImports.String())
35✔
144
        result.WriteString(")\n")
35✔
145
        _, err = buf.WriteTo(&result)
35✔
146
        if err != nil {
35✔
147
                return err
×
148
        }
×
149
        CurrentImports = nil
35✔
150

35✔
151
        err = write(cfg.Filename, result.Bytes(), cfg.Packages)
35✔
152
        if err != nil {
35✔
153
                return err
×
154
        }
×
155

156
        cfg.Packages.Evict(code.ImportPathForDir(filepath.Dir(cfg.Filename)))
35✔
157
        return nil
35✔
158
}
159

160
func parseTemplates(cfg Options, t *template.Template) (*template.Template, error) {
35✔
161
        if cfg.Template != "" {
66✔
162
                var err error
31✔
163
                t, err = t.New("template.gotpl").Parse(cfg.Template)
31✔
164
                if err != nil {
31✔
165
                        return nil, fmt.Errorf("error with provided template: %w", err)
×
166
                }
×
167
                return t, nil
31✔
168
        }
169

170
        var fileSystem fs.FS
4✔
171
        if cfg.TemplateFS != nil {
8✔
172
                fileSystem = cfg.TemplateFS
4✔
173
        } else {
4✔
174
                // load path relative to calling source file
×
175
                _, callerFile, _, _ := runtime.Caller(2)
×
176
                rootDir := filepath.Dir(callerFile)
×
177
                fileSystem = os.DirFS(rootDir)
×
178
        }
×
179

180
        t, err := t.ParseFS(fileSystem, "*.gotpl")
4✔
181
        if err != nil {
4✔
182
                return nil, fmt.Errorf("locating templates: %w", err)
×
183
        }
×
184

185
        return t, nil
4✔
186
}
187

188
func center(width int, pad, s string) string {
51✔
189
        if len(s)+2 > width {
52✔
190
                return s
1✔
191
        }
1✔
192
        lpad := (width - len(s)) / 2
50✔
193
        rpad := width - (lpad + len(s))
50✔
194
        return strings.Repeat(pad, lpad) + s + strings.Repeat(pad, rpad)
50✔
195
}
196

197
func Funcs() template.FuncMap {
35✔
198
        return template.FuncMap{
35✔
199
                "ucFirst":            UcFirst,
35✔
200
                "lcFirst":            LcFirst,
35✔
201
                "quote":              strconv.Quote,
35✔
202
                "rawQuote":           rawQuote,
35✔
203
                "dump":               Dump,
35✔
204
                "ref":                ref,
35✔
205
                "obj":                obj,
35✔
206
                "ts":                 TypeIdentifier,
35✔
207
                "call":               Call,
35✔
208
                "dict":               dict,
35✔
209
                "prefixLines":        prefixLines,
35✔
210
                "notNil":             notNil,
35✔
211
                "strSplit":           StrSplit,
35✔
212
                "reserveImport":      CurrentImports.Reserve,
35✔
213
                "lookupImport":       CurrentImports.Lookup,
35✔
214
                "go":                 ToGo,
35✔
215
                "goPrivate":          ToGoPrivate,
35✔
216
                "goModelName":        ToGoModelName,
35✔
217
                "goPrivateModelName": ToGoPrivateModelName,
35✔
218
                "add": func(a, b int) int {
67✔
219
                        return a + b
32✔
220
                },
32✔
221
                "render": func(filename string, tpldata any) (*bytes.Buffer, error) {
×
222
                        return render(resolveName(filename, 0), tpldata)
×
223
                },
×
224
        }
225
}
226

227
func UcFirst(s string) string {
1,620✔
228
        if s == "" {
1,620✔
229
                return ""
×
230
        }
×
231
        r := []rune(s)
1,620✔
232
        r[0] = unicode.ToUpper(r[0])
1,620✔
233
        return string(r)
1,620✔
234
}
235

236
func LcFirst(s string) string {
196✔
237
        if s == "" {
196✔
238
                return ""
×
239
        }
×
240

241
        r := []rune(s)
196✔
242
        r[0] = unicode.ToLower(r[0])
196✔
243
        return string(r)
196✔
244
}
245

246
func isDelimiter(c rune) bool {
23,556✔
247
        return c == '-' || c == '_' || unicode.IsSpace(c)
23,556✔
248
}
23,556✔
249

250
func ref(p types.Type) string {
1,042✔
251
        typeString := CurrentImports.LookupType(p)
1,042✔
252
        // TODO(steve): figure out why this is needed
1,042✔
253
        // otherwise inconsistent sometimes
1,042✔
254
        if typeString == "interface{}" {
1,042✔
255
                return "any"
×
256
        }
×
257
        return typeString
1,042✔
258
}
259

260
func obj(obj types.Object) string {
×
261
        pkg := CurrentImports.Lookup(obj.Pkg().Path())
×
262
        if pkg != "" {
×
263
                pkg += "."
×
264
        }
×
265

266
        return pkg + obj.Name()
×
267
}
268

269
func Call(p *types.Func) string {
56✔
270
        pkg := CurrentImports.Lookup(p.Pkg().Path())
56✔
271

56✔
272
        if pkg != "" {
112✔
273
                pkg += "."
56✔
274
        }
56✔
275

276
        if p.Type() != nil {
84✔
277
                // make sure the returned type is listed in our imports.
28✔
278
                ref(p.Type().(*types.Signature).Results().At(0).Type())
28✔
279
        }
28✔
280

281
        return pkg + p.Name()
56✔
282
}
283

284
func dict(values ...any) (map[string]any, error) {
150✔
285
        if len(values)%2 != 0 {
151✔
286
                return nil, errors.New("invalid dict call: arguments must be key-value pairs")
1✔
287
        }
1✔
288
        m := make(map[string]any, len(values)/2)
149✔
289
        for i := 0; i < len(values); i += 2 {
445✔
290
                key, ok := values[i].(string)
296✔
291
                if !ok {
297✔
292
                        return nil, errors.New("dict keys must be strings")
1✔
293
                }
1✔
294
                m[key] = values[i+1]
295✔
295
        }
296
        return m, nil
148✔
297
}
298

299
func resetModelNames() {
20✔
300
        modelNamesMu.Lock()
20✔
301
        defer modelNamesMu.Unlock()
20✔
302
        modelNames = make(map[string]string, 0)
20✔
303
}
20✔
304

305
func buildGoModelNameKey(parts []string) string {
846✔
306
        const sep = ":"
846✔
307
        return strings.Join(parts, sep)
846✔
308
}
846✔
309

310
func goModelName(primaryToGoFunc func(string) string, parts []string) string {
846✔
311
        modelNamesMu.Lock()
846✔
312
        defer modelNamesMu.Unlock()
846✔
313

846✔
314
        var (
846✔
315
                goNameKey string
846✔
316
                partLen   int
846✔
317

846✔
318
                nameExists = func(n string) bool {
962✔
319
                        for _, v := range modelNames {
1,181✔
320
                                if n == v {
1,089✔
321
                                        return true
24✔
322
                                }
24✔
323
                        }
324
                        return false
92✔
325
                }
326

327
                applyToGoFunc = func(parts []string) string {
116✔
328
                        var out string
116✔
329
                        switch len(parts) {
116✔
330
                        case 0:
×
331
                                return ""
×
332
                        case 1:
70✔
333
                                return primaryToGoFunc(parts[0])
70✔
334
                        default:
46✔
335
                                out = primaryToGoFunc(parts[0])
46✔
336
                        }
337
                        for _, p := range parts[1:] {
98✔
338
                                out = fmt.Sprintf("%s%s", out, ToGo(p))
52✔
339
                        }
52✔
340
                        return out
46✔
341
                }
342

343
                applyValidGoName = func(parts []string) string {
18✔
344
                        var out string
18✔
345
                        for _, p := range parts {
38✔
346
                                out = fmt.Sprintf("%s%s", out, replaceInvalidCharacters(p))
20✔
347
                        }
20✔
348
                        return out
18✔
349
                }
350
        )
351

352
        // build key for this entity
353
        goNameKey = buildGoModelNameKey(parts)
846✔
354

846✔
355
        // determine if we've seen this entity before, and reuse if so
846✔
356
        if goName, ok := modelNames[goNameKey]; ok {
1,600✔
357
                return goName
754✔
358
        }
754✔
359

360
        // attempt first pass
361
        if goName := applyToGoFunc(parts); !nameExists(goName) {
166✔
362
                modelNames[goNameKey] = goName
74✔
363
                return goName
74✔
364
        }
74✔
365

366
        // determine number of parts
367
        partLen = len(parts)
18✔
368

18✔
369
        // if there is only 1 part, append incrementing number until no conflict
18✔
370
        if partLen == 1 {
20✔
371
                base := applyToGoFunc(parts)
2✔
372
                for i := 0; ; i++ {
4✔
373
                        tmp := fmt.Sprintf("%s%d", base, i)
2✔
374
                        if !nameExists(tmp) {
4✔
375
                                modelNames[goNameKey] = tmp
2✔
376
                                return tmp
2✔
377
                        }
2✔
378
                }
379
        }
380

381
        // best effort "pretty" name
382
        for i := partLen - 1; i >= 1; i-- {
34✔
383
                tmp := fmt.Sprintf("%s%s", applyToGoFunc(parts[0:i]), applyValidGoName(parts[i:]))
18✔
384
                if !nameExists(tmp) {
30✔
385
                        modelNames[goNameKey] = tmp
12✔
386
                        return tmp
12✔
387
                }
12✔
388
        }
389

390
        // finally, fallback to just adding an incrementing number
391
        base := applyToGoFunc(parts)
4✔
392
        for i := 0; ; i++ {
8✔
393
                tmp := fmt.Sprintf("%s%d", base, i)
4✔
394
                if !nameExists(tmp) {
8✔
395
                        modelNames[goNameKey] = tmp
4✔
396
                        return tmp
4✔
397
                }
4✔
398
        }
399
}
400

401
func ToGoModelName(parts ...string) string {
825✔
402
        return goModelName(ToGo, parts)
825✔
403
}
825✔
404

405
func ToGoPrivateModelName(parts ...string) string {
21✔
406
        return goModelName(ToGoPrivate, parts)
21✔
407
}
21✔
408

409
func replaceInvalidCharacters(in string) string {
20✔
410
        return goNameRe.ReplaceAllLiteralString(in, "_")
20✔
411
}
20✔
412

413
func wordWalkerFunc(private bool, nameRunes *[]rune) func(*wordInfo) {
2,487✔
414
        return func(info *wordInfo) {
6,554✔
415
                word := info.Word
4,067✔
416

4,067✔
417
                switch {
4,067✔
418
                case private && info.WordOffset == 0:
354✔
419
                        if strings.ToUpper(word) == word || strings.ToLower(word) == word {
666✔
420
                                // ID → id, CAMEL → camel
312✔
421
                                word = strings.ToLower(info.Word)
312✔
422
                        } else {
354✔
423
                                // ITicket → iTicket
42✔
424
                                word = LcFirst(info.Word)
42✔
425
                        }
42✔
426

427
                case info.MatchCommonInitial:
93✔
428
                        word = strings.ToUpper(word)
93✔
429

430
                case !info.HasCommonInitial && (strings.ToUpper(word) == word || strings.ToLower(word) == word):
1,413✔
431
                        // FOO or foo → Foo
1,413✔
432
                        // FOo → FOo
1,413✔
433
                        word = UcFirst(strings.ToLower(word))
1,413✔
434
                }
435

436
                *nameRunes = append(*nameRunes, []rune(word)...)
4,067✔
437
        }
438
}
439

440
func ToGo(name string) string {
2,133✔
441
        if name == "_" {
2,134✔
442
                return "_"
1✔
443
        }
1✔
444
        runes := make([]rune, 0, len(name))
2,132✔
445

2,132✔
446
        wordWalker(name, wordWalkerFunc(false, &runes))
2,132✔
447

2,132✔
448
        return string(runes)
2,132✔
449
}
450

451
func ToGoPrivate(name string) string {
356✔
452
        if name == "_" {
357✔
453
                return "_"
1✔
454
        }
1✔
455
        runes := make([]rune, 0, len(name))
355✔
456

355✔
457
        wordWalker(name, wordWalkerFunc(true, &runes))
355✔
458

355✔
459
        return sanitizeKeywords(string(runes))
355✔
460
}
461

462
type wordInfo struct {
463
        WordOffset         int
464
        Word               string
465
        MatchCommonInitial bool
466
        HasCommonInitial   bool
467
}
468

469
// This function is based on the following code.
470
// https://github.com/golang/lint/blob/06c8688daad7faa9da5a0c2f163a3d14aac986ca/lint.go#L679
471
func wordWalker(str string, f func(*wordInfo)) {
2,507✔
472
        runes := []rune(strings.TrimFunc(str, isDelimiter))
2,507✔
473
        w, i, wo := 0, 0, 0 // index of start of word, scan, word offset
2,507✔
474
        hasCommonInitial := false
2,507✔
475
        for i+1 <= len(runes) {
23,124✔
476
                eow := false // whether we hit the end of a word
20,617✔
477
                switch {
20,617✔
478
                case i+1 == len(runes):
2,504✔
479
                        eow = true
2,504✔
480
                case isDelimiter(runes[i+1]):
112✔
481
                        // underscore; shift the remainder forward over any run of underscores
112✔
482
                        eow = true
112✔
483
                        n := 1
112✔
484
                        for i+n+1 < len(runes) && isDelimiter(runes[i+n+1]) {
116✔
485
                                n++
4✔
486
                        }
4✔
487

488
                        // Leave at most one underscore if the underscore is between two digits
489
                        if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) {
112✔
490
                                n--
×
491
                        }
×
492

493
                        copy(runes[i+1:], runes[i+n+1:])
112✔
494
                        runes = runes[:len(runes)-n]
112✔
495
                case unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]):
1,470✔
496
                        // lower->non-lower
1,470✔
497
                        eow = true
1,470✔
498
                }
499
                i++
20,617✔
500

20,617✔
501
                initialisms := GetInitialisms()
20,617✔
502
                // [w,i) is a word.
20,617✔
503
                word := string(runes[w:i])
20,617✔
504
                if !eow && initialisms[word] && !unicode.IsLower(runes[i]) {
20,640✔
505
                        // through
23✔
506
                        // split IDFoo → ID, Foo
23✔
507
                        // but URLs → URLs
23✔
508
                } else if !eow {
37,125✔
509
                        if initialisms[word] {
16,514✔
510
                                hasCommonInitial = true
6✔
511
                        }
6✔
512
                        continue
16,508✔
513
                }
514

515
                matchCommonInitial := false
4,109✔
516
                upperWord := strings.ToUpper(word)
4,109✔
517
                if initialisms[upperWord] {
4,260✔
518
                        // If the uppercase word (string(runes[w:i]) is "ID" or "IP"
151✔
519
                        // AND
151✔
520
                        // the word is the first two characters of the current word
151✔
521
                        // AND
151✔
522
                        // that is not the end of the word
151✔
523
                        // AND
151✔
524
                        // the length of the remaining string is greater than 3
151✔
525
                        // AND
151✔
526
                        // the third rune is an uppercase one
151✔
527
                        // THEN
151✔
528
                        // do NOT count this as an initialism.
151✔
529
                        switch upperWord {
151✔
530
                        case "ID", "IP":
101✔
531
                                if remainingRunes := runes[w:]; word == string(remainingRunes[:2]) && !eow && len(remainingRunes) > 3 && unicode.IsUpper(remainingRunes[3]) {
109✔
532
                                        continue
8✔
533
                                }
534
                        }
535
                        hasCommonInitial = true
143✔
536
                        matchCommonInitial = true
143✔
537
                }
538

539
                f(&wordInfo{
4,101✔
540
                        WordOffset:         wo,
4,101✔
541
                        Word:               word,
4,101✔
542
                        MatchCommonInitial: matchCommonInitial,
4,101✔
543
                        HasCommonInitial:   hasCommonInitial,
4,101✔
544
                })
4,101✔
545
                hasCommonInitial = false
4,101✔
546
                w = i
4,101✔
547
                wo++
4,101✔
548
        }
549
}
550

551
var keywords = []string{
552
        "break",
553
        "default",
554
        "func",
555
        "interface",
556
        "select",
557
        "case",
558
        "defer",
559
        "go",
560
        "map",
561
        "struct",
562
        "chan",
563
        "else",
564
        "goto",
565
        "package",
566
        "switch",
567
        "const",
568
        "fallthrough",
569
        "if",
570
        "range",
571
        "type",
572
        "continue",
573
        "for",
574
        "import",
575
        "return",
576
        "var",
577
        "_",
578
}
579

580
// sanitizeKeywords prevents collisions with go keywords for arguments to resolver functions
581
func sanitizeKeywords(name string) string {
355✔
582
        for _, k := range keywords {
9,260✔
583
                if name == k {
8,947✔
584
                        return name + "Arg"
42✔
585
                }
42✔
586
        }
587
        return name
313✔
588
}
589

590
func rawQuote(s string) string {
2✔
591
        return "`" + strings.ReplaceAll(s, "`", "`+\"`\"+`") + "`"
2✔
592
}
2✔
593

594
func notNil(field string, data any) bool {
6✔
595
        v := reflect.ValueOf(data)
6✔
596

6✔
597
        if v.Kind() == reflect.Ptr {
12✔
598
                v = v.Elem()
6✔
599
        }
6✔
600
        if v.Kind() != reflect.Struct {
6✔
601
                return false
×
602
        }
×
603
        val := v.FieldByName(field)
6✔
604

6✔
605
        return val.IsValid() && !val.IsNil()
6✔
606
}
607

608
func StrSplit(s, sep string) []string {
×
609
        return strings.Split(s, sep)
×
610
}
×
611

612
func Dump(val any) string {
×
613
        switch val := val.(type) {
×
614
        case int:
×
615
                return strconv.Itoa(val)
×
616
        case int64:
×
617
                return strconv.FormatInt(val, 10)
×
618
        case float64:
×
619
                return fmt.Sprintf("%f", val)
×
620
        case string:
×
621
                return strconv.Quote(val)
×
622
        case bool:
×
623
                return strconv.FormatBool(val)
×
624
        case nil:
×
625
                return "nil"
×
626
        case []any:
×
627
                var parts []string
×
628
                for _, part := range val {
×
629
                        parts = append(parts, Dump(part))
×
630
                }
×
631
                return "[]any{" + strings.Join(parts, ",") + "}"
×
632
        case map[string]any:
×
633
                buf := bytes.Buffer{}
×
634
                buf.WriteString("map[string]any{")
×
635
                var keys []string
×
636
                for key := range val {
×
637
                        keys = append(keys, key)
×
638
                }
×
639
                sort.Strings(keys)
×
640

×
641
                for _, key := range keys {
×
642
                        data := val[key]
×
643

×
644
                        buf.WriteString(strconv.Quote(key))
×
645
                        buf.WriteString(":")
×
646
                        buf.WriteString(Dump(data))
×
647
                        buf.WriteString(",")
×
648
                }
×
649
                buf.WriteString("}")
×
650
                return buf.String()
×
651
        default:
×
652
                panic(fmt.Errorf("unsupported type %T", val))
×
653
        }
654
}
655

656
func prefixLines(prefix, s string) string {
39✔
657
        return prefix + strings.ReplaceAll(s, "\n", "\n"+prefix)
39✔
658
}
39✔
659

660
func resolveName(name string, skip int) string {
×
661
        if name[0] == '.' {
×
662
                // load path relative to calling source file
×
663
                _, callerFile, _, _ := runtime.Caller(skip + 1)
×
664
                return filepath.Join(filepath.Dir(callerFile), name[1:])
×
665
        }
×
666

667
        // load path relative to this directory
668
        _, callerFile, _, _ := runtime.Caller(0)
×
669
        return filepath.Join(filepath.Dir(callerFile), name)
×
670
}
671

672
func render(filename string, tpldata any) (*bytes.Buffer, error) {
×
673
        t := template.New("").Funcs(Funcs())
×
674

×
675
        b, err := os.ReadFile(filename)
×
676
        if err != nil {
×
677
                return nil, err
×
678
        }
×
679

680
        t, err = t.New(filepath.Base(filename)).Parse(string(b))
×
681
        if err != nil {
×
682
                panic(err)
×
683
        }
684

685
        buf := &bytes.Buffer{}
×
686
        return buf, t.Execute(buf, tpldata)
×
687
}
688

689
func write(filename string, b []byte, packages *code.Packages) error {
35✔
690
        err := os.MkdirAll(filepath.Dir(filename), 0o755)
35✔
691
        if err != nil {
35✔
692
                return fmt.Errorf("failed to create directory: %w", err)
×
693
        }
×
694

695
        formatted, err := imports.Prune(filename, b, packages)
35✔
696
        if err != nil {
37✔
697
                fmt.Fprintf(os.Stderr, "gofmt failed on %s: %s\n", filepath.Base(filename), err.Error())
2✔
698
                formatted = b
2✔
699
        }
2✔
700

701
        err = os.WriteFile(filename, formatted, 0o644)
35✔
702
        if err != nil {
35✔
703
                return fmt.Errorf("failed to write %s: %w", filename, err)
×
704
        }
×
705

706
        return nil
35✔
707
}
708

709
var pkgReplacer = strings.NewReplacer(
710
        "/", "ᚋ",
711
        ".", "ᚗ",
712
        "-", "ᚑ",
713
        "~", "א",
714
)
715

716
func TypeIdentifier(t types.Type) string {
1,311✔
717
        res := ""
1,311✔
718
        for {
3,352✔
719
                switch it := code.Unalias(t).(type) {
2,041✔
720
                case *types.Pointer:
502✔
721
                        t.Underlying()
502✔
722
                        res += "ᚖ"
502✔
723
                        t = it.Elem()
502✔
724
                case *types.Slice:
228✔
725
                        res += "ᚕ"
228✔
726
                        t = it.Elem()
228✔
727
                case *types.Named:
544✔
728
                        res += pkgReplacer.Replace(it.Obj().Pkg().Path())
544✔
729
                        res += "ᚐ"
544✔
730
                        res += it.Obj().Name()
544✔
731
                        return res
544✔
732
                case *types.Basic:
763✔
733
                        res += it.Name()
763✔
734
                        return res
763✔
735
                case *types.Map:
4✔
736
                        res += "map"
4✔
737
                        return res
4✔
738
                case *types.Interface:
×
739
                        res += "interface"
×
740
                        return res
×
741
                default:
×
742
                        panic(fmt.Errorf("unexpected type %T", it))
×
743
                }
744
        }
745
}
746

747
// CommonInitialisms is a set of common initialisms.
748
// Only add entries that are highly unlikely to be non-initialisms.
749
// For instance, "ID" is fine (Freudian code is rare), but "AND" is not.
750
var CommonInitialisms = map[string]bool{
751
        "ACL":   true,
752
        "API":   true,
753
        "ASCII": true,
754
        "CPU":   true,
755
        "CSS":   true,
756
        "CSV":   true,
757
        "DNS":   true,
758
        "EOF":   true,
759
        "GUID":  true,
760
        "HTML":  true,
761
        "HTTP":  true,
762
        "HTTPS": true,
763
        "ICMP":  true,
764
        "ID":    true,
765
        "IP":    true,
766
        "JSON":  true,
767
        "KVK":   true,
768
        "LHS":   true,
769
        "PDF":   true,
770
        "PGP":   true,
771
        "QPS":   true,
772
        "QR":    true,
773
        "RAM":   true,
774
        "RHS":   true,
775
        "RPC":   true,
776
        "SLA":   true,
777
        "SMTP":  true,
778
        "SQL":   true,
779
        "SSH":   true,
780
        "SVG":   true,
781
        "TCP":   true,
782
        "TLS":   true,
783
        "TTL":   true,
784
        "UDP":   true,
785
        "UI":    true,
786
        "UID":   true,
787
        "URI":   true,
788
        "URL":   true,
789
        "UTF8":  true,
790
        "UUID":  true,
791
        "VM":    true,
792
        "XML":   true,
793
        "XMPP":  true,
794
        "XSRF":  true,
795
        "XSS":   true,
796
        "AWS":   true,
797
        "GCP":   true,
798
}
799

800
// GetInitialisms returns the initialisms to capitalize in Go names. If unchanged, default initialisms will be returned
801
var GetInitialisms = func() map[string]bool {
1,545✔
802
        return CommonInitialisms
1,545✔
803
}
1,545✔
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