diff --git a/cmd/cue/cmd/modget.go b/cmd/cue/cmd/modget.go index b7c55300a31..dcfdde64466 100644 --- a/cmd/cue/cmd/modget.go +++ b/cmd/cue/cmd/modget.go @@ -77,7 +77,7 @@ func runModGet(cmd *Command, args []string) error { } mf, err := modload.UpdateVersions(ctx, os.DirFS(modRoot), ".", reg, args) if err != nil { - return err + return suggestModCommand(err) } // TODO check whether it's changed or not. data, err := mf.Format() diff --git a/cmd/cue/cmd/modpublish.go b/cmd/cue/cmd/modpublish.go index 73ea5477077..216c6ee88c5 100644 --- a/cmd/cue/cmd/modpublish.go +++ b/cmd/cue/cmd/modpublish.go @@ -122,7 +122,7 @@ func runModUpload(cmd *Command, args []string) error { return err } if err := modload.CheckTidy(ctx, os.DirFS(modRoot), ".", reg); err != nil { - return suggestModTidy(err) + return suggestModCommand(err) } modPath := filepath.Join(modRoot, "cue.mod/module.cue") diff --git a/cmd/cue/cmd/modtidy.go b/cmd/cue/cmd/modtidy.go index 18303658a28..7f27f1575f8 100644 --- a/cmd/cue/cmd/modtidy.go +++ b/cmd/cue/cmd/modtidy.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" "cuelang.org/go/internal/mod/modload" + "cuelang.org/go/mod/modfile" ) func newModTidyCmd(c *Command) *cobra.Command { @@ -67,11 +68,11 @@ func runModTidy(cmd *Command, args []string) error { } if flagCheck.Bool(cmd) { err := modload.CheckTidy(ctx, os.DirFS(modRoot), ".", reg) - return suggestModTidy(err) + return suggestModCommand(err) } mf, err := modload.Tidy(ctx, os.DirFS(modRoot), ".", reg) if err != nil { - return err + return suggestModCommand(err) } data, err := mf.Format() if err != nil { @@ -93,11 +94,16 @@ func runModTidy(cmd *Command, args []string) error { return nil } -// suggestModTidy rewrites [modload.ErrModuleNotTidy] errors -// so that they suggest running `cue mod tidy` to the user. -func suggestModTidy(err error) error { +// suggestModCommand rewrites a non-nil error to suggest to the user +// what command they could use to fix a problem. +// [modload.ErrModuleNotTidy] suggests running `cue mod tidy`, +// and [modfile.ErrNoLanguageVersion] suggests running `cue mod fix`. +func suggestModCommand(err error) error { notTidyErr := new(modload.ErrModuleNotTidy) - if errors.As(err, ¬TidyErr) { + switch { + case errors.Is(err, modfile.ErrNoLanguageVersion): + err = fmt.Errorf("%w; run 'cue mod fix'", err) + case errors.As(err, ¬TidyErr): if notTidyErr.Reason == "" { err = fmt.Errorf("module is not tidy, use 'cue mod tidy'") } else { diff --git a/cmd/cue/cmd/testdata/script/modpublish_no_source.txtar b/cmd/cue/cmd/testdata/script/modpublish_no_source.txtar index c1f5d259663..0ee87cf6958 100644 --- a/cmd/cue/cmd/testdata/script/modpublish_no_source.txtar +++ b/cmd/cue/cmd/testdata/script/modpublish_no_source.txtar @@ -49,6 +49,6 @@ module: "test.example/05@v1" language: version: "v0.5.0" -- none/both.stderr -- -no language version declared in module.cue +no language version declared in module.cue; run 'cue mod fix' -- none/cue.mod/module.cue -- module: "test.example/none@v1" diff --git a/cmd/cue/cmd/testdata/script/modtidy_with_version.txtar b/cmd/cue/cmd/testdata/script/modtidy_with_version.txtar index e0bbd17f3dd..22afc236f76 100644 --- a/cmd/cue/cmd/testdata/script/modtidy_with_version.txtar +++ b/cmd/cue/cmd/testdata/script/modtidy_with_version.txtar @@ -5,7 +5,7 @@ cmp stderr want-stderr -- want-stderr -- -no language version declared in module.cue +no language version declared in module.cue; run 'cue mod fix' -- cue.mod/module.cue -- module: "main.org@v0" diff --git a/cmd/cue/cmd/testdata/script/module_compatibility_backwards.txtar b/cmd/cue/cmd/testdata/script/module_compatibility_backwards.txtar index fa73f991942..e1bbf7495cd 100644 --- a/cmd/cue/cmd/testdata/script/module_compatibility_backwards.txtar +++ b/cmd/cue/cmd/testdata/script/module_compatibility_backwards.txtar @@ -37,11 +37,11 @@ stderr '^no language version declared in module.cue$' # Upstream development works once `cue mod fix` adds a language.version field. # TODO(mvdan): we don't have anything like `cue mod edit -langversion` if the user wants an older version. ! exec cue mod tidy --check -stderr '^no language version declared in module.cue$' +stderr '^no language version declared in module.cue; run ''cue mod fix''$' ! exec cue mod get some.dependency -stderr '^no language version declared in module.cue$' +stderr '^no language version declared in module.cue; run ''cue mod fix''$' ! exec cue mod publish v0.0.2 -stderr '^no language version declared in module.cue$' +stderr '^no language version declared in module.cue; run ''cue mod fix''$' exec cue mod fix cmp cue.mod/module.cue ${WORK}/premodules-module.cue.fixed exec cue export diff --git a/mod/modfile/modfile.go b/mod/modfile/modfile.go index 97dba922a9d..d38a005f5bc 100644 --- a/mod/modfile/modfile.go +++ b/mod/modfile/modfile.go @@ -220,11 +220,11 @@ var schemaVersionLimits = sync.OnceValue(func() [2]string { return limits }) -// Parse verifies that the module file has correct syntax. +// Parse verifies that the module file has correct syntax +// and follows the schema following the required language.version field. // The file name is used for error messages. // All dependencies must be specified correctly: with major -// versions in the module paths and canonical dependency -// versions. +// versions in the module paths and canonical dependency versions. func Parse(modfile []byte, filename string) (*File, error) { return parse(modfile, filename, true) } @@ -349,8 +349,7 @@ func parse(modfile []byte, filename string, strict bool) (*File, error) { return nil, errors.Wrapf(err, token.NoPos, "cannot determine language version") } if base.Language.Version == "" { - // TODO is something different we could do here? - return nil, fmt.Errorf("no language version declared in module.cue") + return nil, ErrNoLanguageVersion } if !semver.IsValid(base.Language.Version) { return nil, fmt.Errorf("language version %q in module.cue is not valid semantic version", base.Language.Version) @@ -466,6 +465,10 @@ func parse(modfile []byte, filename string, strict bool) (*File, error) { return mf, nil } +// ErrNoLanguageVersion is returned by [Parse] and [ParseNonStrict] +// when a cue.mod/module.cue file lacks the `language.version` field. +var ErrNoLanguageVersion = fmt.Errorf("no language version declared in module.cue") + func parseDataOnlyCUE(ctx *cue.Context, cueData []byte, filename string) (*ast.File, error) { dec := encoding.NewDecoder(ctx, &build.File{ Filename: filename,