Skip to content

Release v15.2.3

Choose a tag to compare

@saropa saropa released this 29 Aug 18:06
· 492 commits to main since this release

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.0 migration (Dart 3.13+ / Flutter 3.47.1+, released 2026-08-19) is complete and tested but held off main — adoption of 3.47.1 is near zero. It is parked on the analyzer-13-migration branch and will ship as a <n+1>.0.0 major bump once adoption is widespread.

Fixed

  • avoid_context_in_async_static no longer false-positives when BuildContext is passed solely as an argument to the awaited call and never read after the await resumes (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_copy no longer false-positives when .toList() feeds a ?? expression, a List<T>-typed argument, an explicit List<T> variable, a List<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_constructor and avoid_datetime_constructor_unvalidated no longer flag DateTime() / DateTime.utc() calls when all three date components (year, month, day) are property accesses on a DateTime-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_conditions no 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 with fetch/load/get/etc. Now flags ANY method invocation in FutureBuilder(future:) inside build(). Also detects non-deterministic Future constructors while exempting Future.value() and Future.error(). Scoped to FutureBuilder only (no longer flags custom widgets with a future: parameter). Widget class detection now covers third-party bases (HookWidget, ConsumerWidget, etc.). No action required.
  • pass_existing_future_to_future_builder (v9) no longer flags Future.value() and Future.error() constructors. Cache-method exemption now also recognizes @cachedFuture annotation from package:saropa_lints/annotations.dart. No action required.
  • require_error_widget no 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_MB to override the adaptive cap.
  • Scan CLI: Rules with usesTypeResolution, INFO severity, or cost above low were silently blocked by the analysis-server lane gate, which defaulted to light in the CLI path. The scanner now runs at full lane coverage so all enabled rules fire correctly. No action required.
  • avoid_context_across_async and avoid_retaining_disposed_widgets now check the resolved type (when type information is available, e.g. in-editor or --resolve scans) instead of matching on the bare identifier/type name alone. Fixes false positives on non-Flutter classes that happen to be named context or Element (an analyzer Element, a custom Context type, 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 for avoid_datetime_constructor and avoid_datetime_constructor_unvalidated — recognizes the strip-time idiom DateTime(x.year, x.month, x.day) and the explicit-midnight-zeros variant DateTime(x.year, x.month, x.day, 0, 0, 0), replacing both with DateUtils.dateOnly(x). Appears above the existing DateTime.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.
  • @cachedFuture annotation (package:saropa_lints/annotations.dart) — marks a method as returning a cached Future, suppressing pass_existing_future_to_future_builder without 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 the analyzer-13-migration branch. 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|light flag controls which rule lane the scanner uses. Defaults to full (every enabled rule); light restricts 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-stats prints 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-compat standalone audit cross-references the pubspec SDK lower bound against Dart syntax features in lib/. 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-ignores flag 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 json for CI integration. Exits 1 if any stale ignores found, 0 if clean. No action required.
  • Scan CLI: --fix-stale-ignores flag 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 a default: catch-all is the correct design choice. Also upgraded from string heuristic to proper EnumElement resolution when type information is available. No action required.
  • avoid_stream_in_build (v3) now also detects StreamBuilder(stream: method()) where a method invocation creates a new subscription on every rebuild. Previously only caught StreamController() instantiation inside build(). Excludes safe constructors (Stream.value(), Stream.empty()) and the ??= caching idiom. A new quick fix converts a simple StatelessWidget flagged this way into a StatefulWidget with the stream cached in initState(). 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() and isInsideBuildMethod() utilities into target_matcher_utils.dart — used by avoid_stream_in_build and avoid_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) and front_end compiler exceptions during test compilation. The test step now auto-tunes concurrency by probing a single test at increasing -j levels (4, 6, 8, 10, 12), caching the result in build/.dart_test_max_j; crash retries halve concurrency automatically, the failure prompt offers [F]ewer workers to halve manually, and set SAROPA_TEST_MAX_J=N to skip the probe entirely.
  • Publish script: fixed test temp dir location — kernel-cache .dill files were written inside the project tree (build/test_tmp/), causing uri_does_not_exist scan errors and filling the C: drive. Temp dir now defaults to <system_temp>/saropa_dart_test outside the project tree; set SAROPA_TEST_TMP to override (validated: falls back if inside project tree).
  • Removed plans/known_issues_review.md from git tracking (generated file, regenerated each publish run).
  • New dart run saropa_lints:memory_report command — summarizes the analysis server's RSS trend from plugin.log for 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 when plugin.log was 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's PLAN_* convention. Added scripts/check_plan_naming.py — an informational, non-blocking report of plans/*.md files that don't follow the PLAN_<name>.md naming convention.
  • Publish script: --dry-run CLI flag — runs dependency resolution, audit, format, analysis, tests, and dart pub publish --dry-run with 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 appliesToMaxVersion or replacementObsoleteFromVersion to avoid false-positive flagging of version-scoped entries that are correct by design.
  • require_sdk_syntax_match quick fix: removed dead Map<Type, String> lookup (analyzer concrete types are private *Impl classes that never matched abstract keys); hardened regex with triple-quoted raw string to handle embedded quotes.
  • bugs/BUG_REPORT_GUIDE.md renamed to bugs/ISSUE_REPORT_GUIDE.md and 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.full explicitly instead of relying on the constructor default.
  • Scan CLI warns when --lane light is 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 shared roundToDecimalPlaces helper instead of parsing a self-produced string; a regex-guaranteed-digits int.parse triple 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 gained assert_single_unreleased_section() as a belt-and-suspenders gate.
  • Publish script: live test progress — dart test output 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 to reports/ 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 at plans/PLAN_full_audit.md.
  • i18n translation pipeline: deferred Qwen/Ollama provisioning — extension/scripts/i18n/generate_locales.py and mt_fallback.py previously 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 marked tags: ['slow'] — the (process) groups shell out to real dart run saropa_lints:scan subprocesses (including full essential-tier scans of the project itself) and were timing out at 2 minutes each under full-suite -j contention, failing the fast publish pass. Only the process-spawning groups are tagged; the ~250 in-memory parseScanArgs unit tests in the same file remain in the fast pass.
  • Publish script: fixed dart fix audit crash on Windows — the two dart fix subprocess calls in the pre-publish audit were the only dart invocations in the publish modules missing shell mode, so they crashed with WinError 2 on Windows where dart resolves to a .bat wrapper that CreateProcess cannot 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 the slow tag 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-on group can still exceed even 5 minutes under contention — the durable fix (a precompiled scan snapshot) is tracked separately.