Repository navigation
Release v15.2.3
Major scan CLI expansion: lane control (--lane full|light, --lane-stats), CI gating by rule impact or tier (--fail-on-impact, --fail-on-tier), stale-ignore detection (--find-stale-ignores), SDK compatibility audit (--check-sdk-compat), and include/exclude glob filters for fine-grained file targeting. Eight false-positive fixes across core rules including avoid_context_in_async_static, avoid_large_list_copy, avoid_datetime_constructor, no_equal_nested_conditions, and the context-across-async family. An OOM crash fix for projects over 4 000 files adds per-file memory budgeting and adaptive RSS caps. Two new rules: prefer_primary_constructor (Dart 3.13+ syntax) and require_sdk_syntax_match (catches AI-generated code using syntax the project's SDK constraint doesn't support).
The
analyzer ^13.1.0migration (Dart 3.13+ / Flutter 3.47.1+, released 2026-08-19) is complete and tested but held offmain— adoption of 3.47.1 is near zero. It is parked on theanalyzer-13-migrationbranch and will ship as a<n+1>.0.0 major bump once adoption is widespread.
Fixed
avoid_context_in_async_staticno longer false-positives whenBuildContextis passed solely as an argument to the awaited call and never read after theawaitresumes (e.g.await showDialog(context: context)). The rule now walks all context usages in the method body and suppresses the diagnostic when every usage is consumed synchronously inside the awaited expression. No action required.avoid_large_list_copyno longer false-positives when.toList()feeds a??expression, aList<T>-typed argument, an explicitList<T>variable, aList<T>return type, a cascade, a property access, or a collection literal — all cases where removing.toList()would cause a compile error. No action required.avoid_datetime_constructorandavoid_datetime_constructor_unvalidatedno longer flagDateTime()/DateTime.utc()calls when all three date components (year, month, day) are property accesses on aDateTime-typed expression, since the source object already guarantees valid components. Day arithmetic (dt.day ± N) is also suppressed because Dart documents rollover behavior. No action required.no_equal_nested_conditionsno longer false-positives when the condition variable is reassigned between the outer and inner checks (e.g.if (x == null) { x = compute(); if (x == null) ... }). Simple, null-aware (??=), and compound (+=) assignments are all recognized. No action required.avoid_future_in_build(v3) removed name-prefix heuristic that only caught methods starting withfetch/load/get/etc. Now flags ANY method invocation inFutureBuilder(future:)insidebuild(). Also detects non-deterministicFutureconstructors while exemptingFuture.value()andFuture.error(). Scoped toFutureBuilderonly (no longer flags custom widgets with afuture:parameter). Widget class detection now covers third-party bases (HookWidget,ConsumerWidget, etc.). No action required.pass_existing_future_to_future_builder(v9) no longer flagsFuture.value()andFuture.error()constructors. Cache-method exemption now also recognizes@cachedFutureannotation frompackage:saropa_lints/annotations.dart. No action required.require_error_widgetno longer false-positives when error handling is delegated to an extension method on the snapshot parameter (e.g.snapshot.snapLoadingProgress()). Any method invocation on the snapshot is now recognized as delegated error handling. No action required.- OOM crash on large projects (4000+ files): The in-process analyzer plugin could exhaust memory on projects with thousands of files because forward-accumulating trackers were never evicted under pressure, the hard RSS safety valve defaulted too high, and violation tracking continued after the valve tripped. The plugin now sheds tracker data under memory pressure, stops accumulating records while memory-critical, adapts the default RSS cap to 60% of system RAM (capped at 8 GB on high-RAM machines), warns when the project exceeds 2000 files, and includes tracker sizes in the memory estimate. No action required — set
SAROPA_LINTS_MAX_RSS_MBto override the adaptive cap. - Scan CLI: Rules with
usesTypeResolution, INFO severity, or cost abovelowwere silently blocked by the analysis-server lane gate, which defaulted tolightin the CLI path. The scanner now runs at full lane coverage so all enabled rules fire correctly. No action required. avoid_context_across_asyncandavoid_retaining_disposed_widgetsnow check the resolved type (when type information is available, e.g. in-editor or--resolvescans) instead of matching on the bare identifier/type name alone. Fixes false positives on non-Flutter classes that happen to be namedcontextorElement(an analyzerElement, a customContexttype, etc.). No action required.- Scan CLI now excludes platform ephemeral directories (
ephemeral/,.plugin_symlinks/) by default. Previously these symlinked plugin sources appeared in scan results even though the user doesn't control them. No action required — the exclusion is automatic. (#313)
Added
DateUtils.dateOnly()quick fix foravoid_datetime_constructorandavoid_datetime_constructor_unvalidated— recognizes the strip-time idiomDateTime(x.year, x.month, x.day)and the explicit-midnight-zeros variantDateTime(x.year, x.month, x.day, 0, 0, 0), replacing both withDateUtils.dateOnly(x). Appears above the existingDateTime.tryParse()fix when both apply. Not offered for.utc()constructors, nullable receivers, non-DateTime types, or pure Dart projects without Flutter. No action required.- Per-file memory budget: On large projects approaching the RSS cap, the plugin now skips cold (unmodified >24h) files and prioritizes recently edited files for lint analysis — partial coverage instead of all-or-nothing OOM. The analysis summary reports how many files were skipped. No action required.
@cachedFutureannotation (package:saropa_lints/annotations.dart) — marks a method as returning a cached Future, suppressingpass_existing_future_to_future_builderwithout needing the heuristic (private method +Future?field). Use when your naming convention doesn't match the heuristic.- New rule:
prefer_primary_constructor(Professional, INFO) — flags classes eligible for Dart 3.13+ primary constructor syntax when the project's SDK lower bound is >=3.13.0. Reduces boilerplate for simple data classes that AI generators consistently produce in the verbose pre-3.13 form. Detection only for now — the quick fix ships with the analyzer 13 migration on theanalyzer-13-migrationbranch. No action required. - New rule:
require_sdk_syntax_match(Comprehensive, WARNING) — flags Dart syntax features that require a newer SDK than the lower bound declared in pubspec.yaml, with a quick fix to raise the SDK lower bound. Catches AI-generated code that uses records, switch expressions, extension types, or digit separators when the project's SDK constraint doesn't support them. No action required. - Scan CLI:
--lane full|lightflag controls which rule lane the scanner uses. Defaults tofull(every enabled rule);lightrestricts to the same cheap, resolution-free subset the analysis server runs in its default lane. No action required — existing scans are unaffected. - Scan CLI:
--lane-statsprints how many of the loaded rules are light-lane vs full-only; when in light lane, lists every blocked rule name so the gate's effect is fully observable. - Scan CLI:
--check-sdk-compatstandalone audit cross-references the pubspec SDK lower bound against Dart syntax features inlib/. Prints a grouped summary showing which files force each version bump. Exits 1 on mismatch, 0 when compatible — suitable for CI gating. - Scan CLI:
--exclude-globs <pattern>...flag excludes files matching glob patterns from the scan. Supports**(any path segments),*(any non-separator chars), and?(single char). Use it to skip vendored code, generated directories, or any paths the hardcoded exclusions don't cover. (#313) - Scan CLI:
--include-globs <pattern>...flag overrides the hardcoded exclusions for matching paths — when a path matches both a default exclusion and an include-glob, the include wins. Use it to force-scan third-party plugin code in ephemeral or generated directories. (#313) - Scan CLI:
--fail-on-impact <level>flag exits 1 when any saropa rule's declared impact meets the threshold (info/warning/error). Unlike--fail-on(which uses analyzer severity), this checks the rule author's business-consequence rating — use it to gate CI on high-impact rules regardless of their configurable severity. Pair with--fail-on-impact-count <n>to tolerate a known baseline during migration. (#312) - Scan CLI:
--fail-on-tier <name>flag exits 1 only when a diagnostic comes from a rule in the specified tier or below. Scan at a high tier for visibility but only fail on essential-tier findings during incremental adoption — e.g.--tier comprehensive --fail-on-tier essential. (#312) - Scan CLI:
--find-stale-ignoresflag detects// ignore:comments whose suppressed saropa_lints rule no longer fires on the target line — the code was fixed but the ignore was left behind. Reports each stale ignore with file path, line number, and rule name. Supports--format jsonfor CI integration. Exits 1 if any stale ignores found, 0 if clean. No action required. - Scan CLI:
--fix-stale-ignoresflag detects AND automatically removes stale// ignore:directives from source files. Standalone comments are deleted entirely; inline comments are stripped preserving the code; multi-rule comments have only the stale rules pruned. Prints a summary of files modified. No action required. - VS Code extension: Stale Ignore commands — two new command palette entries ("Find Stale Ignore Comments" and "Fix Stale Ignore Comments") plus sidebar action rows in the Settings panel. Find runs the scan and publishes stale ignores as warnings in the Problems panel with squiggly lines on the offending comment lines. Fix confirms before auto-removing dead
// ignore:comments from source files. A lightbulb quick fix on each stale-ignore diagnostic offers a file-scoped "Fix stale ignores in this file" action with no confirmation prompt, for cleaning up one file at a time without leaving the editor. No action required.
Changed
avoid_wildcard_cases_with_enums(v6) now suppresses the diagnostic when the switched enum has more than 20 members, where exhaustive case listing is impractical and adefault:catch-all is the correct design choice. Also upgraded from string heuristic to properEnumElementresolution when type information is available. No action required.avoid_stream_in_build(v3) now also detectsStreamBuilder(stream: method())where a method invocation creates a new subscription on every rebuild. Previously only caughtStreamController()instantiation insidebuild(). Excludes safe constructors (Stream.value(),Stream.empty()) and the??=caching idiom. A new quick fix converts a simpleStatelessWidgetflagged this way into aStatefulWidgetwith the stream cached ininitState(). No action required.- known_issues.json — reviewed 51 flagged entries against live pub.dev data. Version-scoped 2 entries (flutter_calendar_carousel, keyboard_actions) whose issues were fixed in newer releases. Updated workmanager and flutter_email_sender from stale caution to active. Fixed missing reason on flutter_vibrate. Updated better_player from stale maintenance_mode to active.
- known_issues.json — source-verified the remaining 10 UNCLEAR entries from that review. Removed 5 entries with no corroborating evidence in changelogs or issue trackers (agora_rtc_engine end-of-life claim, badges Material 3 bug, flutter_cache_manager disk-space bug, fluttertoast overlay/context leak, google_fonts thread-blocking claim). Version-scoped or corrected 6 entries against confirmed fix versions (animations, audioplayers, flutter_downloader, flutter_modular, graphql, shimmer). No action required.
Maintenance
- Extracted shared
isWidgetOrStateClass()andisInsideBuildMethod()utilities intotarget_matcher_utils.dart— used byavoid_stream_in_buildandavoid_future_in_build; replaces per-rule private duplicates. - Publish script: fixed Dart frontend_server crash —
dart test -j <all-cores>(24 on a 24-core machine) caused native access violations (STATUS_ACCESS_VIOLATION) andfront_endcompiler exceptions during test compilation. The test step now auto-tunes concurrency by probing a single test at increasing-jlevels (4, 6, 8, 10, 12), caching the result inbuild/.dart_test_max_j; crash retries halve concurrency automatically, the failure prompt offers[F]ewer workersto halve manually, and setSAROPA_TEST_MAX_J=Nto skip the probe entirely. - Publish script: fixed test temp dir location — kernel-cache
.dillfiles were written inside the project tree (build/test_tmp/), causinguri_does_not_existscan errors and filling the C: drive. Temp dir now defaults to<system_temp>/saropa_dart_testoutside the project tree; setSAROPA_TEST_TMPto override (validated: falls back if inside project tree). - Removed
plans/known_issues_review.mdfrom git tracking (generated file, regenerated each publish run). - New
dart run saropa_lints:memory_reportcommand — summarizes the analysis server's RSS trend fromplugin.logfor post-crash diagnosis. The in-process plugin now writes a memory sample line roughly every 30 seconds; the command reports min/max/latest RSS, percent of the configured cap, and a CAVEAT whenplugin.logwas rotated mid-session (so the summary is known to be incomplete rather than silently wrong). A one-time log line now also flags when RSS sampling itself is unavailable on the current platform, so an empty trend log is diagnosable instead of looking identical to "plugin never ran". - Reorganized the memory-monitor proposal into
plans/PLAN_analyzer_memory_monitor.md(phased checklist: soft RSS threshold, selective rule shedding, VS Code status-bar integration), matching the repo'sPLAN_*convention. Addedscripts/check_plan_naming.py— an informational, non-blocking report ofplans/*.mdfiles that don't follow thePLAN_<name>.mdnaming convention. - Publish script:
--dry-runCLI flag — runs dependency resolution, audit, format, analysis, tests, anddart pub publish --dry-runwith no commit, tag, version bump, or publish. Needs no pub.dev credentials; intended for CI pre-merge validation. - Publish script: further crash-detection hardening — test-temp-dir contents are wiped before each run instead of accumulating across publish attempts, the temp dir is write-verified before use (falls back to system temp on failure), and the auto-tune concurrency cache key now includes an available-RAM bucket so a level probed safe on an idle machine doesn't get trusted indefinitely under memory pressure.
- known_issues review script — now skips entries with
appliesToMaxVersionorreplacementObsoleteFromVersionto avoid false-positive flagging of version-scoped entries that are correct by design. require_sdk_syntax_matchquick fix: removed deadMap<Type, String>lookup (analyzer concrete types are private*Implclasses that never matched abstract keys); hardened regex with triple-quoted raw string to handle embedded quotes.bugs/BUG_REPORT_GUIDE.mdrenamed tobugs/ISSUE_REPORT_GUIDE.mdand extended with a feature request template, proposal naming patterns, and lifecycle, alongside the existing bug report process._rule_metrics.py's bug counter now reports open feature proposals separately from unsolved bugs in the publish "WORK REPORT" banner, instead of lumping both into one count.- Scan daemon and accuracy report now pass
lane: RuleLane.fullexplicitly instead of relying on the constructor default. - Scan CLI warns when
--lane lightis combined with--exclude-light-lane(degenerate: zero rules to scan). - Fixed the 17 real ERROR-severity findings the full-lane self-scan surfaced against this package's own source:
double.parse(x.toStringAsFixed(n))round-trip patterns in the project-health/vibrancy models now round arithmetically via a sharedroundToDecimalPlaceshelper instead of parsing a self-produced string; a regex-guaranteed-digitsint.parsetriple in the pubspec constraint parser is annotated as a verified false positive. - Changelog version-drift guard — new
scripts/hooks/changelog_guard.py(dual-mode: Claude PostToolUse + git pre-commit) blocks commits that introduce multiple unreleased sections in CHANGELOG.md or bump version numbers in pubspec.yaml / package.json ahead of the publish script. Publish script also gainedassert_single_unreleased_section()as a belt-and-suspenders gate. - Publish script: live test progress —
dart testoutput now streams to the terminal with a real-time progress bar showing elapsed time, pass/skip/fail counts, and the current test name. Failure details print immediately instead of silently accumulating in a log file. Full output is still written toreports/for post-mortem analysis. - Publish script: delta-first testing — the test step now automatically detects changed files via
git diff, maps them to corresponding test files, and runs only those first (seconds instead of minutes). If the delta pass fails, it stops immediately without compiling the full 340+ file suite. Infrastructure changes (tiers, registration, pubspec) bypass delta and run the full suite. - Full Audit plan — design for a "run every rule" audit feature: CLI subcommand (
dart run saropa_lints audit), visible sidebar button + Explorer context menu, filterable webview report with tier/severity/impact/category facets, diff mode (--since <ref>) as a UI quick-pick, and baseline diffing (--save-baseline/--baseline) with datetime-stamped outputs. Plan atplans/PLAN_full_audit.md. - i18n translation pipeline: deferred Qwen/Ollama provisioning —
extension/scripts/i18n/generate_locales.pyandmt_fallback.pypreviously resolved the primary MT engine (self-provisioning Ollama: starting the daemon, pulling a multi-GB model on first use) unconditionally before checking whether any locale actually had untranslated strings. A fully-cached run now never touches Qwen/Ollama at all — engine resolution is deferred until a string is confirmed missing from every cached engine's keyspace. - Publish script: no more forced retry on real test failures — a failing test pass used to always re-run once automatically before asking the user anything, silently doubling the wait on a run that was never going to pass. The automatic retry now only fires when the failure is diagnosed as transient (VM crash or file lock) AND the pass is cheap (delta); the expensive full-suite ("fast") pass always goes straight to the Continue/Retry/Abort prompt on any failure, transient or not, so a multi-minute compile is never silently repeated without the user deciding.
scan_cli_args_test.dart: five untagged process groups now markedtags: ['slow']— the(process)groups shell out to realdart run saropa_lints:scansubprocesses (including full essential-tier scans of the project itself) and were timing out at 2 minutes each under full-suite-jcontention, failing the fast publish pass. Only the process-spawning groups are tagged; the ~250 in-memoryparseScanArgsunit tests in the same file remain in the fast pass.- Publish script: fixed
dart fixaudit crash on Windows — the twodart fixsubprocess calls in the pre-publish audit were the onlydartinvocations in the publish modules missing shell mode, so they crashed withWinError 2on Windows wheredartresolves to a.batwrapper thatCreateProcesscannot launch directly. Both calls now pass shell mode like every other subprocess in the pipeline. scan_cli_args_test.dart: slow process groups given an explicit 5-minute timeout — the publish delta pass runs changed test files with tag filters deliberately ignored, so theslowtag alone could not protect these tests there; each cold-starts an uncompiled scan CLI that can exceed the 2-minute default. Known limitation: the--fail-ongroup can still exceed even 5 minutes under contention — the durable fix (a precompiled scan snapshot) is tracked separately.