1// Copyright 2019 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package cmd
16
17import (
18	"io/ioutil"
19	"os"
20	"path/filepath"
21	"strings"
22
23	"cuelang.org/go/cue/ast"
24	"cuelang.org/go/cue/errors"
25	"cuelang.org/go/cue/format"
26	"cuelang.org/go/cue/load"
27	"cuelang.org/go/cue/token"
28	"cuelang.org/go/tools/fix"
29	"github.com/spf13/cobra"
30)
31
32func newFixCmd(c *Command) *cobra.Command {
33	cmd := &cobra.Command{
34		Use:   "fix [packages]",
35		Short: "rewrite packages to latest standards",
36		Long: `Fix finds CUE programs that use old syntax and old APIs and rewrites them to use newer ones.
37After you update to a new CUE release, fix helps make the necessary changes
38to your program.
39
40Without any packages, fix applies to all files within a module.
41`,
42		RunE: mkRunE(c, runFixAll),
43	}
44
45	cmd.Flags().BoolP(string(flagForce), "f", false,
46		"rewrite even when there are errors")
47
48	return cmd
49}
50
51func runFixAll(cmd *Command, args []string) error {
52	dir, err := os.Getwd()
53	if err != nil {
54		return err
55	}
56
57	var opts []fix.Option
58	if flagSimplify.Bool(cmd) {
59		opts = append(opts, fix.Simplify())
60	}
61
62	if len(args) == 0 {
63		args = []string{"./..."}
64
65		for {
66			if fi, err := os.Stat(filepath.Join(dir, "cue.mod")); err == nil {
67				if fi.IsDir() {
68					args = appendDirs(args, filepath.Join(dir, "cue.mod", "gen"))
69					args = appendDirs(args, filepath.Join(dir, "cue.mod", "pkg"))
70					args = appendDirs(args, filepath.Join(dir, "cue.mod", "usr"))
71				} else {
72					args = appendDirs(args, filepath.Join(dir, "pkg"))
73				}
74				break
75			}
76
77			dir = filepath.Dir(dir)
78			if info, _ := os.Stat(dir); !info.IsDir() {
79				return errors.Newf(token.NoPos, "no module root found")
80			}
81		}
82	}
83
84	instances := load.Instances(args, &load.Config{
85		Tests: true,
86		Tools: true,
87	})
88
89	errs := fix.Instances(instances, opts...)
90
91	if errs != nil && flagForce.Bool(cmd) {
92		return errs
93	}
94
95	done := map[*ast.File]bool{}
96
97	for _, i := range instances {
98		for _, f := range i.Files {
99			if done[f] || !strings.HasSuffix(f.Filename, ".cue") {
100				continue
101			}
102			done[f] = true
103
104			b, err := format.Node(f)
105			if err != nil {
106				errs = errors.Append(errs, errors.Promote(err, "format"))
107			}
108
109			err = ioutil.WriteFile(f.Filename, b, 0644)
110			if err != nil {
111				errs = errors.Append(errs, errors.Promote(err, "write"))
112			}
113		}
114	}
115
116	return errs
117}
118
119func appendDirs(a []string, base string) []string {
120	_ = filepath.Walk(base, func(path string, fi os.FileInfo, err error) error {
121		if err == nil && fi.IsDir() && path != base {
122			short := filepath.ToSlash(path[len(base)+1:])
123			if strings.ContainsAny(short, "/") {
124				a = append(a, short)
125			}
126		}
127		return nil
128	})
129	return a
130}
131