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  }