| // Copyright 2021 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 internal |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| "golang.org/x/mod/modfile" |
| "golang.org/x/mod/semver" |
| ) |
| |
| // LatestModuleVersions describes the latest versions of a module. It also holds the |
| // go.mod file of the raw latest version, which establishes whether the module |
| // is deprecated, and what versions are retracted. |
| type LatestModuleVersions struct { |
| ModulePath string |
| RawVersion string // ignoring retractions |
| CookedVersion string // considering retractions |
| GoodVersion string // successfully processed |
| GoModFile *modfile.File // of raw |
| Deprecated bool |
| deprecationComment string |
| } |
| |
| func NewLatestModuleVersions(modulePath, raw, cooked, good string, modBytes []byte) (*LatestModuleVersions, error) { |
| modFile, err := modfile.ParseLax(fmt.Sprintf("%s@%s/go.mod", modulePath, raw), modBytes, nil) |
| if err != nil { |
| return nil, err |
| } |
| |
| dep, comment := isDeprecated(modFile) |
| return &LatestModuleVersions{ |
| ModulePath: modulePath, |
| RawVersion: raw, |
| CookedVersion: cooked, |
| GoodVersion: good, |
| GoModFile: modFile, |
| Deprecated: dep, |
| deprecationComment: comment, |
| }, nil |
| } |
| |
| // isDeprecated reports whether the go.mod deprecates this module. |
| // It looks for "Deprecated" comments in the line comments before and next to |
| // the module declaration. If it finds one, it returns true along with the text |
| // after "Deprecated:". Otherwise it returns false, "". |
| func isDeprecated(mf *modfile.File) (bool, string) { |
| const prefix = "Deprecated:" |
| |
| if mf.Module == nil { |
| return false, "" |
| } |
| for _, comment := range append(mf.Module.Syntax.Before, mf.Module.Syntax.Suffix...) { |
| text := strings.TrimSpace(strings.TrimPrefix(comment.Token, "//")) |
| if strings.HasPrefix(text, prefix) { |
| return true, strings.TrimSpace(text[len(prefix):]) |
| } |
| } |
| return false, "" |
| } |
| |
| // PopulateModuleInfo uses the LatestModuleVersions to populate fields of the given module. |
| func (li *LatestModuleVersions) PopulateModuleInfo(mi *ModuleInfo) { |
| mi.Deprecated = li.Deprecated |
| mi.DeprecationComment = li.deprecationComment |
| mi.Retracted, mi.RetractionRationale = isRetracted(li.GoModFile, mi.Version) |
| } |
| |
| // IsRetracted reports whether the version is retracted according to the go.mod |
| // file in the receiver. |
| func (li *LatestModuleVersions) IsRetracted(version string) bool { |
| r, _ := isRetracted(li.GoModFile, version) |
| return r |
| } |
| |
| // isRetracted reports whether the go.mod file retracts the version. |
| // If so, it returns true along with the rationale for the retraction. |
| func isRetracted(mf *modfile.File, resolvedVersion string) (bool, string) { |
| for _, r := range mf.Retract { |
| if semver.Compare(resolvedVersion, r.Low) >= 0 && semver.Compare(resolvedVersion, r.High) <= 0 { |
| return true, r.Rationale |
| } |
| } |
| return false, "" |
| } |