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  }