diff options
author | Daniel Baumann <daniel@debian.org> | 2024-10-18 20:33:49 +0200 |
---|---|---|
committer | Daniel Baumann <daniel@debian.org> | 2024-12-12 23:57:56 +0100 |
commit | e68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch) | |
tree | 97775d6c13b0f416af55314eb6a89ef792474615 /build/codeformat | |
parent | Initial commit. (diff) | |
download | forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip |
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rw-r--r-- | build/codeformat/formatimports.go | 195 | ||||
-rw-r--r-- | build/codeformat/formatimports_test.go | 125 |
2 files changed, 320 insertions, 0 deletions
diff --git a/build/codeformat/formatimports.go b/build/codeformat/formatimports.go new file mode 100644 index 0000000..c9fc2a2 --- /dev/null +++ b/build/codeformat/formatimports.go @@ -0,0 +1,195 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package codeformat + +import ( + "bytes" + "errors" + "io" + "os" + "sort" + "strings" +) + +var importPackageGroupOrders = map[string]int{ + "": 1, // internal + "code.gitea.io/gitea/": 2, +} + +var errInvalidCommentBetweenImports = errors.New("comments between imported packages are invalid, please move comments to the end of the package line") + +var ( + importBlockBegin = []byte("\nimport (\n") + importBlockEnd = []byte("\n)") +) + +type importLineParsed struct { + group string + pkg string + content string +} + +func parseImportLine(line string) (*importLineParsed, error) { + il := &importLineParsed{content: line} + p1 := strings.IndexRune(line, '"') + if p1 == -1 { + return nil, errors.New("invalid import line: " + line) + } + p1++ + p := strings.IndexRune(line[p1:], '"') + if p == -1 { + return nil, errors.New("invalid import line: " + line) + } + p2 := p1 + p + il.pkg = line[p1:p2] + + pDot := strings.IndexRune(il.pkg, '.') + pSlash := strings.IndexRune(il.pkg, '/') + if pDot != -1 && pDot < pSlash { + il.group = "domain-package" + } + for groupName := range importPackageGroupOrders { + if groupName == "" { + continue // skip internal + } + if strings.HasPrefix(il.pkg, groupName) { + il.group = groupName + } + } + return il, nil +} + +type ( + importLineGroup []*importLineParsed + importLineGroupMap map[string]importLineGroup +) + +func formatGoImports(contentBytes []byte) ([]byte, error) { + p1 := bytes.Index(contentBytes, importBlockBegin) + if p1 == -1 { + return nil, nil + } + p1 += len(importBlockBegin) + p := bytes.Index(contentBytes[p1:], importBlockEnd) + if p == -1 { + return nil, nil + } + p2 := p1 + p + + importGroups := importLineGroupMap{} + r := bytes.NewBuffer(contentBytes[p1:p2]) + eof := false + for !eof { + line, err := r.ReadString('\n') + eof = err == io.EOF + if err != nil && !eof { + return nil, err + } + line = strings.TrimSpace(line) + if line != "" { + if strings.HasPrefix(line, "//") || strings.HasPrefix(line, "/*") { + return nil, errInvalidCommentBetweenImports + } + importLine, err := parseImportLine(line) + if err != nil { + return nil, err + } + importGroups[importLine.group] = append(importGroups[importLine.group], importLine) + } + } + + var groupNames []string + for groupName, importLines := range importGroups { + groupNames = append(groupNames, groupName) + sort.Slice(importLines, func(i, j int) bool { + return strings.Compare(importLines[i].pkg, importLines[j].pkg) < 0 + }) + } + + sort.Slice(groupNames, func(i, j int) bool { + n1 := groupNames[i] + n2 := groupNames[j] + o1 := importPackageGroupOrders[n1] + o2 := importPackageGroupOrders[n2] + if o1 != 0 && o2 != 0 { + return o1 < o2 + } + if o1 == 0 && o2 == 0 { + return strings.Compare(n1, n2) < 0 + } + return o1 != 0 + }) + + formattedBlock := bytes.Buffer{} + for _, groupName := range groupNames { + hasNormalImports := false + hasDummyImports := false + // non-dummy import comes first + for _, importLine := range importGroups[groupName] { + if strings.HasPrefix(importLine.content, "_") { + hasDummyImports = true + } else { + formattedBlock.WriteString("\t" + importLine.content + "\n") + hasNormalImports = true + } + } + // dummy (_ "pkg") comes later + if hasDummyImports { + if hasNormalImports { + formattedBlock.WriteString("\n") + } + for _, importLine := range importGroups[groupName] { + if strings.HasPrefix(importLine.content, "_") { + formattedBlock.WriteString("\t" + importLine.content + "\n") + } + } + } + formattedBlock.WriteString("\n") + } + formattedBlockBytes := bytes.TrimRight(formattedBlock.Bytes(), "\n") + + var formattedBytes []byte + formattedBytes = append(formattedBytes, contentBytes[:p1]...) + formattedBytes = append(formattedBytes, formattedBlockBytes...) + formattedBytes = append(formattedBytes, contentBytes[p2:]...) + return formattedBytes, nil +} + +// FormatGoImports format the imports by our rules (see unit tests) +func FormatGoImports(file string, doWriteFile bool) error { + f, err := os.Open(file) + if err != nil { + return err + } + var contentBytes []byte + { + defer f.Close() + contentBytes, err = io.ReadAll(f) + if err != nil { + return err + } + } + formattedBytes, err := formatGoImports(contentBytes) + if err != nil { + return err + } + if formattedBytes == nil { + return nil + } + if bytes.Equal(contentBytes, formattedBytes) { + return nil + } + + if doWriteFile { + f, err = os.OpenFile(file, os.O_TRUNC|os.O_WRONLY, 0o644) + if err != nil { + return err + } + defer f.Close() + _, err = f.Write(formattedBytes) + return err + } + + return err +} diff --git a/build/codeformat/formatimports_test.go b/build/codeformat/formatimports_test.go new file mode 100644 index 0000000..1abc9f8 --- /dev/null +++ b/build/codeformat/formatimports_test.go @@ -0,0 +1,125 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package codeformat + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFormatImportsSimple(t *testing.T) { + formatted, err := formatGoImports([]byte(` +package codeformat + +import ( + "github.com/stretchr/testify/assert" + "testing" +) +`)) + + expected := ` +package codeformat + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) +` + + require.NoError(t, err) + assert.Equal(t, expected, string(formatted)) +} + +func TestFormatImportsGroup(t *testing.T) { + // gofmt/goimports won't group the packages, for example, they produce such code: + // "bytes" + // "image" + // (a blank line) + // "fmt" + // "image/color/palette" + // our formatter does better, and these packages are grouped into one. + + formatted, err := formatGoImports([]byte(` +package test + +import ( + "bytes" + "fmt" + "image" + "image/color" + + _ "image/gif" // for processing gif images + _ "image/jpeg" // for processing jpeg images + _ "image/png" // for processing png images + + "code.gitea.io/other/package" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + + "xorm.io/the/package" + + "github.com/issue9/identicon" + "github.com/nfnt/resize" + "github.com/oliamb/cutter" +) +`)) + + expected := ` +package test + +import ( + "bytes" + "fmt" + "image" + "image/color" + + _ "image/gif" // for processing gif images + _ "image/jpeg" // for processing jpeg images + _ "image/png" // for processing png images + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + + "code.gitea.io/other/package" + "github.com/issue9/identicon" + "github.com/nfnt/resize" + "github.com/oliamb/cutter" + "xorm.io/the/package" +) +` + + require.NoError(t, err) + assert.Equal(t, expected, string(formatted)) +} + +func TestFormatImportsInvalidComment(t *testing.T) { + // why we shouldn't write comments between imports: it breaks the grouping of imports + // for example: + // "pkg1" + // "pkg2" + // // a comment + // "pkgA" + // "pkgB" + // the comment splits the packages into two groups, pkg1/2 are sorted separately, pkgA/B are sorted separately + // we don't want such code, so the code should be: + // "pkg1" + // "pkg2" + // "pkgA" // a comment + // "pkgB" + + _, err := formatGoImports([]byte(` +package test + +import ( + "image/jpeg" + // for processing gif images + "image/gif" +) +`)) + require.ErrorIs(t, err, errInvalidCommentBetweenImports) +} |