1// Copyright 2020 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package copyright checks that files have the correct copyright notices. 6package copyright 7 8import ( 9 "go/ast" 10 "go/parser" 11 "go/token" 12 "io/ioutil" 13 "os" 14 "path/filepath" 15 "regexp" 16 "strings" 17) 18 19func checkCopyright(dir string) ([]string, error) { 20 var files []string 21 err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 22 if err != nil { 23 return err 24 } 25 if info.IsDir() { 26 // Skip directories like ".git". 27 if strings.HasPrefix(info.Name(), ".") { 28 return filepath.SkipDir 29 } 30 return nil 31 } 32 needsCopyright, err := checkFile(dir, path) 33 if err != nil { 34 return err 35 } 36 if needsCopyright { 37 files = append(files, path) 38 } 39 return nil 40 }) 41 return files, err 42} 43 44var copyrightRe = regexp.MustCompile(`Copyright \d{4} The Go Authors. All rights reserved. 45Use of this source code is governed by a BSD-style 46license that can be found in the LICENSE file.`) 47 48func checkFile(toolsDir, filename string) (bool, error) { 49 // Only check Go files. 50 if !strings.HasSuffix(filename, "go") { 51 return false, nil 52 } 53 // Don't check testdata files. 54 normalized := strings.TrimPrefix(filepath.ToSlash(filename), filepath.ToSlash(toolsDir)) 55 if strings.Contains(normalized, "/testdata/") { 56 return false, nil 57 } 58 // goyacc is the only file with a different copyright header. 59 if strings.HasSuffix(normalized, "cmd/goyacc/yacc.go") { 60 return false, nil 61 } 62 content, err := ioutil.ReadFile(filename) 63 if err != nil { 64 return false, err 65 } 66 fset := token.NewFileSet() 67 parsed, err := parser.ParseFile(fset, filename, content, parser.ParseComments) 68 if err != nil { 69 return false, err 70 } 71 // Don't require headers on generated files. 72 if isGenerated(fset, parsed) { 73 return false, nil 74 } 75 shouldAddCopyright := true 76 for _, c := range parsed.Comments { 77 // The copyright should appear before the package declaration. 78 if c.Pos() > parsed.Package { 79 break 80 } 81 if copyrightRe.MatchString(c.Text()) { 82 shouldAddCopyright = false 83 break 84 } 85 } 86 return shouldAddCopyright, nil 87} 88 89// Copied from golang.org/x/tools/internal/lsp/source/util.go. 90// Matches cgo generated comment as well as the proposed standard: 91// https://golang.org/s/generatedcode 92var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`) 93 94func isGenerated(fset *token.FileSet, file *ast.File) bool { 95 for _, commentGroup := range file.Comments { 96 for _, comment := range commentGroup.List { 97 if matched := generatedRx.MatchString(comment.Text); !matched { 98 continue 99 } 100 // Check if comment is at the beginning of the line in source. 101 if pos := fset.Position(comment.Slash); pos.Column == 1 { 102 return true 103 } 104 } 105 } 106 return false 107} 108