// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package godoctests

import (
	"go/ast"
	"strings"

	"golang.org/x/tools/go/analysis"
	"golang.org/x/tools/go/analysis/passes/inspect"
	"golang.org/x/tools/go/ast/inspector"
)

var Analyzer = &analysis.Analyzer{
	Name:     "godoctests",
	Doc:      "Verifies that every go test has a go doc",
	Run:      run,
	Requires: []*analysis.Analyzer{inspect.Analyzer},
}

func run(pass *analysis.Pass) (interface{}, error) {
	inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

	nodeFilter := []ast.Node{
		(*ast.FuncDecl)(nil),
	}

	inspector.Preorder(nodeFilter, func(node ast.Node) {
		funcDecl, ok := node.(*ast.FuncDecl)
		if !ok {
			return
		}

		// starts with 'Test'
		if !strings.HasPrefix(funcDecl.Name.Name, "Test") {
			return
		}

		// has one parameter
		params := funcDecl.Type.Params.List
		if len(params) != 1 {
			return
		}

		// parameter is a pointer
		firstParamType, ok := params[0].Type.(*ast.StarExpr)
		if !ok {
			return
		}

		selector, ok := firstParamType.X.(*ast.SelectorExpr)
		if !ok {
			return
		}

		// the pointer comes from package 'testing'
		selectorIdent, ok := selector.X.(*ast.Ident)
		if !ok {
			return
		}
		if selectorIdent.Name != "testing" {
			return
		}

		// the pointer has type 'T'
		if selector.Sel == nil || selector.Sel.Name != "T" {
			return
		}

		// then there must be a godoc
		if funcDecl.Doc == nil {
			pass.Reportf(node.Pos(), "Test %s is missing a go doc",
				funcDecl.Name.Name)
		} else if !strings.HasPrefix(funcDecl.Doc.Text(), funcDecl.Name.Name) {
			pass.Reportf(node.Pos(), "Test %s must have a go doc beginning with the function name",
				funcDecl.Name.Name)
		}
	})
	return nil, nil
}
