// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ast

import (
	
	
)

// ----------------------------------------------------------------------------
// Export filtering

// exportFilter is a special filter function to extract exported nodes.
func exportFilter( string) bool {
	return IsExported()
}

// FileExports trims the AST for a Go source file in place such that
// only exported nodes remain: all top-level identifiers which are not exported
// and their associated information (such as type, initial value, or function
// body) are removed. Non-exported fields and methods of exported types are
// stripped. The [File.Comments] list is not changed.
//
// FileExports reports whether there are exported declarations.
func ( *File) bool {
	return filterFile(, exportFilter, true)
}

// PackageExports trims the AST for a Go package in place such that
// only exported nodes remain. The pkg.Files list is not changed, so that
// file names and top-level package comments don't get lost.
//
// PackageExports reports whether there are exported declarations;
// it returns false otherwise.
func ( *Package) bool {
	return filterPackage(, exportFilter, true)
}

// ----------------------------------------------------------------------------
// General filtering

type Filter func(string) bool

func filterIdentList( []*Ident,  Filter) []*Ident {
	 := 0
	for ,  := range  {
		if (.Name) {
			[] = 
			++
		}
	}
	return [0:]
}

// fieldName assumes that x is the type of an anonymous field and
// returns the corresponding field name. If x is not an acceptable
// anonymous field, the result is nil.
func fieldName( Expr) *Ident {
	switch t := .(type) {
	case *Ident:
		return 
	case *SelectorExpr:
		if ,  := .X.(*Ident);  {
			return .Sel
		}
	case *StarExpr:
		return (.X)
	}
	return nil
}

func filterFieldList( *FieldList,  Filter,  bool) ( bool) {
	if  == nil {
		return false
	}
	 := .List
	 := 0
	for ,  := range  {
		 := false
		if len(.Names) == 0 {
			// anonymous field
			 := fieldName(.Type)
			 =  != nil && (.Name)
		} else {
			 := len(.Names)
			.Names = filterIdentList(.Names, )
			if len(.Names) <  {
				 = true
			}
			 = len(.Names) > 0
		}
		if  {
			if  {
				filterType(.Type, , )
			}
			[] = 
			++
		}
	}
	if  < len() {
		 = true
	}
	.List = [0:]
	return
}

func filterCompositeLit( *CompositeLit,  Filter,  bool) {
	 := len(.Elts)
	.Elts = filterExprList(.Elts, , )
	if len(.Elts) <  {
		.Incomplete = true
	}
}

func filterExprList( []Expr,  Filter,  bool) []Expr {
	 := 0
	for ,  := range  {
		switch x := .(type) {
		case *CompositeLit:
			filterCompositeLit(, , )
		case *KeyValueExpr:
			if ,  := .Key.(*Ident);  && !(.Name) {
				continue
			}
			if ,  := .Value.(*CompositeLit);  {
				filterCompositeLit(, , )
			}
		}
		[] = 
		++
	}
	return [0:]
}

func filterParamList( *FieldList,  Filter,  bool) bool {
	if  == nil {
		return false
	}
	var  bool
	for ,  := range .List {
		if filterType(.Type, , ) {
			 = true
		}
	}
	return 
}

func filterType( Expr,  Filter,  bool) bool {
	switch t := .(type) {
	case *Ident:
		return (.Name)
	case *ParenExpr:
		return (.X, , )
	case *ArrayType:
		return (.Elt, , )
	case *StructType:
		if filterFieldList(.Fields, , ) {
			.Incomplete = true
		}
		return len(.Fields.List) > 0
	case *FuncType:
		 := filterParamList(.Params, , )
		 := filterParamList(.Results, , )
		return  || 
	case *InterfaceType:
		if filterFieldList(.Methods, , ) {
			.Incomplete = true
		}
		return len(.Methods.List) > 0
	case *MapType:
		 := (.Key, , )
		 := (.Value, , )
		return  || 
	case *ChanType:
		return (.Value, , )
	}
	return false
}

func filterSpec( Spec,  Filter,  bool) bool {
	switch s := .(type) {
	case *ValueSpec:
		.Names = filterIdentList(.Names, )
		.Values = filterExprList(.Values, , )
		if len(.Names) > 0 {
			if  {
				filterType(.Type, , )
			}
			return true
		}
	case *TypeSpec:
		if (.Name.Name) {
			if  {
				filterType(.Type, , )
			}
			return true
		}
		if ! {
			// For general filtering (not just exports),
			// filter type even if name is not filtered
			// out.
			// If the type contains filtered elements,
			// keep the declaration.
			return filterType(.Type, , )
		}
	}
	return false
}

func filterSpecList( []Spec,  Filter,  bool) []Spec {
	 := 0
	for ,  := range  {
		if filterSpec(, , ) {
			[] = 
			++
		}
	}
	return [0:]
}

// FilterDecl trims the AST for a Go declaration in place by removing
// all names (including struct field and interface method names, but
// not from parameter lists) that don't pass through the filter f.
//
// FilterDecl reports whether there are any declared names left after
// filtering.
func ( Decl,  Filter) bool {
	return filterDecl(, , false)
}

func filterDecl( Decl,  Filter,  bool) bool {
	switch d := .(type) {
	case *GenDecl:
		.Specs = filterSpecList(.Specs, , )
		return len(.Specs) > 0
	case *FuncDecl:
		return (.Name.Name)
	}
	return false
}

// FilterFile trims the AST for a Go file in place by removing all
// names from top-level declarations (including struct field and
// interface method names, but not from parameter lists) that don't
// pass through the filter f. If the declaration is empty afterwards,
// the declaration is removed from the AST. Import declarations are
// always removed. The [File.Comments] list is not changed.
//
// FilterFile reports whether there are any top-level declarations
// left after filtering.
func ( *File,  Filter) bool {
	return filterFile(, , false)
}

func filterFile( *File,  Filter,  bool) bool {
	 := 0
	for ,  := range .Decls {
		if filterDecl(, , ) {
			.Decls[] = 
			++
		}
	}
	.Decls = .Decls[0:]
	return  > 0
}

// FilterPackage trims the AST for a Go package in place by removing
// all names from top-level declarations (including struct field and
// interface method names, but not from parameter lists) that don't
// pass through the filter f. If the declaration is empty afterwards,
// the declaration is removed from the AST. The pkg.Files list is not
// changed, so that file names and top-level package comments don't get
// lost.
//
// FilterPackage reports whether there are any top-level declarations
// left after filtering.
func ( *Package,  Filter) bool {
	return filterPackage(, , false)
}

func filterPackage( *Package,  Filter,  bool) bool {
	 := false
	for ,  := range .Files {
		if filterFile(, , ) {
			 = true
		}
	}
	return 
}

// ----------------------------------------------------------------------------
// Merging of package files

// The MergeMode flags control the behavior of [MergePackageFiles].
type MergeMode uint

const (
	// If set, duplicate function declarations are excluded.
	FilterFuncDuplicates MergeMode = 1 << iota
	// If set, comments that are not associated with a specific
	// AST node (as Doc or Comment) are excluded.
	FilterUnassociatedComments
	// If set, duplicate import declarations are excluded.
	FilterImportDuplicates
)

// nameOf returns the function (foo) or method name (foo.bar) for
// the given function declaration. If the AST is incorrect for the
// receiver, it assumes a function instead.
func nameOf( *FuncDecl) string {
	if  := .Recv;  != nil && len(.List) == 1 {
		// looks like a correct receiver declaration
		 := .List[0].Type
		// dereference pointer receiver types
		if ,  := .(*StarExpr);  != nil {
			 = .X
		}
		// the receiver type must be a type name
		if ,  := .(*Ident);  != nil {
			return .Name + "." + .Name.Name
		}
		// otherwise assume a function instead
	}
	return .Name.Name
}

// separator is an empty //-style comment that is interspersed between
// different comment groups when they are concatenated into a single group
var separator = &Comment{token.NoPos, "//"}

// MergePackageFiles creates a file AST by merging the ASTs of the
// files belonging to a package. The mode flags control merging behavior.
func ( *Package,  MergeMode) *File {
	// Count the number of package docs, comments and declarations across
	// all package files. Also, compute sorted list of filenames, so that
	// subsequent iterations can always iterate in the same order.
	 := 0
	 := 0
	 := 0
	 := make([]string, len(.Files))
	var ,  token.Pos
	 := 0
	for ,  := range .Files {
		[] = 
		++
		if .Doc != nil {
			 += len(.Doc.List) + 1 // +1 for separator
		}
		 += len(.Comments)
		 += len(.Decls)
		if  == 0 || .FileStart <  {
			 = .FileStart
		}
		if  == 0 || .FileEnd >  {
			 = .FileEnd
		}
	}
	slices.Sort()

	// Collect package comments from all package files into a single
	// CommentGroup - the collected package documentation. In general
	// there should be only one file with a package comment; but it's
	// better to collect extra comments than drop them on the floor.
	var  *CommentGroup
	var  token.Pos
	if  > 0 {
		 := make([]*Comment, -1) // -1: no separator before first group
		 := 0
		for ,  := range  {
			 := .Files[]
			if .Doc != nil {
				if  > 0 {
					// not the first group - add separator
					[] = separator
					++
				}
				for ,  := range .Doc.List {
					[] = 
					++
				}
				if .Package >  {
					// Keep the maximum package clause position as
					// position for the package clause of the merged
					// files.
					 = .Package
				}
			}
		}
		 = &CommentGroup{}
	}

	// Collect declarations from all package files.
	var  []Decl
	if  > 0 {
		 = make([]Decl, )
		 := make(map[string]int) // map of func name -> decls index
		 := 0                        // current index
		 := 0                        // number of filtered entries
		for ,  := range  {
			 := .Files[]
			for ,  := range .Decls {
				if &FilterFuncDuplicates != 0 {
					// A language entity may be declared multiple
					// times in different package files; only at
					// build time declarations must be unique.
					// For now, exclude multiple declarations of
					// functions - keep the one with documentation.
					//
					// TODO(gri): Expand this filtering to other
					//            entities (const, type, vars) if
					//            multiple declarations are common.
					if ,  := .(*FuncDecl);  {
						 := nameOf()
						if ,  := [];  {
							// function declared already
							if [] != nil && [].(*FuncDecl).Doc == nil {
								// existing declaration has no documentation;
								// ignore the existing declaration
								[] = nil
							} else {
								// ignore the new declaration
								 = nil
							}
							++ // filtered an entry
						} else {
							[] = 
						}
					}
				}
				[] = 
				++
			}
		}

		// Eliminate nil entries from the decls list if entries were
		// filtered. We do this using a 2nd pass in order to not disturb
		// the original declaration order in the source (otherwise, this
		// would also invalidate the monotonically increasing position
		// info within a single file).
		if  > 0 {
			 = 0
			for ,  := range  {
				if  != nil {
					[] = 
					++
				}
			}
			 = [0:]
		}
	}

	// Collect import specs from all package files.
	var  []*ImportSpec
	if &FilterImportDuplicates != 0 {
		 := make(map[string]bool)
		for ,  := range  {
			 := .Files[]
			for ,  := range .Imports {
				if  := .Path.Value; ![] {
					// TODO: consider handling cases where:
					// - 2 imports exist with the same import path but
					//   have different local names (one should probably
					//   keep both of them)
					// - 2 imports exist but only one has a comment
					// - 2 imports exist and they both have (possibly
					//   different) comments
					 = append(, )
					[] = true
				}
			}
		}
	} else {
		// Iterate over filenames for deterministic order.
		for ,  := range  {
			 := .Files[]
			 = append(, .Imports...)
		}
	}

	// Collect comments from all package files.
	var  []*CommentGroup
	if &FilterUnassociatedComments == 0 {
		 = make([]*CommentGroup, )
		 := 0
		for ,  := range  {
			 := .Files[]
			 += copy([:], .Comments)
		}
	}

	// TODO(gri) need to compute unresolved identifiers!
	return &File{, , NewIdent(.Name), , , , .Scope, , nil, , ""}
}