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  }