1// Copyright (c) 2020 Denis Tingajkin 2// 3// SPDX-License-Identifier: Apache-2.0 4// 5// Licensed under the Apache License, Version 2.0 (the "License"); 6// you may not use this file except in compliance with the License. 7// You may obtain a copy of the License at: 8// 9// http://www.apache.org/licenses/LICENSE-2.0 10// 11// Unless required by applicable law or agreed to in writing, software 12// distributed under the License is distributed on an "AS IS" BASIS, 13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14// See the License for the specific language governing permissions and 15// limitations under the License. 16 17package goheader 18 19import ( 20 "fmt" 21 "go/ast" 22 "os" 23 "os/exec" 24 "strings" 25 "time" 26) 27 28type Target struct { 29 Path string 30 File *ast.File 31} 32 33const iso = "2006-01-02 15:04:05 -0700" 34 35func (t *Target) ModTime() (time.Time, error) { 36 diff, err := exec.Command("git", "diff", t.Path).CombinedOutput() 37 if err == nil && len(diff) == 0 { 38 line, err := exec.Command("git", "log", "-1", "--pretty=format:%cd", "--date=iso", "--", t.Path).CombinedOutput() 39 if err == nil { 40 return time.Parse(iso, string(line)) 41 } 42 } 43 info, err := os.Stat(t.Path) 44 if err != nil { 45 return time.Time{}, err 46 } 47 return info.ModTime(), nil 48} 49 50type Analyzer struct { 51 values map[string]Value 52 template string 53} 54 55func (a *Analyzer) Analyze(target *Target) Issue { 56 if a.template == "" { 57 return NewIssue("Missed template for check") 58 } 59 if t, err := target.ModTime(); err == nil { 60 if t.Year() != time.Now().Year() { 61 return nil 62 } 63 } 64 file := target.File 65 var header string 66 var offset = Location{ 67 Position: 1, 68 } 69 if len(file.Comments) > 0 && file.Comments[0].Pos() < file.Package { 70 if strings.HasPrefix(file.Comments[0].List[0].Text, "/*") { 71 header = (&ast.CommentGroup{List: []*ast.Comment{file.Comments[0].List[0]}}).Text() 72 } else { 73 header = file.Comments[0].Text() 74 offset.Position += 3 75 } 76 } 77 header = strings.TrimSpace(header) 78 if header == "" { 79 return NewIssue("Missed header for check") 80 } 81 s := NewReader(header) 82 s.SetOffset(offset) 83 t := NewReader(a.template) 84 for !s.Done() && !t.Done() { 85 templateCh := t.Peek() 86 if templateCh == '{' { 87 name := a.readField(t) 88 if a.values[name] == nil { 89 return NewIssue(fmt.Sprintf("Template has unknown value: %v", name)) 90 } 91 if i := a.values[name].Read(s); i != nil { 92 return i 93 } 94 continue 95 } 96 sourceCh := s.Peek() 97 if sourceCh != templateCh { 98 l := s.Location() 99 notNextLine := func(r rune) bool { 100 return r != '\n' 101 } 102 actual := s.ReadWhile(notNextLine) 103 expected := t.ReadWhile(notNextLine) 104 return NewIssueWithLocation(fmt.Sprintf("Actual: %v\nExpected:%v", actual, expected), l) 105 } 106 s.Next() 107 t.Next() 108 } 109 if !s.Done() { 110 l := s.Location() 111 return NewIssueWithLocation(fmt.Sprintf("Unexpected string: %v", s.Finish()), l) 112 } 113 if !t.Done() { 114 l := s.Location() 115 return NewIssueWithLocation(fmt.Sprintf("Missed string: %v", t.Finish()), l) 116 } 117 return nil 118} 119 120func (a *Analyzer) readField(reader *Reader) string { 121 _ = reader.Next() 122 _ = reader.Next() 123 124 r := reader.ReadWhile(func(r rune) bool { 125 return r != '}' 126 }) 127 128 _ = reader.Next() 129 _ = reader.Next() 130 131 return strings.ToLower(strings.TrimSpace(r)) 132} 133 134func New(options ...Option) *Analyzer { 135 a := &Analyzer{} 136 for _, o := range options { 137 o.apply(a) 138 } 139 for _, v := range a.values { 140 err := v.Calculate(a.values) 141 if err != nil { 142 panic(err.Error()) 143 } 144 } 145 return a 146} 147