@@ -3310,6 +3310,86 @@ impl DatabaseTypestateRule {
33103310 sm. is_safe_pattern ( content)
33113311 }
33123312
3313+ /// Check if file has database-related imports/requires
3314+ fn has_database_context ( content : & str , language : Language ) -> bool {
3315+ let db_indicators = match language {
3316+ Language :: JavaScript | Language :: TypeScript => & [
3317+ // Database drivers
3318+ "mysql" , "mysql2" , "pg" , "postgres" , "mongodb" , "mongoose" ,
3319+ "sequelize" , "prisma" , "typeorm" , "knex" , "drizzle" ,
3320+ "better-sqlite3" , "sql.js" , "sqlite3" ,
3321+ // Database-specific imports
3322+ "PrismaClient" , "MongoClient" , "createConnection" , "createPool" ,
3323+ // ORM indicators
3324+ "@prisma/client" , "@nestjs/typeorm" , "mikro-orm" ,
3325+ ] [ ..] ,
3326+ Language :: Python => & [
3327+ "psycopg2" , "pymysql" , "mysql.connector" , "sqlite3" , "sqlalchemy" ,
3328+ "asyncpg" , "databases" , "tortoise" , "peewee" , "mongoengine" ,
3329+ "pymongo" , "motor" , "django.db" , "flask_sqlalchemy" ,
3330+ ] [ ..] ,
3331+ Language :: Go => & [
3332+ "database/sql" , "gorm" , "sqlx" , "pgx" , "mongo-driver" ,
3333+ "go-redis" , "ent" , "sql.Open" , "gorm.Open" ,
3334+ ] [ ..] ,
3335+ Language :: Java => & [
3336+ "java.sql" , "javax.sql" , "jdbc" , "hibernate" , "jpa" ,
3337+ "spring.data" , "mybatis" , "mongodb" , "EntityManager" ,
3338+ ] [ ..] ,
3339+ Language :: Rust => & [
3340+ "sqlx" , "diesel" , "sea-orm" , "mongodb" , "tokio-postgres" ,
3341+ "rusqlite" , "postgres" , "mysql_async" ,
3342+ ] [ ..] ,
3343+ _ => & [ ] [ ..] ,
3344+ } ;
3345+
3346+ db_indicators. iter ( ) . any ( |indicator| content. contains ( indicator) )
3347+ }
3348+
3349+ /// Check if line looks like an API client call (not a database call)
3350+ fn is_api_client_call ( line : & str ) -> bool {
3351+ // Patterns that indicate HTTP API clients, not database operations
3352+ let api_patterns = [
3353+ // Common API client naming patterns (case-insensitive check)
3354+ "api." , "Api." , "API." ,
3355+ "service." , "Service." ,
3356+ "client." , "Client." , // Only when followed by HTTP-like methods
3357+ "http." , "Http." , "HTTP." ,
3358+ "axios." , "fetch(" , "request." ,
3359+ // React Query / TanStack patterns
3360+ "useMutation" , "useQuery" ,
3361+ // Common API method patterns
3362+ ".get(" , ".post(" , ".put(" , ".patch(" , ".delete(" ,
3363+ ] ;
3364+
3365+ // Check for API client naming convention: variableApi.method() or variableService.method()
3366+ let trimmed = line. trim ( ) ;
3367+
3368+ // Skip lines that are clearly API calls
3369+ if api_patterns. iter ( ) . any ( |p| trimmed. contains ( p) ) {
3370+ return true ;
3371+ }
3372+
3373+ // Check for camelCase API client patterns: someApi.update(), cartApi.update()
3374+ // Match pattern: word ending in Api/Service/Client followed by .method(
3375+ let api_var_pattern = regex:: Regex :: new ( r"\b\w+(Api|Service|Client)\.(create|update|delete|get|post|put|patch|fetch|send|request)\(" ) . unwrap ( ) ;
3376+ if api_var_pattern. is_match ( trimmed) {
3377+ return true ;
3378+ }
3379+
3380+ // Check for await with API patterns
3381+ if trimmed. contains ( "await" ) && (
3382+ trimmed. contains ( "Api." ) ||
3383+ trimmed. contains ( "Service." ) ||
3384+ trimmed. contains ( "api." ) ||
3385+ trimmed. contains ( "service." )
3386+ ) {
3387+ return true ;
3388+ }
3389+
3390+ false
3391+ }
3392+
33133393 /// Detect potential connection leak
33143394 fn check_connection_leak (
33153395 conn : & TrackedDbConnection ,
@@ -3379,6 +3459,12 @@ impl Rule for DatabaseTypestateRule {
33793459 return Vec :: new ( ) ;
33803460 }
33813461
3462+ // Only run database checks if the file has database-related imports/code
3463+ // This prevents false positives on API client code
3464+ if !Self :: has_database_context ( & parsed. content , parsed. language ) {
3465+ return Vec :: new ( ) ;
3466+ }
3467+
33823468 let sm = Self :: state_machine ( parsed. language ) ;
33833469 let mut findings = Vec :: new ( ) ;
33843470 let mut connections: Vec < TrackedDbConnection > = Vec :: new ( ) ;
@@ -3389,6 +3475,11 @@ impl Rule for DatabaseTypestateRule {
33893475 for ( line_num, line) in parsed. content . lines ( ) . enumerate ( ) {
33903476 let line_num = line_num + 1 ;
33913477
3478+ // Skip lines that look like API client calls (not database operations)
3479+ if Self :: is_api_client_call ( line) {
3480+ continue ;
3481+ }
3482+
33923483 // Check for safe patterns on this line
33933484 let line_has_safe = sm. is_safe_pattern ( line) ;
33943485
0 commit comments