github.com/serversong/goreporter@v0.0.0-20200325104552-3cfaf44fd178/linters/countcode/countcode.go (about) 1 package countcode 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path" 9 "strings" 10 11 "github.com/360EntSecGroup-Skylar/goreporter/utils" 12 ) 13 14 var languages = []Language{ 15 Language{"Go", mExt(".go"), cComments}, 16 } 17 18 type Commenter struct { 19 LineComment string 20 StartComment string 21 EndComment string 22 Nesting bool 23 } 24 25 var ( 26 noComments = Commenter{"\000", "\000", "\000", false} 27 cComments = Commenter{`//`, `/*`, `*/`, false} 28 ) 29 30 type Language struct { 31 Namer 32 Matcher 33 Commenter 34 } 35 36 // TODO work properly with unicode 37 func (l Language) Update(c []byte, s *Stats) { 38 s.FileCount++ 39 40 inComment := 0 // this is an int for nesting 41 inLComment := false 42 blank := true 43 lc := []byte(l.LineComment) 44 sc := []byte(l.StartComment) 45 ec := []byte(l.EndComment) 46 lp, sp, ep := 0, 0, 0 47 48 for _, b := range c { 49 if inComment == 0 && b == lc[lp] { 50 lp++ 51 if lp == len(lc) { 52 inLComment = true 53 lp = 0 54 } 55 } else { 56 lp = 0 57 } 58 if !inLComment && b == sc[sp] { 59 sp++ 60 if sp == len(sc) { 61 inComment++ 62 if inComment > 1 && !l.Nesting { 63 inComment = 1 64 } 65 sp = 0 66 } 67 } else { 68 sp = 0 69 } 70 if !inLComment && inComment > 0 && b == ec[ep] { 71 ep++ 72 if ep == len(ec) { 73 if inComment > 0 { 74 inComment-- 75 } 76 ep = 0 77 } 78 } else { 79 ep = 0 80 } 81 82 if b != byte(' ') && b != byte('\t') && b != byte('\n') && b != byte('\r') { 83 blank = false 84 } 85 86 // BUG(srl): lines with comment don't count towards code 87 // Note that lines with both code and comment count towards 88 // each, but are not counted twice in the total. 89 if b == byte('\n') { 90 s.TotalLines++ 91 if inComment > 0 || inLComment { 92 inLComment = false 93 s.CommentLines++ 94 } else if blank { 95 s.BlankLines++ 96 } else { 97 s.CodeLines++ 98 } 99 blank = true 100 continue 101 } 102 } 103 } 104 105 type Namer string 106 107 func (l Namer) Name() string { return string(l) } 108 109 type Matcher func(string) bool 110 111 func (m Matcher) Match(fname string) bool { return m(fname) } 112 113 func mExt(exts ...string) Matcher { 114 return func(fname string) bool { 115 for _, ext := range exts { 116 if ext == path.Ext(fname) { 117 return true 118 } 119 } 120 return false 121 } 122 } 123 124 func mName(names ...string) Matcher { 125 return func(fname string) bool { 126 for _, name := range names { 127 if name == path.Base(fname) { 128 return true 129 } 130 } 131 return false 132 } 133 } 134 135 type Stats struct { 136 FileCount int 137 TotalLines int 138 CodeLines int 139 BlankLines int 140 CommentLines int 141 } 142 143 var info = map[string]*Stats{} 144 145 func handleFile(fname string) { 146 var l Language 147 ok := false 148 for _, lang := range languages { 149 if lang.Match(fname) { 150 ok = true 151 l = lang 152 break 153 } 154 } 155 if !ok { 156 return // ignore this file 157 } 158 i, ok := info[l.Name()] 159 if !ok { 160 i = &Stats{} 161 info[l.Name()] = i 162 } 163 c, err := ioutil.ReadFile(fname) 164 if err != nil { 165 fmt.Fprintf(os.Stderr, " ! %s\n", fname) 166 return 167 } 168 l.Update(c, i) 169 } 170 171 var files []string 172 var excepts = make([]string, 0) 173 174 func add(n string) { 175 fi, err := os.Stat(n) 176 if err != nil { 177 goto invalid 178 } 179 if fi.IsDir() { 180 fs, err := ioutil.ReadDir(n) 181 if err != nil { 182 goto invalid 183 } 184 185 for _, f := range fs { 186 if exceptPkg(path.Join(n, f.Name())) { 187 continue 188 } 189 if f.Name()[0] != '.' { 190 add(path.Join(n, f.Name())) 191 } 192 } 193 return 194 } 195 if fi.Mode()&os.ModeType == 0 { 196 files = append(files, n) 197 return 198 } 199 200 println(fi.Mode()) 201 202 invalid: 203 fmt.Fprintf(os.Stderr, " ! %s\n", n) 204 } 205 206 type LData []LResult 207 208 func (d LData) Len() int { return len(d) } 209 210 func (d LData) Less(i, j int) bool { 211 if d[i].CodeLines == d[j].CodeLines { 212 return d[i].Name > d[j].Name 213 } 214 return d[i].CodeLines > d[j].CodeLines 215 } 216 217 func (d LData) Swap(i, j int) { 218 d[i], d[j] = d[j], d[i] 219 } 220 221 type LResult struct { 222 Name string 223 FileCount int 224 CodeLines int 225 CommentLines int 226 BlankLines int 227 TotalLines int 228 } 229 230 func (r *LResult) Add(a LResult) { 231 r.FileCount += a.FileCount 232 r.CodeLines += a.CodeLines 233 r.CommentLines += a.CommentLines 234 r.BlankLines += a.BlankLines 235 r.TotalLines += a.TotalLines 236 } 237 238 func printJSON() { 239 bs, err := json.MarshalIndent(info, "", " ") 240 if err != nil { 241 panic(err) 242 } 243 fmt.Println(string(bs)) 244 } 245 246 func printInfo() (fileCount, codeLines, commentLines, totalLines int) { 247 d := LData([]LResult{}) 248 total := &LResult{} 249 total.Name = "Total" 250 for n, i := range info { 251 r := LResult{ 252 n, 253 i.FileCount, 254 i.CodeLines, 255 i.CommentLines, 256 i.BlankLines, 257 i.TotalLines, 258 } 259 d = append(d, r) 260 total.Add(r) 261 } 262 d = append(d, *total) 263 if len(d) >= 1 { 264 return d[0].FileCount, d[0].CodeLines, d[0].CommentLines, d[0].TotalLines 265 } 266 return 0, 0, 0, 0 267 } 268 269 func CountCode(projectPath, except string) (codeCounts map[string][]int) { 270 codeCounts = make(map[string][]int, 0) 271 272 allFilesPath, err := utils.FileList(projectPath, ".go", except) 273 if err != nil { 274 fmt.Println(err) 275 } 276 277 temp := strings.Split(except, ",") 278 temp = append(temp, "vendor") 279 280 for i := range temp { 281 if !(temp[i] == "") { 282 excepts = append(excepts, temp[i]) 283 } 284 } 285 286 for _, dirPath := range allFilesPath { 287 args := []string{dirPath} 288 files = make([]string, 0) 289 excepts = make([]string, 0) 290 info = make(map[string]*Stats, 0) 291 for _, n := range args { 292 add(n) 293 } 294 for _, f := range files { 295 handleFile(f) 296 } 297 fileCount, codeLines, commentLines, totalLines := printInfo() 298 codeCounts[dirPath] = append(codeCounts[dirPath], fileCount, codeLines, commentLines, totalLines) 299 } 300 return codeCounts 301 } 302 303 // exceptPkg is a function that will determine whether the package is an exception. 304 func exceptPkg(pkg string) bool { 305 if len(excepts) == 0 { 306 if strings.Contains(pkg, "vendor") { 307 return true 308 } 309 } 310 311 for _, va := range excepts { 312 if strings.Contains(pkg, va) { 313 return true 314 } 315 } 316 return false 317 }