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