mirror of
				https://github.com/mgechev/revive.git
				synced 2025-10-30 23:37:49 +02:00 
			
		
		
		
	first working version
This commit is contained in:
		| @@ -84,6 +84,7 @@ var allRules = append([]lint.Rule{ | ||||
| 	&rule.CognitiveComplexityRule{}, | ||||
| 	&rule.StringOfIntRule{}, | ||||
| 	&rule.EarlyReturnRule{}, | ||||
| 	&rule.UnconditionalRecursionRule{}, | ||||
| }, defaultRules...) | ||||
|  | ||||
| var allFormatters = []lint.Formatter{ | ||||
|   | ||||
							
								
								
									
										143
									
								
								rule/unconditional-recursion.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								rule/unconditional-recursion.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | ||||
| package rule | ||||
|  | ||||
| import ( | ||||
| 	"go/ast" | ||||
|  | ||||
| 	"github.com/mgechev/revive/lint" | ||||
| ) | ||||
|  | ||||
| // UnconditionalRecursionRule lints given else constructs. | ||||
| type UnconditionalRecursionRule struct{} | ||||
|  | ||||
| // Apply applies the rule to given file. | ||||
| func (r *UnconditionalRecursionRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { | ||||
| 	var failures []lint.Failure | ||||
|  | ||||
| 	onFailure := func(failure lint.Failure) { | ||||
| 		failures = append(failures, failure) | ||||
| 	} | ||||
|  | ||||
| 	// err := file.Pkg.TypeCheck() | ||||
| 	// if err != nil { | ||||
| 	// 	panic(fmt.Sprintf("Error while type-checking file %s: %v", file.Name, err)) | ||||
| 	// } | ||||
| 	w := lintUnconditionalRecursionRule{onFailure: onFailure} | ||||
| 	ast.Walk(w, file.AST) | ||||
| 	return failures | ||||
| } | ||||
|  | ||||
| // Name returns the rule name. | ||||
| func (r *UnconditionalRecursionRule) Name() string { | ||||
| 	return "unconditional-recursion" | ||||
| } | ||||
|  | ||||
| type funcDesc struct { | ||||
| 	reciverId *ast.Ident | ||||
| 	id        *ast.Ident | ||||
| } | ||||
|  | ||||
| func (fd *funcDesc) equal(other *funcDesc) bool { | ||||
| 	receiversAreEqual := (fd.reciverId == nil && other.reciverId == nil) || fd.reciverId != nil && other.reciverId != nil && fd.reciverId.Name == other.reciverId.Name | ||||
| 	idsAreEqual := (fd.id == nil && other.id == nil) || fd.id.Name == other.id.Name | ||||
|  | ||||
| 	return receiversAreEqual && idsAreEqual | ||||
| } | ||||
|  | ||||
| type funcStatus struct { | ||||
| 	funcDesc            *funcDesc | ||||
| 	seenConditionalExit bool | ||||
| } | ||||
|  | ||||
| type lintUnconditionalRecursionRule struct { | ||||
| 	onFailure   func(lint.Failure) | ||||
| 	currentFunc *funcStatus | ||||
| } | ||||
|  | ||||
| func (w lintUnconditionalRecursionRule) Visit(node ast.Node) ast.Visitor { | ||||
| 	switch n := node.(type) { | ||||
| 	case *ast.IfStmt: | ||||
| 		w.updateFuncStatus(n.Body) | ||||
| 		w.updateFuncStatus(n.Else) | ||||
| 		return nil | ||||
| 	case *ast.SelectStmt: | ||||
| 		w.updateFuncStatus(n.Body) | ||||
| 		return nil | ||||
| 	case *ast.RangeStmt: | ||||
| 		w.updateFuncStatus(n.Body) | ||||
| 		return nil | ||||
| 	case *ast.TypeSwitchStmt: | ||||
| 		w.updateFuncStatus(n.Body) | ||||
| 		return nil | ||||
| 	case *ast.SwitchStmt: | ||||
| 		w.updateFuncStatus(n.Body) | ||||
| 		return nil | ||||
| 	case *ast.GoStmt: | ||||
| 		for _, a := range n.Call.Args { | ||||
| 			ast.Walk(w, a) // check if arguments have a recursive call | ||||
| 		} | ||||
| 		return nil // recursive async call is not an issue | ||||
| 	case *ast.ForStmt: | ||||
| 		if n.Cond != nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 		// unconditional loop | ||||
| 		return w | ||||
| 	case *ast.FuncDecl: | ||||
| 		var rec *ast.Ident | ||||
| 		switch { | ||||
| 		case n.Recv == nil || n.Recv.NumFields() < 1 || len(n.Recv.List[0].Names) < 1: | ||||
| 			rec = nil | ||||
| 		default: | ||||
| 			rec = n.Recv.List[0].Names[0] | ||||
| 		} | ||||
|  | ||||
| 		w.currentFunc = &funcStatus{&funcDesc{rec, n.Name}, false} | ||||
| 	case *ast.CallExpr: | ||||
| 		var funcId *ast.Ident | ||||
| 		var selector *ast.Ident | ||||
| 		switch c := n.Fun.(type) { | ||||
| 		case *ast.Ident: | ||||
| 			selector = nil | ||||
| 			funcId = c | ||||
| 		case *ast.SelectorExpr: | ||||
| 			var ok bool | ||||
| 			selector, ok = c.X.(*ast.Ident) | ||||
| 			if !ok { // a.b....Foo() | ||||
| 				return nil | ||||
| 			} | ||||
| 			funcId = c.Sel | ||||
| 		default: | ||||
| 			return w | ||||
| 		} | ||||
|  | ||||
| 		if w.currentFunc != nil && // not in a func body | ||||
| 			!w.currentFunc.seenConditionalExit && // there is a conditional exit in the function | ||||
| 			w.currentFunc.funcDesc.equal(&funcDesc{selector, funcId}) { | ||||
| 			w.onFailure(lint.Failure{ | ||||
| 				Category:   "logic", | ||||
| 				Confidence: 1, | ||||
| 				Node:       n, | ||||
| 				Failure:    "unconditional recursive call", | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return w | ||||
| } | ||||
|  | ||||
| func (w *lintUnconditionalRecursionRule) updateFuncStatus(node ast.Node) { | ||||
| 	if node == nil || w.currentFunc == nil || w.currentFunc.seenConditionalExit { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	w.currentFunc.seenConditionalExit = w.hasControlExit(node) | ||||
| } | ||||
|  | ||||
| func (w *lintUnconditionalRecursionRule) hasControlExit(node ast.Node) bool { | ||||
| 	filter := func(n ast.Node) bool { | ||||
| 		_, ok := n.(*ast.ReturnStmt) | ||||
| 		return ok | ||||
| 	} | ||||
|  | ||||
| 	return len(pick(node, filter, nil)) != 0 | ||||
| } | ||||
							
								
								
									
										11
									
								
								test/unconditional-recursion_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								test/unconditional-recursion_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| package test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/mgechev/revive/rule" | ||||
| ) | ||||
|  | ||||
| func TestUnconditionalRecursion(t *testing.T) { | ||||
| 	testRule(t, "unconditional-recursion", &rule.UnconditionalRecursionRule{}) | ||||
| } | ||||
							
								
								
									
										141
									
								
								testdata/unconditional-recursion.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								testdata/unconditional-recursion.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | ||||
| package fixtures | ||||
|  | ||||
| import "time" | ||||
|  | ||||
| func ur1() { | ||||
| 	ur1() // MATCH /unconditional recursive call/ | ||||
| } | ||||
|  | ||||
| func ur1bis() { | ||||
| 	if true { | ||||
| 		print() | ||||
| 	} else { | ||||
| 		switch { | ||||
| 		case true: | ||||
| 			println() | ||||
| 		default: | ||||
| 			for i := 0; i < 10; i++ { | ||||
| 				print() | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	ur1bis() // MATCH /unconditional recursive call/ | ||||
| } | ||||
|  | ||||
| func ur2tris() { | ||||
| 	for { | ||||
| 		println() | ||||
| 		ur2tris() // MATCH /unconditional recursive call/ | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ur2() { | ||||
| 	if true { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	ur2() | ||||
| } | ||||
|  | ||||
| func ur3() { | ||||
| 	ur1() | ||||
| } | ||||
|  | ||||
| func urn4() { | ||||
| 	if true { | ||||
| 		print() | ||||
| 	} else if false { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	ur4() | ||||
| } | ||||
|  | ||||
| func urn5() { | ||||
| 	if true { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if true { | ||||
| 		println() | ||||
| 	} | ||||
|  | ||||
| 	ur5() | ||||
| } | ||||
|  | ||||
| func ur2tris() { | ||||
| 	for true == false { | ||||
| 		println() | ||||
| 		ur2tris() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type myType struct { | ||||
| 	foo int | ||||
| 	bar int | ||||
| } | ||||
|  | ||||
| func (mt *myType) Foo() int { | ||||
| 	return mt.Foo() // MATCH /unconditional recursive call/ | ||||
| } | ||||
|  | ||||
| func (mt *myType) Bar() int { | ||||
| 	return mt.bar | ||||
| } | ||||
|  | ||||
| func ur6() { | ||||
| 	switch { | ||||
| 	case true: | ||||
| 		return | ||||
| 	default: | ||||
| 		println() | ||||
| 	} | ||||
|  | ||||
| 	ur6() | ||||
| } | ||||
|  | ||||
| func ur7(a interface{}) { | ||||
| 	switch a.(type) { | ||||
| 	case int: | ||||
| 		return | ||||
| 	default: | ||||
| 		println() | ||||
| 	} | ||||
|  | ||||
| 	ur7() | ||||
| } | ||||
|  | ||||
| func ur8(a []int) { | ||||
| 	for _, i := range a { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	ur8() | ||||
| } | ||||
|  | ||||
| func ur9(a []int) { | ||||
| 	for _, i := range a { | ||||
| 		ur9() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ur10() { | ||||
| 	select { | ||||
| 	case <-aChannel: | ||||
| 	case <-time.After(2 * time.Second): | ||||
| 		return | ||||
| 	} | ||||
| 	ur10() | ||||
| } | ||||
|  | ||||
| func ur11() { | ||||
| 	go ur11() | ||||
| } | ||||
|  | ||||
| func ur12() { | ||||
| 	go foo(ur12())                   // MATCH /unconditional recursive call/ | ||||
| 	go bar(1, "string", ur12(), 1.0) // MATCH /unconditional recursive call/ | ||||
| 	go foo(bar()) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user