saropa_lints 15.0.2
saropa_lints: ^15.0.2 copied to clipboard
2134 custom lint rules with 254 quick fixes for Flutter and Dart. Static analysis for security, accessibility, and performance.
Changelog #
....
-+shdmNMMMMNmdhs+-
-odMMMNyo/-..``.++:+o+/-
/dMMMMMM/ `````
dMMMMMMMMNdhhhdddmmmNmmddhs+-
/MMMMMMMMMMMMMMMMMMMMMMMMMMMMMNh/
. :sdmNNNNMMMMMNNNMMMMMMMMMMMMMMMMm+
o ..~~~::~+==+~:/+sdNMMMMMMMMMMMo
m .+NMMMMMMMMMN
m+ :MMMMMMMMMm
/N: :MMMMMMMMM/
oNs. +NMMMMMMMMo
:dNy/. ./smMMMMMMMMm:
/dMNmhyso+++oosydNNMMMMMMMMMd/
.odMMMMMMMMMMMMMMMMMMMMdo-
-+shdNNMMMMNNdhs+-
``
Made by Saropa. All rights reserved.
Learn more at https://saropa.com, or mailto://dev.tools@saropa.com
2100+ custom lint rules with 250+ quick fixes for Flutter and Dart — static analysis for security, accessibility, performance, and library-specific patterns. Includes a VS Code extension with Package Vibrancy scoring.
Package — pub.dev/packages/saropa_lints
Releases — github.com/saropa/saropa_lints/releases
VS Code Marketplace — marketplace.visualstudio.com/items?itemName=saropa.saropa-lints
Open VSX Registry — open-vsx.org/extension/saropa/saropa-lints
15.0.2 #
This release focuses on improving the reliability and user experience of the extension's setup workflows. Progress notifications now provide real-time feedback during lengthy operations to clearly communicate the current status. Safeguards have also been introduced to prevent duplicate, conflicting tasks from executing concurrently if a command is triggered multiple times. log
Fixed (Extension) #
- The "Enabling Saropa Lints" progress notification stayed on a single static title for the entire
pub getstep, which can take over a minute on projects with many plugins — with nothing on screen to distinguish "still working" from "stuck," clicking Cancel (or clicking "Enable" again) mid-run was a reasonable reaction. The notification now shows which step is running and a live elapsed-time counter (e.g. "Running pub get… (45s)"). No action required. - Clicking "Enable" again while an enable flow was already running started a second, fully concurrent flow — both writing
pubspec.yaml/analysis_options.yamland shelling out topub get/write_configat the same time — instead of joining the one already in progress, which could stack duplicate progress notifications and race on the same files. A second call now joins the in-flight run instead of starting a new one. The same fix applies to "Create Baseline" (saropa_baseline.json), which had the same gap. No action required.
15.0.1 #
Version 15.0.1 improves the editor extension's responsiveness and resolves a file-parsing bug that prevented the plugin from re-enabling. The setup flow now executes asynchronously to prevent UI freezes, while deactivated lint configurations generate significantly smaller files by omitting unused inline documentation. log
Fixed (Extension) #
- A project whose
plugins:block is written commented-out (new projects, or one where "Turn Off Lint Integration" was used) no longer gets the full per-rule description dump on every regenerate — the disabled block now keeps only therule_name: true/falselines needed to restore the exact configured tier, dropping the multi-hundred-line prose and box-drawing headers that served no purpose while inert. A live (uncommented) block is unaffected and keeps its full inline documentation. No action required; re-rundart run saropa_lints:initor trigger a config write to see the smaller file. - "Enabling Saropa Lints" could appear to hang forever on larger projects — the enable flow ran
pub get, config write, and analysis synchronously, freezing the whole editor for as long as those took instead of just showing progress. The flow now runs them without blocking the UI and can be canceled from the progress notification. Canceling during the final analysis step also no longer silently reports "Enable" as successful — it now stops and logs the cancellation instead of turning the plugin on as if the flow had completed. No action required. - "Re-enable Plugin" could report "nothing to restore" on a project whose
analysis_options.yamlmixed CRLF and plain-LF line endings, even though the disabledplugins:block was plainly present — line detection now tolerates mixed endings instead of assuming one for the whole file. No action required.
15.0.0 #
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_loggingnow offers a quick fix: applying it inserts adebugPrintcall 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_usagenow 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_exceptionno longer flagson Object/on Exception/dynamiccatch 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 catchingErrorsubtypes and reporting them, not a swallowed exception. Untypedcatch (e)is unaffected. (plans/history/2026.08/2026.08.15/avoid_catching_generic_exception_false_positive_logged_broad_catch.md)require_error_boundaryno longer flags aMaterialApp/CupertinoAppbuilt insidemain()'s owncatchclause when that clause already logged the caught error and itstrybody attemptedrunApp(...)— that's the app's crash-recovery fallback screen, not its normal entry point, and demanding it also carry an error-boundarybuilder:is recursive. The same shape outsidemain(), without logging, or without anrunAppattempt in thetrybody still requires abuilder:as before. (plans/history/2026.08/2026.08.15/require_error_boundary_false_positive_fallback_ui_inside_catch.md)require_error_loggingno longer flags acatch/on Typeclause with no captured exception variable if its body still calls a recognized logging function — a static message likeon 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, andrequire_lifecycle_observerno longer flag aTimer/Stream.periodic/.listen()subscription that is created and canceled/closed within the sameStateclass's owninitState/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 whosedispose()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_consistencyno longer flagsimport 'dart:async'(or any other import/export URI) as Swiftasync/awaitusage — 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 inrequire_ios_live_activities_setup(triggered byimport 'package:live_activities/...') andrequire_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_methodno 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 explicitthis.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_usageno longer flags a deliberate "load N values, then consume all N in the same order" batch shape (e.g. five sequentialawait-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_productionandrequire_firebase_app_checkno longer flagFirebase.initializeApp()whenFirebaseAppCheck/AppCheckactivation 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_productionno longer flags a bare verbose-log call (e.g.debug(...)) when the called function's own log-level parameter (level,logLevel,severity, orverbosity) already defaults to a safe value — demanding an explicitlevel: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.enablednow 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_daemonprocess 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:initor the extension's Enable) no longer get a live in-process analyzer plugin — theplugins: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 inanalysis_options.yamlto 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,
scanCLI) show a plain file count instead. analysis_options.yamlis 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 fromanalysis_options.yamlinstead of trusting the (possibly stale)saropaLints.tiersetting, so a hand-edited or regenerated config file can no longer silently disagree with what the extension shows or scans with.SAROPA_TIERremains available as a dev-only override but now logs a warning when it disagrees with the project's own config;saropa_tier:inanalysis_options_custom.yamlis deprecated in favor ofanalysis_options.yaml.
Maintenance
- Investigated a
no_magic_stringfalse-positive report (string literal inside a//-commented-outdebugPrintcall) 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
--resolveCLI 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 curateddictionaries.pyoverride so a future translation run can never regenerate the same corruption from cache.
14.5.9 #
This release adds a rule catching a common button-labeling mistake: cramming extra detail into a button's main text using parentheses instead of the dedicated subtitle line. It also closes the last gap in the "Lint integration off" toggle: the analyzer plugin itself now refuses to enable any rules while the integration is disabled, so no fallback configuration can silently re-enable analysis and its multi-gigabyte memory footprint. log
Added #
- New rule
avoid_parenthesized_button_caption(Comprehensive tier): flagsCommonButton/CommonButtonWaitcalls where thetext:parameter contains parenthesized text that belongs insubtitleText:instead. No action required.
Fixed #
- The analyzer plugin now enables zero rules whenever "Lint integration" is toggled off, even if it gets loaded anyway — previously fallback configuration could re-enable over a thousand rules and hold several GB of analysis-server memory on a project the user had disabled. No action required.
14.5.8 #
Fixed the extension running lint analysis in the background while "Lint integration" was turned off. Disabling the integration now stops every automatic analysis trigger and background suggestion, not just the editor diagnostics. log
Fixed (Extension) #
- Turning off "Lint integration" now also stops analysis triggered by saving files, changing dependencies, changing tier, changing config, and enabling a rule pack, along with the crash-coverage rule suggestion. Previously only in-editor diagnostics were suppressed. No action required.
- Turning off "Lint integration" now restarts the Dart analysis server immediately, so the plugin's background process actually exits instead of continuing to run (and hold onto several GB of memory) until the next manual reload. No action required.
Changed (Extension) #
- The "Run Analysis" toolbar button is hidden while "Lint integration" is off, instead of appearing clickable and doing nothing useful. No action required.
14.5.7 #
Dependency maintenance release — no rule or extension changes. log
Maintenance
- Bumped
js-yaml(extension dev dependency, viamocha) from 4.3.0 to 4.3.1, resolving GHSA-5p4m-2wfm-xmqj.
14.5.6 #
This release introduces a new rule to ensure lint suppression comments work correctly in your IDE. The require_ignore_comment_plugin_prefix rule flags ignore comments referencing saropa_lints rules that lack the required package prefix, preventing suppressions from failing silently. An automated quick fix is included to instantly apply the missing prefix. log
Added #
- New rule
require_ignore_comment_plugin_prefix(Essential tier, WARNING) — flags// ignore: rule_nameand// ignore_for_file: rule_namecomments that reference a saropa_lints rule without the requiredsaropa_lints/prefix, which causes the suppression to silently fail in the IDE. A quick fix inserts the prefix. No action required. dart run saropa_lints scan --fix-ignores— bulk-converts bare// ignore: rule_nameto// ignore: saropa_lints/rule_namefor all known saropa_lints rules acrosslib/,test/, andbin/.
Fixed #
require_ignore_comment_plugin_prefix's quick fix could insert the prefix into the wrong// ignore:comment when another ignore comment sat nearby in the file. It now targets the exact flagged comment. No action required.--fix-ignoresskipped hyphenated rule names (e.g.avoid-null-assertion), leaving them unprefixed. It now converts them correctly. No action required.
14.5.5 #
The Analysis Optimizer now makes changes safely: it surgically updates only the patterns you're modifying while preserving your file structure, comments, and ordering, and backs up your configuration before every write for easy manual recovery. The dashboard excludes redundant recommendations when patterns are already covered and automatically rescans to keep the status current. log
Fixed (Extension) #
- Analysis Optimizer could silently destroy hand-curated
analysis_options.yamlstructure — every Apply/Remove/Fix Syntax action rebuilt the entireexclude:block from scratch, discarding section-header comments and blank-line grouping (which aren't attached to any single pattern) and re-sorting every entry alphabetically. Writes are now surgical: only the lines for patterns actually being added or removed are touched, and every other line — comments, spacing, order — is left exactly as it was. No action required. - Folder exclusion recommendations kept showing as "Recommended" even when already covered by a broader applied pattern (e.g. individual
dependency_overrides/<package>/**entries never matched as "Applied" despite adependency_overrides/**already excluding them). These redundant recommendations no longer appear. No action required. - The dashboard now automatically scans on open and rescans after every Apply/Remove/Fix Syntax, instead of requiring a manual "Scan Workspace" click to see current status. No action required.
Added (Extension) #
- Every Analysis Optimizer write now saves a one-step-back copy of
analysis_options.yamltoanalysis_options.yaml.bakfirst, so a change can always be manually reverted. No action required.
14.5.4 #
This release fixes the Analysis Optimizer's exclusion detection, which previously missed patterns already present in analysis_options.yaml and duplicated them on apply. The dashboard's two separate exclusion lists are now one sortable table with clearer status and impact indicators, plus a quick line preview before applying. log
Fixed (Extension) #
- Analysis Optimizer could write invalid YAML that broke Dart analysis entirely — an unquoted exclude pattern starting with
**(routine for Dart globs) is YAML alias syntax, not a literal string, and caused a realUndefined aliasparse error the moment the analyzer read the file. Every written pattern is now quoted, and previously-malformed unquoted entries are automatically re-quoted the next time any change is applied through the dashboard. No action required. - Analysis Optimizer failed to detect existing exclusions — patterns with an inline
# commentor a stray trailing quote (- **/*.g.dart" # ...) were never recognized as already excluded, so the dashboard kept recommending them and applying created a duplicate line. The reader now strips comments and malformed quoting before comparing, and the writer preserves each pattern's original comment on write. No action required. - Analysis Optimizer's "Current exclusions" and "Recommended exclusions" are now one deduplicated "Exclusions" table, with an Applied/Recommended status column, sortable columns, and the chosen sort order preserved across Apply/Remove actions. No action required.
- An already-applied Analysis Optimizer exclusion that matches zero scanned Dart files (e.g. a non-Dart tool reference) now shows a dash and an explanatory reason instead of a misleading "0". No action required.
Added (Extension) #
- Each Analysis Optimizer recommendation now has a "Preview" toggle showing the approximate line that would be added to
analysis_options.yaml, without leaving the table. No action required. - Analysis Optimizer now proactively warns when
analysis_options.yamlalready has invalid exclude syntax and offers a one-click "Fix Syntax" that re-quotes every entry, so a broken file can be repaired without needing to apply or remove a specific pattern first. No action required.
14.5.3 #
This release fixes a test-suite timeout in the health-history archival path and completes Filipino and Dutch translation coverage across the extension UI. No action required. log
Fixed #
loadHealthHistorytest timeout — complexity parsing every Dart file across archived tags exceeded the 2-minute test budget. The function now accepts an optionalwithComplexityparameter (defaultstrue; test passesfalse).
Maintenance
loadHealthHistorynow caches each tag's computedHistoryPointon disk (.dart_tool/saropa_lints/health_history_cache.json), keyed by the tag's resolved commit SHA. Repeat calls against unchanged tags skip re-archiving and re-scanning entirely.- Fill
fil/nlextension i18n coverage gaps forDefault,Pattern,Medium, andOpen analysis_options.yaml.Patternis kept as the English loanword already used in the sibling{count} file pattern(s)string;Open analysis_options.yamluses verb-final Dutch order to match the existingpubspec.yaml openensibling.
14.5.2 #
This release introduces the Analysis Optimizer to help developers proactively manage their Dart analyzer's resource footprint. The extension now identifies memory-intensive files and provides an interactive dashboard for safely previewing and applying workspace exclusion patterns. By intelligently filtering out generated code and high-cost directories, users can easily maintain editor performance and swiftly resolve critical memory warnings. log
Added (Extension) #
- Analysis Optimizer — new dashboard (sidebar, command palette, or memory warning toast) that scans the workspace, identifies high-cost files and folders, and recommends
analyzer: exclude:patterns to reduce Dart analyzer memory usage. Applying a pattern opens a diff preview of the resultinganalysis_options.yamlbefore writing; multi-pattern applies require confirmation. Generated code patterns (*.g.dart,*.freezed.dart, etc.) are recommended by default. No action required. - The critical-memory toast now includes an "Optimize Analysis" button alongside "Clean Up" to surface the optimizer when the analyzer is consuming excessive memory.
14.5.1 #
This release introduces a new balanced memory mode to drastically reduce RAM consumption during incremental analysis, alongside a Full Opportunities Report designed specifically for AI-driven dependency reviews. It also refines localization workflows by eliminating false-positive translation warnings on placeholder-only templates. Developers will experience a significantly lighter background footprint on large projects and gain deeper, exportable insights into their codebase's dependency utilization. log
Added #
- Full Opportunities Report — a new export (sidebar, or
Saropa Lints: Export Full Opportunities Report) that consolidates every dependency and every changelog feature into one HTML, Markdown, and JSON report underreports/. Unlike the Upgrade Opportunities panel, it keeps fully-adopted packages and every changelog category, and counts each feature's usage from zero upward with the exact project file and line of every reference. Built to hand to an AI for a dependency-usage review. - Balanced memory mode — new
memory_mode: balancedsetting (default) that skips type-heavy rules on unchanged files during incremental analysis, reducing CPU work on re-analysis passes. When a dependency changes, all transitive importers are automatically re-analyzed via import-graph invalidation. Setmemory_mode: fullinanalysis_options_custom.yamlorSAROPA_MEMORY_MODE=fullto restore previous behavior. No action required.
Maintenance
- Moved
.vsixoutput fromextension/to the project root for easier access after packaging. - Translation skip logic now recognizes placeholder-only templates (
{category} ({count})) as untranslatable, eliminating 48 false-positive missing-translation reports. - Pinned the opportunities report's symbol matcher against the implementation it replaced with a differential test, which found that the previous matcher silently never counted
$-prefixed identifiers. - Added a headless-DOM test harness (
jsdom) that executes the opportunities report's inline script, so its filter, mode toggles, expand/collapse, and column sort are verified to work rather than merely to be present. - Marked 68 rule files as type-resolution-heavy (
usesTypeResolution) to support balanced memory mode filtering.
14.5.0 #
This release introduces comprehensive system health monitoring to track memory usage and safely terminate orphaned background processes. It also resolves severe memory retention issues during analysis of large codebases and refines localization workflows by preventing false-positive translation warnings. Developers will experience a significantly more stable and responsive environment with highly accurate diagnostic results during extended coding sessions. log
Added #
- System health monitor in the VS Code extension: polls Dart/Flutter process memory and orphaned daemon count every 60 seconds (Windows). Status bar shows a warning or critical suffix when memory exceeds configurable thresholds or orphaned daemons accumulate. One-click "Kill Orphaned Flutter Daemons" command re-queries live processes before killing, avoiding stale-PID risks. All thresholds configurable via extension settings under "System Health".
- Process Health panel (Command Palette → "Process Health"): live table of all Dart/Flutter processes with PID, parent, RSS, type classification (process/daemon/orphan), and per-process kill buttons for orphaned daemons.
Fixed #
- Register 25+ internal caches for eviction under memory pressure — previously only 10 of ~70 were managed, causing unbounded memory growth on large projects (7.8 GB observed on a 3,900-file codebase). No action required.
- Fix memory estimator to measure actual per-file cache sizes instead of flat approximations that understated real usage by ~25×. No action required.
- Cap the per-file passed-rules cache with LRU eviction (default 500 files) — the single largest memory consumer at O(files × rules), previously unbounded. No action required.
- Cap the per-file diff cache with LRU eviction (default 250 files) — retained full source text of every analyzed file (~19.5 MB on a 3,900-file project), now bounded. No action required.
- Release per-file tracking maps after the analysis summary is reported — previously retained indefinitely, wasting memory for the rest of the session. No action required.
- Fix VS Code integrated terminal color detection on Windows — ANSI escape sequences now render correctly when
TERM_PROGRAMis set. No action required.
Maintenance
- Add infrastructure bug report for orphaned
flutter daemonprocesses accumulating on Windows and exhausting system RAM. Includes hardened cleanup scripts with PID-reuse detection, a scheduled task to break the OOM feedback loop, and a Win32 Job Object permanent fix concept. - Add
PID,RSS,Daemonto MT do-not-translate list and expand skip logic for emoji+placeholder patterns (⚠ {size},🔴 {size}), resolving 71 false-positive missing-translation entries across 24 locales. No action required.
14.4.3 #
Resolves a runtime error in the lint diagnostic reporter that could prevent ignore-comments and deduplication checks from functioning correctly. This release also hardens internal code quality with broad static analysis improvements and introduces new automated CI gates to prevent future regressions. log
Fixed #
- Fix undefined
ruleContextreference inSaropaLintRule.registerNodeProcessors— the diagnostic reporter was receiving an unresolved identifier instead of the method'sRuleContextparameter, which could cause ignore-comment and dedup checks to fail at runtime. No action required.
Maintenance
- Resolve
unnecessary_string_interpolations,unnecessary_string_escapes, andprefer_adjacent_string_concatenationlint issues across lib/ to future-proof against pana baseline upgrades topackage:lints/recommended.yaml. No action required. - Resolve 209 dart analyzer lint issues across lib/ and test/: nullable final variables, string interpolation style, dangling library doc comments, unnecessary
this/late, missing@override,prefer_contains,use_super_parameters,prefer_collection_literals, and parameter naming alignment with base class signatures. - Remove dead field
_isProjectRootInitializedfromSaropaLintRule. - Add
scripts/check_dart_fix.py— CI gate that fails if fixable dart issues exist; hardened with multiple regex patterns and error handling for missingdartor timeout. - Integrate
dart fix --dry-run/--applyinto the publish pipeline as an auto-fix step before blocking checks. - Add
// ignore:suppressions for 5 unfixable recommended.yaml issues (implementation_imports, library_private_types_in_public_api, prefer_interpolation_to_compose_strings) with verified rationale comments. - Add
scripts/check_recommended_yaml.py— CI gate that temporarily enables recommended.yaml analysis and asserts zero unsuppressed issues; preserves original file bytes on restore, handles YAML document markers and missingdart. - Add
dart fixand recommended.yaml checks to pre-commit hook — regressions are now caught before push; dart availability is checked by each Python script (exit 2 = skip), not the shell.
14.4.2 #
14.4.1 #
Ignore: Published build error - mixed code/versions. Ignore.
14.4.0 #
Introduces a new lint rule to catch invalid date initializations that would otherwise silently roll over into incorrect dates. Developers are now guided toward strict parsing methods to make date handling safer across Dart and Flutter projects. log
Added #
avoid_datetime_constructor— flagsDateTime()andDateTime.utc()constructors, which silently roll over out-of-range values (e.g. month 13 becomes January of the next year). All-literal in-range calls are allowed. Quick fix available: replace withDateTime.tryParse(). No action required.avoid_datetime_constructor_unvalidated— flagsDateTime()calls whose result is consumed directly (returned, passed as argument, used in field initializer) without being assigned to a local variable where components can be validated. No action required.
14.3.13 #
Fix false positive in avoid_bluetooth_scan_without_timeout — the rule no longer fires on non-Bluetooth scan() calls. log
Fixed #
avoid_bluetooth_scan_without_timeoutno longer flagsscan().listen()on non-Bluetooth receivers (e.g. dedup scanners, port scanners). No action required.require_bluetooth_state_checknow recognizes additional Bluetooth package types (flutter_reactive_ble,bluetooth_low_energy,quick_blue,universal_ble). No action required.avoid_bluetooth_scan_without_timeoutskips files without scan-related strings viarequiredPatternspre-filter, reducing unnecessary AST traversal. No action required.
14.3.12 #
Re-release of v14.3.10 with a build fix — no rule or extension changes. log
Maintenance
- Fix test compilation error that blocked the v14.3.10 publish pipeline. No action required.
- Extract shared
parseMethodBodytest helper and add CI guard againstchildEntitiesusage on class-like declarations. No action required.
14.3.11 #
Skipped: Internal build only.
14.3.10 #
Resolves false positives across matrix scaling operations and resource disposal lints. Uniform scaling factors in matrix transformations are no longer incorrectly flagged as duplicate arguments, and cleanup rules now properly recognize cascade syntax when disposing of controllers, streams, and timers. log
Fixed #
- Fix:
no_equal_argumentsfalse positive on Matrix4 uniform scaling —scaleByDouble(s, s, 1, 1),scale(s, s, 1), anddiagonal3Values(s, s, 1)no longer flag the repeated factor as a copy-paste error. Thescaleexemption is receiver-type-guarded to Matrix4 only, somyWidget.scale(x, x)still fires. - Fix: disposal rules false positive on cascade syntax — all disposal/cleanup rules (
require_text_editing_controller_dispose,require_page_controller_dispose, stream/timer cancel rules, etc.) now recognize_field..dispose()and_field..close()cascade expressions as valid cleanup. Previously only_field.dispose()and_field?.dispose()were matched.
Maintenance
- Fix cascade cleanup test helper to use
ClassDeclaration.body.membersinstead ofchildEntities, which stopped exposingMethodDeclarationin analyzer 12.1.0.
14.3.9 #
Two new comprehensive rules help monitor native bridge performance by requiring the @MethodChannelInstrumented annotation on channel classes and ensuring those calls are wrapped in timing helpers like noteIfSlow. log
Added #
- New rule:
require_method_channel_instrumented— flags classes that callMethodChannel.invokeMethod/invokeListMethod/invokeMapMethodwithout a@MethodChannelInstrumentedannotation, one diagnostic per class. Quick fix inserts the annotation. Comprehensive tier. - New rule:
prefer_method_channel_note_if_slow— flags bare invoke-method calls inside@MethodChannelInstrumentedclasses that are not wrapped innoteIfSlowor an equivalent timing helper. Comprehensive tier.
Maintenance
- Fix: rule packs UI in self-package — the Config Dashboard's "Enable all" and individual pack toggles produced misleading toasts ("already enabled" / "could not write") when the workspace is the saropa_lints package itself. The extension now detects the self-package via
name: saropa_lintsin pubspec, treats the implicit plugin load as configured, and creates aplugins: saropa_lints:block when no anchor exists forrule_packswrites. - CI: full clone for test job — the
health_history_testneeds git tags; shallow CI clones lacked them. Changed tofetch-depth: 0(full clone) so tags and history are always available. - Security: fix 3 Dependabot alerts — upgraded
shell-quote1.8.4 → 1.10.0 (quadratic DoS inparse()), replaced abandonednpm-run-allwith maintainednpm-run-all2@8, and overrodebrace-expansionto patched versions (exponential DoS). All dev-only dependencies. - Dependabot: grouped weekly schedule — added
.github/dependabot.ymlto batch all extension npm security updates into a single weekly PR (Mondays) instead of one PR per alert. - i18n engine: NLLB → Qwen — the extension's machine-translation pipeline now uses Qwen 3 via local Ollama as the primary engine, with Google Translate as the per-string fallback. NLLB is deprecated; existing NLLB-provenance translations are treated as low-quality and re-translated on the next
--mode upgraderun. No user action required. - i18n: LLM control-token rejection — the translation cache validator now rejects cached strings containing leaked LLM control tokens (
/no_think,<|endoftext|>,[INST], etc.). Contaminated entries auto-heal on the next translation run. GPU detection is deferred to first use so importing the engine no longer runsnvidia-smi.
14.3.8 #
Fixes an issue where the analysis server repeatedly restarted the plugin isolate, causing IDE diagnostic results to clear continuously. Automatically excludes common non-Dart output directories during initialization to prevent file-watcher feedback loops. Adds restart-rate telemetry, log rotation, and a configurable log_level setting to control plugin log verbosity. log
Fixed #
- Plugin isolate restart storm — the analysis server respawned the plugin isolate hundreds of times per day (13,660 over 91 days on the
contactsproject), clearing all diagnostics from the Problems tab each time. Two causes addressed: (1)Plugin.start()now skips config loading when the working directory is not a Dart project (e.g. the VS Code install directory), eliminating the 0-rules phase and noisy log entries; (2)PluginLogger.setProjectRoot()now validates that the root containspubspec.yamlbefore writing log files, preventing log writes into non-project directories that could trigger file-watcher restarts. - Init command: non-Dart directories now excluded from analyzer —
dart run saropa_lints:initand the headless config writer now ensure common non-Dart directories (reports/**,docs/**,bugs/**,plans/**,doc/**,output/**,tmp/**) are in theanalyzer > excludelist. Without this, plugin log writes toreports/.saropa_lints/could trigger the analysis server's file watcher and restart the plugin isolate in a feedback loop. - Plugin logger: restart-rate telemetry — after each isolate spawn,
PluginLoggercounts recent "session started" entries in the log file. When the rate exceeds 10 restarts in 10 minutes, aWARNINGline is emitted with remediation advice. The log file itself is the durable counter since statics reset per isolate. - Init command: flow-style YAML guard —
ensureNonDartExcludesnow detects flow-styleexclude: [...]under theanalyzer:section and leaves it unchanged instead of inserting a duplicateexclude:key. Trailing comments afterexclude:are also handled correctly. - Plugin logger: log rotation —
plugin.logis now capped at 512 KB; oldest content is discarded at each isolate start, bounding the cost of the restart-rate telemetry read and preventing unbounded disk growth. No action required.
Added #
- Plugin logger: configurable log level — new
log_level:key underplugins > saropa_lintsinanalysis_options.yamlcontrols which messages are written toplugin.log. Valid values:off,error,warning,info(default),debug. Messages below the configured level are still sent to the analysis server's developer log but skip the user-visible file. The init command writeslog_level: infoby default. - Plugin logger: convenience API —
PluginLogger.debug(),.warning(), and.error()replace thelevel:named parameter pattern, making log call sites more concise. Unrecognizedlog_levelvalues now emit a warning instead of silently falling back toinfo. Tab-indented configs are now parsed correctly.
14.3.7 #
Updates the Dio linting behavior to favor dependency injection and factory patterns over static singletons. The updated rule flags top-level and static Dio declarations while permitting instantiation inside methods, constructors, and callbacks, resolving an architectural contradiction with anti-singleton guidelines. log
Changed #
- Breaking: Renamed
require_dio_singletontorequire_dio_factory— the rule now flagsDio()in static fields and top-level variables (the singleton anti-pattern) instead of recommending them.Dio()inside methods, constructors, closures, and DI callbacks is allowed. Resolves the architectural contradiction withavoid_singleton_pattern(#274). No action required if already using factory/DI patterns. require_dio_factoryconfig alias: Projects usingrequire_dio_singletoninanalysis_options.yamlcontinue working viaconfigAliases— no config migration required on upgrade.
Added #
- Hardened
require_dio_factorydetection: Added coverage forlate static final Diofields, static getters, nested closures, and mixin method bodies. No action required.
Maintenance
- Closed Dependabot PR #271 bug (js-yaml 4.1.1 → 4.3.0): lock file already resolves to 4.3.0 via mocha; archived as fixed.
- Publish audit now detects dangling
bugs/*.mdreferences in active documents (skips frozenplans/history/).
14.3.6 #
Removes the avoid_debug_print rule, which contradicted the existing prefer_debug_print and left no valid console output path. Also fixes false positives in avoid_redundant_null_check and avoid_redundant_await when types are nullable or resolve across package boundaries. A new --debug-rule flag on the scan CLI traces type resolution for any named rule, making it easier to diagnose false positives.
log
Removed #
avoid_debug_printrule deleted. The rule contradictedprefer_debug_print— one said "use debugPrint," the other said "don't" — leaving no valid console output function for projects without a custom logging wrapper.prefer_debug_printremains and covers theprint()→debugPrint()upgrade path. No action required unless your config explicitly enabledavoid_debug_print; if so, remove the entry.CommentOutDebugPrintFixquick fix deleted (was the only fix for the removed rule). No action required.
Added #
--debug-rule <name>flag for the scan CLI. Emits per-node type-resolution trace output (staticType, staticInvokeType, returnType) for the named rule during a scan. Use with--resolvefor full type information. Designed for diagnosing false positives caused by type-resolution divergence in the analyzer plugin context. No action required.
Fixed #
avoid_redundant_null_checkno longer fires on variables, parameters, fields, or getters declared with a nullable type (Type?). The rule cross-checks the element's declared type against the resolvedstaticTypeand guards againstInvalidTypefrom failed type resolution, preventing false positives in cross-package contexts.avoid_redundant_awaitno longer fires onawaitof static methods returningFuture<T>. The rule now guards againstInvalidType(unresolvable types) and falls back to checking the invoked method signature's return type viastaticInvokeTypewhenstaticTypefails to resolve for cross-file static invocations.
14.3.5 #
This update improves the precision of our accessibility lints by isolating Flutter UI components from lower-level graphics classes. Projects utilizing external image processing libraries alongside Flutter will no longer experience irrelevant warnings. log
Added #
isFlutterWidgetNamed(Element?, String)shared utility for verifying a resolved element is a Flutter SDK widget by name and library origin, withTypeAliasElementunwrapping.
Fixed #
require_image_semantics,require_image_description,require_accessible_imagesno longer fire on non-Flutter classes namedImage(e.g.package:image's pixel-bufferImageordart:ui'sImage). All three rules now verify the declaring library ispackage:flutter/before reporting, withTypeAliasElementunwrapping for typedef'd widget references.
14.3.4 #
Adds a cross-tool data channel so sibling Saropa Suite tools can pull this project's daily health snapshot, adds a validated fresh_code risk flag to the Code Health report, and revives a batch of lint rules that never fired for anyone: seven that were missing their most common bad-code shape, and fourteen whole-file rules (desktop, BLoC, Riverpod, iOS, testing, navigation, i18n, and animation checks) that reported through an end-of-file step the analysis engine ignored. Also fixes a broken age signal that scored every function as maximally stale. No action required — the API is opt-in and the new flag and fixes take effect automatically. log
Added #
fresh_codeflag in the Code Health (vibrancy) report. Functions with cyclomatic complexity above 10 whose body was written or rewritten within the last 90 days are now flagged, because validation against real bug-fix history showed recently rewritten complex code causes incidents far more often than old code. No action required — the flag appears in the CLI report and as a filterable pill in the extension's Code Health view.- (Extension)
getDailySummary(date)on the extension's public API. Sibling Saropa Suite tools can now read this project's current health score, violation counts, and error-level trouble items for a given day viagetExtension('saropa.saropa-lints').exports.getDailySummary('YYYY-MM-DD'), which resolves to a documentedDailySummary(orundefinedbefore any analysis has run). No action required — the summary is built lazily on call, reads only local analysis output, and transmits nothing.
Fixed #
prefer_notifier_over_statefalse positives eliminated. The rule matchedStateProviderby scanning serialized source text, which could match unrelated identifiers containing that substring; it now checks the constructor/invocation name directly via the AST. TheMethodInvocationbranch is restricted to the known Riverpod factory methods (autoDispose,family) to prevent false positives from unrelated static methods. A fixture pins all three detection branches and a false-positive decoy. No action required.- Code Health age scores were stuck at zero. A broken decay formula scored every function with git history as maximally stale, so the age component contributed nothing to health rankings; ages now decay correctly from 100 (touched today) toward 0 over years. Overall scores rise slightly on recently maintained code — no action required.
prefer_list_containsnow flagsindexOf(x) != -1. The rule only recognized a bare0or-1on the right of the comparison, but-1is written as a negation, not a plain number, so the most common presence check —list.indexOf(x) != -1— was never flagged. It now is. No action required.avoid_map_keys_containsnow flagsmap.keys.contains(k)on a plain variable. The rule previously matched only chained receivers (likethis.map.keys.contains(k)) and missed the ordinarymap.keys.contains(k)on a simple map variable — the usual shape. Its quick fix (map.containsKey(k)) now applies to those cases too. No action required.avoid_unnecessary_collectionsnow flagsList.of([...])/Set.of(...)/Map.of(...). The rule missed these wrapped-literal constructors during full analysis because they are constructor calls, which analysis represents differently from the method-call shape the rule looked for. Both shapes are now flagged. No action required.prefer_asmap_over_indexed_iterationnow flagsfor (i = 0; i < list.length; i++). The rule required the loop bound to be a chained property read and missed the ordinarylist.lengthon a plain list variable — the usual shape — so it effectively never fired. It now does. No action required.require_key_for_collectionnow flagsListView.builder/GridView.builderduring full analysis. These are constructor calls, which full analysis represents differently from the method-call shape the rule looked for, so keyless items in the most common list builders went unflagged; only a few less-common widgets were caught. All shapes are now flagged. No action required.prefer_commenting_future_delayednow works during full analysis and stops flagging already-commented delays.Future.delayedis a constructor call (represented differently from a method call during full analysis), so the rule never fired for anyone; and it looked for the explanatory comment on the wrong token, so anawait Future.delayed(...)with a comment above it was treated as uncommented. Both are fixed: the rule fires on uncommented delays and stays quiet when a comment precedes the statement. No action required.avoid_sequential_awaitsnow fires. The rule registered for a callback the analysis engine silently ignores, so three or more independent sequential awaits (which could run together withFuture.wait) were never flagged for anyone. It now registers correctly and reports. No action required.- Four more rules that never fired now work:
prefer_single_exit_point,prefer_guard_clauses,require_getit_registration_order, andrequire_hive_adapter_registration_order. All registered through the same ignored callback asavoid_sequential_awaits, so none produced a diagnostic for anyone. All four now register correctly and report. No action required. pass_correct_accepted_typenow fires, andprefer_correct_identifier_lengthnow checks parameter names. Both registered for a parameter callback the engine ignores:pass_correct_accepted_typenever fired at all, andprefer_correct_identifier_lengthonly checked variable names, silently skipping parameters. Both now register correctly. No action required.- Fourteen more whole-file rules that never fired now work. Each aggregated information across the whole file and then reported through an end-of-file callback the analysis engine silently ignores, so none produced a diagnostic for anyone. The revived rules are
require_menu_bar_for_desktop,require_window_close_confirmation,require_error_state,avoid_circular_provider_deps,prefer_notifier_over_state,require_apple_sign_in,require_error_case_tests,avoid_test_coupling,require_test_cleanup,prefer_test_variant,require_route_transition_consistency,prefer_shell_route_for_persistent_ui,require_intl_locale_initialization, andprefer_implicit_animations. All now scan the file in a single pass and report correctly;require_intl_locale_initializationalso stops missing usages that a duplicate registration had been discarding, andrequire_apple_sign_innow recognizes the standardGoogleSignIn().signIn()call shape (a constructor-call receiver) that its detection had been skipping. No action required.
Maintenance
- Fixed the rule-liveness report (
accuracy_report) so it exercises stylistic rules. No tier — not even pedantic — contains the stylistic rules, so the previous tier-scoped scan never enabled them and falsely reported stylistic rules with fixtures as silent; correcting it flipped 80 previously-false-silent rules to firing (the silent worklist dropped from 744 to 664). The report now defaults to all defined rules (--tier <name>narrows it), via a new optional explicit rule-set on the scan runner. - Repaired the collection and async rule fixtures so the liveness instrument exercises the rules that were correct but sitting on inadequate fixtures. Collection reached full coverage (all 27 rules fire). Async went from 13 silent to 4: eight fixtures made realistic (typed streams/futures, class-method context, matching heuristic identifiers, a real
WebSocketChannel). The four remaining are two rules whose fixtures resolve to zero diagnostics under the full-corpus scan (cause not yet isolated with per-file tooling) and twoexpect_lintmarkers naming rules that were never implemented. - Added an integrity test that fails the build if any rule calls one of the three no-op registration stubs (
addPostRunCallback,addFunctionBody,addFormalParameter), which silently discard their callback and were the root cause of the fourteen dead whole-file rules revived this release. The guard forces authors to the real registrations instead. - Repaired the liveness fixtures for the revived whole-file rules and added fixtures for three that had none (
require_error_state,avoid_circular_provider_deps,prefer_notifier_over_state). Because these rules judge the whole file, a fixture that placed a BAD and a GOOD example together let the GOOD example mask the BAD; the compliant examples were moved to sibling*_good.dartfiles, path-gated fixtures were relocated, and mock classes (GoogleSignIn,CupertinoPageRoute,FadeTransitionRoute) were added so constructor-based rules resolve. All fourteen are confirmed firing (six in the full corpus scan, eight in isolated scans — the eight hit the pre-existing full-corpus-scan measurement limitation with the crowded test-fixture directory, already noted for the async cluster). - Fixed the Code Health
unusedflag producing ~50% false positives on multi-package repos. Nestedpubspec.yamlfiles fragmented the analysis context, the resolved-usage pass silently degraded, and every@overridemethod andbin/-only-called function was flagged dead. The fix scopes the analysis context tolib/,test/,bin/(preventing fragmentation), includesbin/files in the usage set (so CLI delegates get real caller counts), and adds a syntactic@overridesafety net that protects polymorphic methods even if resolution degrades. No action required. - Split the Issues tree provider's ~220-line tree-item renderer into a sibling module so the provider class carries only its stateful filter/index logic. Behavior-identical; the tree-item tests pin the render output.
- Closed the oversized view-file breakdown plan and archived it to plan history — all ten tracked files are decomposed, and the two residual stateful controllers are accepted as cohesive final-state modules.
- Ran the flight-risk scoring research gate (predictive-score validation) and recorded a negative result: on a 16-incident corpus mined from this repo's fix history, the candidate composite formula lost to the complexity-alone baseline, so the feature stays unbuilt and its plan stays open with the findings and re-attempt conditions documented.
- Closed the sidebar-and-affordance inventory snapshot and archived it to plan history — every count had drifted from the manifest, and the one durable decision (the palette-only JSON-export tree providers are intentionally never registered as views) now lives as a comment at their construction site.
- Fixed the
loadHealthHistorytest so it asserts real behavior instead of silently passing on empty results. The test hadif (points.isEmpty) return, which meant a completely broken function still produced a green test. It now requires non-empty results (this repo has tags), assertscodeLoc > 0, thecodeLoc <= locinvariant, and distinct tags when two points are returned. - Added
HistoryPoint.toMarkdownRow(),HistoryPoint.markdownHeader, andHistoryPoint.toMarkdownTable()for rendering health trajectory as markdown tables. - Fixed the performance-rules fixture verification test: renamed
require_window_close_confirmation_desktop_fixture.dartto match the rule-name convention, and added 8 fixture files that existed on disk but were missing from the verification list. - Replaced the hardcoded fixture list in the performance test with a directory scan, so new fixture files are verified automatically without manual list maintenance. Also renamed the stale
require_window_close_confirmation_desktop_good.dartto drop the_desktopsuffix. - Converted all 126 remaining test files from hardcoded fixture lists to the same
Directory.listSync()auto-discovery pattern. Every fixture verification group now scans its directory on disk, so adding a fixture file is automatically tested — no manual list to maintain or drift out of sync. Theandroid_rules_testretains one explicit test for a cross-directory fixture (require_android_manifest_entriesinexample/lib/platform/). Two files (roadmap_15_rules_test,migration_rules_test) were excluded because their fixture groups contain content-validation tests beyond simple existence checks. - Extracted fixture auto-discovery into a shared
discoverFixtures()helper (test/helpers/fixture_discovery.dart) and migrated all 127 fixture-verification test files to use it. The helper returns an empty list when the directory is missing, so the guard test fails with a clear assertion instead of aFileSystemExceptionaborting the group. Removes ~7 lines of duplicatedlistSyncchain per file. - Added a fixture-vs-tiers integrity test (
test/integrity/fixture_integrity_test.dart) that cross-references every*_fixture.darton disk againstgetAllDefinedRules(). Catches stale or misspelled fixture files whose names don't match any registered rule. Group/category fixtures (covering multiple rules) are logged but not failed. Includes a regression floor at >2300 exact-match fixtures.
Historical Changelog Archive #
Looking for older changes? See CHANGELOG_ARCHIVE.md for older versions.