88//
99// Example: show gopls' total source line count, and its breakdown
1010// between gopls, x/tools, and the std go/* packages. (The balance
11- // comes from other std packages.)
11+ // comes from other std packages, other x/ repos, and external
12+ // dependencies.)
1213//
1314// $ linecount -mode=total ./gopls
1415// 752124
@@ -41,6 +42,8 @@ import (
4142 "cmp"
4243 "flag"
4344 "fmt"
45+ "go/scanner"
46+ "go/token"
4447 "log"
4548 "os"
4649 "path"
@@ -53,7 +56,6 @@ import (
5356)
5457
5558// TODO(adonovan): filters:
56- // - exclude comment and blank lines (-nonblank)
5759// - exclude generated files (-generated=false)
5860// - exclude non-CompiledGoFiles
5961// - include OtherFiles (asm, etc)
@@ -75,8 +77,9 @@ func main() {
7577 log .SetFlags (0 )
7678 var (
7779 mode = flag .String ("mode" , "file" , "group lines by 'module', 'package', or 'file', or show only 'total'" )
78- prefix = flag .String ("prefix" , "" , "only count files in packages whose path has the specified prefix" )
79- onlyModule = flag .String ("module" , "" , "only count files in the specified module" )
80+ prefix = flag .String ("prefix" , "" , "count files only in packages whose path has the specified prefix" )
81+ onlyModule = flag .String ("module" , "" , "count files only in the specified module" )
82+ nonblank = flag .Bool ("nonblank" , false , "count only non-comment, non-blank lines" )
8083 )
8184 flag .Usage = usage
8285 flag .Parse ()
@@ -128,7 +131,8 @@ func main() {
128131 if err != nil {
129132 return err
130133 }
131- n := bytes .Count (data , []byte ("\n " ))
134+
135+ n := count (* nonblank , data )
132136
133137 mu .Lock ()
134138 byFile [f ] = n
@@ -187,3 +191,35 @@ func within(file, dir string) bool {
187191 return file == dir ||
188192 strings .HasPrefix (file , dir ) && file [len (dir )] == os .PathSeparator
189193}
194+
195+ // count counts lines, or non-comment non-blank lines.
196+ func count (nonblank bool , src []byte ) int {
197+ if nonblank {
198+ // Count distinct lines containing tokens.
199+ var (
200+ fset = token .NewFileSet ()
201+ f = fset .AddFile ("" , fset .Base (), len (src ))
202+ prevLine = 0
203+ count = 0
204+ scan scanner.Scanner
205+ )
206+ scan .Init (f , src , nil , 0 )
207+ for {
208+ pos , tok , _ := scan .Scan ()
209+ if tok == token .EOF {
210+ break
211+ }
212+ // This may be slow because it binary
213+ // searches the newline offset table.
214+ line := f .PositionFor (pos , false ).Line // ignore //line directives
215+ if line > prevLine {
216+ prevLine = line
217+ count ++
218+ }
219+ }
220+ return count
221+ }
222+
223+ // Count all lines.
224+ return bytes .Count (src , []byte ("\n " ))
225+ }
0 commit comments