Skip to content

Commit

Permalink
cmd/cue: suggest cue mod fix when language.version is missing
Browse files Browse the repository at this point in the history
That is, if a user tries to use `mod tidy`, `mod get`, or `mod publish`
on a module without a `language.version` field, which is now mandatory,
suggest what the user should run to resolve that issue.

Note that we don't apply this change to non-mod commands such as
`cue export` because, as the TODOs explain, those scenarios should work
for the sake of backwards compatibility as a downstream user.

We also don't try to make this be consistent for all `cue mod` commands
as it's not clear that all of them should require `language.version`.
For example, `mod init` creates a module, `mod fix` can add the field,
and we should arguably teach `mod edit` to add or set the field too.
For now, add the suggestion to the three `cue mod` commands which are
most likely to be used when developing or publishing a CUE module.

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
Change-Id: I11cef96900b2a5984ad6c07abbd705108c2c2759
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1195784
Reviewed-by: Paul Jolly <paul@myitcv.io>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
  • Loading branch information
mvdan committed Jun 5, 2024
1 parent 2cc2c44 commit a229224
Show file tree
Hide file tree
Showing 7 changed files with 27 additions and 18 deletions.
2 changes: 1 addition & 1 deletion cmd/cue/cmd/modget.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion cmd/cue/cmd/modpublish.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
18 changes: 12 additions & 6 deletions cmd/cue/cmd/modtidy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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, &notTidyErr) {
switch {
case errors.Is(err, modfile.ErrNoLanguageVersion):
err = fmt.Errorf("%w; run 'cue mod fix'", err)
case errors.As(err, &notTidyErr):
if notTidyErr.Reason == "" {
err = fmt.Errorf("module is not tidy, use 'cue mod tidy'")
} else {
Expand Down
2 changes: 1 addition & 1 deletion cmd/cue/cmd/testdata/script/modpublish_no_source.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion cmd/cue/cmd/testdata/script/modtidy_with_version.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 8 additions & 5 deletions mod/modfile/modfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit a229224

Please sign in to comment.