/tools/vendor/github.com/tetafro/godot/getters.go
https://github.com/coreos/coreos-assembler · Go · 283 lines · 216 code · 26 blank · 41 comment · 62 complexity · 1749182f761efadcfcaee6ca7ddc03d9 MD5 · raw file
- package godot
- import (
- "errors"
- "fmt"
- "go/ast"
- "go/token"
- "io/ioutil"
- "regexp"
- "strings"
- )
- var (
- errEmptyInput = errors.New("empty input")
- errUnsuitableInput = errors.New("unsuitable input")
- )
- // specialReplacer is a replacer for some types of special lines in comments,
- // which shouldn't be checked. For example, if comment ends with a block of
- // code it should not necessarily have a period at the end.
- const specialReplacer = "<godotSpecialReplacer>"
- type parsedFile struct {
- fset *token.FileSet
- file *ast.File
- lines []string
- }
- func newParsedFile(file *ast.File, fset *token.FileSet) (*parsedFile, error) {
- if file == nil || fset == nil || len(file.Comments) == 0 {
- return nil, errEmptyInput
- }
- pf := parsedFile{
- fset: fset,
- file: file,
- }
- var err error
- // Read original file. This is necessary for making a replacements for
- // inline comments. I couldn't find a better way to get original line
- // with code and comment without reading the file. Function `Format`
- // from "go/format" won't help here if the original file is not gofmt-ed.
- pf.lines, err = readFile(file, fset)
- if err != nil {
- return nil, fmt.Errorf("read file: %v", err)
- }
- // Dirty hack. For some cases Go generates temporary files during
- // compilation process if there is a cgo block in the source file. Some of
- // these temporary files are just copies of original source files but with
- // new generated comments at the top. Because of them the content differs
- // from AST. For some reason it differs only in golangci-lint. I failed to
- // find out the exact description of the process, so let's just skip files
- // generated by cgo.
- if isCgoGenerated(pf.lines) {
- return nil, errUnsuitableInput
- }
- // Check consistency to avoid checking slice indexes in each function
- lastComment := pf.file.Comments[len(pf.file.Comments)-1]
- if p := pf.fset.Position(lastComment.End()); len(pf.lines) < p.Line {
- return nil, fmt.Errorf("inconsistency between file and AST: %s", p.Filename)
- }
- return &pf, nil
- }
- // getComments extracts comments from a file.
- func (pf *parsedFile) getComments(scope Scope, exclude []*regexp.Regexp) []comment {
- var comments []comment
- decl := pf.getDeclarationComments(exclude)
- switch scope {
- case AllScope:
- // All comments
- comments = pf.getAllComments(exclude)
- case TopLevelScope:
- // All top level comments and comments from the inside
- // of top level blocks
- comments = append(
- pf.getBlockComments(exclude),
- pf.getTopLevelComments(exclude)...,
- )
- default:
- // Top level declaration comments and comments from the inside
- // of top level blocks
- comments = append(pf.getBlockComments(exclude), decl...)
- }
- // Set `decl` flag
- setDecl(comments, decl)
- return comments
- }
- // getBlockComments gets comments from the inside of top level blocks:
- // var (...), const (...).
- func (pf *parsedFile) getBlockComments(exclude []*regexp.Regexp) []comment {
- var comments []comment
- for _, decl := range pf.file.Decls {
- d, ok := decl.(*ast.GenDecl)
- if !ok {
- continue
- }
- // No parenthesis == no block
- if d.Lparen == 0 {
- continue
- }
- for _, c := range pf.file.Comments {
- if c == nil || len(c.List) == 0 {
- continue
- }
- // Skip comments outside this block
- if d.Lparen > c.Pos() || c.Pos() > d.Rparen {
- continue
- }
- // Skip comments that are not top-level for this block
- // (the block itself is top level, so comments inside this block
- // would be on column 2)
- // nolint: gomnd
- if pf.fset.Position(c.Pos()).Column != 2 {
- continue
- }
- firstLine := pf.fset.Position(c.Pos()).Line
- lastLine := pf.fset.Position(c.End()).Line
- comments = append(comments, comment{
- lines: pf.lines[firstLine-1 : lastLine],
- text: getText(c, exclude),
- start: pf.fset.Position(c.List[0].Slash),
- })
- }
- }
- return comments
- }
- // getTopLevelComments gets all top level comments.
- func (pf *parsedFile) getTopLevelComments(exclude []*regexp.Regexp) []comment {
- var comments []comment // nolint: prealloc
- for _, c := range pf.file.Comments {
- if c == nil || len(c.List) == 0 {
- continue
- }
- if pf.fset.Position(c.Pos()).Column != 1 {
- continue
- }
- firstLine := pf.fset.Position(c.Pos()).Line
- lastLine := pf.fset.Position(c.End()).Line
- comments = append(comments, comment{
- lines: pf.lines[firstLine-1 : lastLine],
- text: getText(c, exclude),
- start: pf.fset.Position(c.List[0].Slash),
- })
- }
- return comments
- }
- // getDeclarationComments gets top level declaration comments.
- func (pf *parsedFile) getDeclarationComments(exclude []*regexp.Regexp) []comment {
- var comments []comment // nolint: prealloc
- for _, decl := range pf.file.Decls {
- var cg *ast.CommentGroup
- switch d := decl.(type) {
- case *ast.GenDecl:
- cg = d.Doc
- case *ast.FuncDecl:
- cg = d.Doc
- }
- if cg == nil || len(cg.List) == 0 {
- continue
- }
- firstLine := pf.fset.Position(cg.Pos()).Line
- lastLine := pf.fset.Position(cg.End()).Line
- comments = append(comments, comment{
- lines: pf.lines[firstLine-1 : lastLine],
- text: getText(cg, exclude),
- start: pf.fset.Position(cg.List[0].Slash),
- })
- }
- return comments
- }
- // getAllComments gets every single comment from the file.
- func (pf *parsedFile) getAllComments(exclude []*regexp.Regexp) []comment {
- var comments []comment //nolint: prealloc
- for _, c := range pf.file.Comments {
- if c == nil || len(c.List) == 0 {
- continue
- }
- firstLine := pf.fset.Position(c.Pos()).Line
- lastLine := pf.fset.Position(c.End()).Line
- comments = append(comments, comment{
- lines: pf.lines[firstLine-1 : lastLine],
- start: pf.fset.Position(c.List[0].Slash),
- text: getText(c, exclude),
- })
- }
- return comments
- }
- // getText extracts text from comment. If comment is a special block
- // (e.g., CGO code), a block of empty lines is returned. If comment contains
- // special lines (e.g., tags or indented code examples), they are replaced
- // with `specialReplacer` to skip checks for it.
- // The result can be multiline.
- func getText(comment *ast.CommentGroup, exclude []*regexp.Regexp) (s string) {
- if len(comment.List) == 1 &&
- strings.HasPrefix(comment.List[0].Text, "/*") &&
- isSpecialBlock(comment.List[0].Text) {
- return ""
- }
- for _, c := range comment.List {
- text := c.Text
- isBlock := false
- if strings.HasPrefix(c.Text, "/*") {
- isBlock = true
- text = strings.TrimPrefix(text, "/*")
- text = strings.TrimSuffix(text, "*/")
- }
- for _, line := range strings.Split(text, "\n") {
- if isSpecialLine(line) {
- s += specialReplacer + "\n"
- continue
- }
- if !isBlock {
- line = strings.TrimPrefix(line, "//")
- }
- if matchAny(line, exclude) {
- s += specialReplacer + "\n"
- continue
- }
- s += line + "\n"
- }
- }
- if len(s) == 0 {
- return ""
- }
- return s[:len(s)-1] // trim last "\n"
- }
- // readFile reads file and returns it's lines as strings.
- func readFile(file *ast.File, fset *token.FileSet) ([]string, error) {
- fname := fset.File(file.Package)
- f, err := ioutil.ReadFile(fname.Name())
- if err != nil {
- return nil, err
- }
- return strings.Split(string(f), "\n"), nil
- }
- // setDecl sets `decl` flag to comments which are declaration comments.
- func setDecl(comments, decl []comment) {
- for _, d := range decl {
- for i, c := range comments {
- if d.start == c.start {
- comments[i].decl = true
- break
- }
- }
- }
- }
- // matchAny checks if string matches any of given regexps.
- func matchAny(s string, rr []*regexp.Regexp) bool {
- for _, re := range rr {
- if re.MatchString(s) {
- return true
- }
- }
- return false
- }
- func isCgoGenerated(lines []string) bool {
- for i := range lines {
- if strings.Contains(lines[i], "Code generated by cmd/cgo") {
- return true
- }
- }
- return false
- }