github.com/AngusLu/go-swagger@v0.28.0/codescan/application.go (about)

     1  package codescan
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  	"go/types"
     8  	"log"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/go-openapi/swag"
    13  
    14  	"golang.org/x/tools/go/packages"
    15  
    16  	"github.com/go-openapi/spec"
    17  )
    18  
    19  const pkgLoadMode = packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo
    20  
    21  func safeConvert(str string) bool {
    22  	b, err := swag.ConvertBool(str)
    23  	if err != nil {
    24  		return false
    25  	}
    26  	return b
    27  }
    28  
    29  // Debug is true when process is run with DEBUG=1 env var
    30  var Debug = safeConvert(os.Getenv("DEBUG"))
    31  
    32  type node uint32
    33  
    34  const (
    35  	metaNode node = 1 << iota
    36  	routeNode
    37  	operationNode
    38  	modelNode
    39  	parametersNode
    40  	responseNode
    41  )
    42  
    43  // Options for the scanner
    44  type Options struct {
    45  	Packages    []string
    46  	InputSpec   *spec.Swagger
    47  	ScanModels  bool
    48  	WorkDir     string
    49  	BuildTags   string
    50  	ExcludeDeps bool
    51  	Include     []string
    52  	Exclude     []string
    53  	IncludeTags []string
    54  	ExcludeTags []string
    55  }
    56  
    57  type scanCtx struct {
    58  	pkgs []*packages.Package
    59  	app  *typeIndex
    60  }
    61  
    62  func sliceToSet(names []string) map[string]bool {
    63  	result := make(map[string]bool)
    64  	for _, v := range names {
    65  		result[v] = true
    66  	}
    67  	return result
    68  }
    69  
    70  // Run the scanner to produce a spec with the options provided
    71  func Run(opts *Options) (*spec.Swagger, error) {
    72  	sc, err := newScanCtx(opts)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	sb := newSpecBuilder(opts.InputSpec, sc, opts.ScanModels)
    77  	return sb.Build()
    78  }
    79  
    80  func newScanCtx(opts *Options) (*scanCtx, error) {
    81  	cfg := &packages.Config{
    82  		Dir:   opts.WorkDir,
    83  		Mode:  pkgLoadMode,
    84  		Tests: false,
    85  	}
    86  	if opts.BuildTags != "" {
    87  		cfg.BuildFlags = []string{"-tags", opts.BuildTags}
    88  	}
    89  
    90  	pkgs, err := packages.Load(cfg, opts.Packages...)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	app, err := newTypeIndex(pkgs, opts.ExcludeDeps,
    96  		sliceToSet(opts.IncludeTags), sliceToSet(opts.ExcludeTags),
    97  		opts.Include, opts.Exclude)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	return &scanCtx{
   103  		pkgs: pkgs,
   104  		app:  app,
   105  	}, nil
   106  }
   107  
   108  type entityDecl struct {
   109  	Comments               *ast.CommentGroup
   110  	Type                   *types.Named
   111  	Ident                  *ast.Ident
   112  	Spec                   *ast.TypeSpec
   113  	File                   *ast.File
   114  	Pkg                    *packages.Package
   115  	hasModelAnnotation     bool
   116  	hasResponseAnnotation  bool
   117  	hasParameterAnnotation bool
   118  }
   119  
   120  func (d *entityDecl) Names() (name, goName string) {
   121  	goName = d.Ident.Name
   122  	name = goName
   123  	if d.Comments == nil {
   124  		return
   125  	}
   126  
   127  DECLS:
   128  	for _, cmt := range d.Comments.List {
   129  		for _, ln := range strings.Split(cmt.Text, "\n") {
   130  			matches := rxModelOverride.FindStringSubmatch(ln)
   131  			if len(matches) > 0 {
   132  				d.hasModelAnnotation = true
   133  			}
   134  			if len(matches) > 1 && len(matches[1]) > 0 {
   135  				name = matches[1]
   136  				break DECLS
   137  			}
   138  		}
   139  	}
   140  	return
   141  }
   142  
   143  func (d *entityDecl) ResponseNames() (name, goName string) {
   144  	goName = d.Ident.Name
   145  	name = goName
   146  	if d.Comments == nil {
   147  		return
   148  	}
   149  
   150  DECLS:
   151  	for _, cmt := range d.Comments.List {
   152  		for _, ln := range strings.Split(cmt.Text, "\n") {
   153  			matches := rxResponseOverride.FindStringSubmatch(ln)
   154  			if len(matches) > 0 {
   155  				d.hasResponseAnnotation = true
   156  			}
   157  			if len(matches) > 1 && len(matches[1]) > 0 {
   158  				name = matches[1]
   159  				break DECLS
   160  			}
   161  		}
   162  	}
   163  	return
   164  }
   165  
   166  func (d *entityDecl) OperationIDS() (result []string) {
   167  	if d == nil || d.Comments == nil {
   168  		return nil
   169  	}
   170  
   171  	for _, cmt := range d.Comments.List {
   172  		for _, ln := range strings.Split(cmt.Text, "\n") {
   173  			matches := rxParametersOverride.FindStringSubmatch(ln)
   174  			if len(matches) > 0 {
   175  				d.hasParameterAnnotation = true
   176  			}
   177  			if len(matches) > 1 && len(matches[1]) > 0 {
   178  				for _, pt := range strings.Split(matches[1], " ") {
   179  					tr := strings.TrimSpace(pt)
   180  					if len(tr) > 0 {
   181  						result = append(result, tr)
   182  					}
   183  				}
   184  			}
   185  		}
   186  	}
   187  	return
   188  }
   189  
   190  func (d *entityDecl) HasModelAnnotation() bool {
   191  	if d.hasModelAnnotation {
   192  		return true
   193  	}
   194  	if d.Comments == nil {
   195  		return false
   196  	}
   197  	for _, cmt := range d.Comments.List {
   198  		for _, ln := range strings.Split(cmt.Text, "\n") {
   199  			matches := rxModelOverride.FindStringSubmatch(ln)
   200  			if len(matches) > 0 {
   201  				d.hasModelAnnotation = true
   202  				return true
   203  			}
   204  		}
   205  	}
   206  	return false
   207  }
   208  
   209  func (d *entityDecl) HasResponseAnnotation() bool {
   210  	if d.hasResponseAnnotation {
   211  		return true
   212  	}
   213  	if d.Comments == nil {
   214  		return false
   215  	}
   216  	for _, cmt := range d.Comments.List {
   217  		for _, ln := range strings.Split(cmt.Text, "\n") {
   218  			matches := rxResponseOverride.FindStringSubmatch(ln)
   219  			if len(matches) > 0 {
   220  				d.hasResponseAnnotation = true
   221  				return true
   222  			}
   223  		}
   224  	}
   225  	return false
   226  }
   227  
   228  func (d *entityDecl) HasParameterAnnotation() bool {
   229  	if d.hasParameterAnnotation {
   230  		return true
   231  	}
   232  	if d.Comments == nil {
   233  		return false
   234  	}
   235  	for _, cmt := range d.Comments.List {
   236  		for _, ln := range strings.Split(cmt.Text, "\n") {
   237  			matches := rxParametersOverride.FindStringSubmatch(ln)
   238  			if len(matches) > 0 {
   239  				d.hasParameterAnnotation = true
   240  				return true
   241  			}
   242  		}
   243  	}
   244  	return false
   245  }
   246  
   247  func (s *scanCtx) FindDecl(pkgPath, name string) (*entityDecl, bool) {
   248  	if pkg, ok := s.app.AllPackages[pkgPath]; ok {
   249  		for _, file := range pkg.Syntax {
   250  			for _, d := range file.Decls {
   251  				gd, ok := d.(*ast.GenDecl)
   252  				if !ok {
   253  					continue
   254  				}
   255  
   256  				for _, sp := range gd.Specs {
   257  					if ts, ok := sp.(*ast.TypeSpec); ok && ts.Name.Name == name {
   258  						def, ok := pkg.TypesInfo.Defs[ts.Name]
   259  						if !ok {
   260  							debugLog("couldn't find type info for %s", ts.Name)
   261  							continue
   262  						}
   263  						nt, isNamed := def.Type().(*types.Named)
   264  						if !isNamed {
   265  							debugLog("%s is not a named type but a %T", ts.Name, def.Type())
   266  							continue
   267  						}
   268  						decl := &entityDecl{
   269  							Comments: gd.Doc,
   270  							Type:     nt,
   271  							Ident:    ts.Name,
   272  							Spec:     ts,
   273  							File:     file,
   274  							Pkg:      pkg,
   275  						}
   276  						return decl, true
   277  					}
   278  
   279  				}
   280  			}
   281  		}
   282  	}
   283  	return nil, false
   284  }
   285  
   286  func (s *scanCtx) FindModel(pkgPath, name string) (*entityDecl, bool) {
   287  	for _, cand := range s.app.Models {
   288  		ct := cand.Type.Obj()
   289  		if ct.Name() == name && ct.Pkg().Path() == pkgPath {
   290  			return cand, true
   291  		}
   292  	}
   293  	if decl, found := s.FindDecl(pkgPath, name); found {
   294  		s.app.ExtraModels[decl.Ident] = decl
   295  		return decl, true
   296  	}
   297  	return nil, false
   298  }
   299  
   300  func (s *scanCtx) PkgForPath(pkgPath string) (*packages.Package, bool) {
   301  	v, ok := s.app.AllPackages[pkgPath]
   302  	return v, ok
   303  }
   304  
   305  func (s *scanCtx) DeclForType(t types.Type) (*entityDecl, bool) {
   306  	switch tpe := t.(type) {
   307  	case *types.Pointer:
   308  		return s.DeclForType(tpe.Elem())
   309  	case *types.Named:
   310  		return s.FindDecl(tpe.Obj().Pkg().Path(), tpe.Obj().Name())
   311  
   312  	default:
   313  		log.Printf("unknown type to find the package for [%T]: %s", t, t.String())
   314  		return nil, false
   315  	}
   316  }
   317  
   318  func (s *scanCtx) PkgForType(t types.Type) (*packages.Package, bool) {
   319  	switch tpe := t.(type) {
   320  	// case *types.Basic:
   321  	// case *types.Struct:
   322  	// case *types.Pointer:
   323  	// case *types.Interface:
   324  	// case *types.Array:
   325  	// case *types.Slice:
   326  	// case *types.Map:
   327  	case *types.Named:
   328  		v, ok := s.app.AllPackages[tpe.Obj().Pkg().Path()]
   329  		return v, ok
   330  	default:
   331  		log.Printf("unknown type to find the package for [%T]: %s", t, t.String())
   332  		return nil, false
   333  	}
   334  }
   335  
   336  func (s *scanCtx) FindComments(pkg *packages.Package, name string) (*ast.CommentGroup, bool) {
   337  	for _, f := range pkg.Syntax {
   338  		for _, d := range f.Decls {
   339  			gd, ok := d.(*ast.GenDecl)
   340  			if !ok {
   341  				continue
   342  			}
   343  
   344  			for _, s := range gd.Specs {
   345  				if ts, ok := s.(*ast.TypeSpec); ok {
   346  					if ts.Name.Name == name {
   347  						return gd.Doc, true
   348  					}
   349  				}
   350  			}
   351  		}
   352  	}
   353  	return nil, false
   354  }
   355  
   356  func (s *scanCtx) FindEnumValues(pkg *packages.Package, enumName string) (list []interface{}, descList []string, _ bool) {
   357  	for _, f := range pkg.Syntax {
   358  		for _, d := range f.Decls {
   359  			gd, ok := d.(*ast.GenDecl)
   360  			if !ok {
   361  				continue
   362  			}
   363  
   364  			if gd.Tok != token.CONST {
   365  				continue
   366  			}
   367  
   368  			for _, s := range gd.Specs {
   369  				if vs, ok := s.(*ast.ValueSpec); ok {
   370  					if vsIdent, ok := vs.Type.(*ast.Ident); ok {
   371  						if vsIdent.Name == enumName {
   372  							if len(vs.Values) > 0 {
   373  								if bl, ok := vs.Values[0].(*ast.BasicLit); ok {
   374  									blValue := getEnumBasicLitValue(bl)
   375  									list = append(list, blValue)
   376  
   377  									// build the enum description
   378  									var (
   379  										desc     = &strings.Builder{}
   380  										namesLen = len(vs.Names)
   381  									)
   382  									desc.WriteString(fmt.Sprintf("%v ", blValue))
   383  									for i, name := range vs.Names {
   384  										desc.WriteString(name.Name)
   385  										if i < namesLen-1 {
   386  											desc.WriteString(" ")
   387  										}
   388  									}
   389  									if vs.Doc != nil {
   390  										docListLen := len(vs.Doc.List)
   391  										if docListLen > 0 {
   392  											desc.WriteString(" ")
   393  										}
   394  										for i, doc := range vs.Doc.List {
   395  											if doc.Text != "" {
   396  												var text = strings.TrimPrefix(doc.Text, "//")
   397  												desc.WriteString(text)
   398  												if i < docListLen-1 {
   399  													desc.WriteString(" ")
   400  												}
   401  											}
   402  										}
   403  									}
   404  									descList = append(descList, desc.String())
   405  								}
   406  							}
   407  						}
   408  					}
   409  				}
   410  			}
   411  		}
   412  	}
   413  	return list, descList, true
   414  }
   415  
   416  func newTypeIndex(pkgs []*packages.Package,
   417  	excludeDeps bool, includeTags, excludeTags map[string]bool,
   418  	includePkgs, excludePkgs []string) (*typeIndex, error) {
   419  
   420  	ac := &typeIndex{
   421  		AllPackages: make(map[string]*packages.Package),
   422  		Models:      make(map[*ast.Ident]*entityDecl),
   423  		ExtraModels: make(map[*ast.Ident]*entityDecl),
   424  		excludeDeps: excludeDeps,
   425  		includeTags: includeTags,
   426  		excludeTags: excludeTags,
   427  		includePkgs: includePkgs,
   428  		excludePkgs: excludePkgs,
   429  	}
   430  	if err := ac.build(pkgs); err != nil {
   431  		return nil, err
   432  	}
   433  	return ac, nil
   434  }
   435  
   436  type typeIndex struct {
   437  	AllPackages map[string]*packages.Package
   438  	Models      map[*ast.Ident]*entityDecl
   439  	ExtraModels map[*ast.Ident]*entityDecl
   440  	Meta        []metaSection
   441  	Routes      []parsedPathContent
   442  	Operations  []parsedPathContent
   443  	Parameters  []*entityDecl
   444  	Responses   []*entityDecl
   445  	excludeDeps bool
   446  	includeTags map[string]bool
   447  	excludeTags map[string]bool
   448  	includePkgs []string
   449  	excludePkgs []string
   450  }
   451  
   452  func (a *typeIndex) build(pkgs []*packages.Package) error {
   453  	for _, pkg := range pkgs {
   454  		if _, known := a.AllPackages[pkg.PkgPath]; known {
   455  			continue
   456  		}
   457  		a.AllPackages[pkg.PkgPath] = pkg
   458  		if err := a.processPackage(pkg); err != nil {
   459  			return err
   460  		}
   461  		if err := a.walkImports(pkg); err != nil {
   462  			return err
   463  		}
   464  	}
   465  
   466  	return nil
   467  }
   468  
   469  func (a *typeIndex) processPackage(pkg *packages.Package) error {
   470  	if !shouldAcceptPkg(pkg.PkgPath, a.includePkgs, a.excludePkgs) {
   471  		debugLog("package %s is ignored due to rules", pkg.Name)
   472  		return nil
   473  	}
   474  
   475  	for _, file := range pkg.Syntax {
   476  		n, err := a.detectNodes(file)
   477  		if err != nil {
   478  			return err
   479  		}
   480  
   481  		if n&metaNode != 0 {
   482  			a.Meta = append(a.Meta, metaSection{Comments: file.Doc})
   483  		}
   484  
   485  		if n&operationNode != 0 {
   486  			for _, cmts := range file.Comments {
   487  				pp := parsePathAnnotation(rxOperation, cmts.List)
   488  				if pp.Method == "" {
   489  					continue // not a valid operation
   490  				}
   491  				if !shouldAcceptTag(pp.Tags, a.includeTags, a.excludeTags) {
   492  					debugLog("operation %s %s is ignored due to tag rules", pp.Method, pp.Path)
   493  					continue
   494  				}
   495  				a.Operations = append(a.Operations, pp)
   496  			}
   497  		}
   498  
   499  		if n&routeNode != 0 {
   500  			for _, cmts := range file.Comments {
   501  				pp := parsePathAnnotation(rxRoute, cmts.List)
   502  				if pp.Method == "" {
   503  					continue // not a valid operation
   504  				}
   505  				if !shouldAcceptTag(pp.Tags, a.includeTags, a.excludeTags) {
   506  					debugLog("operation %s %s is ignored due to tag rules", pp.Method, pp.Path)
   507  					continue
   508  				}
   509  				a.Routes = append(a.Routes, pp)
   510  			}
   511  		}
   512  
   513  		for _, dt := range file.Decls {
   514  			switch fd := dt.(type) {
   515  			case *ast.BadDecl:
   516  				continue
   517  			case *ast.FuncDecl:
   518  				if fd.Body == nil {
   519  					continue
   520  				}
   521  				for _, stmt := range fd.Body.List {
   522  					if dstm, ok := stmt.(*ast.DeclStmt); ok {
   523  						if gd, isGD := dstm.Decl.(*ast.GenDecl); isGD {
   524  							a.processDecl(pkg, file, n, gd)
   525  						}
   526  					}
   527  				}
   528  			case *ast.GenDecl:
   529  				a.processDecl(pkg, file, n, fd)
   530  			}
   531  		}
   532  	}
   533  	return nil
   534  }
   535  
   536  func (a *typeIndex) processDecl(pkg *packages.Package, file *ast.File, n node, gd *ast.GenDecl) {
   537  	for _, sp := range gd.Specs {
   538  		switch ts := sp.(type) {
   539  		case *ast.ValueSpec:
   540  			debugLog("saw value spec: %v", ts.Names)
   541  			return
   542  		case *ast.ImportSpec:
   543  			debugLog("saw import spec: %v", ts.Name)
   544  			return
   545  		case *ast.TypeSpec:
   546  			def, ok := pkg.TypesInfo.Defs[ts.Name]
   547  			if !ok {
   548  				debugLog("couldn't find type info for %s", ts.Name)
   549  				continue
   550  			}
   551  			nt, isNamed := def.Type().(*types.Named)
   552  			if !isNamed {
   553  				debugLog("%s is not a named type but a %T", ts.Name, def.Type())
   554  				continue
   555  			}
   556  			decl := &entityDecl{
   557  				Comments: gd.Doc,
   558  				Type:     nt,
   559  				Ident:    ts.Name,
   560  				Spec:     ts,
   561  				File:     file,
   562  				Pkg:      pkg,
   563  			}
   564  			key := ts.Name
   565  			if n&modelNode != 0 && decl.HasModelAnnotation() {
   566  				a.Models[key] = decl
   567  			}
   568  			if n&parametersNode != 0 && decl.HasParameterAnnotation() {
   569  				a.Parameters = append(a.Parameters, decl)
   570  			}
   571  			if n&responseNode != 0 && decl.HasResponseAnnotation() {
   572  				a.Responses = append(a.Responses, decl)
   573  			}
   574  		}
   575  	}
   576  }
   577  
   578  func (a *typeIndex) walkImports(pkg *packages.Package) error {
   579  	if a.excludeDeps {
   580  		return nil
   581  	}
   582  	for _, v := range pkg.Imports {
   583  		if _, known := a.AllPackages[v.PkgPath]; known {
   584  			continue
   585  		}
   586  
   587  		a.AllPackages[v.PkgPath] = v
   588  		if err := a.processPackage(v); err != nil {
   589  			return err
   590  		}
   591  		if err := a.walkImports(v); err != nil {
   592  			return err
   593  		}
   594  	}
   595  	return nil
   596  }
   597  
   598  func (a *typeIndex) detectNodes(file *ast.File) (node, error) {
   599  	var n node
   600  	for _, comments := range file.Comments {
   601  		var seenStruct string
   602  		for _, cline := range comments.List {
   603  			if cline == nil {
   604  				continue
   605  			}
   606  		}
   607  
   608  		for _, cline := range comments.List {
   609  			if cline == nil {
   610  				continue
   611  			}
   612  
   613  			matches := rxSwaggerAnnotation.FindStringSubmatch(cline.Text)
   614  			if len(matches) < 2 {
   615  				continue
   616  			}
   617  
   618  			switch matches[1] {
   619  			case "route":
   620  				n |= routeNode
   621  			case "operation":
   622  				n |= operationNode
   623  			case "model":
   624  				n |= modelNode
   625  				if seenStruct == "" || seenStruct == matches[1] {
   626  					seenStruct = matches[1]
   627  				} else {
   628  					return 0, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text)
   629  				}
   630  			case "meta":
   631  				n |= metaNode
   632  			case "parameters":
   633  				n |= parametersNode
   634  				if seenStruct == "" || seenStruct == matches[1] {
   635  					seenStruct = matches[1]
   636  				} else {
   637  					return 0, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text)
   638  				}
   639  			case "response":
   640  				n |= responseNode
   641  				if seenStruct == "" || seenStruct == matches[1] {
   642  					seenStruct = matches[1]
   643  				} else {
   644  					return 0, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text)
   645  				}
   646  			case "strfmt", "name", "discriminated", "file", "enum", "default", "alias", "type":
   647  				// TODO: perhaps collect these and pass along to avoid lookups later on
   648  			case "allOf":
   649  			case "ignore":
   650  			default:
   651  				return 0, fmt.Errorf("classifier: unknown swagger annotation %q", matches[1])
   652  			}
   653  		}
   654  	}
   655  	return n, nil
   656  }
   657  
   658  func debugLog(format string, args ...interface{}) {
   659  	if Debug {
   660  		log.Printf(format, args...)
   661  	}
   662  }