134 lines
4.4 KiB
Go
134 lines
4.4 KiB
Go
/*
|
|
Copyright 2019 The Crossplane Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
// Package generate generates method sets for Go types.
|
|
package generate
|
|
|
|
import (
|
|
"bytes"
|
|
"go/parser"
|
|
"go/token"
|
|
"go/types"
|
|
"os"
|
|
|
|
"github.com/dave/jennifer/jen"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/tools/go/packages"
|
|
|
|
"github.com/crossplane/crossplane-tools/internal/match"
|
|
"github.com/crossplane/crossplane-tools/internal/method"
|
|
)
|
|
|
|
// HeaderGenerated is added to all files generated by angryjet.
|
|
// See https://github.com/golang/go/issues/13560#issuecomment-288457920.
|
|
const HeaderGenerated = "Code generated by angryjet. DO NOT EDIT."
|
|
|
|
type options struct {
|
|
Matches match.Object
|
|
ImportAliases map[string]string
|
|
Headers []string
|
|
}
|
|
|
|
// A WriteOption configures method generation behaviour.
|
|
type WriteOption func(o *options)
|
|
|
|
// WithHeaders specifies strings to be written as comments to the generated
|
|
// file, above the package definition. Single line strings use // comments,
|
|
// while multiline strings use /**/ comments.
|
|
func WithHeaders(h ...string) WriteOption {
|
|
return func(o *options) {
|
|
o.Headers = append(o.Headers, h...)
|
|
}
|
|
}
|
|
|
|
// WithMatcher specifies an Object matcher that is used to filter the Objects
|
|
// within the package down to the set that need the generated methods.
|
|
func WithMatcher(m match.Object) WriteOption {
|
|
return func(o *options) {
|
|
o.Matches = m
|
|
}
|
|
}
|
|
|
|
// WithImportAliases configures a map of import paths to aliases that will be
|
|
// used when generating code. For example if a generated method requires
|
|
// "example.org/foo/bar" it may refer to that package as "foobar" by supplying
|
|
// map[string]string{"example.org/foo/bar": "foobar"}.
|
|
func WithImportAliases(ia map[string]string) WriteOption {
|
|
return func(o *options) {
|
|
o.ImportAliases = ia
|
|
}
|
|
}
|
|
|
|
// WriteMethods writes the supplied methods for each object in the supplied
|
|
// package to the supplied file. Use WithMatcher to limit the objects for which
|
|
// methods will be written. Methods will not be generated if a method with the
|
|
// same name is already defined for the object outside of the supplied filename.
|
|
// Files will not be written if they would contain no methods.
|
|
func WriteMethods(p *packages.Package, ms method.Set, file string, wo ...WriteOption) error {
|
|
opts := &options{Matches: func(_ types.Object) bool { return true }}
|
|
for _, fn := range wo {
|
|
fn(opts)
|
|
}
|
|
|
|
// NewFilePath creates a new File object by taking the full package path such as:
|
|
// 'github.com/org/repo/apis/resource/v1alpha1'
|
|
// File object created using the function ('NewFile') that takes only the package
|
|
// name ('v1alpha1') is not sufficient in order to properly handle imports from
|
|
// the same package and to communicate with the Jennifer tool correctly.
|
|
// We need to create the File object by using NewFilePath (passing package path)
|
|
// so that we can communicate correctly with the library (jennifer).
|
|
f := jen.NewFilePath(p.PkgPath)
|
|
for path, alias := range opts.ImportAliases {
|
|
f.ImportAlias(path, alias)
|
|
}
|
|
for _, hc := range opts.Headers {
|
|
if hc != "" {
|
|
f.HeaderComment(hc)
|
|
}
|
|
}
|
|
f.HeaderComment(HeaderGenerated)
|
|
|
|
for _, n := range p.Types.Scope().Names() {
|
|
o := p.Types.Scope().Lookup(n)
|
|
if !opts.Matches(o) {
|
|
continue
|
|
}
|
|
ms.Write(f, o, method.DefinedOutside(p.Fset, file))
|
|
}
|
|
|
|
b := &bytes.Buffer{}
|
|
if err := f.Render(b); err != nil {
|
|
return errors.Wrap(err, "cannot render Go file")
|
|
}
|
|
|
|
if ProducedNothing(b.Bytes()) {
|
|
return nil
|
|
}
|
|
|
|
return errors.Wrap(os.WriteFile(file, b.Bytes(), 0o644), "cannot write Go file") //nolint:gosec // We're comfortable with this being world readable.
|
|
}
|
|
|
|
// ProducedNothing returns true if the supplied data is either not a valid Go
|
|
// source file, or a valid Go file that contains no top level objects or
|
|
// declarations.
|
|
func ProducedNothing(data []byte) bool {
|
|
f, err := parser.ParseFile(token.NewFileSet(), "f.go", data, 0)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
return len(f.Decls)+len(f.Scope.Objects) == 0
|
|
}
|