package weather import ( "fmt" "go/ast" "go/parser" "go/token" "strings" "testing" ) func TestComments(t *testing.T) { filename := "weather_forecast.go" fs := token.NewFileSet() f, err := parser.ParseFile(fs, filename, nil, parser.ParseComments) if err != nil { t.Fatal(err) } wantedComments := 4 got := len(f.Comments) if got != wantedComments { t.Errorf("Incorrect number of comments: got %d, want %d", got, wantedComments) } testPackageComment(t, f) ast.Inspect(f, func(node ast.Node) bool { switch n := node.(type) { case *ast.GenDecl: if n.Lparen.IsValid() { for _, v := range n.Specs { testBlockIdentifierComment(t, v.(*ast.ValueSpec)) } } else { testIdentifierComment(t, n) } case *ast.FuncDecl: testFunctionComment(t, n) } return true }) } func testPackageComment(t *testing.T, node *ast.File) { t.Helper() if node.Doc == nil { t.Errorf("Package weather should have a comment") } packageName := node.Name.Name want := "Package " + packageName packageComment := node.Doc.Text() if ok, errStr := testComment("Package", packageName, packageComment, want); !ok { t.Error(errStr) } } func testIdentifierComment(t *testing.T, node *ast.GenDecl) { t.Helper() identifierName := node.Specs[0].(*ast.ValueSpec).Names[0].Name if node.Doc == nil { t.Errorf("Exported identifier %s should have a comment", identifierName) } identifierComment := node.Doc.Text() want := identifierName if ok, errStr := testComment("Variable", identifierName, identifierComment, want); !ok { t.Error(errStr) } } func testBlockIdentifierComment(t *testing.T, node *ast.ValueSpec) { t.Helper() identifierName := node.Names[0].Name if node.Doc == nil { t.Errorf("Exported identifier %s should have a comment", identifierName) } identifierComment := node.Doc.Text() want := identifierName if ok, errStr := testComment("Variable", identifierName, identifierComment, want); !ok { t.Error(errStr) } } func testFunctionComment(t *testing.T, node *ast.FuncDecl) { t.Helper() funcName := node.Name.Name if node.Doc == nil { t.Errorf("Exported function %s() should have a comment", funcName) } funcComment := node.Doc.Text() want := funcName if ok, errStr := testComment("Function", funcName, funcComment, want); !ok { t.Error(errStr) } } func testComment(entityKind, entityName, comment, wantedPrefix string) (ok bool, errString string) { trimmedComment := strings.TrimSpace(comment) lowerEntity := strings.ToLower(entityKind) // Check if comment has wanted prefix if !strings.HasPrefix(trimmedComment, wantedPrefix) { errorString := fmt.Sprintf("%s comment for %s '%s' should start with '// %s ...': got '// %s'", entityKind, lowerEntity, entityName, wantedPrefix, trimmedComment) return false, errorString } // Check if comment content is empty commentContent := strings.TrimPrefix(trimmedComment, wantedPrefix) commentContent = strings.TrimSpace(commentContent) commentContent = strings.TrimSuffix(commentContent, ".") if commentContent == "" { lowerEntity := strings.ToLower(entityKind) errorString := fmt.Sprintf("%s comment of '%s' should provide a description of the %s, e.g '// %s <%s_description>'", entityKind, entityName, lowerEntity, wantedPrefix, lowerEntity) return false, errorString } // Check if comment ends in a period if !strings.HasSuffix(trimmedComment, ".") { return false, fmt.Sprintf("%s comment for %s '%s' should end with a period (.)", entityKind, lowerEntity, entityName) } return true, "" }