build/analysis/haslicense: introduce haslicense lint

This linter checks that all go source code has our copyright header

Change-Id: Ib79c0685d09bfe26ef87b29b22654eafabd7dba6
Reviewed-on: https://review.monogon.dev/c/monogon/+/3444
Reviewed-by: Leopold Schabel <leo@monogon.tech>
Tested-by: Jenkins CI
diff --git a/build/analysis/haslicense/haslicense.go b/build/analysis/haslicense/haslicense.go
new file mode 100644
index 0000000..ff76285
--- /dev/null
+++ b/build/analysis/haslicense/haslicense.go
@@ -0,0 +1,63 @@
+// Copyright The Monogon Project Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+package haslicense
+
+import (
+	"fmt"
+	"strings"
+
+	"golang.org/x/tools/go/analysis"
+
+	alib "source.monogon.dev/build/analysis/lib"
+)
+
+const header = `// Copyright The Monogon Project Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+`
+
+var Analyzer = &analysis.Analyzer{
+	Name: "haslicense",
+	Doc:  "haslicense checks for an existing license header in monogon source code.",
+	Run: func(p *analysis.Pass) (any, error) {
+		for _, file := range p.Files {
+			if alib.IsGeneratedFile(file) {
+				continue
+			}
+
+			if len(file.Comments) > 0 {
+				var sb strings.Builder
+				for _, c := range file.Comments[0].List {
+					sb.WriteString(c.Text)
+					sb.WriteRune('\n')
+				}
+				sb.WriteRune('\n')
+
+				if strings.HasPrefix(sb.String(), header) {
+					continue
+				}
+			}
+
+			p.Report(analysis.Diagnostic{
+				Pos:     file.FileStart,
+				End:     file.FileStart,
+				Message: "File is missing license header. Please add it.",
+				SuggestedFixes: []analysis.SuggestedFix{
+					{
+						Message: fmt.Sprintf("should prepend file with `%s`", header),
+						TextEdits: []analysis.TextEdit{
+							{
+								Pos:     file.FileStart,
+								End:     file.FileStart,
+								NewText: []byte(header),
+							},
+						},
+					},
+				},
+			})
+		}
+
+		return nil, nil
+	},
+}