Skip to content

Release v15.0.0

Choose a tag to compare

@saropa saropa released this 15 Aug 16:28
· 77 commits to main since this release

Version 15.0.0 adds new quick fixes for error logging and variable placement while introducing a persistent background daemon for significantly faster IDE save-scans. This release resolves false positives across exception handling, lifecycle timers, static method detection, and platform target checks. Project tier management is now unified directly through project configuration, reducing default editor memory overhead. log

Added

  • require_error_logging now offers a quick fix: applying it inserts a debugPrint call logging the caught error (interpolating the captured exception variable when one exists, or naming the statically-known exception type when it does not) instead of only reporting the missing log call.
  • move_variable_closer_to_its_usage now offers a quick fix: applying it moves the flagged declaration down to just before its first use. The fix only activates when doing so is provably safe (a single-variable declaration whose initializer shares no identifier with any statement it would move past) and otherwise leaves the diagnostic for manual review, so no action is required beyond reviewing the proposed edit before applying it.

Fixed

  • avoid_catching_generic_exception no longer flags on Object/on Exception/dynamic catch clauses whose body forwards the caught error to a logging or crash-reporting call (or rethrows it) before falling back — this is a deliberate pattern for also catching Error subtypes and reporting them, not a swallowed exception. Untyped catch (e) is unaffected. (plans/history/2026.08/2026.08.15/avoid_catching_generic_exception_false_positive_logged_broad_catch.md)
  • require_error_boundary no longer flags a MaterialApp/CupertinoApp built inside main()'s own catch clause when that clause already logged the caught error and its try body attempted runApp(...) — that's the app's crash-recovery fallback screen, not its normal entry point, and demanding it also carry an error-boundary builder: is recursive. The same shape outside main(), without logging, or without an runApp attempt in the try body still requires a builder: as before. (plans/history/2026.08/2026.08.15/require_error_boundary_false_positive_fallback_ui_inside_catch.md)
  • require_error_logging no longer flags a catch/on Type clause with no captured exception variable if its body still calls a recognized logging function — a static message like on TimeoutException { debug('timed out'); } is a complete log entry even without touching the exception object. A clause with no captured variable and no logging call is still flagged, as before. (plans/history/2026.08/2026.08.15/require_error_logging_false_positive_unparamed_catch_with_logged_body.md)
  • require_app_lifecycle_handling, avoid_work_in_paused_state, and require_lifecycle_observer no longer flag a Timer/Stream.periodic/.listen() subscription that is created and canceled/closed within the same State class's own initState/dispose() pair — that's Flutter's standard cleanup contract for a foreground-only ticker that doesn't need to pause on backgrounding, since it stops existing when the widget is disposed. A class whose dispose() does not cancel the field it created, or that assigns the Timer/subscription somewhere dispose() can't prove cleanup for, is still flagged, as before. (plans/history/2026.08/2026.08.15/require_app_lifecycle_handling_false_positive_dispose_cancels_timer.md)
  • require_ios_deployment_target_consistency no longer flags import 'dart:async' (or any other import/export URI) as Swift async/await usage — the rule now skips string literals inside import/export directives before checking them against its tracked iOS 15+ API names. A genuine API name appearing elsewhere in the file is still flagged, as before. The same import/export-URI substring-match false positive was also fixed in require_ios_live_activities_setup (triggered by import 'package:live_activities/...') and require_ios_certificate_pinning (triggered by package import paths containing segments like /auth). (plans/history/2026.08/2026.08.15/require_ios_deployment_target_consistency_false_positive_import_uri_misattribution.md)
  • prefer_static_method no longer flags methods that read instance fields or call instance methods via bare (unprefixed) identifiers — the idiomatic Dart style used throughout most codebases. Previously the rule only recognized an explicit this. prefix, so any method touching instance state through a bare identifier (including inside a nested closure) was misdiagnosed as "could be static." A method that truly uses no instance state anywhere is still flagged, as before. (plans/history/2026.08/2026.08.15/prefer_static_method_false_positive_implicit_field_access.md)
  • move_variable_closer_to_its_usage no longer flags a deliberate "load N values, then consume all N in the same order" batch shape (e.g. five sequential await-loads followed by five field assignments) — a sibling declaration in the same contiguous run that is itself genuinely used elsewhere, or the first-use site of another such sibling, no longer counts toward the "unrelated intervening statements" distance. A genuinely far-apart single declaration, declarations used out of matching order, or unused padding declarations sitting next to a real one, are all still flagged, as before. (plans/history/2026.08/2026.08.15/move_variable_closer_to_its_usage_false_positive_batch_declaration_grouping.md)
  • require_firebase_app_check_production and require_firebase_app_check no longer flag Firebase.initializeApp() when FirebaseAppCheck/AppCheck activation is deferred to a separate, actually-called function elsewhere in the same file — a common pattern for keeping a slow/flaky Play Integrity check off the startup path. A file where App Check is only mentioned in a comment, or where the activating function exists but is never called from anywhere, is still flagged, as before. (plans/history/2026.08/2026.08.15/require_firebase_app_check_production_false_positive_activation_in_separate_function.md)
  • require_log_level_for_production no longer flags a bare verbose-log call (e.g. debug(...)) when the called function's own log-level parameter (level, logLevel, severity, or verbosity) already defaults to a safe value — demanding an explicit level: argument in that case would be a no-op. A callee whose default is itself verbose, unrecognized (numeric or constructor-call), or unresolvable, is still flagged, as before. (plans/history/2026.08/2026.08.15/require_log_level_for_production_false_positive_default_level_param.md)

Changed (Extension)

  • Saving a Dart file now scans it in an external process and shows findings as squiggles and Problems panel entries — no separate setting to find or enable, this is what saropaLints.enabled now does. Turning that toggle off stops save scans and shuts the scanner down immediately, rather than leaving stale findings in the Problems panel. saropaLints.scanOnSave.resolveTypes (default on) controls whether scans fully resolve types so type-based rules fire; turn it off only if save latency matters more than catching those rules.
  • Type-resolved save scans run through a persistent scan_daemon process that builds the analyzer's project context once and keeps it warm, so a save is checked in a few seconds instead of re-paying a roughly one-minute analyzer warmup on every save. The status bar shows a warming message while the first scan after opening is still resolving; the daemon restarts automatically (with backoff) if it stops. Measured memory is comparable to the in-process analyzer plugin — the daemon's advantage is living outside the editor's own process, not a smaller footprint.
  • New projects (dart run saropa_lints:init or the extension's Enable) no longer get a live in-process analyzer plugin — the plugins: block is written commented out by default, since it can hold several GB of resolved analysis state on large projects for no benefit over the scan-on-save daemon above. A project that already had the plugin running, or had it explicitly turned off, keeps that state through tier changes and re-enabling; uncomment the block in analysis_options.yaml to opt back in to live in-editor squiggles, or run the new "Saropa Lints: Re-enable In-Process Plugin" command to do it in one step (it also restarts the Dart analysis server so the plugin reloads immediately).
  • New command "Saropa Lints: Scan Whole Project for Issues" runs a cancelable whole-project scan so files you haven't saved this session still show up in the Problems panel — save-triggered scanning alone only checks a file once you save it. It streams results in chunks as it goes and can be canceled mid-scan from the progress notification; run it from the Command Palette when you want full coverage, not automatically on open (a full pass on a large project can take tens of minutes).
  • A save-triggered scan of a single file no longer prints a misleading progress bar estimating its position against the whole project (e.g. "Files: 1/4477, ETA: 2h"). That estimate now only appears during the long-lived in-editor plugin session it was designed for; one-shot scans (save-triggered daemon, scan CLI) show a plain file count instead.
  • analysis_options.yaml is now the single source of truth for a project's lint tier. Save-triggered scans, the whole-project baseline scan, and the tier picker's "current tier" display now read the tier straight from analysis_options.yaml instead of trusting the (possibly stale) saropaLints.tier setting, so a hand-edited or regenerated config file can no longer silently disagree with what the extension shows or scans with. SAROPA_TIER remains available as a dev-only override but now logs a warning when it disagrees with the project's own config; saropa_tier: in analysis_options_custom.yaml is deprecated in favor of analysis_options.yaml.
Maintenance
  • Investigated a no_magic_string false-positive report (string literal inside a //-commented-out debugPrint call) and confirmed by code inspection it cannot occur — the rule and all its gating helpers are AST-callback-only, with no raw-text scanning. Added a resolved-analyzer regression test pinning this behavior. (bugs/no_magic_string_false_positive_commented_out_code.md)
  • Manually corrected seven German and Swahili extension strings that had shipped corrupted machine-translation output — a mangled literal --resolve CLI flag, two entries collapsed into a repetition loop (one leaking a fragment resembling a stray prompt artifact), and grammatically broken fallback text — and added each as a curated dictionaries.py override so a future translation run can never regenerate the same corruption from cache.