From 5cd583e86cf4c0cd0a44b4620ac1641597b92d33 Mon Sep 17 00:00:00 2001 From: Codey Oxley Date: Thu, 23 Oct 2025 10:08:29 -0400 Subject: [PATCH] fix: detect bad *= assignments --- durationcheck.go | 51 ++++++++++++++++++++++++++++++++------------- go.mod | 2 +- testdata/src/a/a.go | 7 +++++++ 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/durationcheck.go b/durationcheck.go index c47b3a7..5fcb98a 100644 --- a/durationcheck.go +++ b/durationcheck.go @@ -32,6 +32,7 @@ func run(pass *analysis.Pass) (interface{}, error) { nodeTypes := []ast.Node{ (*ast.BinaryExpr)(nil), + (*ast.AssignStmt)(nil), } inspect.Preorder(nodeTypes, check(pass)) @@ -52,25 +53,45 @@ func hasImport(pkg *types.Package, importPath string) bool { // check contains the logic for checking that time.Duration is used correctly in the code being analysed func check(pass *analysis.Pass) func(ast.Node) { return func(node ast.Node) { - expr := node.(*ast.BinaryExpr) - // we are only interested in multiplication - if expr.Op != token.MUL { - return + switch expr := node.(type) { + case *ast.BinaryExpr: + checkBinaryExpr(pass, expr) + case *ast.AssignStmt: + if expr.Tok != token.MUL_ASSIGN { + return + } + // '*=' assignment requires single-valued expressions + if len(expr.Lhs) != 1 || len(expr.Rhs) != 1 { + return + } + checkBinaryExpr(pass, &ast.BinaryExpr{ + X: expr.Lhs[0], + OpPos: expr.TokPos, + Op: expr.Tok, + Y: expr.Rhs[0], + }) } + } +} - // get the types of the two operands - x, xOK := pass.TypesInfo.Types[expr.X] - y, yOK := pass.TypesInfo.Types[expr.Y] +func checkBinaryExpr(pass *analysis.Pass, expr *ast.BinaryExpr) { + // we are only interested in multiplication + if expr.Op != token.MUL && expr.Op != token.MUL_ASSIGN { + return + } - if !xOK || !yOK { - return - } + // get the types of the two operands + x, xOK := pass.TypesInfo.Types[expr.X] + y, yOK := pass.TypesInfo.Types[expr.Y] - if isDuration(x.Type) && isDuration(y.Type) { - // check that both sides are acceptable expressions - if isUnacceptableExpr(pass, expr.X) && isUnacceptableExpr(pass, expr.Y) { - pass.Reportf(expr.Pos(), "Multiplication of durations: `%s`", formatNode(expr)) - } + if !xOK || !yOK { + return + } + + if isDuration(x.Type) && isDuration(y.Type) { + // check that both sides are acceptable expressions + if isUnacceptableExpr(pass, expr.X) && isUnacceptableExpr(pass, expr.Y) { + pass.Reportf(expr.Pos(), "Multiplication of durations: `%s`", formatNode(expr)) } } } diff --git a/go.mod b/go.mod index 87a4e18..12dc5cd 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/charithe/durationcheck -go 1.14 +go 1.16 require golang.org/x/tools v0.6.0 diff --git a/testdata/src/a/a.go b/testdata/src/a/a.go index 2bc13d4..7f7a3dd 100644 --- a/testdata/src/a/a.go +++ b/testdata/src/a/a.go @@ -76,6 +76,9 @@ func validCases() { _ = time.Duration(intArr[0]) * time.Second _ = time.Duration(y) * 24 * time.Hour + + x := time.Second + x *= time.Duration(5) } func invalidCases() { @@ -114,6 +117,10 @@ func invalidCases() { _ = time.Second * b.SomeDuration // want `Multiplication of durations` _ = time.Duration(tdArr[0]) * time.Second // want `Multiplication of durations` + + x *= time.Second // want `Multiplication of durations` + + x *= time.Duration(5) * time.Second // want `Multiplication of durations` } func someDuration() time.Duration {