.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / honnef.co / go / tools@v0.1.1 / analysis / facts / purity.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/honnef.co/go/tools@v0.1.1/analysis/facts/purity.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/honnef.co/go/tools@v0.1.1/analysis/facts/purity.go
new file mode 100644 (file)
index 0000000..582b620
--- /dev/null
@@ -0,0 +1,178 @@
+package facts
+
+import (
+       "go/types"
+       "reflect"
+
+       "honnef.co/go/tools/go/ir"
+       "honnef.co/go/tools/go/ir/irutil"
+       "honnef.co/go/tools/internal/passes/buildir"
+
+       "golang.org/x/tools/go/analysis"
+)
+
+type IsPure struct{}
+
+func (*IsPure) AFact()           {}
+func (d *IsPure) String() string { return "is pure" }
+
+type PurityResult map[*types.Func]*IsPure
+
+var Purity = &analysis.Analyzer{
+       Name:       "fact_purity",
+       Doc:        "Mark pure functions",
+       Run:        purity,
+       Requires:   []*analysis.Analyzer{buildir.Analyzer},
+       FactTypes:  []analysis.Fact{(*IsPure)(nil)},
+       ResultType: reflect.TypeOf(PurityResult{}),
+}
+
+var pureStdlib = map[string]struct{}{
+       "errors.New":                      {},
+       "fmt.Errorf":                      {},
+       "fmt.Sprintf":                     {},
+       "fmt.Sprint":                      {},
+       "sort.Reverse":                    {},
+       "strings.Map":                     {},
+       "strings.Repeat":                  {},
+       "strings.Replace":                 {},
+       "strings.Title":                   {},
+       "strings.ToLower":                 {},
+       "strings.ToLowerSpecial":          {},
+       "strings.ToTitle":                 {},
+       "strings.ToTitleSpecial":          {},
+       "strings.ToUpper":                 {},
+       "strings.ToUpperSpecial":          {},
+       "strings.Trim":                    {},
+       "strings.TrimFunc":                {},
+       "strings.TrimLeft":                {},
+       "strings.TrimLeftFunc":            {},
+       "strings.TrimPrefix":              {},
+       "strings.TrimRight":               {},
+       "strings.TrimRightFunc":           {},
+       "strings.TrimSpace":               {},
+       "strings.TrimSuffix":              {},
+       "(*net/http.Request).WithContext": {},
+}
+
+func purity(pass *analysis.Pass) (interface{}, error) {
+       seen := map[*ir.Function]struct{}{}
+       irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
+       var check func(fn *ir.Function) (ret bool)
+       check = func(fn *ir.Function) (ret bool) {
+               if fn.Object() == nil {
+                       // TODO(dh): support closures
+                       return false
+               }
+               if pass.ImportObjectFact(fn.Object(), new(IsPure)) {
+                       return true
+               }
+               if fn.Pkg != irpkg {
+                       // Function is in another package but wasn't marked as
+                       // pure, ergo it isn't pure
+                       return false
+               }
+               // Break recursion
+               if _, ok := seen[fn]; ok {
+                       return false
+               }
+
+               seen[fn] = struct{}{}
+               defer func() {
+                       if ret {
+                               pass.ExportObjectFact(fn.Object(), &IsPure{})
+                       }
+               }()
+
+               if irutil.IsStub(fn) {
+                       return false
+               }
+
+               if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok {
+                       return true
+               }
+
+               if fn.Signature.Results().Len() == 0 {
+                       // A function with no return values is empty or is doing some
+                       // work we cannot see (for example because of build tags);
+                       // don't consider it pure.
+                       return false
+               }
+
+               for _, param := range fn.Params {
+                       // TODO(dh): this may not be strictly correct. pure code
+                       // can, to an extent, operate on non-basic types.
+                       if _, ok := param.Type().Underlying().(*types.Basic); !ok {
+                               return false
+                       }
+               }
+
+               // Don't consider external functions pure.
+               if fn.Blocks == nil {
+                       return false
+               }
+               checkCall := func(common *ir.CallCommon) bool {
+                       if common.IsInvoke() {
+                               return false
+                       }
+                       builtin, ok := common.Value.(*ir.Builtin)
+                       if !ok {
+                               if common.StaticCallee() != fn {
+                                       if common.StaticCallee() == nil {
+                                               return false
+                                       }
+                                       if !check(common.StaticCallee()) {
+                                               return false
+                                       }
+                               }
+                       } else {
+                               switch builtin.Name() {
+                               case "len", "cap":
+                               default:
+                                       return false
+                               }
+                       }
+                       return true
+               }
+               for _, b := range fn.Blocks {
+                       for _, ins := range b.Instrs {
+                               switch ins := ins.(type) {
+                               case *ir.Call:
+                                       if !checkCall(ins.Common()) {
+                                               return false
+                                       }
+                               case *ir.Defer:
+                                       if !checkCall(&ins.Call) {
+                                               return false
+                                       }
+                               case *ir.Select:
+                                       return false
+                               case *ir.Send:
+                                       return false
+                               case *ir.Go:
+                                       return false
+                               case *ir.Panic:
+                                       return false
+                               case *ir.Store:
+                                       return false
+                               case *ir.FieldAddr:
+                                       return false
+                               case *ir.Alloc:
+                                       return false
+                               case *ir.Load:
+                                       return false
+                               }
+                       }
+               }
+               return true
+       }
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               check(fn)
+       }
+
+       out := PurityResult{}
+       for _, fact := range pass.AllObjectFacts() {
+               out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure)
+       }
+       return out, nil
+}