golang.org/x/tools@v0.21.0/go/analysis/passes/pkgfact/pkgfact.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // The pkgfact package is a demonstration and test of the package fact
     6  // mechanism.
     7  //
     8  // The output of the pkgfact analysis is a set of key/values pairs
     9  // gathered from the analyzed package and its imported dependencies.
    10  // Each key/value pair comes from a top-level constant declaration
    11  // whose name starts and ends with "_".  For example:
    12  //
    13  //	package p
    14  //
    15  //	const _greeting_  = "hello"
    16  //	const _audience_  = "world"
    17  //
    18  // the pkgfact analysis output for package p would be:
    19  //
    20  //	{"greeting": "hello", "audience": "world"}.
    21  //
    22  // In addition, the analysis reports a diagnostic at each import
    23  // showing which key/value pairs it contributes.
    24  package pkgfact
    25  
    26  import (
    27  	"fmt"
    28  	"go/ast"
    29  	"go/token"
    30  	"go/types"
    31  	"reflect"
    32  	"sort"
    33  	"strings"
    34  
    35  	"golang.org/x/tools/go/analysis"
    36  )
    37  
    38  var Analyzer = &analysis.Analyzer{
    39  	Name:       "pkgfact",
    40  	Doc:        "gather name/value pairs from constant declarations",
    41  	URL:        "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/pkgfact",
    42  	Run:        run,
    43  	FactTypes:  []analysis.Fact{new(pairsFact)},
    44  	ResultType: reflect.TypeOf(map[string]string{}),
    45  }
    46  
    47  // A pairsFact is a package-level fact that records
    48  // an set of key=value strings accumulated from constant
    49  // declarations in this package and its dependencies.
    50  // Elements are ordered by keys, which are unique.
    51  type pairsFact []string
    52  
    53  func (f *pairsFact) AFact()         {}
    54  func (f *pairsFact) String() string { return "pairs(" + strings.Join(*f, ", ") + ")" }
    55  
    56  func run(pass *analysis.Pass) (interface{}, error) {
    57  	result := make(map[string]string)
    58  
    59  	// At each import, print the fact from the imported
    60  	// package and accumulate its information into the result.
    61  	// (Warning: accumulation leads to quadratic growth of work.)
    62  	doImport := func(spec *ast.ImportSpec) {
    63  		pkg := imported(pass.TypesInfo, spec)
    64  		var fact pairsFact
    65  		if pass.ImportPackageFact(pkg, &fact) {
    66  			for _, pair := range fact {
    67  				eq := strings.IndexByte(pair, '=')
    68  				result[pair[:eq]] = pair[1+eq:]
    69  			}
    70  			pass.ReportRangef(spec, "%s", strings.Join(fact, " "))
    71  		}
    72  	}
    73  
    74  	// At each "const _name_ = value", add a fact into env.
    75  	doConst := func(spec *ast.ValueSpec) {
    76  		if len(spec.Names) == len(spec.Values) {
    77  			for i := range spec.Names {
    78  				name := spec.Names[i].Name
    79  				if strings.HasPrefix(name, "_") && strings.HasSuffix(name, "_") {
    80  
    81  					if key := strings.Trim(name, "_"); key != "" {
    82  						value := pass.TypesInfo.Types[spec.Values[i]].Value.String()
    83  						result[key] = value
    84  					}
    85  				}
    86  			}
    87  		}
    88  	}
    89  
    90  	for _, f := range pass.Files {
    91  		for _, decl := range f.Decls {
    92  			if decl, ok := decl.(*ast.GenDecl); ok {
    93  				for _, spec := range decl.Specs {
    94  					switch decl.Tok {
    95  					case token.IMPORT:
    96  						doImport(spec.(*ast.ImportSpec))
    97  					case token.CONST:
    98  						doConst(spec.(*ast.ValueSpec))
    99  					}
   100  				}
   101  			}
   102  		}
   103  	}
   104  
   105  	// Sort/deduplicate the result and save it as a package fact.
   106  	keys := make([]string, 0, len(result))
   107  	for key := range result {
   108  		keys = append(keys, key)
   109  	}
   110  	sort.Strings(keys)
   111  	var fact pairsFact
   112  	for _, key := range keys {
   113  		fact = append(fact, fmt.Sprintf("%s=%s", key, result[key]))
   114  	}
   115  	if len(fact) > 0 {
   116  		pass.ExportPackageFact(&fact)
   117  	}
   118  
   119  	return result, nil
   120  }
   121  
   122  func imported(info *types.Info, spec *ast.ImportSpec) *types.Package {
   123  	obj, ok := info.Implicits[spec]
   124  	if !ok {
   125  		obj = info.Defs[spec.Name] // renaming import
   126  	}
   127  	return obj.(*types.PkgName).Imported()
   128  }