mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-03-05 03:21:10 +00:00
132 lines
3.9 KiB
Go
132 lines
3.9 KiB
Go
|
package analyzer
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"go/token"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/daixiang0/gci/pkg/configuration"
|
||
|
"github.com/daixiang0/gci/pkg/gci"
|
||
|
"github.com/daixiang0/gci/pkg/io"
|
||
|
|
||
|
"golang.org/x/tools/go/analysis"
|
||
|
"golang.org/x/tools/go/analysis/passes/inspect"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
NoInlineCommentsFlag = "noInlineComments"
|
||
|
NoPrefixCommentsFlag = "noPrefixComments"
|
||
|
SectionsFlag = "Sections"
|
||
|
SectionSeparatorsFlag = "SectionSeparators"
|
||
|
SectionDelimiter = ";"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
noInlineComments bool
|
||
|
noPrefixComments bool
|
||
|
sectionsStr string
|
||
|
sectionSeparatorsStr string
|
||
|
)
|
||
|
|
||
|
func init() {
|
||
|
Analyzer.Flags.BoolVar(&noInlineComments, NoInlineCommentsFlag, false, "If comments in the same line as the input should be present")
|
||
|
Analyzer.Flags.BoolVar(&noPrefixComments, NoPrefixCommentsFlag, false, "If comments above an input should be present")
|
||
|
Analyzer.Flags.StringVar(§ionsStr, SectionsFlag, "", "Specify the Sections format that should be used to check the file formatting")
|
||
|
Analyzer.Flags.StringVar(§ionSeparatorsStr, SectionSeparatorsFlag, "", "Specify the Sections that are inserted as Separators between Sections")
|
||
|
}
|
||
|
|
||
|
var Analyzer = &analysis.Analyzer{
|
||
|
Name: "gci",
|
||
|
Doc: "A tool that control golang package import order and make it always deterministic.",
|
||
|
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||
|
Run: runAnalysis,
|
||
|
}
|
||
|
|
||
|
func runAnalysis(pass *analysis.Pass) (interface{}, error) {
|
||
|
// TODO input validation
|
||
|
|
||
|
var fileReferences []*token.File
|
||
|
// extract file references for all files in the analyzer pass
|
||
|
for _, pkgFile := range pass.Files {
|
||
|
fileForPos := pass.Fset.File(pkgFile.Package)
|
||
|
if fileForPos != nil {
|
||
|
fileReferences = append(fileReferences, fileForPos)
|
||
|
}
|
||
|
}
|
||
|
expectedNumFiles := len(pass.Files)
|
||
|
foundNumFiles := len(fileReferences)
|
||
|
if expectedNumFiles != foundNumFiles {
|
||
|
return nil, InvalidNumberOfFilesInAnalysis{expectedNumFiles, foundNumFiles}
|
||
|
}
|
||
|
|
||
|
// read configuration options
|
||
|
gciCfg, err := parseGciConfiguration()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
for _, file := range fileReferences {
|
||
|
filePath := file.Name()
|
||
|
unmodifiedFile, formattedFile, err := gci.LoadFormatGoFile(io.File{filePath}, *gciCfg)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
// search for a difference
|
||
|
fileRunes := bytes.Runes(unmodifiedFile)
|
||
|
formattedRunes := bytes.Runes(formattedFile)
|
||
|
diffIdx := compareRunes(fileRunes, formattedRunes)
|
||
|
switch diffIdx {
|
||
|
case -1:
|
||
|
// no difference
|
||
|
default:
|
||
|
diffPos := file.Position(file.Pos(diffIdx))
|
||
|
// prevent invalid access to array
|
||
|
fileRune := "nil"
|
||
|
formattedRune := "nil"
|
||
|
if len(fileRunes)-1 >= diffIdx {
|
||
|
fileRune = fmt.Sprintf("%q", fileRunes[diffIdx])
|
||
|
}
|
||
|
if len(formattedRunes)-1 >= diffIdx {
|
||
|
formattedRune = fmt.Sprintf("%q", formattedRunes[diffIdx])
|
||
|
}
|
||
|
pass.Reportf(file.Pos(diffIdx), "Expected %s, Found %s at %s[line %d,col %d]", formattedRune, fileRune, filePath, diffPos.Line, diffPos.Column)
|
||
|
}
|
||
|
}
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
func compareRunes(a, b []rune) (differencePos int) {
|
||
|
// check shorter rune slice first to prevent invalid array access
|
||
|
shorterRune := a
|
||
|
if len(b) < len(a) {
|
||
|
shorterRune = b
|
||
|
}
|
||
|
// check for differences up to where the length is identical
|
||
|
for idx := 0; idx < len(shorterRune); idx++ {
|
||
|
if a[idx] != b[idx] {
|
||
|
return idx
|
||
|
}
|
||
|
}
|
||
|
// check that we have compared two equally long rune arrays
|
||
|
if len(a) != len(b) {
|
||
|
return len(shorterRune) + 1
|
||
|
}
|
||
|
return -1
|
||
|
}
|
||
|
|
||
|
func parseGciConfiguration() (*gci.GciConfiguration, error) {
|
||
|
fmtCfg := configuration.FormatterConfiguration{noInlineComments, noPrefixComments, false}
|
||
|
|
||
|
var sectionStrings []string
|
||
|
if sectionsStr != "" {
|
||
|
sectionStrings = strings.Split(sectionsStr, SectionDelimiter)
|
||
|
}
|
||
|
|
||
|
var sectionSeparatorStrings []string
|
||
|
if sectionSeparatorsStr != "" {
|
||
|
sectionSeparatorStrings = strings.Split(sectionSeparatorsStr, SectionDelimiter)
|
||
|
}
|
||
|
return gci.GciStringConfiguration{fmtCfg, sectionStrings, sectionSeparatorStrings}.Parse()
|
||
|
}
|