1
15
16
17
18 package main
19
20 import (
21 "encoding/json"
22 "errors"
23 "flag"
24 "fmt"
25 "io/ioutil"
26 "math"
27 "os"
28 "regexp"
29 "strconv"
30 "text/template"
31 )
32
33 const nogoMainTpl = `
34 package main
35
36
37 import (
38 {{- if .NeedRegexp }}
39 "regexp"
40 {{- end}}
41 {{- range $import := .Imports}}
42 {{$import.Name}} "{{$import.Path}}"
43 {{- end}}
44 "golang.org/x/tools/go/analysis"
45 )
46
47 var analyzers = []*analysis.Analyzer{
48 {{- range $import := .Imports}}
49 {{$import.Name}}.Analyzer,
50 {{- end}}
51 }
52
53 // configs maps analysis names to configurations.
54 var configs = map[string]config{
55 {{- range $name, $config := .Configs}}
56 {{printf "%q" $name}}: config{
57 {{- if $config.AnalyzerFlags }}
58 analyzerFlags: map[string]string {
59 {{- range $flagKey, $flagValue := $config.AnalyzerFlags}}
60 {{printf "%q: %q" $flagKey $flagValue}},
61 {{- end}}
62 },
63 {{- end -}}
64 {{- if $config.OnlyFiles}}
65 onlyFiles: []*regexp.Regexp{
66 {{- range $path, $comment := $config.OnlyFiles}}
67 {{- if $comment}}
68 // {{$comment}}
69 {{end -}}
70 {{printf "regexp.MustCompile(%q)" $path}},
71 {{- end}}
72 },
73 {{- end -}}
74 {{- if $config.ExcludeFiles}}
75 excludeFiles: []*regexp.Regexp{
76 {{- range $path, $comment := $config.ExcludeFiles}}
77 {{- if $comment}}
78 // {{$comment}}
79 {{end -}}
80 {{printf "regexp.MustCompile(%q)" $path}},
81 {{- end}}
82 },
83 {{- end}}
84 },
85 {{- end}}
86 }
87 `
88
89 func genNogoMain(args []string) error {
90 analyzerImportPaths := multiFlag{}
91 flags := flag.NewFlagSet("generate_nogo_main", flag.ExitOnError)
92 out := flags.String("output", "", "output file to write (defaults to stdout)")
93 flags.Var(&analyzerImportPaths, "analyzer_importpath", "import path of an analyzer library")
94 configFile := flags.String("config", "", "nogo config file")
95 if err := flags.Parse(args); err != nil {
96 return err
97 }
98 if *out == "" {
99 return errors.New("must provide output file")
100 }
101
102 outFile := os.Stdout
103 var cErr error
104 outFile, err := os.Create(*out)
105 if err != nil {
106 return fmt.Errorf("os.Create(%q): %v", *out, err)
107 }
108 defer func() {
109 if err := outFile.Close(); err != nil {
110 cErr = fmt.Errorf("error closing %s: %v", outFile.Name(), err)
111 }
112 }()
113
114 config, err := buildConfig(*configFile)
115 if err != nil {
116 return err
117 }
118
119 type Import struct {
120 Path, Name string
121 }
122
123 suffix := 1
124 imports := make([]Import, 0, len(analyzerImportPaths))
125 for _, path := range analyzerImportPaths {
126 imports = append(imports, Import{
127 Path: path,
128 Name: "analyzer" + strconv.Itoa(suffix)})
129 if suffix == math.MaxInt32 {
130 return fmt.Errorf("cannot generate more than %d analyzers", suffix)
131 }
132 suffix++
133 }
134 data := struct {
135 Imports []Import
136 Configs Configs
137 NeedRegexp bool
138 }{
139 Imports: imports,
140 Configs: config,
141 }
142 for _, c := range config {
143 if len(c.OnlyFiles) > 0 || len(c.ExcludeFiles) > 0 {
144 data.NeedRegexp = true
145 break
146 }
147 }
148
149 tpl := template.Must(template.New("source").Parse(nogoMainTpl))
150 if err := tpl.Execute(outFile, data); err != nil {
151 return fmt.Errorf("template.Execute failed: %v", err)
152 }
153 return cErr
154 }
155
156 func buildConfig(path string) (Configs, error) {
157 if path == "" {
158 return Configs{}, nil
159 }
160 b, err := ioutil.ReadFile(path)
161 if err != nil {
162 return Configs{}, fmt.Errorf("failed to read config file: %v", err)
163 }
164 configs := make(Configs)
165 if err = json.Unmarshal(b, &configs); err != nil {
166 return Configs{}, fmt.Errorf("failed to unmarshal config file: %v", err)
167 }
168 for name, config := range configs {
169 for pattern := range config.OnlyFiles {
170 if _, err := regexp.Compile(pattern); err != nil {
171 return Configs{}, fmt.Errorf("invalid pattern for analysis %q: %v", name, err)
172 }
173 }
174 for pattern := range config.ExcludeFiles {
175 if _, err := regexp.Compile(pattern); err != nil {
176 return Configs{}, fmt.Errorf("invalid pattern for analysis %q: %v", name, err)
177 }
178 }
179 configs[name] = Config{
180
181 OnlyFiles: config.OnlyFiles,
182 ExcludeFiles: config.ExcludeFiles,
183 AnalyzerFlags: config.AnalyzerFlags,
184 }
185 }
186 return configs, nil
187 }
188
189 type Configs map[string]Config
190
191 type Config struct {
192 Description string
193 OnlyFiles map[string]string `json:"only_files"`
194 ExcludeFiles map[string]string `json:"exclude_files"`
195 AnalyzerFlags map[string]string `json:"analyzer_flags"`
196 }
197
View as plain text