github.com/serversong/goreporter@v0.0.0-20200325104552-3cfaf44fd178/engine/report2html.go (about) 1 // Copyright 2017 The GoReporter Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package engine 15 16 import ( 17 "bytes" 18 "fmt" 19 "html/template" 20 "io/ioutil" 21 "log" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "runtime" 26 "strconv" 27 "strings" 28 "time" 29 30 "github.com/golang/glog" 31 "github.com/json-iterator/go" 32 33 "github.com/360EntSecGroup-Skylar/goreporter/utils" 34 ) 35 36 var ( 37 issues int 38 ) 39 40 // UnitTest is a struct that contains some features in a report of html. 41 // GoReporter HTML Report Features 42 // 43 // +----------------------------------------------------------------------+ 44 // | Feature | Information | 45 // +=======================+==============================================+ 46 // | Project | The path address of the item being detected | 47 // +-----------------------+----------------------------------------------+ 48 // | Score | The score of the tested project | 49 // +-----------------------+----------------------------------------------+ 50 // | CodeTest | Unit test results | 51 // +-----------------------+----------------------------------------------+ 52 // | IssuesNum | Issues number of the project | 53 // +-----------------------+----------------------------------------------+ 54 // | CodeCount | Number of lines of code | 55 // +-----------------------+----------------------------------------------+ 56 // | CodeStyle | Code style check | 57 // +-----------------------+----------------------------------------------+ 58 // | CodeOptimization | Code optimization | 59 // +-----------------------+----------------------------------------------+ 60 // | CodeSmell | Code smell | 61 // +-----------------------+----------------------------------------------+ 62 // | DepGraph | Depend graph of all packages in the project | 63 // +-----------------------+----------------------------------------------+ 64 // | Date | Date assessment of the project | 65 // +-----------------------+----------------------------------------------+ 66 // | LastRefresh | Last refresh time of one project | 67 // +-----------------------+----------------------------------------------+ 68 // | HumanizedLastRefresh | Humanized last refresh setting | 69 // +-----------------------+----------------------------------------------+ 70 // 71 // And the HtmlData just as data for default html template. If you want to customize 72 // your own template file, please follow these data, or you can redefine it yourself. 73 type HtmlData struct { 74 Project string 75 Score int 76 IssuesNum int 77 CodeTest string 78 CodeStyle string 79 CodeOptimization string 80 CodeCount string 81 CodeSmell string 82 DepGraph template.HTML 83 84 Date string 85 LastRefresh time.Time `json:"last_refresh"` 86 HumanizedLastRefresh string `json:"humanized_last_refresh"` 87 } 88 89 // converterUnitTest provides function that convert unit test data into the 90 // format required in the html template.It will extract from the structData 91 // need to convert the data.The result will be saved in the hd's attributes. 92 func (hd *HtmlData) converterCodeTest(structData Reporter) { 93 var codeTestHtmlData CodeTest 94 if result, ok := structData.Metrics["UnitTestTips"]; ok { 95 var totalTime float64 96 for pkgName, testRes := range result.Summaries { 97 var packageUnitTestResult PackageTest 98 jsoniter.Unmarshal([]byte(testRes.Description), &packageUnitTestResult) 99 srcLastIndex := strings.LastIndex(pkgName, hd.Project) 100 if !packageUnitTestResult.IsPass { 101 codeTestHtmlData.Content.NoTest = append(codeTestHtmlData.Content.NoTest, pkgName) 102 } else if srcLastIndex < len(pkgName) && srcLastIndex >= 0 { 103 codeTestHtmlData.Content.Pkg = append(codeTestHtmlData.Content.Pkg, pkgName[srcLastIndex:]) 104 codeTestHtmlData.Content.Time = append(codeTestHtmlData.Content.Time, packageUnitTestResult.Time) 105 totalTime = totalTime + packageUnitTestResult.Time 106 if len(packageUnitTestResult.Coverage) > 1 { 107 cover, _ := strconv.ParseFloat(packageUnitTestResult.Coverage[:(len(packageUnitTestResult.Coverage)-1)], 64) 108 codeTestHtmlData.Content.Cover = append(codeTestHtmlData.Content.Cover, cover) 109 } 110 } 111 } 112 codeTestHtmlData.Summary.TotalTime, _ = strconv.ParseFloat(strconv.FormatFloat(totalTime, 'f', 1, 64), 64) 113 codeTestHtmlData.Summary.CodeCover, _ = strconv.ParseFloat(strconv.FormatFloat(result.Percentage, 'f', 1, 64), 64) 114 if (len(codeTestHtmlData.Content.Pkg) + len(codeTestHtmlData.Content.NoTest)) == 0 { 115 codeTestHtmlData.Summary.PackageCover = 0 116 } else { 117 codeTestHtmlData.Summary.PackageCover, _ = strconv.ParseFloat(strconv.FormatFloat(100*float64(len(codeTestHtmlData.Content.Pkg))*1.0/float64(len(codeTestHtmlData.Content.Pkg)+len(codeTestHtmlData.Content.NoTest)), 'f', 1, 64), 64) 118 } 119 } 120 121 stringCodeTestJson, err := jsoniter.Marshal(codeTestHtmlData) 122 if err != nil { 123 glog.Errorln(err) 124 } 125 hd.CodeTest = string(stringCodeTestJson) 126 } 127 128 // converterUnitTest provides function that convert unit test data into the 129 // format required in the html template.It will extract from the structData 130 // need to convert the data.The result will be saved in the hd's attributes. 131 func (hd *HtmlData) converterCodeStyle(structData Reporter) { 132 var codeStyleHtmlData CodeStyle 133 codeSpellHtmlData := converterCodeSpell(structData) 134 codeStyleHtmlData.Summary.FilesNum = codeStyleHtmlData.Summary.FilesNum + codeSpellHtmlData.filesNum 135 codeStyleHtmlData.Summary.IssuesNum = codeStyleHtmlData.Summary.IssuesNum + codeSpellHtmlData.issuesNum 136 codeStyleHtmlData.Content.MissSpell = codeSpellHtmlData 137 138 codeLintHtmlData := converterCodeLint(structData) 139 codeStyleHtmlData.Summary.FilesNum = codeStyleHtmlData.Summary.FilesNum + codeLintHtmlData.filesNum 140 codeStyleHtmlData.Summary.IssuesNum = codeStyleHtmlData.Summary.IssuesNum + codeLintHtmlData.issuesNum 141 codeStyleHtmlData.Content.GoLint = codeLintHtmlData 142 143 codeFmtHtmlData := converterCodeFmt(structData) 144 codeStyleHtmlData.Summary.FilesNum = codeStyleHtmlData.Summary.FilesNum + codeFmtHtmlData.filesNum 145 codeStyleHtmlData.Summary.IssuesNum = codeStyleHtmlData.Summary.IssuesNum + codeFmtHtmlData.issuesNum 146 codeStyleHtmlData.Content.GoFmt = codeFmtHtmlData 147 148 codeVetHtmlData := converterCodeVet(structData) 149 codeStyleHtmlData.Summary.FilesNum = codeStyleHtmlData.Summary.FilesNum + codeVetHtmlData.filesNum 150 codeStyleHtmlData.Summary.IssuesNum = codeStyleHtmlData.Summary.IssuesNum + codeVetHtmlData.issuesNum 151 codeStyleHtmlData.Content.GoVet = codeVetHtmlData 152 153 stringCodeStyleJson, err := jsoniter.Marshal(codeStyleHtmlData) 154 if err != nil { 155 glog.Errorln(err) 156 } 157 hd.CodeStyle = string(stringCodeStyleJson) 158 } 159 160 // converterUnitTest provides function that convert unit test data into the 161 // format required in the html template.It will extract from the structData 162 // need to convert the data.The result will be saved in the hd's attributes. 163 func (hd *HtmlData) converterCodeOptimization(structData Reporter) { 164 var codeOptimizationHtmlData CodeOptimization 165 codeSimpleHtmlData := converterCodeSimple(structData) 166 codeOptimizationHtmlData.Summary.FilesNum = codeOptimizationHtmlData.Summary.FilesNum + codeSimpleHtmlData.filesNum 167 codeOptimizationHtmlData.Summary.IssuesNum = codeOptimizationHtmlData.Summary.IssuesNum + codeSimpleHtmlData.issuesNum 168 codeOptimizationHtmlData.Content.SimpleCode = codeSimpleHtmlData 169 170 codeDeadHtmlData := converterCodeDead(structData) 171 codeOptimizationHtmlData.Summary.FilesNum = codeOptimizationHtmlData.Summary.FilesNum + codeDeadHtmlData.filesNum 172 codeOptimizationHtmlData.Summary.IssuesNum = codeOptimizationHtmlData.Summary.IssuesNum + codeDeadHtmlData.issuesNum 173 codeOptimizationHtmlData.Content.DeadCode = codeDeadHtmlData 174 175 copyCodeHtmlData := converterCopyCode(structData) 176 codeOptimizationHtmlData.Summary.FilesNum = codeOptimizationHtmlData.Summary.FilesNum + copyCodeHtmlData.filesNum 177 codeOptimizationHtmlData.Summary.IssuesNum = codeOptimizationHtmlData.Summary.IssuesNum + copyCodeHtmlData.issuesNum 178 codeOptimizationHtmlData.Content.CopyCode = copyCodeHtmlData 179 180 codeInterfacerHtmlData := converterCodeInterfacer(structData) 181 codeOptimizationHtmlData.Summary.FilesNum = codeOptimizationHtmlData.Summary.FilesNum + codeInterfacerHtmlData.filesNum 182 codeOptimizationHtmlData.Summary.IssuesNum = codeOptimizationHtmlData.Summary.IssuesNum + codeInterfacerHtmlData.issuesNum 183 codeOptimizationHtmlData.Content.InterfacerCode = codeInterfacerHtmlData 184 185 stringCodeOptimizationJson, err := jsoniter.Marshal(codeOptimizationHtmlData) 186 if err != nil { 187 glog.Errorln(err) 188 } 189 hd.CodeOptimization = string(stringCodeOptimizationJson) 190 } 191 192 // converterCyclo provides function that convert cyclo data into the 193 // format required in the html template.It will extract from the structData 194 // need to convert the data.The result will be saved in the hd's attributes. 195 func (hd *HtmlData) converterCodeSmell(structData Reporter) { 196 var codeSmellHtmlData CodeSmell 197 codeSmellHtmlData.Content.Percentage = make(map[string]int, 0) 198 codeSmellHtmlData.Content.List = make([]CodeSmellItem, 0) 199 codeSmellHtmlData.Content.Percentage["1-15"] = 0 200 codeSmellHtmlData.Content.Percentage["15-50"] = 0 201 codeSmellHtmlData.Content.Percentage["50+"] = 0 202 203 sumComp, sumNum := 0, 0 204 if result, ok := structData.Metrics["CycloTips"]; ok { 205 filesMap := make(map[string]bool, 0) 206 for pkgName, summary := range result.Summaries { 207 var compNum, compSum int 208 for i := 0; i < len(summary.Errors); i++ { 209 cycloTip := strings.Split(summary.Errors[i].ErrorString, ":") 210 if len(cycloTip) >= 3 { 211 if summary.Errors[i].LineNumber < 15 { 212 codeSmellHtmlData.Content.Percentage["1-15"]++ 213 smellItem := CodeSmellItem{ 214 Path: strings.Join(cycloTip[0:], ":"), 215 Cyclo: summary.Errors[i].LineNumber, 216 } 217 codeSmellHtmlData.Content.List = append(codeSmellHtmlData.Content.List, smellItem) 218 filesMap[strings.Join(cycloTip[0:], ":")] = true 219 } else if summary.Errors[i].LineNumber >= 15 { 220 smellItem := CodeSmellItem{ 221 Path: strings.Join(cycloTip[0:], ":"), 222 Cyclo: summary.Errors[i].LineNumber, 223 } 224 codeSmellHtmlData.Content.List = append(codeSmellHtmlData.Content.List, smellItem) 225 filesMap[strings.Join(cycloTip[0:], ":")] = true 226 if summary.Errors[i].LineNumber < 50 { 227 codeSmellHtmlData.Content.Percentage["15-50"]++ 228 } else { 229 codeSmellHtmlData.Content.Percentage["50+"]++ 230 } 231 } 232 compNum++ 233 compSum = compSum + summary.Errors[i].LineNumber 234 } 235 } 236 237 if compNum > 0 { 238 codeSmellHtmlData.Content.Pkg = append(codeSmellHtmlData.Content.Pkg, pkgName) 239 codeSmellHtmlData.Content.Cyclo = append(codeSmellHtmlData.Content.Cyclo, compSum/compNum) 240 } 241 sumComp = sumComp + compSum 242 sumNum = sumNum + compNum 243 } 244 245 sortCycloByComp(codeSmellHtmlData.Content.List, 0, len(codeSmellHtmlData.Content.List)) 246 if sumNum == 0 { 247 codeSmellHtmlData.Summary.CycloAvg = 0 248 } else { 249 codeSmellHtmlData.Summary.CycloAvg = sumComp / sumNum 250 } 251 codeSmellHtmlData.Summary.CycloHigh = codeSmellHtmlData.Content.Percentage["15-50"] 252 codeSmellHtmlData.Summary.CycloGrave = codeSmellHtmlData.Content.Percentage["50+"] 253 } 254 255 stringCodeSmellJson, err := jsoniter.Marshal(codeSmellHtmlData) 256 if err != nil { 257 glog.Errorln(err) 258 } 259 hd.CodeSmell = string(stringCodeSmellJson) 260 } 261 262 // sortCycloByComp implements the quick sorting algorithm sort list by complexity. 263 func sortCycloByComp(input []CodeSmellItem, l, u int) { 264 if l < u { 265 m := partition(input, l, u) 266 sortCycloByComp(input, l, m-1) 267 sortCycloByComp(input, m, u) 268 } 269 } 270 271 func partition(input []CodeSmellItem, l, u int) int { 272 var ( 273 pivot = input[l] 274 left = l 275 right = l + 1 276 ) 277 for ; right < u; right++ { 278 if input[right].Cyclo >= pivot.Cyclo { 279 left++ 280 input[left], input[right] = input[right], input[left] 281 } 282 } 283 input[l], input[left] = input[left], input[l] 284 return left + 1 285 } 286 287 // converterCount provides function that convert countcode data into the 288 // format required in the html template.It will extract from the structData 289 // need to convert the data.The result will be saved in the hd's attributes. 290 func (hd *HtmlData) converterCodeCount(structData Reporter) { 291 var codeCountHtmlData CodeCount 292 293 fileFuncsCount := make(map[string]int, 0) 294 pkgFuncsCount := make(map[string]int, 0) 295 if result, ok := structData.Metrics["CycloTips"]; ok { 296 for _, summary := range result.Summaries { 297 for i := 0; i < len(summary.Errors); i++ { 298 sepFileIndex := strings.LastIndex(summary.Errors[i].ErrorString, ".go") 299 if sepFileIndex >= 0 && (sepFileIndex+3) < len(summary.Errors[i].ErrorString) { 300 fileFuncsCount[string(summary.Errors[i].ErrorString[0:sepFileIndex+3])]++ 301 } 302 sepPkgIndex := strings.LastIndex(summary.Errors[i].ErrorString, string(filepath.Separator)) 303 if sepPkgIndex >= 0 && sepPkgIndex < len(summary.Errors[i].ErrorString) { 304 pkgFuncsCount[string(summary.Errors[i].ErrorString[0:sepPkgIndex])]++ 305 } 306 } 307 } 308 } 309 pkgCommentCount := make(map[string]int, 0) 310 pkgLineCount := make(map[string]int, 0) 311 312 if result, ok := structData.Metrics["CountCodeTips"]; ok { 313 for fileName, codeCount := range result.Summaries { 314 counts := strings.Split(codeCount.Description, ";") 315 if len(counts) == 4 { 316 codeCountHtmlData.Content.File = append(codeCountHtmlData.Content.File, fileName) 317 fileCommentCount, _ := strconv.Atoi(counts[2]) 318 codeCountHtmlData.Content.FileCommentCount = append(codeCountHtmlData.Content.FileCommentCount, fileCommentCount) 319 320 codeCountHtmlData.Content.FileFunctionCount = append(codeCountHtmlData.Content.FileFunctionCount, fileFuncsCount[fileName]) 321 // Add into summary. 322 codeCountHtmlData.Summary.FunctionCount = codeCountHtmlData.Summary.FunctionCount + fileFuncsCount[fileName] 323 fileLineCount, _ := strconv.Atoi(counts[1]) 324 codeCountHtmlData.Content.FileLineCount = append(codeCountHtmlData.Content.FileLineCount, fileLineCount) 325 326 sepPkgIndex := strings.LastIndex(fileName, string(filepath.Separator)) 327 if sepPkgIndex >= 0 && sepPkgIndex < len(fileName) { 328 pkgCommentCount[string(fileName[0:sepPkgIndex])] = pkgCommentCount[string(fileName[0:sepPkgIndex])] + fileCommentCount 329 pkgLineCount[string(fileName[0:sepPkgIndex])] = pkgLineCount[string(fileName[0:sepPkgIndex])] + fileLineCount 330 } 331 } 332 } 333 334 for pkgName, commentCount := range pkgCommentCount { 335 codeCountHtmlData.Content.Pkg = append(codeCountHtmlData.Content.Pkg, pkgName) 336 codeCountHtmlData.Content.PkgCommentCount = append(codeCountHtmlData.Content.PkgCommentCount, commentCount) 337 codeCountHtmlData.Content.PkgFunctionCount = append(codeCountHtmlData.Content.PkgFunctionCount, pkgFuncsCount[pkgName]) 338 codeCountHtmlData.Content.PkgLineCount = append(codeCountHtmlData.Content.PkgLineCount, pkgLineCount[pkgName]) 339 340 codeCountHtmlData.Summary.LineCount = codeCountHtmlData.Summary.LineCount + pkgLineCount[pkgName] 341 codeCountHtmlData.Summary.CommentCount = codeCountHtmlData.Summary.CommentCount + commentCount 342 codeCountHtmlData.Summary.FunctionCount = codeCountHtmlData.Summary.FunctionCount + pkgFuncsCount[pkgName] 343 } 344 codeCountHtmlData.Summary.FileCount = len(result.Summaries) 345 } 346 347 stringCodeCountJson, err := jsoniter.Marshal(codeCountHtmlData) 348 if err != nil { 349 glog.Errorln(err) 350 } 351 hd.CodeCount = string(stringCodeCountJson) 352 } 353 354 // converterCyclo provides function that convert cyclo data into the 355 // format required in the html template.It will extract from the structData 356 // need to convert the data.The result will be saved in the hd's attributes. 357 // func converterFunctionDepth(structData Reporter) { 358 // if result, ok := structData.Metrics["DepthTips"]; ok { 359 // for pkgName, summary := range result.Summaries { 360 // depthTips := summary.Errors 361 // depth := Depth{ 362 // Pkg: pkgName, 363 // Size: len(depthTips), 364 // } 365 // var infos []DepthInfo 366 // for i := 0; i < len(depthTips); i++ { 367 // depthTip := strings.Split(depthTips[i].ErrorString, ":") 368 // if len(depthTip) >= 3 { 369 // depthInfo := DepthInfo{ 370 // Comp: depthTips[i].LineNumber, 371 // Info: strings.Join(depthTip[0:], ":"), 372 // } 373 // if depthTips[i].LineNumber > 3 { 374 // hd.CycloBigThan15 = hd.CycloBigThan15 + 1 375 // issues = issues + 1 376 // } 377 // infos = append(infos, depthInfo) 378 // } 379 // } 380 // depth.Info = infos 381 // depthHtmlRes = append(depthHtmlRes, depth) 382 // } 383 // } 384 385 // stringDepthJson, err := jsoniter.Marshal(depthHtmlRes) 386 // if err != nil { 387 // glog.Errorln(err) 388 // } 389 // hd.Depths = string(stringDepthJson) 390 // } 391 392 // converterSimple provides function that convert simplecode data into the 393 // format required in the html template.It will extract from the structData 394 // need to convert the data.The result will be saved in the hd's attributes. 395 func converterCodeSimple(structData Reporter) (simpleHtmlData StyleItem) { 396 simpleHtmlData.Label = `gosimple is a linter for Go source code that specialises on simplifying code.` 397 if result, ok := structData.Metrics["SimpleTips"]; ok { 398 fileMap := make(map[string]bool, 0) 399 mapItem2DetailIndex := make(map[string]int, 0) 400 for _, summary := range result.Summaries { 401 simpleCodeTips := summary.Errors 402 for i := 0; i < len(simpleCodeTips); i++ { 403 simpleCodeTip := strings.Split(simpleCodeTips[i].ErrorString, ":") 404 if len(simpleCodeTip) == 4 { 405 if fileIndex, ok := mapItem2DetailIndex[strings.Join(simpleCodeTip[0:2], ":")]; ok { 406 simpleHtmlData.Detail[fileIndex].Content = append(simpleHtmlData.Detail[fileIndex].Content, strings.Join(simpleCodeTip[2:], ":")) 407 } else { 408 spellcode := Item{ 409 File: strings.Join(simpleCodeTip[0:2], ":"), 410 } 411 spellcode.Content = append(spellcode.Content, strings.Join(simpleCodeTip[2:], ":")) 412 fileMap[spellcode.File] = true 413 mapItem2DetailIndex[strings.Join(simpleCodeTip[0:2], ":")] = len(simpleHtmlData.Detail) 414 simpleHtmlData.Detail = append(simpleHtmlData.Detail, spellcode) 415 } 416 } else if len(simpleCodeTip) == 5 { 417 if fileIndex, ok := mapItem2DetailIndex[strings.Join(simpleCodeTip[0:3], ":")]; ok { 418 simpleHtmlData.Detail[fileIndex].Content = append(simpleHtmlData.Detail[fileIndex].Content, strings.Join(simpleCodeTip[3:], ":")) 419 } else { 420 spellcode := Item{ 421 File: strings.Join(simpleCodeTip[0:3], ":"), 422 } 423 spellcode.Content = append(spellcode.Content, strings.Join(simpleCodeTip[3:], ":")) 424 fileMap[spellcode.File] = true 425 mapItem2DetailIndex[strings.Join(simpleCodeTip[0:3], ":")] = len(simpleHtmlData.Detail) 426 simpleHtmlData.Detail = append(simpleHtmlData.Detail, spellcode) 427 } 428 } 429 } 430 } 431 simpleHtmlData.filesNum = len(fileMap) 432 simpleHtmlData.issuesNum = len(simpleHtmlData.Detail) 433 } 434 435 return simpleHtmlData 436 } 437 438 // converterInterfacer provides function that convert interfacer data into the 439 // format required in the html template.It will extract from the structData 440 // need to convert the data.The result will be saved in the hd's attributes. 441 func converterCodeInterfacer(structData Reporter) (interfacerHtmlData StyleItem) { 442 interfacerHtmlData.Label = `A linter that suggests interface types. In other words, it warns about the usage of types that are more specific than necessary.` 443 if result, ok := structData.Metrics["InterfacerTips"]; ok { 444 filesMap := make(map[string]bool, 0) 445 mapItem2DetailIndex := make(map[string]int, 0) 446 for _, summary := range result.Summaries { 447 interfacerCodeTips := summary.Errors 448 for i := 0; i < len(interfacerCodeTips); i++ { 449 interfacerCodeTip := strings.Split(interfacerCodeTips[i].ErrorString, ":") 450 if len(interfacerCodeTip) == 4 { 451 if fileIndex, ok := mapItem2DetailIndex[strings.Join(interfacerCodeTip[0:2], ":")]; ok { 452 interfacerHtmlData.Detail[fileIndex].Content = append(interfacerHtmlData.Detail[fileIndex].Content, strings.Join(interfacerCodeTip[2:], ":")) 453 } else { 454 spellcode := Item{ 455 File: strings.Join(interfacerCodeTip[0:2], ":"), 456 } 457 spellcode.Content = append(spellcode.Content, strings.Join(interfacerCodeTip[2:], ":")) 458 filesMap[spellcode.File] = true 459 mapItem2DetailIndex[strings.Join(interfacerCodeTip[0:2], ":")] = len(interfacerHtmlData.Detail) 460 interfacerHtmlData.Detail = append(interfacerHtmlData.Detail, spellcode) 461 } 462 } else if len(interfacerCodeTip) == 5 { 463 if fileIndex, ok := mapItem2DetailIndex[strings.Join(interfacerCodeTip[0:3], ":")]; ok { 464 interfacerHtmlData.Detail[fileIndex].Content = append(interfacerHtmlData.Detail[fileIndex].Content, strings.Join(interfacerCodeTip[3:], ":")) 465 } else { 466 spellcode := Item{ 467 File: strings.Join(interfacerCodeTip[0:3], ":"), 468 } 469 spellcode.Content = append(spellcode.Content, strings.Join(interfacerCodeTip[3:], ":")) 470 filesMap[spellcode.File] = true 471 mapItem2DetailIndex[strings.Join(interfacerCodeTip[0:3], ":")] = len(interfacerHtmlData.Detail) 472 interfacerHtmlData.Detail = append(interfacerHtmlData.Detail, spellcode) 473 } 474 } 475 } 476 } 477 } 478 479 return interfacerHtmlData 480 } 481 482 // converterCopy provides function that convert copycode data into the 483 // format required in the html template.It will extract from the structData 484 // need to convert the data.The result will be saved in the hd's attributes. 485 func converterCopyCode(structData Reporter) (copyHtmlData CopyItem) { 486 copyHtmlData.Label = `Find code clones. So far it can find clones only in the Go source files. The method uses suffix tree for serialized ASTs. It ignores values of AST nodes.` 487 if result, ok := structData.Metrics["CopyCodeTips"]; ok { 488 filesMap := make(map[string]bool, 0) 489 for _, copyResult := range result.Summaries { 490 copyTips := copyResult.Errors 491 var copyCodePathList []string 492 for i := 0; i < len(copyTips); i++ { 493 copyCodeTip := strings.Split(copyTips[i].ErrorString, ":") 494 if len(copyCodeTip) >= 2 { 495 newPath := strings.Join(copyCodeTip[0:], ":") 496 filesMap[newPath] = true 497 copyCodePathList = append(copyCodePathList, newPath) 498 } 499 } 500 copyHtmlData.Detail = append(copyHtmlData.Detail, copyCodePathList) 501 } 502 copyHtmlData.filesNum = len(filesMap) 503 copyHtmlData.issuesNum = len(copyHtmlData.Detail) 504 } 505 506 return copyHtmlData 507 } 508 509 // converterDead provides function that convert deadcode data into the 510 // format required in the html template.It will extract from the structData 511 // need to convert the data.The result will be saved in the hd's attributes. 512 func converterCodeDead(structData Reporter) (deadHtmlData StyleItem) { 513 deadHtmlData.Label = `Unused code.` 514 if result, ok := structData.Metrics["DeadCodeTips"]; ok { 515 filesMap := make(map[string]bool, 0) 516 mapItem2DetailIndex := make(map[string]int, 0) 517 for _, deadCodeResult := range result.Summaries { 518 deadCodeTips := deadCodeResult.Errors 519 for i := 0; i < len(deadCodeTips); i++ { 520 deadCodeTip := strings.Split(deadCodeTips[i].ErrorString, ":") 521 if len(deadCodeTip) == 4 { 522 if fileIndex, ok := mapItem2DetailIndex[strings.Join(deadCodeTip[0:2], ":")]; ok { 523 deadHtmlData.Detail[fileIndex].Content = append(deadHtmlData.Detail[fileIndex].Content, strings.Join(deadCodeTip[2:], ":")) 524 } else { 525 spellcode := Item{ 526 File: strings.Join(deadCodeTip[0:2], ":"), 527 } 528 spellcode.Content = append(spellcode.Content, strings.Join(deadCodeTip[2:], ":")) 529 filesMap[spellcode.File] = true 530 mapItem2DetailIndex[strings.Join(deadCodeTip[0:2], ":")] = len(deadHtmlData.Detail) 531 deadHtmlData.Detail = append(deadHtmlData.Detail, spellcode) 532 } 533 } else if len(deadCodeTip) == 5 { 534 if fileIndex, ok := mapItem2DetailIndex[strings.Join(deadCodeTip[0:3], ":")]; ok { 535 deadHtmlData.Detail[fileIndex].Content = append(deadHtmlData.Detail[fileIndex].Content, strings.Join(deadCodeTip[3:], ":")) 536 } else { 537 spellcode := Item{ 538 File: strings.Join(deadCodeTip[0:3], ":"), 539 } 540 spellcode.Content = append(spellcode.Content, strings.Join(deadCodeTip[3:], ":")) 541 filesMap[spellcode.File] = true 542 mapItem2DetailIndex[strings.Join(deadCodeTip[0:3], ":")] = len(deadHtmlData.Detail) 543 deadHtmlData.Detail = append(deadHtmlData.Detail, spellcode) 544 } 545 } 546 } 547 } 548 } 549 550 return deadHtmlData 551 } 552 553 // converterSpell provides function that convert spellcheck data into the 554 // format required in the html template.It will extract from the structData 555 // need to convert the data.The result will be saved in the hd's attributes. 556 func converterCodeSpell(structData Reporter) (spellHtmlData StyleItem) { 557 spellHtmlData.Label = `Correct commonly misspelled English words... quickly` 558 if result, ok := structData.Metrics["SpellCheckTips"]; ok { 559 filesMap := make(map[string]bool, 0) 560 mapItem2DetailIndex := make(map[string]int, 0) 561 for _, summary := range result.Summaries { 562 spellCodeTips := summary.Errors 563 for i := 0; i < len(spellCodeTips); i++ { 564 spellCodeTip := strings.Split(spellCodeTips[i].ErrorString, ":") 565 if len(spellCodeTip) == 4 { 566 if fileIndex, ok := mapItem2DetailIndex[strings.Join(spellCodeTip[0:2], ":")]; ok { 567 spellHtmlData.Detail[fileIndex].Content = append(spellHtmlData.Detail[fileIndex].Content, strings.Join(spellCodeTip[2:], ":")) 568 } else { 569 spellcode := Item{ 570 File: strings.Join(spellCodeTip[0:2], ":"), 571 } 572 spellcode.Content = append(spellcode.Content, strings.Join(spellCodeTip[2:], ":")) 573 filesMap[spellcode.File] = true 574 mapItem2DetailIndex[strings.Join(spellCodeTip[0:2], ":")] = len(spellHtmlData.Detail) 575 spellHtmlData.Detail = append(spellHtmlData.Detail, spellcode) 576 } 577 } else if len(spellCodeTip) == 5 { 578 if fileIndex, ok := mapItem2DetailIndex[strings.Join(spellCodeTip[0:3], ":")]; ok { 579 spellHtmlData.Detail[fileIndex].Content = append(spellHtmlData.Detail[fileIndex].Content, strings.Join(spellCodeTip[3:], ":")) 580 } else { 581 spellcode := Item{ 582 File: strings.Join(spellCodeTip[0:3], ":"), 583 } 584 spellcode.Content = append(spellcode.Content, strings.Join(spellCodeTip[3:], ":")) 585 filesMap[spellcode.File] = true 586 mapItem2DetailIndex[strings.Join(spellCodeTip[0:3], ":")] = len(spellHtmlData.Detail) 587 spellHtmlData.Detail = append(spellHtmlData.Detail, spellcode) 588 } 589 } 590 } 591 } 592 spellHtmlData.filesNum = len(filesMap) 593 spellHtmlData.issuesNum = len(spellHtmlData.Detail) 594 } 595 596 return spellHtmlData 597 } 598 599 // converterCodeLint provides function that convert spellcheck data into the 600 // format required in the html template.It will extract from the structData 601 // need to convert the data.The result will be saved in the hd's attributes. 602 func converterCodeLint(structData Reporter) (lintHtmlData StyleItem) { 603 lintHtmlData.Label = `Correct commonly misspelled English words... quickly` 604 if result, ok := structData.Metrics["GoLintTips"]; ok { 605 fileMap := make(map[string]bool, 0) 606 mapItem2DetailIndex := make(map[string]int, 0) 607 for _, summary := range result.Summaries { 608 spellCodeTips := summary.Errors 609 for i := 0; i < len(spellCodeTips); i++ { 610 lintCodeTip := strings.Split(spellCodeTips[i].ErrorString, ":") 611 if len(lintCodeTip) == 4 { 612 if fileIndex, ok := mapItem2DetailIndex[strings.Join(lintCodeTip[0:2], ":")]; ok { 613 lintHtmlData.Detail[fileIndex].Content = append(lintHtmlData.Detail[fileIndex].Content, strings.Join(lintCodeTip[2:], ":")) 614 } else { 615 spellcode := Item{ 616 File: strings.Join(lintCodeTip[0:2], ":"), 617 } 618 spellcode.Content = append(spellcode.Content, strings.Join(lintCodeTip[2:], ":")) 619 fileMap[spellcode.File] = true 620 mapItem2DetailIndex[strings.Join(lintCodeTip[0:2], ":")] = len(lintHtmlData.Detail) 621 lintHtmlData.Detail = append(lintHtmlData.Detail, spellcode) 622 } 623 } else if len(lintCodeTip) == 5 { 624 if fileIndex, ok := mapItem2DetailIndex[strings.Join(lintCodeTip[0:3], ":")]; ok { 625 lintHtmlData.Detail[fileIndex].Content = append(lintHtmlData.Detail[fileIndex].Content, strings.Join(lintCodeTip[3:], ":")) 626 } else { 627 spellcode := Item{ 628 File: strings.Join(lintCodeTip[0:3], ":"), 629 } 630 spellcode.Content = append(spellcode.Content, strings.Join(lintCodeTip[3:], ":")) 631 fileMap[spellcode.File] = true 632 mapItem2DetailIndex[strings.Join(lintCodeTip[0:3], ":")] = len(lintHtmlData.Detail) 633 lintHtmlData.Detail = append(lintHtmlData.Detail, spellcode) 634 } 635 } 636 } 637 } 638 lintHtmlData.filesNum = len(fileMap) 639 lintHtmlData.issuesNum = len(lintHtmlData.Detail) 640 } 641 642 return lintHtmlData 643 } 644 645 // converterCodeFmt provides function that convert spellcheck data into the 646 // format required in the html template.It will extract from the structData 647 // need to convert the data.The result will be saved in the hd's attributes. 648 func converterCodeFmt(structData Reporter) (fmtHtmlData StyleItem) { 649 fmtHtmlData.Label = `Correct commonly misspelled English words... quickly` 650 if result, ok := structData.Metrics["GoFmtTips"]; ok { 651 filesMap := make(map[string]bool, 0) 652 mapItem2DetailIndex := make(map[string]int, 0) 653 for _, summary := range result.Summaries { 654 fmtCodeTips := summary.Errors 655 for i := 0; i < len(fmtCodeTips); i++ { 656 fmtCodeTip := strings.Split(fmtCodeTips[i].ErrorString, ":") 657 if len(fmtCodeTip) == 3 { 658 if fileIndex, ok := mapItem2DetailIndex[strings.Join(fmtCodeTip[0:1], ":")]; ok { 659 fmtHtmlData.Detail[fileIndex].Content = append(fmtHtmlData.Detail[fileIndex].Content, strings.Join(fmtCodeTip[1:], ":")) 660 } else { 661 spellcode := Item{ 662 File: strings.Join(fmtCodeTip[0:1], ":"), 663 } 664 spellcode.Content = append(spellcode.Content, strings.Join(fmtCodeTip[1:], ":")) 665 filesMap[spellcode.File] = true 666 fmtHtmlData.Detail = append(fmtHtmlData.Detail, spellcode) 667 } 668 } else if len(fmtCodeTip) == 4 { 669 if fileIndex, ok := mapItem2DetailIndex[strings.Join(fmtCodeTip[0:2], ":")]; ok { 670 fmtHtmlData.Detail[fileIndex].Content = append(fmtHtmlData.Detail[fileIndex].Content, strings.Join(fmtCodeTip[2:], ":")) 671 } else { 672 spellcode := Item{ 673 File: strings.Join(fmtCodeTip[0:2], ":"), 674 } 675 spellcode.Content = append(spellcode.Content, strings.Join(fmtCodeTip[2:], ":")) 676 filesMap[spellcode.File] = true 677 mapItem2DetailIndex[strings.Join(fmtCodeTip[0:3], ":")] = len(fmtHtmlData.Detail) 678 fmtHtmlData.Detail = append(fmtHtmlData.Detail, spellcode) 679 } 680 } 681 } 682 } 683 fmtHtmlData.filesNum = len(filesMap) 684 fmtHtmlData.issuesNum = len(fmtHtmlData.Detail) 685 } 686 687 return fmtHtmlData 688 } 689 690 // converterCodeVet provides function that convert spellcheck data into the 691 // format required in the html template.It will extract from the structData 692 // need to convert the data.The result will be saved in the hd's attributes. 693 func converterCodeVet(structData Reporter) (vetHtmlData StyleItem) { 694 vetHtmlData.Label = `Correct commonly misspelled English words... quickly` 695 if result, ok := structData.Metrics["GoVetTips"]; ok { 696 fileMap := make(map[string]bool, 0) 697 mapItem2DetailIndex := make(map[string]int, 0) 698 for _, summary := range result.Summaries { 699 vetCodeTips := summary.Errors 700 for i := 0; i < len(vetCodeTips); i++ { 701 vetCodeTip := strings.Split(vetCodeTips[i].ErrorString, ":") 702 if len(vetCodeTip) == 4 { 703 if fileIndex, ok := mapItem2DetailIndex[strings.Join(vetCodeTip[0:2], ":")]; ok { 704 vetHtmlData.Detail[fileIndex].Content = append(vetHtmlData.Detail[fileIndex].Content, strings.Join(vetCodeTip[2:], ":")) 705 } else { 706 spellcode := Item{ 707 File: strings.Join(vetCodeTip[0:2], ":"), 708 } 709 spellcode.Content = append(spellcode.Content, strings.Join(vetCodeTip[2:], ":")) 710 fileMap[spellcode.File] = true 711 mapItem2DetailIndex[strings.Join(vetCodeTip[0:2], ":")] = len(vetHtmlData.Detail) 712 vetHtmlData.Detail = append(vetHtmlData.Detail, spellcode) 713 } 714 } else if len(vetCodeTip) == 5 { 715 if fileIndex, ok := mapItem2DetailIndex[strings.Join(vetCodeTip[0:3], ":")]; ok { 716 vetHtmlData.Detail[fileIndex].Content = append(vetHtmlData.Detail[fileIndex].Content, strings.Join(vetCodeTip[3:], ":")) 717 } else { 718 spellcode := Item{ 719 File: strings.Join(vetCodeTip[0:3], ":"), 720 } 721 spellcode.Content = append(spellcode.Content, strings.Join(vetCodeTip[3:], ":")) 722 fileMap[spellcode.File] = true 723 mapItem2DetailIndex[strings.Join(vetCodeTip[0:3], ":")] = len(vetHtmlData.Detail) 724 vetHtmlData.Detail = append(vetHtmlData.Detail, spellcode) 725 } 726 } 727 728 } 729 } 730 vetHtmlData.filesNum = len(fileMap) 731 vetHtmlData.issuesNum = len(vetHtmlData.Detail) 732 } 733 734 return vetHtmlData 735 } 736 737 // converterDependGraph provides function that convert depend graph data into the 738 // format required in the html template.It will extract from the structData 739 // need to convert the data.The result will be saved in the hd's attributes. 740 func (hd *HtmlData) converterDependGraph(structData Reporter) { 741 hd.DepGraph = template.HTML(structData.Metrics["DependGraphTips"].Summaries["graph"].Description) 742 } 743 744 // SaveAsHtml is a function that save HtmlData as a html report.And will receive 745 // htmlData, projectPath, savePath and tpl parameters. 746 func SaveAsHtml(htmlData HtmlData, projectPath, savePath, timestamp, tpl string) { 747 t, err := template.New("goreporter").Parse(tpl) 748 if err != nil { 749 glog.Errorln(err) 750 } 751 752 var ( 753 out bytes.Buffer 754 htmlpath string 755 ) 756 757 err = t.Execute(&out, htmlData) 758 if err != nil { 759 glog.Errorln(err) 760 } 761 projectName := utils.ProjectName(projectPath) 762 if savePath != "" { 763 htmlpath = strings.Replace(savePath+string(filepath.Separator)+projectName+"-"+timestamp+".html", string(filepath.Separator)+string(filepath.Separator), string(filepath.Separator), -1) 764 err = ioutil.WriteFile(htmlpath, out.Bytes(), 0666) 765 if err != nil { 766 glog.Errorln(err) 767 } else { 768 glog.Info("Html report was saved in:", htmlpath) 769 } 770 absPath, err := filepath.Abs(htmlpath) 771 if err != nil { 772 log.Println(err) 773 } else { 774 displayReport(absPath) 775 } 776 777 } else { 778 htmlpath = projectName + "-" + timestamp + ".html" 779 err = ioutil.WriteFile(htmlpath, out.Bytes(), 0666) 780 if err != nil { 781 glog.Errorln(err) 782 } else { 783 glog.Info("Html report was saved in:", htmlpath) 784 } 785 absPath, err := filepath.Abs("." + string(filepath.Separator) + htmlpath) 786 if err != nil { 787 log.Println(err) 788 } else { 789 displayReport(absPath) 790 } 791 } 792 } 793 794 // displayReport function can be open system default browser automatic. 795 func displayReport(filePath string) { 796 fileURL := fmt.Sprintf("file://%v", filePath) 797 log.Println("To display report", fileURL, "in browser") 798 var err error 799 switch runtime.GOOS { 800 case "linux": 801 err = callSystemCmd("xdg-open", fileURL) 802 case "darwin": 803 err = callSystemCmd("open", fileURL) 804 case "windows": 805 r := strings.NewReplacer("&", "^&") 806 err = callSystemCmd("cmd", "/c", "start", r.Replace(fileURL)) 807 default: 808 err = fmt.Errorf("Unsupported platform,please view report file.") 809 } 810 if err != nil { 811 log.Println(err) 812 } 813 } 814 815 // callSystemCmd call system command opens a new browser window pointing to url. 816 func callSystemCmd(prog string, args ...string) error { 817 cmd := exec.Command(prog, args...) 818 cmd.Stdout = os.Stdout 819 cmd.Stderr = os.Stderr 820 return cmd.Run() 821 }