blob: 887fc4e857e41b8adaec903b00fb67b5be2d62a2 [file] [log] [blame]
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Command genopts generates JSON describing gopls' user options.
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"go/ast"
"go/types"
"os"
"reflect"
"strings"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/lsp/source"
)
var (
output = flag.String("output", "", "output file")
)
func main() {
flag.Parse()
if err := doMain(); err != nil {
fmt.Fprintf(os.Stderr, "Generation failed: %v\n", err)
os.Exit(1)
}
}
func doMain() error {
out := os.Stdout
if *output != "" {
var err error
out, err = os.OpenFile(*output, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
if err != nil {
return err
}
defer out.Close()
}
content, err := generate()
if err != nil {
return err
}
if _, err := out.Write(content); err != nil {
return err
}
return out.Close()
}
func generate() ([]byte, error) {
pkgs, err := packages.Load(
&packages.Config{
Mode: packages.NeedTypes | packages.NeedSyntax | packages.NeedDeps,
},
"golang.org/x/tools/internal/lsp/source",
)
if err != nil {
return nil, err
}
pkg := pkgs[0]
defaults := source.DefaultOptions()
categories := map[string][]option{}
for _, cat := range []reflect.Value{
reflect.ValueOf(defaults.DebuggingOptions),
reflect.ValueOf(defaults.UserOptions),
reflect.ValueOf(defaults.ExperimentalOptions),
} {
opts, err := loadOptions(cat, pkg)
if err != nil {
return nil, err
}
catName := strings.TrimSuffix(cat.Type().Name(), "Options")
categories[catName] = opts
}
marshaled, err := json.Marshal(categories)
if err != nil {
return nil, err
}
buf := bytes.NewBuffer(nil)
fmt.Fprintf(buf, "// Code generated by \"golang.org/x/tools/internal/lsp/source/genopts\"; DO NOT EDIT.\n\npackage source\n\nconst OptionsJson = %q\n", string(marshaled))
return buf.Bytes(), nil
}
type option struct {
Name string
Type string
Doc string
Default string
}
func loadOptions(category reflect.Value, pkg *packages.Package) ([]option, error) {
// Find the type information and ast.File corresponding to the category.
optsType := pkg.Types.Scope().Lookup(category.Type().Name())
if optsType == nil {
return nil, fmt.Errorf("could not find %v in scope %v", category.Type().Name(), pkg.Types.Scope())
}
fset := pkg.Fset
var file *ast.File
for _, f := range pkg.Syntax {
if fset.Position(f.Pos()).Filename == fset.Position(optsType.Pos()).Filename {
file = f
}
}
if file == nil {
return nil, fmt.Errorf("no file for opts type %v", optsType)
}
var opts []option
optsStruct := optsType.Type().Underlying().(*types.Struct)
for i := 0; i < optsStruct.NumFields(); i++ {
// The types field gives us the type.
typesField := optsStruct.Field(i)
path, _ := astutil.PathEnclosingInterval(file, typesField.Pos(), typesField.Pos())
if len(path) < 1 {
return nil, fmt.Errorf("could not find AST node for field %v", typesField)
}
// The AST field gives us the doc.
astField, ok := path[1].(*ast.Field)
if !ok {
return nil, fmt.Errorf("unexpected AST path %v", path)
}
// The reflect field gives us the default value.
reflectField := category.FieldByName(typesField.Name())
if !reflectField.IsValid() {
return nil, fmt.Errorf("could not find reflect field for %v", typesField.Name())
}
// Format the default value. String values should look like strings. Other stuff should look like JSON literals.
var defString string
switch def := reflectField.Interface().(type) {
case fmt.Stringer:
defString = `"` + def.String() + `"`
case string:
defString = `"` + def + `"`
default:
defString = fmt.Sprint(def)
}
if reflectField.Kind() == reflect.Map {
b, err := json.Marshal(reflectField.Interface())
if err != nil {
return nil, err
}
defString = string(b)
}
opts = append(opts, option{
Name: lowerFirst(typesField.Name()),
Type: typesField.Type().String(),
Doc: lowerFirst(astField.Doc.Text()),
Default: defString,
})
}
return opts, nil
}
func lowerFirst(x string) string {
if x == "" {
return x
}
return strings.ToLower(x[:1]) + x[1:]
}