src/cmd/go/internal/modload/query.go GO 1,350 lines View on github.com → Search inside
1// Copyright 2018 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 modload67import (8	"bytes"9	"context"10	"errors"11	"fmt"12	"io/fs"13	"os"14	pathpkg "path"15	"slices"16	"sort"17	"strings"18	"sync"19	"time"2021	"cmd/go/internal/cfg"22	"cmd/go/internal/gover"23	"cmd/go/internal/imports"24	"cmd/go/internal/modfetch"25	"cmd/go/internal/modfetch/codehost"26	"cmd/go/internal/modinfo"27	"cmd/go/internal/search"28	"cmd/go/internal/str"29	"cmd/go/internal/trace"30	"cmd/internal/pkgpattern"3132	"golang.org/x/mod/module"33	"golang.org/x/mod/semver"34)3536// Query looks up a revision of a given module given a version query string.37// The module must be a complete module path.38// The version must take one of the following forms:39//40//   - the literal string "latest", denoting the latest available, allowed41//     tagged version, with non-prereleases preferred over prereleases.42//     If there are no tagged versions in the repo, latest returns the most43//     recent commit.44//45//   - the literal string "upgrade", equivalent to "latest" except that if46//     current is a newer version, current will be returned (see below).47//48//   - the literal string "patch", denoting the latest available tagged version49//     with the same major and minor number as current (see below).50//51//   - v1, denoting the latest available tagged version v1.x.x.52//53//   - v1.2, denoting the latest available tagged version v1.2.x.54//55//   - v1.2.3, a semantic version string denoting that tagged version.56//57//   - <v1.2.3, <=v1.2.3, >v1.2.3, >=v1.2.3,58//     denoting the version closest to the target and satisfying the given operator,59//     with non-prereleases preferred over prereleases.60//61//   - a repository commit identifier or tag, denoting that commit.62//63// current denotes the currently-selected version of the module; it may be64// "none" if no version is currently selected, or "" if the currently-selected65// version is unknown or should not be considered. If query is66// "upgrade" or "patch", current will be returned if it is a newer67// semantic version or a chronologically later pseudo-version than the68// version that would otherwise be chosen. This prevents accidental downgrades69// from newer pre-release or development versions.70//71// The allowed function (which may be nil) is used to filter out unsuitable72// versions (see AllowedFunc documentation for details). If the query refers to73// a specific revision (for example, "master"; see IsRevisionQuery), and the74// revision is disallowed by allowed, Query returns the error. If the query75// does not refer to a specific revision (for example, "latest"), Query76// acts as if versions disallowed by allowed do not exist.77//78// If path is the path of the main module and the query is "latest",79// Query returns Target.Version as the version.80//81// Query often returns a non-nil *RevInfo with a non-nil error,82// to provide an info.Origin that can allow the error to be cached.83func Query(ld *Loader, ctx context.Context, path, query, current string, allowed AllowedFunc) (*modfetch.RevInfo, error) {84	ctx, span := trace.StartSpan(ctx, "modload.Query "+path)85	defer span.Done()8687	return queryReuse(ld, ctx, path, query, current, allowed, nil)88}8990// queryReuse is like Query but also takes a map of module info that can be reused91// if the validation criteria in Origin are met.92func queryReuse(ld *Loader, ctx context.Context, path, query, current string, allowed AllowedFunc, reuse map[module.Version]*modinfo.ModulePublic) (*modfetch.RevInfo, error) {93	var info *modfetch.RevInfo94	err := modfetch.TryProxies(func(proxy string) (err error) {95		info, err = queryProxy(ld, ctx, proxy, path, query, current, allowed, reuse)96		return err97	})98	return info, err99}100101// checkReuse checks whether a revision of a given module102// for a given module may be reused, according to the information in origin.103func checkReuse(ld *Loader, ctx context.Context, m module.Version, old *codehost.Origin) error {104	return modfetch.TryProxies(func(proxy string) error {105		repo, err := lookupRepo(ld, ctx, proxy, m.Path)106		if err != nil {107			return err108		}109		return checkReuseRepo(ctx, repo, m.Path, m.Version, old)110	})111}112113func checkReuseRepo(ctx context.Context, repo versionRepo, path, query string, origin *codehost.Origin) error {114	if origin == nil {115		return errors.New("nil Origin")116	}117118	// Ensure that the Origin actually includes enough fields to resolve the query.119	// If we got the previous Origin data from a proxy, it may be missing something120	// that we would have needed to resolve the query directly from the repo.121	switch {122	case origin.RepoSum != "":123		// A RepoSum is always acceptable, since it incorporates everything124		// (and is often associated with an error result).125126	case query == module.CanonicalVersion(query):127		// This query refers to a specific version, and Go module versions128		// are supposed to be cacheable and immutable (confirmed with checksums).129		// If the version exists at all, we shouldn't need any extra information130		// to identify which commit it resolves to.131		//132		// It may be associated with a Ref for a semantic-version tag, but if so133		// we don't expect that tag to change in the future. We also don't need a134		// TagSum: if a tag is removed from some ancestor commit, the version may135		// change from valid to invalid, but we're ok with keeping stale versions136		// as long as they were valid at some point in the past.137		//138		// If the version did not successfully resolve, the origin may indicate139		// a TagSum and/or RepoSum instead of a Hash, in which case we still need140		// to check those to ensure that the error is still applicable.141		if origin.Hash == "" && origin.Ref == "" && origin.TagSum == "" {142			return errors.New("no Origin information to check")143		}144145	case IsRevisionQuery(path, query):146		// This query may refer to a branch, non-version tag, or commit ID.147		//148		// If it is a commit ID, we expect to see a Hash in the Origin data. On149		// the other hand, if it is not a commit ID, we expect to see either a Ref150		// (for a positive result) or a RepoSum (for a negative result), since151		// we don't expect refs in general to remain stable over time.152		if origin.Hash == "" && origin.Ref == "" {153			return fmt.Errorf("query %q requires a Hash or Ref", query)154		}155		// Once we resolve the query to a particular commit, we will need to156		// also identify the most appropriate version to assign to that commit.157		// (It may correspond to more than one valid version.)158		//159		// The most appropriate version depends on the tags associated with160		// both the commit itself (if the commit is a tagged version)161		// and its ancestors (if we need to produce a pseudo-version for it).162		if origin.TagSum == "" {163			return fmt.Errorf("query %q requires a TagSum", query)164		}165166	default:167		// The query may be "latest" or a version inequality or prefix.168		// Its result depends on the absence of higher tags matching the query,169		// not just the state of an individual ref or tag.170		if origin.TagSum == "" {171			return fmt.Errorf("query %q requires a TagSum", query)172		}173	}174175	return repo.CheckReuse(ctx, origin)176}177178// AllowedFunc is used by Query and other functions to filter out unsuitable179// versions, for example, those listed in exclude directives in the main180// module's go.mod file.181//182// An AllowedFunc returns an error equivalent to ErrDisallowed for an unsuitable183// version. Any other error indicates the function was unable to determine184// whether the version should be allowed, for example, the function was unable185// to fetch or parse a go.mod file containing retractions. Typically, errors186// other than ErrDisallowed may be ignored.187type AllowedFunc func(context.Context, module.Version) error188189var errQueryDisabled error = queryDisabledError{}190191type queryDisabledError struct{}192193func (queryDisabledError) Error() string {194	if cfg.BuildModReason == "" {195		return fmt.Sprintf("cannot query module due to -mod=%s", cfg.BuildMod)196	}197	return fmt.Sprintf("cannot query module due to -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)198}199200func queryProxy(ld *Loader, ctx context.Context, proxy, path, query, current string, allowed AllowedFunc, reuse map[module.Version]*modinfo.ModulePublic) (*modfetch.RevInfo, error) {201	ctx, span := trace.StartSpan(ctx, "modload.queryProxy "+path+" "+query)202	defer span.Done()203204	if current != "" && current != "none" && !gover.ModIsValid(path, current) {205		return nil, fmt.Errorf("invalid previous version %v@%v", path, current)206	}207	if cfg.BuildMod == "vendor" {208		return nil, errQueryDisabled209	}210	if allowed == nil {211		allowed = func(context.Context, module.Version) error { return nil }212	}213214	if ld.MainModules.Contains(path) && (query == "upgrade" || query == "patch") {215		m := module.Version{Path: path}216		if err := allowed(ctx, m); err != nil {217			return nil, fmt.Errorf("internal error: main module version is not allowed: %w", err)218		}219		return &modfetch.RevInfo{Version: m.Version}, nil220	}221222	if path == "std" || path == "cmd" {223		return nil, fmt.Errorf("can't query specific version (%q) of standard-library module %q", query, path)224	}225226	repo, err := lookupRepo(ld, ctx, proxy, path)227	if err != nil {228		return nil, err229	}230231	if old := reuse[module.Version{Path: path, Version: query}]; old != nil {232		if err := checkReuseRepo(ctx, repo, path, query, old.Origin); err == nil {233			info := &modfetch.RevInfo{234				Version: old.Version,235				Origin:  old.Origin,236			}237			if old.Time != nil {238				info.Time = *old.Time239			}240			return info, nil241		}242	}243244	// Parse query to detect parse errors (and possibly handle query)245	// before any network I/O.246	qm, err := newQueryMatcher(path, query, current, allowed)247	if (err == nil && qm.canStat) || err == errRevQuery {248		// Direct lookup of a commit identifier or complete (non-prefix) semantic249		// version.250251		// If the identifier is not a canonical semver tag — including if it's a252		// semver tag with a +metadata suffix — then modfetch.Stat will populate253		// info.Version with a suitable pseudo-version.254		info, err := repo.Stat(ctx, query)255		if err != nil {256			queryErr := err257			// The full query doesn't correspond to a tag. If it is a semantic version258			// with a +metadata suffix, see if there is a tag without that suffix:259			// semantic versioning defines them to be equivalent.260			canonicalQuery := module.CanonicalVersion(query)261			if canonicalQuery != "" && query != canonicalQuery {262				info, err = repo.Stat(ctx, canonicalQuery)263				if err != nil && !errors.Is(err, fs.ErrNotExist) {264					return info, err265				}266			}267			if err != nil {268				return info, queryErr269			}270		}271		if err := allowed(ctx, module.Version{Path: path, Version: info.Version}); errors.Is(err, ErrDisallowed) {272			return nil, err273		}274		return info, nil275	} else if err != nil {276		return nil, err277	}278279	// Load versions and execute query.280	versions, err := repo.Versions(ctx, qm.prefix)281	if err != nil {282		return nil, err283	}284	origin := versions.Origin285286	revWithOrigin := func(rev *modfetch.RevInfo) *modfetch.RevInfo {287		if rev == nil {288			if origin == nil {289				return nil290			}291			return &modfetch.RevInfo{Origin: origin}292		}293294		clone := *rev295		clone.Origin = origin296		return &clone297	}298299	releases, prereleases, err := qm.filterVersions(ld, ctx, versions.List)300	if err != nil {301		return revWithOrigin(nil), err302	}303304	lookup := func(v string) (*modfetch.RevInfo, error) {305		rev, err := repo.Stat(ctx, v)306		if rev != nil {307			// Note that Stat can return a non-nil rev and a non-nil err,308			// in order to provide origin information to make the error cacheable.309			origin = mergeOrigin(origin, rev.Origin)310		}311		if err != nil {312			return revWithOrigin(nil), err313		}314315		if (query == "upgrade" || query == "patch") && module.IsPseudoVersion(current) && !rev.Time.IsZero() {316			// Don't allow "upgrade" or "patch" to move from a pseudo-version317			// to a chronologically older version or pseudo-version.318			//319			// If the current version is a pseudo-version from an untagged branch, it320			// may be semantically lower than the "latest" release or the latest321			// pseudo-version on the main branch. A user on such a version is unlikely322			// to intend to “upgrade” to a version that already existed at that point323			// in time.324			//325			// We do this only if the current version is a pseudo-version: if the326			// version is tagged, the author of the dependency module has given us327			// explicit information about their intended precedence of this version328			// relative to other versions, and we shouldn't contradict that329			// information. (For example, v1.0.1 might be a backport of a fix already330			// incorporated into v1.1.0, in which case v1.0.1 would be chronologically331			// newer but v1.1.0 is still an “upgrade”; or v1.0.2 might be a revert of332			// an unsuccessful fix in v1.0.1, in which case the v1.0.2 commit may be333			// older than the v1.0.1 commit despite the tag itself being newer.)334			currentTime, err := module.PseudoVersionTime(current)335			if err == nil && rev.Time.Before(currentTime) {336				if err := allowed(ctx, module.Version{Path: path, Version: current}); errors.Is(err, ErrDisallowed) {337					return revWithOrigin(nil), err338				}339				rev, err = repo.Stat(ctx, current)340				if rev != nil {341					origin = mergeOrigin(origin, rev.Origin)342				}343				if err != nil {344					return revWithOrigin(nil), err345				}346				return revWithOrigin(rev), nil347			}348		}349350		return revWithOrigin(rev), nil351	}352353	if qm.preferLower {354		if len(releases) > 0 {355			return lookup(releases[0])356		}357		if len(prereleases) > 0 {358			return lookup(prereleases[0])359		}360	} else {361		if len(releases) > 0 {362			return lookup(releases[len(releases)-1])363		}364		if len(prereleases) > 0 {365			return lookup(prereleases[len(prereleases)-1])366		}367	}368369	if qm.mayUseLatest {370		latest, err := repo.Latest(ctx)371		if latest != nil {372			origin = mergeOrigin(origin, latest.Origin)373		}374		if err == nil {375			if qm.allowsVersion(ctx, latest.Version) {376				return lookup(latest.Version)377			}378		} else if !errors.Is(err, fs.ErrNotExist) {379			return revWithOrigin(nil), err380		}381	}382383	if (query == "upgrade" || query == "patch") && current != "" && current != "none" {384		// "upgrade" and "patch" may stay on the current version if allowed.385		if err := allowed(ctx, module.Version{Path: path, Version: current}); errors.Is(err, ErrDisallowed) {386			return revWithOrigin(nil), err387		}388		return lookup(current)389	}390391	return revWithOrigin(nil), &NoMatchingVersionError{query: query, current: current}392}393394// IsRevisionQuery returns true if vers is a version query that may refer to395// a particular version or revision in a repository like "v1.0.0", "master",396// or "0123abcd". IsRevisionQuery returns false if vers is a query that397// chooses from among available versions like "latest" or ">v1.0.0".398func IsRevisionQuery(path, vers string) bool {399	if vers == "latest" ||400		vers == "upgrade" ||401		vers == "patch" ||402		strings.HasPrefix(vers, "<") ||403		strings.HasPrefix(vers, ">") ||404		(gover.ModIsValid(path, vers) && gover.ModIsPrefix(path, vers)) {405		return false406	}407	return true408}409410type queryMatcher struct {411	path               string412	prefix             string413	filter             func(version string) bool414	allowed            AllowedFunc415	canStat            bool // if true, the query can be resolved by repo.Stat416	preferLower        bool // if true, choose the lowest matching version417	mayUseLatest       bool418	preferIncompatible bool419}420421var errRevQuery = errors.New("query refers to a non-semver revision")422423// newQueryMatcher returns a new queryMatcher that matches the versions424// specified by the given query on the module with the given path.425//426// If the query can only be resolved by statting a non-SemVer revision,427// newQueryMatcher returns errRevQuery.428func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (*queryMatcher, error) {429	badVersion := func(v string) (*queryMatcher, error) {430		return nil, fmt.Errorf("invalid semantic version %q in range %q", v, query)431	}432433	matchesMajor := func(v string) bool {434		_, pathMajor, ok := module.SplitPathVersion(path)435		if !ok {436			return false437		}438		return module.CheckPathMajor(v, pathMajor) == nil439	}440441	qm := &queryMatcher{442		path:               path,443		allowed:            allowed,444		preferIncompatible: strings.HasSuffix(current, "+incompatible"),445	}446447	switch {448	case query == "latest":449		qm.mayUseLatest = true450451	case query == "upgrade":452		if current == "" || current == "none" {453			qm.mayUseLatest = true454		} else {455			qm.mayUseLatest = module.IsPseudoVersion(current)456			qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, current) >= 0 }457		}458459	case query == "patch":460		if current == "" || current == "none" {461			return nil, &NoPatchBaseError{path}462		}463		if current == "" {464			qm.mayUseLatest = true465		} else {466			qm.mayUseLatest = module.IsPseudoVersion(current)467			qm.prefix = gover.ModMajorMinor(qm.path, current) + "."468			qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, current) >= 0 }469		}470471	case strings.HasPrefix(query, "<="):472		v := query[len("<="):]473		if !gover.ModIsValid(path, v) {474			return badVersion(v)475		}476		if gover.ModIsPrefix(path, v) {477			// Refuse to say whether <=v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3).478			return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query)479		}480		qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, v) <= 0 }481		if !matchesMajor(v) {482			qm.preferIncompatible = true483		}484485	case strings.HasPrefix(query, "<"):486		v := query[len("<"):]487		if !gover.ModIsValid(path, v) {488			return badVersion(v)489		}490		qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, v) < 0 }491		if !matchesMajor(v) {492			qm.preferIncompatible = true493		}494495	case strings.HasPrefix(query, ">="):496		v := query[len(">="):]497		if !gover.ModIsValid(path, v) {498			return badVersion(v)499		}500		qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, v) >= 0 }501		qm.preferLower = true502		if !matchesMajor(v) {503			qm.preferIncompatible = true504		}505506	case strings.HasPrefix(query, ">"):507		v := query[len(">"):]508		if !gover.ModIsValid(path, v) {509			return badVersion(v)510		}511		if gover.ModIsPrefix(path, v) {512			// Refuse to say whether >v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3).513			return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query)514		}515		qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, v) > 0 }516		qm.preferLower = true517		if !matchesMajor(v) {518			qm.preferIncompatible = true519		}520521	case gover.ModIsValid(path, query):522		if gover.ModIsPrefix(path, query) {523			qm.prefix = query + "."524			// Do not allow the query "v1.2" to match versions lower than "v1.2.0",525			// such as prereleases for that version. (https://golang.org/issue/31972)526			qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, query) >= 0 }527		} else {528			qm.canStat = true529			qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, query) == 0 }530			qm.prefix = semver.Canonical(query)531		}532		if !matchesMajor(query) {533			qm.preferIncompatible = true534		}535536	default:537		return nil, errRevQuery538	}539540	return qm, nil541}542543// allowsVersion reports whether version v is allowed by the prefix, filter, and544// AllowedFunc of qm.545func (qm *queryMatcher) allowsVersion(ctx context.Context, v string) bool {546	if qm.prefix != "" && !strings.HasPrefix(v, qm.prefix) {547		if gover.IsToolchain(qm.path) && strings.TrimSuffix(qm.prefix, ".") == v {548			// Allow 1.21 to match "1.21." prefix.549		} else {550			return false551		}552	}553	if qm.filter != nil && !qm.filter(v) {554		return false555	}556	if qm.allowed != nil {557		if err := qm.allowed(ctx, module.Version{Path: qm.path, Version: v}); errors.Is(err, ErrDisallowed) {558			return false559		}560	}561	return true562}563564// filterVersions classifies versions into releases and pre-releases, filtering565// out:566//  1. versions that do not satisfy the 'allowed' predicate, and567//  2. "+incompatible" versions, if a compatible one satisfies the predicate568//     and the incompatible version is not preferred.569//570// If the allowed predicate returns an error not equivalent to ErrDisallowed,571// filterVersions returns that error.572func (qm *queryMatcher) filterVersions(ld *Loader, ctx context.Context, versions []string) (releases, prereleases []string, err error) {573	needIncompatible := qm.preferIncompatible574575	var lastCompatible string576	for _, v := range versions {577		if !qm.allowsVersion(ctx, v) {578			continue579		}580581		if !needIncompatible {582			// We're not yet sure whether we need to include +incompatible versions.583			// Keep track of the last compatible version we've seen, and use the584			// presence (or absence) of a go.mod file in that version to decide: a585			// go.mod file implies that the module author is supporting modules at a586			// compatible version (and we should ignore +incompatible versions unless587			// requested explicitly), while a lack of go.mod file implies the588			// potential for legacy (pre-modules) versioning without semantic import589			// paths (and thus *with* +incompatible versions).590			//591			// This isn't strictly accurate if the latest compatible version has been592			// replaced by a local file path, because we do not allow file-path593			// replacements without a go.mod file: the user would have needed to add594			// one. However, replacing the last compatible version while595			// simultaneously expecting to upgrade implicitly to a +incompatible596			// version seems like an extreme enough corner case to ignore for now.597598			if !strings.HasSuffix(v, "+incompatible") {599				lastCompatible = v600			} else if lastCompatible != "" {601				// If the latest compatible version is allowed and has a go.mod file,602				// ignore any version with a higher (+incompatible) major version. (See603				// https://golang.org/issue/34165.) Note that we even prefer a604				// compatible pre-release over an incompatible release.605				ok, err := versionHasGoMod(ld, ctx, module.Version{Path: qm.path, Version: lastCompatible})606				if err != nil {607					return nil, nil, err608				}609				if ok {610					// The last compatible version has a go.mod file, so that's the611					// highest version we're willing to consider. Don't bother even612					// looking at higher versions, because they're all +incompatible from613					// here onward.614					break615				}616617				// No acceptable compatible release has a go.mod file, so the versioning618				// for the module might not be module-aware, and we should respect619				// legacy major-version tags.620				needIncompatible = true621			}622		}623624		if gover.ModIsPrerelease(qm.path, v) {625			prereleases = append(prereleases, v)626		} else {627			releases = append(releases, v)628		}629	}630631	return releases, prereleases, nil632}633634type QueryResult struct {635	Mod      module.Version636	Rev      *modfetch.RevInfo637	Packages []string638}639640// QueryPackages is like QueryPattern, but requires that the pattern match at641// least one package and omits the non-package result (if any).642func QueryPackages(ld *Loader, ctx context.Context, pattern, query string, current func(string) string, allowed AllowedFunc) ([]QueryResult, error) {643	pkgMods, modOnly, err := QueryPattern(ld, ctx, pattern, query, current, allowed)644645	if len(pkgMods) == 0 && err == nil {646		replacement := Replacement(ld, modOnly.Mod)647		return nil, &PackageNotInModuleError{648			Mod:         modOnly.Mod,649			Replacement: replacement,650			Query:       query,651			Pattern:     pattern,652		}653	}654655	return pkgMods, err656}657658// QueryPattern looks up the module(s) containing at least one package matching659// the given pattern at the given version. The results are sorted by module path660// length in descending order. If any proxy provides a non-empty set of candidate661// modules, no further proxies are tried.662//663// For wildcard patterns, QueryPattern looks in modules with package paths up to664// the first "..." in the pattern. For the pattern "example.com/a/b.../c",665// QueryPattern would consider prefixes of "example.com/a".666//667// If any matching package is in the main module, QueryPattern considers only668// the main module and only the version "latest", without checking for other669// possible modules.670//671// QueryPattern always returns at least one QueryResult (which may be only672// modOnly) or a non-nil error.673func QueryPattern(ld *Loader, ctx context.Context, pattern, query string, current func(string) string, allowed AllowedFunc) (pkgMods []QueryResult, modOnly *QueryResult, err error) {674	ctx, span := trace.StartSpan(ctx, "modload.QueryPattern "+pattern+" "+query)675	defer span.Done()676677	base := pattern678679	firstError := func(m *search.Match) error {680		if len(m.Errs) == 0 {681			return nil682		}683		return m.Errs[0]684	}685686	var match func(mod module.Version, roots []string, isLocal bool) *search.Match687	matchPattern := pkgpattern.MatchPattern(pattern)688689	if i := strings.Index(pattern, "..."); i >= 0 {690		base = pathpkg.Dir(pattern[:i+3])691		if base == "." {692			return nil, nil, &WildcardInFirstElementError{Pattern: pattern, Query: query}693		}694		match = func(mod module.Version, roots []string, isLocal bool) *search.Match {695			m := search.NewMatch(pattern)696			matchPackages(ld, ctx, m, imports.AnyTags(), omitStd, []module.Version{mod})697			return m698		}699	} else {700		match = func(mod module.Version, roots []string, isLocal bool) *search.Match {701			m := search.NewMatch(pattern)702			prefix := mod.Path703			if ld.MainModules.Contains(mod.Path) {704				prefix = ld.MainModules.PathPrefix(module.Version{Path: mod.Path})705			}706			for _, root := range roots {707				if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil {708					m.AddError(err)709				} else if ok {710					m.Pkgs = []string{pattern}711				}712			}713			return m714		}715	}716717	var mainModuleMatches []module.Version718	for _, mainModule := range ld.MainModules.Versions() {719		m := match(mainModule, ld.modRoots, true)720		if len(m.Pkgs) > 0 {721			if query != "upgrade" && query != "patch" {722				return nil, nil, &QueryMatchesPackagesInMainModuleError{723					Pattern:  pattern,724					Query:    query,725					Packages: m.Pkgs,726				}727			}728			if err := allowed(ctx, mainModule); err != nil {729				return nil, nil, fmt.Errorf("internal error: package %s is in the main module (%s), but version is not allowed: %w", pattern, mainModule.Path, err)730			}731			return []QueryResult{{732				Mod:      mainModule,733				Rev:      &modfetch.RevInfo{Version: mainModule.Version},734				Packages: m.Pkgs,735			}}, nil, nil736		}737		if err := firstError(m); err != nil {738			return nil, nil, err739		}740741		var matchesMainModule bool742		if matchPattern(mainModule.Path) {743			mainModuleMatches = append(mainModuleMatches, mainModule)744			matchesMainModule = true745		}746747		if (query == "upgrade" || query == "patch") && matchesMainModule {748			if err := allowed(ctx, mainModule); err == nil {749				modOnly = &QueryResult{750					Mod: mainModule,751					Rev: &modfetch.RevInfo{Version: mainModule.Version},752				}753			}754		}755	}756757	var (758		results          []QueryResult759		candidateModules = modulePrefixesExcludingTarget(ld, base)760	)761	if len(candidateModules) == 0 {762		if modOnly != nil {763			return nil, modOnly, nil764		} else if len(mainModuleMatches) != 0 {765			return nil, nil, &QueryMatchesMainModulesError{766				MainModules:     mainModuleMatches,767				Pattern:         pattern,768				Query:           query,769				PatternIsModule: ld.MainModules.Contains(pattern),770			}771		} else {772			return nil, nil, &PackageNotInModuleError{773				MainModules: mainModuleMatches,774				Query:       query,775				Pattern:     pattern,776			}777		}778	}779780	err = modfetch.TryProxies(func(proxy string) error {781		queryModule := func(ctx context.Context, path string) (r QueryResult, err error) {782			ctx, span := trace.StartSpan(ctx, "modload.QueryPattern.queryModule ["+proxy+"] "+path)783			defer span.Done()784785			pathCurrent := current(path)786			r.Mod.Path = path787			r.Rev, err = queryProxy(ld, ctx, proxy, path, query, pathCurrent, allowed, nil)788			if err != nil {789				return r, err790			}791			r.Mod.Version = r.Rev.Version792			if gover.IsToolchain(r.Mod.Path) {793				return r, nil794			}795			root, isLocal, err := fetch(ld, ctx, r.Mod)796			if err != nil {797				return r, err798			}799			m := match(r.Mod, []string{root}, isLocal)800			r.Packages = m.Pkgs801			if len(r.Packages) == 0 && !matchPattern(path) {802				if err := firstError(m); err != nil {803					return r, err804				}805				replacement := Replacement(ld, r.Mod)806				return r, &PackageNotInModuleError{807					Mod:         r.Mod,808					Replacement: replacement,809					Query:       query,810					Pattern:     pattern,811				}812			}813			return r, nil814		}815816		allResults, err := queryPrefixModules(ld, ctx, candidateModules, queryModule)817		results = allResults[:0]818		for _, r := range allResults {819			if len(r.Packages) == 0 {820				modOnly = &r821			} else {822				results = append(results, r)823			}824		}825		return err826	})827828	if len(mainModuleMatches) > 0 && len(results) == 0 && modOnly == nil && errors.Is(err, fs.ErrNotExist) {829		return nil, nil, &QueryMatchesMainModulesError{830			Pattern:         pattern,831			Query:           query,832			PatternIsModule: ld.MainModules.Contains(pattern),833		}834	}835	return slices.Clip(results), modOnly, err836}837838// modulePrefixesExcludingTarget returns all prefixes of path that may plausibly839// exist as a module, excluding targetPrefix but otherwise including path840// itself, sorted by descending length. Prefixes that are not valid module paths841// but are valid package paths (like "m" or "example.com/.gen") are included,842// since they might be replaced.843func modulePrefixesExcludingTarget(ld *Loader, path string) []string {844	prefixes := make([]string, 0, strings.Count(path, "/")+1)845846	mainModulePrefixes := make(map[string]bool)847	for _, m := range ld.MainModules.Versions() {848		mainModulePrefixes[m.Path] = true849	}850851	for {852		if !mainModulePrefixes[path] {853			if _, _, ok := module.SplitPathVersion(path); ok {854				prefixes = append(prefixes, path)855			}856		}857858		j := strings.LastIndexByte(path, '/')859		if j < 0 {860			break861		}862		path = path[:j]863	}864865	return prefixes866}867868func queryPrefixModules(ld *Loader, ctx context.Context, candidateModules []string, queryModule func(ctx context.Context, path string) (QueryResult, error)) (found []QueryResult, err error) {869	ctx, span := trace.StartSpan(ctx, "modload.queryPrefixModules")870	defer span.Done()871872	// If the path we're attempting is not in the module cache and we don't have a873	// fetch result cached either, we'll end up making a (potentially slow)874	// request to the proxy or (often even slower) the origin server.875	// To minimize latency, execute all of those requests in parallel.876	type result struct {877		QueryResult878		err error879	}880	results := make([]result, len(candidateModules))881	var wg sync.WaitGroup882	wg.Add(len(candidateModules))883	for i, p := range candidateModules {884		ctx := trace.StartGoroutine(ctx)885		go func(p string, r *result) {886			r.QueryResult, r.err = queryModule(ctx, p)887			wg.Done()888		}(p, &results[i])889	}890	wg.Wait()891892	// Classify the results. In case of failure, identify the error that the user893	// is most likely to find helpful: the most useful class of error at the894	// longest matching path.895	var (896		noPackage      *PackageNotInModuleError897		noVersion      *NoMatchingVersionError898		noPatchBase    *NoPatchBaseError899		invalidPath    *module.InvalidPathError // see comment in case below900		invalidVersion error901		notExistErr    error902	)903	for _, r := range results {904		switch rErr := r.err.(type) {905		case nil:906			found = append(found, r.QueryResult)907		case *PackageNotInModuleError:908			// Given the option, prefer to attribute “package not in module”909			// to modules other than the main one.910			if noPackage == nil || ld.MainModules.Contains(noPackage.Mod.Path) {911				noPackage = rErr912			}913		case *NoMatchingVersionError:914			if noVersion == nil {915				noVersion = rErr916			}917		case *NoPatchBaseError:918			if noPatchBase == nil {919				noPatchBase = rErr920			}921		case *module.InvalidPathError:922			// The prefix was not a valid module path, and there was no replacement.923			// Prefixes like this may appear in candidateModules, since we handle924			// replaced modules that weren't required in the repo lookup process925			// (see lookupRepo).926			//927			// A shorter prefix may be a valid module path and may contain a valid928			// import path, so this is a low-priority error.929			if invalidPath == nil {930				invalidPath = rErr931			}932		default:933			if errors.Is(rErr, fs.ErrNotExist) {934				if notExistErr == nil {935					notExistErr = rErr936				}937			} else if _, ok := errors.AsType[*module.InvalidVersionError](rErr); ok {938				if invalidVersion == nil {939					invalidVersion = rErr940				}941			} else if err == nil {942				if len(found) > 0 || noPackage != nil {943					// golang.org/issue/34094: If we have already found a module that944					// could potentially contain the target package, ignore unclassified945					// errors for modules with shorter paths.946947					// golang.org/issue/34383 is a special case of this: if we have948					// already found example.com/foo/v2@v2.0.0 with a matching go.mod949					// file, ignore the error from example.com/foo@v2.0.0.950				} else {951					err = r.err952				}953			}954		}955	}956957	// TODO(#26232): If len(found) == 0 and some of the errors are 4xx HTTP958	// codes, have the auth package recheck the failed paths.959	// If we obtain new credentials for any of them, re-run the above loop.960961	if len(found) == 0 && err == nil {962		switch {963		case noPackage != nil:964			err = noPackage965		case noVersion != nil:966			err = noVersion967		case noPatchBase != nil:968			err = noPatchBase969		case invalidPath != nil:970			err = invalidPath971		case invalidVersion != nil:972			err = invalidVersion973		case notExistErr != nil:974			err = notExistErr975		default:976			panic("queryPrefixModules: no modules found, but no error detected")977		}978	}979980	return found, err981}982983// A NoMatchingVersionError indicates that Query found a module at the requested984// path, but not at any versions satisfying the query string and allow-function.985//986// NOTE: NoMatchingVersionError MUST NOT implement Is(fs.ErrNotExist).987//988// If the module came from a proxy, that proxy had to return a successful status989// code for the versions it knows about, and thus did not have the opportunity990// to return a non-400 status code to suppress fallback.991type NoMatchingVersionError struct {992	query, current string993}994995func (e *NoMatchingVersionError) Error() string {996	currentSuffix := ""997	if (e.query == "upgrade" || e.query == "patch") && e.current != "" && e.current != "none" {998		currentSuffix = fmt.Sprintf(" (current version is %s)", e.current)999	}1000	return fmt.Sprintf("no matching versions for query %q", e.query) + currentSuffix1001}10021003// A NoPatchBaseError indicates that Query was called with the query "patch"1004// but with a current version of "" or "none".1005type NoPatchBaseError struct {1006	path string1007}10081009func (e *NoPatchBaseError) Error() string {1010	return fmt.Sprintf(`can't query version "patch" of module %s: no existing version is required`, e.path)1011}10121013// A WildcardInFirstElementError indicates that a pattern passed to QueryPattern1014// had a wildcard in its first path element, and therefore had no pattern-prefix1015// modules to search in.1016type WildcardInFirstElementError struct {1017	Pattern string1018	Query   string1019}10201021func (e *WildcardInFirstElementError) Error() string {1022	return fmt.Sprintf("no modules to query for %s@%s because first path element contains a wildcard", e.Pattern, e.Query)1023}10241025// A PackageNotInModuleError indicates that QueryPattern found a candidate1026// module at the requested version, but that module did not contain any packages1027// matching the requested pattern.1028//1029// NOTE: PackageNotInModuleError MUST NOT implement Is(fs.ErrNotExist).1030//1031// If the module came from a proxy, that proxy had to return a successful status1032// code for the versions it knows about, and thus did not have the opportunity1033// to return a non-400 status code to suppress fallback.1034type PackageNotInModuleError struct {1035	MainModules []module.Version1036	Mod         module.Version1037	Replacement module.Version1038	Query       string1039	Pattern     string1040}10411042func (e *PackageNotInModuleError) Error() string {1043	if len(e.MainModules) > 0 {1044		prefix := "workspace modules do"1045		if len(e.MainModules) == 1 {1046			prefix = fmt.Sprintf("main module (%s) does", e.MainModules[0])1047		}1048		if strings.Contains(e.Pattern, "...") {1049			return fmt.Sprintf("%s not contain packages matching %s", prefix, e.Pattern)1050		}1051		return fmt.Sprintf("%s not contain package %s", prefix, e.Pattern)1052	}10531054	found := ""1055	if r := e.Replacement; r.Path != "" {1056		replacement := r.Path1057		if r.Version != "" {1058			replacement = fmt.Sprintf("%s@%s", r.Path, r.Version)1059		}1060		if e.Query == e.Mod.Version {1061			found = fmt.Sprintf(" (replaced by %s)", replacement)1062		} else {1063			found = fmt.Sprintf(" (%s, replaced by %s)", e.Mod.Version, replacement)1064		}1065	} else if e.Query != e.Mod.Version {1066		found = fmt.Sprintf(" (%s)", e.Mod.Version)1067	}10681069	if strings.Contains(e.Pattern, "...") {1070		return fmt.Sprintf("module %s@%s found%s, but does not contain packages matching %s", e.Mod.Path, e.Query, found, e.Pattern)1071	}1072	return fmt.Sprintf("module %s@%s found%s, but does not contain package %s", e.Mod.Path, e.Query, found, e.Pattern)1073}10741075func (e *PackageNotInModuleError) ImportPath() string {1076	if !strings.Contains(e.Pattern, "...") {1077		return e.Pattern1078	}1079	return ""1080}10811082// versionHasGoMod returns whether a version has a go.mod file.1083//1084// versionHasGoMod fetches the go.mod file (possibly a fake) and true if it1085// contains anything other than a module directive with the same path. When a1086// module does not have a real go.mod file, the go command acts as if it had one1087// that only contained a module directive. Normal go.mod files created after1088// 1.12 at least have a go directive.1089//1090// This function is a heuristic, since it's possible to commit a file that would1091// pass this test. However, we only need a heuristic for determining whether1092// +incompatible versions may be "latest", which is what this function is used1093// for.1094//1095// This heuristic is useful for two reasons: first, when using a proxy,1096// this lets us fetch from the .mod endpoint which is much faster than the .zip1097// endpoint. The .mod file is used anyway, even if the .zip file contains a1098// go.mod with different content. Second, if we don't fetch the .zip, then1099// we don't need to verify it in go.sum. This makes 'go list -m -u' faster1100// and simpler.1101func versionHasGoMod(ld *Loader, _ context.Context, m module.Version) (bool, error) {1102	_, data, err := rawGoModData(ld, m)1103	if err != nil {1104		return false, err1105	}1106	isFake := bytes.Equal(data, modfetch.LegacyGoMod(m.Path))1107	return !isFake, nil1108}11091110// A versionRepo is a subset of modfetch.Repo that can report information about1111// available versions, but cannot fetch specific source files.1112type versionRepo interface {1113	ModulePath() string1114	CheckReuse(context.Context, *codehost.Origin) error1115	Versions(ctx context.Context, prefix string) (*modfetch.Versions, error)1116	Stat(ctx context.Context, rev string) (*modfetch.RevInfo, error)1117	Latest(ctx context.Context) (*modfetch.RevInfo, error)1118}11191120var _ versionRepo = modfetch.Repo(nil)11211122func lookupRepo(ld *Loader, ctx context.Context, proxy, path string) (repo versionRepo, err error) {1123	if path != "go" && path != "toolchain" {1124		err = module.CheckPath(path)1125	}1126	if err == nil {1127		repo = ld.Fetcher().Lookup(ctx, proxy, path)1128	} else {1129		repo = emptyRepo{path: path, err: err}1130	}11311132	if ld.MainModules == nil {1133		return repo, err1134	} else if _, ok := ld.MainModules.HighestReplaced()[path]; ok {1135		return &replacementRepo{repo: repo, ld: ld}, nil1136	}11371138	return repo, err1139}11401141// An emptyRepo is a versionRepo that contains no versions.1142type emptyRepo struct {1143	path string1144	err  error1145}11461147var _ versionRepo = emptyRepo{}11481149func (er emptyRepo) ModulePath() string { return er.path }1150func (er emptyRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error {1151	return fmt.Errorf("empty repo")1152}11531154func (er emptyRepo) Versions(ctx context.Context, prefix string) (*modfetch.Versions, error) {1155	return &modfetch.Versions{}, nil1156}11571158func (er emptyRepo) Stat(ctx context.Context, rev string) (*modfetch.RevInfo, error) {1159	return nil, er.err1160}1161func (er emptyRepo) Latest(ctx context.Context) (*modfetch.RevInfo, error) { return nil, er.err }11621163// A replacementRepo augments a versionRepo to include the replacement versions1164// (if any) found in the main module's go.mod file.1165//1166// A replacementRepo suppresses "not found" errors for otherwise-nonexistent1167// modules, so a replacementRepo should only be constructed for a module that1168// actually has one or more valid replacements.1169type replacementRepo struct {1170	repo versionRepo1171	ld   *Loader1172}11731174var _ versionRepo = (*replacementRepo)(nil)11751176func (rr *replacementRepo) ModulePath() string { return rr.repo.ModulePath() }11771178func (rr *replacementRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error {1179	return fmt.Errorf("replacement repo")1180}11811182// Versions returns the versions from rr.repo augmented with any matching1183// replacement versions.1184func (rr *replacementRepo) Versions(ctx context.Context, prefix string) (*modfetch.Versions, error) {1185	repoVersions, err := rr.repo.Versions(ctx, prefix)1186	if err != nil {1187		if !errors.Is(err, os.ErrNotExist) {1188			return nil, err1189		}1190		repoVersions = new(modfetch.Versions)1191	}11921193	versions := repoVersions.List1194	for _, mm := range rr.ld.MainModules.Versions() {1195		if index := rr.ld.MainModules.Index(mm); index != nil && len(index.replace) > 0 {1196			path := rr.ModulePath()1197			for m := range index.replace {1198				if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !module.IsPseudoVersion(m.Version) {1199					versions = append(versions, m.Version)1200				}1201			}1202		}1203	}12041205	if len(versions) == len(repoVersions.List) { // replacement versions added1206		return repoVersions, nil1207	}12081209	path := rr.ModulePath()1210	sort.Slice(versions, func(i, j int) bool {1211		return gover.ModCompare(path, versions[i], versions[j]) < 01212	})1213	str.Uniq(&versions)1214	return &modfetch.Versions{List: versions}, nil1215}12161217func (rr *replacementRepo) Stat(ctx context.Context, rev string) (*modfetch.RevInfo, error) {1218	info, err := rr.repo.Stat(ctx, rev)1219	if err == nil {1220		return info, err1221	}1222	var hasReplacements bool1223	for _, v := range rr.ld.MainModules.Versions() {1224		if index := rr.ld.MainModules.Index(v); index != nil && len(index.replace) > 0 {1225			hasReplacements = true1226		}1227	}1228	if !hasReplacements {1229		return info, err1230	}12311232	v := module.CanonicalVersion(rev)1233	if v != rev {1234		// The replacements in the go.mod file list only canonical semantic versions,1235		// so a non-canonical version can't possibly have a replacement.1236		return info, err1237	}12381239	path := rr.ModulePath()1240	_, pathMajor, ok := module.SplitPathVersion(path)1241	if ok && pathMajor == "" {1242		if err := module.CheckPathMajor(v, pathMajor); err != nil && semver.Build(v) == "" {1243			v += "+incompatible"1244		}1245	}12461247	if r := Replacement(rr.ld, module.Version{Path: path, Version: v}); r.Path == "" {1248		return info, err1249	}1250	return rr.replacementStat(v)1251}12521253func (rr *replacementRepo) Latest(ctx context.Context) (*modfetch.RevInfo, error) {1254	info, err := rr.repo.Latest(ctx)1255	path := rr.ModulePath()12561257	if v, ok := rr.ld.MainModules.HighestReplaced()[path]; ok {1258		if v == "" {1259			// The only replacement is a wildcard that doesn't specify a version, so1260			// synthesize a pseudo-version with an appropriate major version and a1261			// timestamp below any real timestamp. That way, if the main module is1262			// used from within some other module, the user will be able to upgrade1263			// the requirement to any real version they choose.1264			if _, pathMajor, ok := module.SplitPathVersion(path); ok && len(pathMajor) > 0 {1265				v = module.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")1266			} else {1267				v = module.PseudoVersion("v0", "", time.Time{}, "000000000000")1268			}1269		}12701271		if err != nil || gover.ModCompare(path, v, info.Version) > 0 {1272			return rr.replacementStat(v)1273		}1274	}12751276	return info, err1277}12781279func (rr *replacementRepo) replacementStat(v string) (*modfetch.RevInfo, error) {1280	rev := &modfetch.RevInfo{Version: v}1281	if module.IsPseudoVersion(v) {1282		rev.Time, _ = module.PseudoVersionTime(v)1283		rev.Short, _ = module.PseudoVersionRev(v)1284	}1285	return rev, nil1286}12871288// A QueryMatchesMainModulesError indicates that a query requests1289// a version of the main module that cannot be satisfied.1290// (The main module's version cannot be changed.)1291type QueryMatchesMainModulesError struct {1292	MainModules     []module.Version1293	Pattern         string1294	Query           string1295	PatternIsModule bool // true if pattern is one of the main modules1296}12971298func (e *QueryMatchesMainModulesError) Error() string {1299	if e.PatternIsModule {1300		return fmt.Sprintf("can't request version %q of the main module (%s)", e.Query, e.Pattern)1301	}13021303	plural := ""1304	mainModulePaths := make([]string, len(e.MainModules))1305	for i := range e.MainModules {1306		mainModulePaths[i] = e.MainModules[i].Path1307	}1308	if len(e.MainModules) > 1 {1309		plural = "s"1310	}1311	return fmt.Sprintf("can't request version %q of pattern %q that includes the main module%s (%s)", e.Query, e.Pattern, plural, strings.Join(mainModulePaths, ", "))1312}13131314// A QueryUpgradesAllError indicates that a query requests1315// an upgrade on the all pattern.1316// (The main module's version cannot be changed.)1317type QueryUpgradesAllError struct {1318	MainModules []module.Version1319	Query       string1320}13211322func (e *QueryUpgradesAllError) Error() string {1323	plural := ""1324	if len(e.MainModules) != 1 {1325		plural = "s"1326	}13271328	return fmt.Sprintf("can't request version %q of pattern \"all\" that includes the main module%s", e.Query, plural)1329}13301331// A QueryMatchesPackagesInMainModuleError indicates that a query cannot be1332// satisfied because it matches one or more packages found in the main module.1333type QueryMatchesPackagesInMainModuleError struct {1334	Pattern  string1335	Query    string1336	Packages []string1337}13381339func (e *QueryMatchesPackagesInMainModuleError) Error() string {1340	if len(e.Packages) > 1 {1341		return fmt.Sprintf("pattern %s matches %d packages in the main module, so can't request version %s", e.Pattern, len(e.Packages), e.Query)1342	}13431344	if search.IsMetaPackage(e.Pattern) || strings.Contains(e.Pattern, "...") {1345		return fmt.Sprintf("pattern %s matches package %s in the main module, so can't request version %s", e.Pattern, e.Packages[0], e.Query)1346	}13471348	return fmt.Sprintf("package %s is in the main module, so can't request version %s", e.Packages[0], e.Query)1349}

Code quality findings 12

Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
rev.Time, _ = module.PseudoVersionTime(v)
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
rev.Short, _ = module.PseudoVersionRev(v)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
prereleases = append(prereleases, v)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
releases = append(releases, v)
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if err := allowed(ctx, mainModule); err != nil {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
results = append(results, r)
Map created without size hint before being populated in a loop; provide capacity hint to reduce allocations
info performance map-without-size-hint
mainModulePrefixes := make(map[string]bool)
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 i, p := range candidateModules {
Type switch without default case; unhandled types will silently do nothing. Add a default case for safety
info correctness unchecked-type-switch
switch rErr := r.err.(type) {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
found = append(found, r.QueryResult)
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for _, mm := range rr.ld.MainModules.Versions() {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
versions = append(versions, m.Version)

Get this view in your editor

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