diff --git a/pgx_examples_results.txt b/pgx_examples_results.txt index fac1c41..01dcd39 100644 --- a/pgx_examples_results.txt +++ b/pgx_examples_results.txt @@ -1,4 +1,4 @@ # github.com/ryanrolds/sqlclosecheck/testdata/pgx_examples -testdata/pgx_examples/missing_close.go:8:26: Rows/Stmt was not closed -testdata/pgx_examples/missing_close.go:17:28: Rows/Stmt was not closed -testdata/pgx_examples/missing_close.go:26:28: Rows/Stmt was not closed +testdata/pgx_examples/missing_close.go:8:26: Rows/Stmt/NamedStmt was not closed +testdata/pgx_examples/missing_close.go:17:28: Rows/Stmt/NamedStmt was not closed +testdata/pgx_examples/missing_close.go:26:28: Rows/Stmt/NamedStmt was not closed diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index f12a1fc..55e931a 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -9,9 +9,10 @@ import ( ) const ( - rowsName = "Rows" - stmtName = "Stmt" - closeMethod = "Close" + rowsName = "Rows" + stmtName = "Stmt" + namedStmtName = "NamedStmt" + closeMethod = "Close" ) type action uint8 @@ -39,7 +40,7 @@ var ( func NewAnalyzer() *analysis.Analyzer { return &analysis.Analyzer{ Name: "sqlclosecheck", - Doc: "Checks that sql.Rows, sql.Stmt, pgx.Query are closed.", + Doc: "Checks that sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query are closed.", Run: run, Requires: []*analysis.Analyzer{ buildssa.Analyzer, @@ -76,7 +77,7 @@ func run(pass *analysis.Pass) (interface{}, error) { refs := (*targetValue.value).Referrers() isClosed := checkClosed(refs, targetTypes) if !isClosed { - pass.Reportf((targetValue.instr).Pos(), "Rows/Stmt was not closed") + pass.Reportf((targetValue.instr).Pos(), "Rows/Stmt/NamedStmt was not closed") } checkDeferred(pass, refs, targetTypes, false) @@ -112,6 +113,11 @@ func getTargetTypes(pssa *buildssa.SSA, targetPackages []string) []any { if stmtType != nil { targets = append(targets, stmtType) } + + namedStmtType := getTypePointerFromName(pkg, namedStmtName) + if namedStmtType != nil { + targets = append(targets, namedStmtType) + } } return targets @@ -120,7 +126,7 @@ func getTargetTypes(pssa *buildssa.SSA, targetPackages []string) []any { func getTypePointerFromName(pkg *ssa.Package, name string) *types.Pointer { pkgType := pkg.Type(name) if pkgType == nil { - // this package does not use Rows/Stmt + // this package does not use Rows/Stmt/NamedStmt return nil } diff --git a/pkg/analyzer/testdata/pgx/missing_close.go b/pkg/analyzer/testdata/pgx/missing_close.go index 5a4019e..63b5836 100644 --- a/pkg/analyzer/testdata/pgx/missing_close.go +++ b/pkg/analyzer/testdata/pgx/missing_close.go @@ -5,7 +5,7 @@ import ( ) func missingCloseTx() { - rows, err := pgxTx.Query(ctx, "SELECT username FROM users") // want "Rows/Stmt was not closed" + rows, err := pgxTx.Query(ctx, "SELECT username FROM users") // want "Rows/Stmt/NamedStmt was not closed" if err != nil { log.Fatal(err) } @@ -14,7 +14,7 @@ func missingCloseTx() { } func missingCloseConn() { - rows, err := pgxConn.Query(ctx, "SELECT username FROM users") // want "Rows/Stmt was not closed" + rows, err := pgxConn.Query(ctx, "SELECT username FROM users") // want "Rows/Stmt/NamedStmt was not closed" if err != nil { log.Fatal(err) } @@ -23,7 +23,7 @@ func missingCloseConn() { } func missingClosePgxPool() { - rows, err := pgxPool.Query(ctx, "SELECT username FROM users") // want "Rows/Stmt was not closed" + rows, err := pgxPool.Query(ctx, "SELECT username FROM users") // want "Rows/Stmt/NamedStmt was not closed" if err != nil { log.Fatal(err) } diff --git a/pkg/analyzer/testdata/rows/missing_close.go b/pkg/analyzer/testdata/rows/missing_close.go index 063d383..8fac3a4 100644 --- a/pkg/analyzer/testdata/rows/missing_close.go +++ b/pkg/analyzer/testdata/rows/missing_close.go @@ -7,7 +7,7 @@ import ( func missingClose() { age := 27 - rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age) // want "Rows/Stmt was not closed" + rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age) // want "Rows/Stmt/NamedStmt was not closed" if err != nil { log.Fatal(err) } diff --git a/pkg/analyzer/testdata/stmt/missing_close.go b/pkg/analyzer/testdata/stmt/missing_close.go index 4ed4932..edd11fa 100644 --- a/pkg/analyzer/testdata/stmt/missing_close.go +++ b/pkg/analyzer/testdata/stmt/missing_close.go @@ -7,7 +7,7 @@ import ( func missingClose() { // In normal use, create one Stmt when your process starts. - stmt, err := db.PrepareContext(ctx, "SELECT username FROM users WHERE id = ?") // want "Rows/Stmt was not closed" + stmt, err := db.PrepareContext(ctx, "SELECT username FROM users WHERE id = ?") // want "Rows/Stmt/NamedStmt was not closed" if err != nil { log.Fatal(err) } diff --git a/testdata/pgx_examples/expected_results.txt b/testdata/pgx_examples/expected_results.txt index fac1c41..01dcd39 100644 --- a/testdata/pgx_examples/expected_results.txt +++ b/testdata/pgx_examples/expected_results.txt @@ -1,4 +1,4 @@ # github.com/ryanrolds/sqlclosecheck/testdata/pgx_examples -testdata/pgx_examples/missing_close.go:8:26: Rows/Stmt was not closed -testdata/pgx_examples/missing_close.go:17:28: Rows/Stmt was not closed -testdata/pgx_examples/missing_close.go:26:28: Rows/Stmt was not closed +testdata/pgx_examples/missing_close.go:8:26: Rows/Stmt/NamedStmt was not closed +testdata/pgx_examples/missing_close.go:17:28: Rows/Stmt/NamedStmt was not closed +testdata/pgx_examples/missing_close.go:26:28: Rows/Stmt/NamedStmt was not closed diff --git a/testdata/pgx_examples/missing_close.go b/testdata/pgx_examples/missing_close.go index d696e9d..d411908 100644 --- a/testdata/pgx_examples/missing_close.go +++ b/testdata/pgx_examples/missing_close.go @@ -5,7 +5,7 @@ import ( ) func missingCloseTx() { - rows, err := pgxTx.Query(ctx, "SELECT username FROM users") // want "Rows/Stmt was not closed" + rows, err := pgxTx.Query(ctx, "SELECT username FROM users") // want "Rows/Stmt/NamedStmt was not closed" if err != nil { log.Fatal(err) } @@ -14,7 +14,7 @@ func missingCloseTx() { } func missingCloseConn() { - rows, err := pgxConn.Query(ctx, "SELECT username FROM users") // want "Rows/Stmt was not closed" + rows, err := pgxConn.Query(ctx, "SELECT username FROM users") // want "Rows/Stmt/NamedStmt was not closed" if err != nil { log.Fatal(err) } @@ -23,7 +23,7 @@ func missingCloseConn() { } func missingClosePgxPool() { - rows, err := pgxPool.Query(ctx, "SELECT username FROM users") // want "Rows/Stmt was not closed" + rows, err := pgxPool.Query(ctx, "SELECT username FROM users") // want "Rows/Stmt/NamedStmt was not closed" if err != nil { log.Fatal(err) } diff --git a/testdata/sqlx_examples/expected_results.txt b/testdata/sqlx_examples/expected_results.txt index cae2c9e..67b4915 100644 --- a/testdata/sqlx_examples/expected_results.txt +++ b/testdata/sqlx_examples/expected_results.txt @@ -1,5 +1,6 @@ # github.com/ryanrolds/sqlclosecheck/testdata/sqlx_examples -testdata/sqlx_examples/failure_generics.go:6:21: Rows/Stmt was not closed -testdata/sqlx_examples/failure_generics.go:13:21: Rows/Stmt was not closed -testdata/sqlx_examples/missing_close.go:10:24: Rows/Stmt was not closed +testdata/sqlx_examples/failure_generics.go:6:21: Rows/Stmt/NamedStmt was not closed +testdata/sqlx_examples/failure_generics.go:13:21: Rows/Stmt/NamedStmt was not closed +testdata/sqlx_examples/missing_close.go:10:24: Rows/Stmt/NamedStmt was not closed +testdata/sqlx_examples/missing_close_named_stmt.go:8:30: Rows/Stmt/NamedStmt was not closed testdata/sqlx_examples/non_defer_close.go:30:12: Close should use defer diff --git a/testdata/sqlx_examples/missing_close_named_stmt.go b/testdata/sqlx_examples/missing_close_named_stmt.go new file mode 100644 index 0000000..3567a0b --- /dev/null +++ b/testdata/sqlx_examples/missing_close_named_stmt.go @@ -0,0 +1,16 @@ +package sqlx_examples + +import ( + "log" +) + +func missingCloseNamedStmt() { + stmt, err := db.PrepareNamed("SELECT * FROM users WHERE id = :id") + if err != nil { + log.Fatal(err) + } + + // defer stmt.Close() + + _ = stmt // No need to use stmt +}