src/go/doc/reader.go GO 1,012 lines View on github.com → Search inside
1// Copyright 2009 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package doc67import (8	"cmp"9	"fmt"10	"go/ast"11	"go/token"12	"internal/lazyregexp"13	"path"14	"slices"15	"strconv"16	"strings"17	"unicode"18	"unicode/utf8"19)2021// ----------------------------------------------------------------------------22// function/method sets23//24// Internally, we treat functions like methods and collect them in method sets.2526// A methodSet describes a set of methods. Entries where Decl == nil are conflict27// entries (more than one method with the same name at the same embedding level).28type methodSet map[string]*Func2930// recvString returns a string representation of recv of the form "T", "*T",31// "T[A, ...]", "*T[A, ...]" or "BADRECV" (if not a proper receiver type).32func recvString(recv ast.Expr) string {33	switch t := recv.(type) {34	case *ast.Ident:35		return t.Name36	case *ast.StarExpr:37		return "*" + recvString(t.X)38	case *ast.IndexExpr:39		// Generic type with one parameter.40		return fmt.Sprintf("%s[%s]", recvString(t.X), recvParam(t.Index))41	case *ast.IndexListExpr:42		// Generic type with multiple parameters.43		if len(t.Indices) > 0 {44			var b strings.Builder45			b.WriteString(recvString(t.X))46			b.WriteByte('[')47			b.WriteString(recvParam(t.Indices[0]))48			for _, e := range t.Indices[1:] {49				b.WriteString(", ")50				b.WriteString(recvParam(e))51			}52			b.WriteByte(']')53			return b.String()54		}55	}56	return "BADRECV"57}5859func recvParam(p ast.Expr) string {60	if id, ok := p.(*ast.Ident); ok {61		return id.Name62	}63	return "BADPARAM"64}6566// set creates the corresponding Func for f and adds it to mset.67// If there are multiple f's with the same name, set keeps the first68// one with documentation; conflicts are ignored. The boolean69// specifies whether to leave the AST untouched.70func (mset methodSet) set(f *ast.FuncDecl, preserveAST bool) {71	name := f.Name.Name72	if g := mset[name]; g != nil && g.Doc != "" {73		// A function with the same name has already been registered;74		// since it has documentation, assume f is simply another75		// implementation and ignore it. This does not happen if the76		// caller is using go/build.ScanDir to determine the list of77		// files implementing a package.78		return79	}80	// function doesn't exist or has no documentation; use f81	recv := ""82	if f.Recv != nil {83		var typ ast.Expr84		// be careful in case of incorrect ASTs85		if list := f.Recv.List; len(list) == 1 {86			typ = list[0].Type87		}88		recv = recvString(typ)89	}90	mset[name] = &Func{91		Doc:  f.Doc.Text(),92		Name: name,93		Decl: f,94		Recv: recv,95		Orig: recv,96	}97	if !preserveAST {98		f.Doc = nil // doc consumed - remove from AST99	}100}101102// add adds method m to the method set; m is ignored if the method set103// already contains a method with the same name at the same or a higher104// level than m.105func (mset methodSet) add(m *Func) {106	old := mset[m.Name]107	if old == nil || m.Level < old.Level {108		mset[m.Name] = m109		return110	}111	if m.Level == old.Level {112		// conflict - mark it using a method with nil Decl113		mset[m.Name] = &Func{114			Name:  m.Name,115			Level: m.Level,116		}117	}118}119120// ----------------------------------------------------------------------------121// Named types122123// baseTypeName returns the name of the base type of x (or "")124// and whether the type is imported or not.125func baseTypeName(x ast.Expr) (name string, imported bool) {126	switch t := x.(type) {127	case *ast.Ident:128		return t.Name, false129	case *ast.IndexExpr:130		return baseTypeName(t.X)131	case *ast.IndexListExpr:132		return baseTypeName(t.X)133	case *ast.SelectorExpr:134		if _, ok := t.X.(*ast.Ident); ok {135			// only possible for qualified type names;136			// assume type is imported137			return t.Sel.Name, true138		}139	case *ast.ParenExpr:140		return baseTypeName(t.X)141	case *ast.StarExpr:142		return baseTypeName(t.X)143	}144	return "", false145}146147// An embeddedSet describes a set of embedded types.148type embeddedSet map[*namedType]bool149150// A namedType represents a named unqualified (package local, or possibly151// predeclared) type. The namedType for a type name is always found via152// reader.lookupType.153type namedType struct {154	doc  string       // doc comment for type155	name string       // type name156	decl *ast.GenDecl // nil if declaration hasn't been seen yet157158	isEmbedded bool        // true if this type is embedded159	isStruct   bool        // true if this type is a struct160	embedded   embeddedSet // true if the embedded type is a pointer161162	// associated declarations163	values  []*Value // consts and vars164	funcs   methodSet165	methods methodSet166}167168// ----------------------------------------------------------------------------169// AST reader170171// reader accumulates documentation for a single package.172// It modifies the AST: Comments (declaration documentation)173// that have been collected by the reader are set to nil174// in the respective AST nodes so that they are not printed175// twice (once when printing the documentation and once when176// printing the corresponding AST node).177type reader struct {178	mode Mode179180	// package properties181	doc       string // package documentation, if any182	filenames []string183	notes     map[string][]*Note184185	// imports186	imports      map[string]int187	hasDotImp    bool // if set, package contains a dot import188	importByName map[string]string189190	// declarations191	values []*Value // consts and vars192	order  int      // sort order of const and var declarations (when we can't use a name)193	types  map[string]*namedType194	funcs  methodSet195196	// support for package-local shadowing of predeclared types197	shadowedPredecl map[string]bool198	fixmap          map[string][]*ast.InterfaceType199}200201func (r *reader) isVisible(name string) bool {202	return r.mode&AllDecls != 0 || token.IsExported(name)203}204205// lookupType returns the base type with the given name.206// If the base type has not been encountered yet, a new207// type with the given name but no associated declaration208// is added to the type map.209func (r *reader) lookupType(name string) *namedType {210	if name == "" || name == "_" {211		return nil // no type docs for anonymous types212	}213	if typ, found := r.types[name]; found {214		return typ215	}216	// type not found - add one without declaration217	typ := &namedType{218		name:     name,219		embedded: make(embeddedSet),220		funcs:    make(methodSet),221		methods:  make(methodSet),222	}223	r.types[name] = typ224	return typ225}226227// recordAnonymousField registers fieldType as the type of an228// anonymous field in the parent type. If the field is imported229// (qualified name) or the parent is nil, the field is ignored.230// The function returns the field name.231func (r *reader) recordAnonymousField(parent *namedType, fieldType ast.Expr) (fname string) {232	fname, imp := baseTypeName(fieldType)233	if parent == nil || imp {234		return235	}236	if ftype := r.lookupType(fname); ftype != nil {237		ftype.isEmbedded = true238		_, ptr := fieldType.(*ast.StarExpr)239		parent.embedded[ftype] = ptr240	}241	return242}243244func (r *reader) readDoc(comment *ast.CommentGroup) {245	// By convention there should be only one package comment246	// but collect all of them if there are more than one.247	text := comment.Text()248	if r.doc == "" {249		r.doc = text250		return251	}252	r.doc += "\n" + text253}254255func (r *reader) remember(predecl string, typ *ast.InterfaceType) {256	if r.fixmap == nil {257		r.fixmap = make(map[string][]*ast.InterfaceType)258	}259	r.fixmap[predecl] = append(r.fixmap[predecl], typ)260}261262func specNames(specs []ast.Spec) []string {263	names := make([]string, 0, len(specs)) // reasonable estimate264	for _, s := range specs {265		// s guaranteed to be an *ast.ValueSpec by readValue266		for _, ident := range s.(*ast.ValueSpec).Names {267			names = append(names, ident.Name)268		}269	}270	return names271}272273// readValue processes a const or var declaration.274func (r *reader) readValue(decl *ast.GenDecl) {275	// determine if decl should be associated with a type276	// Heuristic: For each typed entry, determine the type name, if any.277	//            If there is exactly one type name that is sufficiently278	//            frequent, associate the decl with the respective type.279	domName := ""280	domFreq := 0281	prev := ""282	n := 0283	for _, spec := range decl.Specs {284		s, ok := spec.(*ast.ValueSpec)285		if !ok {286			continue // should not happen, but be conservative287		}288		name := ""289		switch {290		case s.Type != nil:291			// a type is present; determine its name292			if n, imp := baseTypeName(s.Type); !imp {293				name = n294			}295		case decl.Tok == token.CONST && len(s.Values) == 0:296			// no type or value is present but we have a constant declaration;297			// use the previous type name (possibly the empty string)298			name = prev299		}300		if name != "" {301			// entry has a named type302			if domName != "" && domName != name {303				// more than one type name - do not associate304				// with any type305				domName = ""306				break307			}308			domName = name309			domFreq++310		}311		prev = name312		n++313	}314315	// nothing to do w/o a legal declaration316	if n == 0 {317		return318	}319320	// determine values list with which to associate the Value for this decl321	values := &r.values322	const threshold = 0.75323	if domName != "" && r.isVisible(domName) && domFreq >= int(float64(len(decl.Specs))*threshold) {324		// typed entries are sufficiently frequent325		if typ := r.lookupType(domName); typ != nil {326			values = &typ.values // associate with that type327		}328	}329330	*values = append(*values, &Value{331		Doc:   decl.Doc.Text(),332		Names: specNames(decl.Specs),333		Decl:  decl,334		order: r.order,335	})336	if r.mode&PreserveAST == 0 {337		decl.Doc = nil // doc consumed - remove from AST338	}339	// Note: It's important that the order used here is global because the cleanupTypes340	// methods may move values associated with types back into the global list. If the341	// order is list-specific, sorting is not deterministic because the same order value342	// may appear multiple times (was bug, found when fixing #16153).343	r.order++344}345346// fields returns a struct's fields or an interface's methods.347func fields(typ ast.Expr) (list []*ast.Field, isStruct bool) {348	var fields *ast.FieldList349	switch t := typ.(type) {350	case *ast.StructType:351		fields = t.Fields352		isStruct = true353	case *ast.InterfaceType:354		fields = t.Methods355	}356	if fields != nil {357		list = fields.List358	}359	return360}361362// readType processes a type declaration.363func (r *reader) readType(decl *ast.GenDecl, spec *ast.TypeSpec) {364	typ := r.lookupType(spec.Name.Name)365	if typ == nil {366		return // no name or blank name - ignore the type367	}368369	// A type should be added at most once, so typ.decl370	// should be nil - if it is not, simply overwrite it.371	typ.decl = decl372373	// compute documentation374	doc := spec.Doc375	if doc == nil {376		// no doc associated with the spec, use the declaration doc, if any377		doc = decl.Doc378	}379	if r.mode&PreserveAST == 0 {380		spec.Doc = nil // doc consumed - remove from AST381		decl.Doc = nil // doc consumed - remove from AST382	}383	typ.doc = doc.Text()384385	// record anonymous fields (they may contribute methods)386	// (some fields may have been recorded already when filtering387	// exports, but that's ok)388	var list []*ast.Field389	list, typ.isStruct = fields(spec.Type)390	for _, field := range list {391		if len(field.Names) == 0 {392			r.recordAnonymousField(typ, field.Type)393		}394	}395}396397// isPredeclared reports whether n denotes a predeclared type.398func (r *reader) isPredeclared(n string) bool {399	return predeclaredTypes[n] && r.types[n] == nil400}401402// readFunc processes a func or method declaration.403func (r *reader) readFunc(fun *ast.FuncDecl) {404	// strip function body if requested.405	if r.mode&PreserveAST == 0 {406		fun.Body = nil407	}408409	// associate methods with the receiver type, if any410	if fun.Recv != nil {411		// method412		if len(fun.Recv.List) == 0 {413			// should not happen (incorrect AST); (See issue 17788)414			// don't show this method415			return416		}417		recvTypeName, imp := baseTypeName(fun.Recv.List[0].Type)418		if imp {419			// should not happen (incorrect AST);420			// don't show this method421			return422		}423		if typ := r.lookupType(recvTypeName); typ != nil {424			typ.methods.set(fun, r.mode&PreserveAST != 0)425		}426		// otherwise ignore the method427		// TODO(gri): There may be exported methods of non-exported types428		// that can be called because of exported values (consts, vars, or429		// function results) of that type. Could determine if that is the430		// case and then show those methods in an appropriate section.431		return432	}433434	// Associate factory functions with the first visible result type, as long as435	// others are predeclared types.436	if fun.Type.Results.NumFields() >= 1 {437		var typ *namedType // type to associate the function with438		numResultTypes := 0439		for _, res := range fun.Type.Results.List {440			factoryType := res.Type441			if t, ok := factoryType.(*ast.ArrayType); ok {442				// We consider functions that return slices or arrays of type443				// T (or pointers to T) as factory functions of T.444				factoryType = t.Elt445			}446			if n, imp := baseTypeName(factoryType); !imp && r.isVisible(n) && !r.isPredeclared(n) {447				if lookupTypeParam(n, fun.Type.TypeParams) != nil {448					// Issue #49477: don't associate fun with its type parameter result.449					// A type parameter is not a defined type.450					continue451				}452				if t := r.lookupType(n); t != nil {453					typ = t454					numResultTypes++455					if numResultTypes > 1 {456						break457					}458				}459			}460		}461		// If there is exactly one result type,462		// associate the function with that type.463		if numResultTypes == 1 {464			typ.funcs.set(fun, r.mode&PreserveAST != 0)465			return466		}467	}468469	// just an ordinary function470	r.funcs.set(fun, r.mode&PreserveAST != 0)471}472473// lookupTypeParam searches for type parameters named name within the tparams474// field list, returning the relevant identifier if found, or nil if not.475func lookupTypeParam(name string, tparams *ast.FieldList) *ast.Ident {476	if tparams == nil {477		return nil478	}479	for _, field := range tparams.List {480		for _, id := range field.Names {481			if id.Name == name {482				return id483			}484		}485	}486	return nil487}488489var (490	noteMarker    = `([A-Z][A-Z]+)\(([^)]+)\):?`                // MARKER(uid), MARKER at least 2 chars, uid at least 1 char491	noteMarkerRx  = lazyregexp.New(`^[ \t]*` + noteMarker)      // MARKER(uid) at text start492	noteCommentRx = lazyregexp.New(`^/[/*][ \t]*` + noteMarker) // MARKER(uid) at comment start493)494495// clean replaces each sequence of space, \r, or \t characters496// with a single space and removes any trailing and leading spaces.497func clean(s string) string {498	var b []byte499	p := byte(' ')500	for i := 0; i < len(s); i++ {501		q := s[i]502		if q == '\r' || q == '\t' {503			q = ' '504		}505		if q != ' ' || p != ' ' {506			b = append(b, q)507			p = q508		}509	}510	// remove trailing blank, if any511	if n := len(b); n > 0 && p == ' ' {512		b = b[0 : n-1]513	}514	return string(b)515}516517// readNote collects a single note from a sequence of comments.518func (r *reader) readNote(list []*ast.Comment) {519	text := (&ast.CommentGroup{List: list}).Text()520	if m := noteMarkerRx.FindStringSubmatchIndex(text); m != nil {521		// The note body starts after the marker.522		// We remove any formatting so that we don't523		// get spurious line breaks/indentation when524		// showing the TODO body.525		body := clean(text[m[1]:])526		if body != "" {527			marker := text[m[2]:m[3]]528			r.notes[marker] = append(r.notes[marker], &Note{529				Pos:  list[0].Pos(),530				End:  list[len(list)-1].End(),531				UID:  text[m[4]:m[5]],532				Body: body,533			})534		}535	}536}537538// readNotes extracts notes from comments.539// A note must start at the beginning of a comment with "MARKER(uid):"540// and is followed by the note body (e.g., "// BUG(gri): fix this").541// The note ends at the end of the comment group or at the start of542// another note in the same comment group, whichever comes first.543func (r *reader) readNotes(comments []*ast.CommentGroup) {544	for _, group := range comments {545		i := -1 // comment index of most recent note start, valid if >= 0546		list := group.List547		for j, c := range list {548			if noteCommentRx.MatchString(c.Text) {549				if i >= 0 {550					r.readNote(list[i:j])551				}552				i = j553			}554		}555		if i >= 0 {556			r.readNote(list[i:])557		}558	}559}560561// readFile adds the AST for a source file to the reader.562func (r *reader) readFile(src *ast.File) {563	// add package documentation564	if src.Doc != nil {565		r.readDoc(src.Doc)566		if r.mode&PreserveAST == 0 {567			src.Doc = nil // doc consumed - remove from AST568		}569	}570571	// add all declarations but for functions which are processed in a separate pass572	for _, decl := range src.Decls {573		switch d := decl.(type) {574		case *ast.GenDecl:575			switch d.Tok {576			case token.IMPORT:577				// imports are handled individually578				for _, spec := range d.Specs {579					if s, ok := spec.(*ast.ImportSpec); ok {580						if import_, err := strconv.Unquote(s.Path.Value); err == nil {581							r.imports[import_] = 1582							var name string583							if s.Name != nil {584								name = s.Name.Name585								if name == "." {586									r.hasDotImp = true587								}588							}589							if name != "." {590								if name == "" {591									name = assumedPackageName(import_)592								}593								old, ok := r.importByName[name]594								if !ok {595									r.importByName[name] = import_596								} else if old != import_ && old != "" {597									r.importByName[name] = "" // ambiguous598								}599							}600						}601					}602				}603			case token.CONST, token.VAR:604				// constants and variables are always handled as a group605				r.readValue(d)606			case token.TYPE:607				// types are handled individually608				if len(d.Specs) == 1 && !d.Lparen.IsValid() {609					// common case: single declaration w/o parentheses610					// (if a single declaration is parenthesized,611					// create a new fake declaration below, so that612					// go/doc type declarations always appear w/o613					// parentheses)614					if s, ok := d.Specs[0].(*ast.TypeSpec); ok {615						r.readType(d, s)616					}617					break618				}619				for _, spec := range d.Specs {620					if s, ok := spec.(*ast.TypeSpec); ok {621						// use an individual (possibly fake) declaration622						// for each type; this also ensures that each type623						// gets to (re-)use the declaration documentation624						// if there's none associated with the spec itself625						fake := &ast.GenDecl{626							Doc: d.Doc,627							// don't use the existing TokPos because it628							// will lead to the wrong selection range for629							// the fake declaration if there are more630							// than one type in the group (this affects631							// src/cmd/godoc/godoc.go's posLink_urlFunc)632							TokPos: s.Pos(),633							Tok:    token.TYPE,634							Specs:  []ast.Spec{s},635						}636						r.readType(fake, s)637					}638				}639			}640		}641	}642643	// collect MARKER(...): annotations644	r.readNotes(src.Comments)645	if r.mode&PreserveAST == 0 {646		src.Comments = nil // consumed unassociated comments - remove from AST647	}648}649650func (r *reader) readPackage(pkg *ast.Package, mode Mode) {651	// initialize reader652	r.filenames = make([]string, len(pkg.Files))653	r.imports = make(map[string]int)654	r.mode = mode655	r.types = make(map[string]*namedType)656	r.funcs = make(methodSet)657	r.notes = make(map[string][]*Note)658	r.importByName = make(map[string]string)659660	// sort package files before reading them so that the661	// result does not depend on map iteration order662	i := 0663	for filename := range pkg.Files {664		r.filenames[i] = filename665		i++666	}667	slices.Sort(r.filenames)668669	// process files in sorted order670	for _, filename := range r.filenames {671		f := pkg.Files[filename]672		if mode&AllDecls == 0 {673			r.fileExports(f)674		}675		r.readFile(f)676	}677678	for name, path := range r.importByName {679		if path == "" {680			delete(r.importByName, name)681		}682	}683684	// process functions now that we have better type information685	for _, f := range pkg.Files {686		for _, decl := range f.Decls {687			if d, ok := decl.(*ast.FuncDecl); ok {688				r.readFunc(d)689			}690		}691	}692}693694// ----------------------------------------------------------------------------695// Types696697func customizeRecv(f *Func, recvTypeName string, embeddedIsPtr bool, level int) *Func {698	if f == nil || f.Decl == nil || f.Decl.Recv == nil || len(f.Decl.Recv.List) != 1 {699		return f // shouldn't happen, but be safe700	}701702	// copy existing receiver field and set new type703	newField := *f.Decl.Recv.List[0]704	origPos := newField.Type.Pos()705	_, origRecvIsPtr := newField.Type.(*ast.StarExpr)706	newIdent := &ast.Ident{NamePos: origPos, Name: recvTypeName}707	var typ ast.Expr = newIdent708	if !embeddedIsPtr && origRecvIsPtr {709		newIdent.NamePos++ // '*' is one character710		typ = &ast.StarExpr{Star: origPos, X: newIdent}711	}712	newField.Type = typ713714	// copy existing receiver field list and set new receiver field715	newFieldList := *f.Decl.Recv716	newFieldList.List = []*ast.Field{&newField}717718	// copy existing function declaration and set new receiver field list719	newFuncDecl := *f.Decl720	newFuncDecl.Recv = &newFieldList721722	// copy existing function documentation and set new declaration723	newF := *f724	newF.Decl = &newFuncDecl725	newF.Recv = recvString(typ)726	// the Orig field never changes727	newF.Level = level728729	return &newF730}731732// collectEmbeddedMethods collects the embedded methods of typ in mset.733func (r *reader) collectEmbeddedMethods(mset methodSet, typ *namedType, recvTypeName string, embeddedIsPtr bool, level int, visited embeddedSet) {734	visited[typ] = true735	for embedded, isPtr := range typ.embedded {736		// Once an embedded type is embedded as a pointer type737		// all embedded types in those types are treated like738		// pointer types for the purpose of the receiver type739		// computation; i.e., embeddedIsPtr is sticky for this740		// embedding hierarchy.741		thisEmbeddedIsPtr := embeddedIsPtr || isPtr742		for _, m := range embedded.methods {743			// only top-level methods are embedded744			if m.Level == 0 {745				mset.add(customizeRecv(m, recvTypeName, thisEmbeddedIsPtr, level))746			}747		}748		if !visited[embedded] {749			r.collectEmbeddedMethods(mset, embedded, recvTypeName, thisEmbeddedIsPtr, level+1, visited)750		}751	}752	delete(visited, typ)753}754755// computeMethodSets determines the actual method sets for each type encountered.756func (r *reader) computeMethodSets() {757	for _, t := range r.types {758		// collect embedded methods for t759		if t.isStruct {760			// struct761			r.collectEmbeddedMethods(t.methods, t, t.name, false, 1, make(embeddedSet))762		} else {763			// interface764			// TODO(gri) fix this765		}766	}767768	// For any predeclared names that are declared locally, don't treat them as769	// exported fields anymore.770	for predecl := range r.shadowedPredecl {771		for _, ityp := range r.fixmap[predecl] {772			removeAnonymousField(predecl, ityp)773		}774	}775}776777// cleanupTypes removes the association of functions and methods with778// types that have no declaration. Instead, these functions and methods779// are shown at the package level. It also removes types with missing780// declarations or which are not visible.781func (r *reader) cleanupTypes() {782	for _, t := range r.types {783		visible := r.isVisible(t.name)784		predeclared := predeclaredTypes[t.name]785786		if t.decl == nil && (predeclared || visible && (t.isEmbedded || r.hasDotImp)) {787			// t.name is a predeclared type (and was not redeclared in this package),788			// or it was embedded somewhere but its declaration is missing (because789			// the AST is incomplete), or we have a dot-import (and all bets are off):790			// move any associated values, funcs, and methods back to the top-level so791			// that they are not lost.792			// 1) move values793			r.values = append(r.values, t.values...)794			// 2) move factory functions795			for name, f := range t.funcs {796				// in a correct AST, package-level function names797				// are all different - no need to check for conflicts798				r.funcs[name] = f799			}800			// 3) move methods801			if !predeclared {802				for name, m := range t.methods {803					// don't overwrite functions with the same name - drop them804					if _, found := r.funcs[name]; !found {805						r.funcs[name] = m806					}807				}808			}809		}810		// remove types w/o declaration or which are not visible811		if t.decl == nil || !visible {812			delete(r.types, t.name)813		}814	}815}816817// ----------------------------------------------------------------------------818// Sorting819820func sortedKeys(m map[string]int) []string {821	list := make([]string, len(m))822	i := 0823	for key := range m {824		list[i] = key825		i++826	}827	slices.Sort(list)828	return list829}830831// sortingName returns the name to use when sorting d into place.832func sortingName(d *ast.GenDecl) string {833	if len(d.Specs) == 1 {834		if s, ok := d.Specs[0].(*ast.ValueSpec); ok {835			return s.Names[0].Name836		}837	}838	return ""839}840841func sortedValues(m []*Value, tok token.Token) []*Value {842	list := make([]*Value, len(m)) // big enough in any case843	i := 0844	for _, val := range m {845		if val.Decl.Tok == tok {846			list[i] = val847			i++848		}849	}850	list = list[0:i]851852	slices.SortFunc(list, func(a, b *Value) int {853		r := strings.Compare(sortingName(a.Decl), sortingName(b.Decl))854		if r != 0 {855			return r856		}857		return cmp.Compare(a.order, b.order)858	})859860	return list861}862863func sortedTypes(m map[string]*namedType, allMethods bool) []*Type {864	list := make([]*Type, len(m))865	i := 0866	for _, t := range m {867		list[i] = &Type{868			Doc:     t.doc,869			Name:    t.name,870			Decl:    t.decl,871			Consts:  sortedValues(t.values, token.CONST),872			Vars:    sortedValues(t.values, token.VAR),873			Funcs:   sortedFuncs(t.funcs, true),874			Methods: sortedFuncs(t.methods, allMethods),875		}876		i++877	}878879	slices.SortFunc(list, func(a, b *Type) int {880		return strings.Compare(a.Name, b.Name)881	})882883	return list884}885886func removeStar(s string) string {887	if len(s) > 0 && s[0] == '*' {888		return s[1:]889	}890	return s891}892893func sortedFuncs(m methodSet, allMethods bool) []*Func {894	list := make([]*Func, len(m))895	i := 0896	for _, m := range m {897		// determine which methods to include898		switch {899		case m.Decl == nil:900			// exclude conflict entry901		case allMethods, m.Level == 0, !token.IsExported(removeStar(m.Orig)):902			// forced inclusion, method not embedded, or method903			// embedded but original receiver type not exported904			list[i] = m905			i++906		}907	}908	list = list[0:i]909	slices.SortFunc(list, func(a, b *Func) int {910		return strings.Compare(a.Name, b.Name)911	})912	return list913}914915// noteBodies returns a list of note body strings given a list of notes.916// This is only used to populate the deprecated Package.Bugs field.917func noteBodies(notes []*Note) []string {918	var list []string919	for _, n := range notes {920		list = append(list, n.Body)921	}922	return list923}924925// ----------------------------------------------------------------------------926// Predeclared identifiers927928// IsPredeclared reports whether s is a predeclared identifier.929func IsPredeclared(s string) bool {930	return predeclaredTypes[s] || predeclaredFuncs[s] || predeclaredConstants[s]931}932933var predeclaredTypes = map[string]bool{934	"any":        true,935	"bool":       true,936	"byte":       true,937	"comparable": true,938	"complex64":  true,939	"complex128": true,940	"error":      true,941	"float32":    true,942	"float64":    true,943	"int":        true,944	"int8":       true,945	"int16":      true,946	"int32":      true,947	"int64":      true,948	"rune":       true,949	"string":     true,950	"uint":       true,951	"uint8":      true,952	"uint16":     true,953	"uint32":     true,954	"uint64":     true,955	"uintptr":    true,956}957958var predeclaredFuncs = map[string]bool{959	"append":  true,960	"cap":     true,961	"clear":   true,962	"close":   true,963	"complex": true,964	"copy":    true,965	"delete":  true,966	"imag":    true,967	"len":     true,968	"make":    true,969	"max":     true,970	"min":     true,971	"new":     true,972	"panic":   true,973	"print":   true,974	"println": true,975	"real":    true,976	"recover": true,977}978979var predeclaredConstants = map[string]bool{980	"false": true,981	"iota":  true,982	"nil":   true,983	"true":  true,984}985986// assumedPackageName returns the assumed package name987// for a given import path. This is a copy of988// golang.org/x/tools/internal/imports.ImportPathToAssumedName.989func assumedPackageName(importPath string) string {990	notIdentifier := func(ch rune) bool {991		return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' ||992			'0' <= ch && ch <= '9' ||993			ch == '_' ||994			ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch)))995	}996997	base := path.Base(importPath)998	if strings.HasPrefix(base, "v") {999		if _, err := strconv.Atoi(base[1:]); err == nil {1000			dir := path.Dir(importPath)1001			if dir != "." {1002				base = path.Base(dir)1003			}1004		}1005	}1006	base = strings.TrimPrefix(base, "go-")1007	if i := strings.IndexFunc(base, notIdentifier); i >= 0 {1008		base = base[:i]1009	}1010	return base1011}

Code quality findings 35

Declared map variable without initialization; writing to a nil map causes a panic. Use make() to initialize
warning correctness nil-map-write
var predeclaredTypes = map[string]bool{
Declared map variable without initialization; writing to a nil map causes a panic. Use make() to initialize
warning correctness nil-map-write
var predeclaredFuncs = map[string]bool{
Declared map variable without initialization; writing to a nil map causes a panic. Use make() to initialize
warning correctness nil-map-write
var predeclaredConstants = map[string]bool{
Type switch without default case; unhandled types will silently do nothing. Add a default case for safety
info correctness unchecked-type-switch
switch t := recv.(type) {
Type switch without default case; unhandled types will silently do nothing. Add a default case for safety
info correctness unchecked-type-switch
switch t := x.(type) {
Map created without size hint before being populated in a loop; provide capacity hint to reduce allocations
info performance map-without-size-hint
r.fixmap = make(map[string][]*ast.InterfaceType)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
*values = append(*values, &Value{
Type switch without default case; unhandled types will silently do nothing. Add a default case for safety
info correctness unchecked-type-switch
switch t := typ.(type) {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if n, imp := baseTypeName(factoryType); !imp && r.isVisible(n) && !r.isPredeclared(n) {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
b = append(b, q)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
r.notes[marker] = append(r.notes[marker], &Note{
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for j, c := range list {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for j, c := range list {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if noteCommentRx.MatchString(c.Text) {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if i >= 0 {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
// add all declarations but for functions which are processed in a separate pass
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for _, decl := range src.Decls {
Type switch without default case; unhandled types will silently do nothing. Add a default case for safety
info correctness unchecked-type-switch
switch d := decl.(type) {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
switch d := decl.(type) {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
switch d.Tok {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for _, spec := range d.Specs {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if s, ok := spec.(*ast.ImportSpec); ok {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if import_, err := strconv.Unquote(s.Path.Value); err == nil {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if s.Name != nil {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if name == "." {
Map created without size hint before being populated in a loop; provide capacity hint to reduce allocations
info performance map-without-size-hint
r.imports = make(map[string]int)
Map created without size hint before being populated in a loop; provide capacity hint to reduce allocations
info performance map-without-size-hint
r.types = make(map[string]*namedType)
Map created without size hint before being populated in a loop; provide capacity hint to reduce allocations
info performance map-without-size-hint
r.notes = make(map[string][]*Note)
Map created without size hint before being populated in a loop; provide capacity hint to reduce allocations
info performance map-without-size-hint
r.importByName = make(map[string]string)
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for name, path := range r.importByName {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for embedded, isPtr := range typ.embedded {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
r.values = append(r.values, t.values...)
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for name, f := range t.funcs {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for name, m := range t.methods {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
list = append(list, n.Body)

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.