github.com/daixiang0/gci@v0.13.0/pkg/parse/parse.go (about) 1 package parse 2 3 import ( 4 "go/ast" 5 "go/parser" 6 "go/token" 7 "sort" 8 "strings" 9 ) 10 11 const C = "\"C\"" 12 13 type GciImports struct { 14 // original index of import group, include doc, name, path and comment 15 Start, End int 16 Name, Path string 17 } 18 type ImportList []*GciImports 19 20 func (l ImportList) Len() int { 21 return len(l) 22 } 23 24 func (l ImportList) Less(i, j int) bool { 25 if strings.Compare(l[i].Path, l[j].Path) == 0 { 26 return strings.Compare(l[i].Name, l[j].Name) < 0 27 } 28 29 return strings.Compare(l[i].Path, l[j].Path) < 0 30 } 31 32 func (l ImportList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } 33 34 /* 35 * AST considers a import block as below: 36 * ``` 37 * Doc 38 * Name Path Comment 39 * ``` 40 * An example is like below: 41 * ``` 42 * // test 43 * test "fmt" // test 44 * ``` 45 * getImports return a import block with name, start and end index 46 */ 47 func getImports(imp *ast.ImportSpec) (start, end int, name string) { 48 if imp.Doc != nil { 49 // doc poc need minus one to get the first index of comment 50 start = int(imp.Doc.Pos()) - 1 51 } else { 52 if imp.Name != nil { 53 // name pos need minus one too 54 start = int(imp.Name.Pos()) - 1 55 } else { 56 // path pos start without quote, need minus one for it 57 start = int(imp.Path.Pos()) - 1 58 } 59 } 60 61 if imp.Name != nil { 62 name = imp.Name.Name 63 } 64 65 if imp.Comment != nil { 66 end = int(imp.Comment.End()) 67 } else { 68 end = int(imp.Path.End()) 69 } 70 return 71 } 72 73 func ParseFile(src []byte, filename string) (ImportList, int, int, int, int, error) { 74 fileSet := token.NewFileSet() 75 f, err := parser.ParseFile(fileSet, filename, src, parser.ParseComments) 76 if err != nil { 77 return nil, 0, 0, 0, 0, err 78 } 79 80 if len(f.Imports) == 0 { 81 return nil, 0, 0, 0, 0, NoImportError{} 82 } 83 84 var ( 85 // headEnd means the start of import block 86 headEnd int 87 // tailStart means the end + 1 of import block 88 tailStart int 89 // cStart means the start of C import block 90 cStart int 91 // cEnd means the end of C import block 92 cEnd int 93 data ImportList 94 ) 95 96 for index, decl := range f.Decls { 97 switch decl.(type) { 98 // skip BadDecl and FuncDecl 99 case *ast.GenDecl: 100 genDecl := decl.(*ast.GenDecl) 101 102 if genDecl.Tok == token.IMPORT { 103 // there are two cases, both end with linebreak: 104 // 1. 105 // import ( 106 // "xxxx" 107 // ) 108 // 2. 109 // import "xxx" 110 if headEnd == 0 { 111 headEnd = int(decl.Pos()) - 1 112 } 113 tailStart = int(decl.End()) 114 if tailStart > len(src) { 115 tailStart = len(src) 116 } 117 118 for _, spec := range genDecl.Specs { 119 imp := spec.(*ast.ImportSpec) 120 // there are only one C import block 121 // ensure C import block is the first import block 122 if imp.Path.Value == C { 123 /* 124 common case: 125 126 // #include <png.h> 127 import "C" 128 129 notice that decl.Pos() == genDecl.Pos() > genDecl.Doc.Pos() 130 */ 131 if genDecl.Doc != nil { 132 cStart = int(genDecl.Doc.Pos()) - 1 133 // if C import block is the first, update headEnd 134 if index == 0 { 135 headEnd = cStart 136 } 137 } else { 138 /* 139 special case: 140 141 import "C" 142 */ 143 cStart = int(decl.Pos()) - 1 144 } 145 146 cEnd = int(decl.End()) 147 148 continue 149 } 150 151 start, end, name := getImports(imp) 152 153 data = append(data, &GciImports{ 154 Start: start, 155 End: end, 156 Name: name, 157 Path: strings.Trim(imp.Path.Value, `"`), 158 }) 159 } 160 } 161 } 162 } 163 164 sort.Sort(data) 165 return data, headEnd, tailStart, cStart, cEnd, nil 166 } 167 168 // IsGeneratedFileByComment reports whether the source file is generated code. 169 // Using a bit laxer rules than https://golang.org/s/generatedcode to 170 // match more generated code. 171 // Taken from https://github.com/golangci/golangci-lint. 172 func IsGeneratedFileByComment(in string) bool { 173 const ( 174 genCodeGenerated = "code generated" 175 genDoNotEdit = "do not edit" 176 genAutoFile = "autogenerated file" // easyjson 177 genAutoGenerated = "automatically generated" // genny 178 ) 179 180 markers := []string{genCodeGenerated, genDoNotEdit, genAutoFile, genAutoGenerated} 181 in = strings.ToLower(in) 182 for _, marker := range markers { 183 if strings.Contains(in, marker) { 184 return true 185 } 186 } 187 188 return false 189 } 190 191 type NoImportError struct{} 192 193 func (n NoImportError) Error() string { 194 return "No imports" 195 } 196 197 func (i NoImportError) Is(err error) bool { 198 _, ok := err.(NoImportError) 199 return ok 200 }