Skip to content

Commit 11e4a6e

Browse files
bumahkib7claude
andcommitted
chore: Release v0.14.0
## Added - Typestate Analysis Framework (file, lock, crypto, database, iterator) - Interactive TUI for browsing findings - Smart progress display with ETA - Powerful filtering (--severity, --rules, --category, --search) - Output limiting (--limit, --group-by) ## Fixed - Database typestate false positives on API client code - Array.prototype.find() false positives - Compiler warnings eliminated Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 336fe6e commit 11e4a6e

File tree

14 files changed

+137
-84
lines changed

14 files changed

+137
-84
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.14.0] - 2026-02-02
11+
1012
### Added
1113
- **Typestate Analysis Framework**: Track object state transitions through their lifecycle
1214
- `generic/file-typestate`: Detect use-after-close, unclosed files, double-open
@@ -18,6 +20,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1820
- Safe pattern recognition: `with`, `defer`, try-with-resources, RAII
1921
- FlowContext integration with `compute_typestate()` and `typestate_violations()` methods
2022
- `builtin_typestate_rules()` convenience function for all typestate rules
23+
- **Interactive TUI**: Browse findings with keyboard navigation (`j/k`, `Enter` for details, `s` filter severity)
24+
- **Smart Progress Display**: Real-time progress bar with ETA, file counts, and severity breakdown
25+
- **Powerful Filtering**: `--severity`, `--rules`, `--exclude-rules`, `--files`, `--category`, `--search`
26+
- **Output Limiting**: `--limit N` and `--group-by` (file/rule/severity) for large codebases
27+
28+
### Fixed
29+
- **Database Typestate False Positives**: Rule now requires database imports in file before flagging
30+
- **API Client Detection**: `cartApi.update()`, `userService.create()` no longer flagged as DB queries
31+
- **Array.find() False Positives**: Removed generic `.find(` from DB patterns, use specific ORM patterns
32+
- Compiler warnings eliminated across all crates
2133

2234
## [0.13.0] - 2026-02-02
2335

Cargo.lock

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ members = [
1313
]
1414

1515
[workspace.package]
16-
version = "0.13.0"
16+
version = "0.14.0"
1717
edition = "2024"
1818
authors = ["Rust Monorepo Analyzer Team"]
1919
license = "MIT OR Apache-2.0"

crates/ai/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ edition.workspace = true
66
license.workspace = true
77

88
[dependencies]
9-
rma-common = { version = "0.13.0", path = "../common" }
10-
rma-parser = { version = "0.13.0", path = "../parser" }
9+
rma-common = { version = "0.14.0", path = "../common" }
10+
rma-parser = { version = "0.14.0", path = "../parser" }
1111
anyhow.workspace = true
1212
thiserror.workspace = true
1313
tracing.workspace = true

crates/analyzer/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ oxc = [
1818
]
1919

2020
[dependencies]
21-
rma-common = { version = "0.13.0", path = "../common" }
22-
rma-parser = { version = "0.13.0", path = "../parser" }
21+
rma-common = { version = "0.14.0", path = "../common" }
22+
rma-parser = { version = "0.14.0", path = "../parser" }
2323
anyhow.workspace = true
2424
thiserror.workspace = true
2525
tracing.workspace = true

crates/analyzer/src/security/typestate_rules.rs

Lines changed: 90 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3315,51 +3315,113 @@ impl DatabaseTypestateRule {
33153315
let db_indicators = match language {
33163316
Language::JavaScript | Language::TypeScript => &[
33173317
// Database drivers
3318-
"mysql", "mysql2", "pg", "postgres", "mongodb", "mongoose",
3319-
"sequelize", "prisma", "typeorm", "knex", "drizzle",
3320-
"better-sqlite3", "sql.js", "sqlite3",
3318+
"mysql",
3319+
"mysql2",
3320+
"pg",
3321+
"postgres",
3322+
"mongodb",
3323+
"mongoose",
3324+
"sequelize",
3325+
"prisma",
3326+
"typeorm",
3327+
"knex",
3328+
"drizzle",
3329+
"better-sqlite3",
3330+
"sql.js",
3331+
"sqlite3",
33213332
// Database-specific imports
3322-
"PrismaClient", "MongoClient", "createConnection", "createPool",
3333+
"PrismaClient",
3334+
"MongoClient",
3335+
"createConnection",
3336+
"createPool",
33233337
// ORM indicators
3324-
"@prisma/client", "@nestjs/typeorm", "mikro-orm",
3338+
"@prisma/client",
3339+
"@nestjs/typeorm",
3340+
"mikro-orm",
33253341
][..],
33263342
Language::Python => &[
3327-
"psycopg2", "pymysql", "mysql.connector", "sqlite3", "sqlalchemy",
3328-
"asyncpg", "databases", "tortoise", "peewee", "mongoengine",
3329-
"pymongo", "motor", "django.db", "flask_sqlalchemy",
3343+
"psycopg2",
3344+
"pymysql",
3345+
"mysql.connector",
3346+
"sqlite3",
3347+
"sqlalchemy",
3348+
"asyncpg",
3349+
"databases",
3350+
"tortoise",
3351+
"peewee",
3352+
"mongoengine",
3353+
"pymongo",
3354+
"motor",
3355+
"django.db",
3356+
"flask_sqlalchemy",
33303357
][..],
33313358
Language::Go => &[
3332-
"database/sql", "gorm", "sqlx", "pgx", "mongo-driver",
3333-
"go-redis", "ent", "sql.Open", "gorm.Open",
3359+
"database/sql",
3360+
"gorm",
3361+
"sqlx",
3362+
"pgx",
3363+
"mongo-driver",
3364+
"go-redis",
3365+
"ent",
3366+
"sql.Open",
3367+
"gorm.Open",
33343368
][..],
33353369
Language::Java => &[
3336-
"java.sql", "javax.sql", "jdbc", "hibernate", "jpa",
3337-
"spring.data", "mybatis", "mongodb", "EntityManager",
3370+
"java.sql",
3371+
"javax.sql",
3372+
"jdbc",
3373+
"hibernate",
3374+
"jpa",
3375+
"spring.data",
3376+
"mybatis",
3377+
"mongodb",
3378+
"EntityManager",
33383379
][..],
33393380
Language::Rust => &[
3340-
"sqlx", "diesel", "sea-orm", "mongodb", "tokio-postgres",
3341-
"rusqlite", "postgres", "mysql_async",
3381+
"sqlx",
3382+
"diesel",
3383+
"sea-orm",
3384+
"mongodb",
3385+
"tokio-postgres",
3386+
"rusqlite",
3387+
"postgres",
3388+
"mysql_async",
33423389
][..],
33433390
_ => &[][..],
33443391
};
33453392

3346-
db_indicators.iter().any(|indicator| content.contains(indicator))
3393+
db_indicators
3394+
.iter()
3395+
.any(|indicator| content.contains(indicator))
33473396
}
33483397

33493398
/// Check if line looks like an API client call (not a database call)
33503399
fn is_api_client_call(line: &str) -> bool {
33513400
// Patterns that indicate HTTP API clients, not database operations
33523401
let api_patterns = [
33533402
// 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.",
3403+
"api.",
3404+
"Api.",
3405+
"API.",
3406+
"service.",
3407+
"Service.",
3408+
"client.",
3409+
"Client.", // Only when followed by HTTP-like methods
3410+
"http.",
3411+
"Http.",
3412+
"HTTP.",
3413+
"axios.",
3414+
"fetch(",
3415+
"request.",
33593416
// React Query / TanStack patterns
3360-
"useMutation", "useQuery",
3417+
"useMutation",
3418+
"useQuery",
33613419
// Common API method patterns
3362-
".get(", ".post(", ".put(", ".patch(", ".delete(",
3420+
".get(",
3421+
".post(",
3422+
".put(",
3423+
".patch(",
3424+
".delete(",
33633425
];
33643426

33653427
// Check for API client naming convention: variableApi.method() or variableService.method()
@@ -3378,12 +3440,12 @@ impl DatabaseTypestateRule {
33783440
}
33793441

33803442
// 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-
) {
3443+
if trimmed.contains("await")
3444+
&& (trimmed.contains("Api.")
3445+
|| trimmed.contains("Service.")
3446+
|| trimmed.contains("api.")
3447+
|| trimmed.contains("service."))
3448+
{
33873449
return true;
33883450
}
33893451

crates/cli/Cargo.toml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ default = ["oxc"]
2020
oxc = ["rma-analyzer/oxc"]
2121

2222
[dependencies]
23-
rma-common = { version = "0.13.0", path = "../common" }
24-
rma-parser = { version = "0.13.0", path = "../parser" }
25-
rma-analyzer = { version = "0.13.0", path = "../analyzer", default-features = false }
26-
rma-indexer = { version = "0.13.0", path = "../indexer" }
27-
rma-ai = { version = "0.13.0", path = "../ai" }
28-
rma-daemon = { version = "0.13.0", path = "../daemon" }
29-
rma-plugins = { version = "0.13.0", path = "../plugins" }
23+
rma-common = { version = "0.14.0", path = "../common" }
24+
rma-parser = { version = "0.14.0", path = "../parser" }
25+
rma-analyzer = { version = "0.14.0", path = "../analyzer", default-features = false }
26+
rma-indexer = { version = "0.14.0", path = "../indexer" }
27+
rma-ai = { version = "0.14.0", path = "../ai" }
28+
rma-daemon = { version = "0.14.0", path = "../daemon" }
29+
rma-plugins = { version = "0.14.0", path = "../plugins" }
3030
anyhow.workspace = true
3131
clap = { workspace = true, features = ["derive", "env", "string"] }
3232
clap_complete = "4.5"

crates/cli/src/filter.rs

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ pub struct FilterProfile {
138138
}
139139

140140
/// Comprehensive finding filter
141-
#[derive(Debug, Clone)]
141+
#[derive(Debug, Clone, Default)]
142142
pub struct FindingFilter {
143143
/// Minimum severity threshold
144144
pub min_severity: Option<Severity>,
@@ -171,27 +171,6 @@ pub struct FindingFilter {
171171
stats: FilterStats,
172172
}
173173

174-
impl Default for FindingFilter {
175-
fn default() -> Self {
176-
Self {
177-
min_severity: None,
178-
rules: HashSet::new(),
179-
rules_patterns: Vec::new(),
180-
exclude_rules: HashSet::new(),
181-
exclude_rules_patterns: Vec::new(),
182-
file_patterns: Vec::new(),
183-
exclude_patterns: Vec::new(),
184-
category: None,
185-
fixable_only: false,
186-
high_confidence_only: false,
187-
search_text: None,
188-
search_regex: None,
189-
track_stats: false,
190-
stats: FilterStats::default(),
191-
}
192-
}
193-
}
194-
195174
impl FindingFilter {
196175
/// Create a new empty filter (matches everything)
197176
pub fn new() -> Self {

crates/cli/src/progress.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ impl MultiPhaseProgress {
496496
if let Some(phase) = self.phases.get_mut(index) {
497497
if let Some(ref bar) = phase.bar {
498498
bar.enable_steady_tick(Duration::from_millis(80));
499-
bar.set_message(format!("{}", phase.name));
499+
bar.set_message(phase.name.clone());
500500
}
501501
self.current_phase = index;
502502
}

crates/daemon/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ edition.workspace = true
66
license.workspace = true
77

88
[dependencies]
9-
rma-common = { version = "0.13.0", path = "../common" }
10-
rma-parser = { version = "0.13.0", path = "../parser" }
11-
rma-analyzer = { version = "0.13.0", path = "../analyzer" }
12-
rma-indexer = { version = "0.13.0", path = "../indexer" }
9+
rma-common = { version = "0.14.0", path = "../common" }
10+
rma-parser = { version = "0.14.0", path = "../parser" }
11+
rma-analyzer = { version = "0.14.0", path = "../analyzer" }
12+
rma-indexer = { version = "0.14.0", path = "../indexer" }
1313
anyhow.workspace = true
1414
thiserror.workspace = true
1515
tracing.workspace = true

0 commit comments

Comments
 (0)