type VersionFixer func(path, version string) (string, error)
+// Parse parses the data, reported in errors as being from file,
+// into a File struct. It applies fix, if non-nil, to canonicalize all module versions found.
func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
+ return parseToFile(file, data, fix, true)
+}
+
+// ParseLax is like Parse but ignores unknown statements.
+// It is used when parsing go.mod files other than the main module,
+// under the theory that most statement types we add in the future will
+// only apply in the main module, like exclude and replace,
+// and so we get better gradual deployments if old go commands
+// simply ignore those statements when found in go.mod files
+// in dependencies.
+func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) {
+ return parseToFile(file, data, fix, false)
+}
+
+func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File, error) {
fs, err := parse(file, data)
if err != nil {
return nil, err
for _, x := range fs.Stmt {
switch x := x.(type) {
case *Line:
- f.add(&errs, x, x.Token[0], x.Token[1:], fix)
+ f.add(&errs, x, x.Token[0], x.Token[1:], fix, strict)
case *LineBlock:
if len(x.Token) > 1 {
- fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
+ if strict {
+ fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
+ }
continue
}
switch x.Token[0] {
default:
- fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
+ if strict {
+ fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
+ }
continue
case "module", "require", "exclude", "replace":
for _, l := range x.Line {
- f.add(&errs, l, x.Token[0], l.Token, fix)
+ f.add(&errs, l, x.Token[0], l.Token, fix, strict)
}
}
}
return f, nil
}
-func (f *File) add(errs *bytes.Buffer, line *Line, verb string, args []string, fix VersionFixer) {
- // TODO: We should pass in a flag saying whether this module is a dependency.
- // If so, we should ignore all unknown directives and not attempt to parse
+func (f *File) add(errs *bytes.Buffer, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
+ // If strict is false, this module is a dependency.
+ // We ignore all unknown directives and do not attempt to parse
// replace and exclude either. They don't matter, and it will work better for
- // forward compatibility if we can depend on modules that have local changes.
+ // forward compatibility if we can depend on modules that have unknown
+ // statements (presumed relevant only when acting as the main module).
+ if !strict && verb != "module" && verb != "require" {
+ return
+ }
switch verb {
default:
fmt.Fprintf(errs, "%s:%d: unknown directive: %s\n", f.Syntax.Name, line.Start.Line, verb)
+
case "module":
if f.Module != nil {
fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line)
base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
return nil, ErrRequire
}
- f, err := modfile.Parse(gomod, data, nil)
+ f, err := modfile.ParseLax(gomod, data, nil)
if err != nil {
base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
return nil, ErrRequire
base.Errorf("go: %s@%s: %v\n", mod.Path, mod.Version, err)
return nil, ErrRequire
}
- f, err := modfile.Parse("go.mod", data, nil)
+ f, err := modfile.ParseLax("go.mod", data, nil)
if err != nil {
base.Errorf("go: %s@%s: parsing go.mod: %v", mod.Path, mod.Version, err)
return nil, ErrRequire